Skip to content

Commit eff7111

Browse files
committed
feat(timer): external timer support #97
also refactor timer/GPIO interaction to be more generic. close #97
1 parent 3245a95 commit eff7111

File tree

5 files changed

+171
-95
lines changed

5 files changed

+171
-95
lines changed

src/cpu/cpu.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,8 @@ export class CPU implements ICPU {
7777
private readonly clockEventPool: AVRClockEventEntry[] = []; // helps avoid garbage collection
7878
readonly pc22Bits = this.progBytes.length > 0x20000;
7979

80-
// This lets the Timer Compare output override GPIO pins:
81-
readonly gpioTimerHooks: CPUMemoryHooks = [];
8280
readonly gpioPorts = new Set<AVRIOPort>();
81+
readonly gpioByPort: AVRIOPort[] = [];
8382

8483
pc: u32 = 0;
8584
cycles: u32 = 0;

src/peripherals/gpio.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ describe('GPIO', () => {
8080
const cpu = new CPU(new Uint16Array(1024));
8181
const port = new AVRIOPort(cpu, portBConfig);
8282
cpu.writeData(DDRB, 1 << 1);
83-
cpu.gpioTimerHooks[PORTB](1, PinOverrideMode.Set, PORTB);
83+
port.timerOverridePin(1, PinOverrideMode.Set);
8484
expect(port.pinState(1)).toBe(PinState.High);
8585
expect(cpu.data[PINB]).toBe(1 << 1);
86-
cpu.gpioTimerHooks[PORTB](1, PinOverrideMode.Clear, PORTB);
86+
port.timerOverridePin(1, PinOverrideMode.Clear);
8787
expect(port.pinState(1)).toBe(PinState.Low);
8888
expect(cpu.data[PINB]).toBe(0);
8989
});

src/peripherals/gpio.ts

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export const PCINT2 = {
8787
};
8888

8989
export type GPIOListener = (value: u8, oldValue: u8) => void;
90+
export type ExternalClockListener = (pinValue: boolean) => void;
9091

9192
export const portAConfig: AVRPortConfig = {
9293
PIN: 0x20,
@@ -198,6 +199,8 @@ enum InterruptMode {
198199
}
199200

200201
export class AVRIOPort {
202+
readonly externalClockListeners: (ExternalClockListener | null)[] = [];
203+
201204
private readonly externalInts: (AVRInterruptConfig | null)[];
202205
private readonly PCINT: AVRInterruptConfig | null;
203206
private listeners: GPIOListener[] = [];
@@ -208,8 +211,10 @@ export class AVRIOPort {
208211
private lastDdr: u8 = 0;
209212
private lastPin: u8 = 0;
210213

211-
constructor(private cpu: CPU, private portConfig: AVRPortConfig) {
214+
constructor(private cpu: CPU, readonly portConfig: Readonly<AVRPortConfig>) {
212215
cpu.gpioPorts.add(this);
216+
cpu.gpioByPort[portConfig.PORT] = this;
217+
213218
cpu.writeHooks[portConfig.DDR] = (value: u8) => {
214219
const portValue = cpu.data[portConfig.PORT];
215220
cpu.data[portConfig.DDR] = value;
@@ -234,34 +239,6 @@ export class AVRIOPort {
234239
this.updatePinRegister(ddrMask);
235240
return true;
236241
};
237-
// The following hook is used by the timer compare output to override GPIO pins:
238-
cpu.gpioTimerHooks[portConfig.PORT] = (pin: u8, mode: PinOverrideMode) => {
239-
const pinMask = 1 << pin;
240-
if (mode === PinOverrideMode.None) {
241-
this.overrideMask |= pinMask;
242-
this.overrideValue &= ~pinMask;
243-
} else {
244-
this.overrideMask &= ~pinMask;
245-
switch (mode) {
246-
case PinOverrideMode.Enable:
247-
this.overrideValue &= ~pinMask;
248-
this.overrideValue |= cpu.data[portConfig.PORT] & pinMask;
249-
break;
250-
case PinOverrideMode.Set:
251-
this.overrideValue |= pinMask;
252-
break;
253-
case PinOverrideMode.Clear:
254-
this.overrideValue &= ~pinMask;
255-
break;
256-
case PinOverrideMode.Toggle:
257-
this.overrideValue ^= pinMask;
258-
break;
259-
}
260-
}
261-
const ddrMask = cpu.data[portConfig.DDR];
262-
this.writeGpio(cpu.data[portConfig.PORT], ddrMask);
263-
this.updatePinRegister(ddrMask);
264-
};
265242

266243
// External interrupts
267244
const { externalInterrupts } = portConfig;
@@ -360,13 +337,48 @@ export class AVRIOPort {
360337
this.updatePinRegister(this.cpu.data[this.portConfig.DDR]);
361338
}
362339

340+
/**
341+
* Internal method - do not call this directly!
342+
* Used by the timer compare output units to override GPIO pins.
343+
*/
344+
timerOverridePin(pin: u8, mode: PinOverrideMode) {
345+
const { cpu, portConfig } = this;
346+
const pinMask = 1 << pin;
347+
if (mode === PinOverrideMode.None) {
348+
this.overrideMask |= pinMask;
349+
this.overrideValue &= ~pinMask;
350+
} else {
351+
this.overrideMask &= ~pinMask;
352+
switch (mode) {
353+
case PinOverrideMode.Enable:
354+
this.overrideValue &= ~pinMask;
355+
this.overrideValue |= cpu.data[portConfig.PORT] & pinMask;
356+
break;
357+
case PinOverrideMode.Set:
358+
this.overrideValue |= pinMask;
359+
break;
360+
case PinOverrideMode.Clear:
361+
this.overrideValue &= ~pinMask;
362+
break;
363+
case PinOverrideMode.Toggle:
364+
this.overrideValue ^= pinMask;
365+
break;
366+
}
367+
}
368+
const ddrMask = cpu.data[portConfig.DDR];
369+
this.writeGpio(cpu.data[portConfig.PORT], ddrMask);
370+
this.updatePinRegister(ddrMask);
371+
}
372+
363373
private updatePinRegister(ddr: u8) {
364374
const newPin = (this.pinValue & ~ddr) | (this.lastValue & ddr);
365375
this.cpu.data[this.portConfig.PIN] = newPin;
366376
if (this.lastPin !== newPin) {
367377
for (let index = 0; index < 8; index++) {
368378
if ((newPin & (1 << index)) !== (this.lastPin & (1 << index))) {
369-
this.toggleInterrupt(index, !!(newPin & (1 << index)));
379+
const value = !!(newPin & (1 << index));
380+
this.toggleInterrupt(index, value);
381+
this.externalClockListeners[index]?.(value);
370382
}
371383
}
372384
this.lastPin = newPin;

0 commit comments

Comments
 (0)