summaryrefslogtreecommitdiff
path: root/avr/i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'avr/i2c.c')
-rw-r--r--avr/i2c.c389
1 files changed, 389 insertions, 0 deletions
diff --git a/avr/i2c.c b/avr/i2c.c
new file mode 100644
index 0000000..df97fea
--- /dev/null
+++ b/avr/i2c.c
@@ -0,0 +1,389 @@
+
+/*
+ * I2C (TWI) master interface.
+ */
+
+#include "common.h"
+#include <avr/interrupt.h>
+#include <util/delay.h>
+#include <string.h>
+
+#include "config.h"
+#include "timer.h"
+#include "debug.h"
+#include "i2c.h"
+
+#ifdef DEBUG
+//# define DEBUG_I2C
+#endif
+
+/* General TWI Master status codes */
+#define TWI_START 0x08 /* START has been transmitted */
+#define TWI_REP_START 0x10 /* Repeated START has been transmitted */
+#define TWI_ARB_LOST 0x38 /* Arbitration lost */
+
+/* TWI Master Transmitter status codes */
+#define TWI_MTX_ADR_ACK 0x18 /* SLA+W has been transmitted and ACK received */
+#define TWI_MTX_ADR_NACK 0x20 /* SLA+W has been transmitted and NACK received */
+#define TWI_MTX_DATA_ACK 0x28 /* Data byte has been transmitted and ACK received */
+#define TWI_MTX_DATA_NACK 0x30 /* Data byte has been transmitted and NACK received */
+
+/* TWI Master Receiver status codes */
+#define TWI_MRX_ADR_ACK 0x40 /* SLA+R has been transmitted and ACK received */
+#define TWI_MRX_ADR_NACK 0x48 /* SLA+R has been transmitted and NACK received */
+#define TWI_MRX_DATA_ACK 0x50 /* Data byte has been received and ACK transmitted */
+#define TWI_MRX_DATA_NACK 0x58 /* Data byte has been received and NACK transmitted */
+
+/* TWI Miscellaneous status codes */
+#define TWI_NO_STATE 0xF8 /* No relevant state information available */
+#define TWI_BUS_ERROR 0x00 /* Bus error due to an illegal START or STOP condition */
+
+
+/*
+ * TWINT: TWI Interrupt Flag
+ * TWEA: TWI Enable Acknowledge Bit
+ * TWSTA: TWI START Condition Bit
+ * TWSTO: TWI STOP Condition Bit
+ * TWEN: TWI Enable Bit
+ * TWIE: TWI Interrupt Enable
+ *
+ * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)
+ * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)| (1<<TWEA)
+ * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)
+ *
+ * default:
+ * (1<<TWEN)| (1<<TWINT)| (1<<TWSTO)
+ *
+ * Init:
+ * (1<<TWEN)
+ *
+ * start read/write:
+ * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA)
+ * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA)
+ * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA)
+ * (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA)
+ *
+ * wait ready:
+ * (1<<TWIE)|(1<<TWSTO)
+ *
+ *
+ *
+ *i2c_result
+ *
+ * 0b10000000 Busy (Transmission in progress)
+ * 0b01000000 Timeout
+ * 0b00001000 Start transmitted
+ * 0b00000100 Slave acknowledged address
+ * 0b00000010 Data byte(s) transmitted/received
+ * 0b00000001 Transmission completed
+ *
+ *
+ *----------------------------------------------------------------------
+ */
+
+#define TWI_C_DISABLE 0x00
+#define TWI_C_ENABLE (1<<TWEN)
+
+
+
+ typedef struct i2c_msg_s {
+ uint8_t stat;
+ #define XMIT_DONE (1<<0)
+ #define DATA_ACK (1<<1)
+ #define ADDR_ACK (1<<2)
+ #define START (1<<3)
+ #define TIMEOUT (1<<6)
+ #define BUSY (1<<7)
+ uint8_t idx;
+ uint8_t len;
+ uint8_t buf[CONFIG_SYS_I2C_BUFSIZE];
+} i2c_msg_t;
+
+static i2c_msg_t xmit;
+
+ISR(TWI_vect)
+{
+ uint8_t next_twcr;
+ uint8_t tmp_idx;
+
+ uint8_t twsr = TWSR;
+
+ switch (twsr & 0xf8) {
+
+ case TWI_START:
+ case TWI_REP_START:
+ xmit.idx = 0; /* reset xmit_buf index */
+ xmit.stat = BUSY | START;
+
+ tmp_idx = xmit.idx;
+ if (tmp_idx < xmit.len) { /* all bytes transmited? */
+ TWDR = xmit.buf[tmp_idx];
+ xmit.idx = ++tmp_idx;
+ next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
+ } else {
+ xmit.stat |= XMIT_DONE;
+ xmit.stat &= ~BUSY;
+ next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
+ }
+ break;
+
+ case TWI_MTX_ADR_ACK:
+ xmit.stat |= ADDR_ACK;
+
+ tmp_idx = xmit.idx;
+ if (tmp_idx < xmit.len) { /* all bytes transmited? */
+ TWDR = xmit.buf[tmp_idx];
+ xmit.idx = ++tmp_idx;
+ next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
+ } else {
+ xmit.stat |= XMIT_DONE;
+ xmit.stat &= ~BUSY;
+ next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
+ }
+ break;
+
+ case TWI_MTX_DATA_ACK:
+ xmit.stat |= DATA_ACK;
+
+ tmp_idx = xmit.idx;
+ if (tmp_idx < xmit.len) { /* all bytes transmited? */
+ TWDR = xmit.buf[tmp_idx];
+ xmit.idx = ++tmp_idx;
+ next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
+ } else {
+ xmit.stat |= XMIT_DONE;
+ xmit.stat &= ~BUSY;
+ next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
+ }
+
+ break;
+
+ case TWI_MTX_DATA_NACK:
+ xmit.stat |= XMIT_DONE;
+ xmit.stat &= ~BUSY;
+ next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
+ break;
+
+ case TWI_MRX_ADR_ACK:
+ xmit.stat |= ADDR_ACK;
+ tmp_idx = xmit.idx;
+ if (tmp_idx < xmit.len-1) {
+ next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA);
+ } else {
+ next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
+ }
+ break;
+
+ case TWI_MRX_DATA_ACK:
+ xmit.stat |= DATA_ACK;
+ tmp_idx = xmit.idx;
+ xmit.buf[tmp_idx] = TWDR;
+ xmit.idx = ++tmp_idx;
+ if (tmp_idx < xmit.len-1) {
+ next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWEA);
+ } else {
+
+ next_twcr = (1<<TWEN)|(1<<TWIE)|(1<<TWINT);
+ }
+ break;
+
+ case TWI_MRX_DATA_NACK:
+ xmit.stat |= ADDR_ACK | DATA_ACK;
+
+ tmp_idx = xmit.idx;
+ xmit.buf[tmp_idx] = TWDR;
+ xmit.idx = ++tmp_idx;
+ /* fall thru */
+
+ default:
+ xmit.stat &= ~BUSY;
+ next_twcr = (1<<TWEN)|(0<<TWIE)|(1<<TWINT)|(1<<TWSTO);
+ break;
+ }
+
+#ifdef DEBUG_I2C
+ debug("|%02x", twsr);
+#endif
+ TWCR = next_twcr;
+}
+
+
+/*------------------------------------------------------------------*/
+
+static uint8_t twps;
+static uint8_t twbr;
+
+
+static void _init(void)
+{
+ xmit.stat = 0;
+#ifdef DEBUG_I2C
+ memset((void *) xmit.buf, 0xdf, sizeof(xmit.buf));
+#endif
+
+ /* Disable TWI, disable TWI interrupt. */
+ /* (Reset TWI hardware state machine.) */
+ TWCR = TWI_C_DISABLE;
+ _delay_us(5);
+
+ TWBR = twbr;
+ TWDR = 0xff;
+ TWCR = TWI_C_ENABLE + twps;
+}
+
+void i2c_init(uint32_t speed)
+{
+ twps = 0;
+ uint32_t tmptwbr = F_CPU /2 / speed - 8;
+
+ while (tmptwbr > 255) {
+ tmptwbr >>= 4;
+ twps += 1;
+ }
+ debug_cond((twps > 3), "TWCLK too low: %lu Hz\n", speed);
+
+ twbr = (uint8_t) tmptwbr;
+ _init();
+}
+
+
+int_fast8_t i2c_waitready(void)
+{
+ uint32_t timer = get_timer(0);
+ uint8_t timeout = 0;
+
+ do {
+ if (get_timer(timer) >= 30) {
+ timeout = TIMEOUT;
+ _init();
+ }
+ } while ((TWCR & ((1<<TWIE)|(1<<TWSTO))) != 0 && !timeout);
+
+ xmit.stat |= timeout;
+
+#ifdef DEBUG_I2C
+ dump_ram((uint8_t *) &xmit, 4, "=== i2c_wait ready: (done)");
+ _delay_ms(30);
+#endif
+ return xmit.stat;
+}
+
+//static
+int i2c_send(uint8_t chip, uint16_t addr, uint8_t alen, uint8_t *buffer, int8_t len)
+{
+ uint8_t i, n;
+ uint8_t rc;
+
+ rc = i2c_waitready();
+ if ((rc & (BUSY | TIMEOUT)) != 0)
+ return rc;
+
+ xmit.stat = BUSY;
+ xmit.buf[0] = chip<<1;
+ for (i = 1; i < alen+1; i++) {
+ xmit.buf[i] = (uint8_t) addr;
+ addr >>= 8;
+ }
+ for (n = len + i; i < n; i++)
+ xmit.buf[i] = *buffer++;
+ xmit.len = i;
+
+#ifdef DEBUG_I2C
+ dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_send");
+ _delay_ms(30);
+#endif
+ /* Enable TWI, TWI int and initiate start condition */
+ TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
+
+ rc = xmit.stat;
+
+ return rc;
+}
+
+//static
+int i2c_recv(uint8_t chip, uint8_t *buffer, int8_t len)
+{
+ uint8_t rc;
+
+ rc = i2c_waitready();
+ if ((rc & (BUSY | TIMEOUT)) != 0)
+ return rc;
+
+ xmit.stat = BUSY;
+ xmit.len = len + 1;
+ xmit.buf[0] = (chip<<1) | 1;
+
+#ifdef DEBUG_I2C
+ dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_recv: before start");
+ _delay_ms(30);
+#endif
+ /* Enable TWI, TWI int and initiate start condition */
+ TWCR = (1<<TWEN)|(1<<TWIE)|(1<<TWINT)|(1<<TWSTA);
+ rc = i2c_waitready();
+
+#ifdef DEBUG_I2C
+ dump_ram((uint8_t *) &xmit, 0x20, "=== i2c_recv: after completion");
+ _delay_ms(30);
+#endif
+ if (rc & DATA_ACK) {
+ /* at least 1 byte received */
+ for (uint8_t i=1, n=xmit.idx; i < n; i++)
+ *buffer++ = xmit.buf[i];
+ }
+
+ return rc;
+}
+
+/*
+ * Read/Write interface:
+ * chip: I2C chip address, range 0..127
+ * addr: Memory (register) address within the chip
+ * alen: Number of bytes to use for addr (typically 1, 2 for larger
+ * memories, 0 for register type devices with only one
+ * register)
+ * buffer: Where to read/write the data
+ * len: How many bytes to read/write
+ *
+ * Returns: 0 on success, not 0 on failure
+ */
+
+int i2c_write(uint8_t chip, unsigned int addr, uint_fast8_t alen,
+ uint8_t *buffer, uint_fast8_t len)
+{
+ int rc;
+
+ if ((alen > 2) || (1 + alen + len > CONFIG_SYS_I2C_BUFSIZE)) {
+ debug("** i2c_write: buffer overflow, alen: %u, len: %u\n",
+ alen, len);
+ return -1;
+ }
+
+ i2c_send(chip, addr, alen, buffer, len);
+ rc = i2c_waitready();
+ debug("** i2c_write: result=0x%02x\n",rc);
+
+ return (rc & XMIT_DONE) != 0;
+}
+
+int i2c_read(uint8_t chip, unsigned int addr, uint_fast8_t alen,
+ uint8_t *buffer, uint_fast8_t len)
+{
+ int rc;
+
+ if ((alen > 2) || (1 + len > CONFIG_SYS_I2C_BUFSIZE)) {
+ debug("** i2c_read: parameter error: alen: %u, len: %u\n",
+ alen, len);
+ return -1;
+ }
+
+ if (alen != 0) {
+ i2c_send(chip, addr, alen, NULL, 0);
+ }
+ rc = i2c_recv(chip, buffer, len);
+ debug("** i2c_read: result=0x%02x\n",rc);
+
+ return !((rc & (XMIT_DONE|DATA_ACK)) == (XMIT_DONE|DATA_ACK));
+}
+
+
+