Published in


ESP32-IDF — LEDC Get Started

The LED control (LEDC) peripheral — ESP_IDF_Series Episode #04

This post will give you enough understanding of applied C programming language from where you can take yourself to a higher level of expertise.

The ESP32 is a dual-core system with two Harvard Architecture Xtensa LX6 CPUs.

It has 41 Peripherals, such as SPI, I2C, UART, WiFi, BT, among others, but in this post, we will focus only on the LEDC peripheral (or LED_PWM controller).

So we will be dealing with LEDC (LED Controller) fade example code provided by Expressif in its ESP-IDF — PERIPHERALS directory, offered to the public in this Github Repo link:


Here is the official Expressif LED Control API page:


So what is the LED Control Module?

Is a module, among others provided for free by Expressif, built inside of ESP32’s driver/ledc.h library to provide services such as generating pwm signals to control the LED intensity or other purposes, like driving DC motors, valves, pumps, hydraulics, and other mechanical parts, RGB strips control, signal generator, etc.You can read about this from page 300 of ESP32 Technical Reference Manual (13. LED_PWM)
Fig 1. Simplified Block diagram of LED Control Module; To use it follows this sequence: 1 — Times, 2 — Channel and 3 — PWM signal (from Expressif page,)

The LED_PWM controller (LEDC) is primarily designed to control the intensity of LEDs, although it can be used to generate PWM signals for other purposes as well. It has 16 channels that can generate independent waveforms that can be used to drive RGB LED devices.

For maximum flexibility, the high-speed as well as the low-speed channels can be driven from one of four high-speed/low-speed timers. The PWM controller also has the ability to automatically increase or decrease the duty cycle gradually, allowing for fades without any processor interference. To increase resolution, the LED_PWM controller is also able to dither between two values, when a fractional PWM value is configured. (from ESP32 Technical Reference Manual)

The process to use this peripheral is divided into three steps:

1. Configure Timer by specifying the PWM signal’s frequency and duty cycle resolution;2. Configure Channel by associating it with the timer and GPIO to output the PWM signal; 3. Change PWM Signal that drives the output in order to change LED’s intensity. This can be done under the full control of software or with hardware fading functions.

To run it on you win10:

cd to: 
idf.py build
idf.py menuconfig
to run:
idf.py -p COM3 flash
Video 1. How to get started — LEDC Module

Let’s see line-by-line of this code:

Don’t get put off by the size of the code… I’ll make a sort of reverse-engineering to the coding technique applied by Expressif programmers, and analyze it thoroughly.

At first glance, it looks like a boilerplate code, when in fact Expressif offers us a fantastic way of how to code professionally for readability and consistency:)

Let’s get it on!

Of course, at the header, it declares all the libraries needed:

#include <stdio.h>#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_err.h"

As you can see, these 3 groups are for a standard lib for C, libs for FreeRTOS, and the last one, the theme of this post, for LEDC (LED Controller peripheral) using .h file… and error management :)

Just fine!

Now Expressif declares a bunch of defines for CONSTANTS:


#define LEDC_HS_CH0_GPIO (18)
#define LEDC_HS_CH1_GPIO (19)
#endif#define LEDC_LS_TIMER LEDC_TIMER_1
#define LEDC_LS_CH1_GPIO (19)
#endif#define LEDC_LS_CH2_GPIO (4)
#define LEDC_LS_CH3_GPIO (5)
#define LEDC_TEST_CH_NUM (4)
#define LEDC_TEST_DUTY (4000)
#define LEDC_TEST_FADE_TIME (3000)


It is known as a from-to constant; from APP to API CONSTANTS ;)

Most statements #define are related to the declaration of channels. Hold on, you will figure out soon…keep on going…

To declare a channel and match each name with the LEDC API, it is important to set the following variables information per channel:

. channel
. duty
. gpio_num
. mode
. hpoint
. timer_sel

Most of the variables declared above are obvious, unless hpoint;

What follows below is my try to explain these terms:)

But first, let’s prepare and set the configuration of timers that will be used by LED Controller:

01#Step — Configuring the Timer:


ledc_timer_config_t ledc_timer = {        
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 5000,
.speed_mode = LEDC_LS_MODE,
.timer_num = LEDC_LS_TIMER,
.clk_cfg = LEDC_AUTO_CLK,

The ESP32 has 20_bit configurable duty timers resolution.

The frequency and the duty resolution are interdependent. The higher the PWM frequency, the lower duty resolution is available, and vice versa. This relationship might be important if you are planning to use this API for purposes other than changing the intensity of LEDs. For more details, see Section Supported Range of Frequency and Duty Resolutions. (from ESP32 Technical Reference Manual)

The 13-bit duty resolution was chosen ie the counter goes up to 8192 before overflow, 5k frequency, and low-speed mode.

So far, so good!

02#Step — Configuring the Channels:


It is good to know that this peripheral (LEDC) has 2 groups of 8 channels each. 4 high-speed (HS) mode and 4 low-speed (LS) mode channels.

For testing, four channels will be raised: channels CH[0], CH[1], CH[2], and CH[3];



As well as four GPIOs will be initialized: GPIOs 18,19, 4, and 5; Pins 18 and 19 for HS and pins 4 and 5 for LS mode. In each pin, I will attach 4 LEDs 3mm type (2 red for HS and 2 green for LS)

#define LEDC_HS_CH0_GPIO      (18)
#define LEDC_HS_CH1_GPIO (19)
#define LEDC_LS_CH2_GPIO (4)

(*name convention: MODULE_SPEEDMODE_CHX_GPIO)

Photo 1. The LEDC test breadboard:) put a 220 Ohm resistor to GND.

Just fine!

If the target is ESP32 — for this experiment I’ll be using WiFi LoRa 32 (v2) — the GPIOs 18, 19 will be set to HS mode and GPIOs 4 and 5 set to LS mode— please, watch out for the preprocessor directives in C #ifdef and #endif; the difference between them and subtle:

High Speed mode : this mode is implemented in hardware and offers automatic and glitch-free changing of the PWM duty cycle

On the other hand:

Low Speed mode : in this the PWM duty cycle must be changed by the driver in software.
Fig 2. Heltec ESP32 LORA WIFI SX1276 kit

In the end, this will be transparent for the user :/ we will perceive no glitch or effect whatsoever during the channel change in both HS or LS channel…


The duty cycle is the ratio of time a load or circuit is ON compared to the time the load or circuit is OFF.

In other words, it can determine the speed at which a motor is running or the intensity of the theater stage lighting in a more exciting scene :)

To set the duty cycle, use the dedicated function ledc_set_duty(). After that, call ledc_update_duty() to activate the changes. To check the currently set value, use the corresponding _get_ function ledc_get_duty().

Another way to set the duty cycle, as well as some other channel parameters, is by calling ledc_channel_config() covered in Section Configure Channel.

The range of the duty cycle values passed to functions depends on selected duty_resolution and should be from 0 to (2 ** duty_resolution) - 1. For example, if the selected duty resolution is 10, then the duty cycle values can range from 0 to 1023. This provides a resolution of ~0.1%. (from ESP32 Technical Reference Manual)

The maximum available frequency for The LED PWM Controller is 40 MHz with a duty resolution of 1 bit.

For this test the PWM duty cycle will be set to:

#define LEDC_TEST_DUTY         (4000)

See Section Supported Range of Frequency and Duty Resolutions too.

By setting it to 4000, 50 % duty cycle has been established, as the counter timer can go up to approximately 8000 (13-bit duty resolution).

In other words, you now have control of the intensity of the LEDs simply by changing this variable (LEDC_TEST_DUTY). Fantastic!


The GPIO18/19 are from the high-speed channel group.

The GPIO4/5 are from low-speed channel group.


The GPIO18/19 are set to LEDC high-speed speed_mode.

The GPIO4/5 are set to LEDC low-speed speed_mode,


The code set all channels’s hpoint to zero, which means that the output will be latched high at the very beginning of the counter (13-bit counts from 0 to 8,192).

.hpoint  =  0

Please, read this extract of the ESP32 Technical Reference Manual to understand what is hpoint and lpoint :)

Fig 1. Pg 301 — ESP32 Technical Reference Manual. hpoint is the interruption to latch the output high.


timer_sel: Timer index (0-3), there are 4 timers in LEDC module.

Now the configuration of the channel.


The code use struct (struct serves to collect correlated variables).

A struct in the C programming language is a composite data type declaration that defines a physically grouped list of variables under one name in a block of memory, allowing the different variables to be accessed via a single pointer or by the struct declared name which returns the same address. (from Wikipedia)

For a warm-up, here are 4 projects linked below that play with struct using C:

Project 16 - Struct - How to Create Struct in C
Project 17 - 3 ways to declare a struct in C
Project 18 - Chained Struct - How To Declared Nested Struct
Project 19 - How to Align Structs

There are 3 ways to initialize a struct:

First, declare it:

typedef struct Serial{ 
unsigned char ComPort;
unsigned int BaudRate;

Then initialize it by one of these three methods:

Three ways to declare structs:
Serial_t Uart;
Uart.ComPort = 1;
Uart.BaudRate = 9600;

Serial_t Uart = {2, 57600};

Serial_t Uart = {
.ComPort = 3,
.BaudRate = 115200

As you see in the Expressif code the method preferred is the last method (which access members via a single pointer) because it’s cleaner and clearer.



It stores all the necessary and correlated variables for each of the channels.

This is the LEDC_CHANNEL_0 configuration example:

ledc_channel_config_t ledc_channel[0] = {
.channel = LEDC_HS_CH0_CHANNEL,
.duty = 0,
.gpio_num = LEDC_HS_CH0_GPIO,
.speed_mode = LEDC_HS_MODE,
.hpoint = 0,
.timer_sel = LEDC_HS_TIMER

The other channels follow the same pattern.

The code analyzed declares a multi-dimensional array of arrays, like this, {{},{},{},{}, {}, {}}and enumerate it; then it select the high and low channel in the preprocessor directives and call each pointer to the channel (remember, LEDC_TEST_CH_NUM=4). Then, passes to the method ledc_channel_config() the settings of each channel struct:

for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {           

It passes the pointer of the struct address of each channel configured above to the function(pass by reference):

esp_err_t ledc_channel_config(
const ledc_channel_config_t *ledc_conf)
LEDC channel configuration Configure LEDC channel with the given:

. channel/
. LEDC duty resolution/
. output gpio_num/
. frequency(Hz)/
. interrupt/
. source timer/

03#Step — Change PWM Signal:


It does four loops almost simultaneously; one for each of the effects it prints on the LED attached to the GPIOs 18,19,4, 5.

The end result is the synchronization of the four LEDs; first, turn on and off them quickly; then ramp them up; then ramp them down the brightness of the LEDs.

The implementation loops through the structure of each channel (ledc_channel[LEDC_TEST_CH_NUM]) and retrieve each of the variables requested by the method called.

The first two loops are for HS mode; it is implemented in hardware and offers automatic and glitch-free changing of the PWM:

esp_err_t ledc_set_fade_with_time
(ledc_mode_t speed_mode,
ledc_channel_t channel,
uint32_t target_duty,
int max_fade_time_ms)
Set LEDC fade function, with a limited time.


esp_err_t ledc_fade_start
(ledc_mode_t speed_mode,
ledc_channel_t channel,
ledc_fade_mode_t fade_mode)
Start LEDC fading.

The last two methods are for LS mode— the PWM duty cycle must be changed by the driver in software:

esp_err_t ledc_set_duty
(ledc_mode_t speed_mode,
ledc_channel_t channel,
uint32_t duty)
LEDC set duty This function do not change the hpoint value of this channel. if needed, please call ledc_set_duty_with_hpoint. only after calling ledc_update_duty will the duty update.


esp_err_t ledc_update_duty
(ledc_mode_t speed_mode,
ledc_channel_t channel)
LEDC update channel parameters.

And that is all!

Hopefully, this post has helped shed some light on why ESP32-IDF stand out from the crowd when it comes to IoT solution.

Please, send feedback if you wish; we are all ESP32!

Please follow my studies in this series about this impressive framework — ESP-IDF. A lot of efforts have been made to translate it for hobbyist…it really is worth the effort though 😜

In the next ESP_IDF_Series Episode let’s study about another module: PCNT_MODULE!

See you there!

Bye, for now, o/

Credits & References

ESP32 Technical Reference Manual by wiki/esp32

Related Posts

00#Episode — ESP_IDF_Series — ESP-IDF Programming Guide + WiFi LoRa 32 (v2) — How To Get Started

01#Episode — ESP_IDF_Series — LoRa LPWAN — Long Range Low Power Wide Area Network — LoRa was developed by Semtech, founder LoRa Alliance

02#Episode — ESP_IDF_Series — ESP-IDF Programming Guide + WiFi LoRa 32 (v2) + idf.py utility — How To Really Get Started

03#Episode — ESP_IDF_Series — ESP32-IDF — HelloWorld Get Started — How To Get Started

04#Episode — ESP_IDF_Series — ESP32-IDF — LEDC Get Started — The LED control (LEDC) peripheral (this one:)

Video 2. Soldering Heltec ESP32 LORA WIFI SX1276 kit




J of Jungle + 3 Plats Arduino/RPi/Pic = J3

Recommended from Medium

Working with MinIO

Introducing the Discoverable Construction Knowledge schema

The sources of knowledge a practitioner from; personal and company resources, employer’s and personal memberships and the web

Java is too old, What should you learn in 2018?

Building OTP Entry Stepper For Xamarin Forms

iFrame to iFrame — Virtuoso QA Blog

Questions on MongoDB database - A leading NOSQL

Establishment of Defibox Development Fund

How to Reboot Windows 10 in Safe Mode

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


Hi, Guys o/ I am J3! I am just a hobby-dev, playing around with Python, Django, Lego, Arduino, Raspy, PIC, AI… Welcome! Join us!

More from Medium

Testing ESP32 Web Server Using BMP280

Hackthebox — Meta Walkthrough

Python Celery. 🚀