Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XIAO ESP32-C3: I2S takes 250ms before a INMP441 microphone can give accurate readings with I2S. #8207

Closed
1 task done
zackees opened this issue May 16, 2023 · 37 comments
Closed
1 task done
Assignees
Labels
Area: Peripherals API Relates to peripheral's APIs. Resolution: Awaiting response Waiting for response of author Type: Question Only question
Milestone

Comments

@zackees
Copy link

zackees commented May 16, 2023

Confirmed working in esp32 idf 5.1 alpha 3.0.0

The INMP441 mems mic is the culprit. The working theory is that when the I2S clock stops the microphone goes into a power down mode and then generates noise during power up when I2S resumes. See final comment for possible work around using LEDC to generate a pseudo clock to keep the device awake.

Example:

Now entering light sleep
Exited light sleep, now outputting sound levels for one second.
vol: 2874
vol: 1315
vol: 579
vol: 887
vol: 764
vol: 587
vol: 399
vol: 270
vol: 231
vol: 134
vol: 50
vol: 68
vol: 50
vol: 31
vol: 38
vol: 36
vol: 41
vol: 35
vol: 33
vol: 47

Board

XIAO ESP32-C3 with battery

Device Description

https://www.amazon.com/dp/B0B94JZ2YF?psc=1&ref=ppx_yo2ov_dt_b_product_details

I'm using Platform io. Here is my ini file

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:esp32c3]
platform = espressif32@^6.2.0
board = seeed_xiao_esp32c3
framework = arduino

; change microcontroller
board_build.mcu = esp32c3
;upload_speed = 115200

; change WiFi firmware
board_build.variant = esp32c3

monitor_speed = 115200

board_build.f_cpu = 80000000L

build_flags = 
    -D CONFIG_PM_ENABLE
    -D CONFIG_PM_USE_RTC
    -D CONFIG_PM_LIGHTSLEEP_RTC_OSC_CAL_INTERVAL=8

I have a INMP441 mems mic:

https://www.amazon.com/AITRIP-Omnidirectional-Microphone-Precision-Interface/dp/B092HWW4RS/ref=sr_1_3?keywords=INMP441&sr=8-3

For the life of me I cannot get the IS2 peripheral to power back on quickly. I have to wait about 1/3rd of a second before the microphone stabilizes. I've tried so many different things: changing the clock signal. Changing the board frequency. Nothing seems to work to make the microphone better.

Hardware Configuration

#ifndef _DEFS_H_
#define _DEFS_H_

#include <iostream>
using std::cout;
using std::endl;

#define MAX_ANALOG_READ 1023

#define LED_PIN 9  // Pin is held high on startup.
#define LED_PIN_IS_SINK true
#define LED_0 0  // Led channel name
#define LED_PWM_FREQ 2000
#define LED_PWM_RESOLUTION 14  // Max on ESP32-c3 XIOA

#define TIME_PWM_CYCLE_MS  3 // Flickers at 1ms
#define TIME_PWM_TRANSITION_MS 3  // 60 fps

#define PIN_I2S_WS GPIO_NUM_3  // TODO change this pins
#define PIN_IS2_SD GPIO_NUM_2  // TODO change this pins
#define PIN_I2S_SCK GPIO_NUM_4  // TODO change this pins
#define PIN_AUDIO_PWR GPIO_NUM_5  // TODO change this pins

#define I2S_NUM I2S_NUM_0
// #define IS2_AUDIO_BUFFER_LEN 1024 // max samples for i2s_read
#define IS2_AUDIO_BUFFER_LEN 1024 // max samples for i2s_read
#define AUDIO_BIT_RESOLUTION 16
#define AUDIO_SAMPLE_RATE (44100ul / 1)
#define AUDIO_CHANNELS 1 // Not tested with 2 channels
#define AUDIO_DMA_BUFFER_COUNT 3
#define AUDIO_RECORDING_SECONDS 1

#define TIME_BEFORE_LIGHT_SLEEP_MS 1000
#define LIGHT_SLEEP_TIME_uS uint32_t(1000 * 100) // 100 ms.


#define ASSERT_IMPL(x, msg, file, lineno)                                                                       \
  do                                                                                                            \
  {                                                                                                             \
    if (!(x))                                                                                                   \
    {                                                                                                           \
      std::cout << "#############\n# ASSERTION FAILED: " << file << ":" << lineno << "\n# MSG: " << msg << "\n#############\n"; \
      configASSERT(x);                                                                                          \
    }                                                                                                           \
  } while (false);

#define ASSERT(x, msg) ASSERT_IMPL(x, msg, __FILE__, __LINE__)


#endif  // _DEFS_H_

Version

v2.0.9

IDE Name

PlatformIO

Operating System

Max OS 13 on M1

Flash frequency

default

PSRAM enabled

no

Upload speed

115200

Description

I2S has to wait a long time before it stabilizes after a light sleep. I have to throw away the next 12 buffers of 1024, 44100 hz audio data in mono format. I thought it was the microphone but it appears to be the IS2 bus.

Sketch

++

#include <iostream>

#include "audio.h"
#include "defs.h"
#include <Arduino.h>
#include <stdint.h>
#include <driver/i2s.h>
#include "ringbuffer.hpp"
#include "alloc.h"
#include "buffer.hpp"
#include "task.h"
#include <limits>
#include "time.h"
#include <stdio.h>
#include <atomic>

#include "alloc.h"

using namespace std;

#define ENABLE_AUDIO_TASK 0

#define AUDIO_TASK_SAMPLING_PRIORITY 7

#define AUDIO_BUFFER_SAMPLES (AUDIO_RECORDING_SECONDS * AUDIO_SAMPLE_RATE * AUDIO_CHANNELS)

// During power
#define POWER_ON_TIME_MS 85  // Time to power on the microphone according to the datasheet.
#define POWER_OFF_TIME_MS 85  // Time to power off the microphone is 43 ms but we round up.
                              // Note that during power down, no data should be attempted to be read
                              // or the ESD diodes will be activated and the microphone will be damaged.

namespace
{
  static_assert(AUDIO_BIT_RESOLUTION == 16, "Only 16 bit resolution is supported");
  static_assert(AUDIO_CHANNELS == 1, "Only 1 channel is supported");
  static_assert(sizeof(audio_sample_t) == 2, "audio_sample_t must be 16 bit");
  std::atomic<float> s_loudness_dB;
  std::atomic<uint32_t> s_loudness_updated;
  int garbage_buffer_count = 0;

  const i2s_config_t i2s_config = {
      .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
      .sample_rate = AUDIO_SAMPLE_RATE,
      .bits_per_sample = i2s_bits_per_sample_t(AUDIO_BIT_RESOLUTION),
      .channel_format = i2s_channel_fmt_t(I2S_CHANNEL_FMT_ONLY_RIGHT),
      .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
      .intr_alloc_flags = 0,
      .dma_buf_count = AUDIO_DMA_BUFFER_COUNT,
      .dma_buf_len = IS2_AUDIO_BUFFER_LEN,
      .use_apll = false,
      //.tx_desc_auto_clear = true,
      //.fixed_mclk = 4000000ul,
  };

  const i2s_pin_config_t pin_config = {
      .bck_io_num = PIN_I2S_SCK,
      .ws_io_num = PIN_I2S_WS,
      .data_out_num = I2S_PIN_NO_CHANGE,
      .data_in_num = PIN_IS2_SD};

  void i2s_audio_init()
  {

    i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM, &pin_config);
    //i2s_zero_dma_buffer(I2S_NUM_0);
    //i2s_start(I2S_NUM_0);
  }

  void i2s_audio_shutdown()
  {
    //i2s_stop(I2S_NUM_0);
    i2s_driver_uninstall(I2S_NUM_0);
  }



  double audio_loudness_to_dB(double rms_loudness)
  {
    // This is a rough approximation of the loudness to dB scale.
    // The data was taken from the following video featuring brown
    // noise: https://www.youtube.com/watch?v=hXetO_bYcMo
    // This linear regression was done on the following data:
    // DB | LOUDNESS
    // ---+---------
    // 50 | 15
    // 55 | 22
    // 60 | 33
    // 65 | 56
    // 70 | 104
    // 75 | 190
    // 80 | 333
    // This will produce an exponential regression of the form:
    //  0.0833 * std::exp(0.119 * x);
    // Below is the inverse exponential regression.
    const float kCoefficient = 0.119f;
    const float kIntercept = 0.0833f;
    const float kInverseCoefficient = 1.0f / kCoefficient; // Maybe faster to precompute this.
    const float kInverseIntercept = 1.0f / kIntercept;
    return std::log(rms_loudness * kInverseIntercept) * kInverseCoefficient;
  }

  float calc_rms_loudness(const audio_sample_t *samples, size_t num_samples)
  {
    uint64_t sum_of_squares = 0;
    for (size_t i = 0; i < num_samples; ++i)
    {
      sum_of_squares += samples[i] * samples[i];
    }
    double mean_square = static_cast<double>(sum_of_squares) / num_samples;
    return static_cast<float>(std::sqrt(mean_square));
  }

  size_t read_raw_samples(audio_sample_t (&buffer)[IS2_AUDIO_BUFFER_LEN])
  {
    size_t bytes_read = 0;
    i2s_event_t event;

    uint32_t current_time = millis();
    esp_err_t result = i2s_read(I2S_NUM_0, buffer, sizeof(buffer), &bytes_read, 0);
    if (result == ESP_OK)
    {
      if (bytes_read > 0)
      {
        //cout << "Bytes read: " << bytes_read << endl;
        const size_t count = bytes_read / sizeof(audio_sample_t);
        return count;
      }
    }
    return 0;
  }

  bool update_audio_samples()
  {
    audio_sample_t buffer[IS2_AUDIO_BUFFER_LEN] = {0};
    bool updated = false;
    while (true) {
      size_t samples_read = read_raw_samples(buffer);
      if (samples_read <= 0)
      {
        break;
      }
      if (garbage_buffer_count > 0) {
        --garbage_buffer_count;
        continue;
      }
      updated = true;
      float rms = calc_rms_loudness(buffer, samples_read);
      s_loudness_dB.store(audio_loudness_to_dB(rms));
      s_loudness_updated.store(millis());
    }
    return updated;
  }
  bool s_audio_initialized = false;
} // anonymous namespace

void audio_task(void *pvParameters)
{
  while (true)
  {
    // Drain out all pending buffers.
    while (update_audio_samples())
    {
      ;
    }
    delay_task_ms(7);
  }
}

void audio_init(bool wait_for_power_on)
{
  if (s_audio_initialized)
  {
    cout << "Audio already initialized." << endl;
    return;
  }
  s_audio_initialized = true;
  pinMode(PIN_AUDIO_PWR, OUTPUT);
  digitalWrite(PIN_AUDIO_PWR, HIGH);  // Power on the IS2 microphone.
  i2s_audio_init();
  if (wait_for_power_on) {
    delay_task_ms(POWER_ON_TIME_MS);  // Wait for the microphone to power on.
  }


  // start a task to read the audio samples using psram
  //TaskCreatePsramPinnedToCore(
  //    audio_task, "audio_task", 4096, NULL, AUDIO_TASK_SAMPLING_PRIORITY, NULL, 0);

  #if ENABLE_AUDIO_TASK
  xTaskCreatePinnedToCore(
      audio_task, "audio_task", 4096, NULL, AUDIO_TASK_SAMPLING_PRIORITY, NULL, 0);
  #endif
}

  // UNTESTED
  void audio_shutdown()
  {
    //i2s_stop(I2S_NUM_0);          // Stop the I2S
    //i2s_driver_uninstall(I2S_NUM_0); // Uninstall the driver
    s_audio_initialized = false;
  }


audio_state_t audio_update()
{
  uint32_t start_time = millis();
  update_audio_samples();
  #if ENABLE_AUDIO_TASK
  for (int i = 0; i < 3; i++)
  {
    vPortYield();
  }
  #endif
  audio_state_t state = audio_state_t(audio_loudness_dB(), s_loudness_updated.load());
  return state;
}

float audio_loudness_dB() { return s_loudness_dB.load(); }


// Audio

void audio_loudness_test()
{
  Buffer<double> sample_buffer;
  sample_buffer.init(32);
  cout << "Done initializing audio buffers" << endl;

  while (true)
  {
    // This is a test to see how loud the audio is.
    // It's not used in the final product.
    audio_sample_t buffer[IS2_AUDIO_BUFFER_LEN] = {0};
    size_t samples_read = read_raw_samples(buffer);
    if (samples_read > 0)
    {
      double rms_loudness = calc_rms_loudness(buffer, samples_read);
      sample_buffer.write(&rms_loudness, 1);
      double avg = 0;
      for (size_t i = 0; i < sample_buffer.size(); ++i)
      {
        avg += sample_buffer[i];
      }
      avg /= sample_buffer.size();
      String loudness_str = String(avg, 3);
      // Serial.printf("Avg rms loudness: %s\n", loudness_str.c_str());
      float dB = audio_loudness_to_dB(avg);
      String dB_str = String(dB, 3);
      Serial.printf("dB: %s, loudness: %s\n", dB_str.c_str(), loudness_str.c_str());
      // buffer->clear();
      sample_buffer.clear();
    }
  }
}

