Inputting audio to an ESP32 from an INMP441 I2S microphone: success

chris_oz
Posts: 4
Joined: Mon Apr 13, 2020 11:10 pm

Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby chris_oz » Wed Apr 15, 2020 9:15 am

I'm working on a baby monitor. I've got this microphone: https://invensense.tdk.com/products/digital/inmp441/

So i got this audio input to work :D ! Here I'll share my findings:

Here's the setup code: (note that this mic is not PDM)

Code: Select all

i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 11025, // or 44100 if you like
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // Ground the L/R pin on the INMP441.
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 4,
    .dma_buf_len = ESP_NOW_MAX_DATA_LEN * 4,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0,
};
if (ESP_OK != i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL)) {
    Serial.println("i2s_driver_install: error");
}

i2s_pin_config_t pin_config = {
    .bck_io_num = 14,   // Bit Clock.
    .ws_io_num = 15,    // Word Select aka left/right clock aka LRCL.
    .data_out_num = -1,
    .data_in_num = 34,  // Data-out of the mic. (someone used 23 on forums).
};
if (ESP_OK != i2s_set_pin(I2S_NUM_0, &pin_config)) {
    Serial.println("i2s_set_pin: error");
}
Here's the code to input the data:

Code: Select all

size_t bytesRead = 0;
uint8_t buffer32[ESP_NOW_MAX_DATA_LEN * 4] = {0};
i2s_read(I2S_NUM_0, &buffer32, sizeof(buffer32), &bytesRead, 100);
int samplesRead = bytesRead / 4;
The microphone is a 24-bit one, but if you use bits_per_sample = I2S_BITS_PER_SAMPLE_24BIT it doesn't work, maybe an ESP bug? Anyway 32 bits works with some workarounds.

The 4 bytes that make up each sample in buffer32 are as follows:

First byte: E0 or 00, regardless of the sign of the other bytes, discard these!
Byte 2: Lowest significant byte of the sample. You can also discard this because it's just fuzz even in a quiet room.
Byte 3: Middle byte of the sample (signed)
Last byte: Most significant byte of the sample (signed).

Now what i recommend is converting it to 16-bit signed samples:

Code: Select all

int16_t buffer16[ESP_NOW_MAX_DATA_LEN] = {0};
for (int i=0; i<samplesRead; i++) {
    uint8_t mid = buffer32[i * 4 + 2];
    uint8_t msb = buffer32[i * 4 + 3];
    uint16_t raw = (((uint32_t)msb) << 8) + ((uint32_t)mid);
    memcpy(&buffer16[i], &raw, sizeof(raw)); // Copy so sign bits aren't interfered with somehow.
}
And that's it! You've got an array of 16-bit signed samples that are good to go. I find in a quiet room, the maximum samples hover around 7, after converting to the int16, and around 2-300 when i speak normally half a metre away.

If you'd like to play them back to the built-in DAC, what i've found is that the DAC expects non-signed samples, so basically you cast each sample to an int32_t, add 0x8000, then cast back to a uint16_t.

Hope that helps someone

jenishrudani
Posts: 1
Joined: Thu Oct 22, 2020 9:34 am

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby jenishrudani » Thu Oct 22, 2020 9:36 am

Hi, Thanks for the elaborate explanation of the nitty-gritty of this particular microphone. This would help us tremendously. Thanks again!

anttok
Posts: 5
Joined: Thu Sep 10, 2020 10:14 pm

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby anttok » Wed Jan 06, 2021 3:11 pm

Thanks, most helpful!

ESP_Minatel
Posts: 361
Joined: Mon Jan 04, 2021 2:06 pm

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby ESP_Minatel » Wed Jan 06, 2021 3:49 pm

chris_oz wrote:
Wed Apr 15, 2020 9:15 am
I'm working on a baby monitor. I've got this microphone: https://invensense.tdk.com/products/digital/inmp441/

So i got this audio input to work :D ! Here I'll share my findings:
...
Thank you for your contribution!

gupta.aadityav
Posts: 1
Joined: Sun Apr 18, 2021 10:49 pm

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby gupta.aadityav » Sun Apr 18, 2021 11:28 pm

I am using the SPH0690LM4H-1 Mems microphone with the esp32 development board. My objective is to amplify the voice received from the microphone using a the MAX 98357 amplifier and a standard 8 Ohm 1W speaker. Currently this is the code I have but I am not able to transmit anything to the output.

