Pulse Width Modulation (PWM) is a technique where a microcontroller rapidly switches a digital pin on and off to simulate a variable analog voltage by changing the ratio of on-time to off-time. In a real circuit, PWM does not actually change the peak voltage—a 5V microcontroller pin still outputs exactly 5V when high and 0V when low. Instead, it changes the average power delivered to the load over time. Beginners frequently confuse PWM with a true Digital-to-Analog Converter (DAC); while a DAC outputs a smooth, continuous voltage wave, PWM outputs a harsh, square-wave pulse train that relies on the load's physical inertia (like a motor's rotating mass or an LED's persistence of vision) to smooth out the effect.

The Math Behind the Magic: Duty Cycle and Resolution

To understand PWM on the bench, you need to master two variables: frequency (how many on/off cycles happen per second, measured in Hertz) and duty cycle (the percentage of time the signal spends in the HIGH state during one cycle).

Imagine you are using an Arduino Uno to control the speed of a 12V, 2A DC cooling fan. Because the Uno's ATmega328P chip can only output 5V on its digital pins, you use a logic-level MOSFET (like the IRLZ44N) as a switch. The Uno's default PWM frequency on pins 3, 9, 10, and 11 is approximately 490 Hz. This means one complete on/off cycle takes about 2.04 milliseconds (1 / 490).

Worked Numeric Example: 75% Fan Speed
If you want the fan to run at 75% speed, you need a 75% duty cycle. In Arduino code, the analogWrite() function uses an 8-bit resolution (0 to 255 steps).
• Target Duty Cycle: 75%
• PWM Value: 255 × 0.75 = 191
• Command: analogWrite(fanPin, 191);
• Timing: The pin outputs 5V for 1.53 ms, then 0V for 0.51 ms.
• Result: The MOSFET gate sees an average of 3.75V, switching the 12V fan on for 75% of the time, delivering 75% of its rated power.

If you attempt this same setup on an ESP32, the math changes because the ESP32 uses a dedicated hardware peripheral called LEDC (LED Control) rather than the legacy AVR timers. The ESP32's base timer clock runs at 80 MHz. When configuring PWM, you must balance frequency and resolution. If you demand a 20 kHz frequency (to avoid audible motor whine) and a 12-bit resolution (4096 steps), the math breaks: 80,000,000 / (4096 × 20,000) = 0.97. Because the result is less than 1, the ESP32 cannot achieve 12-bit resolution at 20 kHz. You must drop the resolution to 11 bits (2048 steps) or lower the frequency. This hardware limitation is a common trap for makers migrating from 8-bit Arduinos to 32-bit ESP32s.

Where You Meet PWM in Practice

PWM is the backbone of embedded power control. Because microcontrollers cannot efficiently dissipate the heat required to act as variable resistors (a linear regulator dropping 12V to 5V at 2A would waste 14W as heat), PWM allows the switching element to be either fully ON (minimal voltage drop, minimal heat) or fully OFF (zero current, zero heat).

Here is where you will encounter PWM in real-world installations and projects, along with the specific tuning parameters required for each:

Application Typical Frequency Resolution Why this frequency?
LED Dimming 1 kHz - 5 kHz 8 to 12-bit Must exceed the human flicker fusion threshold (~100 Hz) and avoid rolling shutter banding on smartphone cameras.
DC Motor Speed 20 kHz - 25 kHz 8 to 10-bit Pushed just above the upper limit of human hearing (20 kHz) to eliminate high-pitched acoustic whine from the motor windings.
RC Servo Positioning 50 Hz N/A (Pulse Width) Industry standard for hobby servos. Position is dictated by absolute pulse width (1.0ms to 2.0ms), not duty cycle percentage.
Heater / Peltier Control 1 Hz - 10 Hz 8-bit Thermal mass is incredibly slow. High frequencies waste switching energy; slow PWM allows the heating element to physically absorb the pulses.

For a deeper look at how PWM interfaces with physical loads, the Adafruit Motor Selection Guide provides excellent bench-tested data on how different PWM frequencies affect torque and thermal performance in DC motors.

Hardware Timers vs. Software Bit-Banging

A critical distinction in embedded systems is how the PWM signal is actually generated. When you call analogWrite() on an Arduino or ledcWrite() on an ESP32, you are configuring a hardware timer. You set the registers, and a dedicated silicon peripheral flips the GPIO pin high and low in the background. Your main code loop is completely free to read sensors, handle WiFi stacks, or run PID algorithms without interrupting the PWM signal.

Conversely, software bit-banging involves writing a loop that manually sets a pin HIGH, waits via delayMicroseconds(), sets it LOW, and waits again. This is almost always a mistake for power control. Software delays introduce "jitter"—microsecond-level variations in pulse width caused by interrupts (like a UART byte arriving or a timer overflow). While an LED might not show jitter, a DC motor will stutter, and a PID-controlled heater will suffer from thermal oscillation.

On the ESP32, the Arduino core maps analogWrite() to the LEDC peripheral under the hood. However, for advanced motor control (like driving a BLDC motor or a half-bridge inverter), you should bypass LEDC and use the ESP32's MCPWM (Motor Control PWM) peripheral. MCPWM includes hardware-level dead-time insertion—a mandatory safety feature that ensures both the high-side and low-side MOSFETs in an H-bridge are never turned on at the exact same time, which would cause a catastrophic shoot-through short circuit. You can review the official Espressif LEDC API documentation and the Arduino analogWrite() reference to understand the exact register limits for your specific board variant.

Frequently Asked Questions

Why does my PWM motor controller whine at 500 Hz?

That high-pitched whine is acoustic noise generated by magnetostriction and physical vibration in the motor windings and the inductor coils on your motor driver board. When the PWM frequency falls within the human hearing range (roughly 20 Hz to 20,000 Hz), the rapid magnetic expansion and contraction of the components act like a tiny speaker. To fix this, increase your PWM frequency to at least 21 kHz or 22 kHz. On an Arduino Uno, this requires modifying the Timer1 prescaler registers directly, as the default analogWrite() pins are hardcoded to 490 Hz or 980 Hz.

Can I use PWM to power a sensitive analog audio amplifier?

No. If you feed a raw PWM square wave into an analog audio amplifier, the high-frequency harmonics of the square wave will cause severe distortion, intermodulation, and potentially overheat the amplifier's input stage. Audio requires a smooth, continuous voltage. If your microcontroller lacks a true DAC (like the Arduino Uno), you must pass the PWM signal through an RC low-pass filter (a resistor and a capacitor to ground) to smooth the square wave into a DC voltage before it reaches the amplifier. Be aware that adding an RC filter severely limits how fast the voltage can change, making it useless for high-frequency audio signals.

What is the difference between PWM resolution and frequency?

Frequency is how fast the signal cycles, while resolution is how finely you can divide the on-time within that cycle. They are inversely linked by the microcontroller's clock speed. Think of it like slicing a pie: the frequency dictates how many pies you have to bake per second, and the resolution dictates how many slices you cut each pie into. If your oven (the CPU clock) has a maximum baking speed, demanding more pies per second (higher frequency) forces you to cut each pie into fewer slices (lower resolution). This is why high-speed motor control often sacrifices fine 16-bit resolution for raw 20 kHz switching speeds.