PIO I2S Audio Configuration in Raspberry Pi Pico WAV Player: Complete Technical Guide
The rpi_pico_wav_player configures PIO state machine 0 with DMA channels 0 and 1, GPIO pins 18–20, and a 96 MHz USB PLL clock to drive I2S audio output via the RP2040's Programmable I/O subsystem.
The elehobica/rpi_pico_wav_player project leverages the RP2040's Programmable I/O (PIO) to stream WAV files to external I2S DACs such as the PCM5102. Understanding the specific PIO I2S audio configuration is essential for customizing the player or integrating high-fidelity audio into your own Pico-based projects.
Core PIO Configuration for I2S Output
All PIO-related parameters are encapsulated in an audio_i2s_config_t structure defined in lib/PlayAudio/i2s_audio_init.cpp. This structure is passed to the Pico SDK's audio_i2s_setup() helper to initialize the PIO state machine, DMA channels, and GPIO pins.
/* lib/PlayAudio/i2s_audio_init.cpp – lines 26-32 */
static audio_i2s_config_t i2s_config = {
.data_pin = PICO_AUDIO_I2S_DATA_PIN,
.clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE,
.dma_channel0 = 0,
.dma_channel1 = 1,
.pio_sm = 0
};
GPIO Pin Assignment
The configuration assigns specific GPIO pins for the I2S data and clock lines:
data_pin– Mapped toPICO_AUDIO_I2S_DATA_PIN(GPIO 18 by default), this pin carries the serial audio data (SD) to the DAC.clock_pin_base– Mapped toPICO_AUDIO_I2S_CLOCK_PIN_BASE(GPIO 20 by default), this defines the base GPIO for the I2S clocks. The SDK usesclock_pin_basefor the LRCK (word-select) line andclock_pin_base+1for the BCLK (bit clock) line.
DMA Channel Allocation
The player reserves two DMA channels for the PIO I2S interface:
dma_channel0– Channel 0 moves samples from the producer buffer to the PIO TX FIFO.dma_channel1– Channel 1 supports optional "ping-pong" buffer handling for seamless audio streaming, though the player primarily uses channel 0 for simple playback.
State Machine Selection
The configuration explicitly selects PIO state machine 0 (pio_sm = 0) to run the I2S transmitter program. This leaves state machines 1–3 available for other peripherals, maximizing the RP2040's parallel processing capabilities.
Clock Configuration for PIO I2S Timing
I2S audio requires precise timing, which the player achieves by configuring the RP2040's USB PLL to generate a 96 MHz clock for the PIO domain. This is implemented in src/power_manage.cpp:
/* src/power_manage.cpp – lines 31-44 */
void pw_set_pll_usb_96MHz()
{
pll_init(pll_usb, 1, 1536 * MHZ, 4, 4); // 1536 MHz / 16 = 96 MHz
clock_configure(clk_usb, 0,
CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
96 * MHZ, 48 * MHZ);
// … propagate 96 MHz to clk_sys and clk_peri …
}
The main.cpp entry point invokes this routine before any audio initialization to ensure the PIO state machine receives the correct clock frequency:
/* src/main.cpp – line 44 */
pw_set_pll_usb_96MHz(); // Set PLL_USB 96 MHz and use it for PIO clock for I2S
Initialization Sequence
The complete PIO I2S initialization follows this sequence, orchestrated through lib/PlayAudio/i2s_audio_init.cpp:
- Clock preparation –
pw_set_pll_usb_96MHz()establishes the 96 MHz PIO clock domain. - Buffer pool creation –
audio_new_producer_pool()allocates a ring of audio buffers sized byPICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH(defined inlib/PlayAudio/i2s_audio_init.hat line 11). - PIO program loading –
audio_i2s_setup()programs state machine 0 with the SDK's I2S transmitter, configures DMA channels 0 and 1, and binds the GPIO pins defined ini2s_config. - Connector establishment –
audio_i2s_connect()wires the producer buffer to the PIO→DMA pipeline. - Enable –
audio_i2s_set_enabled(true)starts state machine 0, beginning audio sample transmission.
Custom Configuration Examples
Re-using the Default PIO Configuration
To leverage the existing PIO setup in a custom module without redefining parameters:
#include "i2s_audio_init.h"
void my_module_init(uint32_t sample_rate, audio_buffer_pool_t *&pool)
{
// Re-use the same PIO configuration (SM 0, DMA 0/1, GPIO pins)
i2s_setup(sample_rate, pool);
}
Overriding Default Pins for Custom Hardware
For boards with different GPIO routing, instantiate a custom audio_i2s_config_t:
#include "pico/audio_i2s.h"
#include "i2s_audio_init.h"
static audio_i2s_config_t my_i2s_cfg = {
.data_pin = 2, // GPIO 2 as SD
.clock_pin_base = 3, // GPIO 3 = LRCK, GPIO 4 = BCLK
.dma_channel0 = 2,
.dma_channel1 = 3,
.pio_sm = 1 // use a different state machine
};
void my_i2s_init(uint32_t sr, audio_buffer_pool_t *&pool)
{
audio_format_t fmt = {
.sample_freq = sr,
.pcm_format = AUDIO_PCM_FORMAT_S32,
.channel_count = AUDIO_CHANNEL_STEREO
};
audio_i2s_setup(&fmt, &fmt, &my_i2s_cfg);
// … follow the same steps as i2s_audio_init …
}
Summary
- The rpi_pico_wav_player uses PIO state machine 0 dedicated to I2S audio transmission, leaving SM 1–3 available for other tasks.
- DMA channels 0 and 1 handle sample transfer from memory to the PIO TX FIFO, with channel 1 supporting optional ping-pong buffering.
- GPIO pins 18–20 are configured by default for I2S data (SD), word-select (LRCK), and bit clock (BCLK) respectively.
- A 96 MHz USB PLL clock is required for accurate I2S timing, initialized via
pw_set_pll_usb_96MHz()insrc/power_manage.cppbefore audio setup. - All PIO parameters are encapsulated in
audio_i2s_config_tdefined inlib/PlayAudio/i2s_audio_init.cpp, making the configuration portable and customizable.
Frequently Asked Questions
Which PIO state machine does the rpi_pico_wav_player use for I2S audio?
The player exclusively uses PIO state machine 0 (SM 0) for I2S audio output. This is explicitly set in the i2s_config structure in lib/PlayAudio/i2s_audio_init.cpp at line 31. Using SM 0 leaves state machines 1 through 3 free for additional peripherals, maximizing the RP2040's parallel processing capabilities.
What clock frequency is required for the PIO I2S configuration?
The PIO I2S logic requires a 96 MHz clock source to generate accurate audio bit clocks and word-select signals. The player achieves this by reconfiguring the USB PLL to 96 MHz in src/power_manage.cpp (lines 31-44) via the pw_set_pll_usb_96MHz() function, which must be called before initializing the audio subsystem.
Can I change the GPIO pins used for I2S output?
Yes, the GPIO pins are fully configurable by modifying the audio_i2s_config_t structure before calling audio_i2s_setup(). The default configuration uses GPIO 18 for data (SD) and GPIO 20 as the clock base (LRCK on 20, BCLK on 21), but you can override data_pin and clock_pin_base to use any valid GPIO pins for your specific board layout.
How many DMA channels are allocated for I2S audio playback?
The player allocates two DMA channels: channel 0 for primary sample transfer from the producer buffer to the PIO TX FIFO, and channel 1 for optional ping-pong buffer management. These are defined in lib/PlayAudio/i2s_audio_init.cpp at lines 28-29, where dma_channel0 is set to 0 and dma_channel1 is set to 1.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →