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!

Code: sph0645.ino Select all


#include <driver/i2s.h>
#include <soc/i2s_reg.h>

#define BUF_LEN 2048
#define SAMPLE_RATE 44100
#define I2S_PORT I2S_NUM_0

#define DATA_PIN 7
#define BCLK_PIN 4
#define WS_PIN 17

void setup() {
Serial.begin(115200);

const i2s_config_t micCfg = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
// My microphone is configured to send on the right channel.
// A bug in ESP-IDF swaps the two, so this is set to left.
// See https://github.com/espressif/esp-idf/issues/6625

.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0,
};

i2s_pin_config_t micPins = {
.bck_io_num = BCLK_PIN,
.ws_io_num = WS_PIN,
.data_out_num = -1,
.data_in_num = DATA_PIN,
};

esp_err_t err;
err = i2s_driver_install(I2S_PORT, &micCfg, 0, NULL);
if (err != ESP_OK) {
Serial.println("I2S driver initialization failed");
while (true);
}

// Delay by falling edge
REG_SET_BIT(I2S_RX_TIMING_REG(I2S_PORT), BIT(1));
// Force Philips mode
REG_SET_BIT(I2S_RX_CONF1_REG(I2S_PORT), I2S_RX_MSB_SHIFT);

err = i2s_set_pin(I2S_PORT, &micPins);
if (err != ESP_OK) {
Serial.println("Setting pins failed");
while (true);
}
}

void loop() {
static int32_t buf32[BUF_LEN];
static int16_t buf16[BUF_LEN];
size_t bytesRead;

esp_err_t err = i2s_read(I2S_PORT, (uint8_t*) buf32, BUF_LEN*sizeof(int32_t), &bytesRead, portMAX_DELAY);
if (err != ESP_OK) {
Serial.println("Error reading from microphone");
Serial.println(err);
return;
}

size_t samplesRead = bytesRead / sizeof(int32_t);
for (int i=0; i<samplesRead; i++) {
// Discard unused lower 12 bits. Sign bit is already where it needs to be.
buf32[i] >>= 12;
buf16[i] = (int16_t) (buf32[i] & 0xFFFF);
}

// Do stuff with signed 16-bit little-endian samples.
}

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 57474 times

Code: Untitled.cpp Select all


#include <Arduino.h>
#include <driver/i2s.h>
#include <soc/i2s_reg.h>

#define BUF_LEN 2048
#define SAMPLE_RATE 16000
#define I2S_PORT I2S_NUM_0

#define DATA_PIN 38
#define BCLK_PIN 21
#define WS_PIN 47

void setup() {
delay(2000);
USBSerial.begin(115200);

const i2s_config_t micCfg = {
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
// My microphone is configured to send on the right channel.
// A bug in ESP-IDF swaps the two, so this is set to left.
// See https://github.com/espressif/esp-idf/issues/6625

.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 8,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0,
};

i2s_pin_config_t micPins = {
.bck_io_num = BCLK_PIN,
.ws_io_num = WS_PIN,
.data_out_num = -1,
.data_in_num = DATA_PIN,
};

esp_err_t err;
err = i2s_driver_install(I2S_PORT, &micCfg, 0, NULL);
if (err != ESP_OK) {
USBSerial.println("I2S driver initialization failed");
while (true)
;
}

// Delay by falling edge
REG_SET_BIT(I2S_RX_TIMING_REG(I2S_PORT), BIT(1));
// Force Philips mode
REG_SET_BIT(I2S_RX_CONF1_REG(I2S_PORT), I2S_RX_MSB_SHIFT);

err = i2s_set_pin(I2S_PORT, &micPins);
if (err != ESP_OK) {
USBSerial.println("Setting pins failed");
while (true)
;
}
}

void loop() {
static int32_t buf32[BUF_LEN];
static int16_t buf16[BUF_LEN];
size_t bytesRead;

esp_err_t err =
i2s_read(I2S_PORT, (uint8_t*)buf32, BUF_LEN * sizeof(int32_t),
&bytesRead, portMAX_DELAY);
if (err != ESP_OK) {
USBSerial.println("Error reading from microphone");
USBSerial.println(err);
return;
}

size_t samplesRead = bytesRead / sizeof(int32_t);
for (int i = 0; i < samplesRead; i++) {
// Discard unused lower 12 bits. Sign bit is already where it needs to
// be.
buf32[i] >>= 12;
buf16[i] = (int16_t)(buf32[i] & 0xFFFF);
}

for (int i = 0; i < samplesRead; i++) {
// 输出样本值到串口终端
USBSerial.println(buf32[i]);
}
USBSerial.println("ok\n");
// Do stuff with signed 16-bit little-endian samples.
}

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);
}

StevenCellist
Posts: 2
Joined: Sat Nov 27, 2021 4:45 pm

Re: SPH0645LM4H with ESP32-S3

Postby StevenCellist » Mon Sep 08, 2025 3:27 pm

@jdoei2s I believe the only actual change required is the expand the

Code: Select all

I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG
, as it enables Left and Right by default. This works for me:

Code: Select all

.slot_cfg = { 
      .data_bit_width = I2S_DATA_BIT_WIDTH_32BIT, 
      .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, 
      .slot_mode = I2S_SLOT_MODE_MONO, 
      .slot_mask = I2S_STD_SLOT_LEFT, 		// left channel enabled
      .ws_width = I2S_DATA_BIT_WIDTH_32BIT, 
      .ws_pol = false, 
      .bit_shift = true, 
      .left_align = true, 
      .big_endian = false, 
      .bit_order_lsb = false
    },
With this, it also appears that the two register writes are not necessary anymore, as I think the `bit_shift` field takes care of this. From my (limited) testing, I don't see any obvious results with or without register writes.

Who is online

Users browsing this forum: No registered users and 1 guest