Skip to content

StopWatch: fix the ES8311 gain structure (mic noise/clipping, speaker distortion at high volume) - #350

Merged
lovyan03 merged 2 commits into
m5stack:developfrom
ainyan03:stopwatch_audio_gain
Sep 2, 2026
Merged

StopWatch: fix the ES8311 gain structure (mic noise/clipping, speaker distortion at high volume)#350
lovyan03 merged 2 commits into
m5stack:developfrom
ainyan03:stopwatch_audio_gain

Conversation

@ainyan03

@ainyan03 ainyan03 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Overview

Fixes the audio gain structure on M5StopWatch (ES8311). Both directions took their gain in the wrong domain, which caused the recording quality issues and the speaker distortion at higher volumes.

Problems

Mic (capture): the enable sequence used analog PGA 0 dB with +32 dB of ADC digital volume. Digital gain after the ADC amplifies the noise floor together with the signal and clips loud input early, so recordings were noisy (white noise) and sounded flattened/muffled.

Speaker (playback): the enable sequence set the DAC digital volume to +24 dB (reg 0x32 = 0xEF). Digital gain ahead of the DAC cannot raise the full-scale ceiling, so this adds no loudness at all — it only moves the clipping point down. Any sample above 1/16 of full scale clips hard: with Speaker.setVolume() above ~64, a sine wave turns into a buzzer-like square wave without getting any louder.

Changes

Mic enable bulk:

  • 0x14: analog PGA 0 dB → +21 dB (gain code 7; the measured SNR plateaus by this level, limited by the mic element)
  • 0x17: ADC digital volume +32 dB → +6 dB
  • 0x1C: HPF stage-2 coefficient 10 → 4 (the dynamic HPF was already enabled by the old value; the new coefficient cuts sub-200 Hz rumble at the default 16 kHz rate)
  • the reset-witness comparison (added in StopWatch: recover ES8311 capture after power-on reset #348) follows the 0x17 bulk value

Speaker enable bulk:

  • 0x32: DAC digital volume +24 dB → +6 dB (0xCB). This value reaches DAC full scale exactly at maximum master volume.

Measurements

Mic (440 Hz acoustic reference + Goertzel analysis on-device): SNR 14.6 dB → ~19 dB, silence rms 312 → 52.

Speaker (1 kHz tone played on StopWatch, recorded and analyzed by a second device's mic):

  • clipping onset by 0x32 value: 0xEF = vol 64–128, 0xDF = 128–192, 0xCF = 192–255, 0xBF = none up to 255
  • 0xEF at vol 64 and 0xBF at vol 255 produce the same acoustic level, confirming +24 dB adds no loudness
  • with 0xCB the fundamental rises monotonically through the whole volume range (vol 64/128/192/224/255 → relative amplitude 20/100/222/300/380) with no harmonic explosion; the maximum clean output is about twice the old clean ceiling

The #348 recovery matrix (speaker/mic alternating stress, 20 cycles) still passes with these values, and the stock Basic/Speaker and advanced/Mic_FFT examples were verified by ear on the device.

ChainCaptain shares the same ES8311 register values but is left unchanged: I have no hardware to verify it.

The capture path took all its gain after the ADC: analog PGA at 0 dB
with +32 dB of ADC digital volume, which amplified the noise floor
along with the signal and clipped loud input early (white noise and
flattened waveforms in recordings).

Move the gain to the analog side and trim the digital stage:
- 0x14: analog PGA 0 dB -> +21 dB (gain code 7, 3 dB/code; the
  measured SNR plateaus by this level, limited by the mic element)
- 0x17: ADC digital volume +32 dB -> +6 dB
- 0x1C: HPF stage-2 coefficient 10 -> 4. The dynamic HPF was already
  enabled by the old value; the new coefficient cuts sub-200 Hz rumble
  at the default 16 kHz rate (silence rms 312 -> 52)

Measured result: SNR 14.6 dB -> ~19 dB against a 440 Hz reference.

The reset-witness comparison follows the 0x17 bulk value (0xCB).
The speaker enable procedure set DAC volume (reg 0x32) to 0xEF (+24 dB).
Since digital gain ahead of the DAC cannot raise the full-scale ceiling,
this only moved the clipping point down: any sample above 1/16 of full
scale clipped hard, so master volumes above ~64 turned a sine wave into
a buzzer-like square wave without getting any louder.

Measured with a two-device sweep (StopWatch playing a 1 kHz tone,
CoreS3 microphone running a Goertzel harmonic analysis):
- clipping onset by 0x32 value: 0xEF = vol 64-128, 0xDF = 128-192,
  0xCF = 192-255, 0xBF = none up to 255
- 0xEF at vol 64 and 0xBF at vol 255 produce the same acoustic level,
  confirming +24 dB adds no loudness
- 0xCB (+6 dB) reaches DAC full scale right at vol 255: fundamental
  rises monotonically 20/100/222/300/380 (vol 64/128/192/224/255) with
  no 3rd-harmonic explosion, and its maximum clean output is about
  double the old clean ceiling

0xCB therefore maximizes clean output while removing digital clipping
from the entire master volume range. ChainCaptain shares the same 0xEF
value but is left unchanged (no hardware at hand to verify).
@lovyan03
lovyan03 merged commit 38e1738 into m5stack:develop Sep 2, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants