I2S
I2S (Inter-IC Sound) is the protocol used to stream digital audio from the ESP32 to the MAX98357A amplifier. It is designed exclusively for continuous audio data — unlike SPI or I2C, there is no addressing, no request/response cycle, just a constant stream of audio samples.
The Three Wires
| Signal | Direction | Purpose |
|---|---|---|
| BCLK | Controller → Peripheral | Bit Clock — one pulse per audio bit |
| LRCLK (also called WS) | Controller → Peripheral | Left/Right Clock (Word Select) — toggles each audio frame to indicate left or right channel |
| DIN | Controller → Peripheral | Serial Data — the audio samples |
The MAX98357A only receives data (it is an amplifier, not a microphone), so there is no DOUT line.
How Audio Is Encoded
Digital audio is a sequence of PCM (Pulse-Code Modulation) samples. At 44100 Hz, 16-bit stereo, you need:
44100 samples/sec × 2 channels × 16 bits = 1,411,200 bits/sec
I2S clocks all of this out continuously. The ESP32 uses DMA (Direct Memory Access) to transfer sample data from RAM to the I2S peripheral without CPU involvement — the firmware fills a buffer, and the hardware drains it to the wire automatically.
DMA Buffers
The I2S DMA configuration has two key parameters:
| Parameter | Effect | Tradeoff |
|---|---|---|
dma_buf_len | Samples per DMA buffer | Larger = less interrupt overhead; more latency |
dma_buf_count | Number of DMA buffers | More buffers = more resilience to CPU spikes |
A good starting configuration: dma_buf_len = 1024, dma_buf_count = 8. This gives
~23 ms of buffer at 44.1 kHz stereo 16-bit, which is enough to absorb display refresh
CPU spikes.
Left/Right Channel Selection on MAX98357A
The MAX98357A has a SD (Shutdown / Gain) pin that also selects which channel to output:
| SD pin connection | Behaviour |
|---|---|
| GND | Left channel only |
| Floating (open) | (L + R) / 2 — mono mix |
| 100 kΩ to VCC | Right channel only |
For a mono alarm clock, connect SD to GND for left channel or leave it floating for a mono mix. See the audio wiring doc.
ESP-IDF v5 I2S Setup
ESP-IDF v5 replaced the old driver/i2s.h API with driver/i2s_std.h:
#include "driver/i2s_std.h"
i2s_chan_handle_t tx_handle;
// Create the transmit channel
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(
I2S_NUM_0, I2S_ROLE_MASTER
);
i2s_new_channel(&chan_cfg, &tx_handle, NULL); // NULL = no RX
// Configure standard I2S mode
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100), // 44.1 kHz
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(
I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_STEREO
),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = GPIO_NUM_15,
.ws = GPIO_NUM_16,
.dout = GPIO_NUM_17,
.din = I2S_GPIO_UNUSED,
},
};
i2s_channel_init_std_mode(tx_handle, &std_cfg);
i2s_channel_enable(tx_handle);
After enabling the channel, write PCM samples with:
size_t written;
i2s_channel_write(tx_handle, pcm_buffer, buffer_size, &written, portMAX_DELAY);
DMA and SPI Contention
The ESP32-S3 shares DMA resources between I2S and SPI. If you observe audio glitches during display refresh operations, try:
- Pinning the audio task to core 1 (
xTaskCreatePinnedToCore(..., 1)) and the display task to core 0 - Increasing
dma_buf_countso there is more buffer to absorb the stall
The ESP32-S3 is dual-core; using separate cores for audio and display is the cleanest fix.