void audio_enter_light_sleep() {
  audio_sample_t buffer[IS2_AUDIO_BUFFER_LEN] = {0};
  //i2s_stop(I2S_NUM_0);          // Stop the I2S
  i2s_audio_shutdown();
  pinMode(PIN_I2S_SCK, OUTPUT);  // This is all desperation trying to reset pin state so that when I2S restarts it works.
  digitalWrite(PIN_I2S_SCK, LOW);
  pinMode(PIN_I2S_WS, OUTPUT);
  digitalWrite(PIN_I2S_WS, LOW);
  pinMode(PIN_IS2_SD, OUTPUT);
  digitalWrite(PIN_IS2_SD, LOW);
  // i2s_zero_dma_buffer(I2S_NUM_0);
  //digitalWrite(PIN_AUDIO_PWR, HIGH);
  gpio_hold_en(PIN_I2S_WS);
  gpio_hold_en(PIN_IS2_SD);
  gpio_hold_en(PIN_I2S_SCK);
  gpio_hold_en(PIN_AUDIO_PWR);
}

void audio_exit_light_sleep() {

  //delay(8);
  i2s_audio_init();
  gpio_hold_dis(PIN_I2S_WS);
  gpio_hold_dis(PIN_IS2_SD);
  gpio_hold_dis(PIN_I2S_SCK);
  // gpio_hold_dis(PIN_AUDIO_PWR);

  // i2s_start(I2S_NUM_0);
  i2s_audio_init();
  //delay(160);
  //audio_sample_t buffer[IS2_AUDIO_BUFFER_LEN] = {0};
  //uint32_t future_time = millis() + 180;
  //while (millis() < future_time) {
  //  read_raw_samples(buffer);
  //}
  //delay(180);
  //delay(POWER_ON_TIME_MS * 2);
  garbage_buffer_count = 14; // For some reason it takes a few buffers to get the audio going again.
}

Debug Message

None. But the recorded sound levels are through the roof for the first few buffers then decrease.

Other Steps to Reproduce

No response

I have checked existing issues, online documentation and the Troubleshooting Guide

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.
@zackees zackees added the Status: Awaiting triage Issue is waiting for triage label May 16, 2023
@zackees zackees changed the title XIAO ESP32-C3: I2S takes a MASSIVE amount of time to come back to life XIAO ESP32-C3: I2S takes 250ms before a mems microphone can give accurate readings May 16, 2023
@me-no-dev
Copy link
Member

Since you are using ESP-IDF's API, I suggest you file an issue in their repo. You can say that you are running on ESP-IDF v4.4.4

@SuGlider
Copy link
Collaborator

My wild guess about it is that entering/returning from light sleep changes the source frequency of the I2S peripheral.
Thus, the first 300ms, the I2S peripheral may be running in the wrong frequency and it looks like reading garbage data up to the system starts back to run in the correct APB Freq.

Maybe (just a guess) if the I2S is set to use a Clock Source that is not APB dependent, it may not be affected by this change.

.use_apll = true, instead of false?

The new IDF 5.1 I2S driver documentation talk about using APLL... (but remember that Arduino 2.0.9 uses IDF 4.4.4).
This is just for reading and understanding my "wild guess".
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#i2s-clock

@SuGlider
Copy link
Collaborator

Anyway.... this exactly same issue also happens with UART when returning from Light Sleep. The first bytes read are bad...
It is documented. Maybe.... it just needs to document that I2S has a similar behavior, like the one you are experimenting.

@zackees
Copy link
Author

zackees commented May 17, 2023

I used .use_apll = true as well and used a fixed clock frequency. I also tried different CPU frequencies. It seems that 80 MHz is better and I'm able to minimize the number of 1k buffers I have to discard ([email protected], single channel, 16-bit) down from 16 to about 10. Adjusting the frame down to 512, 128 bytes doesn't seem to affect the length of time that the I2S is generating bad data.

It's a shame because the MEMS mic performs so well otherwise. I tried putting a medium-sized ceramic decoupling capacitor at the power pins of the MEMS, but this didn't do anything.

If I want to get proper light sleep I'll have to use an analog microphone and sample it with an ISR.

@SuGlider
Copy link
Collaborator

I guess that those 300ms after returning from light sleep are very important for the project.
Why using an analog microphone would make it work?

Can you imagine a process/work around that fits better the needs of the project?
Stopping the I2S before going into light sleep and starting it after returning would work?

@zackees
Copy link
Author

zackees commented May 17, 2023

It works because I can just keep the analog microphone on in a powered state while the CPU sleeps and then immediately start reading again after the processor wakes.

This will allow me to microsleep the CPU in the main loop and immediately execute the next loop iteration without a delay. This should massively reduce power consumption.

@SuGlider
Copy link
Collaborator

I'm not sure that ESP32 ADC will not also take some time to read accurate data...
It uses SAR algorithm and it may also depend on the clock source.

A potential reference: espressif/esp-idf#8287

@SuGlider
Copy link
Collaborator

A possible alternative strategy to go low power and get MIC data could be to use the ULP to read the data... but the C3 has no ULP.
It could be possible with an S3 instead, using its RISC-V ULP.
https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/system/ulp-risc-v.html

@SuGlider
Copy link
Collaborator

Another way to think about low power, could be run the C3 at 10MHz, no sleep needed.
It may be possible to read the I2S MIC at this CPU clock and maybe it consumes very low current.
At 10MHz, Wifi and BLE won't work... but if necessary, it can change the CPU frequency back to 80MHz in order to send some data to a server, and then back to 10MHz to read the MIC... just thinking here.

All of it will need experimentation and measures.

@zackees
Copy link
Author

zackees commented May 17, 2023

It's not possible to read the I2S under 80 mhz.

@SuGlider
Copy link
Collaborator

It's not possible to read the I2S under 80 mhz.

With some work to code it... RMT may do it. Not easy... but it may be possible to decode an I2S MIC signal...
Anyway, it may be a huge challenge and not work properly after a big effort to code it. Too risky, maybe.

@zackees
Copy link
Author

zackees commented May 17, 2023

Sounds complicated. Reading from an analog condenser mic is well known and the components are cheap. I only need it for loudness and some fft. While the mems produced excellent audio, the condenser mic will work well enough.

@SuGlider SuGlider added Type: Question Only question and removed Status: Awaiting triage Issue is waiting for triage labels May 17, 2023
@zackees
Copy link
Author

zackees commented May 17, 2023

It's not possible to read the I2S under 80 mhz.

With some work to code it... RMT may do it. Not easy... but it may be possible to decode an I2S MIC signal... Anyway, it may be a huge challenge and not work properly after a big effort to code it. Too risky, maybe.

Do you know of any repositories that have an I2S implementation in software?

@SuGlider
Copy link
Collaborator

