I2s microphone sample rate seems halft as big as it's supposed to

millroad
Posts: 2
Joined: Sat Feb 25, 2023 6:57 pm

I2s microphone sample rate seems halft as big as it's supposed to

Postby millroad » Sat Feb 25, 2023 7:51 pm

I'm using an INMP441 i2s microphone (datasheet) and using the esp-dsp library and esp-idf 5.0.1 to create an fft series. I'm sampling at 10 240 Hz but I can't see any signals higher than about 2500 Hz, and the plot of the fft shows an alias around 2500 Hz as well. Is there some error in my i2s setup? Is using the microphone as mono making the sample rate half as big? The word select clock is supposed to be the same as the sample rate for non-PDM mode.

I played a tone of 2000 Hz and dumped the data to serial, then ran an fft in matlab. The mirroring around fs/2 is expected but not four lines, and anything above 2500 Hz disappears..
plot.png
plot.png (139.7 KiB) Viewed 4547 times
My i2s setup:

Code: Select all

void init_i2s(size_t sample_rate) {
    i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
    ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_chan));

    i2s_std_config_t rx_std_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg =
            {
                .mclk = I2S_GPIO_UNUSED,
                .bclk = SCK_PIN,
                .ws = WS_PIN,
                .dout = I2S_GPIO_UNUSED,
                .din = SD_PIN,
                .invert_flags =
                    {
                        .mclk_inv = false,
                        .bclk_inv = false,
                        .ws_inv = false,
                    },
            },
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &rx_std_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_chan));
}
Reading is done in a task that notifies a different task to copy the data, I've tried swapping the data as suggested by the i2s rx documentation but I saw no difference:

Code: Select all

static int16_t* read_buffer;
static size_t read_bytes = 0;

void sampler_run_task(void* param) {
    extern TaskHandle_t sample_buffer_task_handle;
    read_buffer = (int16_t*)calloc(samp->overlap, sizeof(int16_t));
    size_t buf_size = 1024;
    while (true) {
        ESP_ERROR_CHECK(i2s_channel_read(rx_chan, read_buffer, buf_size, &read_bytes, 1000));
        if (read_bytes > 0) {
            xTaskNotifyGive(sample_buffer_task_handle);  // copy samples to buffer
        }
    }
    free(read_buffer);
    vTaskDelete(NULL);
}

void copy_sample_buffer_task(void* param) {
    extern TaskHandle_t fft_task_handle;
    while (true) {
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);  // wait to be notified
        sampler_buffer_add(sample_buffer, read_buffer, read_bytes / sizeof(int16_t));
        xTaskNotifyGive(fft_task_handle);  // run fft
    }
}



void sampler_buffer_add(int16* buffer, int16_t* batch, size_t length) {
    // samples are in wrong order, swap every other
    // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#std-rx-mode
    
    // for (size_t i = 0; i < length; i += 2) {
    //     buffer[i + 0] = bswap16(batch[i + 1]);
    //     buffer[i + 1] = bswap16(batch[i]);
    // }
    
    for (size_t i = 0; i < length; i += 1) {
        // swap bytes to little endian
        buffer[i] = bswap16(batch[i]);
    }
}

millroad
Posts: 2
Joined: Sat Feb 25, 2023 6:57 pm

Re: I2s microphone sample rate seems halft as big as it's supposed to

Postby millroad » Sun Feb 26, 2023 8:25 pm

I figured out the issue, when setting the sampling bit width to I2S_DATA_BIT_WIDTH_16BIT esp-idf sets the word length to 16 bits as well, so a whole frame is just 32 bits, the i2s bckl was only 327 680 hz. I was expecting 64 bit clock cycles per frame, which would make the bckl 655360 hz. Just setting the .slot_cfg.ws_width = 32 didn't make a difference so I ended up sampling 32 bits of data instead, and throwing half of it away.

The INMP441 microphone requires 64 clock cycles per frame, is there any way of making esp-idf sampling just the first 16 bits of the left channel? What should the configuration be?

I don't think the documentation is very clear that the frame length is dependent on the data bit width. Analog devices writes in their guide on i2s:
The data word length is often 16, 24, or 32 bits. For word lengths less than 32 bits, the frame length is often still 64 bits and the unused bits are just driven low by the transmitting IC.
It seems resonable that the word length is mentioned somewhere, and how the data bit width affects it.

