SPH0645LM4H with ESP32-S3

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

SPH0645LM4H with ESP32-S3

Postby Pippadi » Fri Aug 25, 2023 6:48 am

It is rather well-known that the SPH0645 uses a nonstandard I2S interface (See https://www.youtube.com/watch?v=3g7l5bm7fZ8).
An ESP32-specific workaround has been circulated online. I have found a similar workaround for the ESP32-S3. The code below simply modifies the I2S RX registers to delay reading serial data by falling edge of the clock line, and subsequently force Philips mode.

There may be a better way to do this, but this is what worked for me. Hope this helps someone!
  1. #include <driver/i2s.h>
  2. #include <soc/i2s_reg.h>
  3.  
  4. #define BUF_LEN 2048
  5. #define SAMPLE_RATE 44100
  6. #define I2S_PORT I2S_NUM_0
  7.  
  8. #define DATA_PIN 7
  9. #define BCLK_PIN 4
  10. #define WS_PIN 17
  11.  
  12. void setup() {
  13.     Serial.begin(115200);
  14.  
  15.     const i2s_config_t micCfg = {
  16.         .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
  17.         .sample_rate = SAMPLE_RATE,
  18.         .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
  19.         .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
  20.         // My microphone is configured to send on the right channel.
  21.         // A bug in ESP-IDF swaps the two, so this is set to left.
  22.         // See https://github.com/espressif/esp-idf/issues/6625
  23.  
  24.         .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
  25.         .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
  26.         .dma_buf_count = 8,
  27.         .dma_buf_len = 1024,
  28.         .use_apll = false,
  29.         .tx_desc_auto_clear = false,
  30.         .fixed_mclk = 0,
  31.     };
  32.  
  33.     i2s_pin_config_t micPins = {
  34.         .bck_io_num = BCLK_PIN,
  35.         .ws_io_num = WS_PIN,
  36.         .data_out_num = -1,
  37.         .data_in_num = DATA_PIN,
  38.     };
  39.  
  40.     esp_err_t err;
  41.     err = i2s_driver_install(I2S_PORT, &micCfg, 0, NULL);
  42.     if (err != ESP_OK) {
  43.         Serial.println("I2S driver initialization failed");
  44.         while (true);
  45.     }
  46.  
  47.     // Delay by falling edge
  48.     REG_SET_BIT(I2S_RX_TIMING_REG(I2S_PORT), BIT(1));
  49.     // Force Philips mode
  50.     REG_SET_BIT(I2S_RX_CONF1_REG(I2S_PORT), I2S_RX_MSB_SHIFT);
  51.  
  52.     err = i2s_set_pin(I2S_PORT, &micPins);
  53.     if (err != ESP_OK) {
  54.         Serial.println("Setting pins failed");
  55.         while (true);
  56.     }
  57. }
  58.  
  59. void loop() {
  60.     static int32_t buf32[BUF_LEN];
  61.     static int16_t buf16[BUF_LEN];
  62.     size_t bytesRead;
  63.  
  64.     esp_err_t err = i2s_read(I2S_PORT, (uint8_t*) buf32, BUF_LEN*sizeof(int32_t), &bytesRead, portMAX_DELAY);
  65.     if (err != ESP_OK) {
  66.         Serial.println("Error reading from microphone");
  67.         Serial.println(err);
  68.         return;
  69.     }
  70.  
  71.     size_t samplesRead = bytesRead / sizeof(int32_t);
  72.     for (int i=0; i<samplesRead; i++) {
  73.         // Discard unused lower 12 bits. Sign bit is already where it needs to be.
  74.         buf32[i] >>= 12;
  75.         buf16[i] = (int16_t) (buf32[i] & 0xFFFF);
  76.     }
  77.  
  78.     // Do stuff with signed 16-bit little-endian samples.
  79. }

LonlyBinary
Posts: 1
Joined: Sun Feb 04, 2024 11:30 am

Re: SPH0645LM4H with ESP32-S3

Postby LonlyBinary » Sun Feb 04, 2024 11:37 am

Thank you.
I tried to use your code and he seems to work fine, but on the serial plotter it feels very unusual, it's almost quiet around me, but the values are changing very drastically.
SCR-20240204-rbqu.png
pic
SCR-20240204-rbqu.png (1000.08 KiB) Viewed 31965 times
  1. #include <Arduino.h>
  2. #include <driver/i2s.h>
  3. #include <soc/i2s_reg.h>
  4.  
  5. #define BUF_LEN     2048
  6. #define SAMPLE_RATE 16000
  7. #define I2S_PORT    I2S_NUM_0
  8.  
  9. #define DATA_PIN 38
  10. #define BCLK_PIN 21
  11. #define WS_PIN   47
  12.  
  13. void setup() {
  14.     delay(2000);
  15.     USBSerial.begin(115200);
  16.  
  17.     const i2s_config_t micCfg = {
  18.         .mode            = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
  19.         .sample_rate     = SAMPLE_RATE,
  20.         .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
  21.         .channel_format  = I2S_CHANNEL_FMT_ONLY_LEFT,
  22.         // My microphone is configured to send on the right channel.
  23.         // A bug in ESP-IDF swaps the two, so this is set to left.
  24.         // See https://github.com/espressif/esp-idf/issues/6625
  25.  
  26.         .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
  27.         .intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1,
  28.         .dma_buf_count        = 4,
  29.         .dma_buf_len          = 8,
  30.         .use_apll             = false,
  31.         .tx_desc_auto_clear   = false,
  32.         .fixed_mclk           = 0,
  33.     };
  34.  
  35.     i2s_pin_config_t micPins = {
  36.         .bck_io_num   = BCLK_PIN,
  37.         .ws_io_num    = WS_PIN,
  38.         .data_out_num = -1,
  39.         .data_in_num  = DATA_PIN,
  40.     };
  41.  
  42.     esp_err_t err;
  43.     err = i2s_driver_install(I2S_PORT, &micCfg, 0, NULL);
  44.     if (err != ESP_OK) {
  45.         USBSerial.println("I2S driver initialization failed");
  46.         while (true)
  47.             ;
  48.     }
  49.  
  50.     // Delay by falling edge
  51.     REG_SET_BIT(I2S_RX_TIMING_REG(I2S_PORT), BIT(1));
  52.     // Force Philips mode
  53.     REG_SET_BIT(I2S_RX_CONF1_REG(I2S_PORT), I2S_RX_MSB_SHIFT);
  54.  
  55.     err = i2s_set_pin(I2S_PORT, &micPins);
  56.     if (err != ESP_OK) {
  57.         USBSerial.println("Setting pins failed");
  58.         while (true)
  59.             ;
  60.     }
  61. }
  62.  
  63. void loop() {
  64.     static int32_t buf32[BUF_LEN];
  65.     static int16_t buf16[BUF_LEN];
  66.     size_t bytesRead;
  67.  
  68.     esp_err_t err =
  69.         i2s_read(I2S_PORT, (uint8_t*)buf32, BUF_LEN * sizeof(int32_t),
  70.                  &bytesRead, portMAX_DELAY);
  71.     if (err != ESP_OK) {
  72.         USBSerial.println("Error reading from microphone");
  73.         USBSerial.println(err);
  74.         return;
  75.     }
  76.  
  77.     size_t samplesRead = bytesRead / sizeof(int32_t);
  78.     for (int i = 0; i < samplesRead; i++) {
  79.         // Discard unused lower 12 bits. Sign bit is already where it needs to
  80.         // be.
  81.         buf32[i] >>= 12;
  82.         buf16[i] = (int16_t)(buf32[i] & 0xFFFF);
  83.     }
  84.  
  85.     for (int i = 0; i < samplesRead; i++) {
  86.         // 输出样本值到串口终端
  87.         USBSerial.println(buf32[i]);
  88.     }
  89.     USBSerial.println("ok\n");
  90.     // Do stuff with signed 16-bit little-endian samples.
  91. }

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

Re: SPH0645LM4H with ESP32-S3

Postby Pippadi » Sat Feb 17, 2024 7:47 am

In my case at least, it seems to pick up a lot of noise. At times, the fan in the room is louder than my voice, even when I'm right next to it.

Going by the graph in the datasheet, the microphone is more sensitive to higher frequencies. I guess the noise is more of a problem with the microphone :)