Do you know of any repositories that have an I2S implementation in software?

No. I think that there may be some MCU emulator software that does it, but it may not be applicable within an MCU.

@craigyjp
Copy link

craigyjp commented Jul 4, 2023

Can you confirm you are using a Seeed Studio ESP32-C3, I have been told that I2S is not supported as the pins do not exist, but it seems you are mapping them onto pins 2,3,4,5 for i2s

@zackees
Copy link
Author

zackees commented Jul 4, 2023

Confirmed, I am using an ESP32-C3 Xiao. The ESP32 allows you to use many pins for the protocol.

@VojtechBartoska
Copy link
Collaborator

same here, can you please help with the triage @me-no-dev? Thanks

@zackees
Copy link
Author

zackees commented Aug 18, 2023

@me-no-dev

Here is a public code repo + pinout of a simple device using IS2 in a test setup displaying the bug.

https://github.com/zackees/xiao-inmp441-test

This repo is designed to showcase the IS2 bug above. If changes to this code base are required then please fork and issue a pull request.

The code that will likely need to be adjusted will be audio_enter_light_sleep() and audio_exit_light_sleep() found here:
https://github.com/zackees/xiao-inmp441-test/blob/f75dde1a04dfca01816e3d3de98875db0cc00173/src/audio.cpp#L240
And here
https://github.com/zackees/xiao-inmp441-test/blob/f75dde1a04dfca01816e3d3de98875db0cc00173/src/audio.cpp#L254

The pinouts, included in the repo, are as follows:

#define PIN_I2S_WS GPIO_NUM_7
#define PIN_IS2_SD GPIO_NUM_8
#define PIN_I2S_SCK GPIO_NUM_4
#define PIN_AUDIO_PWR GPIO_NUM_10

With microphone L/R -> VCC

Entering the low power test

Run the test software. To enter the low power IS2 test hold the button down for a number of seconds. When the low power test enters it will check the button press, and then again 1 second later. If activated it will light sleep the MCU then dump out the audio dB readings for 300 ms. like this:

Now doing a microphone check. In a broken state the IS2 will read high values (>70 dB) before settling down to ambient noise levels.
dB: 87.0328
dB: 87.0328
dB: 86.664
...
db: 50.233

Notice that the dB readings are high and then settle down. I've thoroughly investigated this for my software error but consistently found that I could alleviate the error by adding a delay(300) after the audio_exit_light_sleep() call.

Building a test device

The device can be built with just the microphone and all the other components in the diagram can be ignored.

image

@zackees
Copy link
Author

zackees commented Aug 19, 2023

Sorry for the wall of text. Bug reproduction can be hard and I wanted to make sure it was as easy as possible with a working codebase that reproduces the issue clearly.

@me-no-dev
Copy link
Member

@zackees @VojtechBartoska given that all code is actually the ESP-IDF API, I will suggest that you contact the IDF team in their repository for help. Not sure which version ESP-IDF you are using, but 2.0.9/2.0.10 use IDF 4.4.4 and 2.0.11 use IDF 4.4.5

@zackees
Copy link
Author

zackees commented Aug 22, 2023

This is my platform io .ini file:

[env:esp32c3]
platform = espressif32@^6.3.2
board = seeed_xiao_esp32c3
framework = arduino

What is the proper place to file this bug?

@VojtechBartoska VojtechBartoska added the Area: Peripherals API Relates to peripheral's APIs. label Oct 4, 2023
@VojtechBartoska
Copy link
Collaborator

VojtechBartoska commented Oct 23, 2023

Hi, you can try 3.0.0 alpha2 development release as well, there is new implementation of I2S library.

@VojtechBartoska VojtechBartoska removed the Status: Awaiting triage Issue is waiting for triage label Oct 23, 2023
@VojtechBartoska VojtechBartoska added the Resolution: Awaiting response Waiting for response of author label Oct 23, 2023
@zackees
Copy link
Author

zackees commented Oct 23, 2023

Do I read this correctly that this issue will be fixed in the next release?

@VojtechBartoska
Copy link
Collaborator

VojtechBartoska commented Dec 12, 2023

Yes, there is new I2S library so you can try it out. Please let us know if it fixes your issue. Thanks

@zackees
Copy link
Author

zackees commented Dec 12, 2023

How do I get this library?

link?

@VojtechBartoska
Copy link
Collaborator

It is the part of https://github.com/espressif/arduino-esp32/releases/tag/3.0.0-alpha3 release. Related Pull Request is here: #8714

@zackees
Copy link
Author

zackees commented Dec 15, 2023

Thank you.

Is this available on platform io? The following platformio.ini file fails to build:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:esp32c3]
platform = https://github.com/espressif/arduino-esp32.git#3.0.0-alpha3
board = seeed_xiao_esp32c3
framework = arduino



upload_speed = 921600

; change microcontroller
board_build.mcu = esp32c3
;upload_speed = 115200

; change MCU frequency
; board_build.f_cpu = 160000000L

; change WiFi firmware
board_build.variant = esp32c3

monitor_filters = 
	default
	esp32_exception_decoder

monitor_speed = 115200

board_build.f_cpu = 80000000L

build_flags = 
    -D CONFIG_PM_ENABLE
    -D CONFIG_PM_USE_RTC
    -D CONFIG_PM_LIGHTSLEEP_RTC_OSC_CAL_INTERVAL=8
    -D FIXED_POINT=16
    -D KISS_FFT_USE_ESP32_PSRAM=0

Do you know what the solution is? I'll test this out as soon as I have access.

@zackees
Copy link
Author

zackees commented Jan 25, 2024

I'm trying to test this fix but I have at this point re-write my app into the format used by the idf.py tool first.

If anyone else is having trouble installing idf.py in a cross-platform way then I recommend a tool I made that make the process easy and works under Windows/Linux/Mac.

https://github.com/zackees/idf-install

However, it looks like the expressif arduino package is about to land with support for 5.1, which from the changelist includes this fix above. However currently I can't test it out because of a missing platform.json.

Here's the bug report:
#9175

If this turns out to be an easy fix then I'd love to test out the I2S fix before it goes live.

@zackees
Copy link
Author

zackees commented Jan 27, 2024

Good news, there is an installable platformio version of IDF v5.1 with the I2S fix.

get it with this pull request. I verified it my self and now I have to port ledc, I2C etc

platformio/platform-espressif32#1281

@zackees
Copy link
Author

zackees commented Jan 28, 2024

IDF toolchain for platformio just became available thanks to a pull request.

Add these to your ini file:

platform = https://github.com/sgryphon/platform-espressif32.git#sgryphon/add-esp32-arduino-libs
platform_packages =
    platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#master
    platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1

@zackees
Copy link
Author

zackees commented Jan 30, 2024

Okay so follow up. I installed the unofficial IDF 5.1 libs for platform IO and then went through each device driver and ported it to the the new v5.1 API.

LEDC ported fine. But for the life of me, I cannot get the I2S to work with the INMP441 with the new API. It works FINE with the 4.x legacy I2S API.

At first I did a straight port. The data signal is delayed by one frame as defined by the Philips standard, which apparently was the default for the legacy API. The straight port did not work. So I spent about another 4-6 hours diving deep, look at the source code and trying every combination I could think of of clock, pin modes, etc.

Now that I've gone deep, I have absolutely no idea how the legacy drivers were even working. It appears that every copy and paste of the 4.x I2S api appears wrong. INMP441 works with 16 bit audio in the legacy driver, but it's clear it using 24 bit audio in a 32 bit WS frame, delayed by one clock cycle (Philips mode).

I've tried putting the 5.1 api into I2S master mode, slave mode. My INMP441 is set to be right channel only. I saw that in the 4.x lib there was a time where the L/R channels were swapped. So I've tried both but neither work. Bitshift for Philips mode. Left align. Shifting clock values for the MCU. I've exhausted every single thing I could think of and nothing works.

BUG?

What's really strange is that when i query the info state of the channel like this:

err = i2s_channel_init_std_mode(rx_chan, &i2s_std_cfg_rx);
ESP_ERROR_CHECK(err);
i2s_chan_info_t info;
err = i2s_channel_get_info(rx_chan, &info);
ESP_ERROR_CHECK(err);
delay(1000);
Serial.printf("I2S channel info: %d, %d, %d, %d, %d\n", info.id, info.role, info.dir, info.mode, info.pair_chan);
delay(500);

I see that info.dir is 1

So the direction is in TX mode instead of RX mode. Not sure if this is significant.

I'm going to try and get a repro case in a public github. For now, I just have this brain dump of the halfway ported I2S i2s_device.cpp file.

