Skip to main content

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

SignalDirectionPurpose
BCLKController → PeripheralBit Clock — one pulse per audio bit
LRCLK (also called WS)Controller → PeripheralLeft/Right Clock (Word Select) — toggles each audio frame to indicate left or right channel
DINController → PeripheralSerial 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:

ParameterEffectTradeoff
dma_buf_lenSamples per DMA bufferLarger = less interrupt overhead; more latency
dma_buf_countNumber of DMA buffersMore 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 connectionBehaviour
GNDLeft channel only
Floating (open)(L + R) / 2 — mono mix
100 kΩ to VCCRight 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:

  1. Pinning the audio task to core 1 (xTaskCreatePinnedToCore(..., 1)) and the display task to core 0
  2. Increasing dma_buf_count so 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.