From ae5caeb0b044a93bebcb641e1dd4ff9a390da26f Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Sat, 30 Nov 2019 19:55:01 +0200 Subject: feat: Output Compare for Timers close #4 --- src/timer.spec.ts | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/timer.ts | 84 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 185 insertions(+), 6 deletions(-) diff --git a/src/timer.spec.ts b/src/timer.spec.ts index b7d96a9..dd45cee 100644 --- a/src/timer.spec.ts +++ b/src/timer.spec.ts @@ -42,6 +42,30 @@ describe('timer', () => { 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(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 + 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(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 + 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 @@ -81,4 +105,87 @@ describe('timer', () => { expect(cpu.pc).toEqual(0); expect(cpu.cycles).toEqual(1); }); + + it('should set OCF0A flag when timer equals OCRA', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10 + cpu.writeData(0x47, 0x11); // OCR0A <- 0x11 + cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal) + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x35]).toEqual(2); // TIFR0 should have OCF0A bit on + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should clear the timer in CTC mode if it equals to OCRA', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10 + cpu.writeData(0x47, 0x11); // OCR0A <- 0x11 + cpu.writeData(0x44, 0x2); // WGM0 <- 2 (CTC) + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should set OCF0B flag when timer equals OCRB', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x10); // TCNT0 <- 0x50 + cpu.writeData(0x48, 0x11); // OCR0B <- 0x51 + cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal) + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x35]).toEqual(4); // TIFR0 should have OCF0B bit on + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should generate Timer Compare A interrupt when TCNT0 == TCNTA', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 + cpu.writeData(0x47, 0x21); // OCR0A <- 0x21 + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.writeData(0x6e, 0x2); // TIMSK0: OCIEA + cpu.writeData(95, 0x80); // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).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); + }); + + it('should not generate Timer Compare A interrupt when OCIEA is disabled', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 + cpu.writeData(0x47, 0x21); // OCR0A <- 0x21 + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.writeData(0x6e, 0); // TIMSK0 + cpu.writeData(95, 0x80); // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should generate Timer Compare B interrupt when TCNT0 == TCNTB', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 + cpu.writeData(0x48, 0x21); // OCR0B <- 0x21 + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.writeData(0x6e, 0x4); // TIMSK0: OCIEB + cpu.writeData(95, 0x80); // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).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); + }); }); diff --git a/src/timer.ts b/src/timer.ts index 7203574..1406eda 100644 --- a/src/timer.ts +++ b/src/timer.ts @@ -20,6 +20,19 @@ const dividers = { 7: 0 // TODO: External clock source on T0 pin. Clock on rising edge. }; +const WGM_NORMAL = 0; +const WGM_PWM_PHASE_CORRECT = 1; +const WGM_CTC = 2; +const WGM_FASTPWM = 3; + +const TOV = 1; +const OCFA = 2; +const OCFB = 4; + +const TOIE = 1; +const OCIEA = 2; +const OCIEB = 4; + type u8 = number; interface AVRTimerConfig { @@ -95,8 +108,29 @@ export const timer2Config: AVRTimerConfig = { export class AVRTimer { private mask = (1 << this.config.bits) - 1; private lastCycle = 0; + private ocrA: u8 = 0; + private ocrB: u8 = 0; + + constructor(private cpu: CPU, private config: AVRTimerConfig) { + cpu.writeHooks[config.TCNT] = (value: u8) => { + this.TCNT = value; + this.timerUpdated(value); + return true; + }; + cpu.writeHooks[config.OCRA] = (value: u8) => { + // TODO implement buffering when timer running in PWM mode + this.ocrA = value; + }; + cpu.writeHooks[config.OCRB] = (value: u8) => { + this.ocrB = value; + }; + } - constructor(private cpu: CPU, private config: AVRTimerConfig) {} + reset() { + this.lastCycle = 0; + this.ocrA = 0; + this.ocrB = 0; + } get TIFR() { return this.cpu.data[this.config.TIFR]; @@ -114,6 +148,10 @@ export class AVRTimer { this.cpu.data[this.config.TCNT] = value; } + get TCCRA() { + return this.cpu.data[this.config.TCCRA]; + } + get TCCRB() { return this.cpu.data[this.config.TCCRB]; } @@ -126,6 +164,10 @@ export class AVRTimer { return (this.TCCRB & 0x7) as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; } + get WGM() { + return ((this.TCCRB & 0x8) >> 1) | (this.TCCRA & 0x3); + } + tick() { const divider = dividers[this.CS]; const delta = this.cpu.cycles - this.lastCycle; @@ -135,13 +177,43 @@ export class AVRTimer { const val = this.TCNT; const newVal = (val + counterDelta) & this.mask; this.TCNT = newVal; - if (val > newVal) { - this.TIFR |= 1; // TOV + this.timerUpdated(newVal); + if ( + (this.WGM === WGM_NORMAL || + this.WGM === WGM_PWM_PHASE_CORRECT || + this.WGM === WGM_FASTPWM) && + val > newVal + ) { + this.TIFR |= TOV; + } + } + if (this.cpu.interruptsEnabled) { + if (this.TIFR & TOV && this.TIMSK & TOIE) { + avrInterrupt(this.cpu, this.config.ovfInterrupt); + this.TIFR &= ~TOV; + } + if (this.TIFR & OCFA && this.TIMSK & OCIEA) { + avrInterrupt(this.cpu, this.config.compAInterrupt); + this.TIFR &= ~OCFA; + } + if (this.TIFR & OCFB && this.TIMSK & OCIEB) { + avrInterrupt(this.cpu, this.config.compBInterrupt); + this.TIFR &= ~OCFB; + } + } + } + + private timerUpdated(value: u8) { + if (this.ocrA && value === this.ocrA) { + this.TIFR |= OCFA; + if (this.WGM === WGM_CTC) { + // Clear Timer on Compare Match (CTC) Mode + this.TCNT = 0; + this.TIFR |= TOV; } } - if (this.TIFR & 0x1 && this.TIMSK & 0x1 && this.cpu.interruptsEnabled) { - avrInterrupt(this.cpu, this.config.ovfInterrupt); - this.TIFR &= ~0x1; + if (this.ocrB && value === this.ocrB) { + this.TIFR |= OCFB; } } } -- cgit v1.2.3