diff --git a/components/esp_driver_i2c/i2c_slave.c b/components/esp_driver_i2c/i2c_slave.c index 017a2454d7ade..c98c136c07bd9 100644 --- a/components/esp_driver_i2c/i2c_slave.c +++ b/components/esp_driver_i2c/i2c_slave.c @@ -320,6 +320,13 @@ esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave i2c_ll_set_rxfifo_full_thr(hal->dev, SOC_I2C_FIFO_LEN / 2); i2c_ll_set_sda_timing(hal->dev, 10, 10); +#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 + // Workaround for hardware bug in ESP32S3 and ESP32C3. + // Please note that following code has no functionality. + // It's just use for workaround the potential issue. + i2c_ll_slave_enable_auto_start(hal->dev, true); +#endif + i2c_ll_disable_intr_mask(hal->dev, I2C_LL_INTR_MASK); i2c_ll_clear_intr_mask(hal->dev, I2C_LL_INTR_MASK); @@ -443,3 +450,39 @@ esp_err_t i2c_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, c i2c_slave->receive_callback = cbs->on_receive; return ESP_OK; } + +#if !CONFIG_IDF_TARGET_ESP32 +esp_err_t i2c_slave_reset_tx_fifo(i2c_slave_dev_handle_t i2c_slave) +{ + ESP_RETURN_ON_FALSE(i2c_slave != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c slave handle not initialized"); + ESP_RETURN_ON_FALSE(i2c_slave->tx_ring_buf != NULL, ESP_ERR_INVALID_STATE, TAG, "invalid tx buffer state"); + + xSemaphoreTake(i2c_slave->operation_mux, portMAX_DELAY); + + // Clear up TX RingBuffer + if (i2c_slave->tx_ring_buf) { + void *data = NULL; + size_t size = 0; + do { + data = xRingbufferReceiveUpTo(i2c_slave->tx_ring_buf, &size, 0, SIZE_MAX); + if (data) { + vRingbufferReturnItem(i2c_slave->tx_ring_buf, data); + } + } while (data != NULL); + } else { + xSemaphoreGive(i2c_slave->operation_mux); + return ESP_FAIL; + } + + portENTER_CRITICAL(&i2c_slave->base->spinlock); + i2c_ll_txfifo_rst(i2c_slave->base->hal.dev); + +#if !CONFIG_IDF_TARGET_ESP32S2 + i2c_ll_master_fsm_rst(i2c_slave->base->hal.dev); +#endif + + portEXIT_CRITICAL(&i2c_slave->base->spinlock); + xSemaphoreGive(i2c_slave->operation_mux); + return ESP_OK; +} +#endif diff --git a/components/esp_driver_i2c/include/driver/i2c_slave.h b/components/esp_driver_i2c/include/driver/i2c_slave.h index f260c2b28adde..8bbae5e7bb6b0 100644 --- a/components/esp_driver_i2c/include/driver/i2c_slave.h +++ b/components/esp_driver_i2c/include/driver/i2c_slave.h @@ -105,6 +105,22 @@ esp_err_t i2c_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, c */ esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave); +#if !CONFIG_IDF_TARGET_ESP32 +/** + * @brief Reset the transmit (TX) FIFO of the I2C slave device + * + * This function clears the TX RingBuffer and resets the hardware TX FIFO for the specified I2C slave device. + * It is useful when you need to discard all pending data to be sent and restore the TX state. + * + * @param[in] i2c_slave I2C slave device handle created by `i2c_new_slave_device`. + * @return + * - ESP_OK: Reset successfully. + * - ESP_ERR_INVALID_ARG: Invalid argument. + * - ESP_ERR_INVALID_STATE: If the TX buffer is not properly initialized. + */ +esp_err_t i2c_slave_reset_tx_fifo(i2c_slave_dev_handle_t i2c_slave); +#endif + #ifdef __cplusplus } #endif diff --git a/components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_multi.cpp b/components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_multi.cpp index 74d8f0ff4b282..fb4bf9127b9ae 100644 --- a/components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_multi.cpp +++ b/components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_multi.cpp @@ -733,6 +733,151 @@ static void i2c_slave_read_multi_buffer_test(void) TEST_CASE_MULTIPLE_DEVICES("I2C master write slave with multi buffer api test", "[i2c][test_env=generic_multi_device][timeout=150]", i2c_master_write_multi_buffer_test, i2c_slave_read_multi_buffer_test); +static void i2c_slave_reset_tx_fifo_test_master(void) +{ + uint8_t data_rd[16] = {0}; // Buffer for reading data from slave + + // Configure I2C master bus + i2c_master_bus_config_t i2c_mst_config = {}; + i2c_mst_config.i2c_port = TEST_I2C_PORT; + i2c_mst_config.sda_io_num = I2C_MASTER_SDA_IO; + i2c_mst_config.scl_io_num = I2C_MASTER_SCL_IO; + i2c_mst_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_mst_config.flags.enable_internal_pullup = true; + + i2c_master_bus_handle_t bus_handle; + TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config, &bus_handle)); + + // Configure I2C device + i2c_device_config_t dev_cfg = {}; + dev_cfg.dev_addr_length = I2C_ADDR_BIT_LEN_7; + dev_cfg.device_address = ESP_SLAVE_ADDR; + dev_cfg.scl_speed_hz = 100000; + dev_cfg.scl_wait_us = 20000; + + i2c_master_dev_handle_t dev_handle; + TEST_ESP_OK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle)); + + unity_wait_for_signal("i2c slave init finish"); + + // First read: Request 8 bytes from slave (slave prepared 16 bytes) + memset(data_rd, 0, sizeof(data_rd)); + TEST_ESP_OK(i2c_master_receive(dev_handle, data_rd, 8, -1)); + + printf("First read (8 bytes): "); + for (int i = 0; i < 8; i++) { + printf("0x%02x ", data_rd[i]); + } + printf("\n"); + + // Verify first batch data (0x00 to 0x07) + for (int i = 0; i < 8; i++) { + TEST_ASSERT_EQUAL_HEX8(i, data_rd[i]); + } + + unity_send_signal("first read complete"); + unity_wait_for_signal("slave reset tx fifo"); + + // Second read: Request 8 bytes again (should get new data after reset) + memset(data_rd, 0, sizeof(data_rd)); + TEST_ESP_OK(i2c_master_receive(dev_handle, data_rd, 8, -1)); + + printf("Second read (8 bytes): "); + for (int i = 0; i < 8; i++) { + printf("0x%02x ", data_rd[i]); + } + printf("\n"); + + // Verify second batch data (0x10 to 0x17) + for (int i = 0; i < 8; i++) { + TEST_ASSERT_EQUAL_HEX8(0x10 + i, data_rd[i]); + } + + unity_send_signal("second read complete"); + + // Cleanup + TEST_ESP_OK(i2c_master_bus_rm_device(dev_handle)); + TEST_ESP_OK(i2c_del_master_bus(bus_handle)); +} + +static void i2c_slave_reset_tx_fifo_test_slave(void) +{ + i2c_slave_dev_handle_t handle; + uint8_t data_wr_first[16]; // First batch of data (0x00 to 0x0F) + uint8_t data_wr_second[16]; // Second batch of data (0x10 to 0x1F) + + event_queue = xQueueCreate(5, sizeof(i2c_slave_event_t)); + assert(event_queue); + + // Configure I2C slave + i2c_slave_config_t i2c_slv_config = {}; + i2c_slv_config.i2c_port = TEST_I2C_PORT; + i2c_slv_config.sda_io_num = I2C_SLAVE_SDA_IO; + i2c_slv_config.scl_io_num = I2C_SLAVE_SCL_IO; + i2c_slv_config.clk_source = I2C_CLK_SRC_DEFAULT; + i2c_slv_config.send_buf_depth = 32; // Larger buffer for test + i2c_slv_config.receive_buf_depth = DATA_LENGTH; + i2c_slv_config.slave_addr = ESP_SLAVE_ADDR; + i2c_slv_config.flags.enable_internal_pullup = true; + + TEST_ESP_OK(i2c_new_slave_device(&i2c_slv_config, &handle)); + + // Register event callbacks + i2c_slave_event_callbacks_t cbs = {}; + cbs.on_request = i2c_slave_request_cb; + cbs.on_receive = i2c_slave_receive_cb; + TEST_ESP_OK(i2c_slave_register_event_callbacks(handle, &cbs, NULL)); + + // Prepare first batch of data (0x00 to 0x0F) + for (int i = 0; i < 16; i++) { + data_wr_first[i] = i; + } + + // Prepare second batch of data (0x10 to 0x1F) + for (int i = 0; i < 16; i++) { + data_wr_second[i] = 0x10 + i; + } + + unity_send_signal("i2c slave init finish"); + + // Wait for first request and send first batch data + i2c_slave_event_t evt; + uint32_t write_len; + + if (xQueueReceive(event_queue, &evt, portMAX_DELAY) == pdTRUE) { + if (evt == I2C_SLAVE_EVT_TX) { + printf("Slave: Preparing first batch data (16 bytes)\n"); + TEST_ESP_OK(i2c_slave_write(handle, data_wr_first, 16, &write_len, 1000)); + printf("Slave: Written %lu bytes to TX buffer\n", write_len); + } + } + + unity_wait_for_signal("first read complete"); + + // Reset TX FIFO to clear remaining data + printf("Slave: Resetting TX FIFO\n"); + TEST_ESP_OK(i2c_slave_reset_tx_fifo(handle)); + + unity_send_signal("slave reset tx fifo"); + + // Wait for second request and send second batch data + if (xQueueReceive(event_queue, &evt, portMAX_DELAY) == pdTRUE) { + if (evt == I2C_SLAVE_EVT_TX) { + printf("Slave: Preparing second batch data (16 bytes)\n"); + TEST_ESP_OK(i2c_slave_write(handle, data_wr_second, 16, &write_len, 1000)); + printf("Slave: Written %lu bytes to TX buffer\n", write_len); + } + } + + unity_wait_for_signal("second read complete"); + + // Cleanup + vQueueDelete(event_queue); + TEST_ESP_OK(i2c_del_slave_device(handle)); +} + +TEST_CASE_MULTIPLE_DEVICES("I2C slave reset TX FIFO test", "[i2c][test_env=generic_multi_device][timeout=150]", i2c_slave_reset_tx_fifo_test_master, i2c_slave_reset_tx_fifo_test_slave); + #if SOC_HP_I2C_NUM > 1 // Now chips with multiple I2C controllers are up to 2, can test more ports when we have more I2C controllers. static void i2c_master_write_test_more_port(void) diff --git a/components/esp_hal_i2c/esp32s3/include/hal/i2c_ll.h b/components/esp_hal_i2c/esp32s3/include/hal/i2c_ll.h index 5a96009d148a7..6b172a7702ddd 100644 --- a/components/esp_hal_i2c/esp32s3/include/hal/i2c_ll.h +++ b/components/esp_hal_i2c/esp32s3/include/hal/i2c_ll.h @@ -1168,7 +1168,7 @@ static inline void i2c_ll_master_disable_rx_it(i2c_dev_t *hw) */ static inline void i2c_ll_slave_enable_tx_it(i2c_dev_t *hw) { - hw->int_ena.val |= 0x2; + hw->int_ena.val |= I2C_LL_SLAVE_TX_INT; } /** diff --git a/docs/en/api-reference/peripherals/i2c.rst b/docs/en/api-reference/peripherals/i2c.rst index 98bd8e98b1b1e..0562da97f2fb1 100644 --- a/docs/en/api-reference/peripherals/i2c.rst +++ b/docs/en/api-reference/peripherals/i2c.rst @@ -549,6 +549,33 @@ A simple example for transmitting data: vTaskDelete(NULL); } +.. only:: not esp32 + + I2C Slave Reset TX FIFO + ~~~~~~~~~~~~~~~~~~~~~~~~ + + In some scenarios, the slave may prepare more data than the master actually reads. For example, if the slave prepares 16 bytes of data but the master only reads 8 bytes, the remaining 8 bytes will stay in the TX FIFO. To prepare fresh data for the next transaction, you can use :cpp:func:`i2c_slave_reset_tx_fifo` to clear the TX FIFO. + + .. note:: + + It is recommended to call this function after the master has completed the read transaction to ensure data integrity. + + Simple example: + + .. code:: c + + // First write, data may not be completely read by master + ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer_1, buffer_size_1, &write_len_1, 1000)); + + // Wait for the next master read transaction, here we simply use a delay for demonstration + vTaskDelay(pdMS_TO_TICKS(10)); + + // Clear remaining data in TX FIFO + ESP_ERROR_CHECK(i2c_slave_reset_tx_fifo(handle)); + + // Second write, new data will be sent normally + ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer_2, buffer_size_2, &write_len_2, 1000)); + I2C Slave Read ~~~~~~~~~~~~~~ diff --git a/docs/zh_CN/api-reference/peripherals/i2c.rst b/docs/zh_CN/api-reference/peripherals/i2c.rst index 8839440806e78..6268a181bd9e3 100644 --- a/docs/zh_CN/api-reference/peripherals/i2c.rst +++ b/docs/zh_CN/api-reference/peripherals/i2c.rst @@ -549,6 +549,33 @@ I2C 从机写入 vTaskDelete(NULL); } +.. only:: not esp32 + + I2C 从机重置 TX FIFO + ~~~~~~~~~~~~~~~~~~~~ + + 在某些情况下,从机准备的数据可能未完全被主机读取。例如,从机准备了 16 字节数据,但主机只读取了 8 字节,剩余的 8 字节将保留在 TX FIFO 中。为了为下一次事务准备新的数据,可以使用 :cpp:func:`i2c_slave_reset_tx_fifo` 来清空 TX FIFO。 + + .. note:: + + 建议在主机完成读取事务后再调用此函数,以确保数据完整性。 + + 简单示例: + + .. code:: c + + // 第一次写入,数据可能未完全被主机读取 + ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer_1, buffer_size_1, &write_len_1, 1000)); + + // 等待下一次主机读取事务,简单起见使用延时代替 + vTaskDelay(pdMS_TO_TICKS(100)); + + // 清空 TX FIFO 中的剩余数据 + ESP_ERROR_CHECK(i2c_slave_reset_tx_fifo(handle)); + + // 第二次写入,新数据会正常发送 + ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer_2, buffer_size_2, &write_len_2, 1000)); + I2C 从机读取 ~~~~~~~~~~~~