soft i2c work
added a check to soft i2c master for clock stretch on write. mostly implemented soft i2c slave.
This commit is contained in:
parent
904fcf65b4
commit
16ffde1077
|
@ -10,7 +10,8 @@
|
|||
*
|
||||
* quirks and features:
|
||||
* - this implementation is blocking.
|
||||
* - partial clock stretching is supported (only during read phase).
|
||||
* - partial clock stretching is supported
|
||||
* (only during start of write, and within read phase).
|
||||
* - timings likely haven't been tested so long as this message exists.
|
||||
*/
|
||||
|
||||
|
@ -152,6 +153,9 @@ uint8_t i2cm_wr(uint8_t dat)
|
|||
uint8_t x;
|
||||
uint8_t ack;
|
||||
|
||||
SCL_IN_HI();
|
||||
while (!SCL_GET()); // clock stretch
|
||||
|
||||
for (x = 8; x; x--) {
|
||||
if (dat & 0x80) { SDA_IN_HI(); }
|
||||
else { SDA_OUTLO(); }
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* hastily written. likely has bugs.
|
||||
*
|
||||
* known quirks:
|
||||
* - STOP is never checked for
|
||||
* - a fast repeated start may not work as this code
|
||||
* does not check for START after the beginning
|
||||
*/
|
||||
|
@ -16,10 +15,13 @@
|
|||
#include <stdint.h>
|
||||
|
||||
|
||||
#define I2CSS_ERR_TIMEOUT 4 // timeout in bit-times before i2c slave releases control
|
||||
#define I2CSS_IDLE_TIMEOUT 2 // idle timeout required in bit-times before i2c slave can be addressed again
|
||||
|
||||
#define I2CSS_ERR_TIMEOUT 6 // 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
|
||||
|
||||
|
||||
// todo: use with struct instead of hard-coding to allow for multiple slaves, but make it fast too
|
||||
// note: will have issues with non-responsive slaves with more than one, so is it necessary?
|
||||
#define SDA_PIN 13
|
||||
#define SDA_BIT (1 << (SDA_PIN % 8))
|
||||
#define SDA_DIR_REG R8_PA_DIR_0
|
||||
|
@ -47,54 +49,51 @@
|
|||
|
||||
|
||||
|
||||
static uint16_t err_timeout;
|
||||
static uint16_t bit_time;
|
||||
static uint16_t idle_timeout = 0;
|
||||
|
||||
|
||||
|
||||
static inline inline void i2css_reload_timeout(uint16_t bit_time)
|
||||
static int8_t i2css_wait_for_scl_hi()
|
||||
{
|
||||
err_timeout = I2CSS_ERR_TIMEOUT * bit_time;
|
||||
int16_t timeout = I2CSS_ERR_TIMEOUT * bit_time;
|
||||
|
||||
while (!SCL_GET()) { if (!timeout--) return I2CSS_SCL_TIMEOUT; }
|
||||
return I2CSS_OK;
|
||||
}
|
||||
|
||||
static inline int8_t i2css_wait_for_scl_hi()
|
||||
{
|
||||
while (!SCL_GET()) { if (!err_timeout--) return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int8_t i2css_wait_for_scl_lo()
|
||||
static int8_t i2css_wait_for_scl_lo()
|
||||
{
|
||||
uint8_t old_data = SDA_GET();
|
||||
int16_t timeout = I2CSS_ERR_TIMEOUT * bit_time;
|
||||
|
||||
err_timeout--; // compensation
|
||||
timeout--; // compensation
|
||||
|
||||
while (SCL_GET()) {
|
||||
if (err_timeout <= 1) return -1;
|
||||
if (SDA_GET() != old_data) return -2;
|
||||
err_timeout -= 2;
|
||||
if (timeout <= 1) return I2CSS_SCL_TIMEOUT;
|
||||
if (SDA_GET() != old_data) return I2CSS_START_STOP;
|
||||
timeout -= 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return I2CSS_OK;
|
||||
}
|
||||
|
||||
static inline int8_t i2css_ack(uint16_t bit_time)
|
||||
{
|
||||
uint8_t ret;
|
||||
|
||||
SDA_OUTLO();
|
||||
|
||||
i2css_reload_timeout(bit_time);
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_ACK_ERROR;
|
||||
i2css_reload_timeout(bit_time);
|
||||
if (i2css_wait_for_scl_lo()) return I2CSS_ACK_TIMEOUT;
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
|
||||
SDA_IN_HI();
|
||||
|
||||
return 0;
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_ACK_TIMEOUT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// blocking implementation of i2c slave, with 8-bit address register.
|
||||
// basically, simulates a 24C02 EEPROM.
|
||||
// externally kicked off by a low transition on SCL.
|
||||
|
||||
int8_t i2css_idle_init(struct I2CSoftSlave *slave)
|
||||
{
|
||||
// set up a hardware timer to tick our idle timer
|
||||
|
@ -120,63 +119,206 @@ int8_t i2css_idle_check(struct I2CSoftSlave *slave)
|
|||
|
||||
int8_t i2css_read(struct I2CSoftSlave *slave)
|
||||
{
|
||||
return 0;
|
||||
int8_t i;
|
||||
int8_t ret;
|
||||
|
||||
uint8_t ack = 0;
|
||||
uint8_t dat = 0;
|
||||
|
||||
|
||||
// with no read function configured, we can't do anything
|
||||
if (!slave->rd_cb) {
|
||||
SCL_IN_HI();
|
||||
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();
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_READ_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
|
||||
// send data to master
|
||||
for (i = 8; i; i--) {
|
||||
if (dat &= 0x80) { SDA_IN_HI(); }
|
||||
else { SDA_OUTLO(); }
|
||||
|
||||
dat <<= 1;
|
||||
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_READ_ERROR;
|
||||
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_READ_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
}
|
||||
|
||||
// get (n)ack from master
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_READ_ERROR;
|
||||
|
||||
ack = SDA_GET();
|
||||
|
||||
return ack ? I2CSS_NACK : I2CSS_OK;
|
||||
}
|
||||
|
||||
int8_t i2css_write(struct I2CSoftSlave *slave)
|
||||
{
|
||||
int8_t i;
|
||||
int8_t ret;
|
||||
|
||||
uint8_t dat = 0;
|
||||
uint8_t ack;
|
||||
|
||||
|
||||
// release clock for master
|
||||
SCL_IN_HI();
|
||||
|
||||
// with no read function configured, we can't do anything
|
||||
if (!slave->wr_cb) {
|
||||
return I2CSS_NO_WRITE_FUNCTION;
|
||||
}
|
||||
|
||||
// get data from master
|
||||
for (i = 8; i; i--) {
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_WRITE_ERROR;
|
||||
|
||||
dat |= SDA_GET() ? 1 : 0;
|
||||
dat <<=1;
|
||||
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_WRITE_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
}
|
||||
|
||||
// stretch clock, write our data with callback
|
||||
SCL_OUTLO();
|
||||
ack = slave->wr_cb(dat);
|
||||
|
||||
// send (n)ack to master
|
||||
if (ack) { SDA_IN_HI(); } // nack
|
||||
else { SDA_OUTLO(); } // ack
|
||||
|
||||
// release clock and wait
|
||||
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;
|
||||
}
|
||||
|
||||
int8_t i2css_blocking(struct I2CSoftSlave *slave)
|
||||
// blocking implementation of i2c slave, with 8-bit address register.
|
||||
// basically, simulates a 24C02 EEPROM.
|
||||
// externally kicked off by a low transition on SCL.
|
||||
static inline int8_t i2css_process(struct I2CSoftSlave *slave)
|
||||
{
|
||||
int8_t i;
|
||||
int32_t i;
|
||||
int8_t ret;
|
||||
uint8_t rx_addr = 0;
|
||||
|
||||
SCL_IN_HI();
|
||||
|
||||
// wait for SCL low to indicate start condition
|
||||
i2css_reload_timeout(slave->bit_time);
|
||||
while (!SCL_GET()) { if (!err_timeout--) return I2CSS_START_SCL_NOT_LOW; }
|
||||
// wait for SCL low to indicate data has begun
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_START_SCL_NOT_LOW;
|
||||
if (ret) return ret;
|
||||
|
||||
|
||||
// read address from bus
|
||||
for (i = 0; i < 7; i++) {
|
||||
i2css_reload_timeout(slave->bit_time);
|
||||
for (i = 7; i; i--) {
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_ADDR_READ_ERROR;
|
||||
|
||||
rx_addr |= SDA_GET() ? 1 : 0;
|
||||
rx_addr <<=1;
|
||||
|
||||
i2css_reload_timeout(slave->bit_time);
|
||||
if (i2css_wait_for_scl_lo()) return I2CSS_ADDR_READ_TIMEOUT;
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_ADDR_READ_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
}
|
||||
|
||||
// is this us?
|
||||
slave->addr_last_seen = rx_addr;
|
||||
if (slave->addr != rx_addr) return I2CSS_ADDR_NO_MATCH;
|
||||
|
||||
// seems so. get the read/write bit
|
||||
i2css_reload_timeout(slave->bit_time);
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_ADDR_READ_LASTBIT_TIMEOUT;
|
||||
|
||||
rx_addr |= SDA_GET() ? 1 : 0;
|
||||
|
||||
// let the host know we're ready to serve
|
||||
i = i2css_ack(slave->bit_time);
|
||||
if (i) return i;
|
||||
ret = i2css_ack(slave->bit_time);
|
||||
if (ret) return ret;
|
||||
|
||||
// stretch clock while preparing slave to receive
|
||||
SCL_OUTLO();
|
||||
if (slave->start_cb) slave->start_cb();
|
||||
|
||||
switch (rx_addr & 1) {
|
||||
case 0: { // writing
|
||||
while (!i2css_write(slave));
|
||||
case 0: { // master is writing to us
|
||||
do {
|
||||
ret = i2css_write(slave);
|
||||
} while (!ret);
|
||||
break;
|
||||
}
|
||||
case 1: { // reading
|
||||
while (!i2css_read(slave)) {
|
||||
i = i2css_ack(slave->bit_time);
|
||||
if (i) return i;
|
||||
}
|
||||
case 1: { // master is reading from us
|
||||
do {
|
||||
// stretch clock
|
||||
SCL_OUTLO();
|
||||
ret = i2css_read(slave);
|
||||
} while (!ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int8_t i2css_blocking(struct I2CSoftSlave *slave)
|
||||
{
|
||||
int8_t ret;
|
||||
int8_t start;
|
||||
|
||||
do {
|
||||
ret = i2css_process(slave);
|
||||
start = 0;
|
||||
if (ret == I2CSS_START_STOP) {
|
||||
// determine which
|
||||
// high = stop, low = start
|
||||
start = SDA_GET() ? 0 : 1;
|
||||
}
|
||||
} while (start);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// call this function with SDA falling low interrupt.
|
||||
int8_t i2css_start(struct I2CSoftSlave *slave)
|
||||
{
|
||||
uint8_t scl;
|
||||
|
||||
// idle our clock 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;
|
||||
|
||||
// ensure SDA pin will go low when set as an output
|
||||
SDA_SET_LO();
|
||||
|
||||
// if SCL is high, this should be a start condition.
|
||||
if (scl) {
|
||||
return i2css_blocking(slave);
|
||||
}
|
||||
|
||||
// if it isn't, there must be some other communication going on, and it doesn't involve us
|
||||
return I2CSS_BUS_BUSY;
|
||||
}
|
||||
|
|
|
@ -15,16 +15,19 @@
|
|||
|
||||
typedef struct I2CSoftSlave {
|
||||
uint8_t addr;
|
||||
uint8_t addr2;
|
||||
uint8_t addr_last_seen;
|
||||
uint16_t bit_time;
|
||||
uint8_t (*rd_cb)(uint8_t addr, uint8_t *data); // return 0 to ACK
|
||||
void (*wr_cb)(uint8_t addr, uint8_t data);
|
||||
void (*start_cb)(); // call on any new read/write operation
|
||||
uint8_t (*rd_cb)(); // returns data
|
||||
int8_t (*wr_cb)(uint8_t data); // return 0 to ACK, all other NACK
|
||||
} I2CSoftSlave;
|
||||
|
||||
|
||||
enum I2CSoftSlave_Error {
|
||||
// standard responses
|
||||
I2CSS_OK,
|
||||
I2CSS_STOP,
|
||||
I2CSS_NACK,
|
||||
I2CSS_ADDR_NO_MATCH,
|
||||
// errors
|
||||
I2CSS_START_SCL_NOT_LOW = -1,
|
||||
|
@ -33,11 +36,22 @@ enum I2CSoftSlave_Error {
|
|||
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_NO_READ_FUNCTION = -32,
|
||||
I2CSS_NO_WRITE_FUNCTION = -33,
|
||||
I2CSS_SCL_TIMEOUT = -126,
|
||||
I2CSS_BUS_BUSY = -127,
|
||||
I2CSS_START_STOP = -128
|
||||
};
|
||||
|
||||
|
||||
|
||||
int8_t i2css_blocking(struct I2CSoftSlave *slave);
|
||||
// call this function with SDA falling low interrupt.
|
||||
void i2css_init();
|
||||
int8_t i2css_start(struct I2CSoftSlave *slave);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -107,5 +107,5 @@ void ch32sub_write_1b(uint16_t reg, uint8_t dat)
|
|||
|
||||
void ch32sub_rgb_hwen(uint8_t en)
|
||||
{
|
||||
i2c_addr(SUB_I2C_ADDR, 1);
|
||||
ch32sub_write_1b(REG_RGB_HWEN, en);
|
||||
}
|
||||
|
|
|
@ -28,11 +28,75 @@
|
|||
|
||||
struct I2CSoftSlave gat_slave;
|
||||
|
||||
|
||||
|
||||
enum {
|
||||
GAT_MODE_STARTING,
|
||||
GAT_MODE_READ,
|
||||
GAT_MODE_WRITE
|
||||
};
|
||||
|
||||
|
||||
uint8_t gat_state;
|
||||
uint16_t gat_data_idx;
|
||||
uint8_t gat_data[GAT_DATA_SIZE];
|
||||
|
||||
|
||||
|
||||
uint8_t gat_i2c_read_cb()
|
||||
{
|
||||
uint8_t ret;
|
||||
|
||||
switch (gat_state) {
|
||||
case GAT_MODE_STARTING: {
|
||||
gat_state = GAT_MODE_READ;
|
||||
// no break
|
||||
}
|
||||
case GAT_MODE_READ: {
|
||||
// can't read past end of buffer; just send 0xff
|
||||
if (gat_data_idx == GAT_DATA_SIZE) {
|
||||
ret = 0xff;
|
||||
} else {
|
||||
ret = gat_data[gat_data_idx & 0xff];
|
||||
if (gat_data_idx <= GAT_DATA_SIZE) gat_data_idx++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
default: {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int8_t gat_i2c_write_cb(uint8_t dat)
|
||||
{
|
||||
switch (gat_state) {
|
||||
|
||||
case GAT_MODE_STARTING: {
|
||||
// master wrote our address
|
||||
gat_data_idx = dat;
|
||||
gat_state = GAT_MODE_WRITE;
|
||||
break;
|
||||
}
|
||||
case GAT_MODE_WRITE: {
|
||||
if (gat_data_idx >= GAT_DATA_SIZE) {
|
||||
return I2CSS_NACK;
|
||||
} else {
|
||||
gat_data[gat_data_idx++] = dat;
|
||||
}
|
||||
return I2CSS_OK;
|
||||
}
|
||||
default: {
|
||||
return I2CSS_NACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gat_i2c_start_cb()
|
||||
{
|
||||
gat_state = GAT_MODE_STARTING;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void gat_i2c_init()
|
||||
|
@ -43,11 +107,16 @@ void gat_i2c_init()
|
|||
// roughly guess bit time length for expected speed
|
||||
gat_slave.bit_time = GetSysClock() / GAT_I2C_SPEED / 4;
|
||||
|
||||
// set up callbacks
|
||||
gat_slave.start_cb = gat_i2c_start_cb;
|
||||
gat_slave.rd_cb = gat_i2c_read_cb;
|
||||
gat_slave.wr_cb = gat_i2c_write_cb;
|
||||
|
||||
// enable interrupt on SDA pin to detect start condition
|
||||
// this assumes GPIO has already been configured
|
||||
}
|
||||
|
||||
void gat_i2c_irq_cb()
|
||||
{
|
||||
i2css_blocking(&gat_slave);
|
||||
i2css_start(&gat_slave);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue