In this post, you will learn how to use a PCM5102A module with the ESP32 to play audio. The PCM5102A is a high-performance digital-to-analog converter (DAC), which communicates over the I2S (Inter-IC Sound) interface.
It converts audio data into a line-level analog signal suitable for amplification or direct use with high impedance headphones. The module includes a 3.5 mm stereo output and operates from a standard 3.3 V to 5 V supply, making it compatible with common microcontroller such as the ESP32 or Arduino boards.
Throughout this tutorial, you will learn how to generate audio signals, convert text to speech, stream internet radio, play MP3 files from an SD card, and use Bluetooth audio.
Required Parts
You will need a PCM5102A module. There are different versions of PCM5102A based boards but they should all work for this tutorial. I used the board listed below, which has a 3.5mm jack for line out.

You will need a pair of active speakers, preferably with a 3.5mm plug to directly connect to this PCM5102A board.
To play MP3 files from an SD Card you will furthermore need an SD Card with at least 1GB and an SD Card reader module. We will also use a 10K Ohm potentiometer as a volume controller and some push buttons for the MP3 player and Internet Radio we are going to build.
Finally, you will need an ESP32, a breadboard and some cables. I used an ESP32 lite but most other ESP32 boards should work as well. You should get an ESP32-S3 board with PSRAM, if you plan to store and play music from memory.

PCM5102 Amplifier

Active Speaker

10K Ohm Potentiometer

Push Buttons

Micro SD Card Reader

Micro SD Card 8GB

ESP32 lite

USB Data Cable

Dupont Wire Set

Breadboard
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
The I2S Audio Protocol
Let’s start with a quick introduction of the I2S protocol used to transfer audio data digitally from an ESP32 to a DAC such as the PCM5102A.
I2S or Inter-IC Sound, is a serial bus interface standard used for connecting digital audio devices. It was introduced by Philips in the 1980s to simplify the transmission of audio data between integrated circuits. Unlike protocols such as SPI or I2C, I2S is specifically designed for audio applications, ensuring precise timing and synchronization of audio streams.
At its core, I2S transfers Pulse Code Modulation (PCM) audio data in a synchronous manner. The protocol uses three main signals: the serial clock (SCK), word select (WS), and serial data (SD). The serial clock pulses at the bit rate, dictating when bits are sent. The word select signal toggles to indicate whether the current data corresponds to the left or right audio channel. Finally, the serial data line carries the actual audio bits, transmitting the most significant bit (MSB) first.

Typically, audio data is sent in 16-bit or 24-bit words, but the protocol can support other bit depths depending on the hardware.
ESP32’s I2S peripheral supports full-duplex communication, allowing simultaneous audio input and output. It can be configured to operate in master or slave mode. When acting as the master, the ESP32 generates the clock and word select signals. This is the common setup when interfacing with the PCM5102A DAC, which acts as an I2S slave device. In the next section we have a closer look at the PCM5102A.
Technical Features of the PCM5102 Module
PCM5102A modules are based on the Texas Instruments PCM5102A, a stereo digital-to-analog converter designed for high-resolution audio playback. It converts digital audio streams into analog line-level signals using a fully integrated signal path that minimizes the need for external components.
The PCM5102A supports stereo output with typical full-scale analog output of about 2.1 Vrms ground-centered, enabling direct line-level connection without the need for external DC-blocking capacitors.
The module used in this tutorial comes with a 3.5 mm stereo jack for audio output, along with onboard passive components for output filtering and AC coupling. There are also solder bridges for function selection – more about that later. The picture below shows the front and back of the PCM5102 Module:

Digital Audio Input and Clocking
Digital audio data are accepted through a standard PCM interface compliant with I2S and left-justified formats, supporting 16-, 24-, and 32-bit data. Sample rates from 8 kHz up to 384 kHz are accommodated, making the device suitable for both standard and high-resolution audio applications.
The PCM5102A includes a high-performance integrated PLL that can synthesize the high-speed internal clock required by its interpolation and DAC engines from either the bit clock (BCK) or an optional external system clock (SCK). This reduces the need for an external master clock source and allows operation with a 3-wire I2S connection (LRCK, BCK, DIN).
Digital Signal Processing and Filters
Once digital audio data are received, the PCM5102A’s internal signal path includes digital interpolation filters that oversample the input signal by a factor (e.g., 128× the sample rate) before conversion. These interpolation stages reduce quantization noise and simplify the requirements of the analog reconstruction filter.
Analog Output Stage and Line Driver
The module’s analog output stage integrates a DirectPath™ charge-pump line driver, capable of delivering a line-level output into loads as low as 1 kΩ per channel. Intelligent soft-ramp and analog mute circuitry help prevent audible transients during power-up, power-down, or clock errors.
Note that you cannot directly drive a passive speaker with the line-level output of the PCM5102A. You need an extra amplifier or an active speaker!
Power, Control and Operating Conditions
The PCM5102A module is designed to operate from a single power supply, in the range of 3.3V to 5V. Internally, the DAC uses integrated low-dropout regulators to generate the required core and analog supply voltages.
The PCM5102A enters a low-power mode automatically when I2S clocks are inactive to reduce power consumption. Hardware pins and solder bridges allow to configure the audio format (e.g., I2S vs left-justified) and soft mute state, with no serial control interface (I2C/SPI) required for basic operation.
Pinout
The following picture shows the pinout of the PCM5102A board:

