The Reality of Arduino analogWrite: PWM vs. True DAC
The analogWrite() function is arguably the most misunderstood command in the entire maker ecosystem. When beginners first use arduino analogwrite to dim an LED or control a motor, they assume the microcontroller is outputting a variable DC voltage—say, 2.5V instead of 5V. In reality, on classic 8-bit boards like the Arduino Uno R3 or Nano, the chip is physically incapable of outputting true analog voltages. Instead, it generates a Pulse Width Modulation (PWM) square wave, rapidly switching between 0V and 5V.
Understanding the distinction between PWM and a true Digital-to-Analog Converter (DAC) is the dividing line between a novice hobbyist and an embedded systems engineer. If you attempt to drive an analog sensor reference or an audio circuit directly with a PWM pin, you will introduce massive high-frequency noise into your system. This guide will dissect the hardware timers behind the function, teach you how to build an active RC low-pass filter to extract true DC voltage, and explore the true DAC capabilities of modern 32-bit architectures.
Hardware Timer Mapping and Default Frequencies
On the ATmega328P (the chip powering the classic Uno R3 and Nano), PWM generation is handled by three internal 8-bit hardware timers. When you call analogWrite(pin, value), you are instructing a specific timer to toggle a pin based on a duty cycle mapped from 0 to 255.
According to the official Arduino reference documentation, the default PWM frequency is roughly 490 Hz. However, this is not uniform across all pins. Pins 5 and 6 operate at roughly 980 Hz. Why? Because they share Timer0, which is configured differently to support the millis() and delay() timing functions.
| ATmega328P Pin | Hardware Timer | Default PWM Frequency | Shared System Functions |
|---|---|---|---|
| 5, 6 | Timer0 | ~976.56 Hz | millis(), delay(), micros() |
| 9, 10 | Timer1 | ~490.20 Hz | Servo library, Tone library |
| 3, 11 | Timer2 | ~490.20 Hz | Tone library |
The Golden Rule of Timers: Never manually alter the prescaler registers (likeTCCR0B) on Timer0 unless you fully understand the consequences. Changing the frequency on Pins 5 and 6 will instantly break thedelay()function, causing your entire sketch's timing logic to collapse.
Step-by-Step: Converting PWM to True Analog Voltage
If your project requires a genuine, steady DC voltage—for example, generating a programmable reference voltage for a comparator or driving an analog synthesizer control voltage (CV) input—you must filter the PWM square wave. A simple passive RC (Resistor-Capacitor) low-pass filter will smooth the 490 Hz square wave into a DC level, but it comes with severe limitations that require an active buffer.
Calculating the Passive RC Filter
The cutoff frequency ($f_c$) of an RC filter must be set significantly lower than the PWM frequency to effectively attenuate the AC ripple. A good rule of thumb is to set $f_c$ at least 10 times lower than the PWM frequency. For a 490 Hz PWM signal, we want an $f_c$ of roughly 48 Hz.
The formula is: $f_c = \frac{1}{2 \pi R C}$
- Resistor (R): 10,000 Ω (10kΩ)
- Capacitor (C): 330 nF (0.33 µF)
- Resulting $f_c$: ~48.2 Hz
Wire the 10kΩ resistor in series with the PWM output pin, and connect the 330nF capacitor from the other side of the resistor to ground. The junction between the resistor and capacitor is your filtered analog output.
The Output Impedance Problem (And the Op-Amp Solution)
Here is where most tutorials fail. A passive RC filter with a 10kΩ resistor has a high output impedance. If you connect this filtered output to a load with low resistance (e.g., a 1kΩ potentiometer or a motor driver input), the load and the 10kΩ resistor form a voltage divider. Your expected 2.5V output will instantly sag to 0.22V.
The Fix: You must use a unity-gain operational amplifier buffer. By feeding the RC filter output into the non-inverting input of an op-amp like the MCP6001 or LM358 (wired with the output tied directly to the inverting input), you achieve a high-impedance input that won't load the filter, and a low-impedance output capable of sourcing up to 20-30mA without voltage droop. This is standard practice in professional mixed-signal PCB design.
Modern MCU Architecture: The Arduino R4 True DAC
The landscape of microcontrollers has shifted dramatically. If you are using the Arduino Uno R4 Minima or R4 WiFi (priced around $17.50 to $22.00 in 2026), you are working with the Renesas RA4M1 32-bit ARM Cortex-M4 processor. This chip features a genuine, hardware-level 12-bit Digital-to-Analog Converter on pin A0.
When you call analogWrite(A0, value) on the R4, the microcontroller does not output a PWM wave. It uses an internal resistor ladder network to output a true, steady DC voltage. Furthermore, the resolution jumps from 8-bit (0-255) to 12-bit (0-4095), giving you 4,096 discrete voltage steps between 0V and the reference voltage (typically 5V or 3.3V depending on board jumper settings).
For audio generation or precision lab equipment, the R4's true DAC eliminates the need for external RC filters and op-amp buffers entirely, provided your load impedance remains above 5kΩ. For deeper insights into 32-bit PWM and DAC differences, SparkFun's comprehensive guide on PWM provides excellent oscilloscope captures comparing the two waveforms.
Troubleshooting Common analogWrite Failures
When your analogWrite() implementation behaves erratically, it is almost always due to one of the following hardware-level edge cases:
1. Motor Whine and Audible Noise
The default 490 Hz PWM frequency falls squarely in the middle of the human hearing range (20 Hz to 20 kHz). When driving DC motors or ceramic capacitors, this frequency causes physical vibration, resulting in an annoying high-pitched whine. Solution: Move your motor control to Timer1 (Pins 9 or 10) and manually adjust the prescaler to push the PWM frequency above 20 kHz (e.g., 31.25 kHz), rendering it ultrasonic and silent to the human ear.
2. Flickering LEDs at Low Duty Cycles
Human eyes perceive light logarithmically, not linearly. An analogWrite(pin, 10) does not look 10 times dimmer than analogWrite(pin, 100); it looks almost completely off. If your LED fading sketch appears to 'jump' from off to bright, you are fighting human biology. Solution: Implement a gamma correction lookup table in your code to map linear 0-255 values to logarithmic PWM outputs.
3. MOSFET Heating and Slow Switching
If you are using analogWrite() to drive a power MOSFET (like the IRF520) for a heating element or high-power LED strip, the 5V logic output of the Arduino is often insufficient to fully saturate the gate. The IRF520 requires 10V on the gate to achieve its lowest $R_{DS(on)}$. At 5V, it operates in its linear region, acting as a massive resistor and generating severe heat. Solution: Always use a logic-level MOSFET (e.g., IRLZ44N or STP16NF06L) specifically rated for $V_{GS} = 4.5V$, or use a dedicated gate driver IC.
Summary Checklist for Production Code
- Verify if your target pin actually supports hardware PWM (look for the
~symbol on the silkscreen). - Determine if your load requires true DC (use an RC filter + Op-Amp) or if it accepts PWM (LEDs, Motors, Heaters).
- Avoid Pins 5 and 6 if you need to modify timer frequencies to protect the
millis()clock. - Upgrade to an Arduino R4 or ESP32 if your project demands native 12-bit true analog output without external filtering components.
Mastering analogWrite() requires looking past the simplicity of the Arduino IDE and understanding the silicon underneath. By respecting hardware timers, managing output impedance, and choosing the right microcontroller architecture for your signal requirements, you can transition your prototypes from unreliable hobby projects into robust, production-ready embedded systems.






