I2S non blocking readings.

esp_man
Posts: 25
Joined: Tue Mar 21, 2023 12:04 pm

I2S non blocking readings.

Postby esp_man » Thu Oct 02, 2025 2:31 pm

Hello.
I have huge problems with I2S in ESP32, both in "driver/I2S.h" or "ESP_I2S.h" library.
I need something looks simple: receiving samples in master mode using pooling in main loop, but without blocking.
But examples from internet not work as I expect.
For example, the simple code:

Code: Select all

#include <driver/i2s.h>

#define I2S_PORT I2S_NUM_0

// Define input buffer length
#define bufferLen 512
int16_t sBuffer[bufferLen];

void i2s_install()
{
  // Set up I2S Processor configuration
  const i2s_config_t i2s_config =
    {
    .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 8000,
    .bits_per_sample = i2s_bits_per_sample_t(16),
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
    .intr_alloc_flags = 0,
    .dma_buf_count = 4,
    .dma_buf_len = bufferLen,
    .use_apll = false
    };

  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
}

void i2s_setpin()
{
  // Set I2S pin configuration
  const i2s_pin_config_t pin_config =
    {
    //.mck_io_num = 0,
    .bck_io_num = 4,
    .ws_io_num = 5,
    .data_out_num = 15,
    .data_in_num = 2
    };

  i2s_set_pin(I2S_PORT, &pin_config);
}

void setup()
{
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);

  Serial.begin(115200, SERIAL_8N1);

  // Set up I2S
  i2s_install();
  i2s_setpin();
  i2s_start(I2S_PORT);
}

void loop()
{
  digitalWrite(16, HIGH);
  size_t bytesIn = 0;
  //esp_err_t result = i2s_read(I2S_PORT, &sBuffer, bufferLen, &bytesIn, portMAX_DELAY);
  //esp_err_t result = i2s_read(I2S_PORT, &sBuffer, bufferLen, &bytesIn, 2);
  esp_err_t result = i2s_read(I2S_PORT, &sBuffer, 128, &bytesIn, 2);
  digitalWrite(16, LOW);

  if (result == ESP_OK)
    {
    Serial.println(bytesIn);
    }
  else
    {
    Serial.println("_");
    }
  
  //delay(20);
}
I expect to read 1/4 of buffer size, to have margin (or reading small amounts of data, because of timeout set to 2).
Serial.println("_") are never executed.
But in UART console I see something strange:
0
0
0
0
0
128
128
128
128
128
128
128
128
0
0
0
0
0
0
0
Also, oscilloscope on GPIO16, confirms that readings are made in bursts.
I expected something else. Readings of 128 bytes in equal timestamps:
0
0
0
128
0
0
0
0
128
0
0
0
0
or readings more or less random amount of data, but in that amount that sums to 8000 stereo samples in every second.
33
34
44
22
22
56
But I see something else. Bursts of 128 bytes readings, with even not sum to 8000 in second (more precisely, to 8000 * 4 bytes per stereo sample per second)
Why, and how I can repair this?

lbernstone
Posts: 1131
Joined: Mon Jul 22, 2019 3:20 pm

Re: I2S non blocking readings.

Postby lbernstone » Thu Oct 02, 2025 5:22 pm

There is an OS running, so you cannot count on the timing being the same for every loop cycle. There is a new version of the I2S driver which has an interrupt/callback on RX. You should always be looking to make things asynchronous anyhow. Some of the notes about timing and DMA for the new driver should apply to the legacy version, as well.

esp_man
Posts: 25
Joined: Tue Mar 21, 2023 12:04 pm

Re: I2S non blocking readings.

Postby esp_man » Fri Oct 03, 2025 6:47 am

No, no, no. My requirements are very modest. I have a very low sampling frequency of 8000 Hz.
The buffer is 512 B size, but I want to read it when it reaches 128 B. That is, when there is still 384 B of free space left, so plenty of time to read those 128 B before the buffer overflows.
There should be no performance problem here. I have other codes (not based on I2S) that in the main loop and polling achieve much more. Something is wrong with this I2S code. Either there is an issue with the I2S library, or I simply don't understand it correctly and am using it improperly.
Unfortunately, I can't easily switch to ESP-IDF 5.5 because I'm using Arduino. I have the ESP Arduino library version 3.3.1, which is based on ESP-IDF 4.4.2 probably.

Sprite
Espressif staff
Espressif staff
Posts: 10599
Joined: Thu Nov 26, 2015 4:08 am

Re: I2S non blocking readings.

Postby Sprite » Fri Oct 03, 2025 8:56 am

