
Hello readers, hope you all are doing great. In this tutorial, we are going to discuss another ESP32 module’s feature that is PWM (pulse width modulation).
We will also explain and demonstrate, how to implement PWM on DC motor and LED using ESP WROOM-32 module.
What is Pulse Width Modulation?
PWM is used to control the power delivered to the load or peripheral device by pulsating the direct current (DC) and varying the ON time of the digital pulse, without causing power loss. Basically, the Pulse Width Modulation technique generates modulated signal output using a digital input signal.
The behavior of the PWM signal is determined by the following factors:
- Duty Cycle
- Frequency
- Resolution
Duty Cycle: It is the ratio of ON time (when the signal is high) to the total time taken to complete the cycle.
- The duty cycle is represented in the form of a percentage (%) or ratio.

Fig. 2 Duty cycle
Resolution: The resolution of a PWM signal defines the number of steps it can have from zero power to full power. The resolution of the PWM signal is configurable for example, the ESP32 module has a 1- 16 bit resolution, which means we can configure maximum a of 65536 (2^16) steps from zero to full power.
Frequency: The frequency of a signal defines the number of cycles per second and it is inversely proportional to the time period. The frequency of PWM is dependent on the clock source. PWM resolution and frequency are inversely proportional to each other.
If we toggle a digital signal On and OFF with high frequency then the output will look like an analog signal with constant voltage.
Various PWM applications are:
- DC motor speed control
- Servo motor direction or position control
- To control the LED brightness
- Drive buzzer with different loudness
- Measurements
- Fan speed control
Implementing PWM using ESP32
In the ESP WROOM-32 module, there are 16 PWM channels. All the channels are divided into two groups containing 8 channels in each group. The resolution can be programmed between 1 to 16 bits and frequency also depends upon the programmed resolution of the PWM signal.
For the demonstration of PWM in ESP32 we are going to explain two examples:
- Controlling LED brightness using PWM
- Controlling DC motor speed using PWM
ESP32 Code for Controlling LED brightness using PWM
- We are using Arduino IDE to compile and upload the code into the ESP WROOM-32 board.
- If you are new to Arduino IDE and ESP32 then follow our previous tutorial, Introduction to ESP32 programming series for detailed information.
- ESP32’s inbuilt LED is used in this code. You can also connect an external LED as per your requirements.
Arduino IDE Code:
//......... Global variale declaration to set PWM properties const int ledChannel = 0; // select channel 0 const int resolution = 8; // 8 bit resolutin i.e., 0-255 const int frequency = 5000; // set frequency in Hz int dutyCycle =0; void setup() { Serial.begin(115200); ledcSetup(ledChannel, frequency, resolution); // configure LED PWM functionalitites ledcAttachPin(LED_BUILTIN, ledChannel); // attach the channel to the GPIO to be controlled } void loop() { while(dutyCycle <200) { ledcWrite(ledChannel, dutyCycle++); // changing the LED brightness with PWM Serial.print(" duty Cycle ++ :"); Serial.println(dutyCycle); // display the duty cycle on serial minitor delay(5); } while(dutyCycle>0) { ledcWrite(ledChannel, dutyCycle--); // changing the LED brightness with PWM Serial.print(" duty Cycle -- :"); Serial.println(dutyCycle); // display the duty cycle on serial minitor delay(5); } }
Code Description
Global Variable declaration:
- The first step is to declare variables for setting PWM properties.
- As we have already mentioned that ESP WROOM-32 has 16 PWM channels (0 to 15). So, the first step will be to select a PWM channel between 0-15. In the Arduino IDE code, we are using PWM channel_0 to generate a PWM signal.
- The next step will be to choose the resolution. The maximum resolution for ESP32 is 16-bit. You can choose any value between 1-16. PWM resolution is the factor that decides the maximum duty cycle.
- For example, if we choose 10-bit resolution then the maximum duty cycle of the output signal will be 2^10 that is 1024 (0-1023) and similarly, for 8-bit resolution the duty cycle will be 2^8=256 (0- 255).
- 5KHz or 5000Hz is the PWM signal frequency.
- We also need to initialize a variable to store the duty cycle value.

Fig. 4 PWM specifications
Arduino Setup() Function
- Inside setup() function we are going to start serial monitor at 115200 baud rate.
- To configure PWM properties we are calling the ledcSetup() function which uses PWM properties (like PWM channel, frequency and PWM resolution) as arguments.
- ledcAttachPin() function is used to assign the LED_BUILTIN pin to the PWM channel.

Fig. setup() function
Arduino Loop() Function
- Inside the loop function, we going to run a conditional control loop (while loop) to change the LED brightness along with the change in duty cycle.
- At first, the value of the duty cycle is going to increase continuously until it reaches max 8-bit resolution ( that is 255).
- The serial monitor will print the duty cycle value with some delay.
- After increasing the duty cycle to maximum resolution another while loop is used to decrease the duty cycle to value 0 from 255 and proportionally the LED brightness and the PWM output will be printed on the serial monitor.

Fig. 6
Code Testing
- You can see the change in the value of the duty cycle on the serial monitor. We have attached a screenshot below from Arduino IDE serial monitor for reference.

Fig. 7 Serial monitor
- For a better understanding of PWM output you can use an oscilloscope (either CRO or DSO) by connecting the PWM output pint and GND pint to the oscilloscope probe, if available.
- You can also use a serial plotter to see the PWM output if you do not have an oscilloscope.
- To access the serial plotter, use sort-cut key shift+ctrl+L or follow the image attached below:
- We have attached a screenshot of the PWM output waveform from the serial plotter for better understanding.
- You can vary the value of duty cycle anywhere between 0-8 bit resolution.
- For example in the image attached below the duty cycle is 200.

