diff options
| author | Uri Shaked | 2019-11-21 17:09:40 +0200 |
|---|---|---|
| committer | Uri Shaked | 2019-11-21 17:11:02 +0200 |
| commit | ca96ec1ab43979e75c8a551e2aaddda8ad4b9e06 (patch) | |
| tree | 6b591d80de7e00e1889b77f8fbad1eb1e493877c | |
| parent | fix: typescript errors on TS < 3.7 (diff) | |
| download | avr8js-ca96ec1ab43979e75c8a551e2aaddda8ad4b9e06.tar.gz avr8js-ca96ec1ab43979e75c8a551e2aaddda8ad4b9e06.tar.bz2 avr8js-ca96ec1ab43979e75c8a551e2aaddda8ad4b9e06.zip | |
feat: initial timer implementation
8-bit timers basic functionality + tests:
1. basic counting + prescaler
2. timer overflow
3. timer overflow interrupt
Diffstat (limited to '')
| -rw-r--r-- | src/index.ts | 1 | ||||
| -rw-r--r-- | src/timer.spec.ts | 84 | ||||
| -rw-r--r-- | src/timer.ts | 147 |
3 files changed, 232 insertions, 0 deletions
diff --git a/src/index.ts b/src/index.ts index 8a474a4..94e4ecc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ export { CPU, ICPU, ICPUMemoryHook, ICPUMemoryHooks } from './cpu'; export { avrInstruction } from './instruction'; export { avrInterrupt } from './interrupt'; +export { AVRTimer, timer0Config, timer1Config, timer2Config } from './timer'; diff --git a/src/timer.spec.ts b/src/timer.spec.ts new file mode 100644 index 0000000..b7d96a9 --- /dev/null +++ b/src/timer.spec.ts @@ -0,0 +1,84 @@ +import { CPU } from './cpu'; +import { AVRTimer, timer0Config } from './timer'; + +describe('timer', () => { + let cpu: CPU; + + beforeEach(() => { + cpu = new CPU(new Uint16Array(0x1000)); + }); + + it('should update timer every tick when prescaler is 1', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1 + }); + + it('should update timer every 64 ticks when prescaler is 3', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x45] = 0x3; // TCCR0B.CS <- 3 + cpu.cycles = 64; + timer.tick(); + expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1 + }); + + it('should not update timer if it has been disabled', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x45] = 0; // TCCR0B.CS <- 0 + cpu.cycles = 100000; + timer.tick(); + expect(cpu.data[0x46]).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.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 + 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 + expect(cpu.data[0x35]).toEqual(0); // TOV bit in TIFR should be clear + expect(cpu.pc).toEqual(0x20); + expect(cpu.cycles).toEqual(3); + }); + + it('should not generate an overflow interrupt when global interrupts disabled', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0 + cpu.data[95] = 0x0; // SREG: -------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR should be set + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should not generate an overflow interrupt when TOIE0 is clear', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.data[0x6e] = 0; // TIMSK0: clear + cpu.data[95] = 0x80; // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR should be set + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); +}); diff --git a/src/timer.ts b/src/timer.ts new file mode 100644 index 0000000..7203574 --- /dev/null +++ b/src/timer.ts @@ -0,0 +1,147 @@ +/** + * AVR-8 Timers + * 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 + */ + +import { CPU } from './cpu'; +import { avrInterrupt } from './interrupt'; + +const dividers = { + 0: 0, + 1: 1, + 2: 8, + 3: 64, + 4: 256, + 5: 1024, + 6: 0, // TODO: External clock source on T0 pin. Clock on falling edge. + 7: 0 // TODO: External clock source on T0 pin. Clock on rising edge. +}; + +type u8 = number; + +interface AVRTimerConfig { + bits: 8 | 16; + captureInterrupt: u8; + compAInterrupt: u8; + compBInterrupt: u8; + ovfInterrupt: u8; + + // Register addresses + TIFR: u8; + OCRA: u8; + OCRB: u8; + ICR: u8; + TCNT: u8; + TCCRA: u8; + TCCRB: u8; + TCCRC: u8; + TIMSK: u8; +} + +export const timer0Config: AVRTimerConfig = { + bits: 8, + captureInterrupt: 0, // not available + compAInterrupt: 0x1c, + compBInterrupt: 0x1e, + ovfInterrupt: 0x20, + TIFR: 0x35, + OCRA: 0x47, + OCRB: 0x48, + ICR: 0, // not available + TCNT: 0x46, + TCCRA: 0x44, + TCCRB: 0x45, + TCCRC: 0, // not available + TIMSK: 0x6e +}; + +export const timer1Config: AVRTimerConfig = { + bits: 16, + captureInterrupt: 0x14, + compAInterrupt: 0x16, + compBInterrupt: 0x18, + ovfInterrupt: 0x1a, + TIFR: 0x36, + OCRA: 0x88, + OCRB: 0x8a, + ICR: 0x86, + TCNT: 0x84, + TCCRA: 0x80, + TCCRB: 0x81, + TCCRC: 0x82, + TIMSK: 0x6f +}; + +export const timer2Config: AVRTimerConfig = { + bits: 8, + captureInterrupt: 0, // not available + compAInterrupt: 0x0e, + compBInterrupt: 0x10, + ovfInterrupt: 0x12, + TIFR: 0x37, + OCRA: 0xb3, + OCRB: 0xb4, + ICR: 0, // not available + TCNT: 0xb2, + TCCRA: 0xb0, + TCCRB: 0xb1, + TCCRC: 0, // not available + TIMSK: 0x70 +}; + +export class AVRTimer { + private mask = (1 << this.config.bits) - 1; + private lastCycle = 0; + + constructor(private cpu: CPU, private config: AVRTimerConfig) {} + + get TIFR() { + return this.cpu.data[this.config.TIFR]; + } + + set TIFR(value: u8) { + this.cpu.data[this.config.TIFR] = value; + } + + get TCNT() { + return this.cpu.data[this.config.TCNT]; + } + + set TCNT(value: u8) { + this.cpu.data[this.config.TCNT] = value; + } + + get TCCRB() { + return this.cpu.data[this.config.TCCRB]; + } + + get TIMSK() { + return this.cpu.data[this.config.TIMSK]; + } + + get CS() { + return (this.TCCRB & 0x7) as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + } + + tick() { + const divider = dividers[this.CS]; + const delta = this.cpu.cycles - this.lastCycle; + if (divider && delta >= divider) { + const counterDelta = Math.floor(delta / divider); + this.lastCycle += counterDelta * divider; + const val = this.TCNT; + const newVal = (val + counterDelta) & this.mask; + this.TCNT = newVal; + if (val > newVal) { + this.TIFR |= 1; // TOV + } + } + if (this.TIFR & 0x1 && this.TIMSK & 0x1 && this.cpu.interruptsEnabled) { + avrInterrupt(this.cpu, this.config.ovfInterrupt); + this.TIFR &= ~0x1; + } + } +} |
