-
Notifications
You must be signed in to change notification settings - Fork 2
I2C slave & clock calibration #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a5fdacd
256601a
92712d1
a12d18a
504bae6
2052d1f
32fb9f0
258733f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -151,5 +151,4 @@ int8_t i2cm_recv(i2cm_t *m, uint8_t addr, uint8_t *data, uint8_t n) | |
return i; | ||
} | ||
|
||
|
||
///@endcond |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ton avis sur la possibilité de mutualiser les variables recv/send (notamment les buffers) ? |
||
|
||
i2cs_recv_callback_t recv_callback; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tu peux retirer les lignes vides entre chaque définition de 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,31 +98,35 @@ 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) | ||
# error I2CD enabled as both master and slave | ||
#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) | ||
# error I2CE enabled as both master and slave | ||
#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) | ||
# error I2CF enabled as both master and slave | ||
#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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 <avarix.h> | ||
#include <avr/interrupt.h> | ||
#include <stddef.h> | ||
|
||
#error Slave mode not supported yet | ||
#define I2CX(s) X_(I2C,s) | ||
#define i2cX(s) X_(i2c,s) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should define and use |
||
#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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Une raison pour ne pas plutôt avoir une fonction qui prend en paramètre le C'est comme ça que fonctionne le module UART notamment. |
||
{ | ||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. La routine d'interruption étant conséquente, ça serait intéressant d'ajouter une indirection vers une fonction générique qui prendrait un Là encore, ça suit ce qui est fait pour l'UART. |
||
{ | ||
|
||
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_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ça n'est plus correct pour
i2cs_t
mais ça l'est toujours pouri2cm_t
.À adapter pour préserver la doc de
i2cm_t
et adapter pouri2cs_t
.Au passage,
i2cs_t
est censée être "privée" ou pas ?Si elle est privée, le mieux serait d'exporter des
i2cs_t*
et cacher la définition dei2cs_t
(la sortir du.h
).