PROJECT 3 · PWM OUTPUT

PWM (Analog Output): Fading an LED

Produce an analog‑looking output from a digital pin to smoothly fade an LED.

⬇ Download the sketch Save Project_3_ESP32_PWM.ino, then open it in the Arduino IDE (setup: Foundation 3).

🎯 Objective

By the end of this project, you will:

In short: we'll build a simple circuit that dims an LED using the LED PWM controller of the ESP32.

An LED shown at increasing brightness levels
Same LED, different duty cycles → different brightness.

1 · What is PWM?

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:

0% · off
25% · dim
50% · medium
75% · bright
100% · full

The switching here is 5000 Hz (far faster than the eye), so you see steady brightness, not flicker. That's Pulse Width Modulation.

PWM also dims lights, sets motor speed, and (Project 6) mixes colors on an RGB LED.

2 · The ESP32 LED PWM Controller

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-by-step configuration

StepWhat you doOur choice
1. Choose a GPIO pinAny output‑capable pin can be used as a PWM pin.GPIO 4
2. Set the signal frequencyHow many times per second the pin switches ON/OFF. Higher = smoother.freq = 5000 (5 kHz)
3. Set the duty cycle resolutionNumber 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 pinTell the ESP32 to enable PWM on that GPIO with your frequency & resolution.ledcAttach(ledPin, freq, resolution)
5. Write the duty cycleSet 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)
Important: Unlike the older ESP32 Core API (ledcSetup + ledcAttachPin), the v3.x+ ledcAttach() combines both steps and ledcWrite() now takes the GPIO pin, not a channel. This is much simpler!
Any output pin works: you can use any pin that can act as an output. All output-capable pins can be used as PWM pins.

3 · Parts Required

QtyPartNotes
1ESP32 · breadboard · jumper wires-
1LEDHas polarity: long leg = anode (+)
1220 Ω resistorLimits current to protect the LED

4 · Wiring the Circuit

LED on GPIO 4 through a 220 ohm resistor to GND
GPIO 4 → 220 Ω → LED → GND.
  1. GPIO 4220 Ω → LED anode (long leg)
  2. LED cathode (short leg)GND

That's it: just three connections. (Any output‑capable pin works; the sketch uses GPIO 4.)

5 · Code Walkthrough: Understanding the Sketch

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.

Step 1: Pin definition & PWM configuration constants

// 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.

Step 2: configuring the PWM hardware in 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:

In older ESP32 Core versions you needed two separate calls (ledcSetup + ledcAttachPin), but v3.x+ simplifies it down to one.

Step 3: fading up, then down, forever in 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);
  }
}

Key concepts: what each part really does

CodeWhat 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.

Expected output on the Serial Monitor

==============================================
 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!

6 · Demonstration

LED fading on a breadboard
The LED breathes smoothly brighter and dimmer.

7 · Wrapping Up: What You've Learned

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:

8 · Troubleshooting

SymptomLikely causeFix
LED offReversed LED or mis‑wired pinLong leg toward the 220 Ω / GPIO 4 side
Full‑on, no fadeDidn't upload / wrong pinRe‑upload; confirm LED on GPIO 4
Fades but flickersFrequency too lowKeep freq at 5000 Hz
Monitor blankWrong baudSet it to 115200
← Project 2 · Analog Inputs Project 4 · PIR Motion Sensor →