PWM LED dimming controls brightness by rapidly switching the LED fully on and fully off, varying the fraction of time it is on (the duty cycle) to adjust perceived brightness. Because the LED always operates at the same forward current during the on-time, its color temperature and spectrum remain constant regardless of brightness level — a critical advantage over analog dimming which shifts LED color as current decreases. PWM dimming at frequencies above 200Hz is imperceptible to the human eye, making it the standard technique for LED lighting, display backlights, indicator lights, and architectural lighting control.
Introduction: Why LEDs Need Special Dimming Treatment
Controlling the brightness of an incandescent light bulb is straightforward — reduce the voltage or current and the filament glows less brightly. The physics are forgiving: a filament produces light across a wide range of temperatures, and while color temperature shifts somewhat with dimming, the change is gradual and the bulb continues to function normally down to very low intensities.
LEDs are fundamentally different. They are semiconductor devices with exponential current-voltage characteristics — small changes in forward voltage produce large changes in current. At rated current, an LED emits light efficiently and at its specified color temperature. Reduce the current significantly and several problems emerge: efficiency drops because LEDs are more efficient at moderate currents than very low ones, color temperature shifts (especially for phosphor-based white LEDs whose phosphor conversion efficiency changes with drive current), and the relationship between drive current and perceived brightness is nonlinear and often poorly characterized at very low currents.
PWM dimming elegantly sidesteps all these problems. Instead of running the LED at a reduced current, PWM runs it at the same current as always — but only for a fraction of the time. At 50% duty cycle, the LED is on at full brightness for half of each cycle and completely off for the other half. The average light output is half of maximum. The eye and brain, which integrate light over time, perceive the average brightness. The LED itself, during its on-time, operates at exactly its rated conditions — same forward current, same temperature, same color, same efficiency.
The result: PWM dimming maintains perfect color fidelity across the entire brightness range, from 100% down to fractions of a percent, with the LED always operating at its best efficiency point. This is why PWM is used in virtually every application where LED color quality matters — studio lighting, cinema projection, medical imaging displays, horticulture lighting, and any display that must maintain accurate color rendition across all brightness levels.
This article builds a complete understanding of PWM LED dimming: the physics of why PWM preserves color, how to calculate the correct PWM frequency for different applications, the relationship between duty cycle and perceived brightness (which requires gamma correction to feel natural), how to implement PWM LED dimming at every scale from a single indicator LED through to high-power fixtures, the MOSFET and driver circuits needed for different power levels, and a series of complete practical design examples covering the most common LED dimming scenarios.
Why PWM Preserves LED Color: The Physics
How LEDs Produce Light
A light-emitting diode produces light when electrons recombine with holes at the semiconductor junction, releasing energy as photons. The wavelength (color) of the emitted photons is determined by the bandgap energy of the semiconductor material — GaN for blue and white LEDs, AlGaInP for red and amber, GaP for green. This bandgap is a fixed material property that does not change with operating conditions within normal ranges.
For a blue LED with yellow phosphor (the most common construction for white LEDs): the blue chip emits at a fixed wavelength determined by its InGaN composition; the phosphor absorbs some blue photons and re-emits them at longer wavelengths (yellow); the mixture of transmitted blue and converted yellow appears white. The ratio of blue-to-yellow conversion depends on the phosphor layer thickness and the drive current density at the chip.
Why Analog Dimming Shifts Color
At reduced drive current, two effects combine to shift white LED color temperature:
Effect 1 — Droop: At lower current densities, the efficiency of the LED chip itself changes. The proportion of carriers that recombine radiatively (producing light) vs. non-radiatively (producing heat) depends on current density. At very low currents, non-radiative recombination pathways dominate, reducing efficiency. More importantly, the spectrum of the emitted blue light shifts slightly with current, altering the excitation spectrum of the phosphor.
Effect 2 — Phosphor saturation change: The phosphor conversion efficiency changes with the intensity of the blue light hitting it. At lower drive currents, less blue light is available; the phosphor operates at a different point on its conversion curve. The result is a different blue-to-yellow ratio — the apparent color temperature shifts.
The combined effect is visible: an LED fixture dimmed from 100% to 10% by reducing current will appear noticeably “warmer” (more yellow) at 10% because the phosphor converts a higher fraction of the reduced blue output. For color-critical applications, this color shift is unacceptable.
Why PWM Does Not Shift Color
With PWM dimming, the LED operates at its full rated current during every on-pulse. The chip temperature, current density, and phosphor excitation are identical to full-brightness operation. The only change is the fraction of time the LED is on. During the off-time, the LED is dark — but this introduces no color shift because no light is emitted at all. The eye integrates the on-pulses over time, perceiving an average brightness that is proportional to the duty cycle. The color of those on-pulses is identical to full-brightness operation.
Practical result: PWM dimming produces perfect color consistency from 100% to 0.1% duty cycle (and below). Analog dimming produces noticeable color shift below about 30–50% brightness for most phosphor-based white LEDs. For this reason, PWM dimming is specified in professional lighting, cinema, medical displays, and scientific instrumentation wherever color accuracy matters.
Flicker: The Central Challenge of PWM LED Dimming
What Causes LED Flicker
Because PWM rapidly switches LEDs on and off, they do literally flicker — they are off for a portion of every cycle. The question is whether this flicker is perceptible to human observers or causes problems with cameras and video equipment.
Human visual perception of flicker depends on several factors:
- Frequency: The critical flicker fusion frequency (above which flicker becomes imperceptible) varies between individuals and viewing conditions but is generally 50–100Hz for foveal (direct) vision and up to 160Hz for peripheral vision.
- Duty cycle: At very low duty cycles (< 10%), flicker may be perceptible at frequencies up to 200Hz or higher because the brief bright flashes are more conspicuous to the visual system.
- Light level: In bright environments, flicker at a given frequency is more visible than in dim environments.
- Individual sensitivity: Some people are more sensitive to flicker than others; migraine sufferers and people with certain neurological conditions may experience discomfort from flicker imperceptible to others.
IEEE recommendations (IEEE 1789-2015): For general lighting applications, PWM frequencies above 1250Hz pose no risk to any population. Between 90Hz and 1250Hz, risk increases as frequency decreases. Below 90Hz, visible flicker is likely. For applications with low duty cycle (< 10%), higher frequencies are required.
Practical guidelines:
- Below 100Hz: Visible flicker to most people — not suitable for general illumination
- 100Hz–200Hz: Borderline — may be acceptable in some contexts
- 200Hz–1kHz: Generally acceptable for most people in most situations
- 1kHz–10kHz: Safe for virtually all applications including low duty cycles
- Above 10kHz: Absolutely flicker-free; recommended for any sensitive application
Camera and Video Flicker
Digital cameras and video equipment introduce a second flicker problem: the interaction between PWM frequency and frame rate. If the PWM frequency is not an integer multiple of the camera’s frame rate, each frame captures a different phase of the PWM cycle, causing the apparent brightness to vary from frame to frame — resulting in visible flickering in video footage.
The interaction: At 60fps video, the camera captures a new frame every 16.67ms. A 1kHz PWM has a period of 1ms — 16.67 complete cycles per frame. Each frame captures the same number of complete cycles, so each frame receives the same total light exposure. No flicker in the footage.
At 480Hz PWM and 60fps video: each frame lasts 16.67ms, and 480Hz has a period of 2.08ms — exactly 8 complete cycles per 16.67ms. The fractional part is zero (8.000 exactly), so no flicker.
At 500Hz PWM and 24fps video: period = 2ms, frame time = 41.67ms. Each frame captures 20.83 cycles — a fractional number. Different frames capture different fractions of a cycle in different phases, producing frame-to-frame brightness variation: visible flicker in footage.
General rule for camera compatibility: f_PWM should be an integer multiple of the camera frame rate, or high enough (typically above 4000–5000 Hz) that the partial-cycle effect averages out within the rolling shutter integration time.
For professional cinematography and photography: Use f_PWM ≥ 10kHz, or DC (analog) dimming at the brightness level required. Many professional LED fixtures offer both modes.
Stroboscopic Effects
At frequencies between about 100Hz and 1000Hz, PWM-dimmed lighting illuminating moving objects can produce a stroboscopic effect — rotating machinery appears to stop or move in slow motion, and fast-moving objects appear strobed rather than blurred. This is a safety concern in industrial environments: under 120Hz PWM lighting, a machine tool spinning at 3600 RPM (60 Hz) appears stationary (60Hz × 2 poles = 120Hz matches the PWM switching). Workers may not perceive the machine as moving and could be injured.
For industrial or workshop environments with rotating machinery, use either very high PWM frequency (> 1kHz, well above any expected rotation rate) or DC drive with analog dimming.
Perceived Brightness vs. Duty Cycle: Gamma Correction
The Problem with Linear Dimming
A naive PWM dimmer might map a linear control input (0 to 255 on a microcontroller) to a linear duty cycle (0% to 100%). This feels wrong in practice: the light jumps from zero to noticeable brightness within the first 10% of the control range, then changes very slowly over the upper 90%. Users describe it as “all the action is at the bottom of the knob.”
The cause is that human brightness perception is not linear — it is approximately logarithmic (or more precisely, follows a power law). The Weber-Fechner law and Stevens’ power law describe how humans perceive stimulus intensity: equal ratios of physical intensity are perceived as equal differences in sensation. Doubling the light output does not feel like “twice as bright” — it feels like a modest increase.
For practical dimming control, this means a linear mapping from control value to duty cycle does not feel like uniform dimming. The control needs to be corrected to match human perception.
Gamma Correction
The standard approach is gamma correction, borrowed from display calibration. The corrected duty cycle:
D = (control_value / max_control_value)^γWhere γ (gamma) is typically 2.2 for display applications or can be tuned for a particular LED system.
Effect of gamma correction:
At control value 128/255 (50% input):
- Linear: D = 50% (50% of max brightness)
- Gamma 2.2: D = (0.5)^2.2 = 0.218 = 21.8% (much lower brightness at “half” control)
At control value 64/255 (25% input):
- Linear: D = 25%
- Gamma 2.2: D = (0.25)^2.2 = 0.048 = 4.8%
The gamma-corrected control uses much lower duty cycles at the low end, spreading the perceptually “interesting” range of brightness changes across the full control range. Users experience smooth, uniform-feeling dimming from minimum to maximum.
Precomputed lookup table (8-bit input to 8-bit output):
// Generate gamma-corrected PWM values at startup
uint8_t gamma_table[256];
void buildGammaTable(float gamma) {
for (int i = 0; i < 256; i++) {
gamma_table[i] = (uint8_t)(255.0 * pow(i / 255.0, gamma) + 0.5);
}
}
// Use in dimming:
void setLEDBrightness(uint8_t level) {
analogWrite(LED_PIN, gamma_table[level]);
}For 16-bit PWM output (better resolution at low brightness):
uint16_t gamma_table_16[256]; // 8-bit input, 16-bit PWM output
void buildGammaTable16(float gamma) {
for (int i = 0; i < 256; i++) {
gamma_table_16[i] = (uint16_t)(65535.0 * pow(i / 255.0, gamma) + 0.5);
}
}Minimum Perceivable Brightness
With 8-bit PWM (256 steps, 0.39% per step), the minimum non-zero brightness is 1/255 ≈ 0.39% duty cycle. For a 100mA LED at 100% duty cycle, the minimum corresponds to 0.39mA average current — barely perceptible.
For smooth dimming all the way to near-off, the minimum duty cycle step must be smaller than the just-noticeable difference in brightness at low levels. With gamma correction and 8-bit input, the finest steps near zero are already very small (gamma-corrected D at input=1: (1/255)^2.2 ≈ 0.000006 = 0.0006%). In practice, 8-bit input with gamma correction and 10-bit or higher PWM resolution provides extremely smooth dimming.
PWM LED Dimming Circuits: Four Levels of Complexity
Level 1: Microcontroller GPIO Direct Drive (< 20mA)
The simplest possible LED dimming circuit: the LED and current-limiting resistor connected directly to a microcontroller PWM pin.
Suitable for: Indicator LEDs, status lights, small signal LEDs — anything that draws less than the GPIO’s maximum sink/source current (typically 20–40mA for most microcontrollers, 8–12mA for conservative safe operation).
Circuit:
MCU PWM pin → 220Ω → LED anode → LED cathode → GNDCurrent calculation:
I_LED = (V_OH - V_f) / RFor V_OH = 3.3V, V_f = 2.0V (red LED), R = 220Ω:
I_LED = (3.3 - 2.0) / 220 = 5.9mA — safe for all microcontrollersFor V_OH = 5V, V_f = 3.2V (blue/white LED), R = 100Ω:
I_LED = (5.0 - 3.2) / 100 = 18mA — at the limit for some MCUs; use 150Ω for safetyPWM frequency: Set f_PWM ≥ 1kHz to avoid visible flicker. On Arduino, change the timer prescaler as described in Article 75.
Limitation: GPIO current limit means maximum one or two LEDs per pin at modest brightness. For higher current, proceed to Level 2.
Level 2: NPN Transistor Driver (20mA–500mA)
An NPN transistor (BJT) acts as a current amplifier, allowing the microcontroller’s small base current (< 1mA) to switch much larger collector currents through the LED.
Circuit:
MCU PWM pin → 1kΩ base resistor → NPN base
NPN collector → LED cathode
LED anode → current-limiting resistor → V_supply
NPN emitter → GNDTransistor selection: BC547, 2N2222 (small signal, up to 200mA). For higher currents (up to 600mA), use BD139, TIP31.
Base resistor calculation:
In saturation (fully on), the transistor needs sufficient base current:
I_B_required = I_C / hFE_satFor hFE_sat ≈ 10 (conservative for saturation) and I_C = 100mA:
I_B_required = 100mA / 10 = 10mA
R_base = (V_PWM_high - V_BE) / I_B = (3.3 - 0.7) / 10mA = 260Ω → use 220ΩFor a 1kΩ base resistor (more typical, less base current):
I_B = (3.3 - 0.7) / 1000 = 2.6mA
Maximum I_C = 2.6mA × hFE_sat = 2.6 × 10 = 26mAChoose base resistor based on required collector current and transistor’s hFE_sat.
PWM switching consideration: BJTs have minority carrier storage time — when switching off, the base charge must be removed before the transistor turns off. For slow PWM (< 1kHz), any transistor works fine. For fast PWM (> 10kHz), use fast switching transistors (2N2222 has fast recovery) or use a MOSFET instead.
Level 3: N-Channel MOSFET Driver (500mA–20A)
For LED strips, high-power LEDs, or multiple parallel LEDs, an N-channel MOSFET provides low on-resistance, fast switching, and high current capability.
Circuit:
V_supply → LED(s) → MOSFET drain
MOSFET source → GND
MCU PWM pin → gate resistor (100Ω) → MOSFET gate
MOSFET gate → 10kΩ pull-down → GND ← essential! Keeps gate LOW when MCU is off/resetMOSFET selection:
For 3.3V MCU PWM output, use a logic-level MOSFET — one with guaranteed full R_DS(on) specification at V_GS = 4.5V or less:
- IRLZ44N: 55V, 47A, R_DS(on) = 22mΩ at 5V — extremely capable, overkill for most LED work
- FQP30N06L: 60V, 30A, R_DS(on) = 35mΩ at 4.5V
- 2N7000: 60V, 200mA, R_DS(on) = 5Ω at 5V — for small LEDs only
- AO3400: 30V, 5.7A, R_DS(on) = 40mΩ at 4.5V, SOT-23 package — excellent for LED strips
For 5V MCU PWM output: Standard MOSFETs (IRFZ44N, IRF540N) work fine as V_GS = 5V is sufficient to turn them on, though not fully enhanced. Logic-level types are still preferable.
Pull-down resistor on gate: Essential! Without it, the gate floats during MCU power-up, reset, or boot sequence, leaving the MOSFET gate at an undefined voltage. A 10kΩ pull-down keeps the gate at 0V (MOSFET off) whenever the MCU pin is not actively driving it HIGH.
Freewheeling/snubber: For purely resistive LED loads (no inductance), no freewheeling diode is needed — unlike motor loads. However, LED driver wiring has small parasitic inductance that can cause voltage spikes at switching. A 100nF ceramic capacitor from drain to source (as close to the MOSFET as possible) absorbs these spikes.
Level 4: Dedicated LED Driver IC with PWM Input
For professional applications, addressable LED drivers, or where precise current control is critical, dedicated LED driver ICs accept a PWM input and combine it with accurate constant-current output:
TLC5940 / TLC5947 (Texas Instruments): 16/24 channel LED driver with 12-bit individual PWM per channel, driven via SPI. Each channel is a constant-current sink (1–60mA programmable). Widely used for LED matrices and multi-color LED control.
PCA9685 (NXP): 16-channel, 12-bit PWM LED/servo driver with I²C interface. Each channel independently programmable from 0–100% with 4096 steps. Operates at 24Hz to 1526Hz PWM frequency. Popular with Arduino and Raspberry Pi projects for driving many LEDs or servos from just two wires.
AL8860 / AMC7135 (discrete LED driver ICs): Constant-current buck LED drivers with PWM dimming input. The PWM signal enables/disables the driver’s output, giving true PWM dimming at constant LED current. Output current is set by a single resistor. For single high-power LEDs (1W–10W), these are the professional solution.
Implementing PWM LED Dimming on Microcontrollers
Arduino (AVR ATmega328P)
Default PWM frequency: 490Hz (pins 3, 9, 10, 11) or 980Hz (pins 5, 6). Too low for professional applications.
Changing frequency to 31.4kHz (prescaler = 1):
// Timer 1 controls pins 9 and 10
// Timer 2 controls pins 3 and 11
// Timer 0 controls pins 5 and 6 — WARNING: changing Timer 0 breaks delay() and millis()
// Safe: change Timer 1 only (doesn't affect system timing)
TCCR1B = (TCCR1B & 0b11111000) | 0x01; // prescaler = 1 → ~31.4kHz
// Also safe: change Timer 2
TCCR2B = (TCCR2B & 0b11111000) | 0x01; // prescaler = 1 → ~31.4kHzCustom 20kHz with phase-correct PWM and adjustable resolution:
// 10-bit resolution at 7.8kHz (phase-correct, prescaler=1):
// f = 16MHz / (2 × 1 × 1024) = 7.8kHz, 1024 steps
// Or 8-bit resolution at 31.4kHz:
// f = 16MHz / (2 × 1 × 256) = 31.4kHz, 256 steps
// Custom 9-bit at 15.6kHz:
TCCR1A = _BV(COM1A1) | _BV(WGM11); // Phase-correct PWM, TOP=ICR1
TCCR1B = _BV(WGM13) | _BV(CS10); // prescaler=1
ICR1 = 511; // TOP=511 → 9-bit (512 steps)
// f = 16MHz / (2 × 1 × 512) = 15.6kHz
void setLED(uint16_t value) { // value: 0–511
OCR1A = value;
}Complete Arduino LED dimmer with gamma correction and smooth fading:
#include <avr/pgmspace.h>
// Pre-computed gamma 2.2 table stored in flash (saves RAM)
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
};
const int LED_PIN = 9;
void setup() {
// Set Timer 1 to ~31kHz
TCCR1B = (TCCR1B & 0b11111000) | 0x01;
pinMode(LED_PIN, OUTPUT);
}
void setLED(uint8_t brightness) {
// Look up gamma-corrected value from flash
analogWrite(LED_PIN, pgm_read_byte(&gamma8[brightness]));
}
void loop() {
// Smooth fade up
for (int i = 0; i <= 255; i++) {
setLED(i);
delay(8); // 2.04 seconds total fade
}
delay(500);
// Smooth fade down
for (int i = 255; i >= 0; i--) {
setLED(i);
delay(8);
}
delay(500);
}ESP32 (LEDC Peripheral)
The ESP32’s LEDC peripheral was designed for LED control and offers superior flexibility:
// ESP32 PWM LED dimming with gamma correction
const int LED_PIN = 2;
const int PWM_CHANNEL = 0;
const int PWM_FREQ = 25000; // 25kHz — silent, fast
const int PWM_RESOLUTION = 12; // 12-bit: 4096 steps
const int PWM_MAX = 4095;
void setup() {
ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);
ledcAttachPin(LED_PIN, PWM_CHANNEL);
}
// Gamma-corrected brightness (input 0-255, output 0-4095 for 12-bit PWM)
uint16_t gammaCorrect(uint8_t brightness) {
float normalized = brightness / 255.0f;
float corrected = pow(normalized, 2.2f);
return (uint16_t)(corrected * PWM_MAX);
}
void setLED(uint8_t brightness) {
ledcWrite(PWM_CHANNEL, gammaCorrect(brightness));
}
void loop() {
for (int i = 0; i <= 255; i++) {
setLED(i);
delay(8);
}
for (int i = 255; i >= 0; i--) {
setLED(i);
delay(8);
}
}The ESP32’s 12-bit resolution (4096 steps) with gamma correction gives exceptionally smooth dimming at the low end — critical for ambient lighting applications where most operation happens at 5–30% brightness.
Raspberry Pi (pigpio for Precise PWM)
import pigpio
import time
import math
pi = pigpio.pi()
LED_PIN = 18
PWM_FREQ = 10000 # 10kHz
pi.set_PWM_frequency(LED_PIN, PWM_FREQ)
pi.set_PWM_range(LED_PIN, 10000) # 0-10000 for fine resolution
def gamma_correct(brightness_0_to_255, gamma=2.2):
"""Convert 0-255 brightness to gamma-corrected PWM range 0-10000"""
if brightness_0_to_255 <= 0:
return 0
normalized = brightness_0_to_255 / 255.0
corrected = math.pow(normalized, gamma)
return int(corrected * 10000)
def set_led(brightness):
pi.set_PWM_dutycycle(LED_PIN, gamma_correct(brightness))
# Smooth fade
for brightness in range(0, 256, 2):
set_led(brightness)
time.sleep(0.015)
for brightness in range(255, -1, -2):
set_led(brightness)
time.sleep(0.015)
pi.set_PWM_dutycycle(LED_PIN, 0)
pi.stop()High-Power LED Dimming
Single High-Power LED (1W–10W)
High-power LEDs (Cree XHP50, Luxeon C, etc.) operate at forward currents of 350mA to 3A and require thermal management and precision current control. A simple resistor and MOSFET are inadequate — resistor power dissipation is too high and current regulation is poor.
The correct approach: constant-current PWM dimming
A constant-current LED driver regulates the LED current to exactly the specified value regardless of forward voltage variation (which changes with temperature). PWM dimming is applied by enabling/disabling the driver output at the desired duty cycle.
Two dimming methods for constant-current drivers:
Method 1 — Analog dimming input: Many LED driver ICs accept a 0–1V or 0–3.3V analog signal to set the output current proportionally. Apply a filtered PWM signal (PWM → RC filter → AREF pin). This actually performs analog dimming (varying current), which shifts LED color. Not ideal for color-critical work.
Method 2 — PWM enable input (true PWM dimming): The preferred method. A dedicated PWM/DIM input enables or disables the driver’s switching at the desired duty cycle. During the ON phase, the LED operates at full rated current; during the OFF phase, the driver is disabled. This is true PWM dimming at constant current.
Example: AL8860 constant-current buck LED driver with PWM dimming
The AL8860 is an 8-pin SOT-23 IC that drives LEDs at up to 1A constant current from 4.5V to 40V input:
Key connections:
- VIN: input supply (e.g., 12V)
- SW: MOSFET switch node (connect to inductor)
- GND: ground and thermal pad
- EN/DIM: PWM dimming input (logic HIGH = on, logic LOW = off)
- R_SET resistor from SET pin to GND: sets output current (I_out = 0.24V / R_SET)
For 350mA output: R_SET = 0.24 / 0.350 = 686Ω → use 680Ω
For 700mA output: R_SET = 0.24 / 0.700 = 343Ω → use 330Ω
Inductor selection: L = V_in × D × (1−D) / (ΔI × f_sw)
For V_in = 12V, V_LED = 3.5V, D = 3.5/12 = 0.29, f_sw = 500kHz (internal), ΔI = 20% of 350mA = 70mA:
L = 12 × 0.29 × 0.71 / (0.07 × 500,000) = 2.47V / 35,000 = 70.6µH → use 68µH or 100µHPWM dimming frequency for this driver: The EN/DIM pin responds to PWM at any frequency from DC to about 20kHz (higher frequencies may cause incomplete enable/disable cycles). Use 500Hz–5kHz for reliable operation.
LED Strip Dimming (12V/24V WS2812B vs. Non-Addressable)
Non-addressable LED strips (e.g., 5050 SMD, single color or RGB):
These are simply LEDs in parallel with internal current-limiting resistors, powered from 12V or 24V. Dimming is done by PWM-switching the supply voltage (or the low side) with a MOSFET.
For a 5A, 12V LED strip: MOSFET (IRFZ44N or similar), gate resistor (100Ω), 10kΩ gate pull-down, freewheeling capacitor (100µF across the strip supply rails near the MOSFET to handle current interruption).
PWM frequency: 1kHz minimum, 10kHz recommended for video compatibility.
For RGB strips: Three separate MOSFETs (one per color channel), each driven by a separate PWM output from the microcontroller. This enables full color mixing and individual channel dimming.
Addressable LED strips (WS2812B, SK6812, APA102):
Each LED contains its own control IC. Brightness is set digitally via a serial data protocol (typically 800kbps for WS2812B). PWM is performed internally in each LED IC — the user simply sends the desired brightness value (0–255) as part of the color data. No external PWM hardware is needed.
The WS2812B performs 8-bit PWM at approximately 400Hz internally. For professional applications requiring higher dimming frequency or smoother low-end dimming, the SK6812 and APA102 offer improvements (APA102 uses 20kHz internal PWM and a separate 5-bit global brightness control).
Complete Design Examples
Design Example 1: Dimmable Desk Lamp (12V, Single White LED Module)
Specification: Dimmable desk lamp using a 12V, 10W COB LED module (constant current 850mA). Dimming range 5%–100%. Control: potentiometer for analog dimming control. Silent operation.
Architecture: Potentiometer → MCU ADC → PWM output → AL8860 LED driver → COB LED.
Components:
- Arduino Nano (or bare ATmega328P)
- AL8860 constant-current buck driver
- 10kΩ potentiometer (dimming control)
- 68µH, 2A inductor
- 100µF, 25V bulk capacitor
- Schottky diode (1N5819 or SS34)
- 330Ω R_SET resistor (sets 727mA — close to 850mA, adjust as needed)
- COB LED module (12V, 10W, on heatsink)
Arduino code with exponential dimming curve:
const int POT_PIN = A0;
const int DIM_PIN = 9;
void setup() {
// Set Timer 1 to ~3.9kHz (prescaler=8, ~4kHz) for reliable AL8860 response
TCCR1B = (TCCR1B & 0b11111000) | 0x02; // prescaler=8
pinMode(DIM_PIN, OUTPUT);
pinMode(POT_PIN, INPUT);
}
void loop() {
int pot = analogRead(POT_PIN); // 0-1023
// Map to 5%-100% brightness range
// Below 5% (pot < 51) → turn off to avoid warm LED at very low duty cycle
if (pot < 20) {
analogWrite(DIM_PIN, 0); // OFF
return;
}
// Map pot 20-1023 to brightness 5-100%
float normalized = (pot - 20) / 1003.0;
// Gamma correction (γ = 2.0 for smooth feel on this control)
float duty = pow(normalized, 2.0);
// Minimum 5% (12/255) to prevent LED flicker at very low duty cycles
int pwm_value = max(12, (int)(duty * 255));
analogWrite(DIM_PIN, pwm_value);
delay(10); // Debounce / update rate limiting
}Heatsink requirement: 10W LED at full power needs thermal management.
T_j = T_ambient + P × θ_total
θ_total = θ_LED + θ_TIM + θ_heatsink
For T_j < 85°C at 25°C ambient: θ_total < (85-25)/10 = 6°C/WUse at least a 40×40mm aluminum heatsink (typically 4–6°C/W with natural convection).
Design Example 2: RGB LED Mood Light (Common Anode, 3 PWM Channels)
Specification: Desktop RGB LED mood light with three independently dimmable channels (red, green, blue) controlled by three potentiometers. Common-anode RGB LED cluster (or RGB LED strip section). 5V operation.
Common anode vs. common cathode: A common-anode LED has all positive terminals tied together (to V+). Each color cathode is independently controlled by sinking current to ground. This requires NPN transistors or N-channel MOSFETs pulling each cathode LOW (not HIGH) for the LED to light. The PWM logic is inverted: PWM HIGH turns LED OFF (MOSFET off, LED cathode floating); PWM LOW turns LED ON (MOSFET on, cathode pulled to ground).
Inverted PWM on Arduino:
// For common-anode LED: HIGH = off, LOW = on
// analogWrite(pin, 0) = full brightness (always LOW)
// analogWrite(pin, 255) = off (always HIGH)
void setRGB(uint8_t r, uint8_t g, uint8_t b) {
// Gamma correct each channel
analogWrite(RED_PIN, 255 - pgm_read_byte(&gamma8[r]));
analogWrite(GREEN_PIN, 255 - pgm_read_byte(&gamma8[g]));
analogWrite(BLUE_PIN, 255 - pgm_read_byte(&gamma8[b]));
}Full RGB mood light code:
const int RED_PIN = 9;
const int GREEN_PIN = 10;
const int BLUE_PIN = 11;
const int POT_R = A0, POT_G = A1, POT_B = A2;
// Gamma table as defined earlier...
void setup() {
TCCR1B = (TCCR1B & 0b11111000) | 0x01; // pins 9,10 to ~31kHz
TCCR2B = (TCCR2B & 0b11111000) | 0x01; // pin 11 to ~31kHz
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
}
void loop() {
uint8_t r = analogRead(POT_R) >> 2; // 10-bit ADC → 8-bit
uint8_t g = analogRead(POT_G) >> 2;
uint8_t b = analogRead(POT_B) >> 2;
setRGB(r, g, b);
delay(20);
}Adding smooth color transitions (interpolation):
For automatic color cycling rather than manual control, interpolate between color targets:
void fadeToColor(uint8_t r2, uint8_t g2, uint8_t b2,
uint8_t r1, uint8_t g1, uint8_t b1,
int steps, int step_delay_ms) {
for (int i = 0; i <= steps; i++) {
float t = (float)i / steps;
uint8_t r = r1 + (int)((r2 - r1) * t);
uint8_t g = g1 + (int)((g2 - g1) * t);
uint8_t b = b1 + (int)((b2 - b1) * t);
setRGB(r, g, b);
delay(step_delay_ms);
}
}Design Example 3: Flicker-Free LED Panel for Video Production
Specification: 50W LED panel for video production. Must be completely flicker-free on camera at any frame rate (24/25/30/50/60/120fps). Dimming range 1%–100%. Constant color temperature across full range.
Critical requirements:
- f_PWM >> highest camera frame rate × duty cycle inverse
- True PWM dimming (not analog) to preserve color temperature
- Minimum duty cycle step small enough to not cause frame-to-frame variation
Analysis: At 120fps (the most demanding common frame rate), the camera captures a frame every 8.33ms. For truly flicker-free operation at even 1% duty cycle:
At 1% duty cycle and f_PWM = 1kHz: each 1ms cycle has 10µs ON, 990µs OFF. In 8.33ms frame time: 8.33 cycles. The fractional 0.33 cycle means some frames capture 8 complete ON pulses and others capture 9 — a potential 12.5% frame-to-frame variation in exposure. Visible flicker at 1%.
At 1% duty cycle and f_PWM = 10kHz: each 100µs cycle has 1µs ON, 99µs OFF. In 8.33ms: 83.3 cycles. The 0.3-cycle variation represents 0.36% frame-to-frame exposure variation — imperceptible.
Required PWM frequency for cinema use: f_PWM ≥ 10kHz for all duty cycles ≥ 1%.
Architecture:
- Microcontroller with 16-bit Timer (STM32, or Arduino with ICR1 top)
- Constant-current LED driver with PWM DIM input (e.g., MP3302 or similar high-frequency-capable driver)
- 50W LED COB module on heatsink with fan
STM32F103 implementation (72MHz clock, 16-bit timer):
// STM32 HAL pseudo-code for 20kHz, 12-bit resolution
// f = 72MHz / (prescaler × TOP) = 72MHz / (1 × 3600) = 20kHz
// Resolution = 3600 steps (≈11.8 bits)
TIM_HandleTypeDef htim1;
void PWM_Init() {
htim1.Init.Prescaler = 0; // No prescaler
htim1.Init.Period = 3599; // TOP = 3599 → 3600 steps, 20kHz
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
// ... HAL_TIM_PWM_Init, channel config...
}
void setLEDBrightness(float percent) {
// Gamma correct: PWM value = (percent/100)^2.2 × 3599
float duty = pow(percent / 100.0f, 2.2f) * 3599.0f;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, (uint32_t)duty);
}This provides 3600 steps across the full range — effectively 11.8-bit resolution — at 20kHz, completely above any camera frame rate.
Design Example 4: Automotive Interior LED Dimmer
Specification: Dim the interior dome light and dashboard LEDs in a 12V automotive system. Requirements: works from 9V–16V (battery range), withstands load dump transients (up to 40V), soft-start (gradual ramp on power-up), silent operation (passengers sensitive to noise).
Automotive considerations:
Transient protection: Automotive electrical systems have load-dump transients when the alternator is suddenly disconnected under load — voltage spikes to 30–80V for hundreds of milliseconds. The MOSFET must be rated for these transients (use a MOSFET rated ≥ 60V, with a TVS diode across drain-source for additional protection).
Reverse polarity protection: Add a P-channel MOSFET or Schottky diode in series with the supply to prevent damage if the battery is connected backwards.
EMI in automotive: Switching noise couples into AM/FM radio. Use f_PWM that avoids AM radio band (530kHz–1700kHz): either stay below 100kHz or use spread-spectrum dithering.
Component selection:
- MOSFET: IRF540N (100V, 33A — handles automotive transients with TVS diode)
- TVS diode: P6KE30A (30V clamp, from drain to source)
- Gate driver: TC4420 (6A peak, ensures fast switching even at lower temperatures)
- PWM controller: Arduino or dedicated auto-grade MCU
Soft-start implementation:
// On power-up, ramp brightness from 0 to target over 500ms
void softStart(uint8_t target_brightness) {
for (int i = 0; i <= target_brightness; i++) {
setLED(i); // gamma-corrected output
delay(500 / target_brightness); // ~2ms per step for 255 steps
}
}PWM frequency for automotive: 20kHz (above hearing, below AM radio band, well within MOSFET capability at 12V).
Common Problems and Solutions
Problem: LED flickers visibly at low brightness settings
At low duty cycles (< 5%), the brief ON pulses may be visible as individual flashes even above 200Hz because the human eye is more sensitive to brief bright flashes than to continuous dim light. Solutions: increase f_PWM to 1kHz or higher; use a higher-resolution PWM (reduce minimum step size); apply gamma correction so the minimum non-zero PWM value is still reasonably low (use 16-bit PWM to allow very fine steps at low brightness).
Problem: LED changes color when dimmed
Either the system is performing analog dimming (varying current) rather than true PWM dimming — verify the circuit is switching fully ON/OFF rather than partially on. Or the LED driver’s analog dim input is being used — switch to the PWM/DIM enable input for true PWM behavior.
Problem: Camera shows flickering bands in video footage
PWM frequency is close to but not an exact multiple of the camera’s frame rate. Increase f_PWM to ≥ 10kHz to make the effect imperceptible, or set f_PWM to an exact integer multiple of the frame rate.
Problem: High-pitched whine from the LED driver or inductors
The PWM frequency is below 20kHz. The inductors or driver components are vibrating acoustically. Increase f_PWM to above 20kHz. Alternatively, use a constant-current driver operating at a fixed high internal switching frequency with only the enable/disable input at the lower frequency.
Problem: MOSFET runs hot during PWM operation
Two possible causes: (1) Switching losses — PWM frequency too high for the gate drive circuitry, leaving the MOSFET partially on during transitions. Add a gate driver IC (TC4420) or reduce f_PWM. (2) Insufficient gate drive — MOSFET not fully turning on. Check V_GS reaches the specified gate voltage for the chosen MOSFET (use logic-level types for 3.3V/5V MCU drive).
Problem: MCU resets when LED suddenly turns on at full brightness
The LED inrush current causes a voltage droop on the MCU supply. Add a large decoupling capacitor (470µF or more) close to the LED supply rails, and separate the LED power from the MCU power supply with a small series resistor (10Ω) and local MCU bypass capacitor.
Summary
PWM LED dimming operates LEDs at full rated current for a fraction of each cycle, with the fraction (duty cycle) determining perceived brightness. This approach maintains consistent color temperature, spectrum, and efficiency regardless of brightness level — unlike analog dimming which shifts LED color as current decreases.
Flicker is the primary design challenge. PWM above 200Hz is imperceptible to most people; above 1kHz it is safe for all applications including sensitive individuals; above 10kHz it is completely camera-compatible. The IEEE 1789-2015 standard recommends above 1250Hz for all general lighting. Stroboscopic effects in industrial environments with rotating machinery require frequencies above 1kHz.
Perceived brightness perception is logarithmic, requiring gamma correction (typically γ = 2.2) to map control inputs to perceptually uniform brightness steps. Without correction, dimming feels coarse at low levels and insensitive at high levels.
Implementation scales from direct GPIO drive (< 20mA indicator LEDs), through NPN transistor buffers (up to 500mA), N-channel MOSFETs (up to 20A), to dedicated constant-current LED driver ICs with PWM enable inputs (the correct solution for high-power LEDs where color accuracy matters). Microcontroller PWM defaults are often too low — Arduino’s 490Hz causes audible noise and potential flicker; modifying the timer prescaler to achieve 15–31kHz is almost always necessary.








