ESP32 PWM with ledcAttach and ledcWrite
Beginner
Or see every lesson.
- 455parts on the bench
- 43boards running now
- 1.00xreal time, on every board
An ESP32 has no fixed PWM pins. Its PWM comes from a block called LEDC, and any pin with an output driver can be one of its outputs. Since version 3.0 of the Arduino ESP32 core, two calls do everything: ledcAttach(pin, frequency, bits) sets the pin up, and ledcWrite(pin, duty) sets how much of each period it is high. analogWrite() works too, on top of the same hardware.
The circuit above is an ESP32 DevKit with a potentiometer on GPIO 32 and an LED on GPIO 4. The sketch reads the knob and calls analogWrite(4, duty), so turning the knob dims the LED, and the serial monitor shows the reading and the duty it chose. Below is what the LEDC calls do, measured on the same board, and what changed from the 2.x core that most tutorials still use.
ledcAttach and ledcWrite
The sketch below attaches GPIO 4 at 5000 Hz with 8 bits of resolution, so the duty runs from 0 to 255, and writes 64, a quarter. A jumper from GPIO 4 to GPIO 15 lets the same board measure its own output with pulseIn(). It printed "high 50 us, low 150 us": a 200 microsecond period, which is 5000 Hz, high for a quarter of it.
ledcAttach() returns true when it found a channel and a timer for the pin, and false when it did not, so check it. It failed here on GPIO 34, one of the input-only pins, which have no output driver at all. The pin is the name for everything afterward: ledcWrite(4, 128) for half, ledcRead(4) for the duty, ledcReadFreq(4) for the frequency, and ledcDetach(4) to give the channel back.
const int kLed = 4;
const int kProbe = 15; // a jumper from GPIO 4
void setup() {
Serial.begin(115200);
pinMode(kProbe, INPUT);
if (!ledcAttach(kLed, 5000, 8)) {
Serial.println("ledcAttach failed");
}
ledcWrite(kLed, 64); // 64 of 256: a quarter
delay(5);
Serial.print("high ");
Serial.print(pulseIn(kProbe, HIGH));
Serial.print(" us, low ");
Serial.print(pulseIn(kProbe, LOW));
Serial.println(" us");
}
void loop() {
}analogWrite against ledcWrite
On an ESP32, analogWrite(pin, value) attaches the pin to a channel the first time you call it, at 8 bits and 1000 Hz, and then writes the value. With analogWrite(4, 64) the same measurement printed "high 250 us, low 750 us": a 1 kHz period, a quarter high. It is the right call for an LED and for code that should also build for an Uno.
When the frequency matters, set it per pin before the write. analogWriteFrequency(4, 5000) and analogWriteResolution(4, 10), then analogWrite(4, 256), printed "high 50 us, low 150 us" again, because 256 of 1024 is a quarter. A motor driver, a fan or a buzzer usually wants its own frequency, and a servo wants 50 Hz, which is why most ESP32 tutorials use the LEDC calls directly.
Frequency against resolution
The LEDC timer counts a clock, and one period is 2 to the power of the resolution counts. On a classic ESP32 the fastest clock is the 80 MHz APB clock, so the frequency times 2 to the power of the bits has to stay under 80 million (Espressif’s LEDC guide). That allows 13 bits at 5 kHz, 11 bits at 20 kHz and 10 bits at 40 kHz. Ask for more and a real ESP32 refuses the attach.
Mokxi does not check that limit yet: it accepts any frequency at up to 14 bits, so a pair that works here can still fail on the board. Pick the pair from the arithmetic above. For an LED, 5 kHz at 8 bits is plenty. For a motor that should not whine, 20 kHz at 10 bits or fewer.
ledcChangeFrequency(pin, frequency, bits) retunes a pin that is already attached, and ledcWriteTone(pin, 440) plays a square wave at half duty, which is how a passive buzzer plays a note. Here it returned 440, and the high half of each period measured 1130 microseconds, half of a 440 Hz period.
How many PWM channels does an ESP32 have?
A channel drives one pin, and channels at the same frequency and resolution share a timer. Espressif’s table gives the classic ESP32 16 channels in two groups, the S3 eight and the C3 six, and each group has four timers. Core 3.x hands them out by itself; ledcAttachChannel(pin, frequency, bits, channel) picks one when you need to.
Mokxi’s classic ESP32 models the high-speed group: eight channels and four timers. Attaching pin after pin at 5000 Hz, the ninth ledcAttach() returned 0 here. Attaching each pin at a different frequency, the fifth returned 0, because every frequency needs a timer of its own. A real ESP32 has the second group as well, so the same sketch gets twice as far, and the lesson is the same: lights that fade together should share a frequency.
"ledcSetup was not declared in this scope"
This is the error almost everyone meets after updating the ESP32 boards package. Core 3.0 removed ledcSetup() and ledcAttachPin() and merged them into ledcAttach(), and every LEDC call now takes the pin instead of the channel number (Espressif’s 2.x to 3.0 migration guide). A sketch written for 2.x stops in the Arduino IDE with 'ledcSetup' was not declared in this scope. ledcDetachPin() became ledcDetach().
Mokxi takes both spellings, so an old tutorial runs here unchanged: ledcSetup(0, 5000, 8) returned 5000, and after ledcAttachPin(4, 0) and ledcWrite(0, 64) the pin measured "high 50 us" as before. That is a convenience, not what the Arduino IDE does, so port the sketch before you take it to a board. The port is three lines into two: drop the channel, attach by pin, write by pin.
// Core 2.x: by channel
ledcSetup(0, 5000, 8);
ledcAttachPin(4, 0);
ledcWrite(0, 64);
// Core 3.x: by pin
ledcAttach(4, 5000, 8);
ledcWrite(4, 64);Questions
Which ESP32 pins can do PWM?
Any pin with an output driver: LEDC reaches the pads through the GPIO matrix. Not GPIO 34 to 39, which are input only, and best not GPIO 6 to 11, which are the flash.
How do I fix "ledcSetup was not declared in this scope"?
You have core 3.x and a sketch for 2.x. Replace ledcSetup(channel, freq, bits) and ledcAttachPin(pin, channel) with ledcAttach(pin, freq, bits), and pass the pin to ledcWrite instead of the channel.
Should I use analogWrite or ledcWrite on an ESP32?
analogWrite for an LED at the default 1 kHz and 8 bits, or for code shared with an Uno. ledcAttach and ledcWrite when the frequency or resolution matters, as for a servo, a motor driver or a buzzer.
Why is my ESP32 ledcWrite not working?
Usually a 2.x sketch on core 3.x: ledcWrite(0, duty) now means pin 0, not channel 0. Otherwise ledcAttach failed (check its return), the pin is input only, or the frequency and resolution do not fit the 80 MHz clock.
What PWM frequency should I use on an ESP32?
5 kHz at 8 to 13 bits for LEDs, 20 kHz at up to 11 bits for motors you should not hear, 50 Hz for a servo, and the note itself for a buzzer, with ledcWriteTone.
Keep going
Where the facts on this page come from
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.