Files
gd32e230_uart_ring_buffer/Src/i2c.c
2025-08-14 00:07:16 +08:00

640 lines
23 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Created by dell on 24-12-20.
// Improved I2C driver with better state machine and error handling
//
#include "i2c.h"
/* Private variables */
static uint8_t i2c_retry_count = 0;
/*!
\brief configure the GPIO ports
\param[in] none
\param[out] none
\retval none
*/
void i2c_gpio_config(void) {
/* enable IIC GPIO clock */
rcu_periph_clock_enable(RCU_GPIO_I2C);
/* connect I2C_SCL_PIN to I2C_SCL */
gpio_af_set(I2C_SCL_PORT, I2C_GPIO_AF, I2C_SCL_PIN);
/* connect I2C_SDA_PIN to I2C_SDA */
gpio_af_set(I2C_SDA_PORT, I2C_GPIO_AF, I2C_SDA_PIN);
/* configure GPIO pins of I2C */
gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SCL_PIN);
gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SCL_PIN);
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
\param[out] none
\retval i2c_status_t
*/
i2c_status_t i2c_config(void) {
/* configure I2C GPIO */
i2c_gpio_config();
/* enable I2C clock */
rcu_periph_clock_enable(RCU_I2C);
/* configure I2C clock */
i2c_clock_config(I2C0, I2C_SPEED, I2C_DTCY_2);
/* configure I2C address - use 0x00 as master doesn't need specific address */
i2c_mode_addr_config(I2C0, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2C_MASTER_ADDRESS);
/* enable I2CX */
i2c_enable(I2C0);
/* enable acknowledge */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
/* reset retry counter */
i2c_retry_count = 0;
return I2C_STATUS_SUCCESS;
}
/*!
\brief reset I2C bus with proper 9-clock recovery
\param[in] none
\param[out] none
\retval i2c_status_t
*/
i2c_status_t i2c_bus_reset(void) {
uint8_t i;
/* disable I2C peripheral */
i2c_disable(I2C0);
i2c_deinit(I2C0);
/* configure SDA/SCL as GPIO output for manual control */
gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, I2C_SCL_PIN);
gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, I2C_SDA_PIN);
gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SCL_PIN);
gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, I2C_SDA_PIN);
/* ensure both lines are high initially */
gpio_bit_set(I2C_SCL_PORT, I2C_SCL_PIN);
gpio_bit_set(I2C_SDA_PORT, I2C_SDA_PIN);
delay_10us(I2C_DELAY_10US);
/* generate 9 clock pulses to release any stuck slave */
for (i = 0; i < I2C_RECOVERY_CLOCKS; i++) {
gpio_bit_reset(I2C_SCL_PORT, I2C_SCL_PIN);
delay_10us(I2C_DELAY_10US);
gpio_bit_set(I2C_SCL_PORT, I2C_SCL_PIN);
delay_10us(I2C_DELAY_10US);
}
/* generate stop condition */
gpio_bit_reset(I2C_SDA_PORT, I2C_SDA_PIN);
delay_10us(I2C_DELAY_10US);
gpio_bit_set(I2C_SCL_PORT, I2C_SCL_PIN);
delay_10us(I2C_DELAY_10US);
gpio_bit_set(I2C_SDA_PORT, I2C_SDA_PIN);
delay_10us(I2C_DELAY_10US);
/* reconfigure as I2C pins */
gpio_af_set(I2C_SCL_PORT, I2C_GPIO_AF, I2C_SCL_PIN);
gpio_af_set(I2C_SDA_PORT, I2C_GPIO_AF, I2C_SDA_PIN);
gpio_mode_set(I2C_SCL_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SCL_PIN);
gpio_mode_set(I2C_SDA_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, I2C_SDA_PIN);
/* reconfigure the I2CX interface */
return i2c_config();
}
/**
* @brief 扫描I2C总线查找连接的设备
*
* 该函数会扫描I2C总线上的所有地址1到126并尝试与每个地址进行通信。
* 如果在某个地址上发现了设备,则会打印出该设备的地址。
* 最后会打印出找到的设备总数。
*/
void i2c_scan(void) {
uint32_t timeout;
uint8_t address;
int found_devices = 0;
// printf("Scanning I2C bus...\r\n");
const char* msg1 = "Scanning I2C bus...\r\n";
for (uint8_t i = 0; msg1[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg1[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, 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) && (timeout < I2C_TIME_OUT))
timeout++;
if (timeout < I2C_TIME_OUT) {
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
// 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++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg2_prefix[i]);
}
// 发送地址的十六进制表示
uint8_t hex_chars[] = "0123456789ABCDEF";
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, hex_chars[(address >> 4) & 0x0F]);
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, hex_chars[address & 0x0F]);
const char* msg2_suffix = "\r\n";
for (uint8_t i = 0; msg2_suffix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg2_suffix[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
found_devices++;
}
// 生成停止条件
i2c_stop_on_bus(I2C0);
timeout = 0;
while ((I2C_CTL0(I2C0) & I2C_CTL0_STOP) && (timeout < I2C_TIME_OUT))
timeout++;
}
if (found_devices == 0) {
// printf("No I2C devices found.\r\n");
const char* msg3 = "No I2C devices found.\r\n";
for (uint8_t i = 0; msg3[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg3[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
} else {
// printf("Total %d I2C devices found.\r\n", found_devices);
const char* msg4_prefix = "Total ";
for (uint8_t i = 0; msg4_prefix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg4_prefix[i]);
}
// 发送设备数量
if (found_devices >= 10) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, '0' + (found_devices / 10));
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, '0' + (found_devices % 10));
const char* msg4_suffix = " I2C devices found.\r\n";
for (uint8_t i = 0; msg4_suffix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg4_suffix[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
}
}
/*!
\brief write 16-bit data to I2C device with improved state machine
\param[in] slave_addr: 7-bit slave address
\param[in] reg_addr: register address
\param[in] data: pointer to 2-byte data array
\param[out] none
\retval i2c_status_t
*/
i2c_status_t i2c_write_16bits(uint8_t slave_addr, uint8_t reg_addr, const uint8_t data[2]) {
i2c_state_t state = I2C_STATE_START;
uint16_t timeout = 0;
uint8_t data_index = 0;
uint8_t retry_count = 0;
/* Parameter validation */
if (data == NULL || slave_addr > 0x7F) {
return I2C_STATUS_INVALID_PARAM;
}
/* Enable acknowledge */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
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;
}
/* Send start condition */
i2c_start_on_bus(I2C0);
state = I2C_STATE_SEND_ADDRESS;
timeout = 0;
break;
case I2C_STATE_SEND_ADDRESS:
/* Wait for start condition to be sent */
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 with write bit */
i2c_master_addressing(I2C0, (slave_addr << 1), I2C_TRANSMITTER);
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;
}
/* Clear address flag */
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
state = I2C_STATE_TRANSMIT_REG;
timeout = 0;
break;
case I2C_STATE_TRANSMIT_REG:
/* Wait for transmit buffer to be empty */
while ((!i2c_flag_get(I2C0, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* Send register address */
i2c_data_transmit(I2C0, reg_addr);
state = I2C_STATE_TRANSMIT_DATA;
timeout = 0;
data_index = 0;
break;
case I2C_STATE_TRANSMIT_DATA:
/* Wait for byte transfer complete */
while ((!i2c_flag_get(I2C0, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* Send data bytes */
if (data_index < 2) {
i2c_data_transmit(I2C0, data[data_index]);
data_index++;
timeout = 0;
/* Stay in this state until all data is sent */
} else {
/* All data sent, proceed to stop */
state = I2C_STATE_STOP;
timeout = 0;
}
break;
case I2C_STATE_STOP:
/* Send stop condition */
i2c_stop_on_bus(I2C0);
/* 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;
}
/* Success */
return I2C_STATUS_SUCCESS;
case I2C_STATE_ERROR:
/* Send stop condition to release bus */
i2c_stop_on_bus(I2C0);
/* Increment retry counter */
retry_count++;
if (retry_count >= I2C_MAX_RETRY) {
#ifdef DEBUG_VERBOSE
// printf("I2C write failed after %d retries\r\n", I2C_MAX_RETRY);
const char* msg5_prefix = "I2C write failed after ";
for (uint8_t i = 0; msg5_prefix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg5_prefix[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, '0' + I2C_MAX_RETRY);
const char* msg5_suffix = " retries\r\n";
for (uint8_t i = 0; msg5_suffix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg5_suffix[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
#endif
return I2C_STATUS_TIMEOUT;
}
/* Reset state machine for retry */
state = I2C_STATE_START;
timeout = 0;
data_index = 0;
/* Small delay before retry */
delay_10us(10);
break;
default:
state = I2C_STATE_ERROR;
break;
}
}
return I2C_STATUS_TIMEOUT;
}
/*!
\brief read 16-bit data from I2C device with improved state machine
\param[in] slave_addr: 7-bit slave address
\param[in] reg_addr: register address
\param[out] data: pointer to 2-byte data buffer
\retval i2c_status_t
*/
i2c_status_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 data_index = 0;
uint8_t retry_count = 0;
bool write_phase = true; /* First phase: write register address */
/* Parameter validation */
if (data == NULL || slave_addr > 0x7F) {
return I2C_STATUS_INVALID_PARAM;
}
/* Enable acknowledge */
i2c_ack_config(I2C0, I2C_ACK_ENABLE);
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;
}
/* Configure ACK position for 2-byte read */
if (!write_phase) {
i2c_ackpos_config(I2C0, I2C_ACKPOS_NEXT);
}
/* Send start condition */
i2c_start_on_bus(I2C0);
state = I2C_STATE_SEND_ADDRESS;
timeout = 0;
break;
case I2C_STATE_SEND_ADDRESS:
/* Wait for start condition to be sent */
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 */
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);
/* Disable ACK for last byte */
i2c_ack_config(I2C0, I2C_ACK_DISABLE);
}
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;
}
/* Clear address flag */
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
if (write_phase) {
state = I2C_STATE_TRANSMIT_REG;
} else {
/* For single byte read, send stop after clearing address */
if (data_index == 1) {
i2c_stop_on_bus(I2C0);
}
state = I2C_STATE_RECEIVE_DATA;
data_index = 0;
}
timeout = 0;
break;
case I2C_STATE_TRANSMIT_REG:
/* Wait for transmit buffer to be empty */
while ((!i2c_flag_get(I2C0, I2C_FLAG_TBE)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* Send register address */
i2c_data_transmit(I2C0, reg_addr);
state = I2C_STATE_RESTART;
timeout = 0;
break;
case I2C_STATE_RESTART:
/* Wait for byte transfer complete */
while ((!i2c_flag_get(I2C0, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* Switch to read phase */
write_phase = false;
state = I2C_STATE_START;
timeout = 0;
break;
case I2C_STATE_RECEIVE_DATA:
if (data_index < 2) {
if (data_index == 1) {
/* Wait for BTC before sending stop for last byte */
while ((!i2c_flag_get(I2C0, I2C_FLAG_BTC)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* Send stop condition before reading last byte */
i2c_stop_on_bus(I2C0);
}
/* Wait for receive buffer not empty */
while ((!i2c_flag_get(I2C0, I2C_FLAG_RBNE)) && (timeout < I2C_TIME_OUT)) {
timeout++;
}
if (timeout >= I2C_TIME_OUT) {
state = I2C_STATE_ERROR;
break;
}
/* Read data byte */
data[data_index] = i2c_data_receive(I2C0);
data_index++;
timeout = 0;
if (data_index >= 2) {
state = I2C_STATE_STOP;
}
} else {
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;
}
/* Success */
return I2C_STATUS_SUCCESS;
case I2C_STATE_ERROR:
/* Send stop condition to release bus */
i2c_stop_on_bus(I2C0);
/* Increment retry counter */
retry_count++;
if (retry_count >= I2C_MAX_RETRY) {
#ifdef DEBUG_VERBOSE
// printf("I2C read failed after %d retries\r\n", I2C_MAX_RETRY);
const char* msg6_prefix = "I2C read failed after ";
for (uint8_t i = 0; msg6_prefix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg6_prefix[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, '0' + I2C_MAX_RETRY);
const char* msg6_suffix = " retries\r\n";
for (uint8_t i = 0; msg6_suffix[i] != '\0'; i++) {
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TBE) == RESET) {}
usart_data_transmit(I2C_DEBUG_UART, msg6_suffix[i]);
}
while (usart_flag_get(I2C_DEBUG_UART, USART_FLAG_TC) == RESET) {}
#endif
return I2C_STATUS_TIMEOUT;
}
/* Reset state machine for retry */
state = I2C_STATE_START;
write_phase = true;
timeout = 0;
data_index = 0;
/* Small delay before retry */
delay_10us(10);
break;
default:
state = I2C_STATE_ERROR;
break;
}
}
return I2C_STATUS_TIMEOUT;
}
/*!
\brief get status string for debugging
\param[in] status: i2c_status_t value
\param[out] none
\retval const char* status string
*/
const char* i2c_get_status_string(i2c_status_t status) {
switch (status) {
case I2C_STATUS_SUCCESS:
return "SUCCESS";
case I2C_STATUS_TIMEOUT:
return "TIMEOUT";
case I2C_STATUS_NACK:
return "NACK";
case I2C_STATUS_BUS_BUSY:
return "BUS_BUSY";
case I2C_STATUS_ERROR:
return "ERROR";
case I2C_STATUS_INVALID_PARAM:
return "INVALID_PARAM";
default:
return "UNKNOWN";
}
}