From fcd1f1c8086f5b75b1cac1a01ba8db9326c4003f Mon Sep 17 00:00:00 2001 From: Marc CAPDEVILLE Date: Sun, 7 Jan 2024 03:04:03 +0100 Subject: [PATCH 1/2] change(esp_driver_i2s) : Some hack on I2S callback mod for event data to contain the pointer to the dma buffer. mod for clearing data on send buffer before calling tx callback --- components/esp_driver_i2s/i2s_common.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index 387e6a7728..9f90f18d61 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -513,7 +513,7 @@ static bool IRAM_ATTR i2s_dma_rx_callback(gdma_channel_handle_t dma_chan, gdma_e esp_cache_msync((void *)finish_desc->buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_INVALIDATE); #endif i2s_event_data_t evt = { - .data = &(finish_desc->buf), + .data = finish_desc->buf, .size = handle->dma.buf_size, }; if (handle->callbacks.on_recv) { @@ -542,9 +542,16 @@ static bool IRAM_ATTR i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_e finish_desc = (lldesc_t *)event_data->tx_eof_desc_addr; i2s_event_data_t evt = { - .data = &(finish_desc->buf), + .data = finish_desc->buf, .size = handle->dma.buf_size, }; + if (handle->dma.auto_clear) { + uint8_t *sent_buf = (uint8_t *)finish_desc->buf; + memset(sent_buf, 0, handle->dma.buf_size); +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + esp_cache_msync(sent_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); +#endif + } if (handle->callbacks.on_sent) { user_need_yield |= handle->callbacks.on_sent(handle, &evt, handle->user_data); } @@ -555,13 +562,6 @@ static bool IRAM_ATTR i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_e user_need_yield |= handle->callbacks.on_send_q_ovf(handle, &evt, handle->user_data); } } - if (handle->dma.auto_clear) { - uint8_t *sent_buf = (uint8_t *)finish_desc->buf; - memset(sent_buf, 0, handle->dma.buf_size); -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - esp_cache_msync(sent_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); -#endif - } xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2); return need_yield1 | need_yield2 | user_need_yield; @@ -587,7 +587,7 @@ static void IRAM_ATTR i2s_dma_rx_callback(void *arg) if (handle && (status & I2S_LL_EVENT_RX_EOF)) { i2s_hal_get_in_eof_des_addr(&(handle->controller->hal), (uint32_t *)&finish_desc); - evt.data = &(finish_desc->buf); + evt.data = finish_desc->buf; evt.size = handle->dma.buf_size; if (handle->callbacks.on_recv) { user_need_yield |= handle->callbacks.on_recv(handle, &evt, handle->user_data); @@ -625,7 +625,7 @@ static void IRAM_ATTR i2s_dma_tx_callback(void *arg) if (handle && (status & I2S_LL_EVENT_TX_EOF)) { i2s_hal_get_out_eof_des_addr(&(handle->controller->hal), (uint32_t *)&finish_desc); - evt.data = &(finish_desc->buf); + evt.data = finish_desc->buf; evt.size = handle->dma.buf_size; if (handle->callbacks.on_sent) { user_need_yield |= handle->callbacks.on_sent(handle, &evt, handle->user_data); From fd27cef0451b6480247949c54db89a44a52ebbf7 Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Wed, 27 Mar 2024 11:37:51 +0800 Subject: [PATCH 2/2] feat(i2s): support asynchronous read write via callback Split the TX DMA buffer `auto_clear` into `auto_clear_after_cb` and `auto_clear_before_cb`, so that allow user to update the DMA buffer directly in the `on_sent` callback --- components/esp_driver_i2s/i2s_common.c | 42 ++++++---- components/esp_driver_i2s/i2s_private.h | 3 +- .../include/driver/i2s_common.h | 13 ++- .../test_apps/i2s/main/test_i2s.c | 79 ++++++++++++++++++- 4 files changed, 119 insertions(+), 18 deletions(-) diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index 9f90f18d61..7d0b075add 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -513,7 +513,7 @@ static bool IRAM_ATTR i2s_dma_rx_callback(gdma_channel_handle_t dma_chan, gdma_e esp_cache_msync((void *)finish_desc->buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_INVALIDATE); #endif i2s_event_data_t evt = { - .data = finish_desc->buf, + .data = (void *)finish_desc->buf, .size = handle->dma.buf_size, }; if (handle->callbacks.on_recv) { @@ -541,20 +541,23 @@ static bool IRAM_ATTR i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_e uint32_t dummy; finish_desc = (lldesc_t *)event_data->tx_eof_desc_addr; + void *curr_buf = (void *)finish_desc->buf; i2s_event_data_t evt = { - .data = finish_desc->buf, + .data = curr_buf, .size = handle->dma.buf_size, }; - if (handle->dma.auto_clear) { - uint8_t *sent_buf = (uint8_t *)finish_desc->buf; - memset(sent_buf, 0, handle->dma.buf_size); -#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE - esp_cache_msync(sent_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); -#endif + if (handle->dma.auto_clear_before_cb) { + memset(curr_buf, 0, handle->dma.buf_size); } if (handle->callbacks.on_sent) { user_need_yield |= handle->callbacks.on_sent(handle, &evt, handle->user_data); } +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + /* Sync buffer after the callback incase users update the buffer in the callback */ + if (handle->dma.auto_clear_before_cb || handle->callbacks.on_sent) { + esp_cache_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + } +#endif if (xQueueIsQueueFullFromISR(handle->msg_queue)) { xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1); if (handle->callbacks.on_send_q_ovf) { @@ -562,6 +565,12 @@ static bool IRAM_ATTR i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_e user_need_yield |= handle->callbacks.on_send_q_ovf(handle, &evt, handle->user_data); } } + if (handle->dma.auto_clear_after_cb) { + memset(curr_buf, 0, handle->dma.buf_size); +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + esp_cache_msync(curr_buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); +#endif + } xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2); return need_yield1 | need_yield2 | user_need_yield; @@ -587,7 +596,7 @@ static void IRAM_ATTR i2s_dma_rx_callback(void *arg) if (handle && (status & I2S_LL_EVENT_RX_EOF)) { i2s_hal_get_in_eof_des_addr(&(handle->controller->hal), (uint32_t *)&finish_desc); - evt.data = finish_desc->buf; + evt.data = (void *)finish_desc->buf; evt.size = handle->dma.buf_size; if (handle->callbacks.on_recv) { user_need_yield |= handle->callbacks.on_recv(handle, &evt, handle->user_data); @@ -625,8 +634,13 @@ static void IRAM_ATTR i2s_dma_tx_callback(void *arg) if (handle && (status & I2S_LL_EVENT_TX_EOF)) { i2s_hal_get_out_eof_des_addr(&(handle->controller->hal), (uint32_t *)&finish_desc); - evt.data = finish_desc->buf; + void *curr_buf = (void *)finish_desc->buf; + evt.data = curr_buf; evt.size = handle->dma.buf_size; + // Auto clear the dma buffer before data sent + if (handle->dma.auto_clear_before_cb) { + memset(curr_buf, 0, handle->dma.buf_size); + } if (handle->callbacks.on_sent) { user_need_yield |= handle->callbacks.on_sent(handle, &evt, handle->user_data); } @@ -638,9 +652,8 @@ static void IRAM_ATTR i2s_dma_tx_callback(void *arg) } } // Auto clear the dma buffer after data sent - if (handle->dma.auto_clear) { - uint8_t *buff = (uint8_t *)finish_desc->buf; - memset(buff, 0, handle->dma.buf_size); + if (handle->dma.auto_clear_after_cb) { + memset(curr_buf, 0, handle->dma.buf_size); } xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2); } @@ -820,7 +833,8 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * err, TAG, "register I2S tx channel failed"); i2s_obj->tx_chan->role = chan_cfg->role; i2s_obj->tx_chan->intr_prio_flags = chan_cfg->intr_priority ? BIT(chan_cfg->intr_priority) : ESP_INTR_FLAG_LOWMED; - i2s_obj->tx_chan->dma.auto_clear = chan_cfg->auto_clear; + i2s_obj->tx_chan->dma.auto_clear_after_cb = chan_cfg->auto_clear_after_cb; + i2s_obj->tx_chan->dma.auto_clear_before_cb = chan_cfg->auto_clear_before_cb; i2s_obj->tx_chan->dma.desc_num = chan_cfg->dma_desc_num; i2s_obj->tx_chan->dma.frame_num = chan_cfg->dma_frame_num; i2s_obj->tx_chan->start = i2s_tx_channel_start; diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index 9a5877be0f..59e1a6b4a5 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -97,7 +97,8 @@ typedef struct { uint32_t desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */ uint32_t frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots */ uint32_t buf_size; /*!< dma buffer size */ - bool auto_clear; /*!< Set to auto clear DMA TX descriptor, i2s will always send zero automatically if no data to send */ + bool auto_clear_after_cb; /*!< Set to auto clear DMA TX descriptor after callback, i2s will always send zero automatically if no data to send */ + bool auto_clear_before_cb; /*!< Set to auto clear DMA TX descriptor before callback, i2s will always send zero automatically if no data to send */ uint32_t rw_pos; /*!< reading/writing pointer position */ void *curr_ptr; /*!< Pointer to current dma buffer */ void *curr_desc; /*!< Pointer to current dma descriptor used for pre-load */ diff --git a/components/esp_driver_i2s/include/driver/i2s_common.h b/components/esp_driver_i2s/include/driver/i2s_common.h index a50c5d8446..abf63851a3 100644 --- a/components/esp_driver_i2s/include/driver/i2s_common.h +++ b/components/esp_driver_i2s/include/driver/i2s_common.h @@ -24,7 +24,8 @@ extern "C" { .role = i2s_role, \ .dma_desc_num = 6, \ .dma_frame_num = 240, \ - .auto_clear = false, \ + .auto_clear_after_cb = false, \ + .auto_clear_before_cb = false, \ .intr_priority = 0, \ } @@ -63,7 +64,15 @@ typedef struct { uint32_t dma_frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots, * it should be the multiple of `3` when the data bit width is 24. */ - bool auto_clear; /*!< Set to auto clear DMA TX buffer, I2S will always send zero automatically if no data to send */ + union { + bool auto_clear; /*!< Alias of `auto_clear_after_cb` to be compatible with previous version */ + bool auto_clear_after_cb; /*!< Set to auto clear DMA TX buffer after `on_sent` callback, I2S will always send zero automatically if no data to send. + * So that user can assign the data to the DMA buffers directly in the callback, and the data won't be cleared after quitted the callback. + */ + }; + bool auto_clear_before_cb; /*!< Set to auto clear DMA TX buffer before `on_sent` callback, I2S will always send zero automatically if no data to send + * So that user can access data in the callback that just finished to send. + */ int intr_priority; /*!< I2S interrupt priority, range [0, 7], if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */ } i2s_chan_config_t; diff --git a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c index eff30d9f1c..fb47e22ed3 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c +++ b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -907,3 +907,80 @@ finish: // Test failed if package lost within 96000 TEST_ASSERT(i == test_num); } + +#define TEST_I2S_BUF_DATA_OFFSET 100 + +static IRAM_ATTR bool i2s_tx_on_sent_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) +{ + uint32_t *data = (uint32_t *)(event->data); + size_t len = event->size / sizeof(uint32_t); + for (int i = 0; i < len; i++) { + data[i] = i + TEST_I2S_BUF_DATA_OFFSET; + } + return false; +} + +static IRAM_ATTR bool i2s_rx_on_recv_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) +{ + bool *received = (bool *)user_ctx; + uint32_t *data = (uint32_t *)(event->data); + size_t len = event->size / sizeof(uint32_t); + for (int i = 0; i < len; i++) { + if (data[i] == TEST_I2S_BUF_DATA_OFFSET) { + for (int j = 0; i < len && data[i] == (j + TEST_I2S_BUF_DATA_OFFSET); i++, j++); + if (i == len) { + *received = true; + break; + } + } + } + return false; +} + +TEST_CASE("I2S_asynchronous_read_write", "[i2s]") +{ + i2s_chan_handle_t tx_handle; + i2s_chan_handle_t rx_handle; + + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + // Only clear the data before callback, so that won't clear the user given data in the callback + chan_cfg.auto_clear_before_cb = true; + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_STEREO), + .gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN, + }; + std_cfg.gpio_cfg.din = std_cfg.gpio_cfg.dout; // GPIO loopback + + TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle)); + TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); + TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg)); + + i2s_event_callbacks_t cbs = { + .on_sent = i2s_tx_on_sent_callback, + .on_recv = i2s_rx_on_recv_callback, + }; + bool received = false; + TEST_ESP_OK(i2s_channel_register_event_callback(rx_handle, &cbs, &received)); + TEST_ESP_OK(i2s_channel_register_event_callback(tx_handle, &cbs, NULL)); + + TEST_ESP_OK(i2s_channel_enable(rx_handle)); + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + + /* Wait until receive correct data */ + uint32_t timeout_ms = 3000; + while (!received) { + vTaskDelay(pdMS_TO_TICKS(10)); + timeout_ms -= 10; + if (timeout_ms <= 0) { + break; + } + } + + TEST_ESP_OK(i2s_channel_disable(tx_handle)); + TEST_ESP_OK(i2s_channel_disable(rx_handle)); + TEST_ESP_OK(i2s_del_channel(tx_handle)); + TEST_ESP_OK(i2s_del_channel(rx_handle)); + + TEST_ASSERT(received); +}