Audio architecture
This board has one hardware quirk that shapes the entire audio setup, and getting
it right is the difference between a working assistant and one that either crashes
or plays noise. This page explains the design. The annotated config is in the
audio section of base/core.yaml.
The constraint: one shared I2S clock pair
The ES8311 (DAC, to the speaker) and the ES7210 (ADC, from the mics) are wired to the same BCLK (GPIO13) and LRCLK (GPIO14) pins. Only one device can drive that clock pair.
On top of that, ESPHome cannot run a single i2s_audio bus full-duplex. If a
microphone and a speaker share one bus, each calls i2s_new_channel on the I2S
port, and the second one fails at runtime with Parent bus is busy. The speaker
then crackles or stays silent.
So a naive "one bus, mic plus speaker" config does not work on this hardware.
The design: two buses, mic as master
The layout that works, entirely on stock ESPHome components (no patched es8311,
no force_master):
- Two
i2s_audiobuses (two I2S ports) ride the shared GPIO13/14 pins, usingallow_other_usesso both may reference the same pins. - The microphone bus is the I2S master. It drives BCLK, LRCLK and MCLK.
- The speaker bus is a slave. It reads the clock the mic bus produces, so it never needs a port of its own to master.
- The ES8311 and ES7210 are stock and both slave to the mic's clock.
i2s_audio:
- id: i2s_input # mic bus = master (drives the shared clock)
i2s_mclk_pin: GPIO12
i2s_bclk_pin: { number: GPIO13, allow_other_uses: true }
i2s_lrclk_pin: { number: GPIO14, allow_other_uses: true }
- id: i2s_output # speaker bus = slave
i2s_bclk_pin: { number: GPIO13, allow_other_uses: true }
i2s_lrclk_pin: { number: GPIO14, allow_other_uses: true }
microphone:
- platform: i2s_audio
i2s_audio_id: i2s_input # default primary role -> master
bits_per_sample: 16bit
speaker:
- platform: i2s_audio
i2s_audio_id: i2s_output
i2s_mode: secondary # slave to the mic's clock
Why the mic is the master
The microphone is always capturing for the wake word. As master it therefore drives BCLK, LRCLK and MCLK continuously, which is exactly what a slave speaker and the ES8311 DAC need to stay locked.
The obvious alternative, making the ES8311 the master (a force_master-style
patch that sets the codec's MSC bit), also produces a valid clock, but it feeds
the ESP microphone a wrong-rate stream and the wake word stops working. The
ESP-mastered, two-bus layout needs no patched component and keeps the wake word
healthy.
Why the mic is pinned to 16-bit
The i2s_audio default is 32-bit. Because the mic bus is the master, it sets the
frame's slot width for everyone. A 32-bit master frame against the 16-bit ES8311
DAC doubles the bit clock the DAC expects, and playback comes out as noise.
Pinning the mic to bits_per_sample: 16bit makes the master frame match the DAC,
and audio is clean.
Result
Simultaneous capture and playback on stock ESPHome: the wake word keeps listening while music or a reply plays, with no custom component and no clock fighting.
No hardware AEC here
The board's ES7210 has an AEC loopback channel, but hardware acoustic echo
cancellation is not reachable from stock ESPHome on this board (the vendor demo
packs four 16-bit channels into two 32-bit I2S slots and unpacks them in
software; ESPHome does not). This firmware uses a single mic channel and relies on
ESPHome's software noise_suppression_level and auto_gain instead.
The practical consequence: without AEC the mic hears the device's own speaker loudly, so a "stop" wake word to interrupt a reply is detected only weakly and too late to be useful. That feature was tried and removed. See the FAQ.