Skip to content

esp32_i2s

I2S0, driven as a DMA loopback.

The classic ESP32’s I2S has no CPU-accessible FIFO — data only moves through the peripheral’s own DMA. So even a loopback is a full DMA transfer: a transmit chain feeds the serialiser, a receive chain drains the deserialiser, and sig_loopback ties the transmitter’s clock and data to the receiver internally.

One I2S block therefore tests its own whole data path — FIFO, serialiser, deserialiser, and both DMA engines — with no pins and no second peripheral: transmit a buffer, receive it back through the internal loopback, compare.

The transmitter is master (it generates the bit and word clocks); the receiver is slave and consumes those clocks over the loopback. Sixteen bits per sample, two channels; the exact sample rate is immaterial when the block only talks to itself.

sig_loopback shares the word and bit clocks between transmitter and receiver internally, but not the serial data — verified on silicon, where the receiver otherwise clocks in nothing but zeros. So the data output and data input are routed to one pad through the GPIO matrix: the transmitter drives it, the receiver reads it, no external wire. That pad is the only pin this needs.

Buffers and descriptors must live in DMA-reachable RAM and be word-aligned — [soc_esp32::dma::build_chain] checks both. This crate reuses that tested descriptor machinery rather than re-deriving the lldesc format.

DR_REG_I2S_BASE = 0x3FF4F000, offsets from esp-idf soc/i2s_reg.h, field positions from soc/i2s_struct.h, the sequence from hal/i2s_ll.h.

Register Offset Fields used
CONF 0x08 resets [3:0], starts [5:4], slave [7:6], MSB_SHIFT [11:10], SIG_LOOPBACK 18
INT_RAW/INT_CLR 0x0C/0x18 IN_SUC_EOF 9
FIFO_CONF 0x20 DSCR_EN 12, TX_FIFO_MOD [15:13], RX_FIFO_MOD [18:16], data-num [11:0]
RXEOF_NUM 0x24 words to receive before EOF
CONF_CHAN 0x2C TX_CHAN_MOD [2:0], RX_CHAN_MOD [4:3]
OUT_LINK/IN_LINK 0x30/0x34 ADDR [19:0], START 29
LC_CONF 0x60 in/out/AHBM resets [3:0]
CLKM_CONF 0xAC CLKM_DIV_NUM [7:0], _B [13:8], _A [19:14], CLKA_ENA 21
SAMPLE_RATE_CONF 0xB0 bck-div [11:0], bits-mod [23:12]
struct I2sLoopback { /* private fields */ }

I2S0, configured for an internal DMA loopback.

struct I2sStream<'a> { /* private fields */ }

A running continuous DMA stream over the I2S loopback.

The descriptors form a ring the engine cycles endlessly; the CPU services one buffer per IN_SUC_EOF. Between the EOF for a buffer and the engine lapping back to it there are count - 1 buffers of slack — the window in which the CPU consumes the received data and refills the transmit buffer.

enum I2sError {
BadBuffer,
Timeout,
BadGeometry,
}

Why a loopback failed.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq