Digital pins can only be HIGH or LOW — so how does an Arduino dim an LED smoothly or control a motor speed with precision? The answer is Arduino PWM explained: Pulse Width Modulation, a technique that creates the effect of an analog voltage by rapidly switching a pin between on and off at a fixed frequency. Understanding PWM deeply unlocks precise motor control, audio generation, LED dimming, servo positioning, and analog signal synthesis — all without a single external DAC. This guide covers everything from the hardware timers behind PWM to advanced techniques like phase-correct mode and custom frequencies.
- PWM Fundamentals: Duty Cycle and Frequency
- Arduino Timers Behind PWM
- Using analogWrite(): The Basics
- PWM Pins on Popular Arduino Boards
- Changing PWM Frequency
- PWM for Servo and DC Motor Control
- LED Dimming and RGB Colour Control
- Advanced PWM Techniques
- Frequently Asked Questions
PWM Fundamentals: Duty Cycle and Frequency
PWM works by switching a digital output on and off at high speed. The percentage of time the signal is HIGH within each cycle is called the duty cycle:
- 0% duty cycle: Pin always LOW → 0 V average
- 50% duty cycle: Pin HIGH half the time → 2.5 V average (on 5V system)
- 100% duty cycle: Pin always HIGH → 5 V average
The key insight is that the load (motor, LED, speaker) responds to the average voltage, not the instantaneous value, because it can’t react fast enough to individual pulses. An LED connected to a 50% duty cycle PWM signal at 1 kHz sees 500 pulses per second each lasting 0.5 ms — far faster than the human eye’s 15–20 Hz flicker threshold, so it appears to glow at half brightness.
The frequency of PWM matters for different applications:
- LED dimming: Anything above ~50 Hz is flicker-free to the eye. Higher frequencies reduce LED driver switching losses.
- DC motors: 1–20 kHz is typical. Too low and you hear audible whining; too high and the motor driver transistors overheat from switching losses.
- Servos: 50 Hz standard (20 ms period) — this is a protocol requirement, not just a frequency.
- Audio: Needs to be above 40 kHz to push audio frequencies below 20 kHz out of the audible range.
Arduino Timers Behind PWM
PWM on Arduino is generated in hardware by timer/counter peripherals — not by software toggling pins in a loop. This is what makes Arduino PWM precise and non-blocking. The ATmega328P (Uno/Nano) has three timers:
| Timer | Resolution | PWM Pins | Default Frequency | Used by |
|---|---|---|---|---|
| Timer0 | 8-bit | D5, D6 | 976.5 Hz | millis(), delay() |
| Timer1 | 16-bit | D9, D10 | 490.2 Hz | Servo library |
| Timer2 | 8-bit | D3, D11 | 490.2 Hz | tone() |
Important: Timer0 also drives millis() and delay(). If you change Timer0’s prescaler to alter PWM frequency on D5/D6, millis() will return wrong values. Timer1 is used by the Servo library — loading Servo.h disables PWM on D9 and D10. Timer2 is used by tone() — calling tone() disables PWM on D3 and D11.
Using analogWrite(): The Basics
analogWrite(pin, value) sets the duty cycle on a PWM pin. The value ranges from 0 (always off) to 255 (always on) — this is 8-bit resolution for most Arduino boards.
int ledPin = 9; // Must be a PWM pin (~)
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
// Fade in
for (int brightness = 0; brightness <= 255; brightness++) {
analogWrite(ledPin, brightness);
delay(10);
}
// Fade out
for (int brightness = 255; brightness >= 0; brightness--) {
analogWrite(ledPin, brightness);
delay(10);
}
}
A few important rules about analogWrite():
- You don’t need to call
pinMode(pin, OUTPUT)—analogWrite()does it automatically, but it’s good practice to include it for clarity. - Calling
analogWrite()on a non-PWM pin has no effect (the pin stays at its last digital state). analogWrite(pin, 0)is identical todigitalWrite(pin, LOW).analogWrite(pin, 255)is identical todigitalWrite(pin, HIGH).- Once
analogWrite()is called, the timer runs the PWM in hardware independently — your loop() can do other work without interrupting the PWM output.
PWM Pins on Popular Arduino Boards
PWM-capable pins are marked with a tilde (~) on the board silkscreen:
| Board | PWM Pins | Resolution |
|---|---|---|
| Uno R3 | D3, D5, D6, D9, D10, D11 | 8-bit |
| Nano | D3, D5, D6, D9, D10, D11 | 8-bit |
| Mega 2560 | D2–D13, D44–D46 (15 pins) | 8-bit |
| Nano Every | D3, D5, D6, D9, D10 | 8-bit |
| Nano 33 IoT | D2–D13, A0, A1 (12 pins) | 8-bit (via analogWrite) |
Changing PWM Frequency
The default PWM frequencies (490 Hz and 976 Hz) are set by timer prescalers in the Arduino core. You can change them by directly manipulating timer control registers. Here’s how to do it for common use cases on the Uno/Nano:
// Change Timer1 (D9, D10) frequency to ~31 kHz
// For motor control to eliminate audible whine
void setup() {
// Clear prescaler bits, set mode 1 (Phase Correct PWM)
TCCR1B = TCCR1B & B11111000 | B00000001; // Prescaler = 1 → ~31.4 kHz
// Prescaler options for Timer1:
// B00000001 → 31372.55 Hz
// B00000010 → 3921.16 Hz
// B00000011 → 490.20 Hz (default)
// B00000100 → 122.55 Hz
// B00000101 → 30.64 Hz
analogWrite(9, 128); // 50% duty cycle at ~31 kHz
}
// Change Timer2 (D3, D11) — note: affects tone()
void setup() {
TCCR2B = TCCR2B & B11111000 | B00000001; // Prescaler = 1 → ~31 kHz
analogWrite(3, 200);
}
Warning: Do not change Timer0’s prescaler — it breaks millis(), delay(), micros(), and many libraries that depend on Timer0 for timing.
PWM for Servo and DC Motor Control
Servo Control
Standard RC servos use 50 Hz PWM with a pulse width between 1 ms (0°) and 2 ms (180°) within a 20 ms period. Arduino’s Servo library handles this precisely using Timer1:
#include <Servo.h>
Servo myServo;
void setup() {
myServo.attach(9); // Attach to pin 9 (Timer1)
}
void loop() {
myServo.write(0); // Go to 0°
delay(1000);
myServo.write(90); // Go to 90°
delay(1000);
myServo.write(180); // Go to 180°
delay(1000);
}
DC Motor Speed Control
For DC motors, use a motor driver IC (L298N, DRV8833, TB6612FNG) — never connect a motor directly to Arduino pins. PWM on the motor driver’s speed input sets motor speed:
// L298N motor driver
#define ENA 9 // PWM speed pin
#define IN1 7 // Direction
#define IN2 8 // Direction
void setup() {
pinMode(ENA, OUTPUT);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
// Set direction: forward
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
}
void loop() {
// Ramp up speed
for (int speed = 0; speed <= 255; speed += 5) {
analogWrite(ENA, speed);
delay(50);
}
delay(2000);
// Ramp down
for (int speed = 255; speed >= 0; speed -= 5) {
analogWrite(ENA, speed);
delay(50);
}
delay(1000);
}
LED Dimming and RGB Colour Control
LED brightness isn’t linearly proportional to PWM duty cycle — human perception of brightness is logarithmic. A PWM value of 128 (50%) doesn’t look half as bright as 255; it looks about 70% as bright. For perceptually smooth dimming, use a gamma correction curve:
// Gamma correction table for 8-bit PWM
// Maps linear 0-255 to perceptual 0-255
const uint8_t PROGMEM gamma8[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
void setup() {
pinMode(9, OUTPUT);
}
void loop() {
for (int i = 0; i <= 255; i++) {
// Apply gamma correction from PROGMEM table
analogWrite(9, pgm_read_byte(&gamma8[i]));
delay(8);
}
}
For RGB LED control, simply apply analogWrite() independently to each colour channel:
#define RED_PIN 9
#define GREEN_PIN 10
#define BLUE_PIN 11
void setColour(uint8_t r, uint8_t g, uint8_t b) {
analogWrite(RED_PIN, r);
analogWrite(GREEN_PIN, g);
analogWrite(BLUE_PIN, b);
}
void loop() {
setColour(255, 0, 0); // Red
delay(1000);
setColour(0, 255, 0); // Green
delay(1000);
setColour(0, 0, 255); // Blue
delay(1000);
setColour(255, 165, 0); // Orange
delay(1000);
}
Advanced PWM Techniques
Software PWM on Any Pin
Libraries like SoftPWM or TimerOne enable PWM on non-hardware-PWM pins using timer interrupts. This is slower and less precise than hardware PWM but enables more channels when you need more than 6 PWM outputs on an Uno.
High-Resolution PWM with Timer1
Timer1 is a 16-bit timer. By using ICR1 as the TOP value instead of 0xFF, you can set custom frequencies with high resolution. For example, 10-bit PWM at ~15 kHz:
// 10-bit Phase Correct PWM on D9 at ~15.6 kHz
void setup() {
pinMode(9, OUTPUT);
// Phase correct PWM, ICR1 as top
TCCR1A = _BV(COM1A1) | _BV(WGM11);
TCCR1B = _BV(WGM13) | _BV(CS10); // No prescaler
ICR1 = 1023; // 10-bit: 0–1023
OCR1A = 512; // 50% duty cycle
}
void loop() {}
Phase-Correct vs Fast PWM
Arduino defaults to fast PWM (except D5 and D6 which use phase-correct). Fast PWM counts 0→255→0→255 (sawtooth). Phase-correct PWM counts 0→255→255→0 (triangle). Phase-correct has exactly half the frequency but produces less EMI and is preferred for motor control. Choose based on your application’s frequency requirements.
Frequently Asked Questions
Why does analogWrite() produce a frequency of 490 Hz and not something round like 500 Hz?
The PWM frequency is derived from the 16 MHz system clock divided by a prescaler and counter TOP value. With an 8-bit counter (TOP=255) and a prescaler of 64 (Timer1 default): 16,000,000 / 64 / 256 = 976.5625 Hz for fast PWM and 490.2 Hz for phase-correct PWM. These aren’t round numbers because 16 MHz doesn’t divide evenly into clean audio or motor control frequencies.
Can analogWrite() break millis() or delay()?
No — as long as you don’t change Timer0’s prescaler. analogWrite() on D5 or D6 uses Timer0 but doesn’t change its configuration. The PWM simply shares the timer with millis(). Only direct manipulation of TCCR0B (Timer0 control register) breaks millis().
What’s the difference between analogWrite() and a DAC?
A true DAC (Digital-to-Analog Converter) produces a steady analog voltage. analogWrite() produces a square wave that averages to the target voltage. For loads that respond only to average power (motors, LEDs, heaters), this difference is unimportant. For audio or precision voltage references, you need a real DAC (like the MCP4725 I2C DAC) or to filter the PWM output through an RC low-pass filter.
Why does my PWM LED flicker?
Either the PWM frequency is below ~50 Hz (unlikely at default 490 Hz) or your camera/phone is picking up frequency beating between the PWM and the camera’s frame rate. 490 Hz PWM is perfectly flicker-free to human eyes. If you’re filming, increase PWM frequency to 25 kHz to stay above camera refresh artefacts.
Can I run more than 6 PWM channels on an Arduino Uno?
Not with hardware PWM — the Uno has only 6 PWM pins. Options: use a PCA9685 I2C PWM driver (16 channels from 2 pins), use the Arduino Mega (15 PWM pins), or implement software PWM with a timer interrupt library for additional channels.
Explore our full range of Arduino boards and accessories at Zbotic’s Arduino & Microcontrollers collection — from the classic Uno to the Mega 2560, plus motor drivers, LED strips, and servo accessories for all your PWM projects.
Add comment