PWM in ESP32

Since its inception, the ESP32 microcontroller has received the well-deserved love of its fans and is actively used in many homemade products. The main reason for such love from fans is its “package” with all sorts of functions and wireless connection methods, including. In the same article, we will talk about such a useful feature as PWM – “pulse width modulation”.

PWM is a method that allows, by turning on and off the supply voltage supplied to the consumer, to regulate its functioning. For example, if we are talking about an electric motor, then with the help of PWM control it is possible to control the speed of its rotation.

However, this method is not limited to the regulation of electric motors. With it, you can control the brightness of LEDs, light bulbs.

Here we come to the definition of PWM duty cycle. In a very simple way, it shows the ratio of the time during which voltage is applied to the system, to the time when the system is turned off.


In the official espressif documentation, PWM control methods are divided into 2 sub-sections:

  • motor control,
  • LED light sources.

The ESP32 has two modules that control the pulse width of the PWM. Each of them has three blocks of outputs.

Of course, this is all rather arbitrary, and everything can be interchanged. LED control can be used to control motors and vice versa.

However, for engines there is a specialized API designed for use from under the ESP-IDF environment.

By the way, in the manuals widely used on the Internet, it is mainly control using the LED approach that is used, within which the system provides us with 16 independent channels, each of which can be configured independently of the others.

All 16 channels are clocked by an 80 MHz APB bus generator, 8 of which are selectable at 8 MHz. In my opinion, this function is designed to create energy efficient systems with low consumption.

The table below provides official information on the most common PWM output configurations:

Thus, from the table we can see that there is some relationship between the output frequency, the original clock frequency and resolution.

Usually on this topic on the forums often questions arise: “What is the maximum possible PWM frequency for the ESP32?”

This question can be answered as follows: the maximum possible frequency is calculated using the following formula:

original frequency / 2^(in degree of resolution)

For example, 80,000,000 / 2^16 = 1.22 kHz

Thus, we can say that the resolution of the signal (if in a simple way) means the smoothness of its change, since a higher resolution signal has a greater gradation of sublevels. For example, the table above shows that a 16-bit signal has 65536 gradations. That is, you can change the brightness of the LED source very smoothly.

By the way, ESP32 PWM timers have a maximum resolution of 20 bits. That is, 20-bit resolution can be assigned to each of the 16 channels.

Let’s calculate what then the maximum allowable PWM frequency will be:

80,000,000 / 2^20 = 76.3 Hz

In principle, you can set the desired resolution for your purposes yourself. For example, I use 8-bit resolution, which gives me the ability to change the output signal from 0 to 255 gradations. And such a number of “gradations on the scale” suits me perfectly. You may have a different situation, so you can get creative with this question.

The above calculation method allows you to understand how high the frequency can be in your particular case, at your particular resolution and input signal frequency.
For example, if you are using PWM to control an electric motor, then you should stick to a frequency of more than 20 kHz, since a lower frequency already beeps quite strongly and is well audible to the human ear.

Since the ESP32 does not have the ability to control the PWM using the analogWrite () Arduino function, they usually (or rather, “most often on the Internet” – since almost everyone is lazy and does not want to understand the espressif development environment) use the esp32-gal- ledc.h which provides the following functions for PWM control:

ledcSetup(канал, частота, разрешение);    
ledcAttachPin(пин, канал);    
ledcWrite(канал, коэффициент заполнения);    
ledcWriteTone(канал, частота);    

Of this block, the first three functions are most commonly used. An example of their use is shown in the code section below, which is used to control the micromotor of a robotic toy:

Engine Code

#include "esp32-hal-ledc.h"

//частота ШИМ
const int frequency = 30000;

//канал, на который назначим в дальнейшем ШИМ    
const int pwmChannel = 0;

//разрешение ШИМа (в битах)    
const int resolution = 8;

