From 1a64241f92251f3ed16ae54afd020a416565bf74 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Thu, 30 Jan 2020 20:21:31 +0200 Subject: feat(twi): partial TWI master implementation #10 --- src/index.ts | 1 + src/twi.spec.ts | 22 +++++++ src/twi.ts | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 src/twi.spec.ts create mode 100644 src/twi.ts (limited to 'src') diff --git a/src/index.ts b/src/index.ts index ead8081..ef0b514 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,3 +26,4 @@ export { PinState } from './gpio'; export { AVRUSART, usart0Config } from './usart'; +export * from './twi'; diff --git a/src/twi.spec.ts b/src/twi.spec.ts new file mode 100644 index 0000000..dacd958 --- /dev/null +++ b/src/twi.spec.ts @@ -0,0 +1,22 @@ +import { CPU } from './cpu'; +import { AVRTWI, twiConfig } from './twi'; + +const FREQ_16MHZ = 16e6; + +describe('TWI', () => { + it('should correctly calculate the sclFrequency from TWBR', () => { + const cpu = new CPU(new Uint16Array(1024)); + const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); + cpu.writeData(0xb8, 0x48); // TWBR <- 0x48 + cpu.writeData(0xb9, 0); // TWSR <- 0 (prescaler: 1) + expect(twi.sclFrequency).toEqual(100000); + }); + + it('should take the prescaler into consideration when calculating sclFrequency', () => { + const cpu = new CPU(new Uint16Array(1024)); + const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); + cpu.writeData(0xb8, 0x03); // TWBR <- 0x03 + cpu.writeData(0xb9, 0x01); // TWSR <- 1 (prescaler: 4) + expect(twi.sclFrequency).toEqual(400000); + }); +}); 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); + } + } +} -- cgit v1.2.3