aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorUri Shaked2019-11-21 17:09:40 +0200
committerUri Shaked2019-11-21 17:11:02 +0200
commitca96ec1ab43979e75c8a551e2aaddda8ad4b9e06 (patch)
tree6b591d80de7e00e1889b77f8fbad1eb1e493877c /src
parentfix: typescript errors on TS < 3.7 (diff)
downloadavr8js-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 'src')
-rw-r--r--src/index.ts1
-rw-r--r--src/timer.spec.ts84
-rw-r--r--src/timer.ts147
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;
+ }
+ }
+}
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.