aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/timer.spec.ts107
-rw-r--r--src/timer.ts84
2 files changed, 185 insertions, 6 deletions
diff --git a/src/timer.spec.ts b/src/timer.spec.ts
index b7d96a9..dd45cee 100644
--- a/src/timer.spec.ts
+++ b/src/timer.spec.ts
@@ -42,6 +42,30 @@ describe('timer', () => {
expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR
});
+ it('should set TOV if timer overflows in PWM Phase Correct mode', () => {
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
+ cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f
+ cpu.writeData(0x44, 0x1); // WGM0 <- 1 (PWM, Phase Correct)
+ 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 set TOV if timer overflows in FAST PWM mode', () => {
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
+ cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f
+ cpu.writeData(0x44, 0x3); // WGM0 <- 3 (FAST PWM)
+ 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
@@ -81,4 +105,87 @@ describe('timer', () => {
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
});
+
+ it('should set OCF0A flag when timer equals OCRA', () => {
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10
+ cpu.writeData(0x47, 0x11); // OCR0A <- 0x11
+ cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal)
+ cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
+ cpu.cycles = 1;
+ timer.tick();
+ expect(cpu.data[0x35]).toEqual(2); // TIFR0 should have OCF0A bit on
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should clear the timer in CTC mode if it equals to OCRA', () => {
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10
+ cpu.writeData(0x47, 0x11); // OCR0A <- 0x11
+ cpu.writeData(0x44, 0x2); // WGM0 <- 2 (CTC)
+ cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
+ cpu.cycles = 1;
+ timer.tick();
+ expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should set OCF0B flag when timer equals OCRB', () => {
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.writeData(0x46, 0x10); // TCNT0 <- 0x50
+ cpu.writeData(0x48, 0x11); // OCR0B <- 0x51
+ cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal)
+ cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
+ cpu.cycles = 1;
+ timer.tick();
+ expect(cpu.data[0x35]).toEqual(4); // TIFR0 should have OCF0B bit on
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should generate Timer Compare A interrupt when TCNT0 == TCNTA', () => {
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20
+ cpu.writeData(0x47, 0x21); // OCR0A <- 0x21
+ cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
+ cpu.writeData(0x6e, 0x2); // TIMSK0: OCIEA
+ cpu.writeData(95, 0x80); // SREG: I-------
+ cpu.cycles = 1;
+ timer.tick();
+ expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
+ expect(cpu.data[0x35]).toEqual(0); // OCFA bit in TIFR should be clear
+ expect(cpu.pc).toEqual(0x1c);
+ expect(cpu.cycles).toEqual(3);
+ });
+
+ it('should not generate Timer Compare A interrupt when OCIEA is disabled', () => {
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20
+ cpu.writeData(0x47, 0x21); // OCR0A <- 0x21
+ cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
+ cpu.writeData(0x6e, 0); // TIMSK0
+ cpu.writeData(95, 0x80); // SREG: I-------
+ cpu.cycles = 1;
+ timer.tick();
+ expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should generate Timer Compare B interrupt when TCNT0 == TCNTB', () => {
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20
+ cpu.writeData(0x48, 0x21); // OCR0B <- 0x21
+ cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
+ cpu.writeData(0x6e, 0x4); // TIMSK0: OCIEB
+ cpu.writeData(95, 0x80); // SREG: I-------
+ cpu.cycles = 1;
+ timer.tick();
+ expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
+ expect(cpu.data[0x35]).toEqual(0); // OCFB bit in TIFR should be clear
+ expect(cpu.pc).toEqual(0x1e);
+ expect(cpu.cycles).toEqual(3);
+ });
});
diff --git a/src/timer.ts b/src/timer.ts
index 7203574..1406eda 100644
--- a/src/timer.ts
+++ b/src/timer.ts
@@ -20,6 +20,19 @@ const dividers = {
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;
+
+const TOIE = 1;
+const OCIEA = 2;
+const OCIEB = 4;
+
type u8 = number;
interface AVRTimerConfig {
@@ -95,8 +108,29 @@ export const timer2Config: AVRTimerConfig = {
export class AVRTimer {
private mask = (1 << this.config.bits) - 1;
private lastCycle = 0;
+ private ocrA: u8 = 0;
+ private ocrB: u8 = 0;
+
+ constructor(private cpu: CPU, private config: AVRTimerConfig) {
+ cpu.writeHooks[config.TCNT] = (value: u8) => {
+ this.TCNT = value;
+ this.timerUpdated(value);
+ return true;
+ };
+ cpu.writeHooks[config.OCRA] = (value: u8) => {
+ // TODO implement buffering when timer running in PWM mode
+ this.ocrA = value;
+ };
+ cpu.writeHooks[config.OCRB] = (value: u8) => {
+ this.ocrB = value;
+ };
+ }
- constructor(private cpu: CPU, private config: AVRTimerConfig) {}
+ reset() {
+ this.lastCycle = 0;
+ this.ocrA = 0;
+ this.ocrB = 0;
+ }
get TIFR() {
return this.cpu.data[this.config.TIFR];
@@ -114,6 +148,10 @@ export class AVRTimer {
this.cpu.data[this.config.TCNT] = value;
}
+ get TCCRA() {
+ return this.cpu.data[this.config.TCCRA];
+ }
+
get TCCRB() {
return this.cpu.data[this.config.TCCRB];
}
@@ -126,6 +164,10 @@ export class AVRTimer {
return (this.TCCRB & 0x7) as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
}
+ get WGM() {
+ return ((this.TCCRB & 0x8) >> 1) | (this.TCCRA & 0x3);
+ }
+
tick() {
const divider = dividers[this.CS];
const delta = this.cpu.cycles - this.lastCycle;
@@ -135,13 +177,43 @@ export class AVRTimer {
const val = this.TCNT;
const newVal = (val + counterDelta) & this.mask;
this.TCNT = newVal;
- if (val > newVal) {
- this.TIFR |= 1; // TOV
+ this.timerUpdated(newVal);
+ if (
+ (this.WGM === WGM_NORMAL ||
+ this.WGM === WGM_PWM_PHASE_CORRECT ||
+ this.WGM === WGM_FASTPWM) &&
+ val > newVal
+ ) {
+ this.TIFR |= TOV;
+ }
+ }
+ if (this.cpu.interruptsEnabled) {
+ if (this.TIFR & TOV && this.TIMSK & TOIE) {
+ avrInterrupt(this.cpu, this.config.ovfInterrupt);
+ this.TIFR &= ~TOV;
+ }
+ if (this.TIFR & OCFA && this.TIMSK & OCIEA) {
+ avrInterrupt(this.cpu, this.config.compAInterrupt);
+ this.TIFR &= ~OCFA;
+ }
+ if (this.TIFR & OCFB && this.TIMSK & OCIEB) {
+ avrInterrupt(this.cpu, this.config.compBInterrupt);
+ this.TIFR &= ~OCFB;
+ }
+ }
+ }
+
+ private timerUpdated(value: u8) {
+ if (this.ocrA && value === this.ocrA) {
+ this.TIFR |= OCFA;
+ if (this.WGM === WGM_CTC) {
+ // Clear Timer on Compare Match (CTC) Mode
+ this.TCNT = 0;
+ this.TIFR |= TOV;
}
}
- if (this.TIFR & 0x1 && this.TIMSK & 0x1 && this.cpu.interruptsEnabled) {
- avrInterrupt(this.cpu, this.config.ovfInterrupt);
- this.TIFR &= ~0x1;
+ if (this.ocrB && value === this.ocrB) {
+ this.TIFR |= OCFB;
}
}
}