Skip to content

Commit ae5caeb

Browse files
committed
feat: Output Compare for Timers
close #4
1 parent b76637e commit ae5caeb

File tree

2 files changed

+185
-6
lines changed

2 files changed

+185
-6
lines changed

src/timer.spec.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,30 @@ describe('timer', () => {
4242
expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR
4343
});
4444

45+
it('should set TOV if timer overflows in PWM Phase Correct mode', () => {
46+
const timer = new AVRTimer(cpu, timer0Config);
47+
cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
48+
cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f
49+
cpu.writeData(0x44, 0x1); // WGM0 <- 1 (PWM, Phase Correct)
50+
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
51+
cpu.cycles = 1;
52+
timer.tick();
53+
expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
54+
expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR
55+
});
56+
57+
it('should set TOV if timer overflows in FAST PWM mode', () => {
58+
const timer = new AVRTimer(cpu, timer0Config);
59+
cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
60+
cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f
61+
cpu.writeData(0x44, 0x3); // WGM0 <- 3 (FAST PWM)
62+
cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1
63+
cpu.cycles = 1;
64+
timer.tick();
65+
expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
66+
expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR
67+
});
68+
4569
it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => {
4670
const timer = new AVRTimer(cpu, timer0Config);
4771
cpu.data[0x46] = 0xff; // TCNT0 <- 0xff
@@ -81,4 +105,87 @@ describe('timer', () => {
81105
expect(cpu.pc).toEqual(0);
82106
expect(cpu.cycles).toEqual(1);
83107
});
108+
109+
it('should set OCF0A flag when timer equals OCRA', () => {
110+
const timer = new AVRTimer(cpu, timer0Config);
111+
cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10
112+
cpu.writeData(0x47, 0x11); // OCR0A <- 0x11
113+
cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal)
114+
cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
115+
cpu.cycles = 1;
116+
timer.tick();
117+
expect(cpu.data[0x35]).toEqual(2); // TIFR0 should have OCF0A bit on
118+
expect(cpu.pc).toEqual(0);
119+
expect(cpu.cycles).toEqual(1);
120+
});
121+
122+
it('should clear the timer in CTC mode if it equals to OCRA', () => {
123+
const timer = new AVRTimer(cpu, timer0Config);
124+
cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10
125+
cpu.writeData(0x47, 0x11); // OCR0A <- 0x11
126+
cpu.writeData(0x44, 0x2); // WGM0 <- 2 (CTC)
127+
cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
128+
cpu.cycles = 1;
129+
timer.tick();
130+
expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0
131+
expect(cpu.pc).toEqual(0);
132+
expect(cpu.cycles).toEqual(1);
133+
});
134+
135+
it('should set OCF0B flag when timer equals OCRB', () => {
136+
const timer = new AVRTimer(cpu, timer0Config);
137+
cpu.writeData(0x46, 0x10); // TCNT0 <- 0x50
138+
cpu.writeData(0x48, 0x11); // OCR0B <- 0x51
139+
cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal)
140+
cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
141+
cpu.cycles = 1;
142+
timer.tick();
143+
expect(cpu.data[0x35]).toEqual(4); // TIFR0 should have OCF0B bit on
144+
expect(cpu.pc).toEqual(0);
145+
expect(cpu.cycles).toEqual(1);
146+
});
147+
148+
it('should generate Timer Compare A interrupt when TCNT0 == TCNTA', () => {
149+
const timer = new AVRTimer(cpu, timer0Config);
150+
cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20
151+
cpu.writeData(0x47, 0x21); // OCR0A <- 0x21
152+
cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
153+
cpu.writeData(0x6e, 0x2); // TIMSK0: OCIEA
154+
cpu.writeData(95, 0x80); // SREG: I-------
155+
cpu.cycles = 1;
156+
timer.tick();
157+
expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
158+
expect(cpu.data[0x35]).toEqual(0); // OCFA bit in TIFR should be clear
159+
expect(cpu.pc).toEqual(0x1c);
160+
expect(cpu.cycles).toEqual(3);
161+
});
162+
163+
it('should not generate Timer Compare A interrupt when OCIEA is disabled', () => {
164+
const timer = new AVRTimer(cpu, timer0Config);
165+
cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20
166+
cpu.writeData(0x47, 0x21); // OCR0A <- 0x21
167+
cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
168+
cpu.writeData(0x6e, 0); // TIMSK0
169+
cpu.writeData(95, 0x80); // SREG: I-------
170+
cpu.cycles = 1;
171+
timer.tick();
172+
expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
173+
expect(cpu.pc).toEqual(0);
174+
expect(cpu.cycles).toEqual(1);
175+
});
176+
177+
it('should generate Timer Compare B interrupt when TCNT0 == TCNTB', () => {
178+
const timer = new AVRTimer(cpu, timer0Config);
179+
cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20
180+
cpu.writeData(0x48, 0x21); // OCR0B <- 0x21
181+
cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1
182+
cpu.writeData(0x6e, 0x4); // TIMSK0: OCIEB
183+
cpu.writeData(95, 0x80); // SREG: I-------
184+
cpu.cycles = 1;
185+
timer.tick();
186+
expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21
187+
expect(cpu.data[0x35]).toEqual(0); // OCFB bit in TIFR should be clear
188+
expect(cpu.pc).toEqual(0x1e);
189+
expect(cpu.cycles).toEqual(3);
190+
});
84191
});