jdoei2s
Posts: 2
Joined: Thu Jan 02, 2025 9:23 pm

Re: SPH0645LM4H with ESP32-S3

Postby jdoei2s » Thu Jan 23, 2025 12:16 am

Hi! So glad someone got it to work recently. I tried your code on my Arduino IDE and it gives bad results in the plotter, I tried to create a WAV and there is nothing audible / understandable in it.

A few questions on your code.
1. Since you posted, I believe the bug for swapping right / left is fixed. I have a microphone wire on the left channel, and the data is fine. If I swap to the right, I get a constant 0 reading.
2. You shift by 12 the 32 bit value, saying the signed bit is already in the right place. But the datasheet of the microphone mentions 18 bit resolution on 24 bits format, with 0 padded LSB bits. I've seen others online shift by 14 to keep the 18 MSB. Why did you choose 12 ?
3. The I2S driver you used is deprecated. I've migrated to the I2S std API with ESP IDF. The results are somewhat similar with my code to when I try yours in Arduino IDE and watch either the plotter or the WAV in Audacity.

Here's my version of your code. I'd really be interested to see how you got it working.

The values I'm reading are not normal:

Code: Select all

F38C0000
F38D8000
F38D8000
F38D8000
F38D0000
F38B8000
F38A0000
F38B8000
They're almost always negative unless I snap next to the microphone, the left letter turns into and E indicating a positive integer.