BarmyFluid
Posts: 2
Joined: Wed Aug 02, 2023 5:00 pm

Re: I2s microphone sample rate seems halft as big as it's supposed to

Postby BarmyFluid » Wed Aug 02, 2023 5:33 pm

Hi millroad,

I realise you've answered yourself but, I too, had many a moment getting my head around the issues when using the INMP441 microphone. Hoping my notes will help others too, I wish I'd seen your post before I embarked on my journey :lol:

In the end my approach was to configure the channel to have:
  • Data bit width of 24 bits
  • Slot bit width of 32 bits (slot_bit_width = I2S_SLOT_BIT_WIDTH_32BIT)
  • WS signal width of 32 bits
  • Bit shift enabled
  • Left alignment enabled
  • Bit clock line (bclk) inverted
Example code:

Code: Select all

i2s_std_config_t rx_std_cfg = {
        .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(16000),
        .slot_cfg = {
                .data_bit_width = I2S_DATA_BIT_WIDTH_24BIT,
                .slot_bit_width = I2S_SLOT_BIT_WIDTH_32BIT,
                .slot_mode = I2S_SLOT_MODE_MONO,
                .slot_mask = I2S_STD_SLOT_BOTH,
                .ws_width = 32,
                .ws_pol = false,
                .left_align = true,
                .big_endian = false,
                .bit_order_lsb = false,
                .bit_shift = true
        },
        .gpio_cfg = {
                .mclk = I2S_GPIO_UNUSED,
                .bclk = GPIO_NUM_4,
                .ws   = GPIO_NUM_3,
                .dout = I2S_GPIO_UNUSED,
                .din  = GPIO_NUM_2,
                .invert_flags = {
                        .mclk_inv = false,
                        .bclk_inv = true,
                        .ws_inv   = false,
                },
        },
};
Note I'm using ESP-IDF-V5.1 and, I believe, there have been a few differences in syntax but the same can be achieved from what I can tell in V4.4 etc.

So the data stream will include 6 bytes per frame (2 x 24-bit i.e. left and right). In my use-case I just read the first two bytes, 2s complement and discard the remaining bytes in the frame. The LSB is noise in my set-up and I need to pass to ESP-SR which requires a 16-bit feed anyway. The comments for the clock config says you should use multiples of 3 for 24-bit data, however as the INMP441 is 32 bit per slot, this doesn't apply and the default I2S_MCLK_MULTIPLE_256 works for me.

walinsky
Posts: 3
Joined: Wed Sep 20, 2023 2:42 pm

Re: I2s microphone sample rate seems halft as big as it's supposed to

Postby walinsky » Thu Sep 21, 2023 11:10 pm

Funny thing is, this has been hidden in plain sight for years...
viewtopic.php?f=13&t=1756&start=10#p9283

I'm using ESP-IDF5.1 as well, and wrote a piece of code that will output the input from my 2 microphones to an external DAC.

Code: Select all

#include <stdio.h>
#include <string.h>
#include "driver/i2s_std.h"
#include "esp_log.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/FreeRTOS.h"

#define BUFFER_SIZE 1024
#define TX_SAMPLE_SIZE (I2S_DATA_BIT_WIDTH_16BIT * BUFFER_SIZE)
#define RX_SAMPLE_SIZE (I2S_DATA_BIT_WIDTH_32BIT * BUFFER_SIZE)
#define MY_LOG_TAG "I2S_PA"

i2s_chan_handle_t tx_handle;
i2s_chan_handle_t rx_handle;

char i2s_rx_buff[RX_SAMPLE_SIZE];
char i2s_tx_buff[TX_SAMPLE_SIZE];
size_t bytes_read;
size_t bytes_written;

// This is our DAC
void init_tx_i2s_chan()
{
i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    i2s_new_channel(&tx_chan_cfg, &tx_handle, NULL);
    i2s_std_config_t std_tx_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000),
        .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = GPIO_NUM_26,
            .ws = GPIO_NUM_17,
            .dout = GPIO_NUM_25,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    /* Initialize the channel */
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_tx_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
}