void setup() {

pinMode(motor1Pin1, OUTPUT);
pinMode(motor1Pin2, OUTPUT);

// задаём настройки ШИМ-канала:    
ledcSetup(pwmChannel, frequency, resolution);


void loop() {

//крутим двигатель в одну сторону    

ledcWrite(pwmChannel, 155); //255 - максимум оборотов
digitalWrite(motor1Pin1, HIGH);
digitalWrite(motor1Pin2, LOW);

delay (1000);

//крутим двигатель в другую сторону    

ledcWrite(pwmChannel, 155); //255 - максимум оборотов
digitalWrite(motor1Pin1, LOW);
digitalWrite(motor1Pin2, HIGH);


Or, for example, this is how the brightness of the LEDs is controlled:

LED Code

// пин светодиода
const int ledPin1 = 16;

//частота ШИМ                                         
const int frequency = 5000;

//канал, на который назначим в дальнейшем ШИМ                                         
const int ledChannel = 0;

//разрешение ШИМа (в битах)
const int resolution = 8;
void setup(){
// задаём настройки ШИМ-канала:                                         
ledcSetup(ledChannel, frequency, resolution);

// подключаем ШИМ-канал к пину светодиода:                                         
ledcAttachPin(ledPin1, ledChannel);

void loop(){
  // яркость плавно растёт                                         
  for(int i = 0; i <= 255; i++){   

  ledcWrite(ledChannel, dutyCycle);

  // яркость плавно cпадает
  for(int i = 255; i >= 0; i--){

  ledcWrite(ledChannel, dutyCycle);   

I have already said above that espressif has developed its own environment for creating applications for this microcontroller, which is called ESP-IDF (Expressif’s IoT Development Framework).

With this tool, you can develop your applications on ESP32 using C++ and C.

For advanced users, espressif offers a plugin for this environment that is installed in the Arduino IDE.

As the official documentation promises, library builder will allow you to integrate the ESP-IDF into the Arduino environment:

“Arduino Lib Builder is the tool that integrates ESP-IDF into Arduino. It allows you to customize the default settings used by Espressif and try them in Arduino IDE”.

After that, you can use the official espressif documentation, located here

this address

and the API for PWM motor control will become available to you.

For example, initialization of outputs for connecting to them a motor driver, which is an H-bridge, occurs using the function mcpwm_gpio_init()the permission is set using mcpwm_timer_set_resolution().

By the way, the officially available API provides very interesting properties, for example, contact setup method to track the position of the rotor if the motor is brushless.

Screenshot from the official documentation

Another interesting official option is engine failure error handling.

Screenshot from the official documentation

By the way, in order to work with all the functions of the ESP32, it is not necessary to switch from the familiar Arduino IDE to ESP-IDF.

To do this, you just need to write directly to the registers! In short, a register is a memory area located at a certain address (offset) and containing pre-configured microcontroller settings given by espressif.

Theoretically, you can directly access the fields of these registers (specific bits of the register) and set the desired values ​​\u200b\u200bin them, thus setting up the microcontroller.

You can find a full description of the registers, their fields and offsets (grouped for specific peripherals) in the official ESP32 documentation.

The advantage of this approach is that you almost directly give your orders to the microcontroller and do not need intermediate gaskets in the form of API software functions.

For example, LED control registers are on page 381 official manual ESP32 Technical Reference Manual in subsection Register Summary.

If we turn to this subsection, we will see a large table of registers, as well as their addresses (that is, roughly speaking, at what address is the panel with “manual twists”, twisting which we can achieve something).

For example, if we turn to the second register from the top and go to page 384, we will see that this register, which is 32-bit, has active fields from 0 to 3 bits (everything else is “reserved” – that is, unconfigured). These fields have the read/write property, that is, values ​​from there can be both read and written (0 or 1).

You can find examples of working with registers and a detailed explanation of what it is (using Arduino as an example). here. It should be said right away that this is not an easy topic. For ESP32, I didn’t even have to use direct access to registers myself (there was no need), nevertheless I wanted to voice this possibility.

Sometimes the use of PWM helps a lot in homemade products when non-standard problems arise. For example, when creating a radio-controlled car, I had the following problem: when using the standard esp32_servo library to control the robot gripper installed on the car, a conflict arose between the library and the existing PWM settings that were intended for wheels.

No changes in the settings helped, in addition, the time was very limited. As a result, it became clear that it would not be possible to use the library within this limited time.

As a result, a solution was applied that uses PWM and allows you to do the same work as the standard library for servos:

Servo code

#include "esp32-hal-ledc.h"

// переменные для свойств широтно-импульсной модуляции (ШИМ) сервы:                                         
 #define low 1638
 #define high 7864
 #define timer 16

void setup() {

//Настраиваем ШИМ: 1 канал, 50 Гц, 16-бит                                         
ledcSetup(1, 50, timer);

//1 канал ШИМ подключили к 18 пину                                         
ledcAttachPin(18, 1);


void loop() {

//повернули серву в одну сторону на 180 градусов                                         

  for (int i=high ; i >=low  ; i=i-100)
        ledcWrite(1, i);
  delay (1000);

//повернули серву в другую сторону на 180 градусов                                         

  for (int i=low ; i < high ; i=i+100)
      ledcWrite(1, i);


A detailed explanation of this solution (in English) and a code example are



Summing up, we can say that it is thanks to PWM that smooth control of processes is possible, which in many moments is simply irreplaceable.

What solutions do you use?

A UFO flew in and left here a promotional code for our blog readers:

15% for all VDS tariffs (except for the Heating tariff) — HABRFIRSTVDS.

Similar Posts

Leave a Reply