ESP32 DAC True Analog Output Guide: dacWrite vs “Fake PWM”
Prerequisite: PWM Complete Guide. PWM dimming is really “switching fast enough to fool the load” — the pin only ever sits at 0V or 3.3V. This article covers the DAC: genuine continuous voltage output.
1. DAC vs PWM: one letter apart, fundamentally different
| Aspect | PWM (duty cycle) | DAC (true analog) |
|---|---|---|
| Output levels | Only 0V / 3.3V (fast switching) | Any intermediate voltage (continuous) |
| Principle | Time-averaging “fools” the load | Resistor ladder divides directly |
| Pins | Any GPIO | GPIO25/26 only (classic ESP32) |
| Resolution | Up to 16 bits theoretically | 8 bits (256 steps) |
| Filtering needed | Depends on the load | No |
| Typical uses | LED brightness, motor speed | Waveform generation, bias voltage, audio |
One-liner: PWM is duty-cycle art; DAC is voltage direct out. LEDs want PWM; sine waves and op-amp bias voltages want a DAC.
2. Per-chip availability (important)
| Chip | DAC | Notes |
|---|---|---|
| Classic ESP32 | ✅ 2 channels (GPIO25/26) | 8-bit |
| ESP32-S2 | ✅ 2 channels (GPIO17/18) | 8-bit |
| ESP32-S3 | ❌ no DAC | Use an external DAC (e.g. MCP4725 over I2C) |
| ESP32-C3/C6 | ❌ no DAC | Same |
New project on S3/C3 that truly needs analog out: the MCP4725 module (12-bit, I2C, ~¥3) is the standard answer — wiring covered in the buses guide.
3. dacWrite in five minutes
#define DAC_PIN 25 // must be 25 or 26!
void setup() { /* DAC needs no pinMode setup */ }
void loop() {
dacWrite(DAC_PIN, 0); // 0V
delay(1000);
dacWrite(DAC_PIN, 128); // ~1.65V
delay(1000);
dacWrite(DAC_PIN, 255); // ~3.3V
delay(1000);
}
- 8 bits = 0–255, ~12.9mV per step
- The output impedance is high (don’t drive low-impedance loads directly); buffer with an op-amp follower
- Voltages are “approximately” — calibrate if you need precision
4. Project: generating a sine wave
#define DAC_PIN 25
void setup() {
Serial.begin(115200);
}
void loop() {
// 100Hz sine: 100 points per cycle, 100µs each
for (int i = 0; i < 100; i++) {
float v = (sinf(i * 2 * PI / 100) + 1) / 2 * 255;
dacWrite(DAC_PIN, (int)v);
delayMicroseconds(100);
}
}
Replacing
delayMicrosecondswith a hardware timer yields a rock-stable frequency — the four-layer selection from the timers guide applies directly.
Ceiling: DAC conversion takes ~8µs → theoretical max square wave ~1MHz, practical sines in the tens of kHz. Voice-grade audio: fine. HiFi: no.
5. Primer: audio in half a page
DAC + amp (MAX98357A) is the simplest ESP32 audio chain. The modern approach is I2S digital audio (higher precision, doesn’t consume the DAC) — an L3 topic with its own article. DAC audio’s value is understanding the principle: looping dacWrite over PCM samples at the sample rate is bare-metal audio output.
6. Pitfall cheat sheet
| Scenario | Verdict |
|---|---|
Calling dacWrite on S3/C3 | No DAC — may compile but outputs nothing; use MCP4725 |
| Wanting ADC on GPIO25/26 | These are also ADC2 channels — conflict once Wi-Fi starts |
| DAC can’t drive the load | High output impedance — add an op-amp follower |
| Wobbly waveform frequency | delay jitter — drive from a hardware timer |
| Need 12-bit precision | External MCP4725 (I2C); the built-in one is 8-bit |
| Should LEDs use the DAC? | No — PWM is enough and works on any pin |
7. Further reading
- 🧭 Previous: PWM Complete Guide — duty-cycle dimming
- ⏭️ Next: UART/I2C/SPI — the three buses (includes wiring an external DAC)
License: CC BY-SA 4.0. Repost & remix with attribution: ESPDIY Maker Lab - espdiy.com