Fig. 9 Serial plotter PWM output
ESP32 Code for Controlling DC motor speed using PWM

Fig. 10
In this example, we are going to implement PWM using ESP WROOM-32 to control the speed of a DC motor.
The speed of the DC motor depends upon the input power supply. So, by varying the power input we can also vary (increase or decrease) the speed of DC motor.
Hardware components required:
- ESP WROOM-32 board
- DC motor
- L298N motor driver
- Connecting wires
- Data cable
L298N motor driver: A motor driver is used between the ESP32 board and DC motor to resolve the power compatibility issues.
Both the ESP32 board and DC motor operate at different power ratings due to which you can not connect the two devices directly. So a motor driver is used to receive a low power input from the ESP32 board and drive/run DC motor at slightly high power.
L298N can drive a DC motor that operated between 5 to 35 voltage range and maximum current of 2A.
There are various DC motor drivers available in the market for example L293D, DRV8833, MAX14870 single brushed motor driver etc. You can choose the driver of your choice depending upon the application and power ratings.
Fig. 11

FIG. 12 IC L298N pin-out
- You can also change the direction of rotation by connecting the input as per the table drawn below:
IN_1 | IN_2 | Rotation |
HIGH | LOW | DC motor rotates in a clockwise direction |
LOW | HIGH | The motor rotates in an anti-clockwise direction |
LOW | LOW | Motor STOP |
HIGH | HIGH | Motor STOP |

Table 1
Arduino Code
// _____congigure GPIO pins to connect motor driver int enable1Pin = 14; int M_Pin1 = 26; int M_Pin2 = 27; // Setting PWM properties const int freq = 10000; const int pwmChannel = 0; const int resolution = 8; int dutyCycle = 150; void setup() { Serial.begin(115200); // sets the pins as outputs: pinMode(M_Pin1, OUTPUT); pinMode(M_Pin2, OUTPUT); pinMode(enable1Pin, OUTPUT); // configure LED PWM functionalitites ledcSetup(pwmChannel, freq, resolution); // attach the channel to the GPIO to be controlled ledcAttachPin(enable1Pin, pwmChannel); Serial.print("Testing DC Motor..."); } void loop() { // Move the DC motor in anti-clockwise direction at maximum speed Serial.println("Moving reverse"); digitalWrite(M_Pin1, LOW); digitalWrite(M_Pin2, HIGH); delay(500); // Move DC motor forward with increasing speed Serial.println("Moving Forward"); digitalWrite(M_Pin1, HIGH); digitalWrite(M_Pin2, LOW); //----while loop---- while (dutyCycle <= 255) { ledcWrite(pwmChannel, dutyCycle); Serial.print("Speed increasing with duty cycle: "); Serial.println(dutyCycle); dutyCycle = dutyCycle +5; delay(100); } while (dutyCycle >150) { ledcWrite(pwmChannel, dutyCycle); Serial.print("Speed decreasing with duty cycle: "); Serial.println(dutyCycle); dutyCycle = dutyCycle -5; delay(100); } // _____Stop the DC motor Serial.println("STOP DC motor"); digitalWrite(M_Pin1, LOW); digitalWrite(M_Pin2, LOW); delay(500); }
Code Description
- The first step is to configure GPIOs which are to be connected with the DC motor driver (L298N).
- Here we are using three GPIOs, the first one is connected with the Enable_A pin and the rest of the two are connected with motor inputs.
- You can also control 2 motors with the same driver using Enable_2 and its respective input pins.
- The next step is to define variables to store PWM properties as discussed in the previous example.
- You can vary the frequency and duty cycle of the PWM signal as per your requirements but, within the desired range.
- Here we are assigning 10000Hz or 10KHz frequency with 8-bit resolution (0-255 duty cycle) and the initial duty cycle value is 150 for PWM channel 0.

Fig. 14
Arduino Setup() Function
- Inside the setup function, the first task is to initialize the serial monitor with a 115200 baud rate.

Fig. 15
- Set the operating mode of three GPIO pins (which are to be connected with motor driver board) as output.
- Call the function ledcSetup() to configure PWM properties by passing PWM properties as arguments.

- Next, you need to select the GPIO pin which will provide the PWM output from ESP32 to the motor driver using ledcAttachPin() function which uses the PWM output pin and PWM channel as two arguments.

Arduino Loop() Function
- Start rotating the motor in the anti-clockwise direction. Follow Table 1 for more details regarding the direction of rotation.
- Add the delay of 0.5 sec or as per your requirements.
- Rotate the DC motor in a clockwise direction by setting M_PIN1 as high ND m_Pin2 as low.

Fig. 20
- As we have initialized the duty cycle variable with a value of 150. Now, increase the motor speed by increasing the value of the Duty cycle from 150 by increasing 5 steps continuously until it reaches the maximum value that is 255.

Fig. 21 Increasing speed
- After reaching the maximum speed at 255 duty cycle, now let's decrease the speed of DC motor till 150 duty cycle with the decrease of 5 steps every time.

Fig. 22 Reducing speed
- Stop duty cycle by either writing both the motor pins(M_Pin1, M_Pin2) to LOW or HIGH (like XOR gate).

Fig. 23 STOP DC motor
Code Testing
- For a better understanding, open the serial monitor using shortcut keys ctrl+shift+M.
- On the serial monitor, you can see the increase and decrease in duty cycle and can compare it with DC motor speed.

Fig. 24 PWM output on serial monitor
- You can also check the PWM output on the Serial plotter by pressing the ctrl+shift+L keys.
- It is visible through the waveform that the duty cycle is varying from 150 to 255 and reverse and proportionally the seed of the DC motor.

Fig. 25 PWM output on Serial Plotter
This concludes the tutorial. I hope you found this useful, and I hope to see you soon for the new ESP32 tutorial.