From 253905cbf952978df10d6e60dc599176ed593977 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Tue, 17 May 2022 12:37:27 +0300 Subject: [PATCH] feat(usi): ATtiny85 USI implementation --- src/index.ts | 1 + src/peripherals/gpio.ts | 7 ++- src/peripherals/usi.ts | 124 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 src/peripherals/usi.ts diff --git a/src/index.ts b/src/index.ts index c1089aa..1429db5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,3 +60,4 @@ export * from './peripherals/twi'; export { spiConfig, SPIConfig, SPITransferCallback, AVRSPI } from './peripherals/spi'; export { AVRClock, AVRClockConfig, clockConfig } from './peripherals/clock'; export { AVRWatchdog, watchdogConfig, WatchdogConfig } from './peripherals/watchdog'; +export { AVRUSI } from './peripherals/usi'; diff --git a/src/peripherals/gpio.ts b/src/peripherals/gpio.ts index 07667d9..9d84307 100644 --- a/src/peripherals/gpio.ts +++ b/src/peripherals/gpio.ts @@ -210,6 +210,7 @@ export class AVRIOPort { private lastValue: u8 = 0; private lastDdr: u8 = 0; private lastPin: u8 = 0; + openCollector: u8 = 0; constructor(private cpu: CPU, readonly portConfig: Readonly) { cpu.gpioPorts.add(this); @@ -317,10 +318,12 @@ export class AVRIOPort { const ddr = this.cpu.data[this.portConfig.DDR]; const port = this.cpu.data[this.portConfig.PORT]; const bitMask = 1 << index; + const openState = port & bitMask ? PinState.InputPullUp : PinState.Input; + const highValue = this.openCollector & bitMask ? openState : PinState.High; if (ddr & bitMask) { - return this.lastValue & bitMask ? PinState.High : PinState.Low; + return this.lastValue & bitMask ? highValue : PinState.Low; } else { - return port & bitMask ? PinState.InputPullUp : PinState.Input; + return openState; } } diff --git a/src/peripherals/usi.ts b/src/peripherals/usi.ts new file mode 100644 index 0000000..c9e09e7 --- /dev/null +++ b/src/peripherals/usi.ts @@ -0,0 +1,124 @@ +import { AVRInterruptConfig, CPU } from '../cpu/cpu'; +import { AVRIOPort } from './gpio'; + +const USICR = 0x2d; +const USISR = 0x2e; +const USIDR = 0x2f; +const USIBR = 0x30; + +// USISR bits +const USICNT_MASK = 0xf; +const USIDC = 1 << 4; +const USIPF = 1 << 5; +const USIOIF = 1 << 6; +const USISIF = 1 << 7; + +// USICR bits +const USITC = 1 << 0; +const USICLK = 1 << 1; +const USICS0 = 1 << 2; +const USICS1 = 1 << 3; +const USIWM0 = 1 << 4; +const USIWM1 = 1 << 5; +const USIOIE = 1 << 6; +const USISIE = 1 << 7; + +export class AVRUSI { + // Interrupts + private START: AVRInterruptConfig = { + address: 0xd, + flagRegister: USISR, + flagMask: USISIF, + enableRegister: USICR, + enableMask: USISIE, + }; + + private OVF: AVRInterruptConfig = { + address: 0xe, + flagRegister: USISR, + flagMask: USIOIF, + enableRegister: USICR, + enableMask: USIOIE, + }; + + constructor(cpu: CPU, port: AVRIOPort, portPin: number, dataPin: number, clockPin: number) { + const PIN = portPin; + const PORT = PIN + 2; + port.addListener((value) => { + const twoWire = (cpu.data[USICR] & USIWM1) === USIWM1; + if (twoWire) { + if (value & (1 << clockPin) && !(value & (1 << dataPin))) { + // Start condition detected + cpu.setInterruptFlag(this.START); + } + if (value & (1 << clockPin) && value & (1 << dataPin)) { + // Stop condition detected + cpu.data[USISR] |= USIPF; + } + } + }); + const updateOutput = () => { + const oldValue = cpu.data[PORT]; + const newValue = + cpu.data[USIDR] & 0x80 ? oldValue | (1 << dataPin) : oldValue & ~(1 << dataPin); + cpu.writeHooks[PORT](newValue, oldValue, PORT, 0xff); + if (newValue & 0x80 && !(cpu.data[PIN] & 0x80)) { + cpu.data[USISR] |= USIDC; // Shout output HIGH (pulled-up), but input is LOW + } else { + cpu.data[USISR] &= ~USIDC; + } + }; + const count = () => { + const counter = (cpu.data[USISR] + 1) & USICNT_MASK; + cpu.data[USISR] = (cpu.data[USISR] & ~USICNT_MASK) | counter; + if (!counter) { + cpu.data[USIBR] = cpu.data[USIDR]; + cpu.setInterruptFlag(this.OVF); + } + }; + const shift = (inputValue: number) => { + cpu.data[USIDR] = (cpu.data[USIDR] << 1) | inputValue; + updateOutput(); + }; + cpu.writeHooks[USIDR] = (value: number) => { + cpu.data[USIDR] = value; + updateOutput(); + return true; + }; + cpu.writeHooks[USISR] = (value: number) => { + const writeClearMask = USISIF | USIOIF | USIPF; + cpu.data[USISR] = (cpu.data[USISR] & writeClearMask & ~value) | (value & 0xf); + cpu.clearInterruptByFlag(this.START, value); + cpu.clearInterruptByFlag(this.OVF, value); + return true; + }; + cpu.writeHooks[USICR] = (value: number) => { + cpu.data[USICR] = value & ~(USICLK | USITC); + cpu.updateInterruptEnable(this.START, value); + cpu.updateInterruptEnable(this.OVF, value); + const clockSrc = value & ((USICS1 | USICS0) >> 2); + const mode = value & ((USIWM1 | USIWM0) >> 4); + const usiClk = value & USICLK; + port.openCollector = mode >= 2 ? 1 << dataPin : 0; + const inputValue = cpu.data[PIN] & (1 << dataPin) ? 1 : 0; + if (usiClk && !clockSrc) { + shift(inputValue); + count(); + } + if (value & USITC) { + cpu.writeHooks[PIN](1 << clockPin, cpu.data[PIN], PIN, 0xff); + const newValue = cpu.data[PIN] & (1 << clockPin); + if (usiClk && (clockSrc === 2 || clockSrc === 3)) { + if (clockSrc === 2 && newValue) { + shift(inputValue); + } + if (clockSrc === 3 && !newValue) { + shift(inputValue); + } + count(); + } + return true; + } + }; + } +}