Fading an LED with Arduino is one of the first projects that teaches a crucial concept beyond simple on/off control: Pulse Width Modulation (PWM). Once you understand PWM and the analogWrite() function, you unlock smooth dimming, RGB colour mixing, servo control, motor speed control, and much more. This guide covers everything from the basics of duty cycle to advanced gamma correction for perceptually smooth brightness transitions.
Table of Contents
- What is PWM and How Does It Work?
- analogWrite() Basics and Syntax
- Which Arduino Pins Support PWM?
- Simple Fade In and Fade Out
- Smooth Breathing Effect with Sine Wave
- RGB LED Colour Mixing with PWM
- Gamma Correction for Natural-Looking Fades
- Troubleshooting LED Fading Problems
- FAQ
What is PWM and How Does It Work?
Pulse Width Modulation is a technique for simulating an analogue output using a digital pin. The pin rapidly switches between HIGH (5V) and LOW (0V) at a fixed frequency — on the Arduino Uno, this is approximately 490 Hz on most PWM pins and 980 Hz on pins 5 and 6. The key variable is the duty cycle: the percentage of each cycle that the pin spends in the HIGH state.
- Duty cycle 0% → pin always LOW → LED fully off
- Duty cycle 50% → pin half HIGH, half LOW → LED at half brightness
- Duty cycle 100% → pin always HIGH → LED fully on
The LED flickers at 490 Hz, but because this is much faster than the human eye can detect (the persistence of vision threshold is around 50-60 Hz), the LED appears to glow at a steady intermediate brightness. The average voltage delivered to the LED is proportional to the duty cycle.
The Arduino’s analogWrite() function abstracts all of this away. You simply pass a value from 0 (always off) to 255 (always on), and the hardware timer handles the PWM generation automatically — no interrupts or timer configuration required for basic use.
analogWrite() Basics and Syntax
The syntax is straightforward:
analogWrite(pin, value);
pin— a PWM-capable pin number (marked with ~ on the board)value— an integer from 0 to 255
You do not need to call pinMode(pin, OUTPUT) before using analogWrite() — it sets the pin as output automatically. However, it is good practice to explicitly set it in setup() for code clarity.
A simple brightness control example:
const int LED_PIN = 9;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
analogWrite(LED_PIN, 0); // Off
delay(500);
analogWrite(LED_PIN, 64); // 25% brightness
delay(500);
analogWrite(LED_PIN, 128); // 50% brightness
delay(500);
analogWrite(LED_PIN, 255); // Full brightness
delay(500);
}
Note: analogWrite() has nothing to do with analogRead(). Despite the similar names, they are completely different functions. analogRead() reads an analogue voltage on the ADC pins. analogWrite() outputs a PWM signal on digital pins.
Which Arduino Pins Support PWM?
Not all digital pins can use analogWrite(). PWM requires a hardware timer, and only certain pins are connected to those timers. Using analogWrite() on a non-PWM pin will not produce an error — it will simply behave like digitalWrite(), fully on for values ≥ 128 and fully off for values below 128.
Arduino Uno R3 PWM pins: 3, 5, 6, 9, 10, 11 (marked with ~ on the board)
Arduino Mega 2560 PWM pins: 2–13 and 44–46 (14 PWM pins total)
Arduino Nano Every PWM pins: 3, 5, 6, 9, 10
Arduino Nano 33 IoT PWM pins: 2, 3, 5, 6, 9, 10, 11, 12, A2, A3, A5
PWM frequencies on the Uno (default, using hardware timers):
- Pins 5, 6: ~980 Hz (Timer 0)
- Pins 9, 10: ~490 Hz (Timer 1)
- Pins 3, 11: ~490 Hz (Timer 2)
Timer 0 is also used by millis() and delay(). Changing Timer 0’s frequency will break timing functions — avoid modifying Timer 0 unless you know what you are doing.
Simple Fade In and Fade Out
The classic fade example gradually increases then decreases brightness in a loop. This is the “Fading” example built into the Arduino IDE (File → Examples → 03.Analog → Fading):
const int LED_PIN = 9;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// Fade in
for (int brightness = 0; brightness <= 255; brightness++) {
analogWrite(LED_PIN, brightness);
delay(8); // 8ms * 256 steps = ~2 seconds to full
}
// Fade out
for (int brightness = 255; brightness >= 0; brightness--) {
analogWrite(LED_PIN, brightness);
delay(8);
}
}
The delay value controls the fade speed. Lower values produce a faster fade. At delay(4), the full fade cycle takes about 2 seconds. At delay(20), it takes about 10 seconds.
Always use a current-limiting resistor with your LED. For a standard red LED with a 2V forward voltage on a 5V Arduino pin, a 150Ω to 220Ω resistor limits the current to a safe 10-15 mA. Without a resistor, the LED will draw as much current as the pin can supply (40 mA maximum on Arduino digital pins), which will likely burn out both the LED and the ATmega chip over time.
Smooth Breathing Effect with Sine Wave
The linear fade (incrementing by 1 each step) creates a perceptible pause at both the darkest and brightest points. A sine wave produces a more natural, biological breathing rhythm that accelerates through the mid-range and slows at the extremes:
#include <math.h>
const int LED_PIN = 9;
float phase = 0.0;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// Sine wave oscillates between -1 and 1
// Map to 0-255 for analogWrite
float sineVal = (sin(phase) + 1.0) / 2.0; // 0.0 to 1.0
int brightness = (int)(sineVal * 255);
analogWrite(LED_PIN, brightness);
phase += 0.02; // Smaller = slower breathing
if (phase >= TWO_PI) phase = 0;
delay(10);
}
This creates the familiar “breathing” effect seen on Apple MacBooks in sleep mode and many status indicator LEDs. The sine wave ensures the LED spends more time near minimum and maximum brightness, with a smooth transition through the midpoint.
RGB LED Colour Mixing with PWM
An RGB LED is three LEDs (red, green, blue) in a single package. By controlling each channel’s brightness with PWM, you can mix any colour. Common cathode RGB LEDs connect the negative legs together to GND and control each positive leg separately. Common anode types are the opposite — connect the common anode to 5V and use inverted PWM values (0 = full brightness, 255 = off).
const int RED_PIN = 9;
const int GREEN_PIN = 10;
const int BLUE_PIN = 11;
void setup() {
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
}
void setColor(int r, int g, int b) {
analogWrite(RED_PIN, r);
analogWrite(GREEN_PIN, g);
analogWrite(BLUE_PIN, 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, 165, 0); // Orange
delay(1000);
setColor(128, 0, 128); // Purple
delay(1000);
setColor(255, 255, 255); // White
delay(1000);
setColor(0, 0, 0); // Off
delay(1000);
}
For a smooth rainbow cycle, interpolate through hue values using HSV-to-RGB conversion. There are many Arduino HSV libraries available, or you can implement a simple one yourself using the standard hue wheel algorithm.
Gamma Correction for Natural-Looking Fades
The human eye does not perceive brightness linearly. A PWM value of 128 (50% duty cycle) looks much brighter than 50% of maximum brightness to human perception — it actually appears about 73% as bright. This means the first half of a linear 0-to-255 ramp looks slow, while the second half looks fast.
Gamma correction applies a power function to the brightness values to compensate. A gamma value of 2.2 is standard for most displays and LEDs:
// Generate a 256-entry gamma correction lookup table
// Run this once in setup() or store in PROGMEM
uint8_t gamma_table[256];
void buildGammaTable(float gamma) {
for (int i = 0; i < 256; i++) {
gamma_table[i] = (uint8_t)(pow((float)i / 255.0, gamma) * 255.0 + 0.5);
}
}
// Use in your fade loop:
analogWrite(LED_PIN, gamma_table[brightness]);
With gamma correction, the LED appears to dim and brighten at a perceptually uniform rate throughout the full range. This is especially noticeable when dimming LED strips or indicators that must look professional and polished.
For PROGMEM storage (useful when RAM is tight on Uno/Nano), use:
const uint8_t PROGMEM gamma_table[256] = { /* pre-computed values */ };
// Read with: pgm_read_byte(&gamma_table[i])
Troubleshooting LED Fading Problems
LED is full on or full off, no fading:
- Check that you are using a PWM pin (marked ~ on the board)
- Verify the pin number in your code matches the physical wiring
- Confirm the variable type — if your brightness counter is an
int, it should not overflow. Usingbyte(0-255 range) can cause issues if you decrement below 0 on an unsigned type
LED flickers noticeably:
- A PWM frequency of 490 Hz should not be visible. If you see flicker, check for a loose connection in the LED circuit
- Some cameras capture PWM flicker even when the human eye does not — try a different camera or phone for video recording
- If using a MOSFET or transistor driver, ensure the gate/base resistor value is appropriate
LED is dim even at 255:
- Check the current-limiting resistor value — it may be too large
- Measure the LED’s forward voltage with a multimeter and recalculate: R = (Vsupply – Vf) / Idesired
- For high-brightness LEDs, use a dedicated LED driver IC rather than direct Arduino pin control
Timing functions break after using PWM:
- If you modified Timer 0’s prescaler or mode directly, this will break
millis()anddelay() - Only modify Timer 1 or Timer 2 for custom PWM frequencies
- Libraries like
analogWriteFrequency()from the TimerOne library can help change frequency safely
Frequently Asked Questions
Can I use analogWrite() on all Arduino digital pins?
No. Only PWM-capable pins support analogWrite(). On the Arduino Uno, these are pins 3, 5, 6, 9, 10, and 11, all marked with a tilde (~) on the silkscreen. Calling analogWrite() on a non-PWM pin will not generate an error but will produce incorrect behaviour — the pin will be fully HIGH for values above 127 and fully LOW for values of 127 or below.
What is the PWM frequency on Arduino Uno and can it be changed?
Default frequencies are approximately 490 Hz on pins 3, 9, 10, 11 and 980 Hz on pins 5 and 6. You can change the frequency by modifying the timer prescaler registers directly. The TimerOne and TimerThree libraries provide a safer abstraction. Changing Timer 0 (pins 5, 6) will break millis() and delay(), so avoid it unless absolutely necessary.
Why does my fading LED look uneven — fast at first and slow at the end?
This is a perception issue. The human eye responds to brightness logarithmically, not linearly. A PWM duty cycle of 50% appears much brighter than 50% of full brightness perceptually. Apply gamma correction (gamma ≈ 2.2) to your brightness values to create a perceptually uniform fade. The gamma correction section above includes ready-to-use code.
How many LEDs can I fade simultaneously with analogWrite()?
On the Arduino Uno, you have 6 PWM pins, so you can independently fade up to 6 LEDs. For more LEDs, use a dedicated PWM controller like the PCA9685 (16-channel, I2C) or shift registers. WS2812B addressable LED strips handle their own PWM internally and let you control hundreds of RGB LEDs with a single data pin using the FastLED or NeoPixel library.
Does analogWrite() work the same on all Arduino boards?
The function name and 0-255 range are consistent, but the underlying hardware differs. PWM pin count, frequency, and timer assignments vary between Uno, Mega, Leonardo, Nano, and the newer ARM-based boards like the Due and Zero. Always check the pinout diagram for your specific board. ARM-based boards often support higher PWM frequencies (up to 200 kHz or more) and more PWM channels than AVR-based boards.
Mastering PWM and analogWrite() is a major step up from simple digital on/off control. With these techniques, you can build professional-looking LED effects, precise motor speed controllers, audio signal generators, and much more. The same principles apply whether you are dimming a single LED or controlling a 300-LED strip.
Shop our complete range of Arduino boards and accessories at Zbotic.in — from beginner kits to advanced modules, everything you need for your next electronics project, delivered across India.
Add comment