Pulse Width Modulation (PWM) is a technique that controls the average power delivered to a load by rapidly switching a digital signal on and off at a fixed frequency while varying the ratio of on-time to off-time. In a real circuit, PWM changes the average current and thermal or kinetic energy delivered to inductive or resistive loads—like dimming an LED or slowing a DC motor—without requiring a variable voltage power supply. The most common mistake makers and junior engineers make is confusing PWM with a Digital-to-Analog Converter (DAC); a PWM pin does not output a steady intermediate voltage, it outputs a square wave that toggles strictly between 0V and the logic high voltage (e.g., 5V or 3.3V).

The Core Specs: Resolution, Frequency, and Voltage

Before writing a single line of code, you must understand the hardware limitations of your microcontroller's PWM peripherals. Resolution dictates how finely you can slice the duty cycle, while frequency determines how fast the switching occurs. If you push the resolution too high on certain chips, the maximum achievable frequency plummets.

Table 1: PWM Peripheral Specifications Across Common Microcontrollers
Microcontroller Logic Voltage Max Resolution Default Frequency Max Frequency (at practical res)
Arduino Uno (ATmega328P) 5.0V 8-bit / 10-bit 490 Hz / 980 Hz ~62.5 kHz (8-bit)
ESP32 (LEDC Peripheral) 3.3V Up to 20-bit 5,000 Hz 78 kHz (at 10-bit res)
Raspberry Pi Pico (RP2040) 3.3V 16-bit 125 Hz (default slice) ~125 MHz (at 1-bit res)
STM32F103C8T6 (Blue Pill) 3.3V 16-bit 1,000 Hz ~36 MHz (at 1-bit res)
ESP32 Resolution Trap: According to the Espressif LEDC API documentation, if you configure an ESP32 timer for 20-bit resolution, your maximum frequency drops to roughly 76 Hz. If you are driving a DC motor and need at least 16 kHz to avoid audible whine, you must restrict your resolution to 10-bit or 11-bit.

Worked Example: Generating 20kHz PWM on an Arduino Uno

The standard Arduino analogWrite() function defaults to roughly 490 Hz on most pins. If you use this to drive a DC motor via a MOSFET, the motor windings will vibrate at 490 Hz, producing an annoying, high-pitched audible whine. To fix this, we bypass the standard function and configure Timer1 directly to output a 20 kHz signal (ultrasonic and silent to human ears) on pins 9 and 10.

The ATmega328P runs on a 16 MHz system clock. To find our timer limit (the TOP value), we use the formula:

TOP = (Clock_Speed / (Prescaler * Target_Frequency)) - 1

  • Clock Speed: 16,000,000 Hz
  • Prescaler: 1 (no division)
  • Target Frequency: 20,000 Hz

TOP = (16,000,000 / (1 * 20,000)) - 1 = 800 - 1 = 799

We load this value into the ICR1 register for Phase and Frequency Correct PWM mode. For a 50% duty cycle, we set the Output Compare Register (OCR1A) to half of 799, which is 399.

// Arduino Uno Timer1 Setup for 20kHz PWM on Pin 9
void setup() {
  pinMode(9, OUTPUT);
  
  // Clear Timer1 control registers
  TCCR1A = 0;
  TCCR1B = 0;
  
  // Set TOP value for 20kHz frequency
  ICR1 = 799;
  
  // Set 50% duty cycle on Pin 9 (OCR1A)
  OCR1A = 399;
  
  // Configure Phase and Frequency Correct PWM, no prescaler
  TCCR1A |= (1 << COM1A1) | (1 << WGM11);
  TCCR1B |= (1 << WGM13) | (1 << CS10);
}

void loop() {
  // Motor runs silently at 50% power
}

Where You Meet PWM in Practice

PWM is the backbone of modern embedded power control. You will encounter it in three primary scenarios on the workbench:

  1. LED Dimming and Persistence of Vision: Human eyes fuse light pulses occurring faster than ~60 Hz into a continuous glow. By running PWM at 1 kHz to 5 kHz, you can dim an LED from 1% to 100% brightness without the color temperature shifting, which often happens if you try to lower the actual analog voltage.
  2. DC Motor Speed Control: Motors are highly inductive loads. The inductance of the windings naturally resists rapid changes in current, effectively acting as a low-pass filter. A 16 kHz to 20 kHz PWM signal drives the motor smoothly. If you drop below 1 kHz, the motor will cog, vibrate, and waste energy as acoustic noise rather than rotational kinetic energy.
  3. Hobby Servo Positioning: Unlike variable-speed motors, standard RC servos do not use duty cycle to determine speed. They use a very specific 50 Hz PWM signal (a 20 ms period) where the absolute width of the high pulse (typically between 1.0 ms and 2.0 ms) dictates the exact angular position of the output shaft.

Common Pitfalls and Troubleshooting

The 'Multimeter Lies' Problem

If you output a 50% duty cycle PWM signal from a 5V Arduino pin and measure it with a standard digital multimeter set to DC Volts, the screen will likely read ~2.5V. Do not be fooled. The multimeter's internal sampling circuit is averaging the square wave. If you connect that pin directly to a sensitive analog IC rated for a maximum of 3.0V, the 5V peaks of the PWM wave will instantly destroy the input stage. To convert a PWM square wave into a true, steady DC voltage, you must build an RC low-pass filter.

RC Filter Sizing: To smooth a 490 Hz Arduino PWM signal into DC, use a 10kΩ resistor in series and a 1μF capacitor to ground. This yields a cutoff frequency ($f_c$) of roughly 15.9 Hz, effectively blocking the 490 Hz carrier and leaving only the DC average. Be aware that this introduces a slow step-response time; the voltage will take roughly 30-50 ms to settle when you change the duty cycle.

MOSFET Switching Losses at High Frequencies

When using PWM to switch a high-current load via a MOSFET, pushing the frequency too high can cause catastrophic thermal failure. Every time the MOSFET transitions from off to on, it passes through a linear region where both voltage across the drain-source and current through the channel are high simultaneously. This generates heat. At 1 kHz, these switching losses are negligible. At 100 kHz, the MOSFET might spend more time in the transition phase than fully on, causing it to overheat and fail even if the steady-state current is well within the datasheet limits. Always use a dedicated gate driver IC (like the TC4427) to charge and discharge the MOSFET gate capacitance in nanoseconds when operating above 20 kHz.