diff --git a/demo/src/execute.ts b/demo/src/execute.ts index ee5278e..2ba9ee0 100644 --- a/demo/src/execute.ts +++ b/demo/src/execute.ts @@ -3,6 +3,7 @@ import { AVRTimer, CPU, timer0Config, + timer1Config, AVRIOPort, AVRUSART, portBConfig, @@ -19,7 +20,8 @@ const FLASH = 0x8000; export class AVRRunner { readonly program = new Uint16Array(FLASH); readonly cpu: CPU; - readonly timer: AVRTimer; + readonly timer0: AVRTimer; + readonly timer1: AVRTimer; readonly portB: AVRIOPort; readonly portC: AVRIOPort; readonly portD: AVRIOPort; @@ -31,7 +33,8 @@ export class AVRRunner { constructor(hex: string) { loadHex(hex, new Uint8Array(this.program.buffer)); this.cpu = new CPU(this.program); - this.timer = new AVRTimer(this.cpu, timer0Config); + this.timer0 = new AVRTimer(this.cpu, timer0Config); + this.timer1 = new AVRTimer(this.cpu, timer1Config); this.portB = new AVRIOPort(this.cpu, portBConfig); this.portC = new AVRIOPort(this.cpu, portCConfig); this.portD = new AVRIOPort(this.cpu, portDConfig); @@ -44,7 +47,8 @@ export class AVRRunner { const cyclesToRun = this.cpu.cycles + this.workUnitCycles; while (this.cpu.cycles < cyclesToRun) { avrInstruction(this.cpu); - this.timer.tick(); + this.timer0.tick(); + this.timer1.tick(); this.usart.tick(); } diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts index adcef04..9f50239 100644 --- a/src/peripherals/timer.spec.ts +++ b/src/peripherals/timer.spec.ts @@ -1,5 +1,5 @@ import { CPU } from '../cpu/cpu'; -import { AVRTimer, timer0Config, timer2Config } from './timer'; +import { AVRTimer, timer0Config, timer1Config, timer2Config } from './timer'; describe('timer', () => { let cpu: CPU; @@ -201,4 +201,64 @@ describe('timer', () => { timer.tick(); expect(cpu.data[0xb2]).toEqual(2); // TCNT2 should be 2 }); + + describe('16 bit timers', () => { + it('should increment 16-bit TCNT by 1', () => { + const timer = new AVRTimer(cpu, timer1Config); + cpu.writeData(0x85, 0x22); // TCNT1 <- 0x2233 + cpu.writeData(0x84, 0x33); // ... + expect(timer.TCNT).toEqual(0x2233); + cpu.writeData(0x80, 0x0); // WGM1 <- 0 (Normal) + cpu.writeData(0x81, 0x1); // TCCR1B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + 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(0x88, 0xef); // OCR1A <- 0x10ef + cpu.writeData(0x89, 0x10); // ... + cpu.writeData(0x80, 0x0); // TCCR1A <- 0 (Normal Mode) + cpu.writeData(0x81, 0x1); // TCCR1B <- CS10 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x36]).toEqual(2); // TIFR1 should have OCF1A bit on + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => { + const timer = new AVRTimer(cpu, timer1Config); + cpu.writeData(0x85, 0x3); // TCNT1 <- 0x3ff + cpu.writeData(0x84, 0xff); // ... + cpu.writeData(0x80, 0x3); // TCCR1A <- WGM10 | WGM11 (Fast PWM, 10-bit) + cpu.writeData(0x81, 0x9); // TCCR1B <- WGM12 | CS10 + console.log(timer.CS); + cpu.data[0x6f] = 0x1; // TIMSK1: TOIE1 + cpu.data[95] = 0x80; // SREG: I------- + cpu.cycles = 1; + timer.tick(); + 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.pc).toEqual(0x1a); + expect(cpu.cycles).toEqual(3); + }); + + it('should reset the timer once it reaches ICR value in mode 12', () => { + const timer = new AVRTimer(cpu, timer1Config); + cpu.writeData(0x85, 0x50); // TCNT1 <- 0x500f + cpu.writeData(0x84, 0x0f); // ... + cpu.writeData(0x87, 0x50); // ICR1 <- 0x5010 + cpu.writeData(0x86, 0x10); // ... + cpu.writeData(0x81, 0x19); // TCCR1B <- WGM13 | WGM12 | CS10 + cpu.cycles = 2; // 2 cycles should increment timer twice, beyond ICR1 + timer.tick(); + 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); + }); + }); }); diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts index 7e563c2..6e805ee 100644 --- a/src/peripherals/timer.ts +++ b/src/peripherals/timer.ts @@ -3,7 +3,7 @@ * Part of AVR8js * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf * - * Copyright (C) 2019, Uri Shaked + * Copyright (C) 2019, 2020, Uri Shaked */ import { CPU } from '../cpu/cpu'; @@ -20,11 +20,6 @@ const timer01Dividers = { 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; @@ -34,6 +29,7 @@ const OCIEA = 2; const OCIEB = 4; type u8 = number; +type u16 = number; interface TimerDividers { 0: number; @@ -130,24 +126,95 @@ export const timer2Config: AVRTimerConfig = { } }; +/* All the following types and constants are related to WGM (Waveform Generation Mode) bits: */ +enum TimerMode { + Normal, + PWMPhaseCorrect, + CTC, + FastPWM, + PWMPhaseFrequencyCorrect, + Reserved +} + +enum TOVUpdateMode { + Max, + Top, + Bottom +} + +enum OCRUpdateMode { + Immediate, + Top, + Bottom +} + +const TopOCRA = 1; +const TopICR = 2; +type TimerTopValue = 0xff | 0x1ff | 0x3ff | 0xffff | typeof TopOCRA | typeof TopICR; + +type WGMConfig = [TimerMode, TimerTopValue, OCRUpdateMode, TOVUpdateMode]; + +const wgmModes8Bit: WGMConfig[] = [ + /*0*/ [TimerMode.Normal, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max], + /*1*/ [TimerMode.PWMPhaseCorrect, 0xff, OCRUpdateMode.Top, TOVUpdateMode.Bottom], + /*2*/ [TimerMode.CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max], + /*3*/ [TimerMode.FastPWM, 0xff, OCRUpdateMode.Bottom, TOVUpdateMode.Max], + /*4*/ [TimerMode.Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max], + /*5*/ [TimerMode.PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom], + /*6*/ [TimerMode.Reserved, 0xff, OCRUpdateMode.Immediate, TOVUpdateMode.Max], + /*7*/ [TimerMode.FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top] +]; + +// Table 16-4 in the datasheet +const wgmModes16Bit: WGMConfig[] = [ + /*0 */ [TimerMode.Normal, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max], + /*1 */ [TimerMode.PWMPhaseCorrect, 0x00ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom], + /*2 */ [TimerMode.PWMPhaseCorrect, 0x01ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom], + /*3 */ [TimerMode.PWMPhaseCorrect, 0x03ff, OCRUpdateMode.Top, TOVUpdateMode.Bottom], + /*4 */ [TimerMode.CTC, TopOCRA, OCRUpdateMode.Immediate, TOVUpdateMode.Max], + /*5 */ [TimerMode.FastPWM, 0x00ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top], + /*6 */ [TimerMode.FastPWM, 0x01ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top], + /*7 */ [TimerMode.FastPWM, 0x03ff, OCRUpdateMode.Bottom, TOVUpdateMode.Top], + /*8 */ [TimerMode.PWMPhaseFrequencyCorrect, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom], + /*9 */ [TimerMode.PWMPhaseFrequencyCorrect, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Bottom], + /*10*/ [TimerMode.PWMPhaseCorrect, TopICR, OCRUpdateMode.Top, TOVUpdateMode.Bottom], + /*11*/ [TimerMode.PWMPhaseCorrect, TopOCRA, OCRUpdateMode.Top, TOVUpdateMode.Bottom], + /*12*/ [TimerMode.CTC, TopICR, OCRUpdateMode.Immediate, TOVUpdateMode.Max], + /*13*/ [TimerMode.Reserved, 0xffff, OCRUpdateMode.Immediate, TOVUpdateMode.Max], + /*14*/ [TimerMode.FastPWM, TopICR, OCRUpdateMode.Bottom, TOVUpdateMode.Top], + /*15*/ [TimerMode.FastPWM, TopOCRA, OCRUpdateMode.Bottom, TOVUpdateMode.Top] +]; + export class AVRTimer { - private mask = (1 << this.config.bits) - 1; private lastCycle = 0; - private ocrA: u8 = 0; - private ocrB: u8 = 0; + private ocrA: u16 = 0; + private ocrB: u16 = 0; + private timerMode: TimerMode; + private topValue: TimerTopValue; constructor(private cpu: CPU, private config: AVRTimerConfig) { - cpu.writeHooks[config.TCNT] = (value: u8) => { + this.updateWGMConfig(); + this.registerHook(config.TCNT, (value: u16) => { this.TCNT = value; this.timerUpdated(value); return true; - }; - cpu.writeHooks[config.OCRA] = (value: u8) => { + }); + this.registerHook(config.OCRA, (value: u16) => { // TODO implement buffering when timer running in PWM mode this.ocrA = value; - }; - cpu.writeHooks[config.OCRB] = (value: u8) => { + }); + this.registerHook(config.OCRB, (value: u16) => { this.ocrB = value; + }); + cpu.writeHooks[config.TCCRA] = (value) => { + this.cpu.data[config.TCCRA] = value; + this.updateWGMConfig(); + return true; + }; + cpu.writeHooks[config.TCCRB] = (value) => { + this.cpu.data[config.TCCRB] = value; + this.updateWGMConfig(); + return true; }; } @@ -166,11 +233,16 @@ export class AVRTimer { } get TCNT() { - return this.cpu.data[this.config.TCNT]; + return this.config.bits === 16 + ? this.cpu.dataView.getUint16(this.config.TCNT, true) + : this.cpu.data[this.config.TCNT]; } - set TCNT(value: u8) { - this.cpu.data[this.config.TCNT] = value; + 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() { @@ -185,12 +257,45 @@ export class AVRTimer { return this.cpu.data[this.config.TIMSK]; } + get ICR() { + // Only available for 16-bit timers + return (this.cpu.data[this.config.ICR + 1] << 8) | this.cpu.data[this.config.ICR]; + } + get CS() { return (this.TCCRB & 0x7) as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; } get WGM() { - return ((this.TCCRB & 0x8) >> 1) | (this.TCCRA & 0x3); + const mask = this.config.bits === 16 ? 0x18 : 0x8; + return ((this.TCCRB & mask) >> 1) | (this.TCCRA & 0x3); + } + + get TOP() { + switch (this.topValue) { + case TopOCRA: + return this.ocrA; + case TopICR: + return this.ICR; + default: + return this.topValue; + } + } + + private registerHook(address: number, hook: (value: u16) => void) { + if (this.config.bits === 16) { + this.cpu.writeHooks[address] = (value: u8) => hook((this.cpu.data[address + 1] << 8) | value); + this.cpu.writeHooks[address + 1] = (value: u8) => hook((value << 8) | this.cpu.data[address]); + } else { + this.cpu.writeHooks[address] = hook; + } + } + + private updateWGMConfig() { + const wgmModes = this.config.bits === 16 ? wgmModes16Bit : wgmModes8Bit; + const [timerMode, topValue] = wgmModes[this.WGM]; + this.timerMode = timerMode; + this.topValue = topValue; } tick() { @@ -200,13 +305,15 @@ export class AVRTimer { const counterDelta = Math.floor(delta / divider); this.lastCycle += counterDelta * divider; const val = this.TCNT; - const newVal = (val + counterDelta) & this.mask; + const newVal = (val + counterDelta) % (this.TOP + 1); this.TCNT = newVal; this.timerUpdated(newVal); + const { timerMode } = this; if ( - (this.WGM === WGM_NORMAL || - this.WGM === WGM_PWM_PHASE_CORRECT || - this.WGM === WGM_FASTPWM) && + (timerMode === TimerMode.Normal || + timerMode === TimerMode.PWMPhaseCorrect || + timerMode === TimerMode.PWMPhaseFrequencyCorrect || + timerMode === TimerMode.FastPWM) && val > newVal ) { this.TIFR |= TOV; @@ -231,7 +338,7 @@ export class AVRTimer { private timerUpdated(value: u8) { if (this.ocrA && value === this.ocrA) { this.TIFR |= OCFA; - if (this.WGM === WGM_CTC) { + if (this.timerMode === TimerMode.CTC) { // Clear Timer on Compare Match (CTC) Mode this.TCNT = 0; this.TIFR |= TOV;