aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals
diff options
context:
space:
mode:
Diffstat (limited to 'src/peripherals')
-rw-r--r--src/peripherals/timer.spec.ts74
-rw-r--r--src/peripherals/timer.ts109
2 files changed, 170 insertions, 13 deletions
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts
index d89fba2..c1bf1e1 100644
--- a/src/peripherals/timer.spec.ts
+++ b/src/peripherals/timer.spec.ts
@@ -925,6 +925,31 @@ describe('timer', () => {
expect(cpu.cycles).toEqual(2);
});
+ it('should set OCF1C flag when timer equals OCRC', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const OCR1C = 0x8c;
+ const OCR1CH = 0x8d;
+ const OCF1C = 1 << 3;
+ new AVRTimer(cpu, {
+ ...timer1Config,
+ OCRC: OCR1C,
+ OCFC: OCF1C,
+ });
+ cpu.writeData(TCNT1H, 0);
+ cpu.writeData(TCNT1, 0x10);
+ cpu.writeData(OCR1C, 0x11);
+ cpu.writeData(OCR1CH, 0x11);
+ cpu.writeData(TCCR1A, 0x0); // WGM: (Normal)
+ cpu.writeData(TCCR1B, CS00); // Set prescaler to 1
+ cpu.cycles = 1;
+ cpu.tick();
+ cpu.cycles = 2;
+ cpu.tick();
+ expect(cpu.data[TIFR1]).toEqual(OCF1C);
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.cycles).toEqual(2);
+ });
+
it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => {
const cpu = new CPU(new Uint16Array(0x1000));
new AVRTimer(cpu, timer1Config);
@@ -1034,6 +1059,55 @@ describe('timer', () => {
expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle);
});
+ it('should toggle OC1C on Compare Match', () => {
+ const OCR1C = 0x8c;
+ const OCR1CH = 0x8d;
+ const OCF1C = 1 << 3;
+ const { program, lines, instructionCount } = asmProgram(`
+ ; Set waveform generation mode (WGM) to Normal, top 0xFFFF
+ LDI r16, 0x04 ; TCCR1A = (1 << COM1C0);
+ STS ${TCCR1A}, r16
+ LDI r16, 0x1 ; TCCR1B = (1 << CS00);
+ STS ${TCCR1B}, r16
+ LDI r16, 0x0 ; OCR1CH = 0x0;
+ STS ${OCR1CH}, r16
+ LDI r16, 0x4a ; OCR1C = 0x4a;
+ STS ${OCR1C}, r16
+ LDI r16, 0x0 ; TCNT1H = 0x0;
+ STS ${TCNT1H}, r16
+ LDI r16, 0x49 ; TCNT1 = 0x49;
+ STS ${TCNT1}, r16
+
+ NOP ; TCNT1 will be 0x49
+ NOP ; TCNT1 will be 0x4a
+ `);
+
+ const cpu = new CPU(program);
+ new AVRTimer(cpu, {
+ ...timer1Config,
+ OCRC: OCR1C,
+ OCFC: OCF1C,
+ compPortC: portBConfig.PORT,
+ compPinC: 3,
+ });
+
+ // Listen to Port B's internal callback
+ const portB = new AVRIOPort(cpu, portBConfig);
+ const gpioCallback = jest.spyOn(portB, 'timerOverridePin');
+
+ const nopCount = lines.filter((line) => line.bytes == nopOpCode).length;
+ const runner = new TestProgramRunner(cpu);
+ runner.runInstructions(instructionCount - nopCount);
+
+ expect(cpu.readData(TCNT1)).toEqual(0x49);
+ expect(gpioCallback).toHaveBeenCalledWith(3, PinOverrideMode.Enable);
+ gpioCallback.mockClear();
+
+ runner.runInstructions(1);
+ expect(cpu.readData(TCNT1)).toEqual(0x4a);
+ expect(gpioCallback).toHaveBeenCalledWith(3, PinOverrideMode.Toggle);
+ });
+
it('should only update OCR1A when TCNT1=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => {
const { program, instructionCount } = asmProgram(`
LDI r16, 0x0 ; OCR1AH = 0x0;
diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts
index 7b54d3f..35e8efd 100644
--- a/src/peripherals/timer.ts
+++ b/src/peripherals/timer.ts
@@ -47,12 +47,14 @@ export interface AVRTimerConfig {
captureInterrupt: u8;
compAInterrupt: u8;
compBInterrupt: u8;
+ compCInterrupt: u8; // Optional, 0 = unused
ovfInterrupt: u8;
// Register addresses
TIFR: u8;
OCRA: u8;
OCRB: u8;
+ OCRC: u8; // Optional, 0 = unused
ICR: u8;
TCNT: u8;
TCCRA: u8;
@@ -64,17 +66,21 @@ export interface AVRTimerConfig {
TOV: u8;
OCFA: u8;
OCFB: u8;
+ OCFC: u8; // Optional, if compCInterrupt != 0
// TIMSK bits
TOIE: u8;
OCIEA: u8;
OCIEB: u8;
+ OCIEC: u8; // Optional, if compCInterrupt != 0
// Output compare pins
compPortA: u16;
compPinA: u8;
compPortB: u16;
compPinB: u8;
+ compPortC: u16; // Optional, 0 = unused
+ compPinC: u16;
// External clock pin (optional, 0 = unused)
externalClockPort: u16;
@@ -87,11 +93,13 @@ const defaultTimerBits = {
TOV: 1,
OCFA: 2,
OCFB: 4,
+ OCFC: 0, // Unused
// TIMSK bits
TOIE: 1,
OCIEA: 2,
OCIEB: 4,
+ OCIEC: 0, // Unused
};
export const timer0Config: AVRTimerConfig = {
@@ -99,10 +107,12 @@ export const timer0Config: AVRTimerConfig = {
captureInterrupt: 0, // not available
compAInterrupt: 0x1c,
compBInterrupt: 0x1e,
+ compCInterrupt: 0,
ovfInterrupt: 0x20,
TIFR: 0x35,
OCRA: 0x47,
OCRB: 0x48,
+ OCRC: 0, // not available
ICR: 0, // not available
TCNT: 0x46,
TCCRA: 0x44,
@@ -114,6 +124,8 @@ export const timer0Config: AVRTimerConfig = {
compPinA: 6,
compPortB: portDConfig.PORT,
compPinB: 5,
+ compPortC: 0, // Not available
+ compPinC: 0,
externalClockPort: portDConfig.PORT,
externalClockPin: 4,
...defaultTimerBits,
@@ -124,10 +136,12 @@ export const timer1Config: AVRTimerConfig = {
captureInterrupt: 0x14,
compAInterrupt: 0x16,
compBInterrupt: 0x18,
+ compCInterrupt: 0,
ovfInterrupt: 0x1a,
TIFR: 0x36,
OCRA: 0x88,
OCRB: 0x8a,
+ OCRC: 0, // not available
ICR: 0x86,
TCNT: 0x84,
TCCRA: 0x80,
@@ -139,6 +153,8 @@ export const timer1Config: AVRTimerConfig = {
compPinA: 1,
compPortB: portBConfig.PORT,
compPinB: 2,
+ compPortC: 0, // Not available
+ compPinC: 0,
externalClockPort: portDConfig.PORT,
externalClockPin: 5,
...defaultTimerBits,
@@ -149,10 +165,12 @@ export const timer2Config: AVRTimerConfig = {
captureInterrupt: 0, // not available
compAInterrupt: 0x0e,
compBInterrupt: 0x10,
+ compCInterrupt: 0,
ovfInterrupt: 0x12,
TIFR: 0x37,
OCRA: 0xb3,
OCRB: 0xb4,
+ OCRC: 0, // not available
ICR: 0, // not available
TCNT: 0xb2,
TCCRA: 0xb0,
@@ -173,6 +191,8 @@ export const timer2Config: AVRTimerConfig = {
compPinA: 3,
compPortB: portDConfig.PORT,
compPinB: 3,
+ compPortC: 0, // Not available
+ compPinC: 0,
externalClockPort: 0, // Not available
externalClockPin: 0,
...defaultTimerBits,
@@ -264,6 +284,9 @@ export class AVRTimer {
private nextOcrA: u16 = 0;
private ocrB: u16 = 0;
private nextOcrB: u16 = 0;
+ private hasOCRC = this.config.OCRC > 0;
+ private ocrC: u16 = 0;
+ private nextOcrC: u16 = 0;
private ocrUpdateMode = OCRUpdateMode.Immediate;
private tovUpdateMode = TOVUpdateMode.Max;
private icr: u16 = 0; // only for 16-bit timers
@@ -273,6 +296,7 @@ export class AVRTimer {
private tcntNext: u16 = 0;
private compA: CompBitsValue;
private compB: CompBitsValue;
+ private compC: CompBitsValue;
private tcntUpdated = false;
private updateDivider = false;
private countingUp = true;
@@ -305,6 +329,13 @@ export class AVRTimer {
enableRegister: this.config.TIMSK,
enableMask: this.config.OCIEB,
};
+ private OCFC: AVRInterruptConfig = {
+ address: this.config.compCInterrupt,
+ flagRegister: this.config.TIFR,
+ flagMask: this.config.OCFC,
+ enableRegister: this.config.TIMSK,
+ enableMask: this.config.OCIEC,
+ };
constructor(private cpu: CPU, private config: AVRTimerConfig) {
this.updateWGMConfig();
@@ -337,6 +368,14 @@ export class AVRTimer {
this.ocrB = this.nextOcrB;
}
};
+ if (this.hasOCRC) {
+ this.cpu.writeHooks[config.OCRC] = (value: u8) => {
+ this.nextOcrC = (this.highByteTemp << 8) | value;
+ if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
+ this.ocrC = this.nextOcrC;
+ }
+ };
+ }
if (this.config.bits === 16) {
this.cpu.writeHooks[config.ICR] = (value: u8) => {
this.icr = (this.highByteTemp << 8) | value;
@@ -347,6 +386,9 @@ export class AVRTimer {
this.cpu.writeHooks[config.TCNT + 1] = updateTempRegister;
this.cpu.writeHooks[config.OCRA + 1] = updateTempRegister;
this.cpu.writeHooks[config.OCRB + 1] = updateTempRegister;
+ if (this.hasOCRC) {
+ this.cpu.writeHooks[config.OCRC + 1] = updateTempRegister;
+ }
this.cpu.writeHooks[config.ICR + 1] = updateTempRegister;
}
cpu.writeHooks[config.TCCRA] = (value) => {
@@ -383,6 +425,8 @@ export class AVRTimer {
this.nextOcrA = 0;
this.ocrB = 0;
this.nextOcrB = 0;
+ this.ocrC = 0;
+ this.nextOcrC = 0;
this.icr = 0;
this.tcnt = 0;
this.tcntNext = 0;
@@ -455,6 +499,17 @@ export class AVRTimer {
if (!!prevCompB !== !!this.compB) {
this.updateCompB(this.compB ? PinOverrideMode.Enable : PinOverrideMode.None);
}
+
+ if (this.hasOCRC) {
+ const prevCompC = this.compC;
+ this.compC = ((TCCRA >> 2) & 0x3) as CompBitsValue;
+ if (this.compC === 1 && pwmMode) {
+ this.compC = 0; // Reserved, according to the datasheet
+ }
+ if (!!prevCompC !== !!this.compC) {
+ this.updateCompC(this.compC ? PinOverrideMode.Enable : PinOverrideMode.None);
+ }
+ }
}
count = (reschedule = true, external = false) => {
@@ -494,6 +549,7 @@ export class AVRTimer {
// OCRUpdateMode.Top only occurs in Phase Correct modes, handled by phasePwmCount()
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
+ this.ocrC = this.nextOcrC;
}
// OCRUpdateMode.Bottom only occurs in Phase Correct modes, handled by phasePwmCount().
@@ -544,7 +600,7 @@ export class AVRTimer {
};
private phasePwmCount(value: u16, delta: u8) {
- const { ocrA, ocrB, TOP, tcntUpdated } = this;
+ const { ocrA, ocrB, ocrC, hasOCRC, TOP, tcntUpdated } = this;
while (delta > 0) {
if (this.countingUp) {
value++;
@@ -553,6 +609,7 @@ export class AVRTimer {
if (this.ocrUpdateMode === OCRUpdateMode.Top) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
+ this.ocrC = this.nextOcrC;
}
}
} else {
@@ -563,19 +620,28 @@ export class AVRTimer {
if (this.ocrUpdateMode === OCRUpdateMode.Bottom) {
this.ocrA = this.nextOcrA;
this.ocrB = this.nextOcrB;
+ this.ocrC = this.nextOcrC;
}
}
}
- if (!tcntUpdated && value === ocrA) {
- this.cpu.setInterruptFlag(this.OCFA);
- if (this.compA) {
- this.updateCompPin(this.compA, 'A');
+ if (!tcntUpdated) {
+ if (value === ocrA) {
+ this.cpu.setInterruptFlag(this.OCFA);
+ if (this.compA) {
+ this.updateCompPin(this.compA, 'A');
+ }
}
- }
- if (!tcntUpdated && value === ocrB) {
- this.cpu.setInterruptFlag(this.OCFB);
- if (this.compB) {
- this.updateCompPin(this.compB, 'B');
+ if (value === ocrB) {
+ this.cpu.setInterruptFlag(this.OCFB);
+ if (this.compB) {
+ this.updateCompPin(this.compB, 'B');
+ }
+ }
+ if (hasOCRC && value === ocrC) {
+ this.cpu.setInterruptFlag(this.OCFC);
+ if (this.compC) {
+ this.updateCompPin(this.compC, 'C');
+ }
}
}
delta--;
@@ -584,7 +650,7 @@ export class AVRTimer {
}
private timerUpdated(value: number, prevValue: number) {
- const { ocrA, ocrB } = this;
+ const { ocrA, ocrB, ocrC, hasOCRC } = this;
const overflow = prevValue > value;
if (((prevValue < ocrA || overflow) && value >= ocrA) || (prevValue < ocrA && overflow)) {
this.cpu.setInterruptFlag(this.OCFA);
@@ -598,9 +664,18 @@ export class AVRTimer {
this.updateCompPin(this.compB, 'B');
}
}
+ if (
+ hasOCRC &&
+ (((prevValue < ocrC || overflow) && value >= ocrC) || (prevValue < ocrC && overflow))
+ ) {
+ this.cpu.setInterruptFlag(this.OCFC);
+ if (this.compC) {
+ this.updateCompPin(this.compC, 'C');
+ }
+ }
}
- private updateCompPin(compValue: CompBitsValue, pinName: 'A' | 'B', bottom = false) {
+ private updateCompPin(compValue: CompBitsValue, pinName: 'A' | 'B' | 'C', bottom = false) {
let newValue: PinOverrideMode = PinOverrideMode.None;
const invertingMode = compValue === 3;
const isSet = this.countingUp === invertingMode;
@@ -631,8 +706,10 @@ export class AVRTimer {
if (newValue !== PinOverrideMode.None) {
if (pinName === 'A') {
this.updateCompA(newValue);
- } else {
+ } else if (pinName === 'B') {
this.updateCompB(newValue);
+ } else {
+ this.updateCompC(newValue);
}
}
}
@@ -648,4 +725,10 @@ export class AVRTimer {
const port = this.cpu.gpioByPort[compPortB];
port?.timerOverridePin(compPinB, value);
}
+
+ private updateCompC(value: PinOverrideMode) {
+ const { compPortC, compPinC } = this.config;
+ const port = this.cpu.gpioByPort[compPortC];
+ port?.timerOverridePin(compPinC, value);
+ }
}