diff --git a/drivers/avr/twi.c b/drivers/avr/twi.c new file mode 100644 index 00000000..13dec1e9 --- /dev/null +++ b/drivers/avr/twi.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include +#include "twi.h" + +#ifndef F_SCL + #define F_SCL 100000UL // SCL frequency +#endif +#ifndef PRESCALER + #define PRESCALER 1 +#endif + +// Limits the amount of we wait for any one i2c transaction. +// Since were running SCL line 100kHz (=> 10μs/bit), and each transactions is +// 9 bits, a single transaction will take around 90μs to complete. +// +// (F_CPU/SCL_CLOCK) => # of μC cycles to transfer a bit +// poll loop takes at least 8 clock cycles to execute + +#ifdef TWI_TIMEOUT + #ifndef TWI_TX_SIZE + #define TWI_TX_SIZE 9 + #endif +#endif + +// Wait for an twi operation to finish +inline static +void twi_wait(uint8_t status) { + #ifdef TWI_TIMEOUT + uint16_t lim = 0; + while ( !(TWCR & (_BV(status))) && lim < (TWI_TX_SIZE+1)*(F_CPU/F_SCL)/8) + lim++; + #else + while ( !(TWCR & (_BV(status))) ); + #endif +} + +void twi_master_init(void) { + TWBR = (uint8_t) ((((F_CPU / F_SCL) / PRESCALER) - 16 ) / 2); +} + +void twi_slave_init(uint8_t address) { + TWAR = address << 0; // slave i2c address + TWCR = _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWEN); +} + +uint8_t twi_master_start(uint8_t address) { + // reset TWI control register and transmit start + TWCR = 0; + TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTA); + + twi_wait(TWINT); + + // check if the start condition was successfully transmitted + if (TW_STATUS != TW_START) + return 1; + + // set slave address and transmit + TWDR = address; + TWCR = _BV(TWINT) | _BV(TWEN); + + twi_wait(TWINT); + + // check if the device has acknowledged the READ / WRITE mode + if ( (TW_STATUS != TW_MT_SLA_ACK) && (TW_STATUS != TW_MR_SLA_ACK) ) + return 1; + + return 0; +} + +void twi_master_stop(void) { + // transmit STOP condition + TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTO); + + twi_wait(TWSTO); +} + +uint8_t twi_write(uint8_t data) { + // load data into data register and start transmission of data + TWDR = data; + TWCR = _BV(TWINT) | _BV(TWEN); + + twi_wait(TWINT); + + if ( TW_STATUS != TW_MT_DATA_ACK ) + return 1; + + return 0; +} + +uint8_t twi_read(bool ack) { + // start TWI module and acknowledge data after reception + if (ack) + TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA); + else + TWCR = _BV(TWINT) | _BV(TWEN); + + twi_wait(TWINT); + + return TWDR; +} + +uint8_t twi_transmit(uint8_t address, uint8_t* data, uint16_t length) { + if (twi_start(address | TW_WRITE)) + return 1; + + for (uint16_t i = 0; i < length; i++) { + if (twi_write(data[i])) + return 1; + } + + twi_stop(); + + return 0; +} + +uint8_t twi_receive(uint8_t address, uint8_t* data, uint16_t length) { + if (twi_start(address | TW_READ)) + return 1; + + for (uint16_t i = 0; i < (length-1); i++) { + data[i] = twi_read(true); + } + data[(length-1)] = twi_read(false); + + twi_stop(); + + return 0; +} + +uint8_t twi_writeReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length) { + if (twi_start(devaddr | TW_WRITE)) + return 1; + + twi_write(regaddr); + + for (uint16_t i = 0; i < length; i++) { + if (twi_write(data[i])) + return 1; + } + + twi_stop(); + + return 0; +} + +uint8_t twi_readReg(uint8_t devaddr, uint8_t regaddr, uint8_t* data, uint16_t length) +{ + if (twi_start(devaddr)) + return 1; + + twi_write(regaddr); + + if (twi_start(devaddr | TW_READ)) + return 1; + + for (uint16_t i = 0; i < (length-1); i++) { + data[i] = twi_read(true); + } + data[(length-1)] = twi_read(false); + + twi_stop(); + + return 0; +} + +uint8_t buffer_address = 0; + +ISR(TWI_vect) { + uint8_t data; + switch (TW_STATUS) { + case TW_SR_SLA_ACK: + buffer_address = 0xff; + TWCR |= _BV(TWIE) | _BV(TWINT) | _BV(TWEA) | _BV(TWEN); + break; + case TW_SR_DATA_ACK: + data = TWDR; + if (buffer_address == 0xff) { + // store address to read from later + buffer_address = data; + TWCR |= _BV(TWIE) | _BV(TWINT) | _BV(TWEA) | _BV(TWEN); + } else { + // store data from address and increment + rxbuffer[buffer_address] = data; + buffer_address++; + + if (buffer_address < 0xFF) { + // ack + TWCR |= _BV(TWIE) | _BV(TWINT) | _BV(TWEA) | _BV(TWEN); + } else { + // nack + TWCR &= ~_BV(TWEA); + TWCR |= _BV(TWIE) | _BV(TWINT) | _BV(TWEN); + } + + } + break; + case TW_ST_SLA_ACK: + case TW_ST_DATA_ACK: + data = TWDR; + + if (buffer_address == 0xFF) { + buffer_address = data; + } + + TWDR = txbuffer[buffer_address]; + buffer_address++; + + + if (buffer_address < 0xFF) { + // ack + TWCR |= _BV(TWIE) | _BV(TWINT) | _BV(TWEA) | _BV(TWEN); + } else { + // nack + TWCR &= ~_BV(TWEA); + TWCR |= _BV(TWIE) | _BV(TWINT) | _BV(TWEN); + } + break; + case TW_BUS_ERROR: + TWCR = 0; + break; + default: + TWCR |= _BV(TWIE) | _BV(TWEA) | _BV(TWEN); + break; + } +} \ No newline at end of file diff --git a/drivers/avr/twi.h b/drivers/avr/twi.h new file mode 100644 index 00000000..a85df1e7 --- /dev/null +++ b/drivers/avr/twi.h @@ -0,0 +1,4 @@ +#ifndef TWI_H +#define TWI_H + +#endif \ No newline at end of file