Code: Select all

#include "driver/i2s.h"

#define bitDepth        (32)

const i2s_port_t I2S_PORT_RX = I2S_NUM_1;
const i2s_port_t I2S_PORT_TX = I2S_NUM_0;

void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  esp_err_t err;
  // i2s config for the microphone
  const i2s_config_t i2s_config_rx = {
      .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // Receive, not transfer
      .sample_rate = 44100,                         // 44.1KHz
      .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // could only get it to work with 32bits
      .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // use left channel
      .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,     // Interrupt level 1
      .dma_buf_count = 4,                           // number of buffers
      .dma_buf_len = 250*4,                              // 250 is the ESP_NOW_MAX_
      .use_apll = false,
      .tx_desc_auto_clear = false, 
      .fixed_mclk = 0
  };

  // i2s pin config for the microphone
  const i2s_pin_config_t pin_config_rx = {
      .bck_io_num = 16,   // Serial Clock (SCK)
      .ws_io_num = 26,    // Word Select (WS)
      .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers)
      .data_in_num = 2   // Serial Data (SD)
  };


  // Configuring the I2S driver and pins for mic.
  i2s_driver_install(I2S_PORT_RX, &i2s_config_rx, 0, NULL);
  
  // set pins for the microphone
  i2s_set_pin(I2S_PORT_RX, &pin_config_rx);
 

  //setup dac
  const i2s_config_t i2s_config_dac = {
      .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), // Transfer not receive
      .sample_rate = 44100,                         // 44.1KHz
      .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // could only get it to work with 32bits
      .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // use left channel
      .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S_MSB),
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,     // Interrupt level 1
      .dma_buf_count = 4,                           // number of buffers
      .dma_buf_len = 250*4,                         
      .use_apll = false
  };
  // setup dac pin configuration
   const i2s_pin_config_t pin_config_dac = {
       .bck_io_num = 18,   // Serial Clock (SCK)
       .ws_io_num = 17,    // Word Select (WS)
       .data_out_num = 25, // not used (only for speakers)
       .data_in_num = I2S_PIN_NO_CHANGE  // Serial Data (SD)
   };

  i2s_driver_install(I2S_PORT_TX, &i2s_config_dac, 0, NULL);
  
  // set pins for the speaker
  i2s_set_pin(I2S_PORT_TX, &pin_config_dac);
  
  
}

void loop() {
  // put your main code here, to run repeatedly:

  size_t bytesRead = 0;
  uint8_t buffer32[250 * 4] = {0};
  i2s_read(I2S_NUM_0, &buffer32, sizeof(buffer32), &bytesRead, 100);
  int samplesRead = bytesRead / 4;

  // conversion
  int16_t buffer16[250] = {0};
  for (int i=0; i<samplesRead; i++) {
      uint8_t mid = buffer32[i * 4 + 2];
      uint8_t msb = buffer32[i * 4 + 3];
      uint16_t raw = (((uint32_t)msb) << 8) + ((uint32_t)mid);
      memcpy(&buffer16[i], &raw, sizeof(raw)); // Copy so sign bits aren't interfered with somehow.

  }

  int bytes_written = samplesRead * (I2S_BITS_PER_SAMPLE_32BIT / 8);
  size_t bytes_written2 = 0;
  i2s_write(I2S_PORT_TX, &buffer16, (size_t) bytes_written, &bytes_written2, portMAX_DELAY);


}
I am not very experienced with the i2s protocol so help would be appreciated.
P.S. you will also notice that right now I don't have the part where you cast each sample to an int32_t, add 0x8000, then cast back to a uint16_t.

ESP_Minatel
Posts: 361
Joined: Mon Jan 04, 2021 2:06 pm

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby ESP_Minatel » Tue Apr 20, 2021 10:34 am

Hi,

Check the videos done by Atomic14. He's doing a nice work on audio using ESP32.

https://www.youtube.com/c/atomic14/videos

boooleen
Posts: 3
Joined: Thu May 12, 2022 7:43 pm

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby boooleen » Fri May 13, 2022 4:12 pm

