ESP32/Pins and signals/13. PWM with LEDC
Your chip
Your language
Pins and signals · 13 of 81

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.

/esp32/pwm-with-ledc · arduino · S3

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.

5.0 kHz, 12-bit
4096 steps
Frequency5.0 kHz
Resolution12 bit · 4,096 steps
Duty40% · ledcWrite(1638)
Highest resolution here
13 bit
Duty value you write
1638
Average voltage
1.32 V
4,096 brightness steps at 5.0 kHz. The pin is not outputting 1.32 V — it is switching fully on and fully off 5.0 kHz times a second, and the LED and your eye do the averaging. A multimeter averages it too, which is why it agrees; an oscilloscope does not.

Sensible starting points

What you are drivingFrequencyResolution
An LED1–5 kHz12 bit
A DC motor or a fan20–25 kHz10–12 bit
A servo50 Hz16 bit
A heater or a lamp dimmer100 Hz–1 kHz10 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.

On your S3
ChipXtensa LX7 · 2 × 240 MHz
Board settingESP32S3 Dev Module
Default I2CSDA 8 · SCL 9
Watch out forThe port vanishes after upload

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.

fade.ino
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.

When it does not work

The pin outputs nothing and there is no error

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.

A servo twitches or will not reach both ends

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.

A motor whines audibly

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.

An LED flickers when filmed

Below about 200 Hz the camera sees the switching. For anything that will be photographed, use 1 kHz or more.

Where this goes next

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.

Browse ESP32 on the forum