diff --git a/sw/device/examples/i2c.c b/sw/device/examples/i2c.c index 26c15ee1c..2129db949 100644 --- a/sw/device/examples/i2c.c +++ b/sw/device/examples/i2c.c @@ -28,10 +28,13 @@ int main(void) while (true) { timer_busy_sleep_us(timer, 1000u); + uint8_t w_data = 0; // Read current temperature from an AS6212 I^2C-bus sensor and print the value - if (i2c_write_byte(i2c, 0x48u, 0u)) { // select TVAL reg; also a presence check - uint16_t sensor_reading = i2c_read_byte(i2c, 0x48u); // read TVAl reg - if (sensor_reading != 0xFF) { // only print if we get a non-error value + i2c_write_bytes(i2c, 0x48u, &w_data, 1); + if (i2c_wait_write_finish(i2c)) { // select TVAL reg; also a presence check + i2c_read_bytes(i2c, 0x48u, 1); + if (i2c_wait_read_finish(i2c)) { + uint16_t sensor_reading = i2c_rdata_byte(i2c); // read TVAl reg uprintf(uart, "Temperature: 0x%x degC\n", (sensor_reading << 1)); // no decimal printf } diff --git a/sw/device/lib/hal/i2c.c b/sw/device/lib/hal/i2c.c index 4069fb6aa..cf9f43563 100644 --- a/sw/device/lib/hal/i2c.c +++ b/sw/device/lib/hal/i2c.c @@ -60,9 +60,10 @@ void i2c_init(i2c_t i2c) VOLATILE_WRITE(i2c->timing4, t4_reg); } -bool i2c_write_byte(i2c_t i2c, uint8_t addr, uint8_t data) +void i2c_write_bytes(i2c_t i2c, uint8_t addr, const uint8_t *data, uint8_t len_bytes) { - // Reset FMT FIFO (because we currently don't clean-up after errors) + // Reset the FMT FIFO as a precautionary step in case something goes wrong when controller's FSM + // is halted and the SW didn't manage to clear the FIFO during that scenario. i2c_fifo_ctrl fifo_ctrl_reg = { .fmtrst = 1u }; VOLATILE_WRITE(i2c->fifo_ctrl, fifo_ctrl_reg); @@ -74,31 +75,30 @@ bool i2c_write_byte(i2c_t i2c, uint8_t addr, uint8_t data) fdata_reg.start = 1u; VOLATILE_WRITE(i2c->fdata, fdata_reg); - // Send stop bit and data - fdata_reg.fbyte = data; fdata_reg.start = 0; - fdata_reg.stop = 1u; - VOLATILE_WRITE(i2c->fdata, fdata_reg); - // Wait for transaction to complete and report simple succeed/fail - for (uint32_t ii = 0; ii < 10000000ul /*arbitrary number*/; ii++) { - i2c_intr i2c_intr_state_reg = VOLATILE_READ(i2c->intr_state); - if (i2c_intr_state_reg & i2c_intr_controller_halt) { - return false; // transaction failed + for (uint8_t i = 0; i < len_bytes; i++) { + // Check the overflow condition before writing to the FMT FIFO. + i2c_status i2c_status_reg = VOLATILE_READ(i2c->status); + + // Wait until FMT FIFO has some space + while (i2c_status_reg & i2c_status_fmtfull) { + i2c_status_reg = VOLATILE_READ(i2c->status); } - if (i2c_intr_state_reg & i2c_intr_cmd_complete) { - i2c_status i2c_status_reg = VOLATILE_READ(i2c->status); - if (i2c_status_reg & i2c_status_fmtempty) { - return true; // transaction succeeded - } + + // Send all data bytes; assert STOP only on the last byte + fdata_reg.fbyte = data[i]; + if (i == (len_bytes - 1u)) { + fdata_reg.stop = 1u; } + VOLATILE_WRITE(i2c->fdata, fdata_reg); } - return false; // timeout } -uint8_t i2c_read_byte(i2c_t i2c, uint8_t addr) +void i2c_read_bytes(i2c_t i2c, uint8_t addr, uint8_t len_bytes) { - // Reset FMT FIFO (because we currently don't clean-up after errors) + // Reset the FMT FIFO as a precautionary step in case something goes wrong when controller's FSM + // is halted and the SW didn't manage to clear the FIFO during that scenario. i2c_fifo_ctrl fifo_ctrl_reg = { .fmtrst = 1u }; VOLATILE_WRITE(i2c->fifo_ctrl, fifo_ctrl_reg); @@ -112,28 +112,67 @@ uint8_t i2c_read_byte(i2c_t i2c, uint8_t addr) // Send stop bit, read bit and number of bytes to read fdata_reg.readb = 1u; - fdata_reg.fbyte = 1u; // If readb = 1 then fbyte contains the number of bytes to read + fdata_reg.fbyte = len_bytes; // If readb = 1 then fbyte contains the number of bytes to read fdata_reg.start = 0; fdata_reg.stop = 1u; VOLATILE_WRITE(i2c->fdata, fdata_reg); +} + +bool i2c_wait_write_finish(i2c_t i2c) +{ + // Wait for transaction to complete and report simple succeed / fail + while (true) { + i2c_intr i2c_intr_state_reg = VOLATILE_READ(i2c->intr_state); + if (i2c_intr_state_reg & i2c_intr_controller_halt) { + // Reset FMT FIFO as controller's FSM is in halt + i2c_fifo_ctrl fifo_ctrl_reg = { .fmtrst = 1u }; + VOLATILE_WRITE(i2c->fifo_ctrl, fifo_ctrl_reg); - // Wait for transaction to complete and return either read data or 0xFF - for (uint32_t ii = 0; ii < 10000000ul /*arbitrary number*/; ii++) { + // According to programmer's guide, the CONTROLLER_EVENTS register would be cleared + // here to acknowledge the controller halt interrupt. However, since we want to + // treat a halt event as a failure, we intentionally skip clearing it. + return false; // Transaction failed + } + if (i2c_intr_state_reg & i2c_intr_cmd_complete) { + i2c_status i2c_status_reg = VOLATILE_READ(i2c->status); + if (i2c_status_reg & i2c_status_fmtempty) { + return true; // Transaction succeeded + } + } + } + return false; // Timeout +} + +bool i2c_wait_read_finish(i2c_t i2c) +{ + // Wait for transaction to complete and report simple succeed / fail + while (true) { i2c_intr i2c_intr_state_reg = VOLATILE_READ(i2c->intr_state); if (i2c_intr_state_reg & i2c_intr_controller_halt) { - return 0xFF; // transaction failed + // Reset FMT FIFO as controller's FSM is in halt + i2c_fifo_ctrl fifo_ctrl_reg = { .fmtrst = 1u }; + VOLATILE_WRITE(i2c->fifo_ctrl, fifo_ctrl_reg); + + // According to programmer's guide, the CONTROLLER_EVENTS register would be cleared + // here to acknowledge the controller halt interrupt. However, since we want to + // treat a halt event as a failure, we intentionally skip clearing it. + return false; // Transaction failed } i2c_status i2c_status_reg = VOLATILE_READ(i2c->status); if (i2c_status_reg & i2c_status_fmtempty) { - // transaction succeeded, return read data - i2c_rdata rdata_reg = VOLATILE_READ(i2c->rdata); - return rdata_reg.rdata; + return true; } } - return 0xFF; // timeout + return false; // Timeout } void enable_controller_mode(i2c_t i2c) { VOLATILE_WRITE(i2c->ctrl, i2c_ctrl_enablehost); } + +uint8_t i2c_rdata_byte(i2c_t i2c) +{ + i2c_rdata rdata_reg = VOLATILE_READ(i2c->rdata); + return rdata_reg.rdata; +} diff --git a/sw/device/lib/hal/i2c.h b/sw/device/lib/hal/i2c.h index 73857c40d..c46b1628c 100644 --- a/sw/device/lib/hal/i2c.h +++ b/sw/device/lib/hal/i2c.h @@ -90,8 +90,20 @@ void i2c_init(i2c_t i2c); -bool i2c_write_byte(i2c_t i2c, uint8_t addr, uint8_t data); -uint8_t i2c_read_byte(i2c_t i2c, uint8_t addr); +// Transmits multiple bytes to the target +void i2c_write_bytes(i2c_t i2c, uint8_t addr, const uint8_t *data, uint8_t len_bytes); + +// Receive multiple bytes from the target +void i2c_read_bytes(i2c_t i2c, uint8_t addr, uint8_t len_bytes); + +// Wait for the read transfer to complete by checking interrupt state and status register fields +bool i2c_wait_read_finish(i2c_t i2c); + +// Wait for the write transfer to complete by checking interrupt state and status register fields +bool i2c_wait_write_finish(i2c_t i2c); // Enable I2C in controller mode void enable_controller_mode(i2c_t i2c); + +// Return the data in the target's tx fifo +uint8_t i2c_rdata_byte(i2c_t i2c); diff --git a/sw/device/tests/i2c/smoketest.c b/sw/device/tests/i2c/smoketest.c index 8d82e0a34..254cb949c 100644 --- a/sw/device/tests/i2c/smoketest.c +++ b/sw/device/tests/i2c/smoketest.c @@ -11,15 +11,27 @@ // Read temperature from an AS6212 sensor (default address 0x48) static bool as6212_test(i2c_t i2c) { + uint8_t w_data = 0; + // Write the desired register index - if (!i2c_write_byte(i2c, 0x48u, 0u)) { + i2c_write_bytes(i2c, 0x48u, &w_data, 1); + + // Check if the write was successful + if (!i2c_wait_write_finish(i2c)) { return false; } + // Read current temperature - uint8_t byte = i2c_read_byte(i2c, 0x48u); - if (byte == 0xFFu /* error value */) { + i2c_read_bytes(i2c, 0x48u, 1); + + // Check if the read was successful + if (!i2c_wait_read_finish(i2c)) { return false; } + + // If the read was successful, then retrieve the data from the fifo. + uint8_t byte = i2c_rdata_byte(i2c); + int16_t tval = byte; // signed, as temperature can be negative tval <<= 8; // first byte is the most-significant byte of two tval >>= 7; // convert from units of 1/128 degC to 1 degC