ESP32 :: I2S DMA interrupt IN_SUC_EOF problem

code4sex
Posts: 20
Joined: Tue Mar 16, 2021 12:23 pm

ESP32 :: I2S DMA interrupt IN_SUC_EOF problem

Postby code4sex » Mon Sep 18, 2023 1:11 pm

Hi everyone,

I'd like to know what is wrong with my approach.

Goal: getting 16384 bytes (16K) buffer filled by camera via I2S->FIFO->DMA
Platform: ESP32 and IDF 5.0.1, FreeRTOS
Problem: can't get IN_SUC_EOF interrupt triggered.

General approach:
After configuring all the I2S/FIFO/DMA control registers and prior to starting off I perfrom the following key steps:

Setting up all 5 in_link descriptors for the maximum size of 4092 bytes:
- in_link[0].length = 4092
- in_link[1].length = 4092
- in_link[2].length = 4092
- in_link[3].length = 4092
- in_link[4].length = 16

FIFO is also configured to a maximum of 64 words (256 bytes):
- I2S0.fifo_conf.rx_data_num = 0b111111

I tell the DMA to read the total of 16384 bytes and fire the IN_SUC_EOF interrupt:
- I2S0.rx_eof_num = 16384
- I2S0.int_ena.in_suc_eof = 1
- in_link[0].eof = 0
- in_link[1].eof = 0
- in_link[2].eof = 0
- in_link[3].eof = 0
- in_link[4].eof = 1

Next, I start off DMA/in_link/FIFO and allow I2S interrupts:
- I2S0.in_link.start = 1
- I2S0.conf.rx_start = 1
- I2S0.fifo_conf.dscr_en = 1
- esp_intr_enable(i2s_handle)

After that the transmission looks to start as I can get all 5 IN_DONE interrupts if I set them appropriately.
However, IN_SUC_EOF interrupt won't ever get fired. I can't figure out why this is happening...
I haven't looked into the data being received yet and cannot say whether I'm receiving garbage or not. Currently, I'm stuck with the interrupt.

Oh yes, IDF is also configured to put I2S stuff into IRAM.

PS. Also, I've noticed a wierd change in "in_link[4].value" AFTER in_link[4] has been processed (IN_DONE triggered): in_link[4].value = 4092 instead of 16! Anyways, it might be an intended behaviour of I2S in_link internals whatsoever... So I don't actually care.

So the question is: how do you get IN_SUC_EOF triggered in this case? Am I missing any key step?

MicroController
Posts: 1708
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: ESP32 :: I2S DMA interrupt IN_SUC_EOF problem

Postby MicroController » Mon Sep 18, 2023 1:42 pm

Did you set the 'owner' of the DMA descriptors to 1 = 'DMA controller'?

code4sex
Posts: 20
Joined: Tue Mar 16, 2021 12:23 pm

Re: ESP32 :: I2S DMA interrupt IN_SUC_EOF problem

Postby code4sex » Mon Sep 18, 2023 2:54 pm

Yes, sure.

Below is my sample debug function to fill-in all the links and its output:

00. [ addr: 3FFB13BC ] bytes: 4092 / 4092, buf = 3FFB13F8, next_addr = 3FFB13C8 EOF = 0
01. [ addr: 3FFB13C8 ] bytes: 4092 / 4092, buf = 3FFB23F4, next_addr = 3FFB13D4 EOF = 0
02. [ addr: 3FFB13D4 ] bytes: 4092 / 4092, buf = 3FFB43EC, next_addr = 3FFB13E0 EOF = 0
03. [ addr: 3FFB13E0 ] bytes: 4092 / 4092, buf = 3FFB73E0, next_addr = 3FFB13EC EOF = 0
04. [ addr: 3FFB13EC ] bytes: 16 / 4092, buf = 3FFBB3D0, next_addr = 00000000 EOF = 1


Filler function:

Code: Select all

