diff --git a/include/avarix.h b/include/avarix.h index 2b45d3b..b1fc199 100644 --- a/include/avarix.h +++ b/include/avarix.h @@ -12,7 +12,8 @@ #define MAX(x,y) ((x) > (y) ? (x) : (y)) /// Clamp a value to range [x;y] #define CLAMP(v,x,y) ((v) < (x) ? (x) : (v) > (y) ? (y) : (v)) - +/// Get number of elements in an array +#define LENGTHOF(a) ((sizeof(a)/sizeof((a)[0]))/(sizeof(a)%sizeof((a)[0]) == 0)) #endif //@} diff --git a/modules/i2c/i2c.c b/modules/i2c/i2c.c index 1c47559..0c031ea 100644 --- a/modules/i2c/i2c.c +++ b/modules/i2c/i2c.c @@ -151,5 +151,4 @@ int8_t i2cm_recv(i2cm_t *m, uint8_t addr, uint8_t *data, uint8_t n) return i; } - ///@endcond diff --git a/modules/i2c/i2c.h b/modules/i2c/i2c.h index 5b7ac68..b27c458 100644 --- a/modules/i2c/i2c.h +++ b/modules/i2c/i2c.h @@ -22,13 +22,69 @@ typedef struct i2cm_struct i2cm_t; /// I2C slave state typedef struct i2cs_struct i2cs_t; -/// State of i2cx -extern i2cT_t *const i2cx; - #else typedef struct TWI_MASTER_struct i2cm_t; -typedef struct i2cs_struct i2cs_t; + +typedef enum { + I2CS_STATE_NONE, + I2CS_STATE_READ, + I2CS_STATE_WRITE, + +} i2cs_state_t; + +#ifndef I2CS_RECV_BUFFER_SIZE +# define I2CS_RECV_BUFFER_SIZE 32 +#endif + +#ifndef I2CS_SEND_BUFFER_SIZE +# define I2CS_SEND_BUFFER_SIZE 32 +#endif + +/** @brief I2C slave master-write frame received + * + * @param buffer buffer containing the received bytes + * @param n number of bytes received from master + * + * This function is called when a master-write operation has completed + */ +typedef void (*i2cs_recv_callback_t)(uint8_t *buffer, uint8_t n); + +/** @brief I2C slave master-read operation was requested + * + * @param buffer buffer to provision + * @param maxsz maximum number of bytes which can be written to buffer + * @return number of bytes to send, returning 0 will result in a NACK from slave. + * + * This function is called when a master-read operation was requested by master + * and ask user to provision the buffer which will be sent. + */ +typedef uint8_t (*i2cs_prepare_send_callback_t)(uint8_t *buffer, uint8_t maxsz); + +/** @brief I2C slave transaction finished successfully or not + * + * This function is called when a STOP condition or any bus error has ended current transaction + */ +typedef void (*i2cs_reset_callback_t)(void); + +typedef struct { + + i2cs_state_t state; + + uint8_t recvd_bytes; + uint8_t recv_buffer[I2CS_RECV_BUFFER_SIZE]; + + uint8_t sent_bytes; + uint8_t bytes_to_send; + uint8_t send_buffer[I2CS_SEND_BUFFER_SIZE]; + + i2cs_recv_callback_t recv_callback; + + i2cs_prepare_send_callback_t prepare_send_callback; + + i2cs_reset_callback_t reset_callback; + +} i2cs_t; // Check for I2C enabled as both master and slave // Define pointers to internal structures @@ -42,7 +98,8 @@ typedef struct i2cs_struct i2cs_t; #elif (defined I2CC_MASTER) # define i2cC (&TWIC.MASTER) #elif (defined I2CC_SLAVE) -extern i2cs_t *const i2cC; +# define X_(p,s) p ## C ## s +# include "slavex.inc.h" #endif #if (defined I2CD_MASTER) && (defined I2CD_SLAVE) @@ -50,7 +107,8 @@ extern i2cs_t *const i2cC; #elif (defined I2CD_MASTER) # define i2cD (&TWID.MASTER) #elif (defined I2CD_SLAVE) -extern i2cs_t *const i2cD; +# define X_(p,s) p ## D ## s +# include "slavex.inc.h" #endif #if (defined I2CE_MASTER) && (defined I2CE_SLAVE) @@ -58,7 +116,8 @@ extern i2cs_t *const i2cD; #elif (defined I2CE_MASTER) # define i2cE (&TWIE.MASTER) #elif (defined I2CE_SLAVE) -extern i2cs_t *const i2cE; +# define X_(p,s) p ## E ## s +# include "slavex.inc.h" #endif #if (defined I2CF_MASTER) && (defined I2CF_SLAVE) @@ -66,7 +125,8 @@ extern i2cs_t *const i2cE; #elif (defined I2CF_MASTER) # define i2cF (&TWIF.MASTER) #elif (defined I2CF_SLAVE) -extern i2cs_t *const i2cF; +# define X_(p,s) p ## F ## s +# include "slavex.inc.h" #endif #endif diff --git a/modules/i2c/slavex.inc.c b/modules/i2c/slavex.inc.c index bcdd946..d0b457b 100644 --- a/modules/i2c/slavex.inc.c +++ b/modules/i2c/slavex.inc.c @@ -6,7 +6,193 @@ * The X_(p,s) macro must be defined before including. * It is automatically undefined at the end of this file. */ +#include +#include +#include -#error Slave mode not supported yet +#define I2CX(s) X_(I2C,s) +#define i2cX(s) X_(i2c,s) +#define TWIX X_(TWI,) +#define twiX(s) X_(TWI,s) +// declare i2cX singleton +i2cs_t i2cX(); + +void i2cX(_init)(void) +{ + + i2cX().state = I2CS_STATE_NONE; + + i2cX().reset_callback = NULL; + i2cX().prepare_send_callback = NULL; + i2cX().recv_callback = NULL; + + // initialize hardware + TWIX.SLAVE.CTRLA = TWI_SLAVE_ENABLE_bm + | TWI_SLAVE_APIEN_bm + | TWI_SLAVE_DIEN_bm + | TWI_SLAVE_PIEN_bm + | ((I2CX(_INTLVL) << TWI_SLAVE_INTLVL_gp) & TWI_SLAVE_INTLVL_gm); + TWIX.SLAVE.ADDR = I2CX(_ADDRESS) << 1; +} + +void i2cX(s_register_reset_callback)(i2cs_reset_callback_t f) +{ + INTLVL_DISABLE_BLOCK(I2CX(_INTLVL)) { + i2cX().reset_callback = f; + } +} + +void i2cX(s_register_recv_callback)(i2cs_recv_callback_t f) +{ + INTLVL_DISABLE_BLOCK(I2CX(_INTLVL)) { + i2cX().recv_callback = f; + } +} + +void i2cX(s_register_prepare_send_callback)(i2cs_prepare_send_callback_t f) +{ + INTLVL_DISABLE_BLOCK(I2CX(_INTLVL)) { + i2cX().prepare_send_callback = f; + } +} + +// Interrupt handler +ISR(twiX(_TWIS_vect)) +{ + + i2cs_t *i2cs = &i2cX(); + + uint8_t status = TWIX.SLAVE.STATUS; + + if(status & TWI_SLAVE_BUSERR_bm || status & TWI_SLAVE_COLL_bm) { + // bus error happened + i2cs->state = I2CS_STATE_NONE; + if(i2cs->reset_callback) { + i2cs->reset_callback(); + } + return; + } + + else if(status & TWI_SLAVE_APIF_bm) { + // address / stop interrupt + + // check previous state, call recvd callback + // if previous frame was a master-write + if(i2cs->state == I2CS_STATE_WRITE) { + if(i2cs->recv_callback) { + i2cs->recv_callback(i2cs->recv_buffer, i2cs->recvd_bytes); + } + } + + if(status & TWI_SLAVE_AP_bm) { + // valid address interrupt + if(status & TWI_SLAVE_DIR_bm) { + // master read operation + + i2cs->state = I2CS_STATE_READ; + if(i2cs->prepare_send_callback) { + + i2cs->sent_bytes = 0; + + // ask user to provision send buffer + uint8_t rsz = i2cs->prepare_send_callback(i2cs->send_buffer, + sizeof(i2cs->send_buffer)); + if(rsz > 0) { + // user got some data to send + i2cs->bytes_to_send = MIN(rsz, sizeof(i2cs->send_buffer)); + // ACK + TWIX.SLAVE.CTRLB = TWI_SLAVE_CMD_RESPONSE_gc; + return; + } + i2cs->bytes_to_send = 0; + } + + // NACK, refuse read + TWIX.SLAVE.CTRLB = TWI_SLAVE_ACKACT_bm | TWI_SLAVE_CMD_RESPONSE_gc; + return; + } + else { + // master write operation + i2cs->state = I2CS_STATE_WRITE; + + // clear recv buffer + i2cs->recvd_bytes = 0; + // confirm operation + TWIX.SLAVE.CTRLB = TWI_SLAVE_CMD_RESPONSE_gc; + } + return; + } + else { + // STOP condition interrupt + TWIX.SLAVE.CTRLB = TWI_SLAVE_CMD_COMPTRANS_gc; + + i2cs->state = I2CS_STATE_NONE; + if(i2cs->reset_callback) { + i2cs->reset_callback(); + } + return; + } + } + + else if(status & TWI_SLAVE_DIF_bm) { + // data interruption + if(status & TWI_SLAVE_DIR_bm) { + // master read operation + + if(i2cs->sent_bytes > 0 && (status & TWI_SLAVE_RXACK_bm)) { + // previous byte was NACKed by master + TWIX.SLAVE.CTRLB = TWI_SLAVE_CMD_COMPTRANS_gc; + i2cs->state = I2CS_STATE_NONE; + return; + } + + if(i2cs->sent_bytes < i2cs->bytes_to_send) { + // push byte + uint8_t byte = i2cs->send_buffer[i2cs->sent_bytes++]; + TWIX.SLAVE.DATA = byte; + + // ACK read, continue transmission + TWIX.SLAVE.CTRLB = TWI_SLAVE_CMD_RESPONSE_gc; + } + else { + // ACK read, end transmission + TWIX.SLAVE.CTRLB = TWI_SLAVE_CMD_COMPTRANS_gc; + } + + return; + } + else { + // master write operation + uint8_t byte = TWIX.SLAVE.DATA; + + if(i2cs->recvd_bytes < I2CS_RECV_BUFFER_SIZE) { + i2cs->recv_buffer[i2cs->recvd_bytes++] = byte; + + // ACK write + TWIX.SLAVE.CTRLB = TWI_SLAVE_CMD_RESPONSE_gc; + } + else { + // NACK write, buffer is full + TWIX.SLAVE.CTRLB = TWI_SLAVE_ACKACT_bm | TWI_SLAVE_CMD_COMPTRANS_gc; + } + + return; + } + } + + // we encounter an unmanaged case + i2cs->state = I2CS_STATE_NONE; + i2cs->recvd_bytes = 0; + i2cs->sent_bytes = 0; + // reset client-side state + if(i2cs->reset_callback) { + i2cs->reset_callback(); + } +} + +#undef I2CX +#undef i2cX +#undef TWIX +#undef twiX #undef X_ diff --git a/modules/i2c/slavex.inc.h b/modules/i2c/slavex.inc.h new file mode 100644 index 0000000..9189b4a --- /dev/null +++ b/modules/i2c/slavex.inc.h @@ -0,0 +1,25 @@ +/** @defgroup i2c I2C + * @brief I2C module + */ +//@{ +/** + * @file + * @brief I2C slave definitions + */ +#define i2cX(s) X_(i2c,s) + +// I2cX singleton +extern i2cs_t i2cX(); + +/** @brief Register f to be called whenever a master-read operation was requested and user need + * to provision the send buffer */ +void i2cX(s_register_prepare_send_callback)(i2cs_prepare_send_callback_t f); + +/** @brief Register f to be called whenever a master-write operation as finished */ +void i2cX(s_register_recv_callback)(i2cs_recv_callback_t f); + +/** @brief Register f to be called whenever the i2c transaction was terminated (on STOP or ERROR) */ +void i2cX(s_register_reset_callback)(i2cs_reset_callback_t f); + +#undef i2cX +#undef X_