If you need to output a true analog voltage from an ESP32, you are looking for the hardware Digital-to-Analog Converter (DAC). The direct answer: the original ESP32 has exactly two 8-bit DAC pins: GPIO25 (DAC1) and GPIO26 (DAC2). They accept integer values from 0 to 255 and output a nominal 0V to 3.3V.
However, there is a massive catch that trips up makers upgrading their fleet: the ESP32-S3, ESP32-C3, and ESP32-C6 do not have hardware DACs. If you try to run standard DAC code on those newer chips, your compiler will throw errors, or worse, you will silently get digital PWM signals that fry your analog op-amp stages. Furthermore, the ESP32's internal DAC is notoriously non-linear at the voltage rails. In this guide, we will map out the exact silicon variants, write a bulletproof sine wave generator, and debug the most common compiler errors you will face at the bench.
ESP32 DAC Pin Mapping and Silicon Variants
Before writing a single line of code, you must verify which silicon is under the metal RF shield on your development board. Espressif's naming conventions can be confusing; "ESP32" refers to the original dual-core Xtensa LX6 chip, while the S-series and C-series are entirely different architectures.
| Chip Variant | Hardware DAC Pins | Resolution | Max Sampling Rate | PWM/I2S Fallback Required? |
|---|---|---|---|---|
| ESP32 (Original) e.g., WROOM-32E |
GPIO25, GPIO26 | 8-bit (0-255) | ~20 kHz (software) | No |
| ESP32-S2 e.g., WROOM-S2 |
GPIO17, GPIO18 | 8-bit (0-255) | ~100 kHz (hardware DMA) | No |
| ESP32-S3 e.g., WROOM-1 |
None | N/A | N/A | Yes (Use I2S or PWM+RC) |
| ESP32-C3 / C6 RISC-V variants |
None | N/A | N/A | Yes (Use PWM+RC) |
If you hook GPIO25 to an oscilloscope and sweep the value from 0 to 255, you will not see a perfectly straight transfer function. The ESP32 DAC sags at the rails. Values below 20 (~0.2V) and above 230 (~3.1V) compress heavily. If you are driving a precision analog circuit, restrict your software output range to
20-230 and use an external op-amp to scale the voltage back to your required 0-3.3V window.
Recommended Parts List
- Microcontroller: Espressif ESP32-DevKitC V4 (featuring the ESP32-WROOM-32E module). Do not buy the S3 or C3 for this specific build.
- Oscilloscope / Multimeter: Any basic DMM to verify DC output, or a Rigol DS1054Z to view the waveform.
- Low-Pass Filter (Optional): 1kΩ resistor and 100nF ceramic capacitor to smooth out high-frequency stepping noise if you are driving an audio amplifier.
Generating a True Analog Sine Wave: Wiring and Code
The most common use case for the ESP32 DAC is generating audio tones or low-frequency control voltages (LFOs). Because the DAC is only 8-bit, we map our waveform to a 256-step lookup table.
The code below includes a critical pre-processor guard. If you accidentally select an ESP32-S3 or C3 in the Arduino IDE, the compiler will halt and throw a custom error rather than silently failing or bricking your peripheral setup. This targets the ESP32 Arduino Core v2.x and v3.x.
Pin Mapping Table
| ESP32 Pin | DAC Channel | ADC2 Overlap | Connection Target |
|---|---|---|---|
| GPIO25 | DAC1 | ADC2_CH8 | Oscilloscope Probe / Audio Amp IN |
| GPIO26 | DAC2 | ADC2_CH9 | Secondary Channel / Stereo Right |
| GND | Ground | N/A | Scope Ground Clip / Amp GND |
Compilable Sine Wave Code
/*
* Target Board: ESP32-WROOM-32E (Standard ESP32 DevKit V1/V4)
* Core Version: ESP32 Arduino Core 2.0.x or 3.0.x
* Author: ElectricalFlux Bench Team
*/
// Hardware guard: Prevent compilation on chips without a DAC
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
#error "Hardware DAC is only available on the original ESP32 and ESP32-S2. Select the correct board in Tools > Board or use I2S/PWM."
#endif
#include
#include
#define DAC_PIN_1 25
#define SINE_STEPS 256
// Lookup table to avoid calculating sin() in the real-time loop
uint8_t sineWave[SINE_STEPS];
void setup() {
Serial.begin(115200);
// Initialize the DAC pin using the IDF driver API for stability
dac_output_enable(DAC_CHANNEL_1); // Maps to GPIO25
// Pre-calculate sine wave (0-255 mapped to 8-bit DAC)
// We clamp the range to 20-230 to avoid the non-linear rail sag
for (int i = 0; i < SINE_STEPS; i++) {
float rawSine = sin((i * 2.0 * PI) / SINE_STEPS); // -1.0 to 1.0
// Map to 20-230 range (210 steps total, centered at 125)
sineWave[i] = (uint8_t)(125 + 105 * rawSine);
}
Serial.println("DAC Initialized on GPIO25. Outputting ~50Hz Sine Wave.");
}
void loop() {
// Output the waveform
// 256 steps * 75us delay = 19,200us per cycle = ~52Hz
for (int i = 0; i < SINE_STEPS; i++) {
dac_output_voltage(DAC_CHANNEL_1, sineWave[i]);
delayMicroseconds(75);
}
}
GPIO25 and GPIO26 double as ADC2 channels. If you turn on the WiFi radio (
WiFi.begin()), the ESP32 hardware arbiter locks out the ADC2 peripheral. However, the DAC peripheral remains unaffected. You can still output analog voltages while streaming WiFi data, but you cannot use analogRead() on those specific pins to monitor your own output while WiFi is active.
Debugging: "'dacWrite' was not declared in this scope"
If you are migrating an older sketch to a new board, or following a tutorial that uses the simplified dacWrite(pin, value) Arduino wrapper, you will likely hit this exact compiler error:
error: 'dacWrite' was not declared in this scope; did you mean 'ledcWrite'?
This is rarely a syntax error in your code. It is almost always a mismatch between your physical silicon and the Arduino IDE board definition. Here are the first three things to check when this fails:
- Verify the IDE Board Selection: Go to Tools > Board. If you have "ESP32S3 Dev Module" or "ESP32C3 Dev Module" selected, the compiler strips out all DAC functions because the silicon physically lacks the peripheral. Switch to "ESP32 Dev Module" (assuming you actually have an original ESP32 on your desk).
- Read the RF Shield Silkscreen: Look closely at the metal can on your dev board. If it says ESP32-S3-WROOM-1, no amount of IDE tweaking will give you a DAC. You must rewrite your code to use PWM or I2S (see the extension section below).
- Check Arduino Core Version: In ESP32 Arduino Core v3.0.0+, Espressif restructured some of the legacy Arduino API wrappers. If you are on v3.x and targeting an original ESP32, ensure you are including
#include <driver/dac.h>and using the IDF-nativedac_output_voltage()as shown in the code block above, which is significantly more stable across core versions than the legacydacWrite()wrapper.
For deeper architectural details on how the DAC peripheral interfaces with the APB bus, refer to the Espressif ESP32 Technical Reference Manual (Chapter 29: Digital-to-Analog Converter). For standard API wrapper documentation, check the official Arduino-ESP32 DAC API docs.
Extending the Build: High-Fidelity Audio via I2S
The 8-bit hardware DAC is fantastic for generating control voltages, simple beeps, or low-frequency function generator outputs. But if you want to play 16-bit/44.1kHz WAV files or synthesize high-fidelity audio, the internal DAC will introduce noticeable quantization noise and stepping artifacts.
How to Simplify (The PWM + RC Filter Route)
If you only need a slow-moving DC voltage (e.g., simulating a 0-10V industrial control signal via an op-amp) and you are stuck with an ESP32-S3, simplify by using PWM. Use ledcSetup() at a high frequency (e.g., 20kHz), and pass the signal through a simple RC low-pass filter (10kΩ resistor + 1µF capacitor). This averages the digital pulses into a smooth DC voltage. It is not suitable for audio, but it perfectly replaces a DAC for slow control loops.
How to Extend (The I2S Amplifier Route)
If you need true audio on any ESP32 variant (including the S3 and C3), abandon the internal DAC entirely and use the I2S peripheral with an external breakout board.
- Hardware: Adafruit MAX98357A I2S Amplifier Breakout (Product ID: 3006).
- Wiring: Connect ESP32 GPIO22 (BCLK), GPIO25 (LRCLK), and GPIO21 (DIN) to the MAX98357A.
- Software: Use the
ESP8266Audiolibrary or the native<driver/i2s_std.h>IDF driver to stream 16-bit stereo data directly to the amplifier.
By understanding the hard limits of the ESP32 DAC pins—and knowing exactly when to pivot to I2S or PWM—you can stop fighting the silicon and start building reliable, noise-free analog interfaces for your embedded projects.






