ESP32 PWM Tutorial

PWM (Pulse Width Modulation) is a digital technique that allows precise control over analog devices and systems. It is indispensable for applications such as LED dimming, motor control, and audio generation. In this article, we will embark on an in-depth exploration of PWM on ESP32, dissecting the technical intricacies that make it such a powerful tool. We will unravel the relationship between the duty cycle and PWM, discuss the impact of resolution, and dive into advanced configurations, empowering you to create sophisticated and precise microcontroller applications.

Table of Contents

PWM with ESP32

A PWM signal is generated by alternating between two states: a logical high (often represented by a voltage level or logic 1) and a logical low (usually a voltage level or logic 0). The proportion of time the signal remains in the high state compared to the entire signal period is defined by the duty cycle. A higher duty cycle signifies a longer duration of the high state, while a lower duty cycle represents a shorter duration.

The ESP32 offers 16 independent PWM channels, which means you can simultaneously control up to 16 different devices or aspects of a project. These channels can be flexibly mapped to a variety of pins across the ESP32’s GPIO (General-Purpose Input/Output) pins, offering exceptional versatility in project design. The flexibility of mapping PWM channels to pins is a crucial consideration during project planning. It allows developers to allocate resources efficiently, avoid pin conflicts, and optimize signal routing, ensuring that the ESP32 can meet the specific demands of a project.

Duty Cycle and PWM

Duty Cycle and PWM

The duty cycle is a fundamental parameter in the context of PWM. It describes the ratio of the time during which the PWM signal is in the “on” state (high voltage or logic 1) to the total time of one PWM period. In other words, it determines the percentage of time the signal is high, and this percentage corresponds to the duty cycle. Here’s the relationship between the duty cycle and PWM: 

  • Duty Cycle Calculation: Duty Cycle (%) = (On-Time / Total Period) * 100%
  • PWM Signal Visualization: A PWM signal is typically visualized as a square wave, where the high state represents the “on” time and the low state represents the “off” time. The duty cycle is the ratio of the width of the high state (on-time) to the width of the entire period (on-time + off-time).
  • Impact on Output: The duty cycle directly affects the behavior of the output. A higher duty cycle means the signal is high for a greater portion of the period, resulting in a more “on” or active state. Conversely, a lower duty cycle means the signal is high for a smaller portion of the period, creating a less active or “off” state.

Resolution and PWM

Resolution is another essential parameter when it comes to PWM. In the context of PWM, resolution refers to the number of distinct, quantized levels or steps within the PWM signal. It is a critical factor that affects the precision and accuracy of PWM control. Here’s the relationship between resolution and PWM: 

  • Resolution Levels: Resolution is often expressed as the number of discrete levels within the PWM signal. Higher resolution means more distinct levels, allowing for finer control. 
  • Impact on Precision: Resolution directly affects the precision of PWM. A higher resolution provides more precise control. For example, if you have 8-bit resolution, you have 2^8 (256) distinct levels, while a 12-bit resolution offers 2^12 (4,096) levels, providing finer granularity.
  • Control of Output: With higher resolution, you can control the output with greater accuracy. This is particularly important in applications where precise control is required, such as audio signal generation or motor speed regulation. In contrast, lower resolution may result in “steps” or noticeable changes in the output when transitioning between levels.

ESP32 PWM Code and Explanation

				
					const int ledPin = 2;
				
			

This line defines a constant integer variable named ledPin and assigns it the value 2. It represents the GPIO pin to which the LED is connected. In your ESP32 setup, this is the pin where the LED will be controlled.

				
					const int pwmChannel = 0;
				
			

Here, a constant integer variable PWM Channel is defined and set to 0. It represents the PWM channel you’ll use for controlling the LED’s brightness. The ESP32 provides 16 PWM channels (0-15) that can be used for PWM control.

				
					const int pwmFrequency = 5000;
				
			

This line defines a constant integer variable PWM Frequency and sets it to 5000 Hz. It specifies the PWM frequency, which controls how quickly the LED’s brightness changes. A higher frequency results in faster changes.

				
					const int pwmResolution = 8;
				
			

Here, you define the constant integer variable PWM Resolution with a value of 8. It sets the resolution of the PWM signal, determining the number of distinct levels for the LED brightness. In this case, it’s set to 8 bits, which allows for 256 distinct levels.

				
					const int fadeIncrement = 5;
				
			

This line defines a constant integer variable fadeIncrement with a value of 5. It represents the amount by which the LED’s brightness changes during each step of the fading process. A higher value makes the brightness change more quickly.

				
					const int fadeDelay = 50;
				
			

This constant integer variable fadeDelay is set to 50. It controls the duration of the delay between each brightness change step, measured in milliseconds (ms). A shorter delay makes the fading process faster.

				
					void setup() {
  Serial.begin(115200);
  ledcSetup(pwmChannel, pwmFrequency, pwmResolution);
  ledcAttachPin(ledPin, pwmChannel);
}
				
			

In the setup() function, you initialize the serial communication with a baud rate of 115,200 for debugging and monitoring. Then, you configure the PWM settings using ledcSetup, specifying the PWM channel, frequency, and resolution. Afterward, you attach the PWM channel to the GPIO pin specified by ledPin using ledcAttachPin.

				
					void loop() {
  for (int brightness = 0; brightness <= 255; brightness += fadeIncrement) {
    ledcWrite(pwmChannel, brightness); 
    float dutyCyclePercentage = (brightness / 255.0) * 100.0;
    Serial.print("Duty Cycle Percentage: ");
    Serial.println(dutyCyclePercentage, 1); 
    delay(fadeDelay); 
  }
				
			

The loop() function contains a loop that gradually increases the LED’s brightness from 0 to 255 in increments of fadeIncrement. It uses ledcWrite to set the LED’s brightness for each step. Additionally, it calculates the duty cycle percentage for each brightness level and prints it to the serial monitor. After each step, it pauses for a duration specified by fadeDelay.

				
					  for (int brightness = 255; brightness >= 0; brightness -= fadeIncrement) {
    ledcWrite(pwmChannel, brightness); // Set LED brightness
    float dutyCyclePercentage = (brightness / 255.0) * 100.0;
    Serial.print("Duty Cycle Percentage: ");
    Serial.println(dutyCyclePercentage, 1); // Display duty cycle with one decimal place
    delay(fadeDelay); // Pause for a moment
  }
				
			

The code then runs a similar loop to decrease the LED’s brightness from 255 to 0 in increments of fadeIncrement. It again calculates and displays the duty cycle percentage and introduces a delay between each step.

				
					  Serial.println("LED cycle completed.");
  delay(1000); // Delay between cycles
}
				
			

Finally, the code prints a message indicating the completion of the LED brightness cycle. It then adds a delay of 1000 ms before the loop begins the cycle again.

After combining the code:

				
					const int ledPin = 2;          // GPIO pin where the LED is connected
const int pwmChannel = 0;      // PWM channel (0-15)
const int pwmFrequency = 5000; // PWM frequency in Hz
const int pwmResolution = 8;   // PWM resolution (8, 10, 12, 15 bits)
const int fadeIncrement = 5;   // Increment for LED brightness change
const int fadeDelay = 50;      // Delay between brightness changes (in ms)
void setup() {
  Serial.begin(115200);
  ledcSetup(pwmChannel, pwmFrequency, pwmResolution);
  ledcAttachPin(ledPin, pwmChannel);
}
void loop() {
  for (int brightness = 0; brightness <= 255; brightness += fadeIncrement) {
    ledcWrite(pwmChannel, brightness); // Set LED brightness
    float dutyCyclePercentage = (brightness / 255.0) * 100.0;
    Serial.print("Duty Cycle Percentage: ");
    Serial.println(dutyCyclePercentage, 1); // Display duty cycle with one decimal place
    delay(fadeDelay); // Pause for a moment
  }
  for (int brightness = 255; brightness >= 0; brightness -= fadeIncrement) {
    ledcWrite(pwmChannel, brightness); // Set LED brightness
    float dutyCyclePercentage = (brightness / 255.0) * 100.0;
    Serial.print("Duty Cycle Percentage: ");
    Serial.println(dutyCyclePercentage, 1); // Display duty cycle with one decimal place
    delay(fadeDelay); // Pause for a moment
  }
  Serial.println("LED cycle completed.");
  delay(1000); // Delay between cycles
}
				
			
Device is at ON state
Device is at OFF state

More content you may be interested in

esp32 pinout
ESP32 Pinout Reference Guide

The ESP32 is a versatile microcontroller with extensive functionalities. Its pinout includes GPIO pins for digital, analog, and capacitive touch, PWM pins for precise control,

esp32 pwm project
ESP32 PWM Tutorial

This article delves into PWM on the ESP32, crucial for precise control in applications like LED dimming, motor regulation, and audio generation. It explains the

Scroll to Top