feat(i2c_slave): Add API to perform slave tx buffer reset

Closes https://github.com/espressif/esp-idf/issues/16241
This commit is contained in:
Chen Chen
2025-09-22 15:13:50 +08:00
parent 51d5e8fd08
commit 8b8b5df141
6 changed files with 259 additions and 1 deletions

View File

@@ -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_rxfifo_full_thr(hal->dev, SOC_I2C_FIFO_LEN / 2);
i2c_ll_set_sda_timing(hal->dev, 10, 10); 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_disable_intr_mask(hal->dev, I2C_LL_INTR_MASK);
i2c_ll_clear_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; i2c_slave->receive_callback = cbs->on_receive;
return ESP_OK; 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

View File

@@ -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); 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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -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); 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 #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. // 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) static void i2c_master_write_test_more_port(void)

View File

@@ -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) 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;
} }
/** /**

View File

@@ -549,6 +549,33 @@ A simple example for transmitting data:
vTaskDelete(NULL); 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 I2C Slave Read
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@@ -549,6 +549,33 @@ I2C 从机写入
vTaskDelete(NULL); 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 从机读取 I2C 从机读取
~~~~~~~~~~~~ ~~~~~~~~~~~~