Produce an analog‑looking output from a digital pin to smoothly fade an LED.
Project_3_ESP32_PWM.ino, then open it in the Arduino IDE (setup: Foundation 3).
By the end of this project, you will:
ledcAttach() and ledcWrite() functions (ESP32 Core v3.x+ API).In short: we'll build a simple circuit that dims an LED using the LED PWM controller of the ESP32.

A digital pin is only ever fully ON (3.3 V) or OFF (0 V). But switch it on/off very fast and the LED's average brightness follows the fraction of time it's ON: the duty cycle:
The switching here is 5000 Hz (far faster than the eye), so you see steady brightness, not flicker. That's Pulse Width Modulation.
The ESP32 has a dedicated LED PWM controller that can generate PWM signals with different properties. With the ESP32 Core v3.x+ Arduino IDE, using it is simple: just two functions.
| Step | What you do | Our choice |
|---|---|---|
| 1. Choose a GPIO pin | Any output‑capable pin can be used as a PWM pin. | GPIO 4 |
| 2. Set the signal frequency | How many times per second the pin switches ON/OFF. Higher = smoother. | freq = 5000 (5 kHz) |
| 3. Set the duty cycle resolution | Number of bits for the duty value. 8‑bit → 0…255, 10‑bit → 0…1023, up to 16‑bit. | resolution = 8 (0–255) |
| 4. Attach PWM to the pin | Tell the ESP32 to enable PWM on that GPIO with your frequency & resolution. | ledcAttach(ledPin, freq, resolution) |
| 5. Write the duty cycle | Set the brightness value (0 = off, 255 = full on). | ledcWrite(ledPin, dutyCycle) |
The two functions that do all the work: no channel management needed:
ledcAttach(pin, freq, resolution); // 1-4: enable PWM on a GPIO ledcWrite(pin, dutyCycle); // 5: set brightness (0..255)
ledcSetup + ledcAttachPin), the v3.x+ ledcAttach() combines both steps and ledcWrite() now takes the GPIO pin, not a channel. This is much simpler!| Qty | Part | Notes |
|---|---|---|
| 1 | ESP32 · breadboard · jumper wires | - |
| 1 | LED | Has polarity: long leg = anode (+) |
| 1 | 220 Ω resistor | Limits current to protect the LED |

That's it: just three connections. (Any output‑capable pin works; the sketch uses GPIO 4.)
Let's walk through the key parts of the sketch so you understand what each line does and why. The code is only about 35 lines but packs in several important concepts.
// LED (through 220 ohm) -> GPIO 4 const int ledPin = 4; // PWM configuration const int freq = 5000; // 5 kHz: fast enough to avoid flicker const int resolution = 8; // 8‑bit → duty cycle value 0..255
ledPin identifies which GPIO the LED is on. Then we define two PWM properties: the frequency (how many times per second the pin switches on/off) and the resolution (how many distinct brightness levels: 8 bits = 256 levels, from 0 to 255). The ESP32 Core v3.x+ API manages the internal PWM channel for you, so you don't need to pick one.
setup()void setup() { Serial.begin(115200); // Configure PWM on the GPIO pin (ESP32 Core v3.x+ API) ledcAttach(ledPin, freq, resolution); }
A single function call prepares the PWM hardware:
ledcAttach() tells the ESP32: "enable PWM on GPIO 4 at 5000 Hz with 8‑bit resolution". This internally allocates a PWM channel, configures the timer, and routes the signal to the pin, all in one step.In older ESP32 Core versions you needed two separate calls (ledcSetup + ledcAttachPin), but v3.x+ simplifies it down to one.
loop()void loop() { // Fade in: duty cycle 0 -> 255 (0% -> 100% brightness) for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) { ledcWrite(ledPin, dutyCycle); delay(15); } // Fade out: duty cycle 255 -> 0 (100% -> 0% brightness) for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) { ledcWrite(ledPin, dutyCycle); delay(15); } }
| Code | What it does |
|---|---|
ledcAttach(pin, freq, resolution) | Enables PWM on the specified GPIO pin. Internally this allocates a PWM channel, sets up the hardware timer with your frequency & resolution, and connects the pin, all in one call. |
ledcWrite(pin, dutyCycle) | Sets the duty cycle (0 = always OFF, 255 = always ON). The hardware handles the fast switching: your code just says how bright. Note: in v3.x+ the first argument is the GPIO pin, not a channel. |
for (int d = 0; d <= 255; d++) | A loop that counts from 0 to 255. Each iteration increases brightness by one step. With delay(15), the full fade takes 256 × 15 ms ≈ 3.8 seconds. |
delay(15) | Pauses 15 ms between each step. Without it the LED would jump from off to full brightness in microseconds: no visible fade. |
============================================== Project 3: ESP32 PWM (Analog Output) ============================================== LED (PWM) -> GPIO 4 PWM: 5000 Hz, 8-bit (duty 0..255) The LED should fade up and down continuously. ---------------------------------------------- Fading UP (0 -> 255) Fading DOWN (255 -> 0) Fading UP (0 -> 255) ...
The sketch prints one line per direction change: printing all 256 steps would flood the monitor. Watch the LED, not the text!

In this project you've learned how to generate PWM signals with the ESP32 using the Arduino IDE. Here's a summary of the key takeaways:
ledcAttach(), and (5) write the duty cycle with ledcWrite().ledcAttach() replaces the older ledcSetup() + ledcAttachPin() two-step process, and ledcWrite() takes the GPIO pin (not a channel) as its first argument.| Symptom | Likely cause | Fix |
|---|---|---|
| LED off | Reversed LED or mis‑wired pin | Long leg toward the 220 Ω / GPIO 4 side |
| Full‑on, no fade | Didn't upload / wrong pin | Re‑upload; confirm LED on GPIO 4 |
| Fades but flickers | Frequency too low | Keep freq at 5000 Hz |
| Monitor blank | Wrong baud | Set it to 115200 |