// This is our 2 INMP441 mems microphones. 1 with l/r pin high; 1 low.
void init_rx_i2s_chan()
{
    /* RX channel will be registered on our second I2S (for now)*/
    i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);
    i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle);
    i2s_std_config_t std_rx_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000),
        .slot_cfg = {
            .slot_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
            .data_bit_width = I2S_SLOT_BIT_WIDTH_24BIT,
            .slot_mode = I2S_SLOT_MODE_STEREO,
            .slot_mask = I2S_STD_SLOT_BOTH,
            .ws_width = 32,
            .ws_pol = false,
            // left_align, big_endian and bit_order_lsb not supported on esp32 dev board
            // .left_align = true,
            // .big_endian = false,
            // .bit_order_lsb = false,
            .bit_shift = true, // Philips mode
        },
        .gpio_cfg = {
                .mclk = I2S_GPIO_UNUSED,
                .bclk = GPIO_NUM_16,
                .ws   = GPIO_NUM_27,
                .dout = I2S_GPIO_UNUSED,
                .din  = GPIO_NUM_14,
                .invert_flags = {
                        .mclk_inv = false,
                        .bclk_inv = true,
                        .ws_inv   = false,
                },
        },
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_rx_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
}

void app_main(void)
{
    init_tx_i2s_chan();
    init_rx_i2s_chan();

    while (true) {
        char *buf_ptr_read = i2s_rx_buff;
		char *buf_ptr_write = i2s_tx_buff;
        // Read the RAW samples from the microphone
        if (i2s_channel_read(rx_handle, i2s_rx_buff, RX_SAMPLE_SIZE, &bytes_read, 1000) == ESP_OK) {
            ESP_LOGI(MY_LOG_TAG, "bytes read: %d; raw buf size: %d; tx buf size: %d", bytes_read, sizeof(i2s_rx_buff) / sizeof(i2s_rx_buff[0]), sizeof(i2s_tx_buff) / sizeof(i2s_tx_buff[0]));
            int raw_samples_read = bytes_read / (I2S_DATA_BIT_WIDTH_32BIT / 8);
            ESP_LOGI(MY_LOG_TAG, "raw samples read: %d", raw_samples_read);
            for (int i = 0; i < raw_samples_read; i++)
            {
                // let's inspect our input once in a while
                if (i % 10000 == 0) { // the first sample of every new buffer
                    ESP_LOGI(MY_LOG_TAG, "rx packet looks like %04x %04x %04x %04x", i2s_rx_buff[i], i2s_rx_buff[i+1], i2s_rx_buff[i+2], i2s_rx_buff[i+3]);
                }
                
                // left
                buf_ptr_write[0] = buf_ptr_read[2]; // mid
                buf_ptr_write[1] = buf_ptr_read[3]; // high
                // right
                buf_ptr_write[2] = buf_ptr_read[6]; // mid
                buf_ptr_write[3] = buf_ptr_read[7]; // high

                buf_ptr_write += 2 * (I2S_DATA_BIT_WIDTH_16BIT / 8);
                buf_ptr_read += 2 * (I2S_DATA_BIT_WIDTH_32BIT / 8);
            }
            i2s_channel_write(tx_handle, (const char*)i2s_tx_buff, sizeof(i2s_tx_buff), &bytes_written, portMAX_DELAY);
        } else {
            ESP_LOGI(MY_LOG_TAG, "Read Failed!");
        }
    }
}
I get sound out of both speakers when making noise. But low and behold, when I whistle, the sound comes out a few octaves low.
Could you guys please elaborate and test if there's anything wrong with my code?

walinsky
Posts: 3
Joined: Wed Sep 20, 2023 2:42 pm

Re: I2s microphone sample rate seems halft as big as it's supposed to

Postby walinsky » Fri Sep 22, 2023 11:39 am

Ok. Found it.
By setting .slot_cfg.data_bit_width to 24 bit, I effectively decreased the pitch by 3/4.
You can simply set .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), like TS already mentioned.

Correct (tested) code for the Megaphone:

Code: Select all

#include <stdio.h>
#include <string.h>
#include "driver/i2s_std.h"
#include "esp_log.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/FreeRTOS.h"