gupta.aadityav wrote:
Sun Apr 18, 2021 11:28 pm
I am using the SPH0690LM4H-1 Mems microphone with the esp32 development board. My objective is to amplify the voice received from the microphone using a the MAX 98357 amplifier and a standard 8 Ohm 1W speaker. Currently this is the code I have but I am not able to transmit anything to the output.

Code: Select all

#include "driver/i2s.h"

#define bitDepth        (32)

const i2s_port_t I2S_PORT_RX = I2S_NUM_1;
const i2s_port_t I2S_PORT_TX = I2S_NUM_0;

void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  esp_err_t err;
  // i2s config for the microphone
  const i2s_config_t i2s_config_rx = {
      .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // Receive, not transfer
      .sample_rate = 44100,                         // 44.1KHz
      .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // could only get it to work with 32bits
      .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // use left channel
      .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,     // Interrupt level 1
      .dma_buf_count = 4,                           // number of buffers
      .dma_buf_len = 250*4,                              // 250 is the ESP_NOW_MAX_
      .use_apll = false,
      .tx_desc_auto_clear = false, 
      .fixed_mclk = 0
  };

  // i2s pin config for the microphone
  const i2s_pin_config_t pin_config_rx = {
      .bck_io_num = 16,   // Serial Clock (SCK)
      .ws_io_num = 26,    // Word Select (WS)
      .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers)
      .data_in_num = 2   // Serial Data (SD)
  };


  // Configuring the I2S driver and pins for mic.
  i2s_driver_install(I2S_PORT_RX, &i2s_config_rx, 0, NULL);
  
  // set pins for the microphone
  i2s_set_pin(I2S_PORT_RX, &pin_config_rx);
 

  //setup dac
  const i2s_config_t i2s_config_dac = {
      .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), // Transfer not receive
      .sample_rate = 44100,                         // 44.1KHz
      .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // could only get it to work with 32bits
      .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // use left channel
      .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S_MSB),
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,     // Interrupt level 1
      .dma_buf_count = 4,                           // number of buffers
      .dma_buf_len = 250*4,                         
      .use_apll = false
  };
  // setup dac pin configuration
   const i2s_pin_config_t pin_config_dac = {
       .bck_io_num = 18,   // Serial Clock (SCK)
       .ws_io_num = 17,    // Word Select (WS)
       .data_out_num = 25, // not used (only for speakers)
       .data_in_num = I2S_PIN_NO_CHANGE  // Serial Data (SD)
   };

  i2s_driver_install(I2S_PORT_TX, &i2s_config_dac, 0, NULL);
  
  // set pins for the speaker
  i2s_set_pin(I2S_PORT_TX, &pin_config_dac);
  
  
}

void loop() {
  // put your main code here, to run repeatedly:

  size_t bytesRead = 0;
  uint8_t buffer32[250 * 4] = {0};
  i2s_read(I2S_NUM_0, &buffer32, sizeof(buffer32), &bytesRead, 100);
  int samplesRead = bytesRead / 4;

  // conversion
  int16_t buffer16[250] = {0};
  for (int i=0; i<samplesRead; i++) {
      uint8_t mid = buffer32[i * 4 + 2];
      uint8_t msb = buffer32[i * 4 + 3];
      uint16_t raw = (((uint32_t)msb) << 8) + ((uint32_t)mid);
      memcpy(&buffer16[i], &raw, sizeof(raw)); // Copy so sign bits aren't interfered with somehow.

  }

  int bytes_written = samplesRead * (I2S_BITS_PER_SAMPLE_32BIT / 8);
  size_t bytes_written2 = 0;
  i2s_write(I2S_PORT_TX, &buffer16, (size_t) bytes_written, &bytes_written2, portMAX_DELAY);


}
I am not very experienced with the i2s protocol so help would be appreciated.
P.S. you will also notice that right now I don't have the part where you cast each sample to an int32_t, add 0x8000, then cast back to a uint16_t.
Hi ! I have the same problem, did you find a solution ?

dulce_ggb
Posts: 1
Joined: Fri Sep 09, 2022 6:00 am

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby dulce_ggb » Fri Sep 09, 2022 6:11 am

Hi! I'm using the microphone INMP441 and I have the same problem. I only have output noise. Did you have any solution? I will really appreciate your help.

Pippadi
Posts: 3
Joined: Mon Apr 10, 2023 4:27 am

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby Pippadi » Mon Apr 10, 2023 5:07 am

