What Is PWM?

A digital pin can only be fully HIGH or fully LOW — there's no "half on." PWM fakes an in-between brightness by switching the pin on and off very rapidly. The duty cycle (the percentage of time the signal spends HIGH within each cycle) controls the apparent brightness: 0 is off, 255 is fully on, and everything in between looks dimmer or brighter to your eye.

The Hardware Setup

This reuses the exact same wiring as the Blink tutorial — no new parts needed. We're driving the same LED on GPIO 2, just with a different signal.

LED Long Leg (Anode): GPIO 2 (via Resistor)
LED Short Leg (Cathode): GND (Ground)
Resistor Value: 220Ω to 330Ω

The Arduino Source Code

The ESP32 Arduino core (3.0 and newer) supports analogWrite() directly, so you don't need to manually configure the LEDC peripheral for a simple fade. Make sure your board package is up to date in the Boards Manager.

// ESP32-S3 PWM LED Fade Tutorial - CircuitHub.pro
#include <Arduino.h>

const int LED_PIN = 2;

void setup() {
  // No pinMode() needed before analogWrite() on modern ESP32 cores,
  // but it doesn't hurt to be explicit:
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  // Fade in: 0 (off) to 255 (full brightness)
  for (int duty = 0; duty <= 255; duty++) {
    analogWrite(LED_PIN, duty);
    delay(8);
  }

  // Fade out: 255 back down to 0
  for (int duty = 255; duty >= 0; duty--) {
    analogWrite(LED_PIN, duty);
    delay(8);
  }
}

Using an older ESP32 board package? Swap the setup and loop calls for ledcAttach(LED_PIN, 5000, 8) once, then ledcWrite(LED_PIN, duty) in place of analogWrite().

Required Components

Same parts list as the Blink tutorial — if you've already built that circuit, you're ready to go:

ESP32-S3 DevKitC-1 Buy Board
220Ω Resistor Buy Resistor
Standard LED Buy LED

🚀 Take It Further: Breathing Light

Right now the fade is linear, which can look a little mechanical. Try replacing the duty cycle with a sine wave calculation ((sin(millis() / 1000.0) + 1) * 127.5) for a smoother, more organic "breathing" effect.