Pulse Width Modulation (PWM) is one of the most powerful techniques in the Arduino programmer’s toolbox. Whether you want to dim an LED smoothly, control the speed of a DC motor, or generate analog-like output from a digital pin, PWM is your go-to solution. In this comprehensive tutorial, you will learn everything about Arduino PWM — from the basics of analogWrite() to tweaking timer frequencies and building real projects.
Table of Contents
- What Is PWM and How Does It Work?
- Arduino PWM Pins and Timer Mapping
- Using the analogWrite() Function
- Understanding Duty Cycle
- Changing PWM Frequency on Arduino
- Practical PWM Projects
- Troubleshooting Common PWM Issues
- Frequently Asked Questions
What Is PWM and How Does It Work?
PWM stands for Pulse Width Modulation. It is a technique where a digital signal rapidly switches between HIGH (5V) and LOW (0V) at a fixed frequency. By varying the proportion of time the signal stays HIGH versus LOW in each cycle, you can simulate an intermediate voltage level.
Think of it like a light switch that you flick on and off very fast. If it is on 50% of the time and off 50% of the time, your eye perceives it as a light glowing at half brightness. This is exactly how LED dimming works with PWM.
The key parameters of a PWM signal are:
- Frequency: How many on/off cycles happen per second (measured in Hz). Arduino Uno defaults to about 490 Hz or 980 Hz depending on the pin.
- Duty Cycle: The percentage of one cycle during which the signal is HIGH. 0% means always OFF, 100% means always ON, and 50% means equal time on and off.
- Period: The time for one complete on/off cycle (1 / frequency).
PWM is used in motor speed control, servo positioning, audio generation, power regulation, and countless other applications in embedded systems.
Arduino PWM Pins and Timer Mapping
Not every digital pin on an Arduino supports PWM. On the Arduino Uno and Nano, only 6 pins are PWM-capable, and they are marked with a tilde (~) on the board silkscreen: pins 3, 5, 6, 9, 10, and 11.
Each PWM pin is driven by one of the Arduino’s internal hardware timers:
| Pin | Timer | Default Frequency |
|---|---|---|
| 3 | Timer2 (OC2B) | 490 Hz |
| 5 | Timer0 (OC0B) | 980 Hz |
| 6 | Timer0 (OC0A) | 980 Hz |
| 9 | Timer1 (OC1A) | 490 Hz |
| 10 | Timer1 (OC1B) | 490 Hz |
| 11 | Timer2 (OC2A) | 490 Hz |
Important note: Timer0 is also used by Arduino’s millis(), micros(), and delay() functions. If you modify Timer0’s frequency to change PWM on pins 5 or 6, these time-keeping functions will return incorrect values. Be careful when changing Timer0 prescaler settings.
The Arduino Mega 2560 has many more PWM pins — 15 in total — spread across pins 2–13 and 44–46, giving you much more flexibility for complex projects.
Using the analogWrite() Function
The simplest way to output a PWM signal in Arduino is through the built-in analogWrite() function. Despite the name, it does not produce a true analog voltage — it produces a PWM signal whose average voltage simulates an analog level.
Syntax:
analogWrite(pin, value);
pin— A PWM-capable pin number (3, 5, 6, 9, 10, or 11 on Uno)value— An integer from 0 to 255, where 0 = 0% duty cycle (always LOW) and 255 = 100% duty cycle (always HIGH)
LED Fade Example:
int ledPin = 9; // PWM pin
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
// Fade in
for (int brightness = 0; brightness = 0; brightness--) {
analogWrite(ledPin, brightness);
delay(10);
}
}
This code smoothly fades an LED connected to pin 9 (through a 220-ohm resistor) in and out. Each step changes the duty cycle by roughly 0.4%, giving a very smooth visual effect.
You can also use analogWrite() to control DC motor speed via an L298N or similar motor driver module by connecting the motor driver’s enable pin to a PWM pin on the Arduino.
Understanding Duty Cycle
The duty cycle is the percentage of time the PWM signal remains HIGH within a single period. It directly determines how much power is delivered to the load:
- 0% duty cycle (value = 0): Pin stays LOW. Load receives 0V average.
- 25% duty cycle (value ≈ 64): Pin is HIGH 25% of the time. Average ≈ 1.25V.
- 50% duty cycle (value = 127): Pin is HIGH half the time. Average ≈ 2.5V.
- 75% duty cycle (value ≈ 191): Pin is HIGH 75% of the time. Average ≈ 3.75V.
- 100% duty cycle (value = 255): Pin stays HIGH. Load receives 5V average.
The formula to convert the analogWrite value to duty cycle percentage is:
Duty Cycle (%) = (value / 255) * 100
And to get the average output voltage:
Average Voltage = (value / 255) * VCC
Where VCC is 5V for the Uno or 3.3V for boards like the Arduino Nano 33 IoT.
Important: Inductive loads like motors have natural low-pass filtering. The motor’s inductance averages the PWM pulses into a smooth current flow. Resistive loads like LEDs also respond to the average power, which is why LED brightness changes smoothly with duty cycle.
Changing PWM Frequency on Arduino
The default PWM frequency of 490 Hz or 980 Hz works fine for most applications. However, there are cases where you need different frequencies:
- Audio generation: You may need frequencies in the audible range (20 Hz – 20 kHz).
- Motor control: Some motor drivers prefer higher frequencies (above 20 kHz) to eliminate audible whine.
- Switch-mode power supplies: Require specific frequencies for efficiency.
You can change PWM frequency by directly modifying the Timer Control Registers (TCCR) in the ATmega328P. This is done by changing the timer’s prescaler value:
// Change Timer2 frequency for pins 3 and 11
// Prescaler settings for Timer2 (OC2A/OC2B):
// 0x01 = no prescaling → ~31,372 Hz
// 0x02 = /8 → ~3,921 Hz
// 0x03 = /32 → ~980 Hz
// 0x04 = /64 → ~490 Hz (default)
// 0x05 = /128 → ~245 Hz
// 0x06 = /256 → ~122 Hz
// 0x07 = /1024 → ~30 Hz
void setup() {
// Set Timer2 prescaler to 1 (no prescaling) for high frequency
TCCR2B = (TCCR2B & 0b11111000) | 0x01;
// Now pins 3 and 11 output ~31,372 Hz PWM
analogWrite(3, 128); // 50% duty cycle at 31 kHz
}
For Timer1 (pins 9 and 10), you can use a similar approach but Timer1 also supports 16-bit operation, giving you much finer frequency control:
// Set Timer1 for a specific frequency using ICR1
void setPWMFrequency(int pin, long frequency) {
// Only works for Timer1 pins (9, 10)
long period = F_CPU / frequency; // Clock cycles per period
ICR1 = period - 1; // Set TOP value
OCR1A = period / 2; // 50% duty cycle on pin 9
// Fast PWM mode, ICR1 as TOP, prescaler = 1
TCCR1A = _BV(COM1A1) | _BV(WGM11);
TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);
}
Practical PWM Projects
Project 1: RGB LED Color Mixing
By connecting the R, G, and B pins of an RGB LED (each through a 220-ohm resistor) to pins 9, 10, and 11, you can mix any color using three analogWrite() calls:
int redPin = 9;
int greenPin = 10;
int bluePin = 11;
void setup() {
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
}
void setColor(int r, int g, int b) {
analogWrite(redPin, r);
analogWrite(greenPin, g);
analogWrite(bluePin, b);
}
void loop() {
setColor(255, 0, 0); // Red
delay(1000);
setColor(0, 255, 0); // Green
delay(1000);
setColor(0, 0, 255); // Blue
delay(1000);
setColor(255, 255, 0); // Yellow
delay(1000);
setColor(128, 0, 128); // Purple
delay(1000);
}
Project 2: DC Motor Speed Control
Connect a small DC motor to an L298N motor driver module. Connect the driver’s ENA pin to Arduino pin 9. Now you can control motor speed:
int enablePin = 9; // PWM speed control
int in1Pin = 7; // Direction control
int in2Pin = 8; // Direction control
void setup() {
pinMode(enablePin, OUTPUT);
pinMode(in1Pin, OUTPUT);
pinMode(in2Pin, OUTPUT);
// Set direction: forward
digitalWrite(in1Pin, HIGH);
digitalWrite(in2Pin, LOW);
}
void loop() {
// Ramp up speed
for (int speed = 0; speed = 0; speed -= 5) {
analogWrite(enablePin, speed);
delay(50);
}
delay(1000);
}
Project 3: Reading Potentiometer to Control PWM
This is a classic beginner project — read an analog value from a potentiometer and map it to a PWM brightness level:
int potPin = A0;
int ledPin = 9;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
int potValue = analogRead(potPin); // 0–1023
int brightness = potValue / 4; // Map to 0–255
analogWrite(ledPin, brightness);
}
Troubleshooting Common PWM Issues
1. LED doesn’t dim smoothly near 0: Human eyes are not linear. PWM value 10 looks much brighter than 0, but the jump from 50 to 60 is barely visible. Use a gamma correction table or map the potentiometer value through a square or cube function for a perceptually linear response.
2. Motor vibrates or makes noise at low duty cycles: This is normal for DC motors at low PWM frequencies. Increase the PWM frequency (above 20 kHz using Timer2 or Timer1 adjustments) to push the switching noise above audible range.
3. millis() and delay() are wrong after Timer0 change: Timer0 drives the Arduino’s time functions. Changing Timer0 prescaler will make delay(1000) not actually wait 1 second. Use Timer1 or Timer2 for frequency changes to avoid breaking timing functions.
4. PWM pin stays at constant voltage: Make sure you are calling analogWrite() inside loop() or at least once after setup(). Also confirm you are using a real PWM pin (marked with ~).
5. Servo jitters when using analogWrite() on Timer1: Standard servo libraries use Timer1. If you change Timer1 for PWM, servo control will break. Use the Servo library on pins 9 or 10 only if you are NOT manually adjusting Timer1.
Frequently Asked Questions
What is the difference between analogWrite() and true analog output on Arduino?
analogWrite() produces a PWM (digital) signal, not a true analog voltage. The pin rapidly switches between 0V and 5V. For a true analog output, you need an external DAC (Digital to Analog Converter) chip or use a low-pass RC filter after the PWM pin to smooth the signal into a true DC voltage level.
Can I use all 6 PWM pins simultaneously on Arduino Uno?
Yes, you can call analogWrite() on all 6 PWM pins at the same time. Each pin has its own timer compare register (OCR), so they operate independently. The only shared resource is the timer clock, which means pins on the same timer (like 5 and 6 on Timer0) share the same frequency but can have different duty cycles.
What PWM frequency should I use for motor speed control?
For DC motors, anything from 490 Hz to 20 kHz works electrically. Higher frequencies (15–25 kHz) reduce audible whine from motor windings, which improves user experience in quiet environments. For servo motors, use the dedicated Servo library instead, which generates the correct 50 Hz signal with precise pulse widths.
Why does my PWM output measure a fixed voltage on a multimeter?
A standard DC multimeter measures the average voltage of a PWM signal, not its instantaneous value. So a 50% duty cycle on a 5V pin will read approximately 2.5V. This is the expected behavior. To see the actual PWM waveform, use an oscilloscope.
Does the Arduino Nano 33 IoT support PWM?
Yes, the Arduino Nano 33 IoT supports PWM on pins 2, 3, 5, 6, 9, 10, 11, 12, A2, A3, and A5 — far more than the classic Uno. It operates at 3.3V logic, so the maximum average voltage from PWM is 3.3V instead of 5V. Be careful when interfacing with 5V peripherals.
Start Your Arduino PWM Journey Today
PWM is a foundational skill that unlocks a huge range of Arduino projects — from simple LED dimmers to sophisticated motor controllers and audio synthesizers. The key concepts to remember are: analogWrite() takes values from 0–255, duty cycle is the ratio of on-time to total period, and you can change PWM frequency by modifying timer prescaler registers (with care not to break millis() and delay()).
Once you are comfortable with the basics, explore more advanced techniques like complementary PWM for H-bridge control, phase-correct PWM mode for precise motor control, and interrupt-driven PWM for tight timing requirements.
Browse the full range of Arduino boards and accessories at Zbotic.in — India’s trusted electronics components store — and start building your next PWM-powered project today.
Add comment