added bus state tracking, hopefully fixed some clock stretching on soft i2c slave

This commit is contained in:
true 2024-10-30 19:52:09 -07:00
parent 52435ea3f0
commit a7fb413655
2 changed files with 57 additions and 34 deletions

View File

@ -17,7 +17,7 @@
#define I2CSS_ERR_TIMEOUT 10 // timeout in bit-times before i2c slave releases control
#define I2CSS_IDLE_TIMEOUT 3 // idle timeout required in bit-times before i2c slave can be addressed again
#define I2CSS_IDLE_TIMEOUT 4 // idle timeout required in bit-times before i2c slave can be addressed again
// todo: use with struct instead of hard-coding to allow for multiple slaves, but make it fast too
@ -49,9 +49,18 @@
enum {
BUS_STATE_IDLE,
BUS_STATE_BUSY
};
static uint16_t bit_time;
static uint16_t idle_timeout = 0;
static uint8_t bus_state = BUS_STATE_IDLE;
static inline int8_t i2css_wait_for_scl_hi()
@ -64,7 +73,7 @@ static inline int8_t i2css_wait_for_scl_hi()
static inline int8_t i2css_wait_for_scl_lo()
{
uint8_t old_data = SDA_GET();
uint32_t old_data = SDA_GET();
int16_t timeout = I2CSS_ERR_TIMEOUT * bit_time;
timeout--; // compensation
@ -96,8 +105,12 @@ static inline int8_t i2css_ack(uint16_t bit_time)
int8_t i2css_idle_init(struct I2CSoftSlave *slave)
{
// on ch592, timer supports falling to falling edge counting
// we could use this to interrupt after enough idle time, but...
// the pins were misplaced at some point, lol. oops.
// set up a hardware timer to tick our idle timer
// todo
// idle_timeout = slave->bit_time * I2CSS_IDLE_TIMEOUT;
}
@ -134,16 +147,15 @@ int8_t i2css_read(struct I2CSoftSlave *slave)
return I2CSS_NO_READ_FUNCTION;
}
// clock low for first bit / stretch, get data to send to master
SCL_OUTLO();
dat = slave->rd_cb();
// release clock, wait for clock to be low by the master
SCL_IN_HI();
// wait for clock to go low
ret = i2css_wait_for_scl_lo();
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_READ_TIMEOUT;
if (ret) return ret;
// then stretch the clock while we prepare data
SCL_OUTLO();
dat = slave->rd_cb();
// send data to master
for (i = 8; i; i--) {
if (dat &= 0x80) { SDA_IN_HI(); }
@ -151,6 +163,9 @@ int8_t i2css_read(struct I2CSoftSlave *slave)
dat <<= 1;
// release clock if it was held high
SCL_IN_HI();
if (i2css_wait_for_scl_hi()) return I2CSS_READ_ERROR;
ret = i2css_wait_for_scl_lo();
@ -184,6 +199,11 @@ int8_t i2css_write(struct I2CSoftSlave *slave)
return I2CSS_NO_WRITE_FUNCTION;
}
// wait for clock to go low
ret = i2css_wait_for_scl_lo();
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_WRITE_TIMEOUT;
if (ret) return ret;
// get data from master
for (i = 8; i; i--) {
if (i2css_wait_for_scl_hi()) return I2CSS_WRITE_ERROR;
@ -208,14 +228,6 @@ int8_t i2css_write(struct I2CSoftSlave *slave)
SCL_IN_HI();
if (i2css_wait_for_scl_hi()) return I2CSS_WRITE_ERROR;
// wait for clock to go low
ret = i2css_wait_for_scl_lo();
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_WRITE_TIMEOUT;
if (ret) return ret;
// then stretch
SCL_OUTLO();
return 0;
}
@ -262,9 +274,15 @@ static inline int8_t i2css_process(struct I2CSoftSlave *slave)
if (ret) return ret;
// stretch clock while preparing slave to receive
ret = i2css_wait_for_scl_lo();
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_SCL_NEVER_WENT_LOW;
if (ret) return ret;
SCL_OUTLO();
// we're good to go now. do initial prep for data transfer
if (slave->start_cb) slave->start_cb();
// then transfer data
switch (rx_addr & 1) {
case 0: { // master is writing to us
do {
@ -274,14 +292,15 @@ static inline int8_t i2css_process(struct I2CSoftSlave *slave)
}
case 1: { // master is reading from us
do {
// stretch clock
SCL_OUTLO();
ret = i2css_read(slave);
} while (!ret);
break;
}
}
SDA_IN_HI();
SCL_IN_HI();
return ret;
}
@ -291,6 +310,8 @@ int8_t i2css_blocking(struct I2CSoftSlave *slave)
int8_t ret;
int8_t start;
bus_state = BUS_STATE_BUSY;
do {
ret = i2css_process(slave);
start = 0;
@ -298,6 +319,11 @@ int8_t i2css_blocking(struct I2CSoftSlave *slave)
// determine which
// high = stop, low = start
start = SDA_GET() ? 0 : 1;
// if stop detected, bus is idle NOW
if (!start) {
bus_state = BUS_STATE_IDLE;
}
}
} while (start);
@ -311,25 +337,21 @@ int8_t i2css_start(struct I2CSoftSlave *slave)
int8_t ret;
uint8_t scl;
if (idle_timeout) {
if (bus_state != BUS_STATE_IDLE) {
// we're still waiting for bus idle time before we reply to requests
// this will now reset the timeout
if (idle_timeout < I2CSS_IDLE_TIMEOUT) {
idle_timeout = I2CSS_IDLE_TIMEOUT;
}
i2css_idle_init(slave);
return I2CSS_IN_TIMEOUT;
return I2CSS_BUS_BUSY;
}
// idle our clock and check for start/stop
// tristate our clock (should be already) and check for start/stop
SCL_IN_HI();
scl = SCL_GET();
// module will use this bit time for this communication
bit_time = slave->bit_time;
// if SCL is high, this may be a start condition.
if (scl) {
bit_time = slave->bit_time;
ret = i2css_blocking(slave);
} else ret = I2CSS_BUS_BUSY;

View File

@ -36,12 +36,13 @@ enum I2CSoftSlave_Error {
I2CSS_ADDR_READ_ERROR = -2, // could be timeout, start or stop condition detected
I2CSS_ADDR_READ_TIMEOUT = -3,
I2CSS_ADDR_READ_LASTBIT_TIMEOUT = -4,
I2CSS_ACK_ERROR = -5, // could be timeout, start or stop condition detected
I2CSS_ACK_TIMEOUT = -6,
I2CSS_READ_ERROR = -7,
I2CSS_READ_TIMEOUT = -8,
I2CSS_WRITE_ERROR = -9,
I2CSS_WRITE_TIMEOUT = -10,
I2CSS_SCL_NEVER_WENT_LOW = -5,
I2CSS_ACK_ERROR = -6, // could be timeout, start or stop condition detected
I2CSS_ACK_TIMEOUT = -7,
I2CSS_READ_ERROR = -8,
I2CSS_READ_TIMEOUT = -9,
I2CSS_WRITE_ERROR = -10,
I2CSS_WRITE_TIMEOUT = -11,
I2CSS_NO_READ_FUNCTION = -32,
I2CSS_NO_WRITE_FUNCTION = -33,
I2CSS_SCL_TIMEOUT = -126,