Skip to content

Commit b02afeb

Browse files
committed
fix(timer): OCR values should be buffered #76
fix #76
1 parent 79c23de commit b02afeb

File tree

2 files changed

+125
-11
lines changed

2 files changed

+125
-11
lines changed

src/peripherals/timer.spec.ts

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -415,13 +415,13 @@ describe('timer', () => {
415415
describe('Phase-correct PWM mode', () => {
416416
it('should count up to TOP, down to 0, and then set TOV flag', () => {
417417
const { program, instructionCount } = asmProgram(`
418+
LDI r16, 0x3 ; OCR0A = 0x3; // <- TOP value
419+
OUT 0x27, r16
418420
; Set waveform generation mode (WGM) to PWM, Phase Correct, top OCR0A
419421
LDI r16, 0x1 ; TCCR0A = 1 << WGM00;
420422
OUT 0x24, r16
421423
LDI r16, 0x9 ; TCCR0B = (1 << WGM02) | (1 << CS00);
422424
OUT 0x25, r16
423-
LDI r16, 0x3 ; OCR0A = 0x3;
424-
OUT 0x27, r16
425425
LDI r16, 0x2 ; TCNT0 = 0x2;
426426
OUT 0x26, r16
427427
@@ -448,13 +448,13 @@ describe('timer', () => {
448448

449449
it('should clear OC0A when TCNT0=OCR0A and counting up', () => {
450450
const { program, lines, instructionCount } = asmProgram(`
451+
LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value
452+
OUT 0x27, r16
451453
; Set waveform generation mode (WGM) to PWM, Phase Correct
452-
LDI r16, 0x81 ; TCCR0A = (1 << COM0A1) || (1 << WGM01);
454+
LDI r16, 0x81 ; TCCR0A = (1 << COM0A1) | (1 << WGM00);
453455
OUT 0x24, r16
454456
LDI r16, 0x1 ; TCCR0B = (1 << CS00);
455457
OUT 0x25, r16
456-
LDI r16, 0xfe ; OCR0A = 0xfe;
457-
OUT 0x27, r16
458458
LDI r16, 0xfd ; TCNT0 = 0xfd;
459459
OUT 0x26, r16
460460
@@ -491,6 +491,46 @@ describe('timer', () => {
491491
expect(cpu.readData(TCNT0)).toEqual(0xfe);
492492
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b);
493493
});
494+
495+
it('should only update OCR0A when TCNT0=TOP in PWM Phase Correct mode (issue #76)', () => {
496+
const { program, instructionCount } = asmProgram(`
497+
LDI r16, 0x4 ; OCR0A = 0x4;
498+
OUT 0x27, r16
499+
; Set waveform generation mode (WGM) to PWM, Phase Correct
500+
LDI r16, 0x01 ; TCCR0A = (1 << WGM00);
501+
OUT 0x24, r16
502+
LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
503+
OUT 0x25, r16
504+
LDI r16, 0x0 ; TCNT0 = 0x0;
505+
OUT 0x26, r16
506+
507+
LDI r16, 0x2 ; OCR0A = 0x2; // TCNT0 should read 0x0
508+
OUT 0x27, r16 ; // TCNT0 should read 0x1
509+
NOP ; // TCNT0 should read 0x2
510+
NOP ; // TCNT0 should read 0x3
511+
IN r17, 0x26 ; R17 = TCNT; // TCNT0 should read 0x4 (that's old OCR0A / TOP)
512+
NOP ; // TCNT0 should read 0x3
513+
NOP ; // TCNT0 should read 0x2
514+
NOP ; // TCNT0 should read 0x1
515+
NOP ; // TCNT0 should read 0x0
516+
NOP ; // TCNT0 should read 0x1
517+
NOP ; // TCNT0 should read 0x2
518+
IN r18, 0x26 ; R18 = TCNT; // TCNT0 should read 0x1
519+
`);
520+
521+
const cpu = new CPU(program);
522+
new AVRTimer(cpu, timer0Config);
523+
524+
// Listen to Port D's internal callback
525+
const gpioCallback = jest.fn();
526+
cpu.gpioTimerHooks[PORTD] = gpioCallback;
527+
528+
const runner = new TestProgramRunner(cpu);
529+
runner.runInstructions(instructionCount);
530+
531+
expect(cpu.readData(R17)).toEqual(0x4);
532+
expect(cpu.readData(R18)).toEqual(0x1);
533+
});
494534
});
495535

496536
describe('16 bit timers', () => {
@@ -638,5 +678,52 @@ describe('timer', () => {
638678
expect(cpu.readData(TCNT1)).toEqual(0x4a);
639679
expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle, 0x25);
640680
});
681+
682+
it('should only update OCR0A when TCNT0=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => {
683+
const { program, instructionCount } = asmProgram(`
684+
LDI r16, 0x0 ; OCR1AH = 0x0;
685+
STS 0x89, r16
686+
LDI r16, 0x4 ; OCR1AL = 0x4;
687+
STS 0x88, r16
688+
; Set waveform generation mode (WGM) to PWM Phase/Frequency Correct mode (9)
689+
LDI r16, 0x01 ; TCCR1A = (1 << WGM10);
690+
STS 0x80, r16
691+
LDI r16, 0x11 ; TCCR1B = (1 << WGM13) | (1 << CS00);
692+
STS 0x81, r16
693+
LDI r16, 0x0 ; TCNT1H = 0x0;
694+
STS 0x85, r16
695+
LDI r16, 0x0 ; TCNT1L = 0x0;
696+
STS 0x84, r16
697+
698+
LDI r16, 0x8 ; OCR1AL = 0x8; // TCNT1 should read 0x0
699+
STS 0x88, r16 ; // TCNT1 should read 0x2 (going up)
700+
LDS r17, 0x84 ; // TCNT1 should read 0x4 (going down)
701+
LDS r18, 0x84 ; // TCNT1 should read 0x2 (going down)
702+
NOP ; // TCNT1 should read 0x0 (going up)
703+
NOP ; // TCNT1 should read 0x1 (going up)
704+
NOP ; // TCNT1 should read 0x2 (going up)
705+
NOP ; // TCNT1 should read 0x3 (going up)
706+
NOP ; // TCNT1 should read 0x4 (going up)
707+
NOP ; // TCNT1 should read 0x5 (going up)
708+
LDS r19, 0x84 ; // TCNT1 should read 0x6 (going up)
709+
NOP ; // TCNT1 should read 0x8 (going up)
710+
LDS r20, 0x84 ; // TCNT1 should read 0x7 (going up)
711+
`);
712+
713+
const cpu = new CPU(program);
714+
new AVRTimer(cpu, timer1Config);
715+
716+
// Listen to Port D's internal callback
717+
const gpioCallback = jest.fn();
718+
cpu.gpioTimerHooks[PORTD] = gpioCallback;
719+
720+
const runner = new TestProgramRunner(cpu);
721+
runner.runInstructions(instructionCount);
722+
723+
expect(cpu.readData(R17)).toEqual(0x4);
724+
expect(cpu.readData(R18)).toEqual(0x2);
725+
expect(cpu.readData(R19)).toEqual(0x6);
726+
expect(cpu.readData(R20)).toEqual(0x7);
727+
});
641728
});
642729
});

