Skip to content

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

Closed
@zackees

Description

@zackees

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.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions