Skip to content

M5StopWatch: microphone returns all-zero samples once M5.Speaker.begin() has run #347

Description

@mikkmartin

Summary

On the M5Stack StopWatch (board_M5StopWatch), the microphone returns buffers of
exact zeros once M5.Speaker.begin() has been called. The microphone works
correctly if the speaker is never started in that boot, so the capture path
itself is functional — it appears to be released incompletely when the speaker
takes the shared codec.

Environment

  • Board: M5Stack StopWatch (C152), ESP32-S3
  • M5Unified: 774d920cd6851a5231748b56ece1b073645f313f
  • M5GFX: 93b480bb349749202c8a2a953065c8ae95f58320
  • platform: espressif32 @ 6.12.0, Arduino framework

Expected

After ending the speaker, M5.Mic.begin() + M5.Mic.record() should capture
audio, allowing an application to alternate between playback and capture on the
shared ES8311.

Actual

M5.Mic.begin() and M5.Mic.record() both return true, and
M5.Mic.isRecording() completes normally, but every sample in the buffer is
0.

Reproduction

void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);            // internal_spk and internal_mic both default true
  M5.Speaker.begin();       // <-- capture is dead from here on
}

void probe() {
  M5.Speaker.end();
  M5.Mic.begin();
  delay(200);               // generous settle
  static int16_t buf[4800];
  memset(buf, 0, sizeof(buf));
  M5.Mic.record(buf, 4800, 16000);
  delay(320);
  while (M5.Mic.isRecording()) delay(1);
  M5.Mic.end();

  int16_t lo = 0, hi = 0;
  for (auto s : buf) { if (s < lo) lo = s; if (s > hi) hi = s; }
  Serial.printf("raw=[%d..%d]\n", lo, hi);   // raw=[0..0]
}

Comment out M5.Speaker.begin() and set cfg.internal_spk = false, and the
same probe() reports plausible audio, e.g. raw=[-9296..32751], around
-43 dBFS, stable across repeated calls and responsive to room noise.

Things that did not help

Attempt Result
M5.Speaker.end() before M5.Mic.begin() zeros
cfg.internal_spk = false, speaker never begun works
cfg.internal_spk = false, speaker configured via M5.Speaker.config() and started manually zeros
gpio_reset_pin() on the shared MCK/BCK/WS (18/17/15) between the two zeros
periph_module_reset(PERIPH_I2S0_MODULE) after Speaker_Class::end() zeros

Notes

The two paths share MCK 18 / BCK 17 / WS 15 and differ in data pin (mic 16 on
I2S_NUM_1, speaker 21 on I2S_NUM_0) and in the ES8311 clock-manager value
each enable callback writes (0x01 → 0xBA for capture, 0xB5 for playback).

Speaker_Class::end() does call _i2s_driver_uninstall(_cfg.i2s_port), so the
driver is released; whatever keeps the codec in playback appears to sit below
that. Since cfg.internal_spk = false at M5.begin() is sufficient to keep
capture working, something in the speaker's initialisation may be leaving state
that end() does not undo.

Happy to run further tests on the hardware if useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions