Page 1 of 2

I2s MEMS microphone help (sph0645)

Posted: Thu Aug 22, 2024 1:11 pm
by Cimby1
Hello, I would like to get help for my project. I submitted an issue on ESP-IDF github to find an answer, so I try here aswell.
I don't want to duplicate the description of my problem, so I just link my github issue ticket here.

https://github.com/espressif/esp-idf/issues/14415

I hope someone can help me out, or atleast start me somewhere where I can find a solution. :|

Re: I2s MEMS microphone help (sph0645)

Posted: Fri Aug 23, 2024 8:18 am
by MicroController
Github issues are actually meant to report (suspected) bugs in e.g. the IDF. For questions like this, i.e. how to interpret I2S data you receive, this forum would be the more appropriate place.

For reference, in the issue there is some example output:

Code: Select all

Read Task: i2s read 2048 bytes
[0] 0 [1] 80 [2] 6 [3] f3
[4] 0 [5] 80 [6] f8 [7] f2
This looks like plausible little-endian int32 sample values with 24 (or, at least 17) significant bits.
Try interpreting your buffer as int32 (const int32_t* samples = (const int32_t*)r_buf;) and see if that gives you what you expect.

Re: I2s MEMS microphone help (sph0645)

Posted: Fri Aug 23, 2024 10:16 am
by Cimby1
Thanks for your reply :) .
I was not sure where to got with this problem for the first place. But I understand that I should keep posting here for advice.

I try to implement your code into mine and I keep you updated.

Re: I2s MEMS microphone help (sph0645)

Posted: Fri Aug 23, 2024 5:25 pm
by Cimby1
I managed to continue my code, he it is:

Code: Select all

#include <stdint.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "sdkconfig.h"

#define BUFFER_SIZE 2048

static i2s_chan_handle_t rx_chan; // I2S RX channel handler

static void i2s_read_task(void *args)
{
    uint8_t *r_buf = (uint8_t *)calloc(1, BUFFER_SIZE);
    assert(r_buf); // Check if the r_buf allocation was successful

    size_t r_bytes = 0;

    // Enable the RX channel
    ESP_ERROR_CHECK(i2s_channel_enable(rx_chan));

    while (1)
    {
        // Read I2S data
        if (i2s_channel_read(rx_chan, r_buf, BUFFER_SIZE, &r_bytes, 1000) == ESP_OK)
        {
            printf("Read Task: i2s read %zu bytes\n-----------------------------------\n", r_bytes);

            // Print the buffer as samples
            const uint32_t *samples = (const uint32_t *)r_buf;
            int num_samples = r_bytes / sizeof(uint32_t);

            // Print the 32-bit samples and their 18-bit versions
            for (int i = 0; i < num_samples; i++)
            {
                uint32_t sample = samples[i];

                // Print the original 32-bit sample bit by bit
                printf("Original 32-bit Sample: ");
                for (int bit = 31; bit >= 0; bit--)
                {
                    printf("%ld", (sample >> bit) & 1);
                }
                printf("\n");

                // Trim the last 14 bits
                uint32_t trimmed_sample = (sample >> 14) & 0x3FFFF; // Trim the last 14 bits and keep the lower 18 bits

                // Print the trimmed 18-bit sample bit by bit
                printf("Trimmed 18-bit Sample: ");
                for (int bit = 17; bit >= 0; bit--)
                {
                    printf("%ld", (trimmed_sample >> bit) & 1);
                }
                printf("\n");
            }
        }
        else
        {
            printf("Read Task: i2s read failed\n");
        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }

    free(r_buf);
    vTaskDelete(NULL);
}

static void i2s_init_sph0645lm4h(void)
{
    // I2S channel configuration
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_chan));

    // I2S standard configuration
    i2s_std_config_t std_cfg = {
        .clk_cfg = {
            .sample_rate_hz = 48000,
            .clk_src = I2S_CLK_SRC_DEFAULT,
            .mclk_multiple = I2S_MCLK_MULTIPLE_256,
        },
        // Philips or standard configuration
        //.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = GPIO_NUM_4,
            .ws = GPIO_NUM_5,
            .dout = I2S_GPIO_UNUSED, // Only read, no write
            .din = GPIO_NUM_19,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg));
}

void app_main(void)
{
    // I2S initialization
    i2s_init_sph0645lm4h();

    // Task creation
    xTaskCreate(i2s_read_task, "i2s_read_task", 4096, NULL, 5, NULL);
}
But I don't know how to proceed further.
How can I decode the I2s communication ?
And do I have to use two's complement transformation on my trimmed_sample ?

Also here is an output of my code:

Code: Select all

Original 32-bit Sample:111101010011000100 00000000000000
Trimmed 18-bit Sample: 111101010011000100

Original 32-bit Sample:111101010010111000 00000000000000
Trimmed 18-bit Sample: 111101010010111000
Reading the datasheet of my mic, it seems okey.
(https://www.knowles.com/docs/default-so ... asheet.pdf)
Figure 7 shows, that from the 18th bit the data stream should be only zeros, and that's the case here.

Re: I2s MEMS microphone help (sph0645)

Posted: Sat Aug 24, 2024 12:56 pm
by Cimby1
So I continued further (my comments are waiting for approval so it feels like I am talking to myself).

I started reading about DAC features. In the Digital To Analog Converter (DAC) part of ESP IDF docs it says :
On ESP32, the DAC digital controller can be connected internally to the I2S0 and use its DMA for continuous conversion. Although the DAC only needs 8-bit data for conversion, it has to be the left-shifted 8 bits (i.e., the high 8 bits in a 16-bit slot) to satisfy the I2S communication format. By default, the driver helps to expand the data to 16-bit wide automatically. To expand manually, please disable CONFIG_DAC_DMA_AUTO_16BIT_ALIGN in the menuconfig.
Also in the programming guide there is a section that says :
Short example configuring I2S to use internal DAC for analog output:

Code: Select all

#include "driver/i2s.h"
#include "freertos/queue.h"

static const int i2s_num = 0; // i2s port number

static const i2s_config_t i2s_config = {
     .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN,
     .sample_rate = 44100,
     .bits_per_sample = 16, /* the DAC module will only take the 8bits from MSB */
     .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
     .communication_format = I2S_COMM_FORMAT_I2S_MSB,
     .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // high interrupt priority
     .dma_buf_count = 8,
     .dma_buf_len = 64
};

...

    i2s_driver_install(i2s_num, &i2s_config, 0, NULL);   //install and start i2s driver

    i2s_set_pin(i2s_num, NULL); //for internal DAC, this will enable both of the internal channels

    //You can call i2s_set_dac_mode to set built-in DAC output mode.
    //i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);

    i2s_set_sample_rates(i2s_num, 22050); //set sample rates

    i2s_driver_uninstall(i2s_num); //stop & destroy i2s driver
It feels like I can transfer data from I2s to ADC, so easly but I don't know how. Beacuse the example code is not going in detail how sadly. For example where can I set the pins, or which pins-s should I use ?

Re: I2s MEMS microphone help (sph0645)

Posted: Sun Aug 25, 2024 4:31 pm
by MicroController
I think the DAC only needs the I2S peripheral to control the DMA transfer.
You'll have to convert the signed 32-bit data received via I2S to unsigned 8 bit for the DAC, e.g. uint8_t dacSample = (uint8_t)((sample32 >> 24) + 128).

Re: I2s MEMS microphone help (sph0645)

Posted: Sun Aug 25, 2024 5:43 pm
by Cimby1
I reckon I am going to lose some resolution, but the DAC is the bottleneck here.
I try to implement your idea.

Re: I2s MEMS microphone help (sph0645)

Posted: Mon Aug 26, 2024 7:40 pm
by Cimby1
So I added your idea

Code: Select all

#include <stdint.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "sdkconfig.h"

#define BUFFER_SIZE 2048

static i2s_chan_handle_t rx_chan; // I2S RX channel handler

static void i2s_read_task(void *args)
{
    uint8_t *r_buf = (uint8_t *)calloc(1, BUFFER_SIZE);
    assert(r_buf); // Check if the r_buf allocation was successful

    size_t r_bytes = 0;

    // Enable the RX channel
    ESP_ERROR_CHECK(i2s_channel_enable(rx_chan));

    while (1)
    {
        // Read I2S data
        if (i2s_channel_read(rx_chan, r_buf, BUFFER_SIZE, &r_bytes, 1000) == ESP_OK)
        {
            printf("Read Task: i2s read %zu bytes\n-----------------------------------\n", r_bytes);

            // Print the buffer as samples
            const uint32_t *samples = (const uint32_t *)r_buf;
            int num_samples = r_bytes / sizeof(uint32_t);

            // Print the 32-bit samples and their 18-bit versions
            for (int i = 0; i < num_samples; i++)
            {
                uint32_t sample = samples[i];

                // Print the original 32-bit sample bit by bit
                // printf("Original 32-bit Sample: ");
                // for (int bit = 31; bit >= 0; bit--)
                // {
                //     printf("%ld", (sample >> bit) & 1);
                // }
                // printf("\n");

                // Trim the last 14 bits
                uint32_t trimmed_sample = (sample >> 14) & 0x3FFFF; // Trim the last 14 bits and keep the lower 18 bits
                uint8_t dacSample = (uint8_t)((sample >> 24) + 128);

                // Print the trimmed 18-bit sample bit by bit
                printf("Trimmed 18-bit Sample: ");
                for (int bit = 17; bit >= 0; bit--)
                {
                    printf("%ld", (trimmed_sample >> bit) & 1);
                }
                printf("\n");

                printf("DACSample 8-bit Sample: ");
                for (int bit = 7; bit >= 0; bit--) // A cycle from 0 to 7
                {
                    printf("%d", (dacSample >> bit) & 1);
                }
                printf("\n");
            }
        }
        else
        {
            printf("Read Task: i2s read failed\n");
        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }

    free(r_buf);
    vTaskDelete(NULL);
}

static void i2s_init_sph0645lm4h(void)
{
    // I2S channel configuration
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_chan));

    // I2S standard configuration
    i2s_std_config_t std_cfg = {
        .clk_cfg = {
            .sample_rate_hz = 48000,
            .clk_src = I2S_CLK_SRC_DEFAULT,
            .mclk_multiple = I2S_MCLK_MULTIPLE_256,
        },
        // Philips or standard configuration
        //.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,
            .bclk = GPIO_NUM_4,
            .ws = GPIO_NUM_5,
            .dout = I2S_GPIO_UNUSED, // Only read, no write
            .din = GPIO_NUM_19,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };
    std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg));
}

void app_main(void)
{
    // I2S initialization
    i2s_init_sph0645lm4h();

    // Task creation
    xTaskCreate(i2s_read_task, "i2s_read_task", 4096, NULL, 5, NULL);
}
But it is not working as I can tell.

Code: Select all

Trimmed 18-bit Sample: 111001011100010100
DACSample 8-bit Sample: 01100101

Trimmed 18-bit Sample: 111001011011111000
DACSample 8-bit Sample: 01100101

Trimmed 18-bit Sample: 111001011100000000
DACSample 8-bit Sample: 01100101

Trimmed 18-bit Sample: 111001011100100000
DACSample 8-bit Sample: 01100101
As you can tell, the trimmed stream is changing, but the DACSample is not.

I should figure out something easier. How can I decode the I2s data stream to something usefull ? I have the data stream, but that's it. :?

Re: I2s MEMS microphone help (sph0645)

Posted: Mon Aug 26, 2024 10:37 pm
by MicroController
How can I decode the I2s data stream to something usefull ?
I don't know what 'useful' would be to you here.
But: The signed 32-bit sample values you get via I2S are the numerical representations of the air pressure the microphone picks up. 0 means no sound pressure, 2^31-1 is maximum possible positive pressure (amplitude) and -2^31 the maximum negative.
Incident sound will make the pressure, and with it the sample values, oscillate up and down around 0.
Because presumably the sound you're recording has much lower frequencies than half the sample rate, the difference from one sample to the next will be relatively small.
For demonstration, you could e.g. find and print the maximum sample value over a number of samples. Loud sound should give a larger max value than quiet sounds or silence.

Down-converting the resolution from 18 to 8 bits naturally causes loss of information; very small variations between sample values will be lost and similar 18-bit sample values will give the same 8-bit value. In fact, 1024 adjacent 18-bit values will be mapped to the same 8-bit value.

Re: I2s MEMS microphone help (sph0645)

Posted: Wed Aug 28, 2024 7:28 am
by Cimby1
find and print the maximum sample value over a number of samples

I try to do something similiar.
I will have to get a better understanding in digital audio and I2s to know what I wanto to do with this raw datastream. I am going to come back with other dumb questions, if I understand more in this topic :roll:.
Thanks for every comment you made so far, it is very helpful.