diff options
| author | Uri Shaked | 2020-04-28 23:42:20 +0300 |
|---|---|---|
| committer | Uri Shaked | 2020-04-28 23:42:20 +0300 |
| commit | f16d9f9f48fb5e74ab10fe79a8206223342d5010 (patch) | |
| tree | 0ae771f977ce51e1d34ab7f79cd6a31d918bfd12 /src/peripherals | |
| parent | chore: release 0.8.2 (diff) | |
| download | avr8js-f16d9f9f48fb5e74ab10fe79a8206223342d5010.tar.gz avr8js-f16d9f9f48fb5e74ab10fe79a8206223342d5010.tar.bz2 avr8js-f16d9f9f48fb5e74ab10fe79a8206223342d5010.zip | |
fix(timer): incorrect high counter byte behavior
According to the datasheet, the value of the high byte of the counter for 16-bit timers (such as timer 1) is only updated when the low byte is being read/written.
close #37
Diffstat (limited to '')
| -rw-r--r-- | src/peripherals/timer.spec.ts | 93 | ||||
| -rw-r--r-- | src/peripherals/timer.ts | 42 |
2 files changed, 89 insertions, 46 deletions
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts index 5a62246..7d280ed 100644 --- a/src/peripherals/timer.spec.ts +++ b/src/peripherals/timer.spec.ts @@ -23,7 +23,8 @@ describe('timer', () => { cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(1); // TCNT should be 1 }); it('should update timer every 64 ticks when prescaler is 3', () => { @@ -31,7 +32,8 @@ describe('timer', () => { cpu.data[0x45] = 0x3; // TCCR0B.CS <- 3 cpu.cycles = 64; timer.tick(); - expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(1); // TCNT should be 1 }); it('should not update timer if it has been disabled', () => { @@ -39,52 +41,61 @@ describe('timer', () => { cpu.data[0x45] = 0; // TCCR0B.CS <- 0 cpu.cycles = 100000; timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should stay 0 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0); // TCNT should stay 0 }); it('should set TOV if timer overflows', () => { const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff + timer.tick(); cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0); // TCNT should be 0 expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR }); it('should set TOV if timer overflows in PWM Phase Correct mode', () => { const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff + timer.tick(); cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f cpu.writeData(0x44, 0x1); // WGM0 <- 1 (PWM, Phase Correct) cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0); // TCNT should be 0 expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR }); it('should set TOV if timer overflows in FAST PWM mode', () => { const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff + timer.tick(); cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f cpu.writeData(0x44, 0x3); // WGM0 <- 3 (FAST PWM) cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0); // TCNT should be 0 expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR }); it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => { const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff + timer.tick(); cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0 cpu.data[95] = 0x80; // SREG: I------- cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0); // TCNT should be 0 expect(cpu.data[0x35]).toEqual(0); // TOV bit in TIFR should be clear expect(cpu.pc).toEqual(0x20); expect(cpu.cycles).toEqual(3); @@ -92,7 +103,8 @@ describe('timer', () => { it('should not generate an overflow interrupt when global interrupts disabled', () => { const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff + timer.tick(); cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0 cpu.data[95] = 0x0; // SREG: -------- @@ -105,7 +117,8 @@ describe('timer', () => { it('should not generate an overflow interrupt when TOIE0 is clear', () => { const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.writeData(0x46, 0xff); // TCNT0 <- 0xff + timer.tick(); cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 cpu.data[0x6e] = 0; // TIMSK0: clear cpu.data[95] = 0x80; // SREG: I------- @@ -139,7 +152,8 @@ describe('timer', () => { cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0); // TCNT should be 0 expect(cpu.pc).toEqual(0); expect(cpu.cycles).toEqual(1); }); @@ -168,7 +182,8 @@ describe('timer', () => { cpu.writeData(95, 0x80); // SREG: I------- cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0x21); // TCNT should be 0x21 expect(cpu.data[0x35]).toEqual(0); // OCFA bit in TIFR should be clear expect(cpu.pc).toEqual(0x1c); expect(cpu.cycles).toEqual(3); @@ -184,7 +199,8 @@ describe('timer', () => { cpu.writeData(95, 0x80); // SREG: I------- cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0x21); // TCNT should be 0x21 expect(cpu.pc).toEqual(0); expect(cpu.cycles).toEqual(1); }); @@ -199,7 +215,8 @@ describe('timer', () => { cpu.writeData(95, 0x80); // SREG: I------- cpu.cycles = 1; timer.tick(); - expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 + const tcnt = cpu.readData(0x46); + expect(tcnt).toEqual(0x21); // TCNT should be 0x21 expect(cpu.data[0x35]).toEqual(0); // OCFB bit in TIFR should be clear expect(cpu.pc).toEqual(0x1e); expect(cpu.cycles).toEqual(3); @@ -231,11 +248,11 @@ describe('timer', () => { cpu.cycles = 511; timer.tick(); - expect(cpu.data[0xb2]).toEqual(1); // TCNT2 should be 2 + expect(cpu.readData(0xb2)).toEqual(1); // TCNT2 should be 2 cpu.cycles = 512; timer.tick(); - expect(cpu.data[0xb2]).toEqual(2); // TCNT2 should be 2 + expect(cpu.readData(0xb2)).toEqual(2); // TCNT2 should be 2 }); describe('16 bit timers', () => { @@ -244,18 +261,21 @@ describe('timer', () => { cpu.writeData(0x85, 0x22); // TCNT1 <- 0x2233 cpu.writeData(0x84, 0x33); // ... timer.tick(); - expect(timer.TCNT).toEqual(0x2233); + const timerLow = cpu.readData(0x84); + const timerHigh = cpu.readData(0x85); + expect((timerHigh << 8) | timerLow).toEqual(0x2233); cpu.writeData(0x80, 0x0); // WGM1 <- 0 (Normal) cpu.writeData(0x81, 0x1); // TCCR1B.CS <- 1 cpu.cycles = 1; timer.tick(); + cpu.readData(0x84); expect(cpu.dataView.getUint16(0x84, true)).toEqual(0x2234); // TCNT1 should increment }); it('should set OCF0A flag when timer equals OCRA (16 bit mode)', () => { const timer = new AVRTimer(cpu, timer1Config); - cpu.writeData(0x84, 0xee); // TCNT1 <- 0x10ee - cpu.writeData(0x85, 0x10); // ... + cpu.writeData(0x85, 0x10); // TCNT1 <- 0x10ee + cpu.writeData(0x84, 0xee); // ... timer.tick(); cpu.writeData(0x88, 0xef); // OCR1A <- 0x10ef cpu.writeData(0x89, 0x10); // ... @@ -280,7 +300,8 @@ describe('timer', () => { cpu.data[95] = 0x80; // SREG: I------- cpu.cycles = 1; timer.tick(); - expect(cpu.dataView.getUint16(0x84, true)).toEqual(0); // TCNT should be 0 + cpu.readData(0x84); // Refresh TCNT1 + expect(cpu.dataView.getUint16(0x84, true)).toEqual(0); // TCNT1 should be 0 expect(cpu.data[0x36]).toEqual(0); // TOV bit in TIFR should be clear expect(cpu.pc).toEqual(0x1a); expect(cpu.cycles).toEqual(3); @@ -296,9 +317,35 @@ describe('timer', () => { cpu.writeData(0x81, 0x19); // TCCR1B <- WGM13 | WGM12 | CS10 cpu.cycles = 2; // 2 cycles should increment timer twice, beyond ICR1 timer.tick(); + cpu.readData(0x84); // Refresh TCNT1 expect(cpu.dataView.getUint16(0x84, true)).toEqual(0); // TCNT should be 0 expect(cpu.data[0x36]).toEqual(0); // TOV bit in TIFR should be clear expect(cpu.cycles).toEqual(2); }); + + it('should not update the high byte of TCNT if written after the low byte (issue #37)', () => { + const timer = new AVRTimer(cpu, timer1Config); + cpu.writeData(0x84, 0x22); // TCNT1L <- 0x22 + cpu.writeData(0x85, 0x55); // TCNT1H <- 0x55 + timer.tick(); + const timerLow = cpu.readData(0x84); + const timerHigh = cpu.readData(0x85); + expect((timerHigh << 8) | timerLow).toEqual(0x22); + }); + + it('reading from TCNT1H before TCNT1L should return old value (issue #37)', () => { + const timer = new AVRTimer(cpu, timer1Config); + cpu.writeData(0x85, 0xff); // TCNT1H <- 0xff + cpu.writeData(0x84, 0xff); // TCNT1L <- 0xff + cpu.writeData(0x81, 0x9); // TCCR1B <- CS10 + timer.tick(); + cpu.cycles = 1; + timer.tick(); + // We read the high byte before the low byte, so the high byte should still have + // the previous value: + const timerHigh = cpu.readData(0x85); + const timerLow = cpu.readData(0x84); + expect((timerHigh << 8) | timerLow).toEqual(0xff00); + }); }); }); diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts index ebd7b58..04c09aa 100644 --- a/src/peripherals/timer.ts +++ b/src/peripherals/timer.ts @@ -191,16 +191,24 @@ export class AVRTimer { private ocrB: u16 = 0; private timerMode: TimerMode; private topValue: TimerTopValue; + private tcnt: u16 = 0; private tcntUpdated = false; constructor(private cpu: CPU, private config: AVRTimerConfig) { this.updateWGMConfig(); - this.registerHook(config.TCNT, (value: u16) => { - this.TCNT = value; + this.cpu.readHooks[config.TCNT] = (addr: u8) => { + if (this.config.bits === 16) { + this.cpu.data[addr + 1] = this.tcnt >> 8; + } + return (this.cpu.data[addr] = this.tcnt & 0xff); + }; + + this.cpu.writeHooks[config.TCNT] = (value: u8) => { + const highByte = this.config.bits === 16 ? this.cpu.data[config.TCNT + 1] : 0; + this.tcnt = (highByte << 8) | value; this.tcntUpdated = true; - this.timerUpdated(value); - return true; - }); + this.timerUpdated(); + }; this.registerHook(config.OCRA, (value: u16) => { // TODO implement buffering when timer running in PWM mode this.ocrA = value; @@ -234,19 +242,6 @@ export class AVRTimer { this.cpu.data[this.config.TIFR] = value; } - get TCNT() { - return this.config.bits === 16 - ? this.cpu.dataView.getUint16(this.config.TCNT, true) - : this.cpu.data[this.config.TCNT]; - } - - set TCNT(value: u16) { - this.cpu.data[this.config.TCNT] = value & 0xff; - if (this.config.bits === 16) { - this.cpu.data[this.config.TCNT + 1] = (value >> 8) & 0xff; - } - } - get TCCRA() { return this.cpu.data[this.config.TCCRA]; } @@ -306,12 +301,12 @@ export class AVRTimer { if (divider && delta >= divider) { const counterDelta = Math.floor(delta / divider); this.lastCycle += counterDelta * divider; - const val = this.TCNT; + const val = this.tcnt; const newVal = (val + counterDelta) % (this.TOP + 1); // A CPU write overrides (has priority over) all counter clear or count operations. if (!this.tcntUpdated) { - this.TCNT = newVal; - this.timerUpdated(newVal); + this.tcnt = newVal; + this.timerUpdated(); } const { timerMode } = this; if ( @@ -341,12 +336,13 @@ export class AVRTimer { } } - private timerUpdated(value: u8) { + private timerUpdated() { + const value = this.tcnt; if (this.ocrA && value === this.ocrA) { this.TIFR |= OCFA; if (this.timerMode === TimerMode.CTC) { // Clear Timer on Compare Match (CTC) Mode - this.TCNT = 0; + this.tcnt = 0; this.TIFR |= TOV; } } |
