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
	
	Block a user