No, no, no. My requirements are very modest. I have a very low sampling frequency of 8000 Hz.
The buffer is 512 B size, but I want to read it when it reaches 128 B. That is, when there is still 384 B of free space left, so plenty of time to read those 128 B before the buffer overflows.
That's not what you're specifying.

Code: Select all

#define bufferLen 512
    .dma_buf_count = 4,
    .dma_buf_len = bufferLen,
This means you allocate 4 buffers of 512 bytes each. Note that a buffer is only available for reading if it's entirely filled by the DMA subsystem, not earlier.

Code: Select all

  esp_err_t result = i2s_read(I2S_PORT, &sBuffer, 128, &bytesIn, 2);
The data size here is in bytes, so you're reading 128 bytes at a time. In other words, as soon as DMA returns a 512-byte buffer, you will see 4 reads succeeding in quick succession. (Looks like you're seeing 8 in your example, that may be a glitch or some sort)

esp_man
Posts: 25
Joined: Tue Mar 21, 2023 12:04 pm

Re: I2S non blocking readings.

Postby esp_man » Mon Oct 06, 2025 8:22 am

This means you allocate 4 buffers of 512 bytes each. Note that a buffer is only available for reading if it's entirely filled by the DMA subsystem, not earlier.
Thank you for the explanation.
I misunderstood the meaning of this parameter. I thought it was the length of the DMA single transfer word.
However, after the corrections, I still have strange behavior.
This is my current code:

Code: Select all

#include <driver/i2s.h>

#define I2S_PORT I2S_NUM_0

// Define input buffer length
#define bufferLen 512
int16_t sBuffer[256];    //256 x int16 = 512 Bytes

void i2s_install()
{
  // Set up I2S Processor configuration
  const i2s_config_t i2s_config =
    {
    .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 8000,
    .bits_per_sample = i2s_bits_per_sample_t(16),
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
    .intr_alloc_flags = 0,
    .dma_buf_count = 4,
    .dma_buf_len = bufferLen,
    .use_apll = false
    };

  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
}

void i2s_setpin()
{
  // Set I2S pin configuration
  const i2s_pin_config_t pin_config =
    {
    //.mck_io_num = 0,
    .bck_io_num = 4,
    .ws_io_num = 5,
    .data_out_num = 15,
    .data_in_num = 2
    };

  i2s_set_pin(I2S_PORT, &pin_config);
}

void setup()
{
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);

  Serial.begin(115200, SERIAL_8N1);

  // Set up I2S
  i2s_install();
  i2s_setpin();
  i2s_start(I2S_PORT);
}

void loop()
{
  digitalWrite(16, HIGH);
  size_t bytesIn = 0;
  //esp_err_t result = i2s_read(I2S_PORT, &sBuffer, 512, &bytesIn, portMAX_DELAY);    //<------------ version 1
  esp_err_t result = i2s_read(I2S_PORT, &sBuffer, 512, &bytesIn, 4);    //<------------ version 2
  digitalWrite(16, LOW);

  if (result == ESP_OK)
    {
    Serial.println(bytesIn);
    }
  else
    {
    Serial.println("_");
    }
  
  //delay(20);
}
I'm testing version 1 or version 2.
They work a bit differently, but they have the same problem.
Both version does not read 512 B every 16 ms. Instead, it reads in packets of 4 reads of 512 B every 64 ms.
That is, the reading is only performed once when all four 512 B buffers are full, which risks data loss.
The only difference between "version 1" and "version 2" is that "version 2" has additional "zero readings" due to the timeout setting being set to 4 instead of portMAX_DELAY. Version 2 stalls in read function for the entire duration of the read (64 ms period, for four 512 B buffers), while "version 2" exits after 4 ms and giving "0" on UART console. But it is not a problem: it directly results from the code (I am simply testing various versions). The real issue is that these codes do not want to read a single 512 B buffer when it is available.
Is there anything that can be done about this? Without using IDF 5.5.1 and without RX interrupt?

esp_man
Posts: 25
Joined: Tue Mar 21, 2023 12:04 pm

Re: I2S non blocking readings.

Postby esp_man » Thu Oct 09, 2025 12:08 pm

Just a few hours ago, the new Arduino ESP32 library, based on based on ESP-IDF v5.5.1, was released:
https://newreleases.io/project/github/e ... ease/3.3.2
I installed it immediately, and testing my program on in right now.

UPDATE:
Now it works properly. It gives readings every ~16 ms, so at intervals of every 512 bytes = 1 DMA transfer.
Thanks for help!

Who is online

Users browsing this forum: Bing [Bot], ChatGPT-User, PerplexityBot, Qwantbot and 4 guests