At the top of the board are pins for Line Out : LROUT = Left Channel, ROUT=Right Channel, AGNG = Ground) and pins to control the specific functions of the board (more about that in the next section).
The functions are also settable as solder bridges on the back of the board (H1L, H2L, H3L, H4L). And the 3.5mm jack duplicates the Line Out pins.
On the right side you find the pins for the I2S interface (SCK, BCK, DIN, LRCK) and the pins for the power supply (VIN, GND). The I2S pins are fed through a resistor pack which should allow 5V signals, e.g. from an Arduino UNO. The supply voltage can be 3.3V or 5V, since the board has internal low-dropout voltage regulators.
Function Jumpers
The PCM5102A module has five Jumpers/Solder Bridges that need to be set correctly for the board to work with an ESP32. They switch the following functions below, depending if connected to High or Low. The settings you want are marked in bold:
- SCK = Master Clock: Low if not external master clock signal is provided
- H1L / FLT = Filter select : Normal latency (Low) / Low latency (High)
- H2L / DEMP = De-emphasis control for 44.1kHz sampling rate: Off (Low) / On (High)
- H3L / XSMT = Soft mute control: Soft mute (Low) / soft un-mute (High)
- H4L / FMT = Audio format selection I2S : Right justified (Low) / Left justified (High)
The Filter select with normal latency is an FIR with good response, delaying the signal by approx. 500us (at 44.1 kHz). The low latency filter is an IIR with slightly poorer response and delays the signal approx. 80us. Very few (if any) audio sources have pre-emphasis applied so de-emphasis control should be set off. The XSMT pin would allow muting of the output via a GPO or a switch if the H3L bridge is not soldered. And for the I2S audio format we pick right justified.
The picture below shows the schematic of the PCM5102A module with these function jumpers marked in yellow:

Solder Bridges
You can find four solder bridges H1L, H2L, H3L and H4L on the back of the board. The picture below shows you which bridges you have to solder to High (H) or Low (H):

Alternatively there are also 4 pins (FLT, DEMP, XMST, FMT) at the top of the board that you could connect to 3.3V (H) or GND (L) to control the board functions. This is useful if you want to use switches or GPIO to control functions. Especially the mute function (XMST/H3L) might be of interest. But make sure that you use either the pins or the bridges but not both at the same time!
SCK Bridge
There is single bride at the front of the board you need to solder as well. It controls whether an external master clock signal (SCK) is provided or not. In our case we don’t provide SCK and therefore need to solder the SCK bridge to connect SCK to ground.

Technical Specification
The following table summarizes the technical specification of the PCM5102A:
| Parameter | Specification |
|---|---|
| DAC Device | Texas Instruments PCM5102A |
| Internal Architecture | High-performance delta-sigma DAC with digital interpolation filters |
| Digital Audio Interface | Standard I2S, left-justified, right-justified, DSP (PCM) formats |
| Supported Sample Rates | 8 kHz to 384 kHz |
| Supported Bit Depth | 16-bit, 24-bit, 32-bit |
| Input Clock Requirements | Bit Clock (BCK), Word Clock (LRCK); optional external system clock (SCK) |
| Clocking | Integrated PLL with high-performance clock multiplier; internal master clock generation |
| Digital Filter Oversampling | Multiple stages with high oversampling ratio (e.g., 128 x fs) |
| Analog Output Type | Differential internal outputs with on-board passive filtering to single-ended |
| Output Connector | 3.5 mm stereo jack |
| Full-Scale Output Voltage | Approx. 2.1 Vrms (ground-centered) |
| Output Load Drive | Designed for line-level loads ≥ 1 kΩ per channel |
| Signal-to-Noise Ratio (SNR) | ~112 dB (A-weighted, typical) |
| Total Harmonic Distortion + Noise (THD+N) | ~-93 dB (typical) |
| Line Driver | Integrated DirectPath™ charge-pump line driver |
| Mute/Soft-Ramp | Integrated analog soft-mute on power up/down or clock loss |
| Power Supply Voltage (AVDD) | 3.3 V … 5V |
| Logic Levels | LVCMOS compatible with ESP32 I/O levels |
| Control Interface | Hardware pin configuration (no I2C/SPI required for basic operation) |
| Power Consumption | Auto low-power mode when clocks inactive |
For additional information see the datasheet for the PCM5102A that is linked below:
Architecture of a PCM5102A based Audio System
The PCM5102A has only a line level output that is insufficient to drive passive speakers directly. You either need to use active speakers, which have a built-in amplifier or connect a separate amplifier. The diagram below shows the typical architecture of an audio system that uses the PCM5102A DAC.

The microcontroller (ESP32) generates a digital audio signal and transmits it via the I2S protocol digitally to the PCM5102 DAC. The PCM5102 converts the digital audio into a line level, analog signal for the left and right channel. The Amplifier then amplifies the analog signal to drive mono or stereo speakers.
Connect PCM5102A to ESP32
In this section we are wiring the PCM5102A with the ESP32. We start by connecting VIN of the PCM5102A to 3.3V of the ESP32 and GND to G. Next we connect the I2S pins as shown in the following table:
| PCM5102A | ESP32 |
|---|---|
| VIN | 3V3 |
| GND | G |
| LRCK | 32 |
| BCK | 25 |
| DIN | 33 |
| SCK | G |
If you have soldered the SCK bridge you actually won’t need the SCK to ground connection but it doesn’t do any harm.
The following picture shows the complete wiring of the PCM5102A module with an ESP32 lite. Note that you need to connect the line out output of the PCM5102A to an amplifier or active speaker. You should not connect a passive speaker or low impedance headphones directly to the PCM5102A.

SD Card Reader
If you want to play audio files you need to connect an SD Card reader that stores the audio files on an SD Card. The wiring diagram below shows you how to connect an SD Card reader and the PCM5102A to an ESP32:

The SD Card Reader communicates via SPI and the default SPI pins of the ESP32 for SPI are CS=5, MOSI=23, CLK=18 and MISO=19. The table below summarizes the connections you need to make between the SD Card Reader and the ESP32:
| SD Card Reader | ESP32 |
|---|---|
| 3V3 | 3V |
| GND | G |
| CS/SS | 5 |
| MOSI | 23 |
| CLK/SCK | 18 |
| MISO | 19 |
If you are not sure which pins are the default SPI pins of your ESP32 have a look at the Find I2C and SPI default pins tutorial.
The following photo shows my wiring of the PCM5102A with an SD Card Reader, an ESP32 and a small active mono speaker for testing:

Installing Libraries
In this tutorial we are going to use the arduino-audio-tools library by Phil Schatzmann to send audio data to the PCM5102A. To install the library go to the arduino-audio-tools repo, click on the green “<> Code” button and then “Download ZIP” to download the library as a ZIP file as shown below:

