doc: add application notes for i2s

This commit is contained in:
laokaiyao 2021-11-24 17:34:22 +08:00
parent 190502bfec
commit 2da3035b52
3 changed files with 101 additions and 24 deletions

View File

@ -346,7 +346,8 @@ esp_err_t i2s_set_pin(i2s_port_t i2s_num, const i2s_pin_config_t *pin)
static bool IRAM_ATTR i2s_dma_rx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
i2s_obj_t *p_i2s = (i2s_obj_t *) user_data;
portBASE_TYPE high_priority_task_awoken = 0;
portBASE_TYPE need_awoke = 0;
portBASE_TYPE tmp = 0;
int dummy;
i2s_event_t i2s_event;
uint32_t finish_desc;
@ -355,19 +356,23 @@ static bool IRAM_ATTR i2s_dma_rx_callback(gdma_channel_handle_t dma_chan, gdma_e
finish_desc = event_data->rx_eof_desc_addr;
i2s_event.size = ((lldesc_t *)finish_desc)->size;
if (xQueueIsQueueFullFromISR(p_i2s->rx->queue)) {
xQueueReceiveFromISR(p_i2s->rx->queue, &dummy, &high_priority_task_awoken);
xQueueReceiveFromISR(p_i2s->rx->queue, &dummy, &tmp);
need_awoke |= tmp;
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_RX_Q_OVF;
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
xQueueSendFromISR(p_i2s->rx->queue, &(((lldesc_t *)finish_desc)->buf), &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->rx->queue, &(((lldesc_t *)finish_desc)->buf), &tmp);
need_awoke |= tmp;
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_RX_DONE;
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
return high_priority_task_awoken;
return need_awoke;
}
/**
@ -383,7 +388,8 @@ static bool IRAM_ATTR i2s_dma_rx_callback(gdma_channel_handle_t dma_chan, gdma_e
static bool IRAM_ATTR i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
i2s_obj_t *p_i2s = (i2s_obj_t *) user_data;
portBASE_TYPE high_priority_task_awoken = 0;
portBASE_TYPE need_awoke = 0;
portBASE_TYPE tmp = 0;
int dummy;
i2s_event_t i2s_event;
uint32_t finish_desc;
@ -391,23 +397,27 @@ static bool IRAM_ATTR i2s_dma_tx_callback(gdma_channel_handle_t dma_chan, gdma_e
finish_desc = event_data->tx_eof_desc_addr;
i2s_event.size = ((lldesc_t *)finish_desc)->size;
if (xQueueIsQueueFullFromISR(p_i2s->tx->queue)) {
xQueueReceiveFromISR(p_i2s->tx->queue, &dummy, &high_priority_task_awoken);
xQueueReceiveFromISR(p_i2s->tx->queue, &dummy, &tmp);
need_awoke |= tmp;
if (p_i2s->tx_desc_auto_clear) {
memset((void *) dummy, 0, p_i2s->tx->buf_size);
}
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_TX_Q_OVF;
i2s_event.size = p_i2s->tx->buf_size;
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
xQueueSendFromISR(p_i2s->tx->queue, &(((lldesc_t *)finish_desc)->buf), &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->tx->queue, &(((lldesc_t *)finish_desc)->buf), &tmp);
need_awoke |= tmp;
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_TX_DONE;
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
return high_priority_task_awoken;
return need_awoke;
}
#else
@ -429,16 +439,19 @@ static void IRAM_ATTR i2s_intr_handler_default(void *arg)
i2s_event_t i2s_event;
int dummy;
portBASE_TYPE high_priority_task_awoken = 0;
portBASE_TYPE need_awoke = 0;
portBASE_TYPE tmp = 0;
uint32_t finish_desc = 0;
if ((status & I2S_INTR_OUT_DSCR_ERR) || (status & I2S_INTR_IN_DSCR_ERR)) {
ESP_EARLY_LOGE(TAG, "dma error, interrupt status: 0x%08x", status);
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_DMA_ERROR;
if (xQueueIsQueueFullFromISR(p_i2s->i2s_queue)) {
xQueueReceiveFromISR(p_i2s->i2s_queue, &dummy, &high_priority_task_awoken);
xQueueReceiveFromISR(p_i2s->i2s_queue, &dummy, &tmp);
need_awoke |= tmp;
}
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
@ -447,7 +460,8 @@ static void IRAM_ATTR i2s_intr_handler_default(void *arg)
i2s_event.size = ((lldesc_t *)finish_desc)->size;
// All buffers are empty. This means we have an underflow on our hands.
if (xQueueIsQueueFullFromISR(p_i2s->tx->queue)) {
xQueueReceiveFromISR(p_i2s->tx->queue, &dummy, &high_priority_task_awoken);
xQueueReceiveFromISR(p_i2s->tx->queue, &dummy, &tmp);
need_awoke |= tmp;
// See if tx descriptor needs to be auto cleared:
// This will avoid any kind of noise that may get introduced due to transmission
// of previous data from tx descriptor on I2S line.
@ -456,13 +470,16 @@ static void IRAM_ATTR i2s_intr_handler_default(void *arg)
}
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_TX_Q_OVF;
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
xQueueSendFromISR(p_i2s->tx->queue, &(((lldesc_t *)finish_desc)->buf), &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->tx->queue, &(((lldesc_t *)finish_desc)->buf), &tmp);
need_awoke |= tmp;
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_TX_DONE;
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
@ -471,21 +488,25 @@ static void IRAM_ATTR i2s_intr_handler_default(void *arg)
i2s_hal_get_in_eof_des_addr(&(p_i2s->hal), &finish_desc);
i2s_event.size = ((lldesc_t *)finish_desc)->size;
if (xQueueIsQueueFullFromISR(p_i2s->rx->queue)) {
xQueueReceiveFromISR(p_i2s->rx->queue, &dummy, &high_priority_task_awoken);
xQueueReceiveFromISR(p_i2s->rx->queue, &dummy, &tmp);
need_awoke |= tmp;
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_RX_Q_OVF;
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
xQueueSendFromISR(p_i2s->rx->queue, &(((lldesc_t *)finish_desc)->buf), &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->rx->queue, &(((lldesc_t *)finish_desc)->buf), &tmp);
need_awoke |= tmp;
if (p_i2s->i2s_queue) {
i2s_event.type = I2S_EVENT_RX_DONE;
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &high_priority_task_awoken);
xQueueSendFromISR(p_i2s->i2s_queue, (void * )&i2s_event, &tmp);
need_awoke |= tmp;
}
}
i2s_hal_clear_intr_status(&(p_i2s->hal), status);
if (high_priority_task_awoken == pdTRUE) {
if (need_awoke == pdTRUE) {
portYIELD_FROM_ISR();
}
}

View File

@ -113,6 +113,7 @@ typedef struct {
* We assume that the current 'dma_buf_len' is 100, then the real length of the DMA buffer is 8 * 100 = 800 bytes.
* Note that the length of an internal real DMA buffer shouldn't be greater than 4092.
*/
bool use_apll; /*!< I2S using APLL as main I2S clock, enable it to get accurate clock */
bool tx_desc_auto_clear; /*!< I2S auto clear tx descriptor if there is underflow condition (helps in avoiding noise in case of data unavailability) */
int fixed_mclk; /*!< I2S using fixed MCLK output. If use_apll = true and fixed_mclk > 0, then the clock output for i2s is fixed and equal to the fixed_mclk value. If fixed_mclk set, mclk_multiple won't take effect */

View File

@ -347,6 +347,61 @@ Example for general usage.
i2s_driver_uninstall(i2s_num); //stop & destroy i2s driver
Application Notes
-----------------
How to Prevent Data Lost
^^^^^^^^^^^^^^^^^^^^^^^^
For the applications that need a high frequency sample rate, sometimes the massive throughput of receiving data may cause data lost. Users can receive data lost event by registering an event queue handler to the driver during installation:
.. code-block:: c
QueueHandle_t evt_que;
i2s_driver_install(i2s_num, &i2s_config, 10, &evt_que);
You will receive ``I2S_EVENT_RX_Q_OVF`` event when there are data lost.
Please follow these steps to prevent data lost:
1. Determine the interrupt interval. Generally, when data lost happened, the interval should be the bigger the better, it can help to reduce the interrupt times, i.e., ``dma_buf_len`` should be as big as possible while the DMA buffer size won't exceed its maximum value 4092. The relationships are::
interrupt_interval(unit: sec) = dma_buf_len / sample_rate
dma_buffer_size = dma_buf_len * slot_num * data_bit_width / 8 <= 4092
2. Determine the ``dma_buf_count``. The ``dma_buf_count`` is decided by the max time of ``i2s_read`` polling cycle, all the received data are supposed to be stored between two ``i2s_read``. This cycle can be measured by a timer or an outputting gpio signal. The relationship is::
dma_buf_count > polling_cycle / interrupt_interval
3. Determine the receiving buffer size. The receiving buffer that offered by user in ``i2s_read`` should be able to take all the data in all dma buffers, that means it should be bigger than the total size of all the dma buffers::
recv_buffer_size > dma_buf_count * dma_buffer_size
For example, if there is an I2S application, and the known values are::
sample_rate = 144000 Hz
data_bit_width = 32 bits
slot_num = 2
polling_cycle = 10ms
Then the parameters ``dma_buf_len``, ``dma_buf_count`` and ``recv_buf_size`` can be calculated according to the given known values::
dma_buf_len * slot_num * data_bit_width / 8 = dma_buffer_size <= 4092
dma_buf_len <= 511
interrupt_interval = dma_buf_len / sample_rate = 511 / 144000 = 0.003549 s = 3.549 ms
dma_buf_count > polling_cycle / interrupt_interval = cell(10 / 3.549) = cell(2.818) = 3
recv_buffer_size > dma_buf_count * dma_buffer_size = 3 * 4092 = 12276 bytes
To check whether there are data lost, you can offer an event queue handler to the driver during installation:
.. code-block:: c
QueueHandle_t evt_que;
i2s_driver_install(i2s_num, &i2s_config, 10, &evt_que);
You will receive ``I2S_EVENT_RX_Q_OVF`` event when there are data lost.
API Reference
-------------