aboutsummaryrefslogtreecommitdiff
path: root/src/twi.ts
diff options
context:
space:
mode:
authorUri Shaked2020-01-30 20:21:31 +0200
committerUri Shaked2020-01-30 20:21:31 +0200
commit1a64241f92251f3ed16ae54afd020a416565bf74 (patch)
tree67c888ef7352d9d257591f642346ad9d3f382006 /src/twi.ts
parentchore: release 0.5.2 (diff)
downloadavr8js-1a64241f92251f3ed16ae54afd020a416565bf74.tar.gz
avr8js-1a64241f92251f3ed16ae54afd020a416565bf74.tar.bz2
avr8js-1a64241f92251f3ed16ae54afd020a416565bf74.zip
feat(twi): partial TWI master implementation #10
Diffstat (limited to 'src/twi.ts')
-rw-r--r--src/twi.ts188
1 files changed, 188 insertions, 0 deletions
diff --git a/src/twi.ts b/src/twi.ts
new file mode 100644
index 0000000..e2cfe24
--- /dev/null
+++ b/src/twi.ts
@@ -0,0 +1,188 @@
+import { u8 } from './types';
+import { CPU } from './cpu';
+import { avrInterrupt } from './interrupt';
+
+export interface TWIEventHandler {
+ start(repeated: boolean): void;
+
+ stop(): void;
+
+ connectToSlave(addr: u8, write: boolean): void;
+
+ writeByte(value: u8): void;
+
+ readByte(ack: boolean): void;
+}
+
+export interface TWIConfig {
+ twiInterrupt: u8;
+
+ TWBR: u8;
+ TWCR: u8;
+ TWSR: u8;
+ TWDR: u8;
+ TWAR: u8;
+ TWAMR: u8;
+}
+
+/* eslint-disable @typescript-eslint/no-unused-vars */
+// Register bits:
+const TWCR_TWINT = 0x80; // TWI Interrupt Flag
+const TWCR_TWEA = 0x40; // TWI Enable Acknowledge Bit
+const TWCR_TWSTA = 0x20; // TWI START Condition Bit
+const TWCR_TWSTO = 0x10; // TWI STOP Condition Bit
+const TWCR_TWWC = 0x8; //TWI Write Collision Flag
+const TWCR_TWEN = 0x4; // TWI Enable Bit
+const TWCR_TWIE = 0x1; // TWI Interrupt Enable
+const TWSR_TWS_MASK = 0xf8; // TWI Status
+const TWSR_TWPS1 = 0x2; // TWI Prescaler Bits
+const TWSR_TWPS0 = 0x1; // TWI Prescaler Bits
+const TWSR_TWPS_MASK = TWSR_TWPS1 | TWSR_TWPS0; // TWI Prescaler mask
+const TWAR_TWA_MASK = 0xfe; // TWI (Slave) Address Register
+const TWAR_TWGCE = 0x1; // TWI General Call Recognition Enable Bit
+
+const STATUS_BUS_ERROR = 0x0;
+const STATUS_TWI_IDLE = 0xf8;
+// Master states
+const STATUS_START = 0x08;
+const STATUS_REPEATED_START = 0x10;
+const STATUS_SLAW_ACK = 0x18;
+const STATUS_SLAW_NACK = 0x20;
+const STATUS_DATA_SENT_ACK = 0x28;
+const STATUS_DATA_SENT_NACK = 0x30;
+const STATUS_DATA_LOST_ARBITRATION = 0x38;
+const STATUS_SLAR_ACK = 0x40;
+const STATUS_SLAR_NACK = 0x48;
+const STATUS_DATA_RECEIVED_ACK = 0x50;
+const STATUS_DATA_RECEIVED_NACK = 0x58;
+// TODO: add slave states
+/* eslint-enable @typescript-eslint/no-unused-vars */
+
+export const twiConfig: TWIConfig = {
+ twiInterrupt: 0x30,
+ TWBR: 0xb8,
+ TWSR: 0xb9,
+ TWAR: 0xba,
+ TWDR: 0xbb,
+ TWCR: 0xbc,
+ TWAMR: 0xbd
+};
+
+// A simple TWI Event Handler that sends a NACK for all events
+export class NoopTWIEventHandler implements TWIEventHandler {
+ constructor(protected twi: AVRTWI) {}
+
+ start() {
+ this.twi.completeStart();
+ }
+
+ stop() {
+ this.twi.completeStop();
+ }
+
+ connectToSlave() {
+ this.twi.completeConnect(false);
+ }
+
+ writeByte() {
+ this.twi.completeWrite(false);
+ }
+
+ readByte() {
+ this.twi.completeRead(0xff);
+ }
+}
+
+export class AVRTWI {
+ public eventHandler: TWIEventHandler = new NoopTWIEventHandler(this);
+
+ constructor(private cpu: CPU, private config: TWIConfig, private freqMHz: number) {
+ this.updateStatus(STATUS_TWI_IDLE);
+ this.cpu.writeHooks[config.TWCR] = (value) => {
+ const clearInt = value & TWCR_TWINT;
+ if (clearInt) {
+ value &= ~TWCR_TWINT;
+ }
+ const { status } = this;
+ if (clearInt && value & TWCR_TWEN) {
+ const twdrValue = this.cpu.data[this.config.TWDR];
+ // TODO: this should be executed after the current instruction completes
+ setTimeout(() => {
+ if (value & TWCR_TWSTA) {
+ this.eventHandler.start(status !== STATUS_TWI_IDLE);
+ } else if (value & TWCR_TWSTO) {
+ this.eventHandler.stop();
+ } else if (status === STATUS_START) {
+ this.eventHandler.connectToSlave(twdrValue >> 1, twdrValue & 0x1 ? false : true);
+ } else if (status === STATUS_SLAW_ACK || status === STATUS_DATA_SENT_ACK) {
+ this.eventHandler.writeByte(twdrValue);
+ } else if (status === STATUS_SLAR_ACK || status === STATUS_DATA_RECEIVED_ACK) {
+ const ack = !!(value & TWCR_TWEA);
+ this.eventHandler.readByte(ack);
+ }
+ }, 0);
+ this.cpu.data[config.TWCR] = value;
+ return true;
+ }
+ };
+ }
+
+ get prescaler() {
+ switch (this.cpu.data[this.config.TWSR] & TWSR_TWPS_MASK) {
+ case 0:
+ return 1;
+ case 1:
+ return 4;
+ case 2:
+ return 16;
+ case 3:
+ return 64;
+ }
+ // We should never get here:
+ throw new Error('Invalid prescaler value!');
+ }
+
+ get sclFrequency() {
+ return this.freqMHz / (16 + 2 * this.cpu.data[this.config.TWBR] * this.prescaler);
+ }
+
+ completeStart() {
+ this.updateStatus(this.status === STATUS_TWI_IDLE ? STATUS_START : STATUS_REPEATED_START);
+ }
+
+ completeStop() {
+ this.cpu.data[this.config.TWCR] &= ~TWCR_TWSTO;
+ this.updateStatus(STATUS_TWI_IDLE);
+ }
+
+ completeConnect(ack: boolean) {
+ if (this.cpu.data[this.config.TWDR] & 0x1) {
+ this.updateStatus(ack ? STATUS_SLAR_ACK : STATUS_SLAR_NACK);
+ } else {
+ this.updateStatus(ack ? STATUS_SLAW_ACK : STATUS_SLAW_NACK);
+ }
+ }
+
+ completeWrite(ack: boolean) {
+ this.updateStatus(ack ? STATUS_DATA_SENT_ACK : STATUS_DATA_SENT_NACK);
+ }
+
+ completeRead(value: u8) {
+ const ack = !!(this.cpu.data[this.config.TWCR] & TWCR_TWEA);
+ this.cpu.data[this.config.TWDR] = value;
+ this.updateStatus(ack ? STATUS_DATA_RECEIVED_ACK : STATUS_DATA_RECEIVED_NACK);
+ }
+
+ private get status() {
+ return this.cpu.data[this.config.TWSR] & TWSR_TWS_MASK;
+ }
+
+ private updateStatus(value: u8) {
+ const { TWCR, TWSR, twiInterrupt } = this.config;
+ this.cpu.data[TWSR] = (this.cpu.data[TWSR] & ~TWSR_TWS_MASK) | value;
+ this.cpu.data[TWCR] |= TWCR_TWINT;
+ if (this.cpu.interruptsEnabled && this.cpu.data[TWCR] & TWCR_TWIE) {
+ avrInterrupt(this.cpu, twiInterrupt);
+ }
+ }
+}