--- title: M5Stack Cardputer-ADV plays a loud constant whine over all audio (ES8311, Arduino M5Unified) date: 2026-06-14 summary: On a Cardputer-ADV with custom Arduino firmware, every sound (test tone or MP3) had a loud constant whine on top. The speaker ran a mono I2S frame, so BCLK was half what M5Unified's fixed ES8311 clock divider expects. Setting spk_cfg.stereo = true fixed it. tags: m5stack, cardputer, es8311, esp32, arduino, audio environment: M5Stack Cardputer-ADV (ESP32-S3, ES8311 codec); Arduino via PlatformIO, platform espressif32@6.9.0 (ESP-IDF 4.4); M5Cardputer + M5Unified; ESP8266Audio 1.9.7 author: shaun, written up with Claude status: published --- ## Symptoms Custom Arduino firmware on an M5Stack **Cardputer-ADV** (an MP3 player sketch built on M5Cardputer / M5Unified and ESP8266Audio) produced a **loud, constant whine over all audio**: - the same with a generated test tone and with MP3 playback, - also when running on battery, - at every sample rate tried. The pitch of the actual audio was correct; only the added tone was wrong. The stock UIFlow firmware plays through the same ES8311 codec cleanly, which showed it was a configuration problem, not hardware. ## Root cause A clock mismatch between the I2S frame and the codec's clock divider: - The speaker was configured **mono**, so the I2S frame was 16 bits per sample period: BCLK = fs × 16 = **768 kHz** at 48 kHz. - M5Unified initialises the ES8311 with a **fixed clock divider** (register `0x02 = 0x18`) that assumes **BCLK = 1.536 MHz** (a 32-bit stereo frame). - With BCLK at half the expected rate, the DAC master clock ended up about **2× off**, which produced the constant tone. The audio pitch stayed right because LRCK still locked the sample rate. The fix below confirms the mechanism. Register-level changes did **not** help: reprogramming the ES8311 oversampling (OSR) and bias settings, and even a full ESP-ADF-style codec init, left the whine in place. It took about a dozen reflashes to find. ## Fix Configure the speaker as **stereo** before starting it. That gives a 32-bit frame and BCLK = 1.536 MHz, which matches the divider: ```cpp auto spk_cfg = M5Cardputer.Speaker.config(); spk_cfg.stereo = true; // 32-bit I2S frame -> BCLK 1.536 MHz at 48 kHz M5Cardputer.Speaker.config(spk_cfg); M5Cardputer.Speaker.begin(); ``` `spk_cfg.stereo = true` was the whole fix; no codec registers needed changing. The `config()` read-modify-write lines are the usual M5Unified pattern; the line that matters is setting `stereo` before `Speaker.begin()`.