#define BUFFER_SIZE 1024
#define TX_SAMPLE_SIZE (I2S_DATA_BIT_WIDTH_16BIT * BUFFER_SIZE)
#define RX_SAMPLE_SIZE (I2S_DATA_BIT_WIDTH_32BIT * BUFFER_SIZE)
#define MY_LOG_TAG "I2S_PA"

i2s_chan_handle_t tx_handle;
i2s_chan_handle_t rx_handle;

char i2s_rx_buff[RX_SAMPLE_SIZE];
char i2s_tx_buff[TX_SAMPLE_SIZE];
size_t bytes_read;
size_t bytes_written;

// This is our DAC
void init_tx_i2s_chan()
{
i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    i2s_new_channel(&tx_chan_cfg, &tx_handle, NULL);
    i2s_std_config_t std_tx_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000),
        .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = GPIO_NUM_26,
            .ws = GPIO_NUM_17,
            .dout = GPIO_NUM_25,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    /* Initialize the channel */
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_tx_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
}

// This is our 2 INMP441 mems microphones. 1 with l/r pin high; 1 low.
void init_rx_i2s_chan()
{
    /* RX channel will be registered on our second I2S (for now)*/
    i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);
    i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle);
    i2s_std_config_t std_rx_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
        .gpio_cfg = {
                .mclk = I2S_GPIO_UNUSED,
                .bclk = GPIO_NUM_16,
                .ws   = GPIO_NUM_27,
                .dout = I2S_GPIO_UNUSED,
                .din  = GPIO_NUM_14,
                .invert_flags = {
                        .mclk_inv = false,
                        .bclk_inv = true,
                        .ws_inv   = false,
                },
        },
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_rx_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
}

void app_main(void)
{
    init_tx_i2s_chan();
    init_rx_i2s_chan();

    while (true) {
        char *buf_ptr_read = i2s_rx_buff;
        char *buf_ptr_write = i2s_tx_buff;
        // Read the RAW samples from the microphone
        if (i2s_channel_read(rx_handle, i2s_rx_buff, RX_SAMPLE_SIZE, &bytes_read, 1000) == ESP_OK) {
            ESP_LOGI(MY_LOG_TAG, "bytes read: %d; raw buf size: %d; tx buf size: %d", bytes_read, sizeof(i2s_rx_buff) / sizeof(i2s_rx_buff[0]), sizeof(i2s_tx_buff) / sizeof(i2s_tx_buff[0]));
            int raw_samples_read = bytes_read / 2 / (I2S_DATA_BIT_WIDTH_32BIT / 8);
            ESP_LOGI(MY_LOG_TAG, "raw samples read: %d", raw_samples_read);
            for (int i = 0; i < raw_samples_read; i++)
            {
                // let's inspect our input once in a while
                if (i % 10000 == 0) { // the first sample of every new buffer
                    ESP_LOGI(MY_LOG_TAG, "rx packet looks like %04x %04x %04x %04x", i2s_rx_buff[i], i2s_rx_buff[i+1], i2s_rx_buff[i+2], i2s_rx_buff[i+3]);
                }
                
                // left
                buf_ptr_write[0] = buf_ptr_read[2]; // mid
                buf_ptr_write[1] = buf_ptr_read[3]; // high
                // right
                buf_ptr_write[2] = buf_ptr_read[6]; // mid
                buf_ptr_write[3] = buf_ptr_read[7]; // high

                buf_ptr_write += 2 * (I2S_DATA_BIT_WIDTH_16BIT / 8);
                buf_ptr_read += 2 * (I2S_DATA_BIT_WIDTH_32BIT / 8);
            }
            i2s_channel_write(tx_handle, (const char*)i2s_tx_buff, sizeof(i2s_tx_buff), &bytes_written, portMAX_DELAY);
        } else {
            ESP_LOGI(MY_LOG_TAG, "Read Failed!");
        }
    }
}

BarmyFluid
Posts: 2
Joined: Wed Aug 02, 2023 5:00 pm

Re: I2s microphone sample rate seems halft as big as it's supposed to

Postby BarmyFluid » Mon Sep 25, 2023 5:33 pm

Nice work finding that. I never actually played back the audio but it worked when I fed it into ESP-SR so thought nothing of it. I guess it just thinks I have a deeper voice :D

Who is online

Users browsing this forum: No registered users and 162 guests