A mic should be relatively centered on 0, and the amplitudes don't match.

Also, it seems whether I set the timing regs and bit shift regs or not, the result is the same. I don't get it. When I set it before configuring the channel, before enabling i2s, or after all of those, I see no drastic change in the bytes printed and the left F always stays there.

Code: Select all

#include <soc/i2s_reg.h>
#include <stdio.h>
#include <string.h>

#include "driver/i2s_std.h"
#include "esp_adc/adc_continuous.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "soc/i2s_reg.h"

#define MIC_I2S_PORT I2S_NUM_1
#define SAMPLE_RATE 8000
#define BUF_LEN 2048

i2s_chan_handle_t rx_chan = NULL;
i2s_chan_handle_t tx_chan = NULL;

static const char *TAG = "APP";

static void init_i2s_mic() {
  i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(MIC_I2S_PORT, I2S_ROLE_MASTER);
  rx_chan_cfg.auto_clear = true;
  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_32BIT, I2S_SLOT_MODE_MONO),
      .gpio_cfg = {
          .mclk = I2S_GPIO_UNUSED,
          .bclk = 5,
          .ws = 7,
          .dout = I2S_GPIO_UNUSED,
          .din = 6,
          .invert_flags = {
              .mclk_inv = false,
              .bclk_inv = false,
              .ws_inv = false,
          },
      },
  };

  ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &rx_std_cfg));
  /*
   * FIXES for SPH0645
   * https://www.esp32.com/viewtopic.php?t=4997
   * https://hackaday.io/project/162059-street-sense/log/160705-new-i2s-microphone
   * 2025 solution for ESP32-S3: http://forum.esp32.com/viewtopic.php?t=35402
   * #include "soc/i2s_reg.h"
   */
  // REG_SET_BIT(  I2S_TIMING_REG(I2S_NUM),BIT(9)); // ESP32-S2
  REG_SET_BIT(I2S_RX_TIMING_REG(MIC_I2S_PORT), BIT(1));  // ESP32-S3 - Set delay to falling edge (2)
  // REG_SET_BIT(I2S_CONF_REG(MIC_I2S_PORT), I2S_RX_MSB_SHIFT);   // ESP32-S2
  REG_SET_BIT(I2S_RX_CONF1_REG(MIC_I2S_PORT), I2S_RX_MSB_SHIFT);  // ESP32-S3 - enable WS falling edge 1 bit clock before data MSB

  ESP_ERROR_CHECK(i2s_channel_enable(rx_chan));
}

void i2s_read_task() {
  size_t rx_len = 0;
  int32_t rx_data[BUF_LEN] = {0};
  int16_t tx_data[BUF_LEN] = {0};

  while (1) {
    if (i2s_channel_read(rx_chan, (uint8_t *)rx_data, BUF_LEN * sizeof(int32_t), &rx_len, 1000) != ESP_OK) {
      continue;
    }

    size_t samplesRead = rx_len / sizeof(int32_t);

    for (int i = 0; i < samplesRead; i++) {
      rx_data[i] = rx_data[i] >> 12;
      tx_data[i] = (int16_t)(rx_data[i] & 0xFFFF);
      printf("%" PRId16 "\n", tx_data[i]);
    }

    vTaskDelay(1);
  }
}

void app_main(void) {
  init_i2s_mic();
  if (rx_chan == NULL) {
    ESP_LOGE(TAG, "could not init rx chan");
    return;
  }

  xTaskCreate(i2s_read_task, "i2s_read_task", 16000, NULL, 5, NULL);
}

Who is online

Users browsing this forum: No registered users and 19 guests