ESP32 LED PWM Controller Example and Sample Code

About ESP32 LED PWM Controller

The ESP32 LED PWM controller is a generic PWM controller module that you would find in almost any other complex microcontroller such as in most ARM7 SoCs. Here are some features of the LED PWM controller that should attract your attention and are relevant from an application point of view:

  • 16 channels (8 high speed and 8 low speed)
  • 8 independent timer units (4 high speed and 4 low speed)
  • Up to 24-bit effective resolution
  • Automatic smooth transitions
  • Relative phase and duty cycle control for each channel
  • Interrupt generation

Operation Basics

So how does the ESP32 LED PWM controller actually work? 16 channels and 8 timers is a lot of hardware and may seem complex to understand from the datasheet!

On a conceptual level, here is how the LED PWM controller works:

  • The "high speed" section and "low speed" sections are completely independent. It is important to know that the operations cannot be intermixed.
  • The high speed section consists of 8 output channels and 4 timers.
  • The low speed section consists of 8 channels and 4 timers too.
  • Each "channel" actually consists of comparators that compare the timer count to 2 registers - the "start value" and the "duty". The output of the channel is high beginning from when timer count is at "start value" and remains high for "duty" number of timer increments. The same applies for low speed channels.
  • You can program which "channel" receives clock from which "timer", i.e. a particular high speed channel can be attached to any one of the 4 high speed timers. And any of the 8 low speed channels can be attached with any one of the 4 low speed timers.
  • "Timers", both high speed and low speed ones, are simply timers that run from APB clock or some other clock and count upto 2^n, where n is timer bit width (may be up to 20 bits).

Detailed Operation

The block diagram of the ESP32 LED PWM controller is shown here. In this block diagram, a high speed channel has been shown to be attached with a high speed timer:

esp32-led-pwm-block-diagram

This is how the setup will work:

  • Attach a channel to a timer. In this diagram, a high speed channel has been attached to a certain high speed timer module. Next the timer is configured...
  • REF_TICK or APB_CLK is chosen as the timer clock source.
  • The selected clock is divided by an 18-bit divider (integral + fractional divider)
  • This out clock from the divider block increments the count of the 20-bit counter to generate the ref_pulse.

For the channel configuration:

  • This ref_pulse can be attached to any number of channels. Suppose it is attached to one channel as shown above, the comparators use this count value to generate PWM.
  • When input counter value = LEDC_HPOINT parameter latches the channel output high.... and the output stays for LED_DUTY timer counts.
  • The output should be routed properly through the ESP32 GPIO matrix to a physical pad.
  • The PWM signal is now available at the pad!

The PWM module supports many more complex functions such as PWM duty dithering and smooth transitions that is especially helpful for lighting oriented or visual effects applications. However, it will be covered in other articles.

Example ESP32 LED PWM Program


void pwm_task (void *pvParameter)
{
// Enable LED PWM peripheral
SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_LEDC_CLK_EN);
CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_LEDC_RST);


WRITE_PERI_REG (LEDC_HSTIMER0_CONF_REG, (1<<25)|(0b000000001001010100<<5)|4); // Div: 0b0000000010 01010100
WRITE_PERI_REG (LEDC_HSCH2_CONF0_REG, (1<<3)|(1<<2));
WRITE_PERI_REG (LEDC_HSCH2_HPOINT_REG, 10);
WRITE_PERI_REG (LEDC_HSCH2_DUTY_REG, (10<<4));
WRITE_PERI_REG (LEDC_HSCH2_CONF1_REG, (1<<31));


WRITE_PERI_REG (GPIO_ENABLE_REG, (1<<5));
WRITE_PERI_REG (GPIO_FUNC5_OUT_SEL_CFG_REG, (1<<10)|73);
WRITE_PERI_REG (PERIPHS_IO_MUX_GPIO5_U, (3<<10)|(1<<8));
printf("pwm_out\n");

//val = READ_PERI_REG (LEDC_HSCH2_DUTY_R_REG);
//printf("%d\n", val);
//val = READ_PERI_REG (LEDC_HSTIMER0_CNT);
//printf("%d\n", val);
while(1);
}

void app_main()
{
nvs_flash_init();
system_init();
xTaskCreate(&pwm_task, "pwm_task", 2048, NULL, 6, NULL);
}

Detailed explanation of code and ready-to-compile ZIP file of the project coming up in the next article!
Scroll down to the bottom of the page and SUBSCRIBE to get new articles right into your inbox!