mirror of
https://github.com/espressif/esp-idf.git
synced 2024-09-20 00:36:01 -04:00
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
This commit is contained in:
parent
fcd1f1c808
commit
fd27cef045
@ -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);
|
esp_cache_msync((void *)finish_desc->buf, handle->dma.buf_size, ESP_CACHE_MSYNC_FLAG_INVALIDATE);
|
||||||
#endif
|
#endif
|
||||||
i2s_event_data_t evt = {
|
i2s_event_data_t evt = {
|
||||||
.data = finish_desc->buf,
|
.data = (void *)finish_desc->buf,
|
||||||
.size = handle->dma.buf_size,
|
.size = handle->dma.buf_size,
|
||||||
};
|
};
|
||||||
if (handle->callbacks.on_recv) {
|
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;
|
uint32_t dummy;
|
||||||
|
|
||||||
finish_desc = (lldesc_t *)event_data->tx_eof_desc_addr;
|
finish_desc = (lldesc_t *)event_data->tx_eof_desc_addr;
|
||||||
|
void *curr_buf = (void *)finish_desc->buf;
|
||||||
i2s_event_data_t evt = {
|
i2s_event_data_t evt = {
|
||||||
.data = finish_desc->buf,
|
.data = curr_buf,
|
||||||
.size = handle->dma.buf_size,
|
.size = handle->dma.buf_size,
|
||||||
};
|
};
|
||||||
if (handle->dma.auto_clear) {
|
if (handle->dma.auto_clear_before_cb) {
|
||||||
uint8_t *sent_buf = (uint8_t *)finish_desc->buf;
|
memset(curr_buf, 0, handle->dma.buf_size);
|
||||||
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) {
|
if (handle->callbacks.on_sent) {
|
||||||
user_need_yield |= handle->callbacks.on_sent(handle, &evt, handle->user_data);
|
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)) {
|
if (xQueueIsQueueFullFromISR(handle->msg_queue)) {
|
||||||
xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1);
|
xQueueReceiveFromISR(handle->msg_queue, &dummy, &need_yield1);
|
||||||
if (handle->callbacks.on_send_q_ovf) {
|
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);
|
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);
|
xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2);
|
||||||
|
|
||||||
return need_yield1 | need_yield2 | user_need_yield;
|
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)) {
|
if (handle && (status & I2S_LL_EVENT_RX_EOF)) {
|
||||||
i2s_hal_get_in_eof_des_addr(&(handle->controller->hal), (uint32_t *)&finish_desc);
|
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;
|
evt.size = handle->dma.buf_size;
|
||||||
if (handle->callbacks.on_recv) {
|
if (handle->callbacks.on_recv) {
|
||||||
user_need_yield |= handle->callbacks.on_recv(handle, &evt, handle->user_data);
|
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)) {
|
if (handle && (status & I2S_LL_EVENT_TX_EOF)) {
|
||||||
i2s_hal_get_out_eof_des_addr(&(handle->controller->hal), (uint32_t *)&finish_desc);
|
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;
|
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) {
|
if (handle->callbacks.on_sent) {
|
||||||
user_need_yield |= handle->callbacks.on_sent(handle, &evt, handle->user_data);
|
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
|
// Auto clear the dma buffer after data sent
|
||||||
if (handle->dma.auto_clear) {
|
if (handle->dma.auto_clear_after_cb) {
|
||||||
uint8_t *buff = (uint8_t *)finish_desc->buf;
|
memset(curr_buf, 0, handle->dma.buf_size);
|
||||||
memset(buff, 0, handle->dma.buf_size);
|
|
||||||
}
|
}
|
||||||
xQueueSendFromISR(handle->msg_queue, &(finish_desc->buf), &need_yield2);
|
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");
|
err, TAG, "register I2S tx channel failed");
|
||||||
i2s_obj->tx_chan->role = chan_cfg->role;
|
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->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.desc_num = chan_cfg->dma_desc_num;
|
||||||
i2s_obj->tx_chan->dma.frame_num = chan_cfg->dma_frame_num;
|
i2s_obj->tx_chan->dma.frame_num = chan_cfg->dma_frame_num;
|
||||||
i2s_obj->tx_chan->start = i2s_tx_channel_start;
|
i2s_obj->tx_chan->start = i2s_tx_channel_start;
|
||||||
|
@ -97,7 +97,8 @@ typedef struct {
|
|||||||
uint32_t desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */
|
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 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 */
|
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 */
|
uint32_t rw_pos; /*!< reading/writing pointer position */
|
||||||
void *curr_ptr; /*!< Pointer to current dma buffer */
|
void *curr_ptr; /*!< Pointer to current dma buffer */
|
||||||
void *curr_desc; /*!< Pointer to current dma descriptor used for pre-load */
|
void *curr_desc; /*!< Pointer to current dma descriptor used for pre-load */
|
||||||
|
@ -24,7 +24,8 @@ extern "C" {
|
|||||||
.role = i2s_role, \
|
.role = i2s_role, \
|
||||||
.dma_desc_num = 6, \
|
.dma_desc_num = 6, \
|
||||||
.dma_frame_num = 240, \
|
.dma_frame_num = 240, \
|
||||||
.auto_clear = false, \
|
.auto_clear_after_cb = false, \
|
||||||
|
.auto_clear_before_cb = false, \
|
||||||
.intr_priority = 0, \
|
.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,
|
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.
|
* 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) */
|
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;
|
} i2s_chan_config_t;
|
||||||
|
|
||||||
|
@ -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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -907,3 +907,80 @@ finish:
|
|||||||
// Test failed if package lost within 96000
|
// Test failed if package lost within 96000
|
||||||
TEST_ASSERT(i == test_num);
|
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);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user