Background reading

ESP32 PWM: Complete Guide to PWM, Pins, Frequency & Duty Cycle

ESP32 PWM lets a digital GPIO create smooth-looking LED brightness, motor-driver speed commands, buzzer tones, and other adjustable outputs. This guide explains PWM from first principles, then shows the current Arduino IDE pattern for changing duty cycle safely.

18 min read · Updated 2026-09-23

ESP32 PWM guide illustration with LED brightness waveform
Reference Guide

ESP32 PWM: Complete Guide to PWM, Pins, Frequency & Duty Cycle

The Story

A normal digital output is simple: ON or OFF. PWM is what you use when a project needs something that feels adjustable, such as dim LED light, RGB color mixing, or a speed command sent to a motor driver.

Explain Like I'm 12

PWM means the ESP32 switches a pin ON and OFF very quickly. More ON time looks brighter. Less ON time looks dimmer. The pin is still digital, but the average effect becomes useful.

Review, Testing, and References

Author: Abdul Mubeen and the ESP32 Engine editorial team. Last updated: 2026-09-23. Reviewed: educational accuracy and beginner safety. Level: Beginner. Estimated time: 18 min read.

This guide is written for learning and bench prototyping. Check the testing notes before adapting the circuit to different boards, batteries, relays, motors, or outdoor hardware.

What PWM is

PWM stands for Pulse Width Modulation. A pulse is the ON part of a digital signal. Width means how long that ON part lasts. Modulation means changing it.

An ESP32 GPIO pin normally has two simple output states: LOW and HIGH. PWM still uses those same two states, but repeats them quickly. If the pin is ON for most of each cycle, an LED looks bright. If the pin is ON for only a small part of each cycle, the LED looks dim.

That is the key idea: PWM does not create a true adjustable analog voltage on a GPIO pin. It creates an adjustable average effect by switching fast.

PWM vs simple digital HIGH/LOW

Output styleWhat the pin doesBeginner example
Digital LOWStays near 0 V.LED off.
Digital HIGHStays near 3.3 V.LED full brightness.
PWMSwitches LOW/HIGH repeatedly.LED appears dim, medium, or bright depending on duty cycle.

If you have not uploaded code yet, start with the ESP32 Arduino IDE guide. If you have never controlled a GPIO output, build Blink an LED with ESP32 first.

ESP32 PWM terminology

Frequency

PWM frequency is how many complete ON/OFF cycles happen each second. A frequency of 5000 Hz means 5000 PWM cycles per second. LED dimming usually uses a frequency high enough that your eyes do not see obvious blinking. Buzzers, motor drivers, servos, and LEDs may need different frequency ranges, so match the load instead of assuming one value fits everything.

Duty cycle

Duty cycle is the ON-time portion of each cycle. A 25% duty cycle is ON for one quarter of the cycle and OFF for the rest. A 75% duty cycle is ON for most of the cycle. For an LED, higher duty usually looks brighter.

Resolution

Resolution controls how many duty steps are available. With 8-bit resolution, duty can range from 0 to 255. With 10-bit resolution, duty can range from 0 to 1023. More resolution gives finer brightness steps, but very high frequency and high resolution compete for timing resources.

Channels

The ESP32 LEDC peripheral uses PWM channels/timers internally. Older Arduino-ESP32 examples made you choose channel numbers manually. Current Arduino-ESP32 examples can attach PWM directly to a pin with ledcAttach(pin, frequency, resolution), and then write duty with ledcWrite(pin, duty). The underlying channel idea still matters when you run many PWM outputs, but beginners usually start with one pin.

ESP32 PWM pins and GPIO selection

Many output-capable ESP32 GPIO pins can generate LEDC PWM. For a first LED brightness example, use a common output-capable pin such as GPIO18. Avoid these beginner mistakes:

  • Do not use input-only pins such as GPIO34-GPIO39 on classic ESP32 boards for PWM output.
  • Do not use pins connected to internal flash on classic ESP32 modules.
  • Be careful with boot/strapping pins if external wiring could hold them at the wrong level during reset.
  • Do not assume every ESP32 variant exposes the same safe pins in the same place. Check your board label and the ESP32 DevKit guide.

Simple LED brightness wiring

This guide uses an LED because it makes PWM visible immediately. Wire it as a low-current learning circuit:

  • ESP32 GPIO18 -> 220 ohm or 330 ohm resistor
  • Resistor -> LED anode, the longer leg
  • LED cathode, the shorter leg -> ESP32 GND

The resistor is required. PWM changes average brightness, but when the pin is ON, the LED still needs current limiting.

ESP32 PWM with Arduino IDE

Current Arduino-ESP32 cores support the pin-based LEDC helpers shown below. This keeps the beginner sketch clear: attach PWM to a pin, then write duty values to that same pin.

const int PWM_PIN = 18;
  const int PWM_FREQUENCY = 5000;
  const int PWM_RESOLUTION = 8;
  const int DUTY_MAX = (1 << PWM_RESOLUTION) - 1;

  void setup() {
    Serial.begin(115200);

    bool attached = ledcAttach(PWM_PIN, PWM_FREQUENCY, PWM_RESOLUTION);
    if (!attached) {
      Serial.println("PWM attach failed. Check board selection and pin.");
      while (true) {
        delay(1000);
      }
    }

    Serial.println("ESP32 PWM fade ready");
  }

  void loop() {
    for (int duty = 0; duty <= DUTY_MAX; duty += 5) {
      ledcWrite(PWM_PIN, duty);
      Serial.print("Duty: ");
      Serial.println(duty);
      delay(20);
    }

    for (int duty = DUTY_MAX; duty >= 0; duty -= 5) {
      ledcWrite(PWM_PIN, duty);
      Serial.print("Duty: ");
      Serial.println(duty);
      delay(20);
    }
  }

