Skip to content

Commit 988070a

Browse files
committed
fix(timer): Output Compare in PWM modes #78
close #78
1 parent 9c526a6 commit 988070a

File tree

6 files changed

+353
-67
lines changed

6 files changed

+353
-67
lines changed

src/peripherals/spi.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ describe('SPI', () => {
160160
return 0x5b; // we copy this byte to
161161
};
162162

163-
const runner = new TestProgramRunner(cpu);
163+
const runner = new TestProgramRunner(cpu, () => 0);
164164
runner.runToBreak();
165165

166166
// 16 cycles per clock * 8 bits = 128

src/peripherals/timer.spec.ts

Lines changed: 214 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ describe('timer', () => {
246246
cpu.tick();
247247
cpu.cycles = 2;
248248
cpu.tick();
249+
expect(cpu.readData(TCNT0)).toEqual(0);
249250
expect(cpu.data[TIFR0] & (OCF0A | OCF0B)).toEqual(OCF0A | OCF0B);
250251
expect(cpu.pc).toEqual(0);
251252
expect(cpu.cycles).toEqual(2);
@@ -417,7 +418,7 @@ describe('timer', () => {
417418
const { program, instructionCount } = asmProgram(`
418419
LDI r16, 0x1 ; TCCR0B = 1 << CS00;
419420
OUT 0x25, r16
420-
LDI r16, 0x30 ; TCNT <- 0x30
421+
LDI r16, 0x30 ; TCNT0 <- 0x30
421422
OUT 0x26, r16
422423
NOP
423424
IN r17, 0x26 ; r17 <- TCNT
@@ -495,6 +496,152 @@ describe('timer', () => {
495496
expect(cpu.readData(R17)).toEqual(2);
496497
});
497498

499+
describe('Fast PWM mode', () => {
500+
it('should set OC0A on Compare Match, clear on Bottom (issue #78)', () => {
501+
const { program, labels } = asmProgram(`
502+
LDI r16, 0xfc ; TCNT0 = 0xfc;
503+
OUT 0x26, r16
504+
LDI r16, 0xfe ; OCR0A = 0xfe;
505+
OUT 0x27, r16
506+
; WGM: Fast PWM, enable OC0A mode 3 (set on Compare Match, clear on Bottom)
507+
LDI r16, 0xc3 ; TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
508+
OUT 0x24, r16
509+
LDI r16, 0x1 ; TCCR0B = 1 << CS00;
510+
OUT 0x25, r16
511+
512+
NOP ; TCNT is now 0xfd
513+
beforeMatch:
514+
NOP ; TCNT is now 0xfe (Compare Match)
515+
afterMatch:
516+
NOP ; TCNT is now 0xff
517+
beforeBottom:
518+
NOP ; TCNT is now 0x00 (BOTTOM)
519+
afterBottom:
520+
NOP
521+
`);
522+
523+
const cpu = new CPU(program);
524+
new AVRTimer(cpu, timer0Config);
525+
526+
// Listen to Port D's internal callback
527+
const gpioCallback = jest.fn();
528+
cpu.gpioTimerHooks[PORTD] = gpioCallback;
529+
530+
const runner = new TestProgramRunner(cpu);
531+
532+
runner.runToAddress(labels.beforeMatch);
533+
expect(cpu.readData(TCNT0)).toEqual(0xfd);
534+
expect(gpioCallback).toHaveBeenCalledTimes(1);
535+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); // OC0A: Enable
536+
gpioCallback.mockClear();
537+
538+
runner.runToAddress(labels.afterMatch);
539+
expect(cpu.readData(TCNT0)).toEqual(0xfe);
540+
expect(gpioCallback).toHaveBeenCalledTimes(1);
541+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b); // OC0A: Set
542+
gpioCallback.mockClear();
543+
544+
runner.runToAddress(labels.beforeBottom);
545+
expect(cpu.readData(TCNT0)).toEqual(0xff);
546+
expect(gpioCallback).toHaveBeenCalledTimes(0);
547+
gpioCallback.mockClear();
548+
549+
runner.runToAddress(labels.afterBottom);
550+
expect(cpu.readData(TCNT0)).toEqual(0x0);
551+
expect(gpioCallback).toHaveBeenCalledTimes(1);
552+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Clear, 0x2b); // OC0A: Clear
553+
});
554+
555+
it('should toggle OC0A on Compare Match when COM0An = 1 (issue #78)', () => {
556+
const { program, labels } = asmProgram(`
557+
LDI r16, 0xfc ; TCNT0 = 0xfc;
558+
OUT 0x26, r16
559+
LDI r16, 0xfe ; OCR0A = 0xfe;
560+
OUT 0x27, r16
561+
; WGM: Fast PWM, enable OC0A mode 1 (Toggle)
562+
LDI r16, 0x43 ; TCCR0A = (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
563+
OUT 0x24, r16
564+
LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
565+
OUT 0x25, r16
566+
567+
NOP ; TCNT is now 0xfd
568+
beforeMatch:
569+
NOP ; TCNT is now 0xfe (Compare Match, TOP)
570+
afterMatch:
571+
NOP ; TCNT is now 0
572+
afterOverflow:
573+
NOP
574+
`);
575+
576+
const cpu = new CPU(program);
577+
new AVRTimer(cpu, timer0Config);
578+
579+
// Listen to Port D's internal callback
580+
const gpioCallback = jest.fn();
581+
cpu.gpioTimerHooks[PORTD] = gpioCallback;
582+
583+
const runner = new TestProgramRunner(cpu);
584+
585+
runner.runToAddress(labels.beforeMatch);
586+
expect(cpu.readData(TCNT0)).toEqual(0xfd);
587+
expect(gpioCallback).toHaveBeenCalledTimes(1);
588+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); // OC0A: Enable
589+
gpioCallback.mockClear();
590+
591+
runner.runToAddress(labels.afterMatch);
592+
expect(cpu.readData(TCNT0)).toEqual(0xfe);
593+
expect(gpioCallback).toHaveBeenCalledTimes(1);
594+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle, 0x2b); // OC0A: Toggle
595+
gpioCallback.mockClear();
596+
597+
runner.runToAddress(labels.afterOverflow);
598+
expect(cpu.readData(TCNT0)).toEqual(0);
599+
expect(gpioCallback).toHaveBeenCalledTimes(0);
600+
});
601+
602+
it('should leave OC0A disconnected when COM0An = 1 and WGM02 = 0 (issue #78)', () => {
603+
const { program, labels } = asmProgram(`
604+
LDI r16, 0xfc ; TCNT0 = 0xfc;
605+
OUT 0x26, r16
606+
LDI r16, 0xfe ; OCR0A = 0xfe;
607+
OUT 0x27, r16
608+
; WGM: Fast PWM mode 7, enable OC0A mode 1 (Toggle)
609+
LDI r16, 0x43 ; TCCR0A = (1 << COM0A0) | (1 << WGM01) | (1 << WGM00);
610+
OUT 0x24, r16
611+
LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
612+
OUT 0x25, r16
613+
614+
beforeClearWGM02:
615+
LDI r16, 0x01 ; TCCR0B = (1 << CS00);
616+
OUT 0x25, r16
617+
618+
afterClearWGM02:
619+
NOP
620+
`);
621+
622+
const cpu = new CPU(program);
623+
new AVRTimer(cpu, timer0Config);
624+
625+
// Listen to Port D's internal callback
626+
const gpioCallback = jest.fn();
627+
cpu.gpioTimerHooks[PORTD] = gpioCallback;
628+
629+
const runner = new TestProgramRunner(cpu);
630+
631+
// First, run with the bit set and assert that the Pin Override was enabled (OC0A connected)
632+
runner.runToAddress(labels.beforeClearWGM02);
633+
expect(gpioCallback).toHaveBeenCalledTimes(1);
634+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b);
635+
gpioCallback.mockClear();
636+
637+
// Now clear WGM02 and observe that Pin Override was disabled (OC0A disconnected)
638+
runner.runToAddress(labels.afterClearWGM02);
639+
expect(gpioCallback).toHaveBeenCalledTimes(1);
640+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.None, 0x2b);
641+
gpioCallback.mockClear();
642+
});
643+
});
644+
498645
describe('Phase-correct PWM mode', () => {
499646
it('should count up to TOP, down to 0, and then set TOV flag', () => {
500647
const { program, instructionCount } = asmProgram(`
@@ -575,6 +722,71 @@ describe('timer', () => {
575722
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b);
576723
});
577724

725+
it('should toggle OC0A when TCNT0=OCR0A and COM0An=1 (issue #78)', () => {
726+
const { program, labels } = asmProgram(`
727+
LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value
728+
OUT 0x27, r16
729+
; Set waveform generation mode (WGM) to PWM, Phase Correct (mode 5)
730+
LDI r16, 0x41 ; TCCR0A = (1 << COM0A0) | (1 << WGM00);
731+
OUT 0x24, r16
732+
LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
733+
OUT 0x25, r16
734+
LDI r16, 0xfd ; TCNT0 = 0xfd;
735+
OUT 0x26, r16
736+
737+
beforeMatch:
738+
NOP ; TCNT0 will be 0xfe
739+
afterMatch:
740+
NOP
741+
`);
742+
743+
const cpu = new CPU(program);
744+
new AVRTimer(cpu, timer0Config);
745+
746+
// Listen to Port D's internal callback
747+
const gpioCallback = jest.fn();
748+
cpu.gpioTimerHooks[PORTD] = gpioCallback;
749+
750+
const runner = new TestProgramRunner(cpu);
751+
752+
runner.runToAddress(labels.beforeMatch);
753+
expect(cpu.readData(TCNT0)).toEqual(0xfd);
754+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b);
755+
gpioCallback.mockClear();
756+
757+
runner.runToAddress(labels.afterMatch);
758+
expect(cpu.readData(TCNT0)).toEqual(0xfe);
759+
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle, 0x2b);
760+
gpioCallback.mockClear();
761+
});
762+
763+
it('should leave OC0A disconnected TCNT0=OCR0A and COM0An=1 in WGM mode 1 (issue #78)', () => {
764+
const { program, instructionCount } = asmProgram(`
765+
LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value
766+
OUT 0x27, r16
767+
; Set waveform generation mode (WGM) to PWM, Phase Correct (mode 1)
768+
LDI r16, 0x41 ; TCCR0A = (1 << COM0A0) | (1 << WGM00);
769+
OUT 0x24, r16
770+
LDI r16, 0x01 ; TCCR0B = (1 << CS00);
771+
OUT 0x25, r16
772+
LDI r16, 0xfd ; TCNT0 = 0xfd;
773+
OUT 0x26, r16
774+
`);
775+
776+
const cpu = new CPU(program);
777+
new AVRTimer(cpu, timer0Config);
778+
779+
// Listen to Port D's internal callback
780+
const gpioCallback = jest.fn();
781+
cpu.gpioTimerHooks[PORTD] = gpioCallback;
782+
783+
const runner = new TestProgramRunner(cpu);
784+
runner.runInstructions(instructionCount);
785+
786+
// Assert that the pin callback wasn't called (thus it's disconnected)
787+
expect(gpioCallback).not.toHaveBeenCalled();
788+
});
789+
578790
it('should not miss Compare Match when executing multi-cycle instruction (issue #79)', () => {
579791
const { program, instructionCount } = asmProgram(`
580792
LDI r16, 0x10 ; OCR0A = 0x10; // <- TOP value
@@ -793,7 +1005,7 @@ describe('timer', () => {
7931005
expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle, 0x25);
7941006
});
7951007

796-
it('should only update OCR0A when TCNT0=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => {
1008+
it('should only update OCR1A when TCNT1=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => {
7971009
const { program, instructionCount } = asmProgram(`
7981010
LDI r16, 0x0 ; OCR1AH = 0x0;
7991011
STS 0x89, r16

0 commit comments

Comments
 (0)