ESP32 LEDC library with ESP-IDF C++

Create a basic ESP32 LEDC library using C++ with the ESP-IDF in VS Code

The ESP-IDF contains a driver to control LEDs using Pulse Width Modulation (PWM). PWM works by varying the duty cycle (Pulse Width) of a square wave. In simple terms, PWM turns a signal on and off very quickly.

The duty cycle determines how long the signal is on or off. A 70% duty cycle means that the signal is on 70% of the period and 30% off which translates into roughly 70% brightness for an LED.

The LEDC PWM module is primarily designed to use with LEDs, however, it can also be used for other applications. The ESP also has a dedicated PWM driver to control motors, so I would recommend using the LEDC driver for motor control. For more information on PWM, check out this tutorial from Sparkfun.

In this tutorial, we are going to create an initial wrapper class for the LEDC channel and LEDC timer to:

  • Set up the timer(s) for the LEDC module to use
  • Set up the LEDC Channel to output the LEDC PWM signal on
  • Create methods to change any of the properties of both the timer setup and LEDC channel setup

The LEDC driver has too many features to cover in only one tutorial. This tutorial will not cover the fading, interrupt, or more advanced timer functionality. This will be covered in future tutorials.

Basic ESP32 LEDC C++ library

Create a new project

The easiest way to create a new project in VS Code for the ESP-IDF is to open the command palette by pressing ctrl+shift+P and typing ESP-IDF: Show Examples Projects and then selecting Sample Project from the list.

This will create all of the configuration files for your setup.

When the project has been created we need to close the project by clicking on File and then Close Folder.

Now navigate to where the project has been created and rename the folder named sample_project to CPPLEDC. Be careful not to add spaces in the folder name.

We are now going to restructure the folder a bit and create some new files. For more information on creating a project for C++ please see this tutorial: ESP-IDF C++ with CMake for ESP32.

Change the folder structure and add files and folders so that the project looks like the following:

Project Structure

I’m only showing the folder that we created. There will be several more that the Sample Project template has created.

This will be another library to be reusable in future projects that will make use of the LEDC so the project will only have the include and src folders.

The CmakeLists.txt files are also going to change when we integrate the libraries into other projects, however for now we are going to keep it like this to develop and test it as stand-alone projects.

CPPLEDC Folder Structure

Configure CMake

Change the content of CMakeLists.txt in the project root to the following:

# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.8)
set(CMAKE_CXX_STANDARD 17)
set(EXTRA_COMPONENT_DIRS src include)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(CPPLEDC)

Now change the content of CmakeLists.txt in the src folder to the following:

set(SOURCES main.cpp
            CPPLEDC/cppledc.cpp
            CPPLEDC/cppledctimer.cpp)
            
idf_component_register(SRCS ${SOURCES}
                    INCLUDE_DIRS .  ../include/CPPLEDC)

This concludes the Cmake configuration for this project. The project is not configured to use C++ 17 and all the header files will be found and source files will resolve.

Define the LEDC and Timer classes in the header

We are going to define 2 classes for the LEDC driver:

  1. Timer: This class is named CppLedcTimer. There are 4 timers available. In this implementation, we need to create an object and set up each timer that we intend to use.
  2. Channel: This class is named CppLedc There are 2 groups of 8 channels each available on the ESP32, making a total of 16 available LEDC channels. One group of LED channels can operate in high-speed mode. We will create an object of this class for every LEDC output pin that we will use.

Open up cppledc.h in the include\CPPLEDC folder and add the following code:

#pragma once

#include "driver/ledc.h"

namespace CPPLEDC
{
    class CppLedcTimer
    {
    private:
        ledc_timer_config_t _timer_config;
        void _set_defaults();

    public:
        CppLedcTimer(void);

        esp_err_t InitializeDefaults(void);
        esp_err_t SetTimerSpeedMode(ledc_mode_t speed_mode);
        esp_err_t SetTimerDutyResolution(ledc_timer_bit_t duty_resolution);
        esp_err_t SetTimerSource(ledc_timer_t timer_num);
        esp_err_t SetTimerFrequency(uint32_t freq_hz);
        esp_err_t SetTimerClockSource(ledc_clk_cfg_t clk_cfg);
        esp_err_t SetTimerAll(ledc_mode_t speed_mode,
                              ledc_timer_bit_t duty_resolution,
                              ledc_timer_t timer_num,
                              uint32_t freq_hz,
                              ledc_clk_cfg_t clk_cfg);

    }; // Class CppLedTimer