uint8_t dma_links_init(bool printInfo, lldesc_t *ptrLink, uint32_t *dma_buf32){
	int32_t	total_bytes = DMA_JUNK_BUFFER_MAX_BYTES; // defined as 16384 bytes
	uint8_t i=0;

	do{
		total_bytes -= DMA_LINK_DATA_MAX_BYTES; // defined as 4092

		ptrLink->owner = 1;	// = DMA
		ptrLink->eof = (total_bytes<=0)? 1 : 0;
		ptrLink->sosf = 0;
		ptrLink->offset = 0;
		ptrLink->size = DMA_LINK_DATA_MAX_BYTES;
		ptrLink->length = (total_bytes>=0)? DMA_LINK_DATA_MAX_BYTES : (DMA_LINK_DATA_MAX_BYTES + total_bytes);
		ptrLink->buf = (const volatile uint8_t*) dma_buf32;
		ptrLink->empty = (uint32_t) ((total_bytes <= 0)? 0 : (ptrLink + 1));
		if(printInfo){
			printf("%02d. [ addr: %08lX ]	bytes: %d / %d, 	buf = %08lX,	next_addr = %08lX	EOF = %d\n",
				i,
				(uint32_t)ptrLink,
				ptrLink->length,
				ptrLink->size,
				(uint32_t)ptrLink->buf,
				(uint32_t)(ptrLink->empty),
				ptrLink->eof);
			fflush(0);
			}
		i++;
		dma_buf32 += (i*DMA_LINK_DATA_MAX_BYTES/4);
		ptrLink++;
		}while(total_bytes > 0);
	return i;
}

And if it might matter below is the init code for the I2S peripheral:

Code: Select all

    periph_module_enable(PERIPH_I2S0_MODULE);

	// Module Reset = TRM p.312 "In order to finish a reset operation, the corresponding bit should be set and then cleared by software."
	I2S0.conf.val |= (BIT_RX_RESET | BIT_RX_FIFO_RESET); // = 0b1 | 0b100
	I2S0.conf.val &= ~(BIT_RX_RESET | BIT_RX_FIFO_RESET);
	I2S0.lc_conf.val |= (BIT_DMA_FSM_IN_RESET | BIT_DMA_AHBM_FIFO_RESET | BIT_DMA_ABM_RESET); // = 0b1 | 0b100 | 0b1000
	I2S0.lc_conf.val &= ~(BIT_DMA_FSM_IN_RESET | BIT_DMA_AHBM_FIFO_RESET | BIT_DMA_ABM_RESET);

	I2S0.conf.rx_slave_mod = 1;
	I2S0.conf.rx_right_first = 0;
	I2S0.conf.rx_msb_right = 0;
	I2S0.conf.rx_msb_shift = 0; 
	I2S0.conf.rx_mono = 0;
	I2S0.conf.rx_short_sync = 0;

	I2S0.conf2.lcd_en = 1;
	I2S0.conf2.camera_en = 1;

	I2S0.clkm_conf.clkm_div_a = 0;
	I2S0.clkm_conf.clkm_div_b = 0;
	I2S0.clkm_conf.clkm_div_num = 2;

	I2S0.fifo_conf.dscr_en = 1;	
	I2S0.fifo_conf.rx_fifo_mod = SM_0A00_0B00;
	I2S0.fifo_conf.rx_fifo_mod_force_en = 1;
	I2S0.fifo_conf.rx_data_num = 0b111111;

	I2S0.conf_chan.rx_chan_mod = 1;

	I2S0.sample_rate_conf.rx_bits_mod = 0;
	I2S0.timing.val = 0;
    	I2S0.timing.rx_dsync_sw = 1;

	I2S0.int_ena.val = 0;
	I2S0.int_clr.val = I2S0.int_raw.val;

	I2S0.in_link.addr = ((uint32_t)(&dma_links)) & 0b11111111111111111111; // 20-bit length. just to look fancy :)
	I2S0.rx_eof_num = DMA_JUNK_BUFFER_MAX_BYTES; // = 16834

	esp_intr_enable(hCam->hIntr);

