aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUri Shaked2019-11-25 22:03:40 +0200
committerUri Shaked2019-11-25 22:03:40 +0200
commit9b399811c07cc2ab881abacf6ca35107fc6bc658 (patch)
tree4200735eeec384baae148c37cca8d0438e274a67
parentdoc: README for demo, explain about running tests (diff)
downloadavr8js-9b399811c07cc2ab881abacf6ca35107fc6bc658.tar.gz
avr8js-9b399811c07cc2ab881abacf6ca35107fc6bc658.tar.bz2
avr8js-9b399811c07cc2ab881abacf6ca35107fc6bc658.zip
feat: GPIO peripheral implementation
Add new AVRIOPort class, implements GPIO output logic
-rw-r--r--demo/src/execute.ts17
-rw-r--r--demo/src/index.ts11
-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
6 files changed, 146 insertions, 10 deletions
diff --git a/demo/src/execute.ts b/demo/src/execute.ts
index 96a0411..4568839 100644
--- a/demo/src/execute.ts
+++ b/demo/src/execute.ts
@@ -1,4 +1,13 @@
-import { avrInstruction, AVRTimer, CPU, timer0Config } from 'avr8js';
+import {
+ avrInstruction,
+ AVRTimer,
+ CPU,
+ timer0Config,
+ AVRIOPort,
+ portBConfig,
+ portCConfig,
+ portDConfig
+} from 'avr8js';
import { loadHex } from './intelhex';
// ATmega328p params
@@ -8,6 +17,9 @@ export class AVRRunner {
readonly program = new Uint16Array(FLASH);
readonly cpu: CPU;
readonly timer: AVRTimer;
+ readonly portB: AVRIOPort;
+ readonly portC: AVRIOPort;
+ readonly portD: AVRIOPort;
private stopped = false;
@@ -15,6 +27,9 @@ export class AVRRunner {
loadHex(hex, new Uint8Array(this.program.buffer));
this.cpu = new CPU(this.program);
this.timer = new AVRTimer(this.cpu, timer0Config);
+ this.portB = new AVRIOPort(this.cpu, portBConfig);
+ this.portC = new AVRIOPort(this.cpu, portCConfig);
+ this.portD = new AVRIOPort(this.cpu, portDConfig);
}
async execute(callback: (cpu: CPU) => void) {
diff --git a/demo/src/index.ts b/demo/src/index.ts
index 22e8143..e51cbbc 100644
--- a/demo/src/index.ts
+++ b/demo/src/index.ts
@@ -1,7 +1,7 @@
import { buildHex } from './compile';
-import './index.css';
import { AVRRunner } from './execute';
import { formatTime } from './format-time';
+import './index.css';
import { LED } from './led';
let editor: any;
@@ -55,16 +55,13 @@ function executeProgram(hex: string) {
runner = new AVRRunner(hex);
const MHZ = 16000000;
- // Hook to PORTB output
- runner.cpu.writeHooks[0x25] = (value: number) => {
- const DDRB = runner.cpu.data[0x24];
- value &= DDRB;
+ // Hook to PORTB register
+ runner.portB.addListener((value) => {
const D12bit = 1 << 4;
const D13bit = 1 << 5;
led12.value = value & D12bit ? true : false;
led13.value = value & D13bit ? true : false;
- };
-
+ });
runner.execute((cpu) => {
const time = formatTime(cpu.cycles / MHZ);
statusLabel.textContent = 'Simulation time: ' + time;
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';