diff --git a/Inc/i2c.h b/Inc/i2c.h index 29cef7f..5578ac3 100644 --- a/Inc/i2c.h +++ b/Inc/i2c.h @@ -39,7 +39,10 @@ typedef enum { I2C_RESULT_NACK, /* No acknowledge received */ I2C_RESULT_BUS_BUSY, /* Bus is busy */ I2C_RESULT_ERROR, /* General error */ - I2C_RESULT_INVALID_PARAM /* Invalid parameter */ + I2C_RESULT_INVALID_PARAM, /* Invalid parameter */ + I2C_RECOVERY_OK, + I2C_RECOVERY_SDA_STUCK_LOW, + I2C_RECOVERY_SCL_STUCK_LOW } i2c_result_t; /* I2C state machine enumeration */ @@ -69,24 +72,21 @@ typedef enum { /* Function declarations */ - -// TODO I2C Result /*! \brief configure the I2C interface \param[in] none \param[out] none \retval i2c_result_t */ -void i2c_config(void); +i2c_result_t i2c_config(void); -// TODO I2C Result /*! \brief reset I2C bus with proper recovery \param[in] none \param[out] none \retval i2c_result_t */ -void i2c_bus_reset(void); +i2c_result_t i2c_bus_reset(void); /*! \brief scan I2C bus for devices diff --git a/Src/i2c.c b/Src/i2c.c index f819dbb..d87a318 100644 --- a/Src/i2c.c +++ b/Src/i2c.c @@ -31,7 +31,7 @@ void i2c_gpio_config(void) { \param[out] none \retval none */ -void i2c_config(void) { +i2c_result_t i2c_config(void) { /* configure I2C GPIO */ i2c_gpio_config(); /* enable I2C clock */ @@ -44,6 +44,26 @@ void i2c_config(void) { i2c_enable(I2C0); /* enable acknowledge */ i2c_ack_config(I2C0, I2C_ACK_ENABLE); + + return I2C_RESULT_SUCCESS; +} + +static bool i2c_wait_scl_high(uint16_t max_wait_time) { + while (max_wait_time--) { + if (gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN)) { + return true; + } + delay_10us(1); + } + return false; +} + +/* generate one manual SCL pulse; return true if SCL observed high (no stuck/overstretch) */ +static bool i2c_generate_scl_pulse(void) { + GPIO_BC(I2C_SCL_PORT) = I2C_SCL_PIN; /* drive SCL low */ + delay_10us(1); + GPIO_BOP(I2C_SCL_PORT) = I2C_SCL_PIN; /* release SCL (open-drain -> high via pull-up) */ + return i2c_wait_scl_high(200); /* wait up to ~2ms for clock stretching release */ } /*! @@ -52,31 +72,87 @@ void i2c_config(void) { \param[out] none \retval none */ -void i2c_bus_reset(void) { +i2c_result_t i2c_bus_reset(void) { + /* 1. Disable & deinit peripheral so pins can be fully controlled */ + i2c_disable(I2C0); i2c_deinit(I2C0); - /* configure SDA/SCL for GPIO */ - GPIO_BC(I2C_SCL_PORT) |= I2C_SCL_PIN; - GPIO_BC(I2C_SDA_PORT) |= I2C_SDA_PIN; - gpio_output_options_set(I2C_SCL_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, I2C_SCL_PIN); - gpio_output_options_set(I2C_SDA_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, I2C_SDA_PIN); - __NOP(); - __NOP(); - __NOP(); - __NOP(); - __NOP(); - GPIO_BOP(I2C_SCL_PORT) |= I2C_SCL_PIN; - __NOP(); - __NOP(); - __NOP(); - __NOP(); - __NOP(); - GPIO_BOP(I2C_SDA_PORT) |= I2C_SDA_PIN; - /* connect I2C_SCL_PIN to I2C_SCL */ - /* connect I2C_SDA_PIN to I2C_SDA */ + +#ifdef DEBUG_VERBOSE + printf("I2C bus reset\r\n"); +#endif + + /* 2. Configure SCL/SDA as GPIO open-drain outputs with pull-up and release them */ + 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); - /* configure the I2CX interface */ + gpio_bit_set(I2C_SCL_PORT, I2C_SCL_PIN); /* release SCL */ + gpio_bit_set(I2C_SDA_PORT, I2C_SDA_PIN); /* release SDA */ + +#ifdef DEBUG_VERBOSE + printf("I2C bus reset: SCL = %d, SDA = %d\r\n", gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN), gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN)); +#endif + + /* 3. Double sample to confirm bus state */ + delay_10us(1); + bool scl_value1 = gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN); + bool sda_value1 = gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN); + delay_10us(1); + bool scl_value2 = gpio_input_bit_get(I2C_SCL_PORT, I2C_SCL_PIN); + bool sda_value2 = gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN); + + /* 4. If SCL low -> stuck (cannot proceed) */ + if (!scl_value2) { +#ifdef DEBUG_VERBOSE + printf("I2C bus reset: SCL stuck low\r\n"); +#endif + return I2C_RECOVERY_SCL_STUCK_LOW; + } + + /* 5. Fast path: bus idle */ + if (scl_value1 && sda_value1 && scl_value2 && sda_value2) { + i2c_config(); +#ifdef DEBUG_VERBOSE + printf("I2C bus reset: bus idle\r\n"); +#endif + return I2C_RECOVERY_OK; + } + + /* 6. SDA low: attempt to free by generating up to I2C_RECOVERY_CLOCKS pulses */ + if (scl_value2 && !sda_value2) { + bool sda_released = false; +#ifdef DEBUG_VERBOSE + printf("I2C bus reset: SCL will try to free SDA\r\n"); +#endif + for (uint8_t i = 0; i < I2C_RECOVERY_CLOCKS && !sda_released; i++) { + if (!i2c_generate_scl_pulse()) { + return I2C_RECOVERY_SCL_STUCK_LOW; /* SCL failed to go high */ + } + if (gpio_input_bit_get(I2C_SDA_PORT, I2C_SDA_PIN)) { + sda_released = true; + } + } + if (!sda_released) { + return I2C_RECOVERY_SDA_STUCK_LOW; + } + /* 7. Generate a STOP condition to leave bus in idle state */ +#ifdef DEBUG_VERBOSE + printf("I2C bus reset: generating STOP condition\r\n"); +#endif + gpio_bit_reset(I2C_SDA_PORT, I2C_SDA_PIN); /* SDA low */ + delay_10us(1); + gpio_bit_set(I2C_SCL_PORT, I2C_SCL_PIN); /* ensure SCL high */ + delay_10us(1); + gpio_bit_set(I2C_SDA_PORT, I2C_SDA_PIN); /* SDA rising while SCL high -> STOP */ + delay_10us(1); + } + +#ifdef DEBUG_VERBOSE + printf("I2C bus reset: bus recovered\r\n"); +#endif + /* 8. Reconfigure & enable peripheral */ i2c_config(); + return I2C_RECOVERY_OK; } /** diff --git a/Src/main.c b/Src/main.c index 5f11f7e..bb5adba 100644 --- a/Src/main.c +++ b/Src/main.c @@ -60,7 +60,7 @@ int main(void) led_init(); #ifdef DEBUG_VERBOSE - char hello_world[] = {"Hello World!"}; + char hello_world[] = {"Hello World!\r\n"}; for (uint8_t i = 0; i < sizeof(hello_world); i++) { @@ -69,29 +69,23 @@ int main(void) } while (usart_flag_get(RS485_PHY, USART_FLAG_TC) == RESET) {} -#endif +#endif +#ifdef DEBUG_VERBOSE i2c_config(); i2c_scan(); - // i2c_bus_reset(); - - // uint8_t sensor_data[2] = {0}; - - // i2c_read_16bits(0x2B, 0x7E, sensor_data); - - // printf("Sensor Data: 0x%02X 0x%02X\r\n", sensor_data[0], sensor_data[1]); - - // i2c_bus_reset(); + i2c_bus_reset(); + + i2c_scan(); +#endif ldc1612_iic_get_sensor_infomation(); -#ifdef DEBUG_VERBOSE - i2c_scan(); -#endif + while(1){ command_process();