aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUri Shaked2019-11-30 19:55:01 +0200
committerUri Shaked2019-11-30 19:55:01 +0200
commitae5caeb0b044a93bebcb641e1dd4ff9a390da26f (patch)
treed83060286b949704d01f2479f7b167840c5d0798
parentchore: release 0.3.2 (diff)
downloadavr8js-ae5caeb0b044a93bebcb641e1dd4ff9a390da26f.tar.gz
avr8js-ae5caeb0b044a93bebcb641e1dd4ff9a390da26f.tar.bz2
avr8js-ae5caeb0b044a93bebcb641e1dd4ff9a390da26f.zip
feat: Output Compare for Timers
close #4
-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;
}
}
}
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.