Embedded

PWM vs DAC: When to Use Which

Comparing pulse-width modulation and digital-to-analog conversion for real-world signal generation, with ESP32 code and an RC filter you can calculate.

Microcontrollers live in a world of ones and zeros, but the things we control — LED brightness, motor speed, audio, a reference voltage — want something in between. There are two common ways to get "in between" out of a digital chip: switch a pin on and off very fast (PWM), or produce a real intermediate voltage (DAC). They are not interchangeable, and picking the wrong one is a classic source of noise, flicker and frustration.

Two ways to make "half a volt"

  • PWM — pulse-width modulation. The pin is only ever fully on or fully off, but the proportion of time it spends on — the duty cycle — sets the average.
  • DAC — digital-to-analog converter. A circuit inside (or next to) the chip turns a number into an actual steady voltage between 0 and its reference.

Measure both with a slow multimeter and they may show the same voltage. Look with an oscilloscope and they are completely different signals.

PWM: full on / full off DAC: real levels 25%50%75% each step = Vref / 2N
PWM's dashed average equals duty × supply. A DAC holds each level steady until it gets a new value.

How PWM works

A hardware timer counts up from zero to a top value, then restarts. The pin is high while the count is below a compare value and low after it. The ratio of compare to top is the duty cycle, D, and the average voltage is simply:

Vavg = D × Vsupply

At 3.3 V, 25 % duty averages 0.825 V; 50 % averages 1.65 V.

The frequency–resolution trade-off

Because the duty step is one timer tick, resolution and frequency fight each other. For a counter clocked at fclk with N bits:

fPWM,max = fclk / 2N

The ESP32's LEDC peripheral, clocked from 80 MHz, can do 13-bit resolution (8192 steps) only up to about 80 MHz ÷ 8192 ≈ 9.8 kHz. Ask for 20 kHz and the maximum drops to 11 bits; 1 MHz leaves only 6 bits. An Arduino Uno's analogWrite() is 8-bit at about 490 Hz (980 Hz on pins 5 and 6).

How a DAC works

A DAC divides its reference voltage into 2N steps and outputs the one you ask for. The step size is:

Vstep = Vref / 2N

An 8-bit DAC at 3.3 V has steps of about 12.9 mV; a 12-bit DAC at the same reference, about 0.8 mV. The output is a genuine DC level that updates as fast as the DAC and your code can write new values — so it can draw a sine wave, an audio signal or a slow ramp.

The catch is availability. Many popular chips have no DAC at all:

Board / chipBuilt-in DAC
Arduino Uno (ATmega328P)None — PWM only
Arduino Uno R412-bit on A0
ESP32 (original)Two 8-bit channels on GPIO25 and GPIO26
ESP32-S3, ESP32-C3None
Raspberry Pi Pico (RP2040)None
External: MCP472512-bit over I²C, one channel

Turning PWM into a real voltage

If you need a steady voltage but only have PWM, a low-pass RC filter averages the pulses. The resistor and capacitor set a cutoff frequency:

fc = 1 / (2π · R · C)

Put the cutoff far below the PWM frequency. What remains is a small ripple riding on the average. For a simple RC with R·C much longer than one PWM period, the peak-to-peak ripple is approximately:

ΔV ≈ Vsupply · D · (1 − D) / (fPWM · R · C)
PWM pin R 10 kΩ C 1 µF ≈ steady Vavg
The simplest PWM-to-analog converter: one resistor, one capacitor.

A worked example

ESP32 at 3.3 V, PWM at 20 kHz, 50 % duty, R = 10 kΩ, C = 1 µF:

  • R·C = 10 ms, so fc ≈ 16 Hz — over a thousand times below 20 kHz.
  • Ripple ≈ 3.3 × 0.5 × 0.5 ÷ (20 000 × 0.01) ≈ 4 mV peak-to-peak. Fine for a reference or a slow control signal.
  • The price is speed: the output takes about 5·R·C ≈ 50 ms to settle after you change the duty. It cannot follow anything faster than a few hertz.

That is the fundamental trade-off of filtered PWM: less ripple means slower response. A second RC stage (or a higher PWM frequency) improves ripple without slowing things down as much. Also remember the output has the resistor's impedance — buffer it with an op-amp follower before driving any real load.

When PWM is the right choice

PWM shines whenever the load itself does the averaging:

  • LEDs. Your eye averages fast flicker. Use a few kHz or more to avoid visible flicker and camera banding.
  • DC motors. The winding inductance smooths the current. Higher frequencies (around 20 kHz) also move the whine above hearing.
  • Heaters and thermal loads. Thermal mass averages over seconds; even very slow PWM works.
  • Servos. Here the pulse width is the information: typically a 50 Hz frame with a 1–2 ms pulse. No filtering — the servo reads the pulse directly.
  • Efficiency. A switch that is fully on or fully off dissipates very little. That is why power electronics — including buck converters — are built on PWM.

When you really need a DAC

  • Waveforms and audio — anything that changes quickly and must be smooth.
  • Reference or bias voltages for analog circuits, where ripple would show up as noise or error.
  • Driving sensitive analog inputs such as a comparator threshold or an op-amp setpoint.
  • Fast settling — a DAC moves to a new level in microseconds, not tens of milliseconds.

ESP32 code for both

With the ESP32 Arduino core 3.x:

const int LED_PIN = 18;   // any output-capable GPIO
                const int DAC_PIN = 25;   // DAC1 on the original ESP32 (GPIO25 or GPIO26)

                void setup() {
                  // PWM: 5 kHz, 13-bit resolution (0 … 8191)
                  ledcAttach(LED_PIN, 5000, 13);
                }

                void loop() {
                  // PWM: sweep LED brightness
                  for (int duty = 0; duty <= 8191; duty += 64) {
                    ledcWrite(LED_PIN, duty);
                    delay(5);
                  }

                  // DAC: 8-bit value 0 … 255  →  0 … ~3.3 V
                  dacWrite(DAC_PIN, 128);   // ≈ 1.65 V, steady
                  delay(500);
                }
Older core? In ESP32 Arduino core 2.x the PWM calls were ledcSetup(channel, freq, bits), ledcAttachPin(pin, channel) and ledcWrite(channel, duty). Check which version your board package uses.

Side by side

PWMDAC
OutputSquare wave; average = D × VSteady voltage level
AvailabilityAlmost every MCU, many pinsFew MCUs, 1–2 pins, or external chip
ResolutionTrades against frequencyFixed (8, 10, 12 bits…)
SpeedSlow if filteredFast (µs settling)
Drive powerCan switch a MOSFET for ampsWeak; needs a buffer
Best forLEDs, motors, heaters, servos, powerAudio, waveforms, references

Quick decision checklist

  1. Does the load average on its own (light, motor, heat)? → PWM.
  2. Does the signal need to change faster than a few hertz and stay smooth? → DAC.
  3. Need a slow, steady voltage and have no DAC? → PWM + RC filter, buffered.
  4. Need clean 12-bit levels on a chip without a DAC? → external DAC such as the MCP4725.
← All articles