diff --git a/nametag8_CH592/user/comm/soft_i2c_master.c b/nametag8_CH592/user/comm/soft_i2c_master.c index 9827520..329d405 100644 --- a/nametag8_CH592/user/comm/soft_i2c_master.c +++ b/nametag8_CH592/user/comm/soft_i2c_master.c @@ -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(); } diff --git a/nametag8_CH592/user/comm/soft_i2c_slave.c b/nametag8_CH592/user/comm/soft_i2c_slave.c index 2ace9d1..fe2e878 100644 --- a/nametag8_CH592/user/comm/soft_i2c_slave.c +++ b/nametag8_CH592/user/comm/soft_i2c_slave.c @@ -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 -#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; } diff --git a/nametag8_CH592/user/comm/soft_i2c_slave.h b/nametag8_CH592/user/comm/soft_i2c_slave.h index cfb0e72..26207bc 100644 --- a/nametag8_CH592/user/comm/soft_i2c_slave.h +++ b/nametag8_CH592/user/comm/soft_i2c_slave.h @@ -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); diff --git a/nametag8_CH592/user/hw/ch32sub.c b/nametag8_CH592/user/hw/ch32sub.c index f7d3cc6..a97b7c3 100644 --- a/nametag8_CH592/user/hw/ch32sub.c +++ b/nametag8_CH592/user/hw/ch32sub.c @@ -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); } diff --git a/nametag8_CH592/user/hw/gat/gat_i2c.c b/nametag8_CH592/user/hw/gat/gat_i2c.c index 9806e14..c1aaaf0 100644 --- a/nametag8_CH592/user/hw/gat/gat_i2c.c +++ b/nametag8_CH592/user/hw/gat/gat_i2c.c @@ -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); }