Expected behavior: the LED fades from off to bright, then bright to off. Serial Monitor prints changing duty values from 0 toward 255 and back down because this example uses 8-bit resolution.

How to change duty cycle

With 8-bit resolution, these fixed duty values are easy to understand:

Duty valueApproximate duty cycleLED effect
00%Off
6425%Dim
12850%Medium
19275%Bright
255100%Full brightness

To hold one brightness level, call ledcWrite(PWM_PIN, 128) in your code. To build a dimmer, change the duty value when a button event, analog reading, or web command changes.

PWM frequency and resolution tradeoff

Frequency and resolution are connected. A very high PWM frequency leaves less timing room for very fine duty steps. A very high resolution gives more duty steps, but may limit how high the frequency can be. For beginner LED dimming, 5000 Hz and 8-bit resolution are a practical starting point.

If an LED flickers, try a higher frequency or check for loose wiring. If brightness jumps feel coarse, try more resolution or smaller duty changes. If a motor driver behaves strangely, read that driver module's documentation before changing random PWM values.

Legacy Arduino-ESP32 PWM examples

You will still see older tutorials using code like this:

ledcSetup(channel, frequency, resolution);
  ledcAttachPin(pin, channel);
  ledcWrite(channel, duty);

That style is common in older Arduino-ESP32 core examples and in some existing learning material. For new code on current Arduino-ESP32 cores, prefer the pin-based style:

ledcAttach(pin, frequency, resolution);
  ledcWrite(pin, duty);

If old code fails with an error such as ledcAttachPin was not declared, check your installed ESP32 board package version and update the PWM API style.

Common PWM mistakes

  • Using PWM without a resistor: an LED still needs current limiting.
  • Expecting true analog voltage: PWM is fast switching, not a DAC output.
  • Driving motors directly: GPIO pins send control signals, not motor power.
  • Choosing input-only GPIO: input-only pins cannot drive PWM output.
  • Confusing frequency with brightness: duty cycle controls average brightness; frequency controls switching speed.
  • Mixing old and new LEDC APIs: keep code style consistent for your ESP32 Arduino core version.

Troubleshooting ESP32 PWM

ProblemLikely causeFix
LED never turns onWrong GPIO, reversed LED, missing ground, or resistor not in series.Use GPIO18, check LED polarity, and trace GPIO18 -> resistor -> LED -> GND.
LED is always full brightLED may be wired to 3.3 V instead of the PWM pin.Move the circuit so GPIO18 drives the resistor/LED path.
Compile error for ledcAttachWrong board selected or older ESP32 board package.Select an ESP32 board in Arduino IDE and update the ESP32 board package if needed.
Compile error for ledcSetup or ledcAttachPinOld channel-based code on a newer Arduino-ESP32 core.Convert to ledcAttach(pin, freq, resolution) and ledcWrite(pin, duty).
Visible flickerLow frequency, slow duty changes, weak connection, or unsuitable load.Return to 5000 Hz for the LED example and inspect wiring.
Motor does not respondGPIO connected directly to motor or no shared ground with driver.Use a motor driver with separate motor power and common ground.

Practical next steps

Use the idea here in real ESP32 Engine builds:

Frequently Asked Questions

PWM means Pulse Width Modulation. The ESP32 switches a GPIO ON and OFF quickly and changes the ON-time percentage, called duty cycle, to create an adjustable average effect.

Many output-capable ESP32 GPIO pins can generate PWM through the LEDC peripheral. Avoid input-only pins such as GPIO34-GPIO39 on classic ESP32 boards, flash pins, and boot-sensitive pins unless you understand the board.

A PWM channel is a hardware timing resource used by the ESP32 LEDC peripheral. Current Arduino-ESP32 APIs can allocate this for you when you attach a pin, while older examples often selected channel numbers manually.

5000 Hz is a practical beginner value for LED dimming in Arduino examples. Other loads may need different frequencies.

Duty cycle is the percentage of each PWM cycle that the signal is ON. Higher duty cycle sends more average energy to the load.

No. The GPIO still switches between digital LOW and HIGH. PWM creates an analog-like average effect through fast switching.

No. Use PWM as a control signal into a motor driver, MOSFET circuit, or suitable module. Do not connect a motor directly to a GPIO pin.

For current Arduino-ESP32 cores, prefer ledcAttach(pin, frequency, resolution) and ledcWrite(pin, duty). Older tutorials may use ledcSetup, ledcAttachPin, and channel-based ledcWrite as legacy API context.

Conclusion

ESP32 PWM is fast digital switching with a controlled duty cycle. Use duty cycle to change average output energy, choose frequency based on the load, choose resolution based on the duty steps you need, and use output-capable GPIO pins with suitable external drivers for anything larger than a small LED. Start with LED brightness, then apply the same idea to RGB projects, motor drivers, buzzers, and automation outputs.