diff --git a/Inc/i2c.h b/Inc/i2c.h index a11938b..f758e42 100644 --- a/Inc/i2c.h +++ b/Inc/i2c.h @@ -25,6 +25,11 @@ #define I2C_RECOVERY_CLOCKS 9U /* Clock pulses for bus recovery */ #define I2C_MASTER_ADDRESS 0x00U /* Master address (not used) */ +/* Optional delay between the sub-address write phase and repeated START. */ +#ifndef I2C_READ_RESTART_DELAY_MS +#define I2C_READ_RESTART_DELAY_MS 0U +#endif + /* Legacy compatibility */ #define I2C_OK 1 #define I2C_FAIL 0 @@ -91,29 +96,33 @@ void i2c_scan(void); #endif /*! - \brief write 16-bit data to I2C device + \brief write two data bytes using an 8-bit register address \param[in] slave_addr: 7-bit slave address - \param[in] reg_addr: register address (16-bit, high byte first) + \param[in] reg_addr: 8-bit register address \param[in] data: pointer to 2-byte data array \param[out] none \retval i2c_result_t + \note legacy compatibility wrapper; "16bits" describes data length, + not register-address width */ i2c_result_t i2c_write_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t data[2]); /*! - \brief read 16-bit data from I2C device + \brief read two data bytes using an 8-bit register address \param[in] slave_addr: 7-bit slave address - \param[in] reg_addr: register address + \param[in] reg_addr: 8-bit register address \param[out] data: pointer to 2-byte data buffer \retval i2c_result_t + \note legacy compatibility wrapper; "16bits" describes data length, + not register-address width */ i2c_result_t i2c_read_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data); -/* Generic read/write functions with configurable length */ +/* Legacy generic APIs: register address is sent as two bytes, MSB first. */ /*! - \brief write data to I2C device with configurable length + \brief write data using a 16-bit register address \param[in] slave_addr: slave device address (7-bit) - \param[in] reg_addr: register address + \param[in] reg_addr: 16-bit register address (MSB first on the bus) \param[in] data: pointer to data buffer \param[in] length: number of bytes to write (1-255) \param[out] none @@ -122,7 +131,7 @@ i2c_result_t i2c_read_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data i2c_result_t i2c_write(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint8_t length); /*! - \brief read data from I2C device with configurable length + \brief read data using a 16-bit register address \param[in] slave_addr: slave device address (7-bit) \param[in] reg_addr: register address (16-bit, high byte first) \param[out] data: pointer to data buffer @@ -131,6 +140,51 @@ i2c_result_t i2c_write(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uin */ i2c_result_t i2c_read(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint8_t length); +/* Explicit register-address-width APIs. */ +/*! + \brief write data using an 8-bit register address + \param[in] slave_addr: slave device address (7-bit) + \param[in] reg_addr: 8-bit register address + \param[in] data: pointer to data buffer + \param[in] length: number of bytes to write (1-255) + \retval i2c_result_t: operation result +*/ +i2c_result_t i2c_write_reg8(uint8_t slave_addr, uint8_t reg_addr, + const uint8_t *data, uint8_t length); + +/*! + \brief read data using an 8-bit register address + \param[in] slave_addr: slave device address (7-bit) + \param[in] reg_addr: 8-bit register address + \param[out] data: pointer to data buffer + \param[in] length: number of bytes to read (1-255) + \retval i2c_result_t: operation result +*/ +i2c_result_t i2c_read_reg8(uint8_t slave_addr, uint8_t reg_addr, + uint8_t *data, uint8_t length); + +/*! + \brief write data using a 16-bit register address + \param[in] slave_addr: slave device address (7-bit) + \param[in] reg_addr: 16-bit register address (MSB first on the bus) + \param[in] data: pointer to data buffer + \param[in] length: number of bytes to write (1-255) + \retval i2c_result_t: operation result +*/ +i2c_result_t i2c_write_reg16(uint8_t slave_addr, uint16_t reg_addr, + const uint8_t *data, uint8_t length); + +/*! + \brief read data using a 16-bit register address + \param[in] slave_addr: slave device address (7-bit) + \param[in] reg_addr: 16-bit register address (MSB first on the bus) + \param[out] data: pointer to data buffer + \param[in] length: number of bytes to read (1-255) + \retval i2c_result_t: operation result +*/ +i2c_result_t i2c_read_reg16(uint8_t slave_addr, uint16_t reg_addr, + uint8_t *data, uint8_t length); + /* Pure read response without sub-address phase (for commands that already sent read request parameters) */ /*! \brief read data from I2C device without register/sub-address phase @@ -141,7 +195,13 @@ i2c_result_t i2c_read(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint */ i2c_result_t i2c_read_raw(uint8_t slave_addr, uint8_t *data, uint8_t length); -#if DEBUG_VERBOSE == CFG_ENABLE +/*! + \brief probe one 7-bit address (ACK only, no data phase) + \param[in] slave_addr: slave device address (7-bit) + \retval bool: true when the address was acknowledged +*/ +bool i2c_probe_ack(uint8_t slave_addr); + /*! \brief get status string for debugging \param[in] status: i2c_result_t value @@ -149,6 +209,5 @@ i2c_result_t i2c_read_raw(uint8_t slave_addr, uint8_t *data, uint8_t length); \retval const char* status string */ const char* i2c_get_status_string(i2c_result_t status); -#endif #endif //I2C_H diff --git a/Src/i2c.c b/Src/i2c.c index 705070c..fb94521 100644 --- a/Src/i2c.c +++ b/Src/i2c.c @@ -24,7 +24,6 @@ void i2c_gpio_config(void) { gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SDA_PIN); gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SDA_PIN); } - /*! \brief configure the I2CX interface \param[in] none @@ -101,6 +100,8 @@ i2c_result_t i2c_bus_reset(void) { /* 4. If SCL low -> stuck (cannot proceed) */ if (!scl_value2) { + /* Always restore the peripheral before returning to the caller. */ + i2c_config(); return I2C_RECOVERY_SCL_STUCK_LOW; } @@ -115,6 +116,7 @@ i2c_result_t i2c_bus_reset(void) { bool sda_released = false; for (uint8_t i = 0; i < I2C_RECOVERY_CLOCKS && !sda_released; i++) { if (!i2c_generate_scl_pulse()) { + i2c_config(); return I2C_RECOVERY_SCL_STUCK_LOW; /* SCL failed to go high */ } if (gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN)) { @@ -122,6 +124,7 @@ i2c_result_t i2c_bus_reset(void) { } } if (!sda_released) { + i2c_config(); return I2C_RECOVERY_SDA_STUCK_LOW; } /* 7. Generate a STOP condition to leave bus in idle state */ @@ -147,7 +150,6 @@ i2c_result_t i2c_bus_reset(void) { */ #if DEBUG_VERBOSE == CFG_ENABLE void i2c_scan(void) { - uint32_t timeout; uint8_t address; int found_devices = 0; @@ -160,33 +162,7 @@ void i2c_scan(void) { while (usart_flag_get(UART_PHY, USART_FLAG_TC) == RESET) {} for (address = 1; address < 127; address++) { - timeout = 0; - - // 生成起始条件 - while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) - timeout++; - if (timeout >= I2C_TIME_OUT) { - continue; // 超时,跳过该地址 - } - i2c_start_on_bus(I2C0); - timeout = 0; - - // 等待起始条件发送完成 - while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND) && (timeout < I2C_TIME_OUT)) - timeout++; - if (timeout >= I2C_TIME_OUT) { - continue; // 超时,跳过该地址 - } - i2c_master_addressing(I2C0, (address << 1), I2C_TRANSMITTER); - timeout = 0; - - // 等待地址发送完成或错误 - while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND) && !i2c_flag_get(I2C0, I2C_FLAG_AERR) && (timeout < I2C_TIME_OUT)) - timeout++; - - if (timeout < I2C_TIME_OUT && i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) { - // 设备响应 - 清除地址标志 - i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); + if (i2c_probe_ack(address)) { // printf("Found device at 0x%02X\r\n", address); const char* msg2_prefix = "Found device at 0x"; for (uint8_t i = 0; msg2_prefix[i] != '\0'; i++) { @@ -206,18 +182,7 @@ void i2c_scan(void) { } while (usart_flag_get(UART_PHY, USART_FLAG_TC) == RESET) {} found_devices++; - } else if (i2c_flag_get(I2C0, I2C_FLAG_AERR)) { - // 设备无响应 - 清除错误标志 - i2c_flag_clear(I2C0, I2C_FLAG_AERR); } - - // 生成停止条件 - i2c_stop_on_bus(I2C0); - - timeout = 0; - - while (i2c_flag_get(I2C0, I2C_FLAG_STPDET) && (timeout < I2C_TIME_OUT)) - timeout++; } if (found_devices == 0) { @@ -299,1004 +264,514 @@ static i2c_result_t _i2c_wait_flag_clear_timeout(uint32_t flag) return I2C_RESULT_SUCCESS; } -i2c_result_t i2c_write_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t data[2]) { - i2c_state_t state = I2C_STATE_START; - uint16_t timeout = 0; - uint8_t retry_count = 0; - - /* parameter validation */ - if (data == NULL || slave_addr > 0x7F) { - return I2C_RESULT_INVALID_PARAM; - } - - while (retry_count < I2C_MAX_RETRY) { - switch (state) { - case I2C_STATE_START: - timeout = 0; - - /* wait for bus to be idle */ - while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - i2c_start_on_bus(I2C0); - timeout = 0; - state = I2C_STATE_SEND_ADDRESS; - break; - - case I2C_STATE_SEND_ADDRESS: - /* wait for start condition to be sent. SBSEND flag */ - while((!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - /* send slave address */ - i2c_master_addressing(I2C0, slave_addr << 1, I2C_TRANSMITTER); - timeout = 0; - state = I2C_STATE_CLEAR_ADDRESS; - break; - - case I2C_STATE_CLEAR_ADDRESS: - /* wait for address to be acknowledged.ADDSEND set means i2c slave sends ACK */ - while ((!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) && (!i2c_flag_get(I2C0, I2C_FLAG_AERR)) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } else if (i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) - { - i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); - timeout =0; - state = I2C_STATE_TRANSMIT_REG; - break; - } else { - i2c_flag_clear(I2C0, I2C_FLAG_AERR); - timeout =0; -#if DEBUG_VERBOSE == CFG_ENABLE - printf("IIC write failed for Error Slave Address. \n"); -#endif - return I2C_RESULT_NACK; - } - - case I2C_STATE_TRANSMIT_REG: - /* wait until the transmit data buffer is empty */ - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send register address */ - i2c_data_transmit(I2C0, reg_addr); - state = I2C_STATE_TRANSMIT_DATA; - break; - - case I2C_STATE_TRANSMIT_DATA: - /* wait until the transmit data buffer is empty */ - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send register MSB value */ - i2c_data_transmit(I2C0, data[0]); - - /* wait until the transmit data buffer is empty */ - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - if (i2c_flag_get(I2C0, I2C_FLAG_AERR)) { - i2c_stop_on_bus(I2C0); - return I2C_RESULT_NACK; - } else if (i2c_flag_get(I2C0, I2C_FLAG_BERR) || i2c_flag_get(I2C0, I2C_FLAG_LOSTARB)) { - // 可按需清标志 - i2c_stop_on_bus(I2C0); - return I2C_RESULT_ERROR; - } - - /* send register LSB value */ - i2c_data_transmit(I2C0, data[1]); - - /* wait until BTC bit is set */ - if(_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - state = I2C_STATE_STOP; - break; - - case I2C_STATE_STOP: - /* send a stop condition to I2C bus */ - i2c_stop_on_bus(I2C0); - - timeout = 0; - while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - /* i2c master sends STOP signal successfully */ - /* success */ - return I2C_RESULT_SUCCESS; - - case I2C_STATE_ERROR: - /* I2C bus error, try to reset the bus and retry */ - i2c_bus_reset(); - - retry_count ++; - if (retry_count >= I2C_MAX_RETRY) - { -#if DEBUG_VERBOSE == CFG_ENABLE - printf("IIC write failed after %d retries\n", I2C_MAX_RETRY); -#endif - return I2C_RESULT_ERROR; - } - - /* reset state machine for retry */ - state = I2C_STATE_START; - timeout = 0; - - /* small delay before retry */ - delay_10us(10); - break; - - default: - state = I2C_STATE_START; - break; - } - } - return I2C_RESULT_TIMEOUT; -} - -i2c_result_t i2c_read_16bits(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data) { - i2c_state_t state = I2C_STATE_START; - uint16_t timeout = 0; - uint8_t retry_count = 0; - bool write_phase = true; - - // 参数检查:防止空指针和非法地址 - if (data == NULL || slave_addr > 0x7F) { - return I2C_RESULT_INVALID_PARAM; - } - - /* enable acknowledge */ - i2c_ack_config(I2C0, I2C_ACK_ENABLE); - - while (retry_count < (uint8_t)I2C_MAX_RETRY) { - switch (state) { - case I2C_STATE_START: - timeout = 0; - - /* wait for bus to be idle */ - if(_i2c_wait_flag_clear_timeout(I2C_FLAG_I2CBSY) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - // send start condition - i2c_start_on_bus(I2C0); - state = I2C_STATE_SEND_ADDRESS; - break; - - case I2C_STATE_SEND_ADDRESS: - /* wait for start condition to be sent */ - if(_i2c_wait_flag_timeout(I2C_FLAG_SBSEND) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - // send slave address - if (write_phase) { - /* write phase: send address with write bit */ - i2c_master_addressing(I2C0, (slave_addr << 1), I2C_TRANSMITTER); - } else { - /* read phase: send address with read bit */ - i2c_master_addressing(I2C0, (slave_addr << 1) | 0x01, I2C_RECEIVER); - } - - state = I2C_STATE_CLEAR_ADDRESS; - timeout = 0; - break; - - case I2C_STATE_CLEAR_ADDRESS: - /* wait for address to be acknowledged */ - while ((!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - if (write_phase) { - /* clear address flag (write phase) */ - i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); - state = I2C_STATE_TRANSMIT_DATA; - } else { - /* READ phase for 2 bytes: set POS=NEXT and disable ACK BEFORE clearing ADDR */ - i2c_ackpos_config(I2C0, I2C_ACKPOS_NEXT); - i2c_ack_config(I2C0, I2C_ACK_DISABLE); - - /* now clear address flag to release SCL and enter data phase */ - i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); - - state = I2C_STATE_RECEIVE_DATA; - } - - timeout = 0; - break; - - case I2C_STATE_TRANSMIT_DATA: - /* wait for transmit buffer to be empty */ - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send register address */ - i2c_data_transmit(I2C0, reg_addr); - state = I2C_STATE_RESTART; - break; - - case I2C_STATE_RESTART: - /* wait for byte transfer complete BTC: Bit Transfer Complete */ - if(_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* generate repeated start condition */ - i2c_start_on_bus(I2C0); - - /* wait for repeated start condition to be sent */ - if(_i2c_wait_flag_timeout(I2C_FLAG_SBSEND) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send slave address with read bit (R/W bit is set by library) */ - i2c_master_addressing(I2C0, (slave_addr << 1), I2C_RECEIVER); - - /* switch to read phase */ - write_phase = false; - state = I2C_STATE_CLEAR_ADDRESS; - timeout = 0; - break; - - case I2C_STATE_RECEIVE_DATA: - /* Wait for BTC (both bytes received) */ - if(_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* Send STOP before reading the last two bytes */ - i2c_stop_on_bus(I2C0); - - /* Read the two bytes back-to-back */ - data[0] = i2c_data_receive(I2C0); - data[1] = i2c_data_receive(I2C0); - - state = I2C_STATE_STOP; - break; - - case I2C_STATE_STOP: - /* wait for stop condition to complete */ - while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - /* i2c master sends STOP signal successfully */ - /* success */ - return I2C_RESULT_SUCCESS; - - case I2C_STATE_ERROR: - /* I2C bus error, try to reset the bus and retry */ - i2c_bus_reset(); - - retry_count++; - if (retry_count >= I2C_MAX_RETRY) { -#if DEBUG_VERBOSE == CFG_ENABLE - printf("IIC read failed after %d retries\n", I2C_MAX_RETRY); -#endif - return I2C_RESULT_ERROR; - } - - /* reset state machine for retry */ - state = I2C_STATE_START; - write_phase = true; - timeout = 0; - - /* small delay before retry */ - delay_10us(10); - break; - - default: - state = I2C_STATE_START; - break; - } - } - return I2C_RESULT_TIMEOUT; -} - -/*! - \brief write data to I2C device with configurable length - \param[in] slave_addr: slave device address (7-bit) - \param[in] reg_addr: register address (16-bit, high byte first) - \param[in] data: pointer to data buffer - \param[in] length: number of bytes to write (1-255) - \param[out] none - \retval i2c_result_t: operation result -*/ -i2c_result_t i2c_write(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint8_t length) { - i2c_state_t state = I2C_STATE_START; - uint16_t timeout = 0; - uint8_t retry_count = 0; - uint8_t data_index = 0; - - /* parameter validation */ - if (data == NULL || slave_addr > 0x7F || length == 0) { -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C read invalid param: slave=0x%02X, len=%u, data=%p\r\n", slave_addr, length, data); -#endif - return I2C_RESULT_INVALID_PARAM; - } - - while (retry_count < I2C_MAX_RETRY) { - switch (state) { - case I2C_STATE_START: - timeout = 0; - data_index = 0; - - /* wait for bus to be idle */ - while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - i2c_start_on_bus(I2C0); - timeout = 0; - state = I2C_STATE_SEND_ADDRESS; - break; - - case I2C_STATE_SEND_ADDRESS: - /* wait for start condition to be sent. SBSEND flag */ - while((!i2c_flag_get(I2C0, I2C_FLAG_SBSEND)) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - /* send slave address */ - i2c_master_addressing(I2C0, slave_addr << 1, I2C_TRANSMITTER); - timeout = 0; - state = I2C_STATE_CLEAR_ADDRESS; - break; - - case I2C_STATE_CLEAR_ADDRESS: - /* wait for address to be acknowledged.ADDSEND set means i2c slave sends ACK */ - while ((!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) && (!i2c_flag_get(I2C0, I2C_FLAG_AERR)) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } else if (i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) { - i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); - timeout = 0; - state = I2C_STATE_TRANSMIT_REG; - break; - } else { - /* NACK received - address not acknowledged */ - i2c_flag_clear(I2C0, I2C_FLAG_AERR); - i2c_stop_on_bus(I2C0); - timeout = 0; -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C write failed for Error Slave Address. \n"); -#endif - return I2C_RESULT_NACK; - } - - case I2C_STATE_TRANSMIT_REG: - /* wait until the transmit data buffer is empty */ - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send register address, high byte first */ - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - i2c_data_transmit(I2C0, (uint8_t)(reg_addr >> 8)); - - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - i2c_data_transmit(I2C0, (uint8_t)(reg_addr & 0xFF)); - state = I2C_STATE_TRANSMIT_DATA; - break; - - case I2C_STATE_TRANSMIT_DATA: - /* wait until the transmit data buffer is empty */ - while (data_index < length) { - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send data byte */ - i2c_data_transmit(I2C0, data[data_index]); - data_index++; - - /* check for errors */ - if (i2c_flag_get(I2C0, I2C_FLAG_AERR)) { - i2c_stop_on_bus(I2C0); - return I2C_RESULT_NACK; - } else if (i2c_flag_get(I2C0, I2C_FLAG_BERR) || i2c_flag_get(I2C0, I2C_FLAG_LOSTARB)) { - i2c_stop_on_bus(I2C0); - return I2C_RESULT_ERROR; - } - } - if(state == I2C_STATE_ERROR) break; - - - /* check if all data has been sent */ - if (data_index >= length) { - /* wait until BTC bit is set for last byte */ - if(_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - state = I2C_STATE_STOP; - } - break; - - case I2C_STATE_STOP: - /* send a stop condition to I2C bus */ - i2c_stop_on_bus(I2C0); - - timeout = 0; - while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - /* success */ - return I2C_RESULT_SUCCESS; - - case I2C_STATE_ERROR: - /* I2C bus error, try to reset the bus and retry */ - i2c_bus_reset(); - - retry_count++; - if (retry_count >= I2C_MAX_RETRY) { -#if DEBUG_VERBOSE == CFG_ENABLE - printf("IIC write failed after %d retries\n", I2C_MAX_RETRY); -#endif - return I2C_RESULT_ERROR; - } - - /* reset state machine for retry */ - state = I2C_STATE_START; - timeout = 0; - - /* small delay before retry */ - delay_10us(10); - break; - - default: - state = I2C_STATE_START; - break; - } - } - return I2C_RESULT_TIMEOUT; -} - -/*! - \brief read data from I2C device with configurable length - \param[in] slave_addr: slave device address (7-bit) - \param[in] reg_addr: register address (16-bit, high byte first) - \param[out] data: pointer to data buffer - \param[in] length: number of bytes to read (1-255) - \retval i2c_result_t: operation result -*/ -i2c_result_t i2c_read(uint8_t slave_addr, uint16_t reg_addr, uint8_t *data, uint8_t length) { - i2c_state_t state = I2C_STATE_START; - uint16_t timeout = 0; - uint8_t retry_count = 0; - bool write_phase = true; - uint8_t data_index = 0; - - /* parameter validation */ - if (data == NULL || slave_addr > 0x7F || length == 0) { - return I2C_RESULT_INVALID_PARAM; - } - - /* enable acknowledge */ - i2c_ack_config(I2C0, I2C_ACK_ENABLE); - -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C read start: slave=0x%02X reg=0x%02X len=%u\r\n", slave_addr, reg_addr, length); -#endif - - while (retry_count < (uint8_t)I2C_MAX_RETRY) { - switch (state) { - case I2C_STATE_START: - timeout = 0; - data_index = 0; - - /* wait for bus to be idle */ - if(_i2c_wait_flag_clear_timeout(I2C_FLAG_I2CBSY) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send start condition */ - i2c_start_on_bus(I2C0); - state = I2C_STATE_SEND_ADDRESS; - break; - - case I2C_STATE_SEND_ADDRESS: - /* wait for start condition to be sent */ - if(_i2c_wait_flag_timeout(I2C_FLAG_SBSEND) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send slave address */ - if (write_phase) { - /* write phase: send address with write bit */ - i2c_master_addressing(I2C0, (slave_addr << 1), I2C_TRANSMITTER); - } else { - /* read phase: send address with read bit */ - i2c_master_addressing(I2C0, (slave_addr << 1) | 0x01, I2C_RECEIVER); - } - - state = I2C_STATE_CLEAR_ADDRESS; - timeout = 0; - break; - - case I2C_STATE_CLEAR_ADDRESS: - /* wait for address to be acknowledged */ - while ((!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - /* debug: show address ack and error flags */ -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C CLEAR_ADDRESS: write_phase=%d ADDSEND=%d AERR=%d\r\n", - (int)write_phase, - (int)i2c_flag_get(I2C0, I2C_FLAG_ADDSEND), - (int)i2c_flag_get(I2C0, I2C_FLAG_AERR)); -#endif - - if (write_phase) { - /* clear address flag (write phase) */ - i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); - state = I2C_STATE_TRANSMIT_DATA; - } else { - /* READ phase: handle ACK/ACKPOS based on requested length - - length == 1: disable ACK before clearing ADDR - - length == 2: set ACKPOS_NEXT and disable ACK before clearing ADDR - - length > 2: keep ACK enabled and clear ADDR; we'll handle N-2/BTF sequence later */ - if (length == 1) { - i2c_ack_config(I2C0, I2C_ACK_DISABLE); -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C READ phase: length=1, disabling ACK before clearing ADDSEND\r\n"); -#endif - } else if (length == 2) { - i2c_ackpos_config(I2C0, I2C_ACKPOS_NEXT); - i2c_ack_config(I2C0, I2C_ACK_DISABLE); -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C READ phase: length=2, set ACKPOS_NEXT and disabling ACK before clearing ADDSEND\r\n"); -#endif - } else { - /* length > 2: keep ACK enabled so slave will clock out data */ - i2c_ack_config(I2C0, I2C_ACK_ENABLE); -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C READ phase: length=%u (>2), keeping ACK enabled\r\n", length); -#endif - } - - /* now clear address flag to release SCL and enter data phase */ - i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); - - state = I2C_STATE_RECEIVE_DATA; - } - - timeout = 0; - break; - - case I2C_STATE_TRANSMIT_DATA: - /* wait for transmit buffer to be empty */ - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send register address, high byte first */ - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - i2c_data_transmit(I2C0, (uint8_t)(reg_addr >> 8)); - - if(_i2c_wait_flag_timeout(I2C_FLAG_TBE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - i2c_data_transmit(I2C0, (uint8_t)(reg_addr & 0xFF)); - state = I2C_STATE_RESTART; - break; - - case I2C_STATE_RESTART: - /* wait for byte transfer complete */ - if(_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C RESTART: after BTC, delay 5ms and issuing repeated START\r\n"); -#endif - /* small delay to allow slave device to prepare multi-byte data before repeated start */ - /* increased to 5ms to improve reliability for DLPC multi-byte reads */ - delay_ms(5); - - /* generate repeated start condition */ - i2c_start_on_bus(I2C0); - - /* wait for repeated start condition to be sent */ - if(_i2c_wait_flag_timeout(I2C_FLAG_SBSEND) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - - /* send slave address with read bit */ - i2c_master_addressing(I2C0, (slave_addr << 1), I2C_RECEIVER); - - /* switch to read phase */ - write_phase = false; - state = I2C_STATE_CLEAR_ADDRESS; - timeout = 0; -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C addressing sent for read (slave=0x%02X)\r\n", slave_addr); -#endif - break; - - case I2C_STATE_RECEIVE_DATA: -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C RECEIVE_DATA: expecting %u bytes\r\n", length); -#endif - if (length == 1) { - /* single byte read */ - if(_i2c_wait_flag_timeout(I2C_FLAG_RBNE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - data[0] = i2c_data_receive(I2C0); - state = I2C_STATE_STOP; - } else if (length == 2) { - /* two bytes read */ - if(_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - /* send STOP before reading the last two bytes */ - i2c_stop_on_bus(I2C0); - /* read the two bytes back-to-back */ - data[0] = i2c_data_receive(I2C0); - data[1] = i2c_data_receive(I2C0); - state = I2C_STATE_STOP; - } else { - /* multi-byte read (length > 2) */ - while (data_index < length) { - if (data_index < length - 2) { - /* normal bytes: wait for RBNE and read with ACK */ - if(_i2c_wait_flag_timeout(I2C_FLAG_RBNE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - data[data_index] = i2c_data_receive(I2C0); - data_index++; - } else if (data_index == length - 2) { - /* second to last byte: wait for BTF, then disable ACK and read */ - if(_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - /* disable ACK before reading N-1 byte */ - i2c_ack_config(I2C0, I2C_ACK_DISABLE); - /* send STOP before reading N-1 byte */ - i2c_stop_on_bus(I2C0); - /* read N-1 byte */ - data[data_index] = i2c_data_receive(I2C0); - data_index++; - } else { - /* last byte: wait for RBNE and read */ - if(_i2c_wait_flag_timeout(I2C_FLAG_RBNE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - /* read last byte */ - data[data_index] = i2c_data_receive(I2C0); - data_index++; - } - } - if(state == I2C_STATE_ERROR) break; - state = I2C_STATE_STOP; - } - break; - - case I2C_STATE_STOP: - /* wait for stop condition to complete */ - while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - /* reset ACK configuration for next operation */ - i2c_ack_config(I2C0, I2C_ACK_ENABLE); - i2c_ackpos_config(I2C0, I2C_ACKPOS_CURRENT); - - /* success */ - return I2C_RESULT_SUCCESS; - - case I2C_STATE_ERROR: - /* I2C bus error, try to reset the bus and retry */ -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C_STATE_ERROR: resetting bus and retrying (retry_count=%u)\r\n", retry_count); -#endif - i2c_bus_reset(); - - /* reset ACK configuration */ - i2c_ack_config(I2C0, I2C_ACK_ENABLE); - i2c_ackpos_config(I2C0, I2C_ACKPOS_CURRENT); - - retry_count++; - if (retry_count >= I2C_MAX_RETRY) { -#if DEBUG_VERBOSE == CFG_ENABLE - /* Print diagnostic flags to help root-cause analysis */ - printf("I2C read final failure after %d retries, last_state=%d\r\n", I2C_MAX_RETRY, state); - printf(" FLAGS: AERR=%d BERR=%d LOSTARB=%d I2CBSY=%d\r\n", - (int)i2c_flag_get(I2C0, I2C_FLAG_AERR), - (int)i2c_flag_get(I2C0, I2C_FLAG_BERR), - (int)i2c_flag_get(I2C0, I2C_FLAG_LOSTARB), - (int)i2c_flag_get(I2C0, I2C_FLAG_I2CBSY)); -#endif -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C read failed after %d retries\n", I2C_MAX_RETRY); -#endif - return I2C_RESULT_ERROR; - } - - /* reset state machine for retry */ - state = I2C_STATE_START; - write_phase = true; - timeout = 0; - - /* small delay before retry */ - delay_10us(10); - break; - - default: - state = I2C_STATE_START; - break; - } - } - /* timeout path: provide debug info */ -#if DEBUG_VERBOSE == CFG_ENABLE - printf("I2C read timeout (end state). slave=0x%02X, len=%u\r\n", slave_addr, length); - printf(" FLAGS: AERR=%d BERR=%d LOSTARB=%d I2CBSY=%d\r\n", - (int)i2c_flag_get(I2C0, I2C_FLAG_AERR), - (int)i2c_flag_get(I2C0, I2C_FLAG_BERR), - (int)i2c_flag_get(I2C0, I2C_FLAG_LOSTARB), - (int)i2c_flag_get(I2C0, I2C_FLAG_I2CBSY)); -#endif - return I2C_RESULT_TIMEOUT; -} - -/*! - \brief read data from I2C device without register/sub-address phase - \param[in] slave_addr: slave device address (7-bit) - \param[out] data: pointer to data buffer - \param[in] length: number of bytes to read (1-255) - \retval i2c_result_t: operation result -*/ -i2c_result_t i2c_read_raw(uint8_t slave_addr, uint8_t *data, uint8_t length) +/******************************************************************************/ +/* Common master transaction layer. + * + * Memory transactions are assembled from an optional 0/1/2-byte sub-address + * segment and a data segment: + * write: S | address+W | sub-address... | data... | P + * read: S | address+W | sub-address... | Sr | address+R | data | P + * + * All public register-width APIs use these primitives so ACK, NACK, STOP, + * timeout, and retry behavior stays consistent. + */ + +#define I2C_MAX_SUBADDRESS_LENGTH 2U + +typedef struct { + const uint8_t *buffer; + uint8_t length; + uint8_t index; +} i2c_tx_segment_t; + +static bool i2c_tx_segment_next(i2c_tx_segment_t *segment, uint8_t *byte) { - i2c_state_t state = I2C_STATE_START; - uint16_t timeout = 0; - uint8_t retry_count = 0; - uint8_t data_index = 0; - - if (data == NULL || slave_addr > 0x7F || length == 0) { - return I2C_RESULT_INVALID_PARAM; + if (segment->index >= segment->length) { + return false; } + *byte = segment->buffer[segment->index]; + segment->index++; + return true; +} + +/* Wait for STOP generation to complete (CTL0.STOP is cleared by hardware). */ +static i2c_result_t i2c_wait_stop_done(void) +{ + uint16_t timeout = 0U; + + while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT)) { + timeout++; + } + + return (I2C_CTL0(I2C0) & I2C_CTL0_STOP) ? + I2C_RESULT_TIMEOUT : I2C_RESULT_SUCCESS; +} + +/* Clear a possible late NACK and terminate the active transaction. */ +static i2c_result_t i2c_abort_with_stop(void) +{ + if (i2c_flag_get(I2C0, I2C_FLAG_AERR)) { + i2c_flag_clear(I2C0, I2C_FLAG_AERR); + } + + i2c_stop_on_bus(I2C0); + return i2c_wait_stop_done(); +} + +/* Preserve the transaction error unless STOP itself fails. */ +static i2c_result_t i2c_abort_result(i2c_result_t result) +{ + i2c_result_t stop_result = i2c_abort_with_stop(); + + return (stop_result == I2C_RESULT_SUCCESS) ? result : stop_result; +} + +/* Wait for a transmit-side event while also observing the NACK flag. */ +static i2c_result_t i2c_wait_flag_or_nack(uint32_t flag) +{ + uint16_t timeout = 0U; + + while ((!i2c_flag_get(I2C0, flag)) && + (!i2c_flag_get(I2C0, I2C_FLAG_AERR)) && + (timeout < I2C_TIME_OUT)) { + timeout++; + } + + if (i2c_flag_get(I2C0, I2C_FLAG_AERR)) { + i2c_flag_clear(I2C0, I2C_FLAG_AERR); + return I2C_RESULT_NACK; + } + + return i2c_flag_get(I2C0, flag) ? + I2C_RESULT_SUCCESS : I2C_RESULT_TIMEOUT; +} + +/*! + \brief perform one master transmit transaction + \param[in] slave_addr: 7-bit slave address + \param[in] first: first data segment, normally the sub-address + \param[in] second: second data segment, normally the payload + \param[in] send_stop: true to finish with STOP; false to keep the bus for + an immediately following repeated START + \retval i2c_result_t: operation result +*/ +static i2c_result_t i2c_master_tx_once(uint8_t slave_addr, + i2c_tx_segment_t first, + i2c_tx_segment_t second, + bool send_stop) +{ + uint8_t byte; + i2c_result_t result; + + if (_i2c_wait_flag_clear_timeout(I2C_FLAG_I2CBSY) != I2C_RESULT_SUCCESS) { + return I2C_RESULT_TIMEOUT; + } + + i2c_start_on_bus(I2C0); + if (_i2c_wait_flag_timeout(I2C_FLAG_SBSEND) != I2C_RESULT_SUCCESS) { + return i2c_abort_result(I2C_RESULT_TIMEOUT); + } + + i2c_master_addressing(I2C0, (slave_addr << 1), I2C_TRANSMITTER); + result = i2c_wait_flag_or_nack(I2C_FLAG_ADDSEND); + if (result != I2C_RESULT_SUCCESS) { + return i2c_abort_result(result); + } + i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); + + for (;;) { + if (!i2c_tx_segment_next(&first, &byte) && + !i2c_tx_segment_next(&second, &byte)) { + break; + } + + result = i2c_wait_flag_or_nack(I2C_FLAG_TBE); + if (result != I2C_RESULT_SUCCESS) { + return i2c_abort_result(result); + } + i2c_data_transmit(I2C0, byte); + } + + /* BTC is required after the final byte; checking AERR here catches its NACK. */ + result = i2c_wait_flag_or_nack(I2C_FLAG_BTC); + if (result != I2C_RESULT_SUCCESS) { + return i2c_abort_result(result); + } + + if (!send_stop) { + return I2C_RESULT_SUCCESS; + } + + i2c_stop_on_bus(I2C0); + return i2c_wait_stop_done(); +} + +/*! + \brief perform one master receive transaction + \param[in] slave_addr: 7-bit slave address + \param[out] data: receive buffer + \param[in] length: number of bytes to receive + \param[in] after_write_phase: true when the caller retained the bus after + writing a sub-address, so this START is a repeated START + \retval i2c_result_t: operation result + \note The N=1, N=2, and N>2 sequences follow the GD32E23x master + receiver procedure. ACKEN and ACKPOS are restored on every exit. +*/ +static i2c_result_t i2c_master_rx_once(uint8_t slave_addr, + uint8_t *data, + uint8_t length, + bool after_write_phase) +{ + uint8_t index = 0U; + bool transaction_active = after_write_phase; + bool stop_sent = false; + i2c_result_t result = I2C_RESULT_SUCCESS; + i2c_result_t stop_result; + + /* Start each receive with a known ACK configuration. */ + i2c_ackpos_config(I2C0, I2C_ACKPOS_CURRENT); i2c_ack_config(I2C0, I2C_ACK_ENABLE); - while (retry_count < (uint8_t)I2C_MAX_RETRY) { - switch (state) { - case I2C_STATE_START: - timeout = 0; - data_index = 0; + if (!after_write_phase && + (_i2c_wait_flag_clear_timeout(I2C_FLAG_I2CBSY) != I2C_RESULT_SUCCESS)) { + result = I2C_RESULT_TIMEOUT; + goto rx_restore; + } - if (_i2c_wait_flag_clear_timeout(I2C_FLAG_I2CBSY) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; + /* The two-byte sequence requires ACKPOS_NEXT before START. */ + if (length == 2U) { + i2c_ackpos_config(I2C0, I2C_ACKPOS_NEXT); + } + + i2c_start_on_bus(I2C0); + transaction_active = true; + + if (_i2c_wait_flag_timeout(I2C_FLAG_SBSEND) != I2C_RESULT_SUCCESS) { + result = I2C_RESULT_TIMEOUT; + goto rx_finish; + } + + i2c_master_addressing(I2C0, (slave_addr << 1), I2C_RECEIVER); + result = i2c_wait_flag_or_nack(I2C_FLAG_ADDSEND); + if (result != I2C_RESULT_SUCCESS) { + goto rx_finish; + } + + if (length <= 2U) { + i2c_ack_config(I2C0, I2C_ACK_DISABLE); + } + i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); + + if (length == 1U) { + i2c_stop_on_bus(I2C0); + stop_sent = true; + + if (_i2c_wait_flag_timeout(I2C_FLAG_RBNE) != I2C_RESULT_SUCCESS) { + result = I2C_RESULT_TIMEOUT; + goto rx_finish; + } + data[0] = i2c_data_receive(I2C0); + } else if (length == 2U) { + if (_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { + result = I2C_RESULT_TIMEOUT; + goto rx_finish; + } + + i2c_stop_on_bus(I2C0); + stop_sent = true; + data[0] = i2c_data_receive(I2C0); + data[1] = i2c_data_receive(I2C0); + } else { + while (index < length) { + if (index == (uint8_t)(length - 2U)) { + if (_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { + result = I2C_RESULT_TIMEOUT; + goto rx_finish; } - i2c_start_on_bus(I2C0); - state = I2C_STATE_SEND_ADDRESS; - break; - - case I2C_STATE_SEND_ADDRESS: - if (_i2c_wait_flag_timeout(I2C_FLAG_SBSEND) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; + i2c_ack_config(I2C0, I2C_ACK_DISABLE); + i2c_stop_on_bus(I2C0); + stop_sent = true; + data[index] = i2c_data_receive(I2C0); + index++; + } else { + if (_i2c_wait_flag_timeout(I2C_FLAG_RBNE) != I2C_RESULT_SUCCESS) { + result = I2C_RESULT_TIMEOUT; + goto rx_finish; } - i2c_master_addressing(I2C0, (slave_addr << 1), I2C_RECEIVER); - state = I2C_STATE_CLEAR_ADDRESS; - timeout = 0; - break; - - case I2C_STATE_CLEAR_ADDRESS: - while ((!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND)) && (!i2c_flag_get(I2C0, I2C_FLAG_AERR)) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - if (i2c_flag_get(I2C0, I2C_FLAG_AERR)) { - i2c_flag_clear(I2C0, I2C_FLAG_AERR); - i2c_stop_on_bus(I2C0); - return I2C_RESULT_NACK; - } - - if (length == 1) { - i2c_ack_config(I2C0, I2C_ACK_DISABLE); - } else if (length == 2) { - i2c_ackpos_config(I2C0, I2C_ACKPOS_NEXT); - i2c_ack_config(I2C0, I2C_ACK_DISABLE); - } else { - i2c_ack_config(I2C0, I2C_ACK_ENABLE); - } - - i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); - state = I2C_STATE_RECEIVE_DATA; - timeout = 0; - break; - - case I2C_STATE_RECEIVE_DATA: - if (length == 1) { - if (_i2c_wait_flag_timeout(I2C_FLAG_RBNE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - data[0] = i2c_data_receive(I2C0); - state = I2C_STATE_STOP; - } else if (length == 2) { - if (_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - i2c_stop_on_bus(I2C0); - data[0] = i2c_data_receive(I2C0); - data[1] = i2c_data_receive(I2C0); - state = I2C_STATE_STOP; - } else { - while (data_index < length) { - if (data_index < length - 2) { - if (_i2c_wait_flag_timeout(I2C_FLAG_RBNE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - data[data_index++] = i2c_data_receive(I2C0); - } else if (data_index == length - 2) { - if (_i2c_wait_flag_timeout(I2C_FLAG_BTC) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - i2c_ack_config(I2C0, I2C_ACK_DISABLE); - i2c_stop_on_bus(I2C0); - data[data_index++] = i2c_data_receive(I2C0); - } else { - if (_i2c_wait_flag_timeout(I2C_FLAG_RBNE) != I2C_RESULT_SUCCESS) { - state = I2C_STATE_ERROR; - break; - } - data[data_index++] = i2c_data_receive(I2C0); - } - } - if (state == I2C_STATE_ERROR) break; - state = I2C_STATE_STOP; - } - break; - - case I2C_STATE_STOP: - while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT)) { - timeout++; - } - if (timeout >= I2C_TIME_OUT) { - state = I2C_STATE_ERROR; - break; - } - - i2c_ack_config(I2C0, I2C_ACK_ENABLE); - i2c_ackpos_config(I2C0, I2C_ACKPOS_CURRENT); - return I2C_RESULT_SUCCESS; - - case I2C_STATE_ERROR: - i2c_bus_reset(); - i2c_ack_config(I2C0, I2C_ACK_ENABLE); - i2c_ackpos_config(I2C0, I2C_ACKPOS_CURRENT); - - retry_count++; - if (retry_count >= I2C_MAX_RETRY) { - return I2C_RESULT_ERROR; - } - - state = I2C_STATE_START; - timeout = 0; - delay_10us(10); - break; - - default: - state = I2C_STATE_START; - break; + data[index] = i2c_data_receive(I2C0); + index++; + } } } - return I2C_RESULT_TIMEOUT; +rx_finish: + if (transaction_active) { + if (!stop_sent) { + stop_result = i2c_abort_with_stop(); + } else { + stop_result = i2c_wait_stop_done(); + } + + if (stop_result != I2C_RESULT_SUCCESS) { + result = stop_result; + } + } + +rx_restore: + i2c_ack_config(I2C0, I2C_ACK_ENABLE); + i2c_ackpos_config(I2C0, I2C_ACKPOS_CURRENT); + return result; +} + +static bool i2c_subaddress_is_valid(const uint8_t *subaddress, + uint8_t subaddress_length) +{ + return (subaddress_length <= I2C_MAX_SUBADDRESS_LENGTH) && + ((subaddress_length == 0U) || (subaddress != NULL)); } -#if DEBUG_VERBOSE == CFG_ENABLE /*! - \brief get status string for debugging - \param[in] status: i2c_status_t value - \param[out] none - \retval const char* status string + \brief write with an optional 0/1/2-byte sub-address + \param[in] subaddress: sub-address bytes in wire order, or NULL for none + \param[in] subaddress_length: number of sub-address bytes */ -const char* i2c_get_status_string(i2c_result_t status) { +static i2c_result_t i2c_mem_write(uint8_t slave_addr, + const uint8_t *subaddress, + uint8_t subaddress_length, + const uint8_t *data, + uint8_t length) +{ + i2c_tx_segment_t address_segment; + i2c_tx_segment_t data_segment; + uint8_t attempt; + + if ((slave_addr > 0x7FU) || + !i2c_subaddress_is_valid(subaddress, subaddress_length) || + (data == NULL) || + (length == 0U)) { + return I2C_RESULT_INVALID_PARAM; + } + + address_segment.buffer = subaddress; + address_segment.length = subaddress_length; + data_segment.buffer = data; + data_segment.length = length; + + for (attempt = 0U; attempt < I2C_MAX_RETRY; attempt++) { + i2c_result_t result; + + address_segment.index = 0U; + data_segment.index = 0U; + result = i2c_master_tx_once(slave_addr, + address_segment, + data_segment, + true); + if ((result == I2C_RESULT_SUCCESS) || + (result == I2C_RESULT_NACK)) { + return result; + } + + (void)i2c_bus_reset(); + delay_10us(10U); + } + + return I2C_RESULT_ERROR; +} + +/*! + \brief read with an optional 0/1/2-byte sub-address + \param[in] subaddress: sub-address bytes in wire order, or NULL for none + \param[in] subaddress_length: number of sub-address bytes + \param[in] restart_delay_ms: optional delay before the repeated START +*/ +static i2c_result_t i2c_mem_read(uint8_t slave_addr, + const uint8_t *subaddress, + uint8_t subaddress_length, + uint8_t *data, + uint8_t length, + uint16_t restart_delay_ms) +{ + i2c_tx_segment_t address_segment; + i2c_tx_segment_t empty_segment = {NULL, 0U, 0U}; + uint8_t attempt; + + if ((slave_addr > 0x7FU) || + !i2c_subaddress_is_valid(subaddress, subaddress_length) || + (data == NULL) || + (length == 0U)) { + return I2C_RESULT_INVALID_PARAM; + } + + address_segment.buffer = subaddress; + address_segment.length = subaddress_length; + + for (attempt = 0U; attempt < I2C_MAX_RETRY; attempt++) { + i2c_result_t result; + + if (subaddress_length != 0U) { + address_segment.index = 0U; + result = i2c_master_tx_once(slave_addr, + address_segment, + empty_segment, + false); + if (result == I2C_RESULT_NACK) { + return result; + } + if (result != I2C_RESULT_SUCCESS) { + (void)i2c_bus_reset(); + delay_10us(10U); + continue; + } + + if (restart_delay_ms != 0U) { + delay_ms(restart_delay_ms); + } + } + + result = i2c_master_rx_once(slave_addr, + data, + length, + subaddress_length != 0U); + if ((result == I2C_RESULT_SUCCESS) || + (result == I2C_RESULT_NACK)) { + return result; + } + + (void)i2c_bus_reset(); + delay_10us(10U); + } + + return I2C_RESULT_ERROR; +} + +i2c_result_t i2c_write_reg8(uint8_t slave_addr, + uint8_t reg_addr, + const uint8_t *data, + uint8_t length) +{ + return i2c_mem_write(slave_addr, ®_addr, 1U, data, length); +} + +i2c_result_t i2c_read_reg8(uint8_t slave_addr, + uint8_t reg_addr, + uint8_t *data, + uint8_t length) +{ + return i2c_mem_read(slave_addr, + ®_addr, + 1U, + data, + length, + I2C_READ_RESTART_DELAY_MS); +} + +i2c_result_t i2c_write_reg16(uint8_t slave_addr, + uint16_t reg_addr, + const uint8_t *data, + uint8_t length) +{ + const uint8_t subaddress[2] = { + (uint8_t)(reg_addr >> 8), + (uint8_t)reg_addr + }; + + return i2c_mem_write(slave_addr, subaddress, 2U, data, length); +} + +i2c_result_t i2c_read_reg16(uint8_t slave_addr, + uint16_t reg_addr, + uint8_t *data, + uint8_t length) +{ + const uint8_t subaddress[2] = { + (uint8_t)(reg_addr >> 8), + (uint8_t)reg_addr + }; + + return i2c_mem_read(slave_addr, + subaddress, + 2U, + data, + length, + I2C_READ_RESTART_DELAY_MS); +} + +/* Preserve the original template API: uint16_t register addresses are MSB first. */ +i2c_result_t i2c_write(uint8_t slave_addr, + uint16_t reg_addr, + uint8_t *data, + uint8_t length) +{ + return i2c_write_reg16(slave_addr, reg_addr, data, length); +} + +i2c_result_t i2c_read(uint8_t slave_addr, + uint16_t reg_addr, + uint8_t *data, + uint8_t length) +{ + return i2c_read_reg16(slave_addr, reg_addr, data, length); +} + +/* Legacy names refer to a two-byte payload with an 8-bit register address. */ +i2c_result_t i2c_write_16bits(uint8_t slave_addr, + uint8_t reg_addr, + uint8_t data[2]) +{ + return i2c_write_reg8(slave_addr, reg_addr, data, 2U); +} + +i2c_result_t i2c_read_16bits(uint8_t slave_addr, + uint8_t reg_addr, + uint8_t *data) +{ + return i2c_read_reg8(slave_addr, reg_addr, data, 2U); +} + +i2c_result_t i2c_read_raw(uint8_t slave_addr, + uint8_t *data, + uint8_t length) +{ + return i2c_mem_read(slave_addr, NULL, 0U, data, length, 0U); +} + +bool i2c_probe_ack(uint8_t slave_addr) +{ + bool acknowledged = false; + + if (slave_addr > 0x7FU) { + return false; + } + if (_i2c_wait_flag_clear_timeout(I2C_FLAG_I2CBSY) != I2C_RESULT_SUCCESS) { + return false; + } + + i2c_start_on_bus(I2C0); + if (_i2c_wait_flag_timeout(I2C_FLAG_SBSEND) == I2C_RESULT_SUCCESS) { + i2c_result_t result; + + i2c_master_addressing(I2C0, + (slave_addr << 1), + I2C_TRANSMITTER); + result = i2c_wait_flag_or_nack(I2C_FLAG_ADDSEND); + if (result == I2C_RESULT_SUCCESS) { + i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND); + acknowledged = true; + } + } + + if (i2c_abort_with_stop() != I2C_RESULT_SUCCESS) { + (void)i2c_bus_reset(); + acknowledged = false; + } + return acknowledged; +} + +const char* i2c_get_status_string(i2c_result_t status) +{ switch (status) { case I2C_RESULT_SUCCESS: return "SUCCESS"; @@ -1310,8 +785,13 @@ const char* i2c_get_status_string(i2c_result_t status) { return "ERROR"; case I2C_RESULT_INVALID_PARAM: return "INVALID_PARAM"; + case I2C_RECOVERY_OK: + return "RECOVERY_OK"; + case I2C_RECOVERY_SDA_STUCK_LOW: + return "RECOVERY_SDA_STUCK_LOW"; + case I2C_RECOVERY_SCL_STUCK_LOW: + return "RECOVERY_SCL_STUCK_LOW"; default: return "UNKNOWN"; } } -#endif