aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUri Shaked2020-01-30 20:21:31 +0200
committerUri Shaked2020-01-30 20:21:31 +0200
commit1a64241f92251f3ed16ae54afd020a416565bf74 (patch)
tree67c888ef7352d9d257591f642346ad9d3f382006
parentchore: release 0.5.2 (diff)
downloadavr8js-1a64241f92251f3ed16ae54afd020a416565bf74.tar.gz
avr8js-1a64241f92251f3ed16ae54afd020a416565bf74.tar.bz2
avr8js-1a64241f92251f3ed16ae54afd020a416565bf74.zip
feat(twi): partial TWI master implementation #10
-rw-r--r--src/index.ts1
-rw-r--r--src/twi.spec.ts22
-rw-r--r--src/twi.ts188
3 files changed, 211 insertions, 0 deletions
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);
+ }
+ }
+}
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.