Skip to content

Commit

Permalink
fix(timer): Output Compare in PWM modes #78
Browse files Browse the repository at this point in the history
close #78
  • Loading branch information
urish committed Dec 26, 2020
1 parent 9c526a6 commit 988070a
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 67 deletions.
2 changes: 1 addition & 1 deletion src/peripherals/spi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe('SPI', () => {
return 0x5b; // we copy this byte to
};

const runner = new TestProgramRunner(cpu);
const runner = new TestProgramRunner(cpu, () => 0);
runner.runToBreak();

// 16 cycles per clock * 8 bits = 128
Expand Down
216 changes: 214 additions & 2 deletions src/peripherals/timer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 988070a

Please sign in to comment.