aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorUri Shaked2020-12-20 00:57:17 +0200
committerUri Shaked2020-12-20 00:57:17 +0200
commitb02afeb8d563c06bd9a83e76b9902981e02af6dd (patch)
tree4dc9e7ba1d7bea42f898851e93aa4cd9ea5ca14f /src
parentdocs: capitalization (diff)
downloadavr8js-b02afeb8d563c06bd9a83e76b9902981e02af6dd.tar.gz
avr8js-b02afeb8d563c06bd9a83e76b9902981e02af6dd.tar.bz2
avr8js-b02afeb8d563c06bd9a83e76b9902981e02af6dd.zip
fix(timer): OCR values should be buffered #76
fix #76
Diffstat (limited to 'src')
-rw-r--r--src/peripherals/timer.spec.ts97
-rw-r--r--src/peripherals/timer.ts39
2 files changed, 125 insertions, 11 deletions
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts
index 43768d2..11c0264 100644
--- a/src/peripherals/timer.spec.ts
+++ b/src/peripherals/timer.spec.ts
@@ -415,13 +415,13 @@ describe('timer', () => {
describe('Phase-correct PWM mode', () => {
it('should count up to TOP, down to 0, and then set TOV flag', () => {
const { program, instructionCount } = asmProgram(`
+ LDI r16, 0x3 ; OCR0A = 0x3; // <- TOP value
+ OUT 0x27, r16
; Set waveform generation mode (WGM) to PWM, Phase Correct, top OCR0A
LDI r16, 0x1 ; TCCR0A = 1 << WGM00;
OUT 0x24, r16
LDI r16, 0x9 ; TCCR0B = (1 << WGM02) | (1 << CS00);
OUT 0x25, r16
- LDI r16, 0x3 ; OCR0A = 0x3;
- OUT 0x27, r16
LDI r16, 0x2 ; TCNT0 = 0x2;
OUT 0x26, r16
@@ -448,13 +448,13 @@ describe('timer', () => {
it('should clear OC0A when TCNT0=OCR0A and counting up', () => {
const { program, lines, instructionCount } = asmProgram(`
+ LDI r16, 0xfe ; OCR0A = 0xfe; // <- TOP value
+ OUT 0x27, r16
; Set waveform generation mode (WGM) to PWM, Phase Correct
- LDI r16, 0x81 ; TCCR0A = (1 << COM0A1) || (1 << WGM01);
+ LDI r16, 0x81 ; TCCR0A = (1 << COM0A1) | (1 << WGM00);
OUT 0x24, r16
LDI r16, 0x1 ; TCCR0B = (1 << CS00);
OUT 0x25, r16
- LDI r16, 0xfe ; OCR0A = 0xfe;
- OUT 0x27, r16
LDI r16, 0xfd ; TCNT0 = 0xfd;
OUT 0x26, r16
@@ -491,6 +491,46 @@ describe('timer', () => {
expect(cpu.readData(TCNT0)).toEqual(0xfe);
expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b);
});
+
+ it('should only update OCR0A when TCNT0=TOP in PWM Phase Correct mode (issue #76)', () => {
+ const { program, instructionCount } = asmProgram(`
+ LDI r16, 0x4 ; OCR0A = 0x4;
+ OUT 0x27, r16
+ ; Set waveform generation mode (WGM) to PWM, Phase Correct
+ LDI r16, 0x01 ; TCCR0A = (1 << WGM00);
+ OUT 0x24, r16
+ LDI r16, 0x09 ; TCCR0B = (1 << WGM02) | (1 << CS00);
+ OUT 0x25, r16
+ LDI r16, 0x0 ; TCNT0 = 0x0;
+ OUT 0x26, r16
+
+ LDI r16, 0x2 ; OCR0A = 0x2; // TCNT0 should read 0x0
+ OUT 0x27, r16 ; // TCNT0 should read 0x1
+ NOP ; // TCNT0 should read 0x2
+ NOP ; // TCNT0 should read 0x3
+ IN r17, 0x26 ; R17 = TCNT; // TCNT0 should read 0x4 (that's old OCR0A / TOP)
+ NOP ; // TCNT0 should read 0x3
+ NOP ; // TCNT0 should read 0x2
+ NOP ; // TCNT0 should read 0x1
+ NOP ; // TCNT0 should read 0x0
+ NOP ; // TCNT0 should read 0x1
+ NOP ; // TCNT0 should read 0x2
+ IN r18, 0x26 ; R18 = TCNT; // TCNT0 should read 0x1
+ `);
+
+ 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);
+
+ expect(cpu.readData(R17)).toEqual(0x4);
+ expect(cpu.readData(R18)).toEqual(0x1);
+ });
});
describe('16 bit timers', () => {
@@ -638,5 +678,52 @@ describe('timer', () => {
expect(cpu.readData(TCNT1)).toEqual(0x4a);
expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle, 0x25);
});
+
+ it('should only update OCR0A when TCNT0=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => {
+ const { program, instructionCount } = asmProgram(`
+ LDI r16, 0x0 ; OCR1AH = 0x0;
+ STS 0x89, r16
+ LDI r16, 0x4 ; OCR1AL = 0x4;
+ STS 0x88, r16
+ ; Set waveform generation mode (WGM) to PWM Phase/Frequency Correct mode (9)
+ LDI r16, 0x01 ; TCCR1A = (1 << WGM10);
+ STS 0x80, r16
+ LDI r16, 0x11 ; TCCR1B = (1 << WGM13) | (1 << CS00);
+ STS 0x81, r16
+ LDI r16, 0x0 ; TCNT1H = 0x0;
+ STS 0x85, r16
+ LDI r16, 0x0 ; TCNT1L = 0x0;
+ STS 0x84, r16
+
+ LDI r16, 0x8 ; OCR1AL = 0x8; // TCNT1 should read 0x0
+ STS 0x88, r16 ; // TCNT1 should read 0x2 (going up)
+ LDS r17, 0x84 ; // TCNT1 should read 0x4 (going down)
+ LDS r18, 0x84 ; // TCNT1 should read 0x2 (going down)
+ NOP ; // TCNT1 should read 0x0 (going up)
+ NOP ; // TCNT1 should read 0x1 (going up)
+ NOP ; // TCNT1 should read 0x2 (going up)
+ NOP ; // TCNT1 should read 0x3 (going up)
+ NOP ; // TCNT1 should read 0x4 (going up)
+ NOP ; // TCNT1 should read 0x5 (going up)
+ LDS r19, 0x84 ; // TCNT1 should read 0x6 (going up)
+ NOP ; // TCNT1 should read 0x8 (going up)
+ LDS r20, 0x84 ; // TCNT1 should read 0x7 (going up)
+ `);
+
+ const cpu = new CPU(program);
+ new AVRTimer(cpu, timer1Config);
+
+ // Listen to Port D's internal callback
+ const gpioCallback = jest.fn();
+ cpu.gpioTimerHooks[PORTD] = gpioCallback;
+
+ const runner = new TestProgramRunner(cpu);
+ runner.runInstructions(instructionCount);
+
+ expect(cpu.readData(R17)).toEqual(0x4);
+ expect(cpu.readData(R18)).toEqual(0x2);
+ expect(cpu.readData(R19)).toEqual(0x6);
+ expect(cpu.readData(R20)).toEqual(0x7);
+ });
});
});
diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts
index 188832d..0f4d837 100644
--- a/src/peripherals/timer.ts
+++ b/src/peripherals/timer.ts
@@ -240,7 +240,10 @@ function compToOverride(comp: CompBitsValue) {
export class AVRTimer {
private lastCycle = 0;
private ocrA: u16 = 0;
+ private nextOcrA: u16 = 0;
private ocrB: u16 = 0;
+ private nextOcrB: u16 = 0;
+ private ocrUpdateMode = OCRUpdateMode.Immediate;
private icr: u16 = 0; // only for 16-bit timers
private timerMode: TimerMode;
private topValue: TimerTopValue;
@@ -297,12 +300,16 @@ export class AVRTimer {
this.timerUpdated();
};
this.cpu.writeHooks[config.OCRA] = (value: u8) => {
- // TODO implement buffering when timer running in PWM mode
- this.ocrA = (this.highByteTemp << 8) | value;
+ this.nextOcrA = (this.highByteTemp << 8) | value;
+ if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
+ this.ocrA = this.nextOcrA;
+ }
};
this.cpu.writeHooks[config.OCRB] = (value: u8) => {
- // TODO implement buffering when timer running in PWM mode
- this.ocrB = (this.highByteTemp << 8) | value;
+ this.nextOcrB = (this.highByteTemp << 8) | value;
+ if (this.ocrUpdateMode === OCRUpdateMode.Immediate) {
+ this.ocrB = this.nextOcrB;
+ }
};
this.cpu.writeHooks[config.ICR] = (value: u8) => {
this.icr = (this.highByteTemp << 8) | value;
@@ -351,7 +358,9 @@ export class AVRTimer {
this.divider = 0;
this.lastCycle = 0;
this.ocrA = 0;
+ this.nextOcrA = 0;
this.ocrB = 0;
+ this.nextOcrB = 0;
this.icr = 0;
this.tcnt = 0;
this.tcntNext = 0;
@@ -394,9 +403,10 @@ export class AVRTimer {
private updateWGMConfig() {
const wgmModes = this.config.bits === 16 ? wgmModes16Bit : wgmModes8Bit;
- const [timerMode, topValue] = wgmModes[this.WGM];
+ const [timerMode, topValue, ocrUpdateMode] = wgmModes[this.WGM];
this.timerMode = timerMode;
this.topValue = topValue;
+ this.ocrUpdateMode = ocrUpdateMode;
}
count = (reschedule = true) => {
@@ -410,14 +420,23 @@ export class AVRTimer {
const { timerMode } = this;
const phasePwm =
timerMode === TimerMode.PWMPhaseCorrect || timerMode === TimerMode.PWMPhaseFrequencyCorrect;
+ const limit = this.TOP + 1;
const newVal = phasePwm
? this.phasePwmCount(val, counterDelta)
- : (val + counterDelta) % (this.TOP + 1);
+ : (val + counterDelta) % limit;
+ const overflow = val + counterDelta >= limit;
// A CPU write overrides (has priority over) all counter clear or count operations.
if (!this.tcntUpdated) {
this.tcnt = newVal;
this.timerUpdated();
}
+
+ if (!phasePwm && this.ocrUpdateMode == OCRUpdateMode.Bottom && overflow) {
+ // OCRUpdateMode.Top only occurs in Phase Correct modes, handled by phasePwmCount()
+ this.ocrA = this.nextOcrA;
+ this.ocrB = this.nextOcrB;
+ }
+
if ((timerMode === TimerMode.Normal || timerMode === TimerMode.FastPWM) && val > newVal) {
cpu.setInterruptFlag(this.OVF);
}
@@ -447,12 +466,20 @@ export class AVRTimer {
value++;
if (value === this.TOP && !this.tcntUpdated) {
this.countingUp = false;
+ if (this.ocrUpdateMode === OCRUpdateMode.Top) {
+ this.ocrA = this.nextOcrA;
+ this.ocrB = this.nextOcrB;
+ }
}
} else {
value--;
if (!value && !this.tcntUpdated) {
this.countingUp = true;
this.cpu.setInterruptFlag(this.OVF);
+ if (this.ocrUpdateMode === OCRUpdateMode.Bottom) {
+ this.ocrA = this.nextOcrA;
+ this.ocrB = this.nextOcrB;
+ }
}
}
delta--;
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.