Sorry if this is a necropost, but I found this thread useful, and thought I could add to the discussion.
I found this to be a good reference: https://github.com/atomic14/esp32-walki ... er.cpp#L48

Here, only the lower 11 bits of the 32-bit frame are being discarded. That seemed to make all the difference for my particular INMP441 (perhaps different units behave differently?). It seems like shifting by 16 bits was shifting most of my audio signal into oblivion. Speech is discernible (albeit rather garbled) when I import recorded audio into Audacity. Also, I found that I didn't have to swap the byte order (Audacity imported the audio as little-endian without a problem).

Here's how I'm reading from the microphone:
  1. size_t INMP441::read(int16_t* destination, size_t sampleCnt) {
  2.     size_t bytesRead;
  3.     int32_t temp[sampleCnt];
  4.     size_t samplesRead;
  5.  
  6.     esp_err_t err = i2s_read(port, (uint8_t*) temp, sampleCnt*sizeof(int32_t), &bytesRead, portMAX_DELAY);
  7.     if (err != ESP_OK) {
  8.         return 0;
  9.     }
  10.  
  11.     samplesRead = bytesRead / sizeof(int32_t);
  12.     for (int i=0; i<samplesRead; i++) {
  13.         // Discard unused lower 8 bits, and get rid of 3 bits of noise.
  14.         temp[i] >>= 11;
  15.         destination[i] = (int16_t) temp[i];
  16.     }
  17.  
  18.     return samplesRead;
  19. }
Clipping isn't too much of a problem when you're speaking from a distance. From my tests, preventing it doesn't really seem worth the CPU cycles.

Hope this helps someone. Would love to hear what worked for others!

Fluffy_Port
Posts: 6
Joined: Wed Jan 18, 2023 11:20 pm

Re: Inputting audio to an ESP32 from an INMP441 I2S microphone: success

Postby Fluffy_Port » Sat Sep 30, 2023 11:13 pm

Pippadi wrote:
Mon Apr 10, 2023 5:07 am
Sorry if this is a necropost, but I found this thread useful, and thought I could add to the discussion.
I found this to be a good reference: https://github.com/atomic14/esp32-walki ... er.cpp#L48

Here, only the lower 11 bits of the 32-bit frame are being discarded. That seemed to make all the difference for my particular INMP441 (perhaps different units behave differently?). It seems like shifting by 16 bits was shifting most of my audio signal into oblivion. Speech is discernible (albeit rather garbled) when I import recorded audio into Audacity. Also, I found that I didn't have to swap the byte order (Audacity imported the audio as little-endian without a problem).

Here's how I'm reading from the microphone:
  1. size_t INMP441::read(int16_t* destination, size_t sampleCnt) {
  2.     size_t bytesRead;
  3.     int32_t temp[sampleCnt];
  4.     size_t samplesRead;
  5.  
  6.     esp_err_t err = i2s_read(port, (uint8_t*) temp, sampleCnt*sizeof(int32_t), &bytesRead, portMAX_DELAY);
  7.     if (err != ESP_OK) {
  8.         return 0;
  9.     }
  10.  
  11.     samplesRead = bytesRead / sizeof(int32_t);
  12.     for (int i=0; i<samplesRead; i++) {
  13.         // Discard unused lower 8 bits, and get rid of 3 bits of noise.
  14.         temp[i] >>= 11;
  15.         destination[i] = (int16_t) temp[i];
  16.     }
  17.  
  18.     return samplesRead;
  19. }
Clipping isn't too much of a problem when you're speaking from a distance. From my tests, preventing it doesn't really seem worth the CPU cycles.

Hope this helps someone. Would love to hear what worked for others!
You need to be careful when you demote an int32 type to int16 type. With that code, after you discard the 11 LSB you are trying to convert the remaining int32 array into an int16 one but without including any kind of conditional estructure to avoid values beyond the limits of a 16 bits variable.
To fix that we could add this line of code as follows:

Code: Select all

 
 temp[i] >>= 11;
 destination [i]= (temp[i]>INT16_MAX) ? INT16_MAX : (temp[i] < INT16_MIN) ? INT16_MIN : (int16_t) temp
This way you make sure there's no type converting issues.
Last edited by Fluffy_Port on Wed Oct 04, 2023 6:50 pm, edited 1 time in total.

Who is online

Users browsing this forum: iucharbius and 238 guests