← All projects
Basics Beginner

Potentiometer and PWM: the ESP32 ADC Controls LED Brightness

We read a potentiometer voltage with the 12-bit ADC and turn it into PWM with LEDC. Why to use ADC1 rather than ADC2, and how pulse-width modulation works.

Difficulty
Beginner
Time
30 min
Category
Basics
Updated

About the project

Two basic "translators" between the analog world and a digital program: the ADC turns a voltage into a number, and PWM turns a number into an "average" voltage. Turn the potentiometer knob and the LED brightness follows smoothly.

What you need

  • ESP32 DevKit
  • 10 kΩ potentiometer
  • LED and 220 Ω resistor

Wiring

Part Connection
Potentiometer, outer pins 3V3 and GND
Potentiometer, middle (wiper) GPIO34
LED GPIO25 → 220 Ω → anode; cathode → GND

The potentiometer works as a voltage divider: the wiper gives 0 to 3.3 V depending on the knob position.

ADC: from voltage to a number

analogRead() on the ESP32 returns 0…4095 (12 bits). A few details:

  • Use the ADC1 pins (GPIO32–39). ADC2 (GPIO0, 2, 4, 12–15, 25–27) does not work while Wi-Fi is on.
  • The scale is non-linear near the ends: below ~0.1 V and above 3.1 V accuracy drops. For accurate measurements use analogReadMilliVolts(): it applies the factory calibration.
  • Never feed more than 3.3 V into the pin.

PWM: from a number to brightness

An LED cannot shine "at half", but it can be switched on and off very quickly. Keep it on 30 % of the time, 5000 times a second, and the eye sees 30 % brightness. The share of "on" time is the duty cycle.

On the ESP32, PWM comes from the hardware LEDC unit. In the arduino-esp32 3.x core:

  • ledcAttach(pin, freq, bits) binds the pin and sets the frequency and resolution;
  • ledcWrite(pin, duty) sets the duty from 0 to 2^bits − 1.

Code

// Potentiometer (ADC) → LED brightness (LEDC PWM)
const int POT_PIN = 34;   // ADC1: keeps working with Wi-Fi on
const int LED_PIN = 25;

void setup() {
  Serial.begin(115200);
  ledcAttach(LED_PIN, 5000, 8);   // 5 kHz, 8-bit (0..255)
}

void loop() {
  int raw = analogRead(POT_PIN);             // 0..4095
  int duty = map(raw, 0, 4095, 0, 255);
  ledcWrite(LED_PIN, duty);
  Serial.printf("ADC=%4d  duty=%3d\n", raw, duty);
  delay(200);
}

Good to know

  • Perceived brightness is non-linear: 0 → 10 % looks like a much bigger step than 80 → 100 %. For a smooth dimmer, square the value: duty = raw * raw / 65793 (gives 0…255).
  • analogWrite() also works in core 3.x, but LEDC lets you choose the frequency and resolution yourself.
  • Above ~20 kHz PWM is inaudible (it matters for motors); for LEDs 1–5 kHz is enough.

What next

The same potentiometer can drive a servo, or a motor's speed through a MOSFET: the simulator has examples for both.