To convert a microcontroller's digital PWM output into a stable, low-ripple analog DC voltage, use a two-stage RC low-pass filter buffered by a unity-gain rail-to-rail op-amp. For a standard 5kHz PWM signal from an ESP32 or Arduino, use 10kΩ resistors and 1µF ceramic capacitors for the filter stages, paired with an MCP6001 op-amp. This topology yields a cutoff frequency of 15.9Hz and reduces 5kHz switching ripple to under 10mV, providing a clean 0-3.3V analog signal suitable for driving external DACs, motor controllers, or analog sensors.
Topology and Node Map of the Active PWM Modulation Circuit
A basic single-stage RC filter (one resistor, one capacitor) is rarely sufficient for embedded ESP32 LEDC PWM outputs. A single stage requires either massive capacitor values (introducing slow settling times) or it leaves unacceptable AC ripple on the DC line. Furthermore, if you connect a load directly to a passive RC filter, the load's impedance acts in parallel with your capacitor, shifting your cutoff frequency and ruining the filter's effectiveness.
The solution is a cascaded two-stage RC filter followed by an active op-amp buffer. This gives us a second-order (-40dB/decade) roll-off and isolates the filter from the load.
- V_PWM: The raw digital PWM signal from the MCU GPIO pin (e.g., ESP32 GPIO 25).
- N1: The junction between the first resistor (R1) and first capacitor (C1). C1 ties to GND.
- V_FILT: The junction between the second resistor (R2) and second capacitor (C2). C2 ties to GND. This node holds the smoothed but high-impedance DC voltage.
- V_OUT: The output of the op-amp (wired as a unity-gain voltage follower). The non-inverting input (+) connects to V_FILT, and the output ties directly to the inverting input (-).
Why this topology over a Sallen-Key active filter? While a Sallen-Key topology is excellent for strict audio crossover networks, it can be prone to peaking and ringing if the op-amp's gain-bandwidth product is marginal or if component tolerances drift. The cascaded RC with a simple buffer is unconditionally stable, easy to tune on a breadboard, and entirely forgiving of 5% resistor and 10% capacitor tolerances.
Component Selection Matrix and Ripple Data
Selecting the right R and C values depends entirely on your PWM base frequency. The cutoff frequency ($f_c$) for each identical RC stage is calculated as $f_c = 1 / (2 π R C)$. Because the stages are buffered or heavily staggered, we approximate the overall attenuation at the PWM frequency using the second-order roll-off formula. Below is a design matrix using standard E12/E24 component values for common microcontroller PWM frequencies.
| PWM Freq (kHz) | R1, R2 (Ω) | C1, C2 (µF) | Stage Cutoff (Hz) | Settling Time (99%) | Expected Ripple (mV) |
|---|---|---|---|---|---|
| 1.0 | 10,000 | 4.7 | 3.38 | ~1.4 seconds | ~12 mV |
| 5.0 | 10,000 | 1.0 | 15.9 | ~290 ms | ~8 mV |
| 20.0 | 4,700 | 0.1 | 338 | ~14 ms | ~4 mV |
| 40.0 | 2,200 | 0.047 | 1,539 | ~3 ms | ~2 mV |
Design Walkthrough: Picking Real Values for 5kHz
If you are using the ESP32's default LEDC timer configuration, 5kHz is a standard baseline. Looking at the 5kHz row, we select 10kΩ resistors and 1µF capacitors. We use X7R ceramic capacitors for C1 and C2 because they offer low equivalent series resistance (ESR) and do not suffer from the microphonic effects of piezo buzzers often found in cheap breadboard kits. The 10kΩ resistors draw only 330µA from the ESP32 GPIO pin when outputting a 100% duty cycle (3.3V), keeping us well below the 40mA absolute maximum per pin limit.
Failure Modes: What Breaks at the Extremes?
When debugging embedded hardware, knowing how a circuit fails is just as important as knowing how it works. Breadboards are notorious for intermittent contacts. Here is the behavior matrix for single-point failures in this topology.
| Component | Failure State | Resulting Behavior at V_OUT | Diagnostic Measurement |
|---|---|---|---|
| R1 | Open | V_OUT drops to 0V. C1 and C2 discharge through op-amp input bias current. | Measure V_PWM (should be toggling) vs N1 (will read 0V DC). |
| C1 | Short | V_OUT drops to 0V. MCU GPIO pin is shorted to GND through R1; MCU may brownout or pin may overheat. | Measure resistance across C1 pads with power off; reads <1Ω. |
| C2 | Open | Circuit becomes a 1st-order filter. V_OUT shows massive AC ripple (sawtooth wave) equal to the PWM frequency. | Scope V_OUT; observe >500mV peak-to-peak ripple instead of clean DC. |
| Op-Amp Feedback | Open (Pin 6 to Pin 4 broken) | Op-amp operates in open-loop. Output saturates to the positive rail (VCC) due to microvolt input offset voltage. | V_OUT reads exactly 3.3V (or VCC) regardless of PWM duty cycle. |
Step-by-Step Breadboard Validation Protocol
Do not trust your firmware until you have validated the analog hardware. Follow this sequence to verify your PWM modulation circuit on the bench.
- Verify Power Rails First: Before inserting the op-amp, use a multimeter to confirm your breadboard's VCC rail is exactly 3.3V (nominal 3.25V to 3.35V) and GND is continuous. Check for <1Ω resistance between the op-amp's GND pin socket and the MCU GND pin.
- Test the Raw PWM: Upload a simple firmware sketch outputting a 50% duty cycle at 5kHz. Probe the
V_PWMnode with an oscilloscope. You should see a clean 3.3V square wave. If the edges are heavily rounded (RC time constant > 5µs), your breadboard parasitic capacitance is too high; move the components closer to the MCU. - Check the Passive Filter Nodes: Probe
N1andV_FILT. With a 50% duty cycle,V_FILTshould read approximately 1.65V DC on your multimeter. Switch your scope to AC coupling onV_FILT; the ripple should be a tiny sawtooth wave measuring less than 20mV peak-to-peak. - Seat the Op-Amp and Verify Buffer: Insert the MCP6001. Ensure the notch aligns with Pin 1. Probe
V_OUTwith your multimeter. It must read identically toV_FILT(±10mV offset). IfV_OUTis pegged at 3.3V or 0V, your feedback loop (Pin 6 to Pin 4) is open. - Load Testing: Connect a 1kΩ load resistor from
V_OUTto GND. This draws 3.3mA. The voltage atV_OUTshould not drop by more than 15mV. If it drops significantly, your op-amp lacks the output drive current or is not wired as a proper voltage follower. - Sweep the Duty Cycle: Update your firmware to step the duty cycle from 0% to 100% in 10% increments. Log the
V_OUTmultimeter readings. The response should be highly linear (e.g., 0.00V, 0.33V, 0.66V... 3.30V). Non-linearity at the top end indicates op-amp saturation; non-linearity at the bottom end indicates the MCU GPIO isn't pulling fully to 0V.
For deeper theoretical background on converting digital pulses to analog domains, the All About Circuits guide on PWM DAC filtering provides excellent oscilloscope captures of ripple reduction across different filter orders. When designing the physical layout for a final PCB, keep the C1 and C2 ground returns as close to the MCU ground pin as possible to prevent high-frequency PWM switching currents from modulating your analog ground plane.






