LEDC (LED Controller) is a dedicated hardware peripheral on microcontrollers like the ESP32 that generates independent, high-resolution Pulse Width Modulation (PWM) signals without requiring continuous CPU intervention. If you are asking "what is LEDC" while building a dimmable lighting rig, a camera-stabilized gimbal, or a motor drive with an ESP32-WROOM-32, you are looking at the silicon block that handles the rapid on/off switching so your main processor can focus on Wi-Fi stacks and application logic.
In a real circuit, implementing LEDC changes your system architecture by offloading timing-critical tasks from the CPU to dedicated hardware timers. This eliminates the microsecond-level jitter and flickering inherent in software-based PWM. Builders commonly confuse the ESP32's LEDC peripheral with software PWM functions (like the AVR analogWrite()), generic off-the-shelf 12V RGB LED strip remotes, or the ESP32's separate MCPWM (Motor Control PWM) module. While MCPWM is optimized for complex motor commutation and fault-handling, LEDC is purpose-built for stable, multi-channel duty-cycle control.
ledcSetup() and ledcAttachPin() functions. The modern, simplified API uses ledcAttach(pin, freq, resolution) and ledcWrite(pin, duty). Always check your core version before compiling.
The Core Concept: Hardware Timers vs. CPU Interrupts
To understand why LEDC exists, you have to look at how software PWM fails at scale. Software PWM relies on the CPU firing timer interrupts, flipping a GPIO pin high, waiting, and flipping it low. If your ESP32 gets interrupted by a Wi-Fi packet or a Bluetooth handshake, that GPIO pin stays high a fraction of a millisecond too long, causing visible flicker in LEDs or audible whining in piezo buzzers.
Think of software PWM like a traffic cop manually waving cars through an intersection every few seconds; if the cop gets distracted by a jaywalker, traffic backs up. LEDC is like installing an automated, hardware-timed smart traffic light that runs on its own independent clock, completely oblivious to the chaos around it.
The original ESP32 silicon features 4 independent hardware timers and 8 output channels. Newer variants like the ESP32-S3 and ESP32-C3 scale this up (the S3 has 8 timers and 8 channels). Each timer dictates the frequency and resolution for the channels attached to it. You can route these channels to almost any GPIO pin via the ESP32's internal GPIO matrix, giving you immense flexibility on the breadboard.
Worked Example: Calculating LEDC Frequency and Resolution
The most common stumbling block for embedded developers is the mathematical relationship between the ESP32's clock speed, the desired PWM frequency, and the bit resolution. You cannot arbitrarily maximize both frequency and resolution; they share a fixed bandwidth pool.
The core formula for the ESP32 LEDC hardware divider is:
Divider = Clock_Source / (Target_Frequency × 2^Resolution)
Let's calculate the exact hardware configuration for driving an LED strip at 20 kHz (to avoid camera flicker) with 8-bit resolution (256 dimming steps) using the standard 80 MHz APB clock.
- Clock Source: 80,000,000 Hz (APB_CLK)
- Target Frequency: 20,000 Hz
- Resolution Steps: 2^8 = 256
- Calculation: 80,000,000 / (20,000 × 256) = 80,000,000 / 5,120,000 = 15.625
Because the ESP32's LEDC hardware supports fractional dividers (an 18-bit integer plus an 8-bit fractional value), it can handle this perfectly. The integer part is 15. The fractional part is 0.625, which translates to 160 in the hardware's 256-step fractional register (0.625 × 256 = 160). When you use the Arduino ledcAttach(2, 20000, 8) function, the core library handles this fractional math behind the scenes, but understanding it is critical when you drop down to ESP-IDF and must populate the ledc_timer_config_t struct manually.
Where You Meet LEDC in Practice
You will encounter LEDC configuration whenever you need precise analog-like control from a digital pin. Here is how it maps to real-world components:
| Application | Typical Frequency | Resolution | Why LEDC is Required |
|---|---|---|---|
| Dimmable LED Lighting (PWM) | 5 kHz - 25 kHz | 12-bit (4096) | Prevents low-frequency flicker; ensures smooth fades without CPU jitter. |
| Standard Hobby Servos (SG90) | 50 Hz | 16-bit (65536) | Requires exact 20ms periods; software PWM causes servo twitching and overheating. |
| Piezo Buzzers / Tone Gen | 200 Hz - 4 kHz | 8-bit to 10-bit | Clean square waves; allows 50% duty cycle for loudest acoustic output. |
| DC Motor Speed (via H-Bridge) | 1 kHz - 5 kHz | 8-bit to 10-bit | Avoids audible whining in the motor windings (though MCPWM is preferred for braking). |
For deeper integration with Espressif's native environment, refer to the official ESP-IDF LEDC API documentation. If you are strictly using the Arduino IDE, the Arduino ESP32 LEDC Core reference details the modern wrapper functions.
Common Configuration Mistakes and Edge Cases
Even experienced makers trip over the hardware limitations of the LEDC peripheral. Avoid these bench-tested failure modes:
- Timer Collision: Assigning Channel 0 to Timer 0 at 5 kHz, and Channel 1 to Timer 0 at 1 kHz. The Fix: Channels sharing a timer must share the exact same frequency and resolution. Use a different timer for the second channel.
- Exceeding Duty Limits: Writing a duty value of 300 to an 8-bit channel (max 255). The Fix: The hardware will truncate or wrap the value unpredictably. Always cap your software variables to
(1 << resolution) - 1. - Forgetting the Duty Update: In ESP-IDF, changing the duty cycle in the struct doesn't apply it immediately. The Fix: You must call
ledc_update_duty()to push the shadow register value to the active hardware register. - Deep Sleep Leakage: LEDC pins can float or leak current during ESP32 deep sleep. The Fix: Configure the GPIO as a standard output and pull it low before entering sleep, or use the RTC GPIO hold feature.
Frequently Asked Questions
What is the difference between LEDC and MCPWM on ESP32?
LEDC (LED Controller) is designed for simple, stable duty-cycle generation like dimming lights or generating audio tones. It is easy to configure and uses minimal system resources. MCPWM (Motor Control PWM) is a much more complex peripheral designed specifically for motor drives. MCPWM includes hardware-level dead-time insertion (preventing short circuits in H-bridges), hardware fault-tripping (instantly shutting off pins if an overcurrent pin goes high), and synchronization with external encoders. Use LEDC for LEDs and buzzers; use MCPWM for BLDC motors and high-power DC drives.
Can I use LEDC to drive a servo motor?
Yes, and it is actually the recommended method. Hobby servos require a 50 Hz signal with a pulse width between 1ms and 2ms. By configuring an LEDC timer to 50 Hz with a 16-bit resolution (65536 steps), you get incredibly granular control over the pulse width. A 1.5ms center pulse translates to a duty value of roughly 4915. Software PWM driving a servo often results in "hunting" or twitching because the CPU cannot maintain the exact 20,000-microsecond period consistently.
Why is my LEDC PWM output flickering on camera?
If your LED strip looks fine to the naked eye but flickers violently on a smartphone camera, your LEDC frequency is too low and is beating against the camera's shutter speed. The human eye blends PWM flicker above 80 Hz, but cameras capture discrete frames. To fix this, increase your LEDC frequency to at least 5 kHz to 20 kHz. You will need to lower your bit-resolution to maintain the math within the ESP32's 80 MHz clock limits, but trading 14-bit resolution for 10-bit at 20 kHz will completely eliminate camera banding and flicker.