src/peripherals/timer.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,10 @@ function compToOverride(comp: CompBitsValue) {
240240
export class AVRTimer {
241241
private lastCycle = 0;
242242
private ocrA: u16 = 0;
243+
private nextOcrA: u16 = 0;
243244
private ocrB: u16 = 0;
245+
private nextOcrB: u16 = 0;
246+
private ocrUpdateMode = OCRUpdateMode.Immediate;
244247
private icr: u16 = 0; // only for 16-bit timers
245248
private timerMode: TimerMode;
246249
private topValue: TimerTopValue;
@@ -297,12 +300,16 @@ export class AVRTimer {
297300
this.timerUpdated();
298301
};
299302
this.cpu.writeHooks[config.OCRA] = (value: u8) => {
300-
// TODO implement buffering when timer running in PWM mode
301-
this.ocrA = (this.highByteTemp << 8) | value;
303+
this.nextOcrA = (this.highByteTemp << 8) | value;
304+
if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
305+
this.ocrA = this.nextOcrA;
306+
}
302307
};
303308
this.cpu.writeHooks[config.OCRB] = (value: u8) => {
304-
// TODO implement buffering when timer running in PWM mode
305-
this.ocrB = (this.highByteTemp << 8) | value;
309+
this.nextOcrB = (this.highByteTemp << 8) | value;
310+
if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
311+
this.ocrB = this.nextOcrB;
312+
}
306313
};
307314
this.cpu.writeHooks[config.ICR] = (value: u8) => {
308315
this.icr = (this.highByteTemp << 8) | value;
@@ -351,7 +358,9 @@ export class AVRTimer {
351358
this.divider = 0;
352359
this.lastCycle = 0;
353360
this.ocrA = 0;
361+
this.nextOcrA = 0;
354362
this.ocrB = 0;
363+
this.nextOcrB = 0;
355364
this.icr = 0;
356365
this.tcnt = 0;
357366
this.tcntNext = 0;
@@ -394,9 +403,10 @@ export class AVRTimer {
394403

395404
private updateWGMConfig() {
396405
const wgmModes = this.config.bits === 16 ? wgmModes16Bit : wgmModes8Bit;
397-
const [timerMode, topValue] = wgmModes[this.WGM];
406+
const [timerMode, topValue, ocrUpdateMode] = wgmModes[this.WGM];
398407
this.timerMode = timerMode;
399408
this.topValue = topValue;
409+
this.ocrUpdateMode = ocrUpdateMode;
400410
}
401411

402412
count = (reschedule = true) => {
@@ -410,14 +420,23 @@ export class AVRTimer {
410420
const { timerMode } = this;
411421
const phasePwm =
412422
timerMode === TimerMode.PWMPhaseCorrect || timerMode === TimerMode.PWMPhaseFrequencyCorrect;
423+
const limit = this.TOP + 1;
413424
const newVal = phasePwm
414425
? this.phasePwmCount(val, counterDelta)
415-
: (val + counterDelta) % (this.TOP + 1);
426+
: (val + counterDelta) % limit;
427+
const overflow = val + counterDelta >= limit;
416428
// A CPU write overrides (has priority over) all counter clear or count operations.
417429
if (!this.tcntUpdated) {
418430
this.tcnt = newVal;
419431
this.timerUpdated();
420432
}
433+
434+
if (!phasePwm && this.ocrUpdateMode == OCRUpdateMode.Bottom && overflow) {
435+
// OCRUpdateMode.Top only occurs in Phase Correct modes, handled by phasePwmCount()
436+
this.ocrA = this.nextOcrA;
437+
this.ocrB = this.nextOcrB;
438+
}
439+
421440
if ((timerMode === TimerMode.Normal || timerMode === TimerMode.FastPWM) && val > newVal) {
422441
cpu.setInterruptFlag(this.OVF);
423442
}
@@ -447,12 +466,20 @@ export class AVRTimer {
447466
value++;
448467
if (value === this.TOP && !this.tcntUpdated) {
449468
this.countingUp = false;
469+
if (this.ocrUpdateMode === OCRUpdateMode.Top) {
470+
this.ocrA = this.nextOcrA;
471+
this.ocrB = this.nextOcrB;
472+
}
450473
}
451474
} else {
452475
value--;
453476
if (!value && !this.tcntUpdated) {
454477
this.countingUp = true;
455478
this.cpu.setInterruptFlag(this.OVF);
479+
if (this.ocrUpdateMode === OCRUpdateMode.Bottom) {
480+
this.ocrA = this.nextOcrA;
481+
this.ocrB = this.nextOcrB;
482+
}
456483
}
457484
}
458485
delta--;

0 commit comments

Comments
 (0)