src/timer.ts

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ const dividers = {
2020
7: 0 // TODO: External clock source on T0 pin. Clock on rising edge.
2121
};
2222

23+
const WGM_NORMAL = 0;
24+
const WGM_PWM_PHASE_CORRECT = 1;
25+
const WGM_CTC = 2;
26+
const WGM_FASTPWM = 3;
27+
28+
const TOV = 1;
29+
const OCFA = 2;
30+
const OCFB = 4;
31+
32+
const TOIE = 1;
33+
const OCIEA = 2;
34+
const OCIEB = 4;
35+
2336
type u8 = number;
2437

2538
interface AVRTimerConfig {
@@ -95,8 +108,29 @@ export const timer2Config: AVRTimerConfig = {
95108
export class AVRTimer {
96109
private mask = (1 << this.config.bits) - 1;
97110
private lastCycle = 0;
111+
private ocrA: u8 = 0;
112+
private ocrB: u8 = 0;
113+
114+
constructor(private cpu: CPU, private config: AVRTimerConfig) {
115+
cpu.writeHooks[config.TCNT] = (value: u8) => {
116+
this.TCNT = value;
117+
this.timerUpdated(value);
118+
return true;
119+
};
120+
cpu.writeHooks[config.OCRA] = (value: u8) => {
121+
// TODO implement buffering when timer running in PWM mode
122+
this.ocrA = value;
123+
};
124+
cpu.writeHooks[config.OCRB] = (value: u8) => {
125+
this.ocrB = value;
126+
};
127+
}
98128

99-
constructor(private cpu: CPU, private config: AVRTimerConfig) {}
129+
reset() {
130+
this.lastCycle = 0;
131+
this.ocrA = 0;
132+
this.ocrB = 0;
133+
}
100134

101135
get TIFR() {
102136
return this.cpu.data[this.config.TIFR];
@@ -114,6 +148,10 @@ export class AVRTimer {
114148
this.cpu.data[this.config.TCNT] = value;
115149
}
116150

151+
get TCCRA() {
152+
return this.cpu.data[this.config.TCCRA];
153+
}
154+
117155
get TCCRB() {
118156
return this.cpu.data[this.config.TCCRB];
119157
}
@@ -126,6 +164,10 @@ export class AVRTimer {
126164
return (this.TCCRB & 0x7) as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
127165
}
128166

167+
get WGM() {
168+
return ((this.TCCRB & 0x8) >> 1) | (this.TCCRA & 0x3);
169+
}
170+
129171
tick() {
130172
const divider = dividers[this.CS];
131173
const delta = this.cpu.cycles - this.lastCycle;
@@ -135,13 +177,43 @@ export class AVRTimer {
135177
const val = this.TCNT;
136178
const newVal = (val + counterDelta) & this.mask;
137179
this.TCNT = newVal;
138-
if (val > newVal) {
139-
this.TIFR |= 1; // TOV
180+
this.timerUpdated(newVal);
181+
if (
182+
(this.WGM === WGM_NORMAL ||
183+
this.WGM === WGM_PWM_PHASE_CORRECT ||
184+
this.WGM === WGM_FASTPWM) &&
185+
val > newVal
186+
) {
187+
this.TIFR |= TOV;
188+
}
189+
}
190+
if (this.cpu.interruptsEnabled) {
191+
if (this.TIFR & TOV && this.TIMSK & TOIE) {
192+
avrInterrupt(this.cpu, this.config.ovfInterrupt);
193+
this.TIFR &= ~TOV;
194+
}
195+
if (this.TIFR & OCFA && this.TIMSK & OCIEA) {
196+
avrInterrupt(this.cpu, this.config.compAInterrupt);
197+
this.TIFR &= ~OCFA;
198+
}
199+
if (this.TIFR & OCFB && this.TIMSK & OCIEB) {
200+
avrInterrupt(this.cpu, this.config.compBInterrupt);
201+
this.TIFR &= ~OCFB;
202+
}
203+
}
204+
}
205+
206+
private timerUpdated(value: u8) {
207+
if (this.ocrA && value === this.ocrA) {
208+
this.TIFR |= OCFA;
209+
if (this.WGM === WGM_CTC) {
210+
// Clear Timer on Compare Match (CTC) Mode
211+
this.TCNT = 0;
212+
this.TIFR |= TOV;
140213
}
141214
}
142-
if (this.TIFR & 0x1 && this.TIMSK & 0x1 && this.cpu.interruptsEnabled) {
143-
avrInterrupt(this.cpu, this.config.ovfInterrupt);
144-
this.TIFR &= ~0x1;
215+
if (this.ocrB && value === this.ocrB) {
216+
this.TIFR |= OCFB;
145217
}
146218
}
147219
}

0 commit comments

Comments
 (0)