Skip to content

Commit

Permalink
feat(gpio): external interrupt/PCINT support (#82)
Browse files Browse the repository at this point in the history
close #70, #84
  • Loading branch information
urish authored Jul 7, 2021
1 parent 743521d commit af5ac6d
Show file tree
Hide file tree
Showing 4 changed files with 459 additions and 18 deletions.
2 changes: 2 additions & 0 deletions src/cpu/cpu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export {
AVRIOPort,
GPIOListener,
AVRPortConfig,
AVRPinChangeInterrupt,
AVRExternalInterrupt,
PCINT0,
PCINT1,
PCINT2,
INT0,
INT1,
portAConfig,
portBConfig,
portCConfig,
Expand Down
188 changes: 176 additions & 12 deletions src/peripherals/gpio.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -67,39 +95,39 @@ 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', () => {
const cpu = new CPU(new Uint16Array(1024));
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', () => {
const cpu = new CPU(new Uint16Array(1024));
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', () => {
// Related issue: https://github.com/wokwi/avr8js/issues/9
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);
Expand All @@ -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();
Expand All @@ -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);
});

Expand All @@ -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);
});
});
});
Loading

0 comments on commit af5ac6d

Please sign in to comment.