Sample I2S audio in an ISR

User avatar
qcarver
Posts: 2
Joined: Tue Jun 12, 2018 5:16 pm

Sample I2S audio in an ISR

Postby qcarver » Fri Mar 29, 2019 9:09 pm

Hello,

Sampling I2S audio from an ics43434 is trivial enough polling away using i2S_read to a buffer w/in a while loop. However, I am surprised to see that I have been unsuccessful sampling i2s audio using an ISR. Furthermore, I see no examples of doing this on the internet! :shock: To be clear, I mean setting up a I2S(1 or 0) as Master to receive on various GPIO pins then scheduling and ISR (eg: w/ esp_intr_alloc) and finally -- DOING THE i2s_read IN THE ISR!

I've cobbled together some code that looks like it should work, but I hit some stubborn points using esp_intr_alloc. I humbly post the following for your consideration:

Code: Select all

#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "ics43434.h"
#include "buffer.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/i2s.h"
#include <errno.h>

/** ISR **/
static void IRAM_ATTR dmaInt(void* arg) {
	uint32_t numBytesRead = 0;
	//this is an isr, there should be a i2s_event ready for us, so DON'T WAIT!
	if (i2s_read(I2S_NUM_1, Buffer::getCurrent(), DMA_SIZE,
			&numBytesRead, 0) == ESP_OK) {
		if (numBytesRead == DMA_SIZE) Buffer::advance();
	};
	//PS: OH BY THE WAY... how do we qualify that we are interrupted for a read??? can we cast "arg"?
}

//anonymous
namespace {
intr_handle_t i2s_interrupt;

static const char *TAG = "ics4343.cpp";
/* RX: I2S_NUM_1 */
#define I2S_CLOCK GPIO_NUM_25
#define I2S_WS GPIO_NUM_26
#define I2S_DIN GPIO_NUM_22

//incase the issue has something to do with DACs I tried other pins too eg:
//#define I2S_CLOCK GPIO_NUM_18
//#define I2S_WS GPIO_NUM_19
//#define I2S_DIN GPIO_NUM_23
//the results were the same

i2s_config_t i2s_config_rx;
i2s_pin_config_t pin_config_rx = { bck_io_num: I2S_CLOCK, ws_io_num: I2S_WS,
		data_out_num : I2S_PIN_NO_CHANGE, data_in_num: GPIO_NUM_22 };

void setupInterrupt() {
	esp_err_t err = esp_intr_alloc(ETS_I2S1_INTR_SOURCE,
	ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM,
			&dmaInt,
			NULL, &i2s_interrupt);
	if (err != ESP_OK)
		ESP_LOGE(TAG, "Error esp_intr_alloc %s", esp_err_to_name(err));
}
}

bool Ics43434::setup() {
	//stuff the configuration
	i2s_config_rx.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX);
	i2s_config_rx.sample_rate = AUDIO_SAMPLE_RATE;
	i2s_config_rx.bits_per_sample = I2S_BITS_PER_SAMPLE_24BIT;
	i2s_config_rx.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;   // 2-channels
	i2s_config_rx.communication_format =
			(i2s_comm_format_t) (I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_LSB);
	i2s_config_rx.intr_alloc_flags = (ESP_INTR_FLAG_LEVEL1
			| ESP_INTR_FLAG_SHARED);	// Interrupt level 1
	i2s_config_rx.dma_buf_count = 2;		// number of DMA buffers, 128 max.
	i2s_config_rx.dma_buf_len = AUDIO_SAMPLES_PER_DMA; // pretty sure .. this is # of elements, not sizeof
	i2s_config_rx.use_apll = false;	// use APLL as main I2S clock ... less accurate :(
	i2s_config_rx.fixed_mclk = false;				// not using MCLK output
	//mic stuff
	ESP_LOGV(TAG, "Using I2S peripheral");
	i2s_driver_install(I2S_NUM_1, &i2s_config_rx, 0, NULL);
	if (GPIO_IS_VALID_GPIO(I2S_WS)&&GPIO_IS_VALID_GPIO(I2S_CLOCK)
			&& GPIO_IS_VALID_GPIO(I2S_DIN)) {
		ESP_LOGI(TAG, "gpio pins look valid");
		i2s_set_pin(I2S_NUM_1, &pin_config_rx);
		i2s_zero_dma_buffer(I2S_NUM_1);
	} else ESP_LOGE(TAG, "gpio pins aren't valid");
	setupInterrupt();
	return true;
}

bool Ics43434::startRecording() {
	bool rv = true;
	//enable the interrupt and away we go
	esp_err_t err = esp_intr_enable(i2s_interrupt);
	if (err != ESP_OK)
		ESP_LOGE(TAG, "error in esp_intr_enable: %s", esp_err_to_name(err));
	return rv;
}

bool Ics43434::stopRecording() {
	esp_intr_disable(i2s_interrupt);
	return true;
}
So what happens? Output>
V (281) ics4343.cpp: Using I2S peripheral
I (291) ics4343.cpp: gpio pins look valid
E (291) ics4343.cpp: Error esp_intr_alloc ESP_ERR_NOT_FOUND
I (301) main.cpp: : call startRecording
E (301) ics4343.cpp: error in esp_intr_enable: ESP_ERR_INVALID_ARG
According to the docs ESP_ERR_NOT_FOUND above means:
No free interrupt found with the specified flags