Notes: Yes, legacy used role master and i'm using role slave. But I've tried both and neither works. Yes, I've also tried 48kh, 44.1khz, 16khz for audio. Bit depth 16/24/32. Bus bit depth 16/32. I just can't get a signal out of the microphone. However after a while I will start getting garbage readings but they are very very slow. I've tried SLOT stereo/mono, I've tried left and right channels (my mic is configured for

#include <iostream>

#include "audio.h"
#include "defs.h"
// #include <driver/i2s.h>
#include <driver/i2s_std.h>
#include "i2s_device.h"
#include "driver/gpio.h"
namespace
{
  bool g_is_initialized = false;
  static_assert(AUDIO_BIT_RESOLUTION == 16, "Only 16 bit resolution is supported");
  static_assert(AUDIO_CHANNELS == 1, "Only 1 channel is supported");
  static_assert(sizeof(audio_sample_t) == 2, "audio_sample_t must be 16 bit");
  i2s_chan_handle_t     rx_chan = NULL;
  i2s_chan_handle_t tx_chan = NULL;
  //i2s_chan_config_t     i2s_chan_cfg_rx = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); // stores I2S channel values
  i2s_chan_config_t  i2s_chan_cfg_rx = {
    .id = I2S_NUM_0,
    .role = I2S_ROLE_SLAVE,
    .dma_desc_num = 6,
    .dma_frame_num = 240,
    .auto_clear = false,
  };
  i2s_std_config_t      i2s_std_cfg_rx = {
    //.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(AUDIO_SAMPLE_RATE),
    .clk_cfg = {
        .sample_rate_hz = 44100ul,
        .clk_src = i2s_clock_src_t(I2S_CLK_SRC_PLL_160M),
        .mclk_multiple = I2S_MCLK_MULTIPLE_384,
    },


    //.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_24BIT, I2S_SLOT_MODE_MONO),
    /*
#define I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(bits_per_sample, mono_or_stereo) { \
    .data_bit_width = bits_per_sample, \
    .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \
    .slot_mode = mono_or_stereo, \
    .slot_mask = I2S_STD_SLOT_BOTH, \
    .ws_width = bits_per_sample, \
    .ws_pol = false, \
    .bit_shift = true, \
    .left_align = true, \
    .big_endian = false, \
    .bit_order_lsb = false \
}
    */
    .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_RIGHT,
      .ws_width = I2S_SLOT_BIT_WIDTH_32BIT,
      .ws_pol = false,
      .bit_shift = true,
      .left_align = true,
      .big_endian = false,
      .bit_order_lsb = false
    },
    
    .gpio_cfg = {
        .mclk = GPIO_NUM_1,
        .bclk = PIN_I2S_SCK,
        .ws = PIN_I2S_WS,
        .dout = I2S_GPIO_UNUSED,
        .din = PIN_IS2_SD,
        .invert_flags = {
            .mclk_inv = false,
            .bclk_inv = false,
            .ws_inv = false,
        },
    },
};


  void init_i2s_pins() {

    //i2s_chan_cfg_rx.id            = I2S_NUM_AUTO;  // I2S_NUM_AUTO, I2S_NUM_0, I2S_NUM_1
    //i2s_chan_cfg_rx.role          = I2S_ROLE_SLAVE;        // I2S controller master role, bclk and lrc signal will be set to output
    //i2s_chan_cfg_rx.dma_desc_num  = 8;                     // number of DMA buffer
    //i2s_chan_cfg_rx.dma_frame_num = 512;                    // I2S frame number in one DMA buffer.
    //i2s_chan_cfg_rx.auto_clear    = true;                   // i2s will always send zero automatically if no data to send
    esp_err_t err = i2s_new_channel(&i2s_chan_cfg_rx, NULL, &rx_chan);
    //rx_chan->dir = I2S_DIR_RX;
    ESP_ERROR_CHECK(err);

    //i2s_std_cfg_rx.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO);
    //i2s_std_cfg_rx.slot_cfg.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;  // Bits per sample
    //i2s_std_cfg_rx.slot_cfg.slot_bit_width = I2S_SLOT_BIT_WIDTH_16BIT;   // I2S channel slot bit-width equals to data bit-width
    //i2s_std_cfg_rx.slot_cfg.slot_mode      = I2S_SLOT_MODE_STEREO;      // I2S_SLOT_MODE_MONO, I2S_SLOT_MODE_STEREO,
    //i2s_std_cfg_rx.slot_cfg.slot_mask      = I2S_STD_SLOT_RIGHT;         // I2S_STD_SLOT_LEFT, I2S_STD_SLOT_RIGHT
   //i2s_std_cfg_rx.slot_cfg.ws_width       = I2S_DATA_BIT_WIDTH_16BIT;  // WS signal width (i.e. the number of bclk ticks that ws signal is high)
   // i2s_std_cfg_rx.slot_cfg.ws_pol         = false;                     // WS signal polarity, set true to enable high lever first
    //i2s_std_cfg_rx.slot_cfg.bit_shift      = true;                      // Set to enable bit shift in Philips mode
    /*
    i2s_std_cfg_rx.gpio_cfg.bclk           = I2S_GPIO_UNUSED;           // BCLK, Assignment in setPinout()
    i2s_std_cfg_rx.gpio_cfg.din            = I2S_GPIO_UNUSED;           // not used
    i2s_std_cfg_rx.gpio_cfg.dout           = I2S_GPIO_UNUSED;           // DOUT, Assignment in setPinout()
    i2s_std_cfg_rx.gpio_cfg.mclk           = I2S_GPIO_UNUSED;           // MCLK, Assignment in setPinout()
    i2s_std_cfg_rx.gpio_cfg.ws             = I2S_GPIO_UNUSED;           // LRC,  Assignment in setPinout()

    i2s_std_cfg_rx.gpio_cfg.bclk           = PIN_I2S_SCK;           // BCLK, Assignment in setPinout()
    i2s_std_cfg_rx.gpio_cfg.din            = PIN_IS2_SD;           // not used
    i2s_std_cfg_rx.gpio_cfg.dout           = I2S_GPIO_UNUSED;           // DOUT, Assignment in setPinout()
    i2s_std_cfg_rx.gpio_cfg.mclk           = I2S_GPIO_UNUSED;           // MCLK, Assignment in setPinout()
    i2s_std_cfg_rx.gpio_cfg.ws             = PIN_I2S_WS;           // LRC,  Assignment in setPinout()
    i2s_std_cfg_rx.gpio_cfg.invert_flags.mclk_inv = false;
    i2s_std_cfg_rx.gpio_cfg.invert_flags.bclk_inv = false;
    i2s_std_cfg_rx.gpio_cfg.invert_flags.ws_inv   = false;
    i2s_std_cfg_rx.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100);
        */
    //i2s_std_cfg_rx.clk_cfg.sample_rate_hz = 44100;
    //i2s_std_cfg_rx.clk_cfg.clk_src        = I2S_CLK_SRC_DEFAULT;        // Select PLL_F160M as the default source clock
    //i2s_std_cfg_rx.clk_cfg.mclk_multiple  = I2S_MCLK_MULTIPLE_128;      // mclk = sample_rate * 128
    //i2s_std_cfg_rx.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
    //i2s_std_cfg_rx.slot_cfg.bit_shift = 1;
    //i2s_std_cfg_rx.slot_cfg.ws_width = 20;
    
    //i2s_std_cfg_rx.slot_cfg.big_endian = 1;
    // bit_order_lsbbit_order_lsb
    //i2s_std_cfg_rx.slot_cfg.bit_order_lsb = 1;
    err = i2s_channel_init_std_mode(rx_chan, &i2s_std_cfg_rx);
    ESP_ERROR_CHECK(err);
    i2s_chan_info_t info;
    err = i2s_channel_get_info(rx_chan, &info);
    ESP_ERROR_CHECK(err);
    delay(1000);
    Serial.printf("I2S channel info: %d, %d, %d, %d, %d\n", info.id, info.role, info.dir, info.mode, info.pair_chan);
    delay(500);

    ESP_ERROR_CHECK(err);
    err = i2s_channel_enable(rx_chan);
    ESP_ERROR_CHECK(err);

    gpio_num_t gpio_pin = PIN_IS2_SD; // Replace with your GPIO pin

/*
    // Configure the pin as an input
    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_INTR_DISABLE; // Disable GPIO interrupts
    io_conf.mode = GPIO_MODE_INPUT;        // Set as Input mode
    io_conf.pin_bit_mask = (1ULL << gpio_pin); // Bit mask of the pin
    io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; // Enable pull-down resistor
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;    // Disable pull-up resistor
    */

    // Apply the GPIO configuration
    //gpio_config(&io_conf);
    err = gpio_pulldown_en(gpio_pin);
    ESP_ERROR_CHECK(err);
  }

#if 0
  const i2s_config_t i2s_config = {
      .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
      .sample_rate = AUDIO_SAMPLE_RATE,
      .bits_per_sample = i2s_bits_per_sample_t(AUDIO_BIT_RESOLUTION),
      .channel_format = i2s_channel_fmt_t(I2S_CHANNEL_FMT_ONLY_RIGHT),
      .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
      .intr_alloc_flags = 0,
      .dma_buf_count = AUDIO_DMA_BUFFER_COUNT,
      .dma_buf_len = IS2_AUDIO_BUFFER_LEN,
      //.use_apll = true
      // .tx_desc_auto_clear ?
  };

  const i2s_pin_config_t pin_config = {
      .bck_io_num = PIN_I2S_SCK,
      .ws_io_num = PIN_I2S_WS,
      .data_out_num = I2S_PIN_NO_CHANGE,
      .data_in_num = PIN_IS2_SD};

  const i2s_config_t i2s_config = {
      .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // Receive, not transfer
      .sample_rate = 16000,                              // 16KHz
      .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,      // could only get it to work with 32bits
      .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,      // use right 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 = 8                          // 8 samples per buffer (minimum)
  };
  #endif

}

void i2s_audio_init()
{
#if 0
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM, &pin_config);
  i2s_zero_dma_buffer(I2S_NUM_0);
  // i2s_start(I2S_NUM_0);
#else
  //init_i2s_pins();
#endif
}

//esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms)

void i2s_audio_shutdown()
{
  // i2s_stop(I2S_NUM_0);
  // i2s_driver_uninstall(I2S_NUM_0);
  i2s_del_channel(rx_chan);
}

size_t i2s_read_raw_samples(audio_sample_t (&buffer)[IS2_AUDIO_BUFFER_LEN])
{
  if (!g_is_initialized)
  {
    init_i2s_pins();
    g_is_initialized = true;
  }
  #if 0
  size_t bytes_read = 0;
  i2s_event_t event;

  uint32_t current_time = millis();
  esp_err_t result = i2s_read(I2S_NUM_0, buffer, sizeof(buffer), &bytes_read, 0);
  if (result == ESP_OK)
  {
    if (bytes_read > 0)
    {
      // cout << "Bytes read: " << bytes_read << endl;
      const size_t count = bytes_read / sizeof(audio_sample_t);
      return count;
    }
  }
  return 0;
  #else
  size_t bytes_read = 0;
  esp_err_t err = i2s_channel_read(rx_chan, buffer, sizeof(buffer), &bytes_read, 0);
  if (err == ESP_OK)
  {
    if (bytes_read > 0)
    {
      // cout << "Bytes read: " << bytes_read << endl;
      const size_t count = bytes_read / sizeof(audio_sample_t);
      return count;
    }
  }
  if (err != ESP_ERR_TIMEOUT) {
    ESP_ERROR_CHECK(err);
  }
  return 0;
  #endif
}