Then open a Sketch, go to Sketch -> Include Library -> Add .ZIP Library … to install the downloaded ZIP library (arduino-audio-tools-main.zip):

For some of the code examples we need two more libraries by Phil Schatzmann; namely the arduino-libhelix library and the ESP32-A2DP library. You can install them in the same way. Click on the link to go to the github repo, click on the green “<> Code” button to download the libraries (arduino-libhelix-main.zip, ESP32-A2DP-main.zip) and then install them.
Finally, if this is the first time you program an ESP32 board from your Arduino IDE you will need to install the ESP32 core as well. For details see the Install ESP32 core in Arduino IDE tutorial.
Code Example: Test Sound
In this first example we just generate a sine wave test tone and output it through the I2S interface to the DAC
/*
www.makerguides.com
Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools)
Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
*/
#include "AudioTools.h"
// PCM5102A
#define DIN_PIN 33 // serial data
#define LRCK_PIN 32 // word select
#define BCLK_PIN 25 // serial clock
#define VOLUME 1000 // max 32000
AudioInfo info(44100, 2, 16);
SineWaveGenerator<int16_t> sineWave(VOLUME);
GeneratedSoundStream<int16_t> sound(sineWave);
I2SStream out;
StreamCopy copier(out, sound);
void setup(void) {
auto config = out.defaultConfig(TX_MODE);
config.copyFrom(info);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
out.begin(config);
sineWave.begin(info, N_B4);
}
void loop() {
copier.copy();
}
Imports
The code starts by including the AudioTools.h library, which provides classes and functions to generate and stream audio signals easily on the ESP32.
#include "AudioTools.h";
Constants
Next, we define the pins connected to the PCM5102A DAC module. These pins correspond to the I2S interface signals: DIN_PIN for serial data, LRCK_PIN for word select (left-right clock), and BCLK_PIN for the serial clock. We also define a VOLUME constant to set the amplitude of the generated sine wave, with a maximum amplitude of 32000.
#define DIN_PIN 33 // serial data #define LRCK_PIN 32 // word select #define BCLK_PIN 25 // serial clock #define VOLUME 1000 // max 32000
Audio Configuration and Objects
The AudioInfo object info defines the audio format: a sample rate of 44,100 Hz, 2 channels (stereo), and 16 bits per sample. This matches standard CD-quality audio.
The SineWaveGenerator object sineWave is used to generate a sine wave audio signal with the specified volume. It produces 16-bit signed integer samples.
The GeneratedSoundStream object sound wraps the sine wave generator into a stream interface, making it easier to send the audio data to an output.
The I2SStream object out represents the I2S output interface on the ESP32, which will send the audio data to the PCM5102A DAC.
Finally, the StreamCopy object copier is responsible for copying audio data from the generated sound stream to the I2S output stream continuously.
AudioInfo info(44100, 2, 16); SineWaveGenerator<int16_t> sineWave(VOLUME); GeneratedSoundStream<int16_t> sound(sineWave); I2SStream out; StreamCopy copier(out, sound);
Setup Function
Inside the setup() function, we configure the I2S output stream. We start by obtaining a default configuration for transmission mode using out.defaultConfig(TX_MODE). Then, we copy the audio format settings from the info object into this configuration.
Next, we assign the I2S pins to the configuration: BCLK_PIN for the bit clock, LRCK_PIN for the word select, and DIN_PIN for the serial data line.
After configuring the pins, we initialize the I2S output stream with out.begin(config). Finally, we start the sine wave generator with the audio format and a predefined musical note frequency N_B4 (which corresponds to the B4 note).
void setup(void) {
auto config = out.defaultConfig(TX_MODE);
config.copyFrom(info);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
out.begin(config);
sineWave.begin(info, N_B4);
}
Loop Function
The loop() function continuously copies audio data from the sine wave generator stream to the I2S output stream using copier.copy(). This keeps the audio playing indefinitely without interruption.
void loop() {
copier.copy();
}
Code Example: Bluetooth
This next code demonstrates how to play audio over Bluetooth on an ESP32 using the PCM5102A DAC. It leverages several libraries to handle audio streaming and Bluetooth A2DP (Advanced Audio Distribution Profile) functionality. The ESP32 receives audio data via Bluetooth and outputs it through the I2S interface to the PCM5102A digital-to-analog converter.
/*
www.makerguides.com
Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools)
Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
Version: 0.9.2
- [ESP32-A2DP](https://github.com/pschatzmann/ESP32-A2DP)
Version: 1.8.8
*/
#include "AudioTools.h"
#include "BluetoothA2DPSink.h"
#define DIN_PIN 33 // serial data
#define LRCK_PIN 32 // word select
#define BCLK_PIN 25 // serial clock
I2SStream i2s;
BluetoothA2DPSink a2dp_sink(i2s);
void setup() {
auto config = i2s.defaultConfig();
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
a2dp_sink.start("MyMusic");
}
void loop() { }
Imports
The code starts by including the necessary libraries. AudioTools.h provides audio streaming capabilities, while BluetoothA2DPSink.h handles the Bluetooth A2DP sink functionality, allowing the ESP32 to receive audio streams from Bluetooth sources such as smartphones.
#include "AudioTools.h" #include "BluetoothA2DPSink.h"
Constants
Next, three constants define the GPIO pins used for the I2S interface, which is the communication protocol between the ESP32 and the PCM5102A DAC. These pins correspond to the serial data line (DIN_PIN), the word select or left-right clock line (LRCK_PIN), and the bit clock line (BCLK_PIN).
#define DIN_PIN 33 // serial data #define LRCK_PIN 32 // word select #define BCLK_PIN 25 // serial clock
Objects
An I2SStream object named i2s is created to manage the I2S audio data stream. Then, a BluetoothA2DPSink object called a2dp_sink is instantiated, passing the i2s stream as a parameter. This links the Bluetooth audio input directly to the I2S output, enabling seamless audio playback.
I2SStream i2s; BluetoothA2DPSink a2dp_sink(i2s);
Setup function
Inside the setup() function, the I2S interface is configured. The default I2S configuration is obtained via i2s.defaultConfig(), and the relevant pins for bit clock, word select, and data are assigned according to the constants defined earlier. The I2S interface is then initialized with this configuration using i2s.begin(config).
After setting up the I2S interface, the Bluetooth A2DP sink is started with the device name "MyMusic". This makes the ESP32 discoverable as a Bluetooth audio receiver with that name, allowing other devices to connect and stream audio.
void setup() {
auto config = i2s.defaultConfig();
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
a2dp_sink.start("MyMusic");
}
Open your phone and scan for Bluetooth devices to find the "MyMusic" device and then start playing music.
Loop function
The loop() function is empty because the Bluetooth A2DP sink and I2S stream handle audio processing and playback asynchronously. Once the setup is complete, the ESP32 continuously listens for Bluetooth audio streams and outputs them via the I2S interface without requiring additional code in the main loop.
void loop() { }
Code Example: Internet Radio
This code demonstrates how to build an internet radio player using an ESP32 microcontroller and a PCM5102A DAC module. It connects to a WiFi network, streams MP3 audio from an online radio station, decodes the audio, and outputs it via the I2S interface to the DAC. The code also handles metadata from the stream, such as song titles.
/*
www.makerguides.com
Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools)
Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
Version: 0.9.2
*/
#include <Arduino.h>
#include <WiFi.h>
#include <Wire.h>
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#include "AudioTools/Communication/HTTP/ICYStream.h"
// PCM5102A
#define DIN_PIN 33 // serial data
#define LRCK_PIN 32 // word select
#define BCLK_PIN 25 // serial clock
#define VOLUME 0.3 // Volume
const char* ssid = "ssid";
const char* password = "pwd";
const char* url = "https://jazz.stream.laut.fm/jazz";
ICYStream icystream;
I2SStream i2s;
VolumeStream volume(i2s);
EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix());
StreamCopy copier(mp3decode, icystream);
void callbackMetadata(MetaDataType type, const char* str, int len) {
Serial.printf("%s: %s\n", toStr(type), str);
}
void setup() {
Serial.begin(115200);
AudioLogger::instance().begin(Serial, AudioLogger::Warning);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
volume.begin(config);
volume.setVolume(VOLUME);
mp3decode.begin();
icystream.begin(url);
icystream.setMetadataCallback(callbackMetadata);
}
void loop() {
copier.copy();
}
Imports
The code starts by including several libraries necessary for WiFi connectivity, I2S audio output, and MP3 decoding. The Arduino.h and WiFi.h libraries provide basic Arduino and WiFi functions. The Wire.h library is included but not used explicitly here. The AudioTools library and its related components handle audio streaming, decoding, and playback.
#include <Arduino.h> #include <WiFi.h> #include <Wire.h> #include "AudioTools.h" #include "AudioTools/AudioCodecs/CodecMP3Helix.h" #include "AudioTools/Communication/HTTP/ICYStream.h"
Constants and Pin Definitions
Next, the code defines the pins used for the I2S interface to communicate with the PCM5102A DAC. These pins correspond to the serial data, word select (left-right clock), and bit clock signals. Additionally, a volume level is set as a floating-point constant.
#define DIN_PIN 33 // serial data #define LRCK_PIN 32 // word select #define BCLK_PIN 25 // serial clock #define VOLUME 0.3 // Volume
The WiFi credentials and the URL of the internet radio stream are stored as constant character pointers.
const char* ssid = "ssid"; const char* password = "pwd"; const char* url = "https://jazz.stream.laut.fm/jazz";
Here a list of URLS for other internet radio stations you can try:
"https://jazz.stream.laut.fm/jazz" "http://vis.media-ice.musicradio.com/CapitalMP3"; "http://stream.srg-ssr.ch/m/rsj/mp3_128" "http://stream.live.vc.bbcmedia.co.uk/bbc_world_service" "http://icecast.omroep.nl/radio1-bb-mp3" "http://stream-02-eu.relaxingjazz.com/stream/1/"
Audio Stream Objects
Several objects are created to manage the audio streaming pipeline. The ICYStream object handles the HTTP streaming of the MP3 data from the internet radio URL, while the I2SStream object manages the I2S communication with the DAC. The VolumeStream object allows adjusting the playback volume and is connected to the I2S stream. And the EncodedAudioStream object decodes the MP3 data using the Helix MP3 decoder and outputs the decoded audio to the volume stream. Finally, the StreamCopy object copies the decoded audio data from the MP3 decoder to the ICY stream.
ICYStream icystream; I2SStream i2s; VolumeStream volume(i2s); EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix()); StreamCopy copier(mp3decode, icystream);
Metadata Callback Function
The function callbackMetadata is defined to handle metadata received from the internet radio stream, such as the current song title or artist. It prints the metadata type and content to the serial monitor for debugging or display purposes.
void callbackMetadata(MetaDataType type, const char* str, int len) {
Serial.printf("%s: %s\n", toStr(type), str);
}
Setup Function
In the setup() function, serial communication is initialized at 115200 baud for debugging output. The audio logger is also started to capture warnings and errors. The ESP32 then attempts to connect to the specified WiFi network, retrying every 500 milliseconds until successful.
After the WiFi connection is established, the I2S interface is configured for transmission mode with the specified pins for bit clock, word select, and data. The I2S stream and volume stream are initialized with this configuration, and the volume is set to the predefined level.
The MP3 decoder is started, and the ICY stream is initialized with the internet radio URL. The metadata callback function is registered to receive and handle metadata from the stream.
void setup() {
Serial.begin(115200);
AudioLogger::instance().begin(Serial, AudioLogger::Warning);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
volume.begin(config);
volume.setVolume(VOLUME);
mp3decode.begin();
icystream.begin(url);
icystream.setMetadataCallback(callbackMetadata);
}
Loop Function
The loop() function continuously copies audio data from the internet radio stream through the MP3 decoder and volume control to the I2S output. This keeps the audio playback running indefinitely.
void loop() {
copier.copy();
}
Code Example: Internet Radio with Volume Control
In this code example we are adding a potentiometer that operates as a Volume Control for the Internet Radio we implemented in the previous section. The picture below shows you the wiring:

The circuit is essentially the same as before. We just add a 10K Ohm potentiometer and three wires. Start by connecting one of the outer pins of the potentiometer to 3V and the other outer pin to ground (G) of the ESP32. Next connect the middle pin (swiper) of the potentiometer to GPIO 35 of the ESP32. Her a table with the required connections:
| Potentiometer | ESP32 |
|---|---|
| 1 | G |
| 2 | 35 |
| 3 | 3V |
You can swap the connection to pin 1 and 3 of the potentiometer, if you want to change the turning direction to increase the volume.
TickTwo Library
The following code for the Internet Radio with Volume Control requires the TickTwo library. To install it open the LIBRARY MANAGER, type “TickTwo” in the search bar, and click on the INSTALL button for the “TickTwo by Stefan Staub” library:

Below is the code for the internet radio with volume control. It is largely the same code as before. New is the updateVolume() function that is called every 100 msec, which updates the volume based on the potentiometer position:
/*
www.makerguides.com
Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools)
Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
Version: 0.9.2
- [TickTwo](https://github.com/sstaub/TickTwo)
Version: 4.4.0
*/
#include <Arduino.h>
#include <WiFi.h>
#include <Wire.h>
#include "TickTwo.h"
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#include "AudioTools/Communication/HTTP/ICYStream.h"
#define DIN_PIN 33 // serial data
#define LRCK_PIN 32 // word select
#define BCLK_PIN 25 // serial clock
#define POT_PIN 35 // Poti for Volume
const char* ssid = "ssid";
const char* password = "pwd";
const char* url = "https://jazz.stream.laut.fm/jazz";
ICYStream icystream;
I2SStream i2s;
VolumeStream volume(i2s);
EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix());
StreamCopy copier(mp3decode, icystream);
void callbackMetadata(MetaDataType type, const char* str, int len) {
Serial.printf("%s: %s\n", toStr(type), str);
}
void updateVolume() {
static float oldVol = -1;
long pot = analogRead(POT_PIN);
float vol = map(pot, 0, 4095, 0, 100) / 100.0;
if (fabs(vol - oldVol) >= 0.02) {
oldVol = vol;
volume.setVolume(vol);
}
}
TickTwo timer(updateVolume, 100);
void setup() {
Serial.begin(115200);
AudioLogger::instance().begin(Serial, AudioLogger::Warning);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
volume.begin(config);
volume.setVolume(0.1);
mp3decode.begin();
icystream.begin(url);
icystream.setMetadataCallback(callbackMetadata);
pinMode(POT_PIN, INPUT);
timer.start();
}
void loop() {
timer.update();
copier.copy();
}
Instead of a 10K Ohm linear potentiometer you could also use a logarithmic potentiometer, which provides a more natural relation between the perceived loudness and the potentiometer setting.
The updateVolume() function reads the potentiometer setting, maps it to a volume level and adjusts the volume if the new volume level is sufficiently different from the old volume. In the loop() function, we just need to call timer.update() repeatedly, to read the potentiometer and adjust the volume every 100 msec.
You could also add buttons to change the internet stations or to mute the audio. How to add buttons to the circuit and code is explained in the following example for a MP3 Player.
Code Example: Play MP3 from SD Card
The following code demonstrates how to play MP3 audio files stored on an SD card using an ESP32 microcontroller and the PCM5102A DAC module. It leverages the arduino-audio-tools and arduino-libhelix libraries to handle audio decoding and playback via the I2S interface.
/*
www.makerguides.com
Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools)
Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
Version: 0.9.2
*/
#include "AudioTools.h"
#include "AudioTools/Disk/AudioSourceSD.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
// PCM5102A
#define DIN_PIN 33 // serial data
#define LRCK_PIN 32 // word select
#define BCLK_PIN 25 // serial clock
#define VOLUME 0.5 // Volume
#define PATH "/"
#define EXT "mp3"
AudioSourceSD source(PATH, EXT);
I2SStream i2s;
MP3DecoderHelix decoder;
AudioPlayer player(source, i2s, decoder);
void printMetaData(MetaDataType type, const char* str, int len){
Serial.printf("%s: %s\n", toStr(type), str);
}
void setup() {
Serial.begin(115200);
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
player.setMetadataCallback(printMetaData);
player.setVolume(VOLUME);
player.begin();
}
void loop() {
player.copy();
}
Imports
At the beginning, the code includes several header files from the audio libraries. These provide the necessary classes and functions for reading audio files from the SD card, decoding MP3 streams, and outputting audio via I2S.
#include "AudioTools.h" #include "AudioTools/Disk/AudioSourceSD.h" #include "AudioTools/AudioCodecs/CodecMP3Helix.h"
Constants and Pin Definitions
The code defines pins used to connect the ESP32 to the PCM5102A DAC. These pins correspond to the I2S signals: serial data (DIN), word select (LRCK), and serial clock (BCLK). Additionally, a volume level is set as a floating-point value between 0.0 and 1.0.
#define DIN_PIN 33 // serial data #define LRCK_PIN 32 // word select #define BCLK_PIN 25 // serial clock #define VOLUME 0.5 // Volume
The PATH and EXT constants specify the root directory and file extension for the audio files to be played, which in this case are MP3 files located at the root of the SD card.
#define PATH "/" #define EXT "mp3"
Audio Objects
Several objects are instantiated to manage audio playback. AudioSourceSD handles reading audio files from the SD card matching the specified path and extension. I2SStream manages the I2S audio output stream. MP3DecoderHelix is the MP3 decoder based on the Helix library. Finally, AudioPlayer ties these components together to coordinate playback.
AudioSourceSD source(PATH, EXT); I2SStream i2s; MP3DecoderHelix decoder; AudioPlayer player(source, i2s, decoder);
Metadata Callback Function
The function printMetaData() is defined to receive metadata information such as artist, title, or album from the MP3 files during playback. It prints this information to the serial monitor using Serial.printf. The function takes the metadata type, a string containing the metadata, and its length as parameters.
void printMetaData(MetaDataType type, const char* str, int len){
Serial.printf("%s: %s\n", toStr(type), str);
}
Setup Function
In the setup() function, serial communication is initialized at 115200 baud to enable debugging output. The audio logging level is set to Warning to reduce verbosity.
Next, the I2S interface is configured for transmission mode. The default I2S configuration is obtained and modified to assign the previously defined pins for bit clock, word select, and data. The I2S stream is then started with this configuration.
The audio player is configured to use the printMetaData callback to display metadata during playback. The volume is set to the defined level, and the player is started with player.begin().
void setup() {
Serial.begin(115200);
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
player.setMetadataCallback(printMetaData);
player.setVolume(VOLUME);
player.begin();
}
Loop Function
The loop() function continuously calls player.copy(), which processes audio data by reading from the SD card, decoding the MP3 stream, and sending the audio samples to the I2S output. This keeps the audio playback running smoothly.
void loop() {
player.copy();
}
Code Example: MP3 Player with Volume and Track Control Buttons
A typical MP3 player has functions to control the volume and skip tracks forward and backward. In this code example we add a potentiometer for volume control and two buttons to skip tracks. The wiring diagram below shows you how to add a potentiometer and the buttons:

