mirror of
https://github.com/espressif/esp-idf.git
synced 2025-11-11 00:12:06 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -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 从机读取
|
||||
~~~~~~~~~~~~
|
||||
|
||||
|
||||
Reference in New Issue
Block a user