Phase 3: GPIO & Hardware 12 min read

DAC Explained on ESP32: Generate Analog Voltages and Audio Signals

Learn how to use the ESP32 built-in 8-bit DAC — output analog voltages on GPIO25/GPIO26, generate sine waves, audio signals, and understand DAC vs PWM differences.

Updated June 19, 2026

What is a DAC?

A Digital-to-Analog Converter (DAC) is the opposite of an ADC. Where an ADC reads a voltage and converts it to a number, a DAC takes a number and generates a proportional analog voltage. ESP32 includes two 8-bit DAC channels, letting you output any voltage between 0V and 3.3V from GPIO25 and GPIO26.

Applications include audio output, voltage reference generation, waveform synthesis, analog control signals for external amplifiers, and smooth LED dimming without PWM ripple.

ESP32 DAC Pins and Characteristics

Feature Specification
DAC channels 2 (DAC1 = GPIO25, DAC2 = GPIO26)
Resolution 8-bit (0–255)
Output range 0 V to 3.3 V (VDD)
Voltage per step 3.3 V / 255 ≈ 12.9 mV
Maximum output current ~1 mA (buffer amplifier needed for loads)
Settling time ~1 µs
Waveform generator Hardware cosine oscillator built-in

Basic dacWrite() Usage

Arduino (C++)
#include <Arduino.h>

#define DAC_PIN_1 25   // DAC1
#define DAC_PIN_2 26   // DAC2

void setup() {
  Serial.begin(115200);
  // No pinMode needed for DAC pins — dacWrite() handles it
}

void loop() {
  // Step through all 256 voltage levels
  for (int val = 0; val <= 255; val++) {
    dacWrite(DAC_PIN_1, val);
    float volts = val * (3.3f / 255.0f);
    Serial.printf("DAC value: %3d  Voltage: %.3f Vn", val, volts);
    delay(20);
  }
  delay(500);
}

DAC vs PWM Comparison

Feature DAC (True Analog) PWM (Simulated Analog)
Output type Steady DC voltage Pulsed 0V/3.3V switching
Resolution 8-bit (256 steps) Up to 16-bit (65536 steps)
Switching noise None ✓ Present (filterable)
Available pins GPIO25, GPIO26 only All output-capable GPIOs
For audio output? Better ✓ Requires filter
For LED dimming? Works (1 mA limit) Better (higher current)
For motor speed? No (insufficient current) Yes ✓

Generating a Sine Wave

Arduino (C++) — Software sine wave
#include <math.h>

#define DAC_PIN  25
#define SAMPLES  100    // Points per cycle
#define FREQ_HZ  50     // Target frequency: 50 Hz

uint8_t sineTable[SAMPLES];

void setup() {
  Serial.begin(115200);
  // Pre-calculate sine lookup table
  for (int i = 0; i < SAMPLES; i++) {
    float angle = 2.0f * M_PI * i / SAMPLES;
    sineTable[i] = (uint8_t)((sin(angle) + 1.0f) * 127.5f);  // 0–255
  }
}

void loop() {
  unsigned long delayUs = 1000000UL / (FREQ_HZ * SAMPLES);  // µs per step
  for (int i = 0; i < SAMPLES; i++) {
    dacWrite(DAC_PIN, sineTable[i]);
    delayMicroseconds(delayUs);
  }
}

At 50 Hz with 100 samples, each step needs a 200 µs delay. This is achievable but CPU-blocking. For higher frequencies or non-blocking operation, use the I2S-DAC interface with DMA.

Simple Audio Tone

Arduino (C++) — 440 Hz tone (A4 note)
#include <math.h>

#define DAC_PIN 25
#define TONE_HZ 440

void setup() {
  Serial.begin(115200);
  Serial.println("Playing 440 Hz tone on DAC...");
}

void loop() {
  static unsigned long lastMicros = 0;
  static int phase = 0;
  const int STEPS = 50;
  unsigned long stepUs = 1000000UL / (TONE_HZ * STEPS);

  if (micros() - lastMicros >= stepUs) {
    lastMicros = micros();
    float angle = 2.0f * M_PI * phase / STEPS;
    dacWrite(DAC_PIN, (uint8_t)((sin(angle) + 1.0f) * 127));
    phase = (phase + 1) % STEPS;
  }
}

Dual DAC: Two-Phase Signal

Arduino (C++) — Quadrature (I/Q) signals
#include <math.h>

#define DAC1_PIN 25   // In-phase
#define DAC2_PIN 26   // Quadrature (90° offset)
#define STEPS    64

void setup() { }

