I2S - detect empty DMA queue

Posts: 2
Joined: Mon Feb 22, 2021 3:59 am

I2S - detect empty DMA queue

Postby palmerr23 » Mon Feb 22, 2021 4:14 am

Hi (newbie here),

I'm working on a project that inputs audio packets via UDP (VBAN protocol) and outputs them to an external I2S DAC. It's working fine, other than occasional crackles.

I suspect that I'm suffering DMA buffer underrun, but don't know a way to detect when the I2S/DMA has run out of fresh data and is going to re-use old buffers.

Simply, my code is an interrupt driven routine to put incoming packets into a double-buffered queue.


The I2S writing code, which I have tried as both an xTask and in the main program loop, refills the I2S DMA buffers (I've tried 2 - 6 * 256 sample buffers with no real difference) using i2s_write().

I can detect when the DMA queue was full, by measuring the time it takes to execute i2s_write().

Is there a way to detect when the DMA queue is empty? I've tried reading I2S0.state.tx_idle, I2S0.int_st.out_done, I2S0.int_st.tx_rempty, I2S0.int_st.out_eof and I2S0.int_raw.tx_rempty.

None of them seem to return useful data for this purpose - possibly because the DMA is continually re-using the old buffers.

Thanks in anticipation.

Posts: 1
Joined: Fri Mar 05, 2021 12:54 am

Re: I2S - detect empty DMA queue

Postby KickAss101 » Fri Mar 05, 2021 1:17 am

Hi palmerr23, I'm a newbie too.

I'm having a similar problem, I want to trigger a wave sample every now and then. It's a random pulse generator. I want the I2S to stop whenever the buffer's empty. However, as you said, the I2S IDF driver hijacks the I2S interrupts to keep refilling the buffer. I tried to reallocate as a shared interrupt (ESP_INTR_FLAG_SHARED), i.e.

Code: Select all

i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
esp_intr_alloc(ETS_I2S0_INTR_SOURCE, flags, i2sInterrupt, NULL, &my_interrupt_handle);
but it didn't work. So I don't think there's a way to listen to the I2S interrupts without rewriting a version the IDF I2S library itself. However the code seems complex, since it has to play nice with the RTOS functions. It would need some patience to do that.

I found an article that does it for the ADC:

Posts: 2
Joined: Mon Feb 22, 2021 3:59 am

Re: I2S - detect empty DMA queue

Postby palmerr23 » Fri Mar 05, 2021 10:49 pm


I solved the problem in a different way.

When installing the I2S driver, there's an option to create an event queue, which flags TX_DONE (or RX_DONE).

I then created an xTask that sampled the queue, processing an I2S buffer each time the queue indicated one was completed.

To make sure I wasn't waiting for a buffer to clear, I measure the time it takes to write an I2S buffer. If it's longer than 50uS, then I've had to wait for I2S, so I skip the next TX_DONE flag.

It seems to work OK.

Hope this helps you.


Code: Select all

static QueueHandle_t i2s_event_queue;
i2s_driver_install(i2s_num, &i2s_config, I2S_Q_LEN, &i2s_event_queue);
 xret = xTaskCreate(I2Sout, "I2Sout", 20000, NULL, 1, &I2StaskHandle); 

void I2Sout(void * params)
     TXdoneEvent = false;
     qCnt = 0;
     do // wait on I2S event queue until a TX_DONE is found
        retv = xQueueReceive(i2s_event_queue, &i2s_evt, 1);  // don't let this block for long, as we check for the queue stalling
        if ((retv == pdPASS) && (i2s_evt.type == I2S_EVENT_TX_DONE)) //I2S DMA finish sent 1 buffer
          TXdoneEvent = true;
        vTaskDelay(1); // make sure there's time for some other processing if we need to wait!
     } while (retv == pdPASS);
 lastWrite = micros();
              ESP_ERROR_CHECK(i2s_write(i2s_num, (const void*)&inBuf[thisBuf].data, LBUFSIZ*sizeof(uint16_t), &bytesOut, portMAX_DELAY ));          
            lastWrite = micros() - lastWrite; // used to determine when to drop the next packet - I2Swrite had to wait for a buffer to empty

Who is online

Users browsing this forum: william.ferguson.au and 46 guests