The following table list the connections you need to make. When connecting the button make sure that you connect the right poles.
| Potentiometer | Next Button | Prev Button | ESP32 |
|---|---|---|---|
| 1 | 4 | ||
| 1 | 15 | ||
| 1 | 2 | 2 | G |
| 2 | 35 | ||
| 3 | 3V |
We will use the EasyButton library to read the button status. To install it open the LIBRARY MANAGER, search for the “EasyButton by Evert Arias” library and press INSTALL:

The code below is the code for the MP3 player with the added functions onNextTrack(), onPrevTrack() and updateVolume(), which are used to skip tracks or to control the volume:
/*
www.makerguides.com
Libraries:
- ESP32 Core 3.3.7
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools)
Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
Version: 0.9.2
- [TickTwo](https://github.com/sstaub/TickTwo)
Version: 4.4.0
- [EasyButton](https://github.com/evert-arias/EasyButton)
Version: 2.0.3
*/
#include "TickTwo.h"
#include "EasyButton.h"
#include "AudioTools.h"
#include "AudioTools/Disk/AudioSourceSD.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
#define DIN_PIN 33 // serial data
#define LRCK_PIN 32 // word select
#define BCLK_PIN 25 // serial clock
#define POT_PIN 35 // Poti for Volume
#define PATH "/"
#define EXT "mp3"
EasyButton nextBtn(4);
EasyButton prevBtn(15);
AudioSourceSD source(PATH, EXT);
I2SStream i2s;
MP3DecoderHelix decoder;
AudioPlayer player(source, i2s, decoder);
void printMetaData(MetaDataType type, const char* str, int len) {
Serial.printf("%s: %s\n", toStr(type), str);
}
void updateVolume() {
static float oldVol = -1;
long pot = analogRead(POT_PIN);
float vol = map(pot, 0, 4095, 0, 100) / 100.0;
if (fabs(vol - oldVol) >= 0.02) {
oldVol = vol;
player.setVolume(vol);
}
}
void onPrevTrack() {
Serial.println("Prev track");
player.next(-1);
}
void onNextTrack() {
Serial.println("Next track");
player.next(+1);
}
TickTwo timer(updateVolume, 100);
void setup() {
Serial.begin(115200);
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
player.setMetadataCallback(printMetaData);
player.setVolume(0.1);
player.begin();
pinMode(POT_PIN, INPUT);
timer.start();
nextBtn.begin();
nextBtn.onPressed(onNextTrack);
prevBtn.begin();
prevBtn.onPressed(onPrevTrack);
}
void loop() {
timer.update();
nextBtn.read();
prevBtn.read();
player.copy();
}
Code Example: Text to Speech
This code example demonstrates how to implement a Text-to-Speech (TTS) system on an ESP32 using OpenAI’s TTS API and output the audio through a PCM5102A DAC. The audio data is streamed in MP3 format, decoded, and played via the I2S interface. The program connects to WiFi, sends text to the OpenAI API, receives the synthesized speech, and plays it back in real time.
/*
www.makerguides.com
Libraries:
- ESP32 Core 3.3.6
- [arduino-audio-tools](https://github.com/pschatzmann/arduino-audio-tools)
Version: 1.2.2
- [arduino-libhelix](https://github.com/pschatzmann/arduino-libhelix)
Version: 0.9.2
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "AudioTools.h"
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
// PCM5102A
#define DIN_PIN 33 // serial data
#define LRCK_PIN 32 // word select
#define BCLK_PIN 25 // serial clock
// Text to Speech
#define TTS_MODEL "gpt-4o-mini-tts"
#define TTS_VOICE "marin"
#define TTS_VOLUME 0.6
// WiFi credentials
const char* ssid = "ssid";
const char* password = "pwd";
// OpenAI configuration
const char* openaiHost = "api.openai.com";
const int openaiPort = 443;
const char* openaiApiKey = "apikey";
WiFiClientSecure client;
I2SStream i2s;
VolumeStream volume(i2s);
EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix());
StreamCopy copier(mp3decode, client);
void text2speech(const char* text) {
client.setInsecure();
if (!client.connect("api.openai.com", 443)) {
Serial.println("Connection failed");
return;
}
String body = String("{") +
"\"model\":\"" + TTS_MODEL + "\"," +
"\"voice\":\"" + TTS_VOICE + "\"," +
"\"format\":\"mp3\"," +
"\"input\":\"" + text + "\"" +
"}";
client.println("POST /v1/audio/speech HTTP/1.1");
client.println("Host: api.openai.com");
client.println("Authorization: Bearer " + String(openaiApiKey));
client.println("Content-Type: application/json");
client.print("Content-Length: ");
client.println(body.length());
client.println();
client.print(body);
// ---- Skip HTTP headers ----
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line == "\r") break;
}
}
void setup() {
Serial.begin(115200);
AudioLogger::instance().begin(Serial, AudioLogger::Warning);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
mp3decode.begin();
volume.begin(config);
volume.setVolume(TTS_VOLUME);
text2speech("Hello, how are you doing today?");
}
void loop() {
copier.copy();
}
Imports
The code starts by including the necessary libraries. Arduino.h provides the core Arduino functions. WiFi.h and WiFiClientSecure.h handle the WiFi connection and secure HTTPS communication. The AudioTools library and its MP3 codec CodecMP3Helix are used for audio streaming and decoding.
#include <Arduino.h> #include <WiFi.h> #include <WiFiClientSecure.h> #include "AudioTools.h" #include "AudioTools/AudioCodecs/CodecMP3Helix.h"
Constants and Pin Definitions
The pins for the PCM5102A DAC are defined to configure the I2S interface. These include the serial data pin (DIN_PIN), word select pin (LRCK_PIN), and serial clock pin (BCLK_PIN). Additionally, constants for the TTS model, voice, and volume level are set. WiFi credentials and OpenAI API details are also declared as constant character pointers.
// PCM5102A #define DIN_PIN 33 // serial data #define LRCK_PIN 32 // word select #define BCLK_PIN 25 // serial clock // Text to Speech #define TTS_MODEL "gpt-4o-mini-tts" #define TTS_VOICE "marin" #define TTS_VOLUME 0.6 // WiFi credentials const char* ssid = "ssid"; const char* password = "pwd"; // OpenAI configuration const char* openaiHost = "api.openai.com"; const int openaiPort = 443; const char* openaiApiKey = "apikey";
You can try other TTS models, such as “tts-1” and other voices such as “alloy”, “ash”, “coral”, “echo”, “fable”, “onyx”, “nova”, ‘sage”, “shimmer”, “marin”, “cedar”. For more details have a look at platform.openai.com/docs/guides/text-to-speech.
WiFi credentials and OpenAI API details are also stored as constant strings. You will have to replace “ssid” and “pwd” with the credentials for your WiFi.
// WiFi credentials const char* ssid = "ssid"; const char* password = "pwd";
You also will need to get an “apikey” from OpenAI. Go to https://platform.openai.com and sign up with an email address or an existing Google or Microsoft account.
// OpenAI configuration const char* openaiHost = "api.openai.com"; const int openaiPort = 443; const char* openaiApiKey = "apikey";
After verifying your email and completing the initial setup, log in to the OpenAI dashboard, platform.openai.com/api-keys and find or create your API Key (=SECRET KEY) as shown below:

The API Key is a unique, long string, starting with “sk-proj-” that is needed to authenticate your API requests (see below).
sk-proj-xcA.......................OtDu0U
That is all you need to get an API key but I recommend you set a usage limit for your account as well. For more details see the Vision Chatbot with DFRobot ESP32-S3 AI Camera and OpenAI tutorial.
Audio and Network Objects
Several objects are instantiated to manage audio streaming and network communication. WiFiClientSecure handles the HTTPS connection to the OpenAI server. I2SStream manages the I2S audio output. The VolumeStream object wraps the I2S stream to control audio volume. EncodedAudioStream is used to decode the MP3 audio stream using the Helix MP3 decoder. Finally, StreamCopy copies the decoded audio stream to the network client.
WiFiClientSecure client; I2SStream i2s; VolumeStream volume(i2s); EncodedAudioStream mp3decode(&volume, new MP3DecoderHelix()); StreamCopy copier(mp3decode, client);
Text-to-Speech Function
The text2speech() function takes a text string as input and sends it to the OpenAI TTS API to generate speech audio. It first configures the client to accept insecure SSL connections (useful for development). Then it attempts to connect to the OpenAI server on port 443. If the connection fails, it prints an error message and returns.
The function constructs a JSON body containing the TTS model, voice, output format (MP3), and the input text. It sends an HTTP POST request with the appropriate headers, including the authorization bearer token with the API key. After sending the request body, it reads and skips the HTTP response headers to prepare for streaming the audio data.
void text2speech(const char* text) {
client.setInsecure();
if (!client.connect("api.openai.com", 443)) {
Serial.println("Connection failed");
return;
}
String body = String("{") +
"\"model\":\"" + TTS_MODEL + "\"," +
"\"voice\":\"" + TTS_VOICE + "\"," +
"\"format\":\"mp3\"," +
"\"input\":\"" + text + "\"" +
"}";
client.println("POST /v1/audio/speech HTTP/1.1");
client.println("Host: api.openai.com");
client.println("Authorization: Bearer " + String(openaiApiKey));
client.println("Content-Type: application/json");
client.print("Content-Length: ");
client.println(body.length());
client.println();
client.print(body);
// ---- Skip HTTP headers ----
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line == "\r") break;
}
}
Setup Function
In the setup() function, serial communication is initialized for debugging output. The audio logger is configured to report warnings. The ESP32 connects to the specified WiFi network, waiting until the connection is established.
Next, the I2S interface is configured with the previously defined pins for bit clock, word select, and data output. The MP3 decoder and volume control are initialized with this configuration, and the volume is set to the defined level.
Finally, the text2speech() function is called with a sample greeting text, which triggers the TTS process and starts streaming audio from the OpenAI API.
void setup() {
Serial.begin(115200);
AudioLogger::instance().begin(Serial, AudioLogger::Warning);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
auto config = i2s.defaultConfig(TX_MODE);
config.pin_bck = BCLK_PIN;
config.pin_ws = LRCK_PIN;
config.pin_data = DIN_PIN;
i2s.begin(config);
mp3decode.begin();
volume.begin(config);
volume.setVolume(TTS_VOLUME);
text2speech("Hello, how are you doing today?");
}
Loop Function
The loop() function continuously copies the decoded MP3 audio stream from the OpenAI server to the I2S output. This keeps the audio playback running as long as the connection remains open.
void loop() {
copier.copy();
}
Conclusions
In this project, you learned how to play audio using the ESP32 and the PCM5102A DAC. We explored the technical details of the PCM5102A module and how to wire it to the ESP32. You also learned how to convert text to speech, stream internet radio, play MP3 files from an SD Card and play audio via Bluetooth.
For better and louder sound you can connect an amplifier to the PCM5102A line output. See the following tutorials for more information:
| Tutorial | Output Wattage | Power Supply |
| TDA7379 Class AB Audio Amplifier with ESP32 | 2 × 38 W | 9 .. 15 V |
| High-Power ESP32 Audio with TPA3116D2 and PCM5102 | 2 x 50 W | 4.5 .. 26 V |
| Audio with PAM8403, PCM5102 and ESP32 | 2 x 3 W | 2.5 .. 5.5 V |
| Stereo Amplifier with TPA31110 XH-A232, PCM5102 and ESP32 | 2 x 30 W | 8 .. 26 V |
| Playing Audio with ESP32 and MAX98357 | 1 x 3 W | 3.3 .. 5V |
| Playing Audio with ESP32 and PCM5102A | line level | 3.3 .. 5 V |
| Playing Sound with PAM8403 and ESP32 | 2 x 3 W | 2.5 .. 5.5 V |
| Audio with YDA138-E Amplifier, PCM5102 and ESP32 | 2 x 20 W | 9 .. 13.5 V |
If you have any question feel free to leave them in the comment section.
Happy Tinkering ; )
FAQ
Q: What is the PCM5102A and why should I use it?
The PCM5102A is a high-quality digital-to-analog converter that connects to the ESP32 via I2S and converts digital audio into a clean analog stereo signal, which is much better than the ESP32’s internal DAC and results in clearer sound, lower noise, and better overall audio quality for music, speech, or streaming applications.
Q: How is the PCM5102A connected to the ESP32?
The PCM5102A uses three main I2S signals plus power.
Example wiring:
ESP32_3V3 ----> VIN ESP32_GND ----> GND ESP32_GPIO32 ----> LRCK ESP32_GPIO25 ----> BCK ESP32_GPIO33 ----> DIN SCK ----> GND // Optional
The internal clock of the PCM5102A removes the need for an external master clock, which simplifies the wiring.
Q: Can I connect a speaker directly to the PCM5102A?
No, because the PCM5102A only provides a line-level signal, which is too weak to drive a speaker directly and must be amplified first.
Correct setup:
PCM5102A --> Amplifier --> Speaker
Or with active speakers:
PCM5102A --> Active Speakers
Q: What amplifier should I use with the PCM5102A?
You can use a small Class-D amplifier for simple projects or a more powerful stereo amplifier for better sound and higher volume.
See the High-Power ESP32 Audio with TPA3116D2 and PCM5102 or TDA7379 Class AB Audio Amplifier with ESP32 tutorial, for instance.
Q: How can I improve sound quality with proper power supply filtering?
A clean power supply is very important, because noise on the supply line directly affects the DAC output. You can add some capacitors:
VIN -- 100nF -- GND VIN -- 100µF -- GND
The small capacitor removes high-frequency noise, while the large capacitor smooths voltage ripple and provides stable power for the DAC.
If possible, also filter the amplifier supply:
5V -- 10Ω --+-- AMP_VCC | 220µF | GND
This reduces noise coming from the ESP32 or USB power.
Q: Should I use separate power supplies for DAC and amplifier?
Using separate or filtered supplies can improve audio quality, because the amplifier draws large currents that can introduce noise into the DAC supply. For instance:
5V ----> DAC 5V ----> Filter ----> Amplifier
This reduces interference between digital and power stages.
Q: Do I need capacitors on the audio output?
Usually no, because the PCM5102A already has a DC-free output stage, but in some cases, adding a coupling capacitor can help if the amplifier input is sensitive:
DAC_OUT -- 1µF -- AMP_IN
This can reduce low-frequency noise or offset issues.
Q: How can I increase volume?
Volume depends mostly on the amplifier and the speakers, not the DAC. To increase volume:
- Use a higher power amplifier
- Use a higher supply voltage (within limits)
- Use efficient speakers
The DAC already provides a strong line signal, so the amplifier stage determines the final loudness.
Q: What is the difference between 4Ω and 8Ω speakers?
Speaker impedance has a big effect on volume and amplifier load.
- 4Ω speakers draw more current and produce higher volume
- 8Ω speakers draw less current and are easier to drive
Many small amplifiers like the PAM8403 deliver more power into 4Ω speakers, but you must ensure the amplifier can handle the load without overheating or distortion.
Q: How can I improve overall sound quality further?
There are several practical improvements that together make a big difference.
Keep wiring short:
DAC_L ----> AMP_L DAC_R ----> AMP_R
Avoid running audio wires near power or speaker wires.
Use a star ground layout:
ESP32_GND ----+---- DAC_GND | AMP_GND
This prevents noise currents from affecting the audio signal.
Use good speakers, because even the best electronics cannot fix poor speaker quality.
Q: Why is the PCM5102A setup much cleaner than ESP32 DAC + amplifier?
You can connect an amplifier directly to the DACs of the ESP32. See the Playing Sound with PAM8403 and ESP32 tutorial for details but the sound quality will not be very good, due to the low resolution (8 Bits), and the analog signal way.
The PCM5102A on the other hand, uses high-resolution digital processing and internal filtering, which removes most noise and distortion before the signal becomes analog.
Q: What is the best setup for maximum quality and volume?
For the best results, combine a high-quality DAC, a good amplifier, and suitable speakers. See the High-Power ESP32 Audio with TPA3116D2 and PCM5102 or TDA7379 Class AB Audio Amplifier with ESP32 tutorial, for instance.
Stefan is a professional software developer and researcher. He has worked in robotics, bioinformatics, image/audio processing and education at Siemens, IBM and Google. He specializes in AI and machine learning and has a keen interest in DIY projects involving Arduino and 3D printing.


Walter
Thursday 19th of February 2026
Hi Stefan, great article, thanks for providing!
However, I'm a little bit confused about the jumper settings; they differ in "Solder bridges" and "Connect PCM5102A to ESP32". Looks like that "Solder bridges" has jumper 1 set to HIGH, which has not been the recommendation stated above in the schematic.
Did I got it right?
Stefan Maetschke
Thursday 19th of February 2026
Thank you very much for pointing this out! The picture with the jumper settings in "Solder bridges" was incorrect but has been fixed now.