@VojtechBartoska VojtechBartoska added this to the 3.1.0 milestone Jan 31, 2024
@zackees zackees changed the title XIAO ESP32-C3: I2S takes 250ms before a mems microphone can give accurate readings INMP441 MEMS Mic does not work in 5.1 alpha 3.0.0 release - Previously - XIAO ESP32-C3: I2S takes 250ms before a mems microphone can give accurate readings Jan 31, 2024
@zackees zackees changed the title INMP441 MEMS Mic does not work in 5.1 alpha 3.0.0 release - Previously - XIAO ESP32-C3: I2S takes 250ms before a mems microphone can give accurate readings INMP441 MEMS Mic does not work in 5.1 alpha 3.0.0 release - Previously - XIAO ESP32-C3: I2S takes 250ms before a mems microphone can give accurate readings in legacy I2S with INMP441 Jan 31, 2024
@zackees zackees changed the title INMP441 MEMS Mic does not work in 5.1 alpha 3.0.0 release - Previously - XIAO ESP32-C3: I2S takes 250ms before a mems microphone can give accurate readings in legacy I2S with INMP441 INMP441 MEMS Mic does not work in 5.1 alpha 3.0.0 release - Previously - XIAO ESP32-C3: I2S takes 250ms before a INMP441 microphone can give accurate readings with legacy I2S. Jan 31, 2024
@zackees zackees changed the title INMP441 MEMS Mic does not work in 5.1 alpha 3.0.0 release - Previously - XIAO ESP32-C3: I2S takes 250ms before a INMP441 microphone can give accurate readings with legacy I2S. INMP441 MEMS Mic does not work in 5.1 alpha 3.0.0 release using NEW i2s library- Previously - XIAO ESP32-C3: I2S takes 250ms before a INMP441 microphone can give accurate readings with legacy I2S. Jan 31, 2024
@zackees
Copy link
Author

zackees commented Feb 7, 2024

Okay, I've gone ahead and created a minimal repo that reproduces this bug using the alpha release 3.0.0 5.1 libraries that were unofficially released.

For the life of me, I cannot get the INMP441 to work in the new v5.1 drivers, but I could make them work in the v4.4 drivers. The legacy i2s drivers work with the new v5.1 alpha release 3.0.0, fyi.

https://github.com/zackees/esp32-v51-inmp441

@me-no-dev

@VojtechBartoska

@zackees
Copy link
Author

zackees commented Feb 9, 2024

Okay, here's the code to get IS2 to work in v5.1 for the INMP441 mems microphone that is so popular.

The trick was here:

.dma_frame_num = IS2_AUDIO_BUFFER_LEN,

There's very little documentation on this dma_frame_num. But what I was able to figure out is that this represents the number of audio frames in the sample. In this case, 512 in mono.

The INMP441 only seemed to work when I put it in 16 bit mode, which I assume means that the lower 16 bits of the 32 bit word select frame are truncated. Which is the reason why the legacy i2s drivers were able to work out of spec with the INMP441 datasheet.

A humble suggestion is to create a macro designed for the INMP441 and document in the C header code that the dma_frame_num represents the number of samples per chunk.

I think my problem here was that the dma_frame_num was far too low and so the driver just silently fails instead of throwing an assert.

Here's the code for the INMP441

/*
Uses the new idf 5.1 i2s driver..
Not thread safe.
*/

#include <iostream>
#include <Arduino.h>
#include "defs.h"
#include <driver/i2s_std.h>
#include "i2s_device.h"
#include "driver/gpio.h"


namespace
{
  enum {
    INMP441_BIT_RESOLUTION = 24,
    INMP441_CHANNELS = 1,
    INMP441_FULL_FRAME_SIZE = 32
  };


  static_assert(AUDIO_BIT_RESOLUTION == 16, "Only 16 bit resolution is outputted by the microphone");
  static_assert(AUDIO_CHANNELS == 1, "Only 1 channel is supported");
  static_assert(sizeof(audio_sample_t) == 2, "audio_sample_t must be 16 bit");

  struct I2SContext
  {
    i2s_chan_handle_t rx_chan;
    i2s_chan_config_t i2s_chan_cfg_rx;
    i2s_std_config_t i2s_std_cfg_rx;
  };

  I2SContext make_inmp441_context() {
    I2SContext ctx;
    i2s_chan_handle_t rx_chan = NULL;
    i2s_chan_config_t i2s_chan_cfg_rx = {
        .id = I2S_NUM_0,
        .role = I2S_ROLE_MASTER,
        .dma_desc_num = AUDIO_DMA_BUFFER_COUNT,
        .dma_frame_num = IS2_AUDIO_BUFFER_LEN,
        .auto_clear = false,
    };
    i2s_std_config_t rx_std_cfg = {
        .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(AUDIO_CHANNEL_SAMPLE_RATE),
        .slot_cfg = {
                .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
                .slot_bit_width = I2S_SLOT_BIT_WIDTH_32BIT,
                .slot_mode = I2S_SLOT_MODE_MONO,
                .slot_mask = I2S_STD_SLOT_RIGHT,
                .ws_width = 32,
                .ws_pol = false,
                .bit_shift = true,
                .left_align = true,
                .big_endian = false,
                .bit_order_lsb = false,
        },
        .gpio_cfg = {
                .mclk = I2S_GPIO_UNUSED,
                .bclk = PIN_I2S_SCK,
                .ws   = PIN_I2S_WS,
                .dout = I2S_GPIO_UNUSED,
                .din  = PIN_IS2_SD,
                .invert_flags = {
                        .mclk_inv = false,
                        .bclk_inv = true,
                        .ws_inv   = false,
                },
        },
    };
    ctx = {rx_chan, i2s_chan_cfg_rx, rx_std_cfg};
    return ctx;
  }
  
  I2SContext s_i2s_context = make_inmp441_context();

  void init_i2s_pins()
  {
    //s_i2s_context = get_i2s_context();
    esp_err_t err = i2s_new_channel(&s_i2s_context.i2s_chan_cfg_rx, NULL, &s_i2s_context.rx_chan);
    ESP_ERROR_CHECK(err);
    err = i2s_channel_init_std_mode(s_i2s_context.rx_chan, &s_i2s_context.i2s_std_cfg_rx);
    ESP_ERROR_CHECK(err);
    i2s_chan_info_t info;
    err = i2s_channel_get_info(s_i2s_context.rx_chan, &info);
    ESP_ERROR_CHECK(err);
    err = i2s_channel_enable(s_i2s_context.rx_chan);
    ESP_ERROR_CHECK(err);

    // Set the pulldown resistor on the SD pin
    gpio_set_pull_mode(PIN_IS2_SD, GPIO_PULLDOWN_ONLY);
  }
} // namespace

void i2s_audio_init()
{
  pinMode(PIN_AUDIO_PWR, OUTPUT);
  digitalWrite(PIN_AUDIO_PWR, HIGH); // Power on the IS2 microphone.
  init_i2s_pins();
}

// esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms)

void i2s_audio_shutdown()
{
  // i2s_stop(I2S_NUM_0);
  // i2s_driver_uninstall(I2S_NUM_0);
  i2s_del_channel(s_i2s_context.rx_chan);
}

void i2s_audio_enter_light_sleep()
{
  // digitalWrite(PIN_AUDIO_PWR, LOW); // Power off the IS2 microphone.
  // hold pin engaged
  digitalWrite(PIN_AUDIO_PWR, HIGH); // Power on the IS2 microphone.
  gpio_hold_en(PIN_AUDIO_PWR);
  // i2s_stop(I2S_NUM_0);
  // i2s_driver_uninstall(I2S_NUM_0);
}

void i2s_audio_exit_light_sleep()
{
  // digitalWrite(PIN_AUDIO_PWR, HIGH); // Power on the IS2 microphone.

  // i2s_start(I2S_NUM_0);
  // i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
}


size_t i2s_read_samples(audio_sample_t (&buffer)[IS2_AUDIO_BUFFER_LEN])
{
  size_t bytes_read = 0;
  esp_err_t err = i2s_channel_read(s_i2s_context.rx_chan, buffer, sizeof(buffer), &bytes_read, 0);
  const size_t count = bytes_read / sizeof(audio_sample_t);
  ASSERT(bytes_read / sizeof(audio_sample_t) <= IS2_AUDIO_BUFFER_LEN, "Buffer overflow!");
  if (err == ESP_OK)
  {
    if (bytes_read > 0)
    {
      return count;
    }
  }
  if (err != ESP_ERR_TIMEOUT)
  {
    ESP_ERROR_CHECK(err);
  }
  return 0;
}

@zackees zackees changed the title INMP441 MEMS Mic does not work in 5.1 alpha 3.0.0 release using NEW i2s library- Previously - XIAO ESP32-C3: I2S takes 250ms before a INMP441 microphone can give accurate readings with legacy I2S. XIAO ESP32-C3: I2S takes 250ms before a INMP441 microphone can give accurate readings with legacy I2S. Feb 10, 2024
@zackees
Copy link
Author

zackees commented Feb 12, 2024

Update4: Oscilloscope time!

  • ESP32 shuts down the clock and word select in light sleep mode
    • I suspect this puts the microphone in a low power mode
  • The clock/ws signal resumes
  • The microphone outputs noise (decreasing) for ~100 ms

Should the ESP32 keep the clock/ws pin alive and feed into the DMA buffer? Or at least strobe them and discard the data. I'ml going to look into feeding in a clock signal during sleep and see if this solves the issue.

@zackees zackees changed the title XIAO ESP32-C3: I2S takes 250ms before a INMP441 microphone can give accurate readings with legacy I2S. XIAO ESP32-C3: I2S takes 250ms before a INMP441 microphone can give accurate readings with I2S. Feb 12, 2024
@zackees
Copy link
Author

zackees commented Feb 12, 2024

So it looks like DMA is NOT enabled during light sleep. Which is the reason I2S stops. My next trick will be to try and attach an LEDC driver in low bit mode and try to strobe the clk signal to keep the INMP441 from going into sleep mode.

Here is some code that will configure a pin to use LEDC during light sleep:

#3534

@zackees
Copy link
Author

zackees commented Feb 13, 2024

So confirmed working via the oscilloscope in 5.1 alpha 3.0.0.

Closing this bug. It's the INMP441 microphone that is the culprit, which might be auto-sleeping when the I2S halts it's clock during light sleep.

A potential work-around is to use ledc on the RTC clock to create a clock signal that keeps the INMP441 microphone from going to sleep. I'm having difficulty for I2S and LEDC to share a pin so I think I'm just going to tie the pins together and see how it goes.

@zackees zackees closed this as completed Feb 13, 2024
@zackees
Copy link
Author

zackees commented Feb 27, 2024

Okay update: I've mostly got it working.

There are two issue here as I went deep into this issue.

  1. In v5.1 and v4.4, stopping an I2S channel and resuming it DOES cause noise for 250ms. It seems to do this without even sleeping. I'm going to confirm this with deeper analysis and to see if disable/resume with no delay still causes the issue. I was disabling the channel and resuming it on wake from light sleep so this makes sense. The solution is to NOT do anything to the I2S channel before or after sleep. Simply let the whole system go to light sleep and resume on it's own.
  2. Driving a Psuedo I2S signal via the LEDC library works very well. I have to drive both the SCK and WS pins on the INMP441. To do this I have to allocate one of the neighbor pins to these pseudo signals and tie it to the real pin. When the chip wakes up I immediately stop the LEDC driver and then put it into INPUT mode. Simply driving it with psuedo SCK pin does not work. Both pins have to be emulated and only worked when I closely matched the real signals via an oscilloscope.
  3. The I2S must not stop/delete the channel before sleep. Nothing special must be done after awake from light sleep. Upon wake, the I2S DMA buffer will contain some garbage. For my 512 sample buffers this was a big problem, but I found that I could reduce the DMA buffer size to 16 samples and this contains the noise to just a few small samples which I can discard quickly. Yes, it does this for different sampling speeds. Reducing the INMP441 clock (sample rate) has no difference.

Yes, I'm locking the APB frequency on setup().

I suspect still that it's the INMP441 that's causing the issue, but I can't rule out the esp32 driver yet.

Next steps:

  • Deeper analysis with the oscilloscope: what happens when the I2S starts for the first time? I have to trigger catch the first WS signal and then about 250 ms and manually inspect the SCK/WS and data-in signals to determine whether the ESP32C3 is indeed generating the right signals and whether the ESP32C3 is generate good looking data during a quite period.

Other notes:

Wow, it's really hard to get that LEDC driver to operate in light sleep mode! The documentation is missing the following requirements:

  • ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_RTC8M, ESP_PD_OPTION_ON));
  • ESP_ERROR_CHECK(gpio_sleep_sel_dis(PIN_LEDC)); // Needed for light sleep.

I've posted my working code sample here

I've also filed a bug report to update the LEDC manual to include these requirements, which can be found here:

#9292

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Peripherals API Relates to peripheral's APIs. Resolution: Awaiting response Waiting for response of author Type: Question Only question
Projects
Development

No branches or pull requests

5 participants