I2S - detect empty DMA queue

palmerr23
Posts: 4
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.

udpIn.onPacket(UDPtoQ)

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.

KickAss101
Posts: 3
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);
int flags = ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM ;
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:
https://www.toptal.com/embedded/esp32-audio-sampling

palmerr23
Posts: 4
Joined: Mon Feb 22, 2021 3:59 am

Re: I2S - detect empty DMA queue

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

KA101,

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.

Richard
---------------

Code: Select all

setup(){
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)
{
while(1)
   {
     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;
          qCnt++;
          break;
        }
        vTaskDelay(1); // make sure there's time for some other processing if we need to wait!
     } while (retv == pdPASS);
  if(TXdoneEvent) 
     { 
 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
     } 
  }   
}

KickAss101
Posts: 3
Joined: Fri Mar 05, 2021 12:54 am

Re: I2S - detect empty DMA queue

Postby KickAss101 » Wed Apr 28, 2021 11:35 pm

Thanks palmerr23! I didn't think a task could solve my problem. I managed to generate exactly a single wave form in these conditions:

On the I2S configuration, I created 4 buffers to match the exact data length:

Code: Select all

#define NUM_SAMPLES 160
#define DMA_BUF_CNT 4
#define DMA_BUF_LEN (NUM_SAMPLES / DMA_BUF_CNT)
// Note that DMA_BUF_CNT x DMA_BUF_LEN = NUM_SAMPLES
uint32_t buf[NUM_SAMPLES];
volatile bool pulseOn = false;
volatile uint32_t txEvents = 0;

i2s_config_t i2s_config = {
	.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
	.sample_rate = 100000,
	.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
	.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
	.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB,
	.intr_alloc_flags = 0,
	.dma_buf_count = DMA_BUF_CNT,
	.dma_buf_len = DMA_BUF_LEN,
	.use_apll = 0
};
This is the code to generate the pulse wave form:

Code: Select all

void sendPulse() {
	size_t bytesWritten;
	i2s_write(I2S_NUM, buf, sizeof(buf), &bytesWritten, portMAX_DELAY);
	txEvents = 0;
	pulseOn = true;
	digitalWrite(LED_BUILTIN, HIGH);
}
When half the buffers are sent (don't know why), I ask to clear the DMA buffers. If I wait more or less TX_EVENTs, the pulse appears glitched. Here's the task handler function:

Code: Select all

void i2sTask(void*) {
	while (true) {
		i2s_event_t event;
		// Wait indefinitely for a new message in the queue
		if (xQueueReceive(i2s_queue, &event, portMAX_DELAY) == pdTRUE) {
			if (pulseOn and event.type == I2S_EVENT_TX_DONE) {
				if (++txEvents == DMA_BUF_CNT/2) {
					// Stop the pulse
					pulseOn = false;
					i2s_zero_dma_buffer(I2S_NUM);
					digitalWrite(LED_BUILTIN, LOW);
				}
			}
		}
	}
}
Maybe you can use this strategy to avoid counting time?

Who is online

Users browsing this forum: username and 83 guests