void loop() {
  for (int i = 0; i < STEPS; i++) {
    float angle = 2.0f * M_PI * i / STEPS;
    dacWrite(DAC1_PIN, (uint8_t)((sin(angle)        + 1.0f) * 127));
    dacWrite(DAC2_PIN, (uint8_t)((sin(angle + M_PI/2) + 1.0f) * 127));
    delayMicroseconds(100);
  }
}

Adding an Op-Amp Buffer for Driving Loads

ESP32 DAC pins can only source ~1 mA. For driving a small speaker, headphone, or any analog circuit requiring more current, add an LM358 op-amp configured as a unity-gain voltage follower:

Op-Amp Buffer Circuit
ESP32 GPIO25 ──── LM358 IN+ (non-inverting)
LM358 OUT ──── LM358 IN– (inverting, direct feedback = unity gain)
LM358 OUT ──── Load (speaker, analog input, etc.)

LM358 VCC: 3.3V–12V (higher voltage allows more output swing)
LM358 GND: shared with ESP32 GND

Output follows input with up to 20 mA drive capability.

Practical Limitations and When to Use External DAC

Need ESP32 DAC External DAC Recommendation
8-bit rough voltage Sufficient ✓
Audio output (low quality) Sufficient ✓
High-quality audio Insufficient PCM5102A (32-bit I2S)
12-bit precision voltage Insufficient MCP4725 (12-bit I2C)
16-bit output Insufficient DAC8552 (16-bit SPI)
Multiple channels 2 channels max MCP4728 (4-ch, 12-bit I2C)

Summary

ESP32's built-in 8-bit DAC on GPIO25 and GPIO26 provides true analog voltage output without PWM noise, making it ideal for audio tones, waveform generation, and smooth analog control signals. Its main limitations are 8-bit resolution (12.9 mV steps) and 1 mA current output. Add an op-amp buffer for driving loads and consider an external I2S DAC for high-quality audio applications.

Frequently Asked Questions

A DAC (Digital-to-Analog Converter) converts a digital number into a proportional analog voltage. ESP32 has two 8-bit DAC channels that output voltages between 0V and 3.3V on GPIO25 and GPIO26, controlled by the dacWrite() function with values from 0 (0V) to 255 (3.3V).
Only GPIO25 (DAC1) and GPIO26 (DAC2) have hardware DAC capability on ESP32. No other GPIO pins can produce true analog voltage output. GPIO25 and GPIO26 are dedicated to DAC and should not be used for other analog or digital functions when DAC is active.
PWM (Pulse Width Modulation) rapidly switches a pin between 0V and 3.3V. The average voltage simulates analog but still has switching noise. True DAC output is a steady DC voltage with no switching — appropriate for analog circuits, audio, and precision voltage references that cannot tolerate PWM ripple.
ESP32 DAC is 8-bit: 256 steps over 0–3.3V. Each step is 3.3/255 = 0.0129V (12.9 mV) per increment. This is adequate for simple audio and rough voltage control but insufficient for precision applications requiring less than 1% error.
Yes. ESP32 DAC supports DMA-driven audio output at sample rates up to 16 kHz using I2S peripheral routed to the DAC. For better audio quality, use an external I2S DAC (PCM5102, MAX98357A) which provides 32-bit resolution and better SNR.
ESP32 DAC pins can source about 1 mA maximum. They are voltage reference outputs, not power outputs. To drive a speaker or any load needing more than 1 mA, add a buffer amplifier (LM358 op-amp) or audio amplifier IC between the DAC pin and the load.
Yes. If you do not call dacWrite() or initialise the DAC peripheral, GPIO25 and GPIO26 function as normal digital GPIO pins. They can also be used as ADC2 channels (though ADC2 is unreliable when Wi-Fi is active). To switch back to GPIO mode after using DAC, call dac_output_disable(DAC_CHANNEL_1).
Using dacWrite() in loop(), update rates reach about 100 kSPS. Using the I2S-DAC interface with DMA, sample rates up to ~44.1 kHz are achievable, enabling full audio-quality waveforms. The theoretical Nyquist limit for 44.1 kHz sampling is 22 kHz — covering the full audible range.
Yes. Pre-calculate a lookup table of sine values (0–255 for 8-bit), then output each value to dacWrite() in a timed loop or using the cosine waveform generator built into ESP32 (accessed via ESP-IDF). The hardware cosine oscillator can generate sine/cosine waves up to 40 MHz without CPU involvement.
ESP32 has a hardware cosine wave generator (CW generator) inside the DAC peripheral. It can produce sine/cosine waves from a few Hz up to 40 MHz with programmable frequency, amplitude, and phase offset — all without CPU load. Access it through ESP-IDF or the dacCosineEnable() function in some Arduino core variants.

Projects to Build

Put this knowledge to work — try one of these hands-on projects.