aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals
diff options
context:
space:
mode:
Diffstat (limited to 'src/peripherals')
-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;
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.