aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals/usi.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/peripherals/usi.ts')
-rw-r--r--src/peripherals/usi.ts124
1 files changed, 124 insertions, 0 deletions
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;
+ }
+ };
+ }
+}