    class CppLedc
    {
    private:
        ledc_channel_config_t _channel_config;
        void _set_defaults();

    public:
        CppLedc(void);
        CppLedc(int gpioNum);
        CppLedc(int gpioNum, ledc_timer_t timerNum);

        uint32_t GetChannelDutyCycle(void);
        esp_err_t SetChannelGpio(int gpio_num);
        esp_err_t SetChannelSpeedMode(ledc_mode_t speed_mode);
        esp_err_t SetChannel(ledc_channel_t channel);
        esp_err_t SetChannelInterruptEn(ledc_intr_type_t intr_type);
        esp_err_t SetChannelTimerSource(ledc_timer_t timer_num);
        esp_err_t SetChannelDutyCycle(uint32_t duty);
        esp_err_t SetChannelHpoint(int hpoint);
        esp_err_t SetChannelAll(int gpio_num,
                                ledc_mode_t speed_mode,
                                ledc_channel_t channel,
                                ledc_intr_type_t intr_type,
                                ledc_timer_t timer_num,
                                uint32_t duty,
                                int hpoint);

    }; // CppLedc class
} // CPPLEDC Class

Let’s take a closer look at the parts:

#pragma once

#include "driver/ledc.h"

This is the guard to ensure that a header is not included twice and the include for the LEDC driver.

CppLedcTimer Class Definition

Let’s take a look at the CppLedcTimer Class:

    private:
        ledc_timer_config_t _timer_config;
        void _set_defaults();

The only private members are a struct variable to hold the timer configuration and a method to populate the timer configuration struct with default values. Adding the default values saves time if you don’t need specific settings.

public:
        CppLedcTimer(void);

        esp_err_t InitializeDefaults(void);
        esp_err_t SetTimerSpeedMode(ledc_mode_t speed_mode);
        esp_err_t SetTimerDutyResolution(ledc_timer_bit_t duty_resolution);
        esp_err_t SetTimerSource(ledc_timer_t timer_num);
        esp_err_t SetTimerFrequency(uint32_t freq_hz);
        esp_err_t SetTimerClockSource(ledc_clk_cfg_t clk_cfg);
        esp_err_t SetTimerAll(ledc_mode_t speed_mode,
                              ledc_timer_bit_t duty_resolution,
                              ledc_timer_t timer_num,
                              uint32_t freq_hz,
                              ledc_clk_cfg_t clk_cfg);

    }; // Class CppLedTimer

The public members are mostly made up of methods to configure the timer. We will look into a bit more detail when we implement the methods.

CppLedc Class Definition

Let’s take a look at the CppLedc Class:

    private:
        ledc_channel_config_t _channel_config;
        void _set_defaults();

As with the timer class, we only have 2 private members. A struct variable to hold the channel configuration and a method to assign default values to the struct variable.

    public:
        CppLedc(void);
        // Construct with defaults.
        CppLedc(int gpioNum);

        uint32_t GetChannelDutyCycle(void);
        esp_err_t SetChannelGpio(int gpio_num);
        esp_err_t SetChannelSpeedMode(ledc_mode_t speed_mode);
        esp_err_t SetChannel(ledc_channel_t channel);
        esp_err_t SetChannelInterruptEn(ledc_intr_type_t intr_type);
        esp_err_t SetChannelTimerSource(ledc_timer_t timer_num);
        esp_err_t SetChannelDutyCycle(uint32_t duty);
        esp_err_t SetChannelHpoint(int hpoint);
        esp_err_t SetChannelAll(int gpio_num,
                                ledc_mode_t speed_mode,
                                ledc_channel_t channel,
                                ledc_intr_type_t intr_type,
                                ledc_timer_t timer_num,
                                uint32_t duty,
                                int hpoint);

Again, the public members are mostly made of methods to set up the LEDC Channel. The method to note here is SetChannelDutyCycle(uint32_t duty). This is the method that will change the duty cycle of the PWM and is likely the method that will be used most often.

We will look into a bit more detail when we implement the methods.

Implement the LEDC and Timer Class methods