Okay, so what interrupt should I be using? I have also tried or'ing in ESP_INTR_FLAG_SHARED

Thx,
QC

*I am using the esp32_win32_msys2_environment_and_toolchain-20170918 on Windows (mingw32).

ESP_Sprite
Posts: 9766
Joined: Thu Nov 26, 2015 4:08 am

Re: Sample I2S audio in an ISR

Postby ESP_Sprite » Sat Mar 30, 2019 7:35 am

Effectively, FreeRTOS does not allow you do to blocking operations in an ISR. Practically, this means more-or-less only functions specifically marked for it (like FreeRTOS *FromISR() functions) are allowed. The way to do this is to create a task that blocks on e.g. a semaphore before reading from I2S, then using the interrupt to give that semaphore.

User avatar
qcarver
Posts: 2
Joined: Tue Jun 12, 2018 5:16 pm

Re: Sample I2S audio in an ISR

Postby qcarver » Sat Mar 30, 2019 3:26 pm

Sprite, thx, I learned something from your response. However, wouldn't such a semaphore be given in a custom ISR? If so, I'm back to the original problem. That is: How do I schedule the isr? (I still return ESP_ERR_NOT_FOUND from esp_i2s_alloc?).

I am seeing that a default ISR is created on i2s_driver install (see line 478 of esp-idf/components/driver/i2s.c). Am I too understand that I should block waiting for the xQueueSendFromISR in that handler? I suppose I would pass the q handle in with the i2s_config struct. Is this what you mean by semaphore?

Still wierded out that I can schedule an isr :roll: . -- /w Thanks, QC

ESP_Sprite
Posts: 9766
Joined: Thu Nov 26, 2015 4:08 am

Re: Sample I2S audio in an ISR

Postby ESP_Sprite » Mon Apr 01, 2019 1:37 am

Wait, I just re-read your start post, and I'm confused, as I thought you wanted to kick off an I2S transaction from an interrupt or so.

You say you want to use the ISR to read the I2S data, I presume so the CPU can do other things? If so, you may be misunderstanding the I2S driver: reading from it is not polling but blocking. The difference is that while polling implies a CPU spinning for a transfer to complete, a blocking transfer causes the OS to de-schedule the task until something happens. The way you would normally use I2S when you want to have other things happening as well, is to dedicate a task to the I2S reading and other tasks to other things. When written correctly, the OS will make sure all tasks get CPU time and none have to spend their time doing polling.

WiFive
Posts: 3529
Joined: Tue Dec 01, 2015 7:35 am

Re: Sample I2S audio in an ISR

Postby WiFive » Mon Apr 01, 2019 1:53 am

In the driver there is no yielding to a blocked read task from the isr when a dma buffer is ready to read so it is limited by freertos scheduler.

ESP_Sprite
Posts: 9766
Joined: Thu Nov 26, 2015 4:08 am

Re: Sample I2S audio in an ISR

Postby ESP_Sprite » Wed Apr 03, 2019 4:31 am

WiFive: Where did you get that idea? https://github.com/espressif/esp-idf/bl ... i2s.c#L549 has the yield, the high_priority_task_awoken is also used in the queue functions that push the dma descriptors to the tasks.

WiFive
Posts: 3529
Joined: Tue Dec 01, 2015 7:35 am

Re: Sample I2S audio in an ISR

Postby WiFive » Wed Apr 03, 2019 5:36 am

ESP_Sprite wrote:
Wed Apr 03, 2019 4:31 am
WiFive: Where did you get that idea? https://github.com/espressif/esp-idf/bl ... i2s.c#L549 has the yield, the high_priority_task_awoken is also used in the queue functions that push the dma descriptors to the tasks.
That is only for in_suc_eof interrupt https://github.com/espressif/esp-idf/bl ... i2s.c#L534

in_done interrupt is not even enabled

Maybe I am misunderstanding and eof occurs for every buffer which makes it the same as in_done?

ESP_Sprite
Posts: 9766
Joined: Thu Nov 26, 2015 4:08 am

Re: Sample I2S audio in an ISR

Postby ESP_Sprite » Wed Apr 03, 2019 8:00 am

Yeah, that's a bit non-obvious, but the EOF bit is more-or-less used as an indicator that the I2S peripheral should generate an interrupt at that point, so the software can grab the filled/emptied DMA buffer and refill/read it. The in_done is never enabled because the I2S driver has a streaming model, as in: as soon as you start streaming, you do that for an indefinite time; iirc in_done happens when streaming stops.

WiFive
Posts: 3529
Joined: Tue Dec 01, 2015 7:35 am

Re: Sample I2S audio in an ISR

Postby WiFive » Wed Apr 03, 2019 9:00 am

Yes it is confusing because
• I2S_IN_SUC_EOF_INT: Triggered when all data have been received.
• I2S_IN_DONE_INT: Triggered when the current rxlink descriptor is handled.
I2S_IN_EOF_DES_ADDR_REG The address of receive link descriptor producing EOF
I2S_RXEOF_NUM_REG The length of the data to be received. It will trigger I2S_IN_SUC_EOF_INT.
I2S_IN_EOF_DES_ADDR_REG The address of receive link descriptor producing EOF

Who is online

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