PWM with LEDC
The chip cannot output half a volt, so it switches fully on and off thousands of times a second and lets your eye do the averaging. One trade decides everything - frequency against how many brightness steps you get.
Frequency against steps
The PWM counter runs at 80 MHz and has to complete a whole cycle every period. So frequency multiplied by the number of steps cannot exceed 80 million. Ask for more and the setup call fails silently and the pin does nothing.
Sensible starting points
| What you are driving | Frequency | Resolution |
|---|---|---|
| An LED | 1–5 kHz | 12 bit |
| A DC motor or a fan | 20–25 kHz | 10–12 bit |
| A servo | 50 Hz | 16 bit |
| A heater or a lamp dimmer | 100 Hz–1 kHz | 10 bit |
Brightness is not linear
Duty 50% does not look half as bright as 100%, because your eye responds logarithmically. A fade that steps evenly through duty values looks like it rushes the dark end and crawls at the light end. Square the value — or use a lookup table — and the fade looks even.
The code
Arduino core 3.x simplified this - attach a pin to a frequency and a resolution, then write duty values. There are 16 independent channels on the classic ESP32, 8 on the C3 and C6.
const int PIN = 18;
const int FREQ = 5000; // 5 kHz - above hearing, well inside the limit
const int BITS = 12; // 4096 steps
void setup() {
ledcAttach(PIN, FREQ, BITS);
}
void loop() {
for (int d = 0; d < (1 << BITS); d += 16) {
ledcWrite(PIN, d);
delay(2);
}
}analogWrite works too and is always 8-bit. Use ledcWrite when the resolution matters, which is any time you can see steps in a slow fade.
MicroPython fixes the resolution at 16 bits and lets you set the frequency freely. Duty runs 0 to 65535 whatever the frequency, and the driver works out what the hardware can actually do.
from machine import Pin, PWM
import time
led = PWM(Pin(18), freq=5000)
while True:
for d in range(0, 65536, 256):
led.duty_u16(d)
time.sleep_ms(2)duty_u16 is the portable one. The older duty() is 10-bit and still in a lot of example code, which is why copied snippets sometimes give you a quarter of the brightness.
When it does not work
Frequency times steps exceeded 80 MHz, so the setup call failed and returned 0. Lower the resolution or the frequency - the figure above shows where the ceiling is.
Servos want 50 Hz and a pulse between 1 and 2 ms, which at 50 Hz is a duty of about 5 to 10 percent. At 8-bit resolution that whole range is only 13 steps. Use 16-bit, or use the ESP32Servo library.
Your PWM frequency is inside human hearing. Move it above 20 kHz. Remember that costs resolution - at 20 kHz you have 12 bits available, which is still far more than a motor needs.
Below about 200 Hz the camera sees the switching. For anything that will be photographed, use 1 kHz or more.
PWM is how the chip pretends to output an analog voltage. The next page is how it reads one, and why it disagrees with your multimeter.
Reading the ADC →Edit this page — content/esp32/pwm-with-ledc.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.