Keeping to a convention, we are going to create a separate cpp source file to implement each class. The file names match the class names with the file names all being lowercase.

CppLedcTimer Class Implementation

If you have not yet created cppledctimer.cpp in the src folder then do so now and add the following code into the file:

#include "cppledc.h"

namespace CPPLEDC
{
    void CppLedcTimer::_set_defaults()
    {
        _timer_config.speed_mode = LEDC_HIGH_SPEED_MODE;
        _timer_config.duty_resolution = LEDC_TIMER_12_BIT;
        _timer_config.timer_num = LEDC_TIMER_0;
        _timer_config.freq_hz = 5000;
        _timer_config.clk_cfg = LEDC_USE_APB_CLK;
    }

    CppLedcTimer::CppLedcTimer(void)
    {
        _set_defaults();
    }

    esp_err_t CppLedcTimer::InitializeDefaults(void)
    {
        _set_defaults();
        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerSpeedMode(ledc_mode_t speed_mode)
    {
        _timer_config.speed_mode = speed_mode;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerDutyResolution(ledc_timer_bit_t duty_resolution)
    {
        _timer_config.duty_resolution = duty_resolution;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerSource(ledc_timer_t timer_num)
    {
        _timer_config.timer_num = timer_num;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerFrequency(uint32_t freq_hz)
    {
        _timer_config.freq_hz = freq_hz;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerClockSource(ledc_clk_cfg_t clk_cfg)
    {
        _timer_config.clk_cfg = clk_cfg;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerAll(ledc_mode_t speed_mode,
                                        ledc_timer_bit_t duty_resolution,
                                        ledc_timer_t timer_num,
                                        uint32_t freq_hz,
                                        ledc_clk_cfg_t clk_cfg)
    {
        _timer_config.speed_mode = speed_mode;
        _timer_config.duty_resolution = duty_resolution;
        _timer_config.timer_num = timer_num;
        _timer_config.freq_hz = freq_hz;
        _timer_config.clk_cfg = clk_cfg;

        return ledc_timer_config(&_timer_config);
    }
} // CPPLEDC

Let’s take a closer look at the methods:

    void CppLedcTimer::_set_defaults()
    {
        _timer_config.speed_mode = LEDC_HIGH_SPEED_MODE;
        _timer_config.duty_resolution = LEDC_TIMER_12_BIT;
        _timer_config.timer_num = LEDC_TIMER_0;
        _timer_config.freq_hz = 5000;
        _timer_config.clk_cfg = LEDC_USE_APB_CLK;
    }

This method populates the timer configuration struct with default values:

speed_modeLEDC_HIGH_SPEED_MODEOne of the 2 groups of channels can operate in high-speed mode
duty_resolutionLEDC_TIMER_12_BITThe duty resolution defines the number of bits you have to specify for the PWM duty cycle. 12 bits give us 4096 steps.
timer_numLEDC_TIMER_0There are 4 available timers.
freq_hz5000Set the frequency to 5kHz. The maximum frequency with a bit resolution of 12 is 10kHz.
clk_cfgLEDC_USE_APB_CLKThere are 3 available sources for the timer.
Timer source options
Clock nameClock freqSpeed modeClock capabilities
APB_CLK80 MHzHigh / Low/
REF_TICK1 MHzHigh / LowDynamic Frequency Scaling compatible
RTC8M_CLK~8 MHzLowDynamic Frequency Scaling compatible, Light sleep compatible
Source: ESP-IDF Programming Guide

    CppLedcTimer::CppLedcTimer(void)
    {
        _set_defaults();
    }

The constructor populates the timer configuration struct without calling the API function to configure the channel. If the timer configuration variable does not have known values then we could run into undefined behavior if we call a method that sets only one of the individual timer configuration settings.

    esp_err_t CppLedcTimer::InitializeDefaults(void)
    {
        _set_defaults();
        return ledc_timer_config(&_timer_config);
    }

This method configures the timer using default values. One could be tempted to not call the _set_defaults() method since the default is set in the constructor, however, you might want to return the timer to default values after some settings have already been changed.

    esp_err_t CppLedcTimer::SetTimerSpeedMode(ledc_mode_t speed_mode)
    {
        _timer_config.speed_mode = speed_mode;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerDutyResolution(ledc_timer_bit_t duty_resolution)
    {
        _timer_config.duty_resolution = duty_resolution;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerSource(ledc_timer_t timer_num)
    {
        _timer_config.timer_num = timer_num;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerFrequency(uint32_t freq_hz)
    {
        _timer_config.freq_hz = freq_hz;

        return ledc_timer_config(&_timer_config);
    }

    esp_err_t CppLedcTimer::SetTimerClockSource(ledc_clk_cfg_t clk_cfg)
    {
        _timer_config.clk_cfg = clk_cfg;

        return ledc_timer_config(&_timer_config);
    }

These are the methods to set the individual settings for the timer. We are updating the timer by calling the ledc_timer_config(&_timer_config) API function in each method. This is not the most efficient approach, however, it does allow for fewer calls when using this library. For a more efficient way of doing this, we can remove the ledc_timer_config(&_timer_config) API calls from the methods and create another class method to call ledc_timer_config(&_timer_config) after all of the timer configurations have been customized.

    esp_err_t CppLedcTimer::SetTimerAll(ledc_mode_t speed_mode,
                                        ledc_timer_bit_t duty_resolution,
                                        ledc_timer_t timer_num,
                                        uint32_t freq_hz,
                                        ledc_clk_cfg_t clk_cfg)
    {
        _timer_config.speed_mode = speed_mode;
        _timer_config.duty_resolution = duty_resolution;
        _timer_config.timer_num = timer_num;
        _timer_config.freq_hz = freq_hz;
        _timer_config.clk_cfg = clk_cfg;

        return ledc_timer_config(&_timer_config);
    }

This method accepts all available timer configurations enabling the user to completely configure the timer with custom settings in one call.

CppLedc Class Implementation

If you have not yet created cppledc.cpp in the src folder then do so now and add the following code into the file:

#include "cppledc.h"

namespace CPPLEDC
{
    void CppLedc::_set_defaults()
    {
        _channel_config.gpio_num = -1;
        _channel_config.speed_mode = LEDC_HIGH_SPEED_MODE;
        _channel_config.channel = LEDC_CHANNEL_0;
        _channel_config.intr_type = LEDC_INTR_DISABLE;
        _channel_config.timer_sel = LEDC_TIMER_0;
        _channel_config.duty = 0;
        _channel_config.hpoint = 0;
    }

    CppLedc::CppLedc(void)
    {
        _set_defaults();
    }

    CppLedc::CppLedc(int gpioNum)
    {
        _set_defaults();
        _channel_config.gpio_num = gpioNum;

        ledc_channel_config(&_channel_config);
    }

    CppLedc::CppLedc(int gpioNum, ledc_timer_t timerNum)
    {
        _set_defaults();
        _channel_config.gpio_num = gpioNum;
        _channel_config.timer_sel = timerNum;

        ledc_channel_config(&_channel_config);
    }

    uint32_t CppLedc::GetChannelDutyCycle(void)
    {
        return _channel_config.duty;
    }

    esp_err_t CppLedc::SetChannelGpio(int gpio_num)
    {
        _channel_config.gpio_num = gpio_num;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannelSpeedMode(ledc_mode_t speed_mode)
    {
        _channel_config.speed_mode = speed_mode;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannel(ledc_channel_t channel)
    {
        _channel_config.channel = channel;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannelInterruptEn(ledc_intr_type_t intr_type)
    {
        _channel_config.intr_type = intr_type;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannelTimerSource(ledc_timer_t timer_num)
    {
        _channel_config.timer_sel = timer_num;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannelDutyCycle(uint32_t duty)
    {
        esp_err_t status = ESP_OK;

        _channel_config.duty = duty;

        status |= ledc_set_duty(_channel_config.speed_mode,
                                _channel_config.channel,
                                _channel_config.duty);

        status |= ledc_update_duty(_channel_config.speed_mode,
                                   _channel_config.channel);

        return status;
    }

    esp_err_t CppLedc::SetChannelHpoint(int hpoint)
    {
        _channel_config.hpoint = hpoint;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannelAll(int gpio_num,
                                     ledc_mode_t speed_mode,
                                     ledc_channel_t channel,
                                     ledc_intr_type_t intr_type,
                                     ledc_timer_t timer_num,
                                     uint32_t duty,
                                     int hpoint)
    {
        _channel_config.gpio_num = gpio_num;
        _channel_config.speed_mode = speed_mode;
        _channel_config.channel = channel;
        _channel_config.intr_type = intr_type;
        _channel_config.timer_sel = timer_num;
        _channel_config.duty = duty;
        _channel_config.hpoint = hpoint;

        return ledc_channel_config(&_channel_config);
    }
} // CPPLEDC

Let’s take a closer look at the methods:

    void CppLedc::_set_defaults()
    {
        _channel_config.gpio_num = -1;
        _channel_config.speed_mode = LEDC_HIGH_SPEED_MODE;
        _channel_config.channel = LEDC_CHANNEL_0;
        _channel_config.intr_type = LEDC_INTR_DISABLE;
        _channel_config.timer_sel = LEDC_TIMER_0;
        _channel_config.duty = 0;
        _channel_config.hpoint = 0;
    }
gpio_num-1Select the pin for the LEDC PWM signal output. We set the default output pin to an invalid value so that the ESP will give us an error if we forget to set the pin. This will prevent potential damage to the ESP32 or connected devices.
speed_modeLEDC_HIGH_SPEED_MODEOnly one of the 2 groups of 8 channels may be set to high-speed mode
channelLEDC_CHANNEL_0Select which channel to use. Available options are channels 0 to 7 for a total of 8 available channels.
intr_typeLEDC_INTR_DISABLEInterrupts can be enabled for a fade-end event. We are not implementing fade in this tutorial so we disable the interrupt.
timer_selLEDC_TIMER_0Select which timer we are going to use for this channel. The timer must be set up by creating an object of the CppLedcTimer class before it can be used by a channel.
duty0Set the initial duty cycle. The bit resolution for the duty cycle is defined by the timer that the channel will use
hpoint0We will not use hpoint in this tutorial
    CppLedc::CppLedc(void)
    {
        _set_defaults();
    }

    CppLedc::CppLedc(int gpioNum)
    {
        _set_defaults();
        _channel_config.gpio_num = gpioNum;

        ledc_channel_config(&_channel_config);
    }

    CppLedc::CppLedc(int gpioNum, ledc_timer_t timerNum)
    {
        _set_defaults();
        _channel_config.gpio_num = gpioNum;
        _channel_config.timer_sel = timerNum;

        ledc_channel_config(&_channel_config);
    }

We have 2 constructors for the class:

  • CppLedc(void) does not accept any arguments. Because the output has not been defined yet we cannot call ledc_channel_config(&_channel_config) yet. We assign default values to the channel configuration to enable us to configure individual channel configurations.
  • CppLedc(int gpioNum) sets the channel configuration to default values, set the output pin and calls ledc_channel_config(&_channel_config). After this, the LEDC PWM duty cycle can be used.
  • CppLedc::CppLedc(int gpioNum, ledc_timer_t timerNum) will be used when timer 0 is not used, is used for something else, or is not available.
    uint32_t CppLedc::GetChannelDutyCycle(void)
    {
        return _channel_config.duty;
    }

This method returns the current raw duty cycle. To get a percentage you can divide this number by the maximum value of the duty resolution and multiply it by 100.

We cannot create a method for this because we do not have access to the duty resolution variable in the CppLedcTimer class. We can pass the duty resolution into the class as a variable if there is a need for such a method.

    esp_err_t CppLedc::SetChannelGpio(int gpio_num)
    {
        _channel_config.gpio_num = gpio_num;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannelSpeedMode(ledc_mode_t speed_mode)
    {
        _channel_config.speed_mode = speed_mode;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannel(ledc_channel_t channel)
    {
        _channel_config.channel = channel;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannelInterruptEn(ledc_intr_type_t intr_type)
    {
        _channel_config.intr_type = intr_type;

        return ledc_channel_config(&_channel_config);
    }

    esp_err_t CppLedc::SetChannelTimerSource(ledc_timer_t timer_num)
    {
        _channel_config.timer_sel = timer_num;

        return ledc_channel_config(&_channel_config);
    }

These methods set various individual configurations for the LEDC channel.

    esp_err_t CppLedc::SetChannelDutyCycle(uint32_t duty)
    {
        esp_err_t status = ESP_OK;

        _channel_config.duty = duty;

        status |= ledc_set_duty(_channel_config.speed_mode,
                                _channel_config.channel,
                                _channel_config.duty);

        status |= ledc_update_duty(_channel_config.speed_mode,
                                   _channel_config.channel);

        return status;
    }

This is the method that makes the magic happen. Using this method we can increase or decrease the duty cycle f the LEDC PWM signal to anything within the range of the duty resolution setup in the timer. For example, we can set an LED to any brightness that we choose. If we create 3 channels then we can connect an RGB LED to the 3 LEDC channels and set the LED to any color.

    esp_err_t CppLedc::SetChannelHpoint(int hpoint)
    {
        _channel_config.hpoint = hpoint;

        return ledc_channel_config(&_channel_config);
    }

This method sets the hpoint.

    esp_err_t CppLedc::SetChannelAll(int gpio_num,
                                     ledc_mode_t speed_mode,
                                     ledc_channel_t channel,
                                     ledc_intr_type_t intr_type,
                                     ledc_timer_t timer_num,
                                     uint32_t duty,
                                     int hpoint)
    {
        _channel_config.gpio_num = gpio_num;
        _channel_config.speed_mode = speed_mode;
        _channel_config.channel = channel;
        _channel_config.intr_type = intr_type;
        _channel_config.timer_sel = timer_num;
        _channel_config.duty = duty;
        _channel_config.hpoint = hpoint;

        return ledc_channel_config(&_channel_config);
    }

This method accepts all available channel configurations enabling the user to completely configure the channel with custom settings in one call.

USE LEDC to control the brightness of an LED

Create the test circuit

I used the same circuit that we build in the Basic ESP32 DAC C++ library tutorial. I only changed the resistor value to 330R and added an LED. See the circuit below:

ESP32 LEDC Schematic
ESP32 LEDC Breadboard

Notice that I left the potentiometer to the ADC in the circuit. As a bonus for this tutorial, you can add the library that we created in Basic ESP32 DAC C++ library to create a program that adjusts the brightness of the LED according to the value read on the LED. That is beyond the scope of this tutorial, but here is a hint: LEDC.SetChannelDutyCycle(ADC.GetRaw()). We will cover this in the next tutorial where we will add the libraries that we’ve created thus far into a new project.

main.h

Copy the following code into main.h in the src folder:

#pragma once

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <iostream>
#include "cppledc.h"

class Main final
{
public:
    void run(void);
    void setup(void);

    CPPLEDC::CppLedc LEDC;
    CPPLEDC::CppLedcTimer LedcTimer0;

}; // Main Class

We create one CppLedcTimer object named LedcTimer0. This object will set up timer 0 for the LEDC. We can create up to 4 of these objects for each of the 4 LEDC timers.

We also create one CppLedc object named LEDC. LEDC will use default values which configure it to use timer 0 which we set up in the LedcTimer0 object. You can create up to 16 CppLedc objects, however, only 8 of them may be set to high-speed mode.

main.cpp

Copy the following code into main.cpp in the src folder:

#include "main.h"

Main App;

void Main::run(void)
{
    std::cout << "Increasing LED Brightness\n";
    for(int i = 0; i < 255; i++)
    {
        LEDC.SetChannelDutyCycle(i);
        vTaskDelay(1);
    }
    std::cout << "Decreasing LED Brightness\n";
    for(int i = 255; i > 0; i--)
    {
        LEDC.SetChannelDutyCycle(i);
        vTaskDelay(1);
    }
}

void Main::setup(void)
{
    LedcTimer0.InitializeDefaults(); // Set timer0 to default values
    LedcTimer0.SetTimerDutyResolution(LEDC_TIMER_8_BIT); // Set Duty Resolution to 10 bits
    LEDC.SetChannelGpio(25); // LED is on pin 25
}

extern "C" void app_main(void)
{
    App.setup();
     while (true)
    {
        App.run();
    }    
}

We set the Duty resolution to 8 bits to give us a range of 256 steps to speed up the for loops.

Run the program

You can now compile the project and upload the program to your ESP32 device. The LED should now fade in and out in a loop.

LEDC Fading Example

Conclusion

We have created a library that allows us to control LED brightness using PWM. While we have not implemented some of the more advanced hardware controls and interrupts, using this library and creative software engineering we will be able to do just about anything that can be done with PWM LED control.

To download this project from Github, click here.

Thanks for reading.

Similar Posts