aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorUri Shaked2021-08-15 20:27:41 +0300
committerUri Shaked2021-08-15 20:27:41 +0300
commiteff7111c3b08e8bc9030edc284074dfa2e9b7171 (patch)
tree4909fdec5c6602c1da192c0ddd77a801890e941a /src
parentchore(deps): prettier 2.3.2 (diff)
downloadavr8js-eff7111c3b08e8bc9030edc284074dfa2e9b7171.tar.gz
avr8js-eff7111c3b08e8bc9030edc284074dfa2e9b7171.tar.bz2
avr8js-eff7111c3b08e8bc9030edc284074dfa2e9b7171.zip
feat(timer): external timer support #97
also refactor timer/GPIO interaction to be more generic. close #97
Diffstat (limited to 'src')
-rw-r--r--src/cpu/cpu.ts3
-rw-r--r--src/peripherals/gpio.spec.ts4
-rw-r--r--src/peripherals/gpio.ts72
-rw-r--r--src/peripherals/timer.spec.ts121
-rw-r--r--src/peripherals/timer.ts66
5 files changed, 171 insertions, 95 deletions
diff --git a/src/cpu/cpu.ts b/src/cpu/cpu.ts
index 3cb4846..096d9eb 100644
--- a/src/cpu/cpu.ts
+++ b/src/cpu/cpu.ts
@@ -77,9 +77,8 @@ export class CPU implements ICPU {
private readonly clockEventPool: AVRClockEventEntry[] = []; // helps avoid garbage collection
readonly pc22Bits = this.progBytes.length > 0x20000;
- // This lets the Timer Compare output override GPIO pins:
- readonly gpioTimerHooks: CPUMemoryHooks = [];
readonly gpioPorts = new Set<AVRIOPort>();
+ readonly gpioByPort: AVRIOPort[] = [];
pc: u32 = 0;
cycles: u32 = 0;
diff --git a/src/peripherals/gpio.spec.ts b/src/peripherals/gpio.spec.ts
index 31963a9..ed44264 100644
--- a/src/peripherals/gpio.spec.ts
+++ b/src/peripherals/gpio.spec.ts
@@ -80,10 +80,10 @@ describe('GPIO', () => {
const cpu = new CPU(new Uint16Array(1024));
const port = new AVRIOPort(cpu, portBConfig);
cpu.writeData(DDRB, 1 << 1);
- cpu.gpioTimerHooks[PORTB](1, PinOverrideMode.Set, PORTB);
+ port.timerOverridePin(1, PinOverrideMode.Set);
expect(port.pinState(1)).toBe(PinState.High);
expect(cpu.data[PINB]).toBe(1 << 1);
- cpu.gpioTimerHooks[PORTB](1, PinOverrideMode.Clear, PORTB);
+ port.timerOverridePin(1, PinOverrideMode.Clear);
expect(port.pinState(1)).toBe(PinState.Low);
expect(cpu.data[PINB]).toBe(0);
});
diff --git a/src/peripherals/gpio.ts b/src/peripherals/gpio.ts
index bf5e53c..6d1dae8 100644
--- a/src/peripherals/gpio.ts
+++ b/src/peripherals/gpio.ts
@@ -87,6 +87,7 @@ export const PCINT2 = {
};
export type GPIOListener = (value: u8, oldValue: u8) => void;
+export type ExternalClockListener = (pinValue: boolean) => void;
export const portAConfig: AVRPortConfig = {
PIN: 0x20,
@@ -198,6 +199,8 @@ enum InterruptMode {
}
export class AVRIOPort {
+ readonly externalClockListeners: (ExternalClockListener | null)[] = [];
+
private readonly externalInts: (AVRInterruptConfig | null)[];
private readonly PCINT: AVRInterruptConfig | null;
private listeners: GPIOListener[] = [];
@@ -208,8 +211,10 @@ export class AVRIOPort {
private lastDdr: u8 = 0;
private lastPin: u8 = 0;
- constructor(private cpu: CPU, private portConfig: AVRPortConfig) {
+ constructor(private cpu: CPU, readonly portConfig: Readonly<AVRPortConfig>) {
cpu.gpioPorts.add(this);
+ cpu.gpioByPort[portConfig.PORT] = this;
+
cpu.writeHooks[portConfig.DDR] = (value: u8) => {
const portValue = cpu.data[portConfig.PORT];
cpu.data[portConfig.DDR] = value;
@@ -234,34 +239,6 @@ export class AVRIOPort {
this.updatePinRegister(ddrMask);
return true;
};
- // The following hook is used by the timer compare output to override GPIO pins:
- cpu.gpioTimerHooks[portConfig.PORT] = (pin: u8, mode: PinOverrideMode) => {
- const pinMask = 1 << pin;
- if (mode === PinOverrideMode.None) {
- this.overrideMask |= pinMask;
- this.overrideValue &= ~pinMask;
- } else {
- this.overrideMask &= ~pinMask;
- switch (mode) {
- case PinOverrideMode.Enable:
- this.overrideValue &= ~pinMask;
- this.overrideValue |= cpu.data[portConfig.PORT] & pinMask;
- break;
- case PinOverrideMode.Set:
- this.overrideValue |= pinMask;
- break;
- case PinOverrideMode.Clear:
- this.overrideValue &= ~pinMask;
- break;
- case PinOverrideMode.Toggle:
- this.overrideValue ^= pinMask;
- break;
- }
- }
- const ddrMask = cpu.data[portConfig.DDR];
- this.writeGpio(cpu.data[portConfig.PORT], ddrMask);
- this.updatePinRegister(ddrMask);
- };
// External interrupts
const { externalInterrupts } = portConfig;
@@ -360,13 +337,48 @@ export class AVRIOPort {
this.updatePinRegister(this.cpu.data[this.portConfig.DDR]);
}
+ /**
+ * Internal method - do not call this directly!
+ * Used by the timer compare output units to override GPIO pins.
+ */
+ timerOverridePin(pin: u8, mode: PinOverrideMode) {
+ const { cpu, portConfig } = this;
+ const pinMask = 1 << pin;
+ if (mode === PinOverrideMode.None) {
+ this.overrideMask |= pinMask;
+ this.overrideValue &= ~pinMask;
+ } else {
+ this.overrideMask &= ~pinMask;
+ switch (mode) {
+ case PinOverrideMode.Enable:
+ this.overrideValue &= ~pinMask;
+ this.overrideValue |= cpu.data[portConfig.PORT] & pinMask;
+ break;
+ case PinOverrideMode.Set:
+ this.overrideValue |= pinMask;
+ break;
+ case PinOverrideMode.Clear:
+ this.overrideValue &= ~pinMask;
+ break;
+ case PinOverrideMode.Toggle:
+ this.overrideValue ^= pinMask;
+ break;
+ }
+ }
+ const ddrMask = cpu.data[portConfig.DDR];
+ this.writeGpio(cpu.data[portConfig.PORT], ddrMask);
+ this.updatePinRegister(ddrMask);
+ }
+
private updatePinRegister(ddr: u8) {
const newPin = (this.pinValue & ~ddr) | (this.lastValue & ddr);
this.cpu.data[this.portConfig.PIN] = newPin;
if (this.lastPin !== newPin) {
for (let index = 0; index < 8; index++) {
if ((newPin & (1 << index)) !== (this.lastPin & (1 << index))) {
- this.toggleInterrupt(index, !!(newPin & (1 << index)));
+ const value = !!(newPin & (1 << index));
+ this.toggleInterrupt(index, value);
+ this.externalClockListeners[index]?.(value);
}
}
this.lastPin = newPin;
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts
index a15672c..d89fba2 100644
--- a/src/peripherals/timer.spec.ts
+++ b/src/peripherals/timer.spec.ts
@@ -1,6 +1,6 @@
import { CPU } from '../cpu/cpu';
import { asmProgram, TestProgramRunner } from '../utils/test-utils';
-import { PinOverrideMode } from './gpio';
+import { AVRIOPort, PinOverrideMode, portBConfig, portDConfig } from './gpio';
import { AVRTimer, timer0Config, timer1Config, timer2Config } from './timer';
// CPU registers
@@ -13,10 +13,6 @@ const R21 = 21;
const R22 = 22;
const SREG = 95;
-// Port Registers
-const PORTB = 0x25;
-const PORTD = 0x2b;
-
// Timer 0 Registers
const TIFR0 = 0x35;
const TCCR0A = 0x44;
@@ -61,10 +57,13 @@ const WGM12 = 8;
const WGM13 = 16;
const CS00 = 1;
const CS01 = 2;
+const CS02 = 4;
const CS10 = 1;
const CS21 = 2;
const CS22 = 4;
+const T0 = 4; // PD4 on ATmega328p
+
// opcodes
const nopOpCode = '0000';
@@ -558,21 +557,21 @@ describe('timer', () => {
new AVRTimer(cpu, timer0Config);
// Listen to Port D's internal callback
- const gpioCallback = jest.fn();
- cpu.gpioTimerHooks[PORTD] = gpioCallback;
+ const portD = new AVRIOPort(cpu, portDConfig);
+ const gpioCallback = jest.spyOn(portD, 'timerOverridePin');
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
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable); // 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
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set); // OC0A: Set
gpioCallback.mockClear();
runner.runToAddress(labels.beforeBottom);
@@ -583,7 +582,7 @@ describe('timer', () => {
runner.runToAddress(labels.afterBottom);
expect(cpu.readData(TCNT0)).toEqual(0x0);
expect(gpioCallback).toHaveBeenCalledTimes(1);
- expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Clear, 0x2b); // OC0A: Clear
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Clear); // OC0A: Clear
});
it('should toggle OC0A on Compare Match when COM0An = 1 (issue #78)', () => {
@@ -611,21 +610,21 @@ describe('timer', () => {
new AVRTimer(cpu, timer0Config);
// Listen to Port D's internal callback
- const gpioCallback = jest.fn();
- cpu.gpioTimerHooks[PORTD] = gpioCallback;
+ const portD = new AVRIOPort(cpu, portDConfig);
+ const gpioCallback = jest.spyOn(portD, 'timerOverridePin');
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
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable); // 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
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle); // OC0A: Toggle
gpioCallback.mockClear();
runner.runToAddress(labels.afterOverflow);
@@ -657,21 +656,21 @@ describe('timer', () => {
new AVRTimer(cpu, timer0Config);
// Listen to Port D's internal callback
- const gpioCallback = jest.fn();
- cpu.gpioTimerHooks[PORTD] = gpioCallback;
+ const portD = new AVRIOPort(cpu, portDConfig);
+ const gpioCallback = jest.spyOn(portD, 'timerOverridePin');
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);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable);
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);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.None);
gpioCallback.mockClear();
});
});
@@ -731,20 +730,20 @@ describe('timer', () => {
new AVRTimer(cpu, timer0Config);
// Listen to Port D's internal callback
- const gpioCallback = jest.fn();
- cpu.gpioTimerHooks[PORTD] = gpioCallback;
+ const portD = new AVRIOPort(cpu, portDConfig);
+ const gpioCallback = jest.spyOn(portD, 'timerOverridePin');
const nopCount = lines.filter((line) => line.bytes == nopOpCode).length;
const runner = new TestProgramRunner(cpu);
runner.runInstructions(instructionCount - nopCount);
expect(cpu.readData(TCNT0)).toEqual(0xfd);
- expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable);
gpioCallback.mockClear();
runner.runInstructions(1);
expect(cpu.readData(TCNT0)).toEqual(0xfe);
- expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Clear, 0x2b);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Clear);
gpioCallback.mockClear();
runner.runInstructions(1);
@@ -753,7 +752,7 @@ describe('timer', () => {
runner.runInstructions(1);
expect(cpu.readData(TCNT0)).toEqual(0xfe);
- expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set);
});
it('should toggle OC0A when TCNT0=OCR0A and COM0An=1 (issue #78)', () => {
@@ -778,19 +777,19 @@ describe('timer', () => {
new AVRTimer(cpu, timer0Config);
// Listen to Port D's internal callback
- const gpioCallback = jest.fn();
- cpu.gpioTimerHooks[PORTD] = gpioCallback;
+ const portD = new AVRIOPort(cpu, portDConfig);
+ const gpioCallback = jest.spyOn(portD, 'timerOverridePin');
const runner = new TestProgramRunner(cpu);
runner.runToAddress(labels.beforeMatch);
expect(cpu.readData(TCNT0)).toEqual(0xfd);
- expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable);
gpioCallback.mockClear();
runner.runToAddress(labels.afterMatch);
expect(cpu.readData(TCNT0)).toEqual(0xfe);
- expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle, 0x2b);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Toggle);
gpioCallback.mockClear();
});
@@ -811,8 +810,8 @@ describe('timer', () => {
new AVRTimer(cpu, timer0Config);
// Listen to Port D's internal callback
- const gpioCallback = jest.fn();
- cpu.gpioTimerHooks[PORTD] = gpioCallback;
+ const portD = new AVRIOPort(cpu, portDConfig);
+ const gpioCallback = jest.spyOn(portD, 'timerOverridePin');
const runner = new TestProgramRunner(cpu);
runner.runInstructions(instructionCount);
@@ -839,17 +838,17 @@ describe('timer', () => {
new AVRTimer(cpu, timer0Config);
// Listen to Port D's internal callback
- const gpioCallback = jest.fn();
- cpu.gpioTimerHooks[PORTD] = gpioCallback;
+ const portD = new AVRIOPort(cpu, portDConfig);
+ const gpioCallback = jest.spyOn(portD, 'timerOverridePin');
const runner = new TestProgramRunner(cpu);
runner.runInstructions(instructionCount);
expect(cpu.readData(TCNT0)).toEqual(0x11);
- expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable, 0x2b);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Enable);
// Verify that Compare Match has occured and set the OC0A pin (PD6 on ATmega328p)
- expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set, 0x2b);
+ expect(gpioCallback).toHaveBeenCalledWith(6, PinOverrideMode.Set);
});
it('should only update OCR0A when TCNT0=TOP in PWM Phase Correct mode (issue #76)', () => {
@@ -881,10 +880,6 @@ describe('timer', () => {
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);
@@ -1023,20 +1018,20 @@ describe('timer', () => {
new AVRTimer(cpu, timer1Config);
// Listen to Port B's internal callback
- const gpioCallback = jest.fn();
- cpu.gpioTimerHooks[PORTB] = gpioCallback;
+ const portB = new AVRIOPort(cpu, portBConfig);
+ const gpioCallback = jest.spyOn(portB, 'timerOverridePin');
const nopCount = lines.filter((line) => line.bytes == nopOpCode).length;
const runner = new TestProgramRunner(cpu);
runner.runInstructions(instructionCount - nopCount);
expect(cpu.readData(TCNT1)).toEqual(0x49);
- expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Enable, 0x25);
+ expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Enable);
gpioCallback.mockClear();
runner.runInstructions(1);
expect(cpu.readData(TCNT1)).toEqual(0x4a);
- expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle, 0x25);
+ expect(gpioCallback).toHaveBeenCalledWith(2, PinOverrideMode.Toggle);
});
it('should only update OCR1A when TCNT1=BOTTOM in PWM Phase/Frequency Correct mode (issue #76)', () => {
@@ -1073,10 +1068,6 @@ describe('timer', () => {
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);
@@ -1086,4 +1077,44 @@ describe('timer', () => {
expect(cpu.readData(R20)).toEqual(0x7);
});
});
+
+ describe('External clock', () => {
+ it('should count on the falling edge of T0 when CS=110', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const port = new AVRIOPort(cpu, portDConfig);
+ new AVRTimer(cpu, timer0Config);
+ cpu.writeData(TCCR0B, CS02 | CS01); // Count on falling edge
+ cpu.cycles = 1;
+ cpu.tick();
+
+ port.setPin(T0, true); // Rising edge
+ cpu.cycles = 2;
+ cpu.tick();
+ expect(cpu.readData(TCNT0)).toEqual(0);
+
+ port.setPin(T0, false); // Falling edge
+ cpu.cycles = 3;
+ cpu.tick();
+ expect(cpu.readData(TCNT0)).toEqual(1);
+ });
+
+ it('should count on the rising edge of T0 when CS=111', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const port = new AVRIOPort(cpu, portDConfig);
+ new AVRTimer(cpu, timer0Config);
+ cpu.writeData(TCCR0B, CS02 | CS01 | CS00); // Count on rising edge
+ cpu.cycles = 1;
+ cpu.tick();
+
+ port.setPin(T0, true); // Rising edge
+ cpu.cycles = 2;
+ cpu.tick();
+ expect(cpu.readData(TCNT0)).toEqual(1);
+
+ port.setPin(T0, false); // Falling edge
+ cpu.cycles = 3;
+ cpu.tick();
+ expect(cpu.readData(TCNT0)).toEqual(1);
+ });
+ });
});
diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts
index cd55f12..7b54d3f 100644
--- a/src/peripherals/timer.ts
+++ b/src/peripherals/timer.ts
@@ -3,11 +3,11 @@
* Part of AVR8js
* Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf
*
- * Copyright (C) 2019, 2020, Uri Shaked
+ * Copyright (C) 2019, 2020, 2021 Uri Shaked
*/
import { AVRInterruptConfig, CPU } from '../cpu/cpu';
-import { PinOverrideMode, portBConfig, portDConfig } from './gpio';
+import { AVRIOPort, PinOverrideMode, portBConfig, portDConfig } from './gpio';
const timer01Dividers = {
0: 0,
@@ -16,10 +16,15 @@ const timer01Dividers = {
3: 64,
4: 256,
5: 1024,
- 6: 0, // TODO: External clock source on T0 pin. Clock on falling edge.
- 7: 0, // TODO: External clock source on T0 pin. Clock on rising edge.
+ 6: 0, // External clock - see ExternalClockMode
+ 7: 0, // Ditto
};
+enum ExternalClockMode {
+ FallingEdge = 6,
+ RisingEdge = 7,
+}
+
type u8 = number;
type u16 = number;
@@ -70,6 +75,10 @@ export interface AVRTimerConfig {
compPinA: u8;
compPortB: u16;
compPinB: u8;
+
+ // External clock pin (optional, 0 = unused)
+ externalClockPort: u16;
+ externalClockPin: u8;
}
/** These are differnet for some devices (e.g. ATtiny85) */
@@ -105,6 +114,8 @@ export const timer0Config: AVRTimerConfig = {
compPinA: 6,
compPortB: portDConfig.PORT,
compPinB: 5,
+ externalClockPort: portDConfig.PORT,
+ externalClockPin: 4,
...defaultTimerBits,
};
@@ -128,6 +139,8 @@ export const timer1Config: AVRTimerConfig = {
compPinA: 1,
compPortB: portBConfig.PORT,
compPinB: 2,
+ externalClockPort: portDConfig.PORT,
+ externalClockPin: 5,
...defaultTimerBits,
};
@@ -160,6 +173,8 @@ export const timer2Config: AVRTimerConfig = {
compPinA: 3,
compPortB: portDConfig.PORT,
compPinB: 3,
+ externalClockPort: 0, // Not available
+ externalClockPin: 0,
...defaultTimerBits,
};
@@ -262,6 +277,8 @@ export class AVRTimer {
private updateDivider = false;
private countingUp = true;
private divider = 0;
+ private externalClockPort?: AVRIOPort;
+ private externalClockRisingEdge = false;
// This is the temporary register used to access 16-bit registers (section 16.3 of the datasheet)
private highByteTemp: u8 = 0;
@@ -440,12 +457,12 @@ export class AVRTimer {
}
}
- count = (reschedule = true) => {
+ count = (reschedule = true, external = false) => {
const { divider, lastCycle, cpu } = this;
const { cycles } = cpu;
const delta = cycles - lastCycle;
- if (divider && delta >= divider) {
- const counterDelta = Math.floor(delta / divider);
+ if ((divider && delta >= divider) || external) {
+ const counterDelta = external ? 1 : Math.floor(delta / divider);
this.lastCycle += counterDelta * divider;
const val = this.tcnt;
const { timerMode, TOP } = this;
@@ -491,12 +508,27 @@ export class AVRTimer {
this.tcntUpdated = false;
}
if (this.updateDivider) {
- const newDivider = this.config.dividers[this.CS];
+ const { CS } = this;
+ const { externalClockPin } = this.config;
+ const newDivider = this.config.dividers[CS];
this.lastCycle = newDivider ? this.cpu.cycles : 0;
this.updateDivider = false;
this.divider = newDivider;
+ if (this.config.externalClockPort && !this.externalClockPort) {
+ this.externalClockPort = this.cpu.gpioByPort[this.config.externalClockPort];
+ }
+ if (this.externalClockPort) {
+ this.externalClockPort.externalClockListeners[externalClockPin] = null;
+ }
if (newDivider) {
cpu.addClockEvent(this.count, this.lastCycle + newDivider - cpu.cycles);
+ } else if (
+ this.externalClockPort &&
+ (CS === ExternalClockMode.FallingEdge || CS === ExternalClockMode.RisingEdge)
+ ) {
+ this.externalClockPort.externalClockListeners[externalClockPin] =
+ this.externalClockCallback;
+ this.externalClockRisingEdge = CS === ExternalClockMode.RisingEdge;
}
return;
}
@@ -505,6 +537,12 @@ export class AVRTimer {
}
};
+ private externalClockCallback = (value: boolean) => {
+ if (value === this.externalClockRisingEdge) {
+ this.count(false, true);
+ }
+ };
+
private phasePwmCount(value: u16, delta: u8) {
const { ocrA, ocrB, TOP, tcntUpdated } = this;
while (delta > 0) {
@@ -601,17 +639,13 @@ export class AVRTimer {
private updateCompA(value: PinOverrideMode) {
const { compPortA, compPinA } = this.config;
- const hook = this.cpu.gpioTimerHooks[compPortA];
- if (hook) {
- hook(compPinA, value, compPortA);
- }
+ const port = this.cpu.gpioByPort[compPortA];
+ port?.timerOverridePin(compPinA, value);
}
private updateCompB(value: PinOverrideMode) {
const { compPortB, compPinB } = this.config;
- const hook = this.cpu.gpioTimerHooks[compPortB];
- if (hook) {
- hook(compPinB, value, compPortB);
- }
+ const port = this.cpu.gpioByPort[compPortB];
+ port?.timerOverridePin(compPinB, value);
}
}