* * *

Finally, I enable GPIO interrupt to get a VSYNC signal and effectively launch the whole system:

Code: Select all

void IRAM_ATTR I2S_Vsync_ISR(){
	I2S0.int_clr.in_suc_eof = 1;
	I2S0.int_ena.in_suc_eof = 1;

// I2S0.int_clr.in_done = 1; // for testing and debugging purposes
// I2S0.int_ena.in_done = 1; // for testing and debugging purposes

	// here's an optional call to set all the link descriptors any other call (in case the content DOES CHANGE; still to be examined) 
	dma_links_init(0, (lldesc_t*)&dma_links, (uint32_t*)&dma_junk_buf);

	// start off DMA
	I2S0.in_link.start = 1;
	I2S0.conf.rx_start = 1;

	gpio_intr_disable(hCam->pin_vsync); // self-disabling
}

The interrupt handler for I2S is responsible to catch I2S interrupts and post a message to the the worker-task:

Code: Select all

void IRAM_ATTR I2S_ISR(){
	BaseType_t Woken = 0;
	uint32_t msg = MSG_DMA_IN_SUC_EOF;

	// NOTE: the int flag is cleared only on successful QueueSend() so that in case of failure (queue is full) ESP32 re-enters the handler again:
	if(xQueueSendFromISR(hCam->hQueue, &msg, &Woken) == pdTRUE) I2S0.int_clr.in_suc_eof = 1;
	else{
		// perform some termination stuff
		// ..
		ESP_DRAM_LOGE("in_suc_eof", "ERROR: xQueueSendFromISR() failed\n"); // just for debugging purposes
		}
	if(Woken) portYIELD_FROM_ISR();
}

And the worker-task waits for the message. But it fails for some reason...
However, IN_DONE interrupts are received...

User avatar
ok-home
Posts: 78
Joined: Sun May 02, 2021 7:23 pm
Location: Russia Novosibirsk
Contact:

Re: ESP32 :: I2S DMA interrupt IN_SUC_EOF problem

Postby ok-home » Mon Sep 18, 2023 4:27 pm

hi
change
I2S0.rx_eof_num = 16384
to
I2S0.rx_eof_num = 16384 / sizeof(uint32_t); // count in 32 bit word

from esp32 referense manual(page 124):
However, unlike the SPI DMA channels, the data size for a single transfer is one word, or four bytes.
REG_I2S_RX_EOF_NUM[31:0] bit in I2S_RXEOF_NUM_REG is used for configuring the data size of a single transfer operation, in multiples of one word.

code4sex
Posts: 20
Joined: Tue Mar 16, 2021 12:23 pm

Re: ESP32 :: I2S DMA interrupt IN_SUC_EOF problem

Postby code4sex » Mon Sep 18, 2023 4:46 pm

Oopppsss....
I've read that section for bazillion of times and knew that quite well! And the trick was that I took that "4" divisor in the popup window for this very thing whereas it stands for another purpose!
error.jpg
error.jpg (207.4 KiB) Viewed 1367 times

Right you are, the correct value would be:

I2S0.rx_eof_num = DMA_JUNK_BUFFER_MAX_BYTES/4;

It was really my blurred eye :(((((
THANK YOU SO MUCH FOR TAKING ME OUT OF THIS HELL :))))))))) !!!!!!!

code4sex
Posts: 20
Joined: Tue Mar 16, 2021 12:23 pm

Re: ESP32 :: I2S DMA interrupt IN_SUC_EOF problem

Postby code4sex » Tue Sep 19, 2023 4:53 pm

FYI:
If continuous read operations of constant length are expected then it's a good idea to configure link descriptors once only!
The only field will change is the in_link.owner - this bit is set to NULL once DMA has processed each link. Found this while fiddling with DMA...

Who is online

Users browsing this forum: Google [Bot] and 116 guests