Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 31 of 56

ES8311: the audio codec

Control over I2C, audio over I2S, and a clocking relationship you have to get right — the ESP is the master, the codec follows.

Two buses, one device

The Everest ES8311 is a low-power mono codec that is controlled over I2C and carries audio over I2S. Both have to be right before a sound comes out, which is what makes it a more instructive device than it first looks.

subtype Address is ESP32S3.I2C.Slave_Address;   --  0x18 with CE/AD0 low, 0x19 with CE high

The clocking relationship

The ESP is the I2S master and generates MCLK, BCLK and WS; the codec runs as an I2S slave clocked from that MCLK. The driver fixes MCLK = 256 × Sample_Rate with 16-bit samples, and the register coefficients are calculated for that ratio — it is rate-independent, but not ratio-independent. This is why I2S's Mclk pin exists and why it is only available on I2S0.

procedure Setup (...; Sample_Rate : ...; Sda, Scl : ...; Asdout : ... := No_Pin;
                 Ok : out Boolean);

Ok is False if the codec never ACKed on I2C — the first thing to check is the address strap, since 0x18 and 0x19 differ only by the CE pin. Asdout is the codec's ADC data-out (the ESP's data-in): leave it No_Pin for output only, or wire it to bring up the microphone path as well, with PGA gain in 6 dB steps from 0 to 42 dB.

Playing

Audio output goes through a limited, controlled Output handle that owns the I2S port exclusively and releases it on scope exit. The playback calls mirror I2S's exactly, because they are built on them:

CallUse
PlayOne blocking buffer.
Play_ContinuousSelf-looping DMA — a steady tone with no CPU involvement and no gap at the wrap.
Play_Stream + Await_HalfGapless double-buffered playback: refill the half the hardware is not reading.
Capture / Capture_Stream + Await_Capture_HalfThe microphone path, including while playback runs.
DuplexPlay and record simultaneously.
procedure Set_Volume (Percent : Natural; Ok : out Boolean);   --  0 .. 100, after Setup

The register-init sequence and clock coefficients are ported from Espressif's own es8311 driver — this is one place where the "from scratch" claim is honestly a port, because the sequence is a hardware recipe rather than a design decision.