From af5ac6d4687b8b3903fa300527655ff43be12526 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Wed, 7 Jul 2021 15:32:09 +0300 Subject: feat(gpio): external interrupt/PCINT support (#82) close #70, #84--- src/peripherals/gpio.spec.ts | 188 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 12 deletions(-) (limited to 'src/peripherals/gpio.spec.ts') 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); + }); + }); }); -- cgit v1.2.3