diff options
| author | Uri Shaked | 2020-12-27 00:05:54 +0200 |
|---|---|---|
| committer | Uri Shaked | 2020-12-27 00:05:54 +0200 |
| commit | 988070a92d4654a40a0bf55a9b3ff5c0b7e1ae5e (patch) | |
| tree | 07e6c0467ea0f47e31d69afa9e8e6e2377791dbc /src/peripherals/timer.spec.ts | |
| parent | 0.14.7 (diff) | |
| download | avr8js-988070a92d4654a40a0bf55a9b3ff5c0b7e1ae5e.tar.gz avr8js-988070a92d4654a40a0bf55a9b3ff5c0b7e1ae5e.tar.bz2 avr8js-988070a92d4654a40a0bf55a9b3ff5c0b7e1ae5e.zip | |
fix(timer): Output Compare in PWM modes #78
close #78
Diffstat (limited to '')
| -rw-r--r-- | src/peripherals/timer.spec.ts | 216 |
1 files changed, 214 insertions, 2 deletions
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts index 3b5026c..1fa0dc6 100644 --- a/src/peripherals/timer.spec.ts +++ b/src/peripherals/timer.spec.ts @@ -246,6 +246,7 @@ describe('timer', () => { cpu.tick(); cpu.cycles = 2; cpu.tick(); + expect(cpu.readData(TCNT0)).toEqual(0); expect(cpu.data[TIFR0] & (OCF0A | OCF0B)).toEqual(OCF0A | OCF0B); expect(cpu.pc).toEqual(0); expect(cpu.cycles).toEqual(2); @@ -417,7 +418,7 @@ describe('timer', () => { const { program, instructionCount } = asmProgram(` LDI r16, 0x1 ; TCCR0B = 1 << CS00; OUT 0x25, r16 - LDI r16, 0x30 ; TCNT <- 0x30 + LDI r16, 0x30 ; TCNT0 <- 0x30 OUT 0x26, r16 NOP IN r17, 0x26 ; r17 <- TCNT @@ -495,6 +496,152 @@ describe('timer', () => { expect(cpu.readData(R17)).toEqual(2); }); + describe('Fast PWM mode', () => { + it('should set OC0A on Compare Match, clear on Bottom (issue #78)', () => { + const { program, labels } = asmProgram(` + LDI r16, 0xfc ; TCNT0 = 0xfc; + OUT 0x26, r16 + LDI r16, 0xfe ; OCR0A = 0xfe; + OUT 0x27, r16 + ; WGM: Fast PWM, enable OC0A mode 3 (set on Compare Match, clear on Bottom) + LDI r16, 0xc3 ; TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM01) | (1 << WGM00); + OUT 0x24, r16 + LDI r16, 0x1 ; TCCR0B = 1 << CS00; + OUT 0x25, r16 + + NOP ; TCNT is now 0xfd + beforeMatch: + NOP ; TCNT is now 0xfe (Compare Match) + afterMatch: + NOP ; TCNT is now 0xff + beforeBottom: + NOP ; TCNT is now 0x00 (BOTTOM) + afterBottom: + NOP + `); + + const cpu = new CPU(program); + new AVRTimer(cpu, timer0Config); + + // Listen to Port D's internal callback + const gpioCallback = jest.fn(); + cpu.gpioTimerHooks[PORTD] = gpioCallback; + + const runner = new TestProgramRunner(cpu); + + runner.runToAddress(labels.beforeMatch); + expect(cpu.readData(TCNT0)).toEqual(0xfd); + expect(gpioCallback).toHaveBeenCalledTimes(1); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); // OC0A: Enable + gpioCallback.mockClear(); + + runner.runToAddress(labels.afterMatch); + expect(cpu.readData(TCNT0)).toEqual(0xfe); + expect(gpioCallback).toHaveBeenCalledTimes(1); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b); // OC0A: Set + gpioCallback.mockClear(); + + runner.runToAddress(labels.beforeBottom); + expect(cpu.readData(TCNT0)).toEqual(0xff); + expect(gpioCallback).toHaveBeenCalledTimes(0); + gpioCallback.mockClear(); + + runner.runToAddress(labels.afterBottom); + expect(cpu.readData(TCNT0)).toEqual(0x0); + expect(gpioCallback).toHaveBeenCalledTimes(1); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Clear, 0x2b); // OC0A: Clear + }); + + it('should toggle OC0A on Compare Match when COM0An = 1 (issue #78)', () => { + const { program, labels } = asmProgram(` + LDI r16, 0xfc ; TCNT0 = 0xfc; + OUT 0x26, r16 + LDI r16, 0xfe ; OCR0A = 0xfe; + OUT 0x27, r16 + ; WGM: Fast PWM, enable OC0A mode 1 (Toggle) + LDI r16, 0x43 ; TCCR0A = (1 << COM0A0) | (1 << WGM01) | (1 << WGM00); + OUT 0x24, r16 + LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00); + OUT 0x25, r16 + + NOP ; TCNT is now 0xfd + beforeMatch: + NOP ; TCNT is now 0xfe (Compare Match, TOP) + afterMatch: + NOP ; TCNT is now 0 + afterOverflow: + NOP + `); + + const cpu = new CPU(program); + new AVRTimer(cpu, timer0Config); + + // Listen to Port D's internal callback + const gpioCallback = jest.fn(); + cpu.gpioTimerHooks[PORTD] = gpioCallback; + + const runner = new TestProgramRunner(cpu); + + runner.runToAddress(labels.beforeMatch); + expect(cpu.readData(TCNT0)).toEqual(0xfd); + expect(gpioCallback).toHaveBeenCalledTimes(1); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); // OC0A: Enable + gpioCallback.mockClear(); + + runner.runToAddress(labels.afterMatch); + expect(cpu.readData(TCNT0)).toEqual(0xfe); + expect(gpioCallback).toHaveBeenCalledTimes(1); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle, 0x2b); // OC0A: Toggle + gpioCallback.mockClear(); + + runner.runToAddress(labels.afterOverflow); + expect(cpu.readData(TCNT0)).toEqual(0); + expect(gpioCallback).toHaveBeenCalledTimes(0); + }); + + it('should leave OC0A disconnected when COM0An = 1 and WGM02 = 0 (issue #78)', () => { + const { program, labels } = asmProgram(` + LDI r16, 0xfc ; TCNT0 = 0xfc; + OUT 0x26, r16 + LDI r16, 0xfe ; OCR0A = 0xfe; + OUT 0x27, r16 + ; WGM: Fast PWM mode 7, enable OC0A mode 1 (Toggle) + LDI r16, 0x43 ; TCCR0A = (1 << COM0A0) | (1 << WGM01) | (1 << WGM00); + OUT 0x24, r16 + LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00); + OUT 0x25, r16 + + beforeClearWGM02: + LDI r16, 0x01 ; TCCR0B = (1 << CS00); + OUT 0x25, r16 + + afterClearWGM02: + NOP + `); + + const cpu = new CPU(program); + new AVRTimer(cpu, timer0Config); + + // Listen to Port D's internal callback + const gpioCallback = jest.fn(); + cpu.gpioTimerHooks[PORTD] = gpioCallback; + + const runner = new TestProgramRunner(cpu); + + // First, run with the bit set and assert that the Pin Override was enabled (OC0A connected) + runner.runToAddress(labels.beforeClearWGM02); + expect(gpioCallback).toHaveBeenCalledTimes(1); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); + gpioCallback.mockClear(); + + // Now clear WGM02 and observe that Pin Override was disabled (OC0A disconnected) + runner.runToAddress(labels.afterClearWGM02); + expect(gpioCallback).toHaveBeenCalledTimes(1); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.None, 0x2b); + gpioCallback.mockClear(); + }); + }); + describe('Phase-correct PWM mode', () => { it('should count up to TOP, down to 0, and then set TOV flag', () => { const { program, instructionCount } = asmProgram(` @@ -575,6 +722,71 @@ describe('timer', () => { expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b); }); + it('should toggle OC0A when TCNT0=OCR0A and COM0An=1 (issue #78)', () => { + const { program, labels } = asmProgram(` + LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value + OUT 0x27, r16 + ; Set waveform generation mode (WGM) to PWM, Phase Correct (mode 5) + LDI r16, 0x41 ; TCCR0A = (1 << COM0A0) | (1 << WGM00); + OUT 0x24, r16 + LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00); + OUT 0x25, r16 + LDI r16, 0xfd ; TCNT0 = 0xfd; + OUT 0x26, r16 + + beforeMatch: + NOP ; TCNT0 will be 0xfe + afterMatch: + NOP + `); + + const cpu = new CPU(program); + new AVRTimer(cpu, timer0Config); + + // Listen to Port D's internal callback + const gpioCallback = jest.fn(); + cpu.gpioTimerHooks[PORTD] = gpioCallback; + + const runner = new TestProgramRunner(cpu); + + runner.runToAddress(labels.beforeMatch); + expect(cpu.readData(TCNT0)).toEqual(0xfd); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b); + gpioCallback.mockClear(); + + runner.runToAddress(labels.afterMatch); + expect(cpu.readData(TCNT0)).toEqual(0xfe); + expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle, 0x2b); + gpioCallback.mockClear(); + }); + + it('should leave OC0A disconnected TCNT0=OCR0A and COM0An=1 in WGM mode 1 (issue #78)', () => { + const { program, instructionCount } = asmProgram(` + LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value + OUT 0x27, r16 + ; Set waveform generation mode (WGM) to PWM, Phase Correct (mode 1) + LDI r16, 0x41 ; TCCR0A = (1 << COM0A0) | (1 << WGM00); + OUT 0x24, r16 + LDI r16, 0x01 ; TCCR0B = (1 << CS00); + OUT 0x25, r16 + LDI r16, 0xfd ; TCNT0 = 0xfd; + OUT 0x26, r16 + `); + + const cpu = new CPU(program); + new AVRTimer(cpu, timer0Config); + + // Listen to Port D's internal callback + const gpioCallback = jest.fn(); + cpu.gpioTimerHooks[PORTD] = gpioCallback; + + const runner = new TestProgramRunner(cpu); + runner.runInstructions(instructionCount); + + // Assert that the pin callback wasn't called (thus it's disconnected) + expect(gpioCallback).not.toHaveBeenCalled(); + }); + it('should not miss Compare Match when executing multi-cycle instruction (issue #79)', () => { const { program, instructionCount } = asmProgram(` LDI r16, 0x10 ; OCR0A = 0x10; // <- TOP value @@ -793,7 +1005,7 @@ describe('timer', () => { expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle, 0x25); }); - it('should only update OCR0A when TCNT0=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => { + 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; STS 0x89, r16 |
