aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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';
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.