aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/cpu.ts6
-rw-r--r--src/gpio.spec.ts40
-rw-r--r--src/gpio.ts74
-rw-r--r--src/index.ts8
4 files changed, 126 insertions, 2 deletions
diff --git a/src/cpu.ts b/src/cpu.ts
index 7eff3b3..7fc3ba0 100644
--- a/src/cpu.ts
+++ b/src/cpu.ts
@@ -19,7 +19,7 @@ export interface ICPU {
writeData(addr: u16, value: u8): void;
}
-export type ICPUMemoryHook = (value: u8, oldValue: u8, addr: u16) => void;
+export type ICPUMemoryHook = (value: u8, oldValue: u8, addr: u16) => boolean | void;
export interface ICPUMemoryHooks {
[key: number]: ICPUMemoryHook;
}
@@ -43,7 +43,9 @@ export class CPU implements ICPU {
writeData(addr: number, value: number) {
const hook = this.writeHooks[addr];
if (hook) {
- hook(value, this.data[addr], addr);
+ if (hook(value, this.data[addr], addr)) {
+ return;
+ }
}
this.data[addr] = value;
}
diff --git a/src/gpio.spec.ts b/src/gpio.spec.ts
new file mode 100644
index 0000000..03ebb84
--- /dev/null
+++ b/src/gpio.spec.ts
@@ -0,0 +1,40 @@
+import { CPU } from './cpu';
+import { AVRIOPort, portBConfig } from './gpio';
+
+describe('GPIO', () => {
+ it('should invoke the listeners when the port is written to', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portBConfig);
+ const listener = jest.fn();
+ port.addListener(listener);
+ cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f
+ cpu.writeData(0x25, 0x55); // PORTB <- 0x55
+ expect(listener).toHaveBeenCalledWith(0x05, 0);
+ expect(cpu.data[0x23]).toEqual(0x5); // PINB should return port value
+ });
+
+ it('should toggle the pin when writing to the PIN register', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portBConfig);
+ const listener = jest.fn();
+ port.addListener(listener);
+ cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f
+ cpu.writeData(0x25, 0x55); // PORTB <- 0x55
+ cpu.writeData(0x23, 0x01); // PINB <- 0x0f
+ expect(listener).toHaveBeenCalledWith(0x04, 0x5);
+ expect(cpu.data[0x23]).toEqual(0x4); // PINB should return port value
+ });
+
+ describe('removeListener', () => {
+ it('should remove the given listener', () => {
+ const cpu = new CPU(new Uint16Array(1024));
+ const port = new AVRIOPort(cpu, portBConfig);
+ const listener = jest.fn();
+ port.addListener(listener);
+ cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f
+ port.removeListener(listener);
+ cpu.writeData(0x25, 0x99); // PORTB <- 0x99
+ expect(listener).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/gpio.ts b/src/gpio.ts
new file mode 100644
index 0000000..7fdc915
--- /dev/null
+++ b/src/gpio.ts
@@ -0,0 +1,74 @@
+/**
+ * AVR-8 GPIO Port implementation
+ * 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, Uri Shaked
+ */
+import { CPU } from './cpu';
+import { u8 } from './types';
+
+export interface AVRPortConfig {
+ // Register addresses
+ PIN: u8;
+ DDR: u8;
+ PORT: u8;
+}
+
+export type GPIOListener = (value: u8, oldValue: u8) => void;
+
+export const portBConfig: AVRPortConfig = {
+ PIN: 0x23,
+ DDR: 0x24,
+ PORT: 0x25
+};
+
+export const portCConfig: AVRPortConfig = {
+ PIN: 0x26,
+ DDR: 0x27,
+ PORT: 0x28
+};
+
+export const portDConfig: AVRPortConfig = {
+ PIN: 0x29,
+ DDR: 0x2a,
+ PORT: 0x2b
+};
+
+export class AVRIOPort {
+ private listeners: GPIOListener[] = [];
+
+ constructor(cpu: CPU, portConfig: AVRPortConfig) {
+ cpu.writeHooks[portConfig.PORT] = (value: u8, oldValue: u8) => {
+ const ddrMask = cpu.data[portConfig.DDR];
+ value &= ddrMask;
+ cpu.data[portConfig.PIN] = (cpu.data[portConfig.PIN] & ~ddrMask) | value;
+ this.writeGpio(value, oldValue & ddrMask);
+ // TODO: activate pullups if configured as an input pin
+ };
+ cpu.writeHooks[portConfig.PIN] = (value: u8) => {
+ // Writing to 1 PIN toggles PORT bits
+ const oldPortValue = cpu.data[portConfig.PORT];
+ 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.writeGpio(portValue & ddrMask, oldPortValue & ddrMask);
+ return true;
+ };
+ }
+
+ addListener(listener: GPIOListener) {
+ this.listeners.push(listener);
+ }
+
+ removeListener(listener: GPIOListener) {
+ this.listeners = this.listeners.filter((l) => l !== listener);
+ }
+
+ private writeGpio(value: u8, oldValue: u8) {
+ for (const listener of this.listeners) {
+ listener(value, oldValue);
+ }
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 94e4ecc..bb3e216 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,3 +2,11 @@ export { CPU, ICPU, ICPUMemoryHook, ICPUMemoryHooks } from './cpu';
export { avrInstruction } from './instruction';
export { avrInterrupt } from './interrupt';
export { AVRTimer, timer0Config, timer1Config, timer2Config } from './timer';
+export {
+ AVRIOPort,
+ GPIOListener,
+ AVRPortConfig,
+ portBConfig,
+ portCConfig,
+ portDConfig
+} from './gpio';