aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorUri Shaked2020-04-04 16:22:32 +0300
committerUri Shaked2020-04-12 22:56:26 +0300
commit619652de465c0f3c73d867de3ec51c21f612b082 (patch)
tree64e87fbdd3d9ef3f100c50b8d0a02bd9aaca98ce /src
parentfeat(demo): add 16-bit timer (timer1) (diff)
downloadavr8js-619652de465c0f3c73d867de3ec51c21f612b082.tar.gz
avr8js-619652de465c0f3c73d867de3ec51c21f612b082.tar.bz2
avr8js-619652de465c0f3c73d867de3ec51c21f612b082.zip
feat(timer): implement 16-bit timers
e.g. Timer/Counter1 on ATmega328
Diffstat (limited to '')
-rw-r--r--src/peripherals/timer.spec.ts19
-rw-r--r--src/peripherals/timer.ts153
2 files changed, 148 insertions, 24 deletions
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts
index adcef04..7750f26 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,21 @@ describe('timer', () => {
timer.tick();
expect(cpu.data[0xb2]).toEqual(2); // TCNT2 should be 2
});
+
+ describe('16 bit timers', () => {
+ 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); // WGM1 <- 0 (Normal)
+ cpu.writeData(0x81, 0x1); // TCCR1B.CS <- 1
+ cpu.cycles = 1;
+ timer.tick();
+ expect(cpu.data[0x36]).toEqual(2); // TIFR0 should have OCF0A bit on
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.cycles).toEqual(1);
+ });
+ });
});
diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts
index 7e563c2..ea66671 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,91 @@ 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] = () => {
+ this.updateWGMConfig();
+ };
+ cpu.writeHooks[config.TCCRB] = () => {
+ this.updateWGMConfig();
};
}
@@ -166,11 +229,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 >> 16) & 0xff;
+ }
}
get TCCRA() {
@@ -185,12 +253,49 @@ 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];
+ }
+
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] | 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;