aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals/usi.ts
blob: c9e09e754c7976c223da046f85394b98571e23aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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;
      }
    };
  }
}