aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorUri Shaked2020-04-16 18:42:12 +0300
committerGitHub2020-04-16 18:42:12 +0300
commit9f06d4e171ef70c464b0ccc0262d8c9bba67d11e (patch)
tree3b07fed997009f9ac88ffaf27e2ffe802b493c6d /src
parentchore(deps): jest 25.3.0, ts-jest 25.3.1 (diff)
parenttest(timer): add more 16-bit timer tests (diff)
downloadavr8js-9f06d4e171ef70c464b0ccc0262d8c9bba67d11e.tar.gz
avr8js-9f06d4e171ef70c464b0ccc0262d8c9bba67d11e.tar.bz2
avr8js-9f06d4e171ef70c464b0ccc0262d8c9bba67d11e.zip
Merge pull request #30 from wokwi/16bit-timer-fix
Implement 16-bit timers
Diffstat (limited to 'src')
-rw-r--r--src/peripherals/timer.spec.ts62
-rw-r--r--src/peripherals/timer.ts153
2 files changed, 191 insertions, 24 deletions
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;
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.