aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/cpu/cpu.ts2
-rw-r--r--src/index.ts7
-rw-r--r--src/peripherals/gpio.spec.ts188
-rw-r--r--src/peripherals/gpio.ts280
4 files changed, 459 insertions, 18 deletions
diff --git a/src/cpu/cpu.ts b/src/cpu/cpu.ts
index 55b8a68..3cb4846 100644
--- a/src/cpu/cpu.ts
+++ b/src/cpu/cpu.ts
@@ -5,6 +5,7 @@
* Copyright (C) 2019, Uri Shaked
*/
+import { AVRIOPort } from '../peripherals/gpio';
import { u32, u16, u8, i16 } from '../types';
import { avrInterrupt } from './interrupt';
@@ -78,6 +79,7 @@ export class CPU implements ICPU {
// This lets the Timer Compare output override GPIO pins:
readonly gpioTimerHooks: CPUMemoryHooks = [];
+ readonly gpioPorts = new Set<AVRIOPort>();
pc: u32 = 0;
cycles: u32 = 0;
diff --git a/src/index.ts b/src/index.ts
index f4ac3aa..01af188 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -18,6 +18,13 @@ export {
AVRIOPort,
GPIOListener,
AVRPortConfig,
+ AVRPinChangeInterrupt,
+ AVRExternalInterrupt,
+ PCINT0,
+ PCINT1,
+ PCINT2,
+ INT0,
+ INT1,
portAConfig,
portBConfig,
portCConfig,
diff --git a/src/peripherals/gpio.spec.ts b/src/peripherals/gpio.spec.ts
index d4b3c7d..692deaa 100644
--- a/src/peripherals/gpio.spec.ts
+++ b/src/peripherals/gpio.spec.ts
@@ -1,9 +1,37 @@
import { CPU } from '../cpu/cpu';
-import { AVRIOPort, portBConfig, PinState } from './gpio';
+import { AVRIOPort, portBConfig, PinState, portDConfig } from './gpio';
+// CPU registers
+const SREG = 95;
+
+// GPIO registers
const PINB = 0x23;
const DDRB = 0x24;
const PORTB = 0x25;
+const EIFR = 0x3c;
+const EIMSK = 0x3d;
+const PCICR = 0x68;
+const EICRA = 0x69;
+const PCIFR = 0x3b;
+const PCMSK0 = 0x6b;
+
+// Register bit names
+const INT0 = 0;
+const ISC00 = 0;
+const ISC01 = 1;
+const PCIE0 = 0;
+const PCINT3 = 3;
+
+// Pin names
+const PB0 = 0;
+const PB1 = 1;
+const PB3 = 3;
+const PB4 = 4;
+const PD2 = 2;
+
+// Interrupt vector addresses
+const PC_INT_INT0 = 2;
+const PC_INT_PCINT0 = 6;
describe('GPIO', () => {
it('should invoke the listeners when the port is written to', () => {
@@ -67,7 +95,7 @@ describe('GPIO', () => {
const port = new AVRIOPort(cpu, portBConfig);
cpu.writeData(DDRB, 0x1);
cpu.writeData(PORTB, 0x1);
- expect(port.pinState(0)).toEqual(PinState.High);
+ expect(port.pinState(PB0)).toEqual(PinState.High);
});
it('should return PinState.Low when the pin set to output and LOW', () => {
@@ -75,13 +103,13 @@ describe('GPIO', () => {
const port = new AVRIOPort(cpu, portBConfig);
cpu.writeData(DDRB, 0x8);
cpu.writeData(PORTB, 0xf7);
- expect(port.pinState(3)).toEqual(PinState.Low);
+ expect(port.pinState(PB3)).toEqual(PinState.Low);
});
it('should return PinState.Input by default (reset state)', () => {
const cpu = new CPU(new Uint16Array(1024));
const port = new AVRIOPort(cpu, portBConfig);
- expect(port.pinState(1)).toEqual(PinState.Input);
+ expect(port.pinState(PB1)).toEqual(PinState.Input);
});
it('should return PinState.InputPullUp when the pin is set to input with pullup', () => {
@@ -89,7 +117,7 @@ describe('GPIO', () => {
const port = new AVRIOPort(cpu, portBConfig);
cpu.writeData(DDRB, 0);
cpu.writeData(PORTB, 0x2);
- expect(port.pinState(1)).toEqual(PinState.InputPullUp);
+ expect(port.pinState(PB1)).toEqual(PinState.InputPullUp);
});
it('should reflect the current port state when called inside a listener', () => {
@@ -97,9 +125,9 @@ describe('GPIO', () => {
const cpu = new CPU(new Uint16Array(1024));
const port = new AVRIOPort(cpu, portBConfig);
const listener = jest.fn(() => {
- expect(port.pinState(0)).toBe(PinState.High);
+ expect(port.pinState(PB0)).toBe(PinState.High);
});
- expect(port.pinState(0)).toBe(PinState.Input);
+ expect(port.pinState(PB0)).toBe(PinState.Input);
cpu.writeData(DDRB, 0x01);
port.addListener(listener);
cpu.writeData(PORTB, 0x01);
@@ -111,9 +139,9 @@ describe('GPIO', () => {
const cpu = new CPU(new Uint16Array(1024));
const port = new AVRIOPort(cpu, portBConfig);
const listener = jest.fn(() => {
- expect(port.pinState(0)).toBe(PinState.Low);
+ expect(port.pinState(PB0)).toBe(PinState.Low);
});
- expect(port.pinState(0)).toBe(PinState.Input);
+ expect(port.pinState(PB0)).toBe(PinState.Input);
port.addListener(listener);
cpu.writeData(DDRB, 0x01);
expect(listener).toHaveBeenCalled();
@@ -125,9 +153,9 @@ describe('GPIO', () => {
const cpu = new CPU(new Uint16Array(1024));
const port = new AVRIOPort(cpu, portBConfig);
cpu.writeData(DDRB, 0);
- port.setPin(4, true);
+ port.setPin(PB4, true);
expect(cpu.data[0x23]).toEqual(0x10);
- port.setPin(4, false);
+ port.setPin(PB4, false);
expect(cpu.data[0x23]).toEqual(0x0);
});
@@ -136,10 +164,146 @@ describe('GPIO', () => {
const port = new AVRIOPort(cpu, portBConfig);
cpu.writeData(DDRB, 0x10);
cpu.writeData(PORTB, 0x0);
- port.setPin(4, true);
+ port.setPin(PB4, true);
expect(cpu.data[PINB]).toEqual(0x0);
cpu.writeData(DDRB, 0x0);
expect(cpu.data[PINB]).toEqual(0x10);
});
});
+
+ describe('External interrupt', () => {
+ it('should generate INT0 interrupt on rising edge', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portDConfig);
+ cpu.writeData(EIMSK, 1 << INT0);
+ cpu.writeData(EICRA, (1 << ISC01) | (1 << ISC00));
+
+ expect(cpu.data[EIFR]).toEqual(0);
+ port.setPin(PD2, true);
+ expect(cpu.data[EIFR]).toEqual(1 << INT0);
+
+ cpu.data[SREG] = 0x80; // SREG: I------- (enable interrupts)
+ cpu.tick();
+ expect(cpu.pc).toEqual(PC_INT_INT0);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[EIFR]).toEqual(0);
+
+ port.setPin(PD2, false);
+ expect(cpu.data[EIFR]).toEqual(0);
+ });
+
+ it('should generate INT0 interrupt on falling edge', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portDConfig);
+ cpu.writeData(EIMSK, 1 << INT0);
+ cpu.writeData(EICRA, 1 << ISC01);
+
+ expect(cpu.data[EIFR]).toEqual(0);
+ port.setPin(PD2, true);
+ expect(cpu.data[EIFR]).toEqual(0);
+ port.setPin(PD2, false);
+ expect(cpu.data[EIFR]).toEqual(1 << INT0);
+
+ cpu.data[SREG] = 0x80; // SREG: I------- (enable interrupts)
+ cpu.tick();
+ expect(cpu.pc).toEqual(PC_INT_INT0);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[EIFR]).toEqual(0);
+ });
+
+ it('should generate INT0 interrupt on level change', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portDConfig);
+ cpu.writeData(EIMSK, 1 << INT0);
+ cpu.writeData(EICRA, 1 << ISC00);
+
+ expect(cpu.data[EIFR]).toEqual(0);
+ port.setPin(PD2, true);
+ expect(cpu.data[EIFR]).toEqual(1 << INT0);
+ cpu.writeData(EIFR, 1 << INT0);
+ expect(cpu.data[EIFR]).toEqual(0);
+ port.setPin(PD2, false);
+ expect(cpu.data[EIFR]).toEqual(1 << INT0);
+ });
+
+ it('should a sticky INT0 interrupt while the pin level is low', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portDConfig);
+ cpu.writeData(EIMSK, 1 << INT0);
+ cpu.writeData(EICRA, 0);
+ expect(cpu.data[EIFR]).toEqual(0);
+
+ port.setPin(PD2, true);
+ expect(cpu.data[EIFR]).toEqual(0);
+
+ port.setPin(PD2, false);
+ expect(cpu.data[EIFR]).toEqual(1 << INT0);
+
+ // This is a sticky interrupt, verify we can't clear the flag:
+ cpu.writeData(EIFR, 1 << INT0);
+ expect(cpu.data[EIFR]).toEqual(1 << INT0);
+
+ cpu.data[SREG] = 0x80; // SREG: I------- (enable interrupts)
+ cpu.tick();
+ expect(cpu.pc).toEqual(PC_INT_INT0);
+ expect(cpu.cycles).toEqual(2);
+
+ // Flag shouldn't be cleared, as the interrupt is sticky
+ expect(cpu.data[EIFR]).toEqual(1 << INT0);
+
+ // But it will be cleared as soon as the pin goes high.
+ port.setPin(PD2, true);
+ expect(cpu.data[EIFR]).toEqual(0);
+ });
+ });
+
+ describe('Pin change interrupts (PCINT)', () => {
+ it('should generate a pin change interrupt when PB3 (PCINT3) goes high', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portBConfig);
+ cpu.writeData(PCICR, 1 << PCIE0);
+ cpu.writeData(PCMSK0, 1 << PCINT3);
+
+ port.setPin(PB3, true);
+ expect(cpu.data[PCIFR]).toEqual(1 << PCIE0);
+
+ cpu.data[SREG] = 0x80; // SREG: I-------
+ cpu.tick();
+ expect(cpu.pc).toEqual(PC_INT_PCINT0);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[PCIFR]).toEqual(0);
+ });
+
+ it('should generate a pin change interrupt when PB3 (PCINT3) goes low', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portBConfig);
+
+ port.setPin(PB3, true);
+ cpu.writeData(PCICR, 1 << PCIE0);
+ cpu.writeData(PCMSK0, 1 << PCINT3);
+ expect(cpu.data[PCIFR]).toEqual(0);
+
+ port.setPin(PB3, false);
+ expect(cpu.data[PCIFR]).toEqual(1 << PCIE0);
+
+ cpu.data[SREG] = 0x80; // SREG: I-------
+ cpu.tick();
+ expect(cpu.pc).toEqual(PC_INT_PCINT0);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[PCIFR]).toEqual(0);
+ });
+
+ it('should clear the interrupt flag when writing to PCIFR', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portBConfig);
+ cpu.writeData(PCICR, 1 << PCIE0);
+ cpu.writeData(PCMSK0, 1 << PCINT3);
+
+ port.setPin(PB3, true);
+ expect(cpu.data[PCIFR]).toEqual(1 << PCIE0);
+
+ cpu.writeData(PCIFR, 1 << PCIE0);
+ expect(cpu.data[PCIFR]).toEqual(0);
+ });
+ });
});
diff --git a/src/peripherals/gpio.ts b/src/peripherals/gpio.ts
index 4130c82..01d7da3 100644
--- a/src/peripherals/gpio.ts
+++ b/src/peripherals/gpio.ts
@@ -3,84 +3,175 @@
* 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 { CPU } from '../cpu/cpu';
+import { AVRInterruptConfig, CPU } from '../cpu/cpu';
import { u8 } from '../types';
+export interface AVRExternalInterrupt {
+ EICRA: u8;
+ EICRB: u8;
+ EIMSK: u8;
+ EIFR: u8;
+ index: u8; // 0..7
+ interrupt: u8;
+}
+
+export interface AVRPinChangeInterrupt {
+ PCIE: u8; // bit index in PCICR/PCIFR
+ PCICR: u8;
+ PCIFR: u8;
+ PCMSK: u8;
+ pinChangeInterrupt: u8;
+ mask: u8;
+ offset: u8;
+}
+
export interface AVRPortConfig {
// Register addresses
PIN: u8;
DDR: u8;
PORT: u8;
+
+ // Interrupt settings
+ pinChange?: AVRPinChangeInterrupt;
+ externalInterrupts: (AVRExternalInterrupt | null)[];
}
+export const INT0: AVRExternalInterrupt = {
+ EICRA: 0x69,
+ EICRB: 0,
+ EIMSK: 0x3d,
+ EIFR: 0x3c,
+ index: 0,
+ interrupt: 2,
+};
+
+export const INT1: AVRExternalInterrupt = {
+ EICRA: 0x69,
+ EICRB: 0,
+ EIMSK: 0x3d,
+ EIFR: 0x3c,
+ index: 1,
+ interrupt: 4,
+};
+
+export const PCINT0 = {
+ PCIE: 0,
+ PCICR: 0x68,
+ PCIFR: 0x3b,
+ PCMSK: 0x6b,
+ pinChangeInterrupt: 6,
+ mask: 0xff,
+ offset: 0,
+};
+
+export const PCINT1 = {
+ PCIE: 1,
+ PCICR: 0x68,
+ PCIFR: 0x3b,
+ PCMSK: 0x6c,
+ pinChangeInterrupt: 8,
+ mask: 0xff,
+ offset: 0,
+};
+
+export const PCINT2 = {
+ PCIE: 2,
+ PCICR: 0x68,
+ PCIFR: 0x3b,
+ PCMSK: 0x6d,
+ pinChangeInterrupt: 10,
+ mask: 0xff,
+ offset: 0,
+};
+
export type GPIOListener = (value: u8, oldValue: u8) => void;
export const portAConfig: AVRPortConfig = {
PIN: 0x20,
DDR: 0x21,
PORT: 0x22,
+ externalInterrupts: [],
};
export const portBConfig: AVRPortConfig = {
PIN: 0x23,
DDR: 0x24,
PORT: 0x25,
+
+ // Interrupt settings
+ pinChange: PCINT0,
+ externalInterrupts: [],
};
export const portCConfig: AVRPortConfig = {
PIN: 0x26,
DDR: 0x27,
PORT: 0x28,
+
+ // Interrupt settings
+ pinChange: PCINT1,
+ externalInterrupts: [],
};
export const portDConfig: AVRPortConfig = {
PIN: 0x29,
DDR: 0x2a,
PORT: 0x2b,
+
+ // Interrupt settings
+ pinChange: PCINT2,
+ externalInterrupts: [null, null, INT0, INT1],
};
export const portEConfig: AVRPortConfig = {
PIN: 0x2c,
DDR: 0x2d,
PORT: 0x2e,
+ externalInterrupts: [],
};
export const portFConfig: AVRPortConfig = {
PIN: 0x2f,
DDR: 0x30,
PORT: 0x31,
+ externalInterrupts: [],
};
export const portGConfig: AVRPortConfig = {
PIN: 0x32,
DDR: 0x33,
PORT: 0x34,
+ externalInterrupts: [],
};
export const portHConfig: AVRPortConfig = {
PIN: 0x100,
DDR: 0x101,
PORT: 0x102,
+ externalInterrupts: [],
};
export const portJConfig: AVRPortConfig = {
PIN: 0x103,
DDR: 0x104,
PORT: 0x105,
+ externalInterrupts: [],
};
export const portKConfig: AVRPortConfig = {
PIN: 0x106,
DDR: 0x107,
PORT: 0x108,
+ externalInterrupts: [],
};
export const portLConfig: AVRPortConfig = {
PIN: 0x109,
DDR: 0x10a,
PORT: 0x10b,
+ externalInterrupts: [],
};
export enum PinState {
@@ -99,15 +190,26 @@ export enum PinOverrideMode {
Toggle,
}
+enum InterruptMode {
+ LowLevel,
+ Change,
+ FallingEdge,
+ RisingEdge,
+}
+
export class AVRIOPort {
+ private readonly externalInts: (AVRInterruptConfig | null)[];
+ private readonly PCINT: AVRInterruptConfig | null;
private listeners: GPIOListener[] = [];
private pinValue: u8 = 0;
private overrideMask: u8 = 0xff;
- private overrideValue: u8;
+ private overrideValue: u8 = 0;
private lastValue: u8 = 0;
private lastDdr: u8 = 0;
+ private lastPin: u8 = 0;
constructor(private cpu: CPU, private portConfig: AVRPortConfig) {
+ cpu.gpioPorts.add(this);
cpu.writeHooks[portConfig.DDR] = (value: u8) => {
const portValue = cpu.data[portConfig.PORT];
cpu.data[portConfig.DDR] = value;
@@ -128,14 +230,14 @@ export class AVRIOPort {
const ddrMask = cpu.data[portConfig.DDR];
const portValue = oldPortValue ^ value;
cpu.data[portConfig.PORT] = portValue;
- cpu.data[portConfig.PIN] = (cpu.data[portConfig.PIN] & ~ddrMask) | (portValue & ddrMask);
+ this.updatePinRegister(portValue, ddrMask);
this.writeGpio(portValue, 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) {
+ if (mode === PinOverrideMode.None) {
this.overrideMask |= pinMask;
} else {
this.overrideMask &= ~pinMask;
@@ -157,6 +259,62 @@ export class AVRIOPort {
}
this.writeGpio(cpu.data[portConfig.PORT], cpu.data[portConfig.DDR]);
};
+
+ // External interrupts
+ const { externalInterrupts } = portConfig;
+ this.externalInts = externalInterrupts.map((externalConfig) =>
+ externalConfig
+ ? {
+ address: externalConfig.interrupt,
+ flagRegister: externalConfig.EIFR,
+ flagMask: 1 << externalConfig.index,
+ enableRegister: externalConfig.EIMSK,
+ enableMask: 1 << externalConfig.index,
+ }
+ : null
+ );
+ const EICRA = externalInterrupts.find((item) => item && item.EICRA)?.EICRA ?? 0;
+ this.attachInterruptHook(EICRA);
+ const EICRB = externalInterrupts.find((item) => item && item.EICRB)?.EICRB ?? 0;
+ this.attachInterruptHook(EICRB);
+ const EIMSK = externalInterrupts.find((item) => item && item.EIMSK)?.EIMSK ?? 0;
+ this.attachInterruptHook(EIMSK, 'mask');
+ const EIFR = externalInterrupts.find((item) => item && item.EIFR)?.EIFR ?? 0;
+ this.attachInterruptHook(EIFR, 'flag');
+
+ // Pin change interrupts
+ const { pinChange } = portConfig;
+ this.PCINT = pinChange
+ ? {
+ address: pinChange.pinChangeInterrupt,
+ flagRegister: pinChange.PCIFR,
+ flagMask: 1 << pinChange.PCIE,
+ enableRegister: pinChange.PCICR,
+ enableMask: 1 << pinChange.PCIE,
+ }
+ : null;
+ if (pinChange) {
+ const { PCIFR, PCMSK } = pinChange;
+ cpu.writeHooks[PCIFR] = (value) => {
+ for (const gpio of this.cpu.gpioPorts) {
+ const { PCINT } = gpio;
+ if (PCINT) {
+ cpu.clearInterruptByFlag(PCINT, value);
+ }
+ }
+ return true;
+ };
+ cpu.writeHooks[PCMSK] = (value) => {
+ cpu.data[PCMSK] = value;
+ for (const gpio of this.cpu.gpioPorts) {
+ const { PCINT } = gpio;
+ if (PCINT) {
+ cpu.updateInterruptEnable(PCINT, value);
+ }
+ }
+ return true;
+ };
+ }
}
addListener(listener: GPIOListener) {
@@ -200,7 +358,117 @@ export class AVRIOPort {
}
private updatePinRegister(port: u8, ddr: u8) {
- this.cpu.data[this.portConfig.PIN] = (this.pinValue & ~ddr) | (port & ddr);
+ const newPin = (this.pinValue & ~ddr) | (port & 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)));
+ }
+ }
+ this.lastPin = newPin;
+ }
+ }
+
+ private toggleInterrupt(pin: u8, risingEdge: boolean) {
+ const { cpu, portConfig, externalInts, PCINT } = this;
+ const { externalInterrupts, pinChange } = portConfig;
+ const externalConfig = externalInterrupts[pin];
+ const external = externalInts[pin];
+ if (external && externalConfig) {
+ const { index, EICRA, EICRB, EIMSK } = externalConfig;
+ if (cpu.data[EIMSK] & (1 << index)) {
+ const configRegister = index >= 4 ? EICRB : EICRA;
+ const configShift = (index % 4) * 2;
+ const configuration = (cpu.data[configRegister] >> configShift) & 0x3;
+ let generateInterrupt = false;
+ external.constant = false;
+ switch (configuration) {
+ case InterruptMode.LowLevel:
+ generateInterrupt = !risingEdge;
+ external.constant = true;
+ break;
+ case InterruptMode.Change:
+ generateInterrupt = true;
+ break;
+ case InterruptMode.FallingEdge:
+ generateInterrupt = !risingEdge;
+ break;
+ case InterruptMode.RisingEdge:
+ generateInterrupt = risingEdge;
+ break;
+ }
+ if (generateInterrupt) {
+ cpu.setInterruptFlag(external);
+ } else if (external.constant) {
+ cpu.clearInterrupt(external, true);
+ }
+ }
+ }
+
+ if (pinChange && PCINT && pinChange.mask & (1 << pin)) {
+ const { PCMSK } = pinChange;
+ if (cpu.data[PCMSK] & (1 << (pin + pinChange.offset))) {
+ cpu.setInterruptFlag(PCINT);
+ }
+ }
+ }
+
+ private attachInterruptHook(register: number, registerType: 'flag' | 'mask' | 'other' = 'other') {
+ if (!register) {
+ return;
+ }
+
+ const { cpu } = this;
+
+ cpu.writeHooks[register] = (value: u8) => {
+ if (registerType !== 'flag') {
+ cpu.data[register] = value;
+ }
+ for (const gpio of cpu.gpioPorts) {
+ for (const external of gpio.externalInts) {
+ if (external && registerType === 'mask') {
+ cpu.updateInterruptEnable(external, value);
+ }
+ if (external && !external.constant && registerType === 'flag') {
+ cpu.clearInterruptByFlag(external, value);
+ }
+ }
+
+ gpio.checkExternalInterrupts();
+ }
+
+ return true;
+ };
+ }
+
+ private checkExternalInterrupts() {
+ const { cpu } = this;
+ const { externalInterrupts } = this.portConfig;
+ for (let pin = 0; pin < 8; pin++) {
+ const external = externalInterrupts[pin];
+ if (!external) {
+ continue;
+ }
+ const pinValue = !!(this.lastPin & (1 << pin));
+ const { index, EICRA, EICRB, EIMSK, EIFR, interrupt } = external;
+ if (!(cpu.data[EIMSK] & (1 << index)) || pinValue) {
+ continue;
+ }
+ const configRegister = index >= 4 ? EICRB : EICRA;
+ const configShift = (index % 4) * 2;
+ const configuration = (cpu.data[configRegister] >> configShift) & 0x3;
+ if (configuration === InterruptMode.LowLevel) {
+ cpu.queueInterrupt({
+ address: interrupt,
+ flagRegister: EIFR,
+ flagMask: 1 << index,
+ enableRegister: EIMSK,
+ enableMask: 1 << index,
+ constant: true,
+ });
+ }
+ }
}
private writeGpio(value: u8, ddr: u8) {