cI2C/ci2c.c

508 lines
15 KiB
C

/*!\file ci2c.c
** \author SMFSW
** \version 0.2
** \copyright MIT SMFSW (2017)
** \brief arduino master i2c in plain c code
**/
// TODO: add interrupt vector / callback for it operations (if not too messy)
// TODO: consider interrupts at least for RX when slave (and TX when master)
// TODO: change contigous r/w operations so it doesn't send internal address again
// TODO: split functions & headers
#include "ci2c.h"
#define START 0x08
#define REPEATED_START 0x10
#define MT_SLA_ACK 0x18
#define MT_SLA_NACK 0x20
#define MT_DATA_ACK 0x28
#define MT_DATA_NACK 0x30
#define MR_SLA_ACK 0x40
#define MR_SLA_NACK 0x48
#define MR_DATA_ACK 0x50
#define MR_DATA_NACK 0x58
#define LOST_ARBTRTN 0x38
#define TWI_STATUS (TWSR & 0xF8)
//#define isSetBitReg(v, b) ((v & (1 << b)) != 0)
//#define isClrBitReg(v, b) ((v & (1 << b)) == 0)
#define setBitReg(v, b) v |= (1 << b)
#define clrBitReg(v, b) v &= (uint8_t) (~(1 << b))
#define invBitReg(v, b) v ^= (1 << b)
static struct {
struct {
I2C_SPEED speed; //!< i2c bus speed
uint8_t retries; //!< i2c message retries when fail
uint16_t timeout; //!< i2c timeout (ms)
} cfg;
uint16_t start_wait;
bool busy;
} i2c = { {0, DEF_CI2C_NB_RETRIES, DEF_CI2C_TIMEOUT }, 0, false };
// Needed prototypes
static bool I2C_wr(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t nb_bytes);
static bool I2C_rd(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t nb_bytes);
/*! \brief Init an I2C slave structure for cMI2C communication
*! \param [in] slave - pointer to the I2C slave structure to init
*! \param [in] sl_addr - I2C slave address
*! \param [in] reg_sz - internal register map size
*! \return nothing
*/
void I2C_slave_init(I2C_SLAVE * slave, uint8_t sl_addr, I2C_INT_SIZE reg_sz)
{
(void) I2C_slave_set_addr(slave, sl_addr);
(void) I2C_slave_set_reg_size(slave, reg_sz);
I2C_slave_set_rw_func(slave, I2C_rd, 0);
I2C_slave_set_rw_func(slave, I2C_wr, 1);
slave->reg_addr = 0;
slave->status = I2C_OK;
}
/*! \brief Redirect slave I2C read/write function (if needed for advanced use)
*! \param [in] slave - pointer to the I2C slave structure to init
*! \param [in] func - pointer to read/write function to affect
*! \param [in] rw - 0 = read function, 1 = write function
*! \return nothing
*/
void I2C_slave_set_rw_func(I2C_SLAVE * slave, void * func, bool rw)
{
ci2c_fct_ptr * pfc = (ci2c_fct_ptr*) (rw ? &slave->cfg.wr : &slave->cfg.rd);
*pfc = func;
}
/*! \brief Change I2C slave address
*! \param [in, out] slave - pointer to the I2C slave structure to init
*! \param [in] sl_addr - I2C slave address
*! \return true if new address set (false if address is >7Fh)
*/
inline bool I2C_slave_set_addr(I2C_SLAVE * slave, uint8_t sl_addr)
{
if (sl_addr > 0x7F) { return false; }
slave->cfg.addr = sl_addr;
return true;
}
/*! \brief Change I2C registers map size (for access)
*! \param [in, out] slave - pointer to the I2C slave structure
*! \param [in] reg_sz - internal register map size
*! \return true if new size is correct (false otherwise and set to 16bit by default)
*/
inline bool I2C_slave_set_reg_size(I2C_SLAVE * slave, I2C_INT_SIZE reg_sz)
{
slave->cfg.reg_size = reg_sz > I2C_16B_REG ? I2C_16B_REG : reg_sz;
if (reg_sz > I2C_16B_REG) { return false; }
else { return true; }
}
/*! \brief Set I2C current register address
*! \param [in, out] slave - pointer to the I2C slave structure
*! \param [in] reg_addr - register address
*! \return nothing
*/
static inline void I2C_slave_set_reg_addr(I2C_SLAVE * slave, uint16_t reg_addr)
{
slave->reg_addr = reg_addr;
}
/*! \brief Get I2C slave address
*! \param [in] slave - pointer to the I2C slave structure
*! \return I2C slave address
*/
inline uint8_t I2C_slave_get_addr(I2C_SLAVE * slave)
{
return slave->cfg.addr;
}
/*! \brief Get I2C register map size (for access)
*! \param [in] slave - pointer to the I2C slave structure
*! \return register map using 16bits if true (1Byte otherwise)
*/
inline bool I2C_slave_get_reg_size(I2C_SLAVE * slave)
{
return slave->cfg.reg_size;
}
/*! \brief Get I2C current register address (addr may passed this way in procedures if contigous accesses)
*! \param [in] slave - pointer to the I2C slave structure
*! \return current register map address
*/
inline uint16_t I2C_slave_get_reg_addr(I2C_SLAVE * slave)
{
return slave->reg_addr;
}
/*! \brief Enable I2c module on arduino board (including pull-ups,
*! enabling of ACK, and setting clock frequency)
*! \param [in] speed - I2C bus speed in KHz
*! \return nothing
*/
void I2C_init(uint16_t speed)
{
// Set SDA and SCL to ports
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega328P__)
setBitReg(PORTC, 4);
setBitReg(PORTC, 5);
#else
setBitReg(PORTD, 0);
setBitReg(PORTD, 1);
#endif
(void) I2C_set_speed(speed);
}
/*! \brief I2C bus reset (Release SCL and SDA lines and re-enable module)
*! \return nothing
*/
void I2C_reset(void)
{
TWCR = 0;
setBitReg(TWCR, TWEA);
setBitReg(TWCR, TWEN);
}
/*! \brief Change I2C frequency
*! \param [in] speed - I2C speed in kHz (max 1MHz)
*! \return true if change is successful (false otherwise)
*/
bool I2C_set_speed(uint16_t speed)
{
i2c.cfg.speed = (I2C_SPEED) (speed == 0 ? I2C_SLOW : (speed > I2C_FAST ? I2C_SLOW : speed));
// Ensure i2c module is disabled
clrBitReg(TWCR, TWEN);
// Set prescaler and clock frequency
clrBitReg(TWSR, TWPS0);
clrBitReg(TWSR, TWPS1);
TWBR = ((F_CPU / (i2c.cfg.speed * 1000)) - 16) / 2; // TODO: check speed and make it a param
// re-enable module
I2C_reset();
return i2c.cfg.speed == speed ? true : false;
}
/*! \brief Change I2C ack timeout
*! \param [in] timeout - I2C ack timeout (500 ms max)
*! \return true if change is successful (false otherwise)
*/
inline bool I2C_set_timeout(uint16_t timeout)
{
i2c.cfg.timeout = timeout > 500 ? 500 : timeout;
return i2c.cfg.timeout == timeout ? true : false;
}
/*! \brief Change I2C message retries (in case of failure)
*! \param [in] retries - I2C number of retries (max of 8)
*! \return true if change is successful (false otherwise)
*/
inline bool I2C_set_retries(uint8_t retries)
{
i2c.cfg.retries = retries > 8 ? 8 : retries;
return i2c.cfg.retries == retries ? true : false;
}
/*! \brief Get I2C busy status
*! \return true if busy
*/
inline bool I2C_is_busy(void)
{
return i2c.busy;
}
/*! \brief This function reads or writes the provided data to/from the address specified.
*! If anything in the write process is not successful, then it will be
*! repeated up till 3 more times (default). If still not successful, returns NACK
*!
*! \param [in, out] slave - pointer to the I2C slave structure to init
*! \param [in] reg_addr - register address in register map
*! \param [in] data - pointer to the first byte of a block of data to write
*! \param [in] nb_bytes - indicates how many bytes of data to write
*! \param [in] wr - 0 = read, 1 = write operation
*! \return I2C_STATUS status of write attempt
*/
static I2C_STATUS I2C_comm(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t nb_bytes, bool wr)
{
uint8_t retry = i2c.cfg.retries;
bool ack = false;
ci2c_fct_ptr fc = (ci2c_fct_ptr) (wr ? slave->cfg.wr : slave->cfg.rd);
if (I2C_is_busy()) { return slave->status = I2C_BUSY; }
i2c.busy = true;
ack = fc(slave, reg_addr, data, nb_bytes);
while ((!ack) && (retry != 0)) // If com not successful, retry some more times
{
delay(5);
ack = fc(slave, reg_addr, data, nb_bytes);
retry--;
}
i2c.busy = false;
return slave->status = ack ? I2C_OK : I2C_NACK;
}
/*! \brief This function writes the provided data to the address specified.
*!
*! \param [in, out] slave - pointer to the I2C slave structure to init
*! \param [in] reg_addr - register address in register map
*! \param [in] data - pointer to the first byte of a block of data to write
*! \param [in] nb_bytes - indicates how many bytes of data to write
*! \return I2C_STATUS status of write attempt
*/
inline I2C_STATUS I2C_write(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t nb_bytes)
{
return I2C_comm(slave, reg_addr, data, nb_bytes, 1);
}
/*! \brief This inline is a wrapper to I2C_write in case of contigous operations
*!
*! \param [in, out] slave - pointer to the I2C slave structure to init
*! \param [in] data - pointer to the first byte of a block of data to write
*! \param [in] nb_bytes - indicates how many bytes of data to write
*! \return I2C_STATUS status of write attempt
*/
inline I2C_STATUS I2C_write_next(I2C_SLAVE * slave, uint8_t * data, uint16_t nb_bytes)
{
// TODO: implement read next so that it doesn't have to send start register address again
return I2C_write(slave, slave->reg_addr, data, nb_bytes);
}
/*! \brief This function reads data from the address specified and stores this
*! data in the area provided by the pointer.
*!
*! \param [in, out] slave - pointer to the I2C slave structure to init
*! \param [in] reg_addr - register address in register map
*! \param [in, out] data - pointer to the first byte of a block of data to read
*! \param [in] nb_bytes - indicates how many bytes of data to read
*! \return I2C_STATUS status of read attempt
*/
inline I2C_STATUS I2C_read(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t nb_bytes)
{
return I2C_comm(slave, reg_addr, data, nb_bytes, 0);
}
/*! \brief This inline is a wrapper to I2C_read in case of contigous operations
*!
*! \param [in, out] slave - pointer to the I2C slave structure to init
*! \param [in] data - pointer to the first byte of a block of data to read
*! \param [in] nb_bytes - indicates how many bytes of data to read
*! \return I2C_STATUS status of read attempt
*/
inline I2C_STATUS I2C_read_next(I2C_SLAVE * slave, uint8_t * data, uint16_t nb_bytes)
{
// TODO: implement read next so that it doesn't have to send start register address again
return I2C_read(slave, slave->reg_addr, data, nb_bytes);
}
/*! \brief Start i2c_timeout timer
*! \return nothing
*/
static inline void I2C_start_timeout(void)
{
i2c.start_wait = (uint16_t) millis();
}
/*! \brief Test i2c_timeout
*! \return true if i2c_timeout occured (false otherwise)
*/
static inline uint8_t I2C_timeout(void)
{
return (((uint16_t) millis() - i2c.start_wait) >= i2c.cfg.timeout);
}
/*! \brief Send start condition
*! \return true if start condition acknowledged (false otherwise)
*/
bool I2C_start(void)
{
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if ((TWI_STATUS == START) || (TWI_STATUS == REPEATED_START)) { return true; }
if (TWI_STATUS == LOST_ARBTRTN) { I2C_reset(); }
return false;
}
/*! \brief Send stop condition
*! \return true if stop condition acknowledged (false otherwise)
*/
bool I2C_stop(void)
{
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
while ((TWCR & (1 << TWSTO)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
return true;
}
/*! \brief Send I2C address
*! \param [in] sl_addr - I2C slave address
*! \return true if I2C slave address sent acknowledged (false otherwise)
*/
bool I2C_sndAddr(uint8_t sl_addr)
{
TWDR = sl_addr;
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if ((TWI_STATUS == MT_SLA_ACK) || (TWI_STATUS == MR_SLA_ACK)) { return true; }
if ((TWI_STATUS == MT_SLA_NACK) || (TWI_STATUS == MR_SLA_NACK)) { I2C_stop(); }
else { I2C_reset(); }
return false;
}
/*! \brief Send byte on bus
*! \param [in] dat - data to be sent
*! \return true if data sent acknowledged (false otherwise)
*/
bool I2C_snd8(uint8_t dat)
{
TWDR = dat;
I2C_start_timeout();
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if (TWI_STATUS == MT_DATA_ACK) { return true; }
if (TWI_STATUS == MT_DATA_NACK) { I2C_stop(); }
else { I2C_reset(); }
return false;
}
/*! \brief Receive byte from bus
*! \param [in] ack - true if wait for ack
*! \return true if data reception acknowledged (false otherwise)
*/
uint8_t I2C_rcv8(bool ack)
{
I2C_start_timeout();
if (ack) { TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA); }
else { TWCR = (1 << TWINT) | (1 << TWEN); }
while (!(TWCR & (1 << TWINT)))
{ if (I2C_timeout()) { I2C_reset(); return false; } }
if (TWI_STATUS == LOST_ARBTRTN) { I2C_reset(); return false; }
if (((TWI_STATUS == MR_DATA_NACK) && (!ack)) || ((TWI_STATUS == MR_DATA_ACK) && (ack))) { return true; }
else { return false; }
}
/*! \brief This procedure calls appropriate functions to perform a proper send transaction on I2C bus.
*!
*! \param [in, out] slave - pointer to the I2C slave structure to init
*! \param [in] reg_addr - register address in register map
*! \param [in] data - pointer to the first byte of a block of data to write
*! \param [in] nb_bytes - indicates how many bytes of data to write
*! \return Boolean indicating success/fail of write attempt
*/
static bool I2C_wr(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t nb_bytes)
{
uint16_t ct_w;
(void) I2C_slave_set_reg_addr(slave, reg_addr);
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave->cfg.addr << 1) == false) { return false; }
if (slave->cfg.reg_size)
{
if (slave->cfg.reg_size >= I2C_16B_REG) // if size >2, 16bit address is used
{
if (I2C_snd8((uint8_t) (reg_addr >> 8)) == false) { return false; }
}
if (I2C_snd8((uint8_t) reg_addr) == false) { return false; }
}
for (ct_w = 0; ct_w < nb_bytes; ct_w++)
{
if (I2C_snd8(*(data++)) == false) { return false; }
slave->reg_addr++;
}
if (I2C_stop() == false) { return false; }
return true;
}
/*! \brief This procedure calls appropriate functions to perform a proper receive transaction on I2C bus.
*!
*! \param [in, out] slave - pointer to the I2C slave structure to init
*! \param [in] reg_addr - register address in register map
*! \param [in, out] data - pointer to the first byte of a block of data to read
*! \param [in] nb_bytes - indicates how many bytes of data to read
*! \return Boolean indicating success/fail of read attempt
*/
static bool I2C_rd(I2C_SLAVE * slave, uint16_t reg_addr, uint8_t * data, uint16_t nb_bytes)
{
uint16_t ct_r;
(void) I2C_slave_set_reg_addr(slave, reg_addr);
if (nb_bytes == 0) { nb_bytes++; }
if (I2C_start() == false) { return false; }
if (I2C_sndAddr(slave->cfg.addr << 1) == false) { return false; }
if (slave->cfg.reg_size)
{
if (slave->cfg.reg_size >= I2C_16B_REG) // if size >2, 16bit address is used
{
if (I2C_snd8((uint8_t) (reg_addr >> 8)) == false) { return false; }
}
if (I2C_snd8((uint8_t) reg_addr) == false) { return false; }
if (I2C_start() == false) { return false; }
if (I2C_sndAddr((slave->cfg.addr << 1) | 0x01) == false) { return false; }
}
for (ct_r = 0; ct_r < nb_bytes; ct_r++)
{
if (ct_r == (nb_bytes - 1))
{
if (I2C_rcv8(false) == false) { return false; }
}
else
{
if (I2C_rcv8(true) == false) { return false; }
}
*data++ = TWDR;
slave->reg_addr++;
}
if (I2C_stop() == false) { return false; }
return true;
}