Unit G - ESP32 Foundations

38. GPIO, PWM, ADC and UART on ESP32

Map Uno I/O habits onto ESP32 and build a bench lamp dimmer with a clear brightness percent readout.

Estimated time 2-3 hours

Learning outcomes

  • Map Uno digitalWrite / analogWrite / Serial onto ESP32 GPIO habits
  • Dim an LED with analogWrite (LEDC) from a 12-bit pot
  • Build a bench lamp dimmer that reports brightness percent
  • Avoid using input-only pins as outputs
  • Use Serial at 115200 for ESP32 debugging

Parts and preparation

ESP32 DevKit, LED, 220-330 ohm series resistor, 10 k ohm potentiometer, breadboard, jumper wires.

Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.

GPIO, PWM, ADC and UART on ESP32 instructional connection diagram

Project: Bench lamp dimmer

Build a small bench lamp control: pot sets brightness, Serial shows percent so you can match a desired level. Same skills later drive room lights over Wi-Fi (lesson 42) - start local and tactile.

Sketch 1 links pot to PWM duty. Sketch 2 is the product readout: Lab lamp brightness percent with millis-timed Serial.

Uno skills mapped to ESP32 GPIO PWM ADC UART
Same Arduino API ideas; different pins and 3.3 V logic.

Skills you already have

digitalWrite, analogWrite, analogRead and Serial still work. Write silkscreen GPIO numbers (2, 34), not Uno D-labels. Serial at 115200.

Uno habitESP32 course note
digitalWrite(D8, HIGH)digitalWrite(gpio, HIGH) with labelled GPIO
analogWrite on ~ pinsanalogWrite via LEDC on many safe GPIOs
analogRead 0-10230-4095 after analogReadResolution(12)
Serial 9600 commonSerial 115200 for ESP32 course sketches

Pick pins that can do the job

GPIO 34-39 are input-only on classic ESP32 - pot yes, LED no. Course wiring: pot on 34, lamp LED / on-board LED on GPIO 2. Pot from 3V3.

PinThis lesson
GPIO 34Pot wiper - input-only ADC
GPIO 2Lamp LED / PWM
3V3 / GNDPot outer legs

PWM via LEDC / analogWrite

analogWrite(pin, duty) uses LEDC under the hood. map() converts 0-4095 pot codes into 0-255 duty.

int raw = analogRead(34);
int duty = map(raw, 0, 4095, 0, 255);
analogWrite(2, duty);

UART reminder

USB Serial is still Serial. Print brightness while you turn the pot so ADC and eye agree.

Wiring and safe build sequence

  1. Pot outer legs -> 3V3 and GND; wiper -> GPIO 34
  2. GPIO 2 -> 220-330 ohm -> LED anode; cathode -> GND (or on-board LED)
  3. Serial Monitor 115200
Power rule: switch off before moving wires. Arduino I/O pins are control signals; high-current loads require a driver and suitable external supply.

Worked sketch 1: Pot to PWM duty

Download .ino sketch

What this sketch is for: Discovery: turn the pot and watch LED brightness track a 0-255 duty printed on Serial.

const int potPin = 34;
const int ledPin = 2;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
  analogReadResolution(12);
}

void loop() {
  int raw = analogRead(potPin);
  int duty = map(raw, 0, 4095, 0, 255);
  analogWrite(ledPin, duty);
  Serial.println(duty);
  delay(100);
}

How the code works

  1. map scales 12-bit ADC into Uno-like 0-255 duty.
  2. GPIO 34 cannot drive the LED - it is input-only.

Worked sketch 2: Bench lamp percent readout

Download .ino sketch

What this sketch is for: Useful dimmer UI: same hardware, but Serial shows Lab lamp brightness as a percent updated every 200 ms with millis (no long delay). Match the breadboard layout below before upload.

Breadboard layout for ESP32 bench lamp: potentiometer on GPIO 34 and LED with resistor on GPIO 2
Breadboard for sketch 2: pot outer legs to 3V3 and GND, wiper to GPIO 34; LED with series resistor on GPIO 2 to GND. Click to enlarge.
const int potPin = 34;
const int lampPin = 2;
unsigned long lastPrint = 0;

void setup() {
  pinMode(lampPin, OUTPUT);
  Serial.begin(115200);
  analogReadResolution(12);
  Serial.println("Bench lamp dimmer");
}

void loop() {
  int raw = analogRead(potPin);
  int duty = map(raw, 0, 4095, 0, 255);
  analogWrite(lampPin, duty);

  if (millis() - lastPrint >= 200) {
    lastPrint = millis();
    int pct = map(duty, 0, 255, 0, 100);
    Serial.print("Lab lamp ");
    Serial.print(pct);
    Serial.println("%");
  }
}

How the code works

  1. PWM updates every loop; Serial prints on a millis timer.
  2. Percent is easier to talk about in a demo than raw duty.
  3. Later Wi-Fi remotes can show the same percent on a web page.
  4. Build from the breadboard photo: pot on GPIO 34, lamp LED on GPIO 2.

Test and record evidence

Expected result: Sketch 1: LED dims with the pot; Serial shows duty 0-255. Sketch 2: Serial shows Lab lamp N% while brightness still tracks the pot smoothly.

Practical evidence checklist

Common faults and checks
  • No dimming: GPIO 2 wiring / on-board LED mapping.
  • Stuck bright: pot on 34 with 3V3/GND.
  • Values near 1023 only: analogReadResolution(12).
  • External LED: never omit the series resistor.
Extension challenge: Add a button that freezes the current brightness (hold setpoint) until pressed again.

Check your understanding

Q1. What project is this?

Show answer

A bench lamp dimmer with brightness percent readout.

Q2. What sits behind analogWrite PWM?

Show answer

LEDC.

Q3. map range for pot to duty?

Show answer

map(raw, 0, 4095, 0, 255).

Q4. Can GPIO 34 drive the lamp LED?

Show answer

No - input-only on classic ESP32.

Q5. Serial baud?

Show answer

115200.

Q6. Why power the pot from 3V3?

Show answer

Keep wiper voltage ESP32-safe.

Q7. Why print percent in sketch 2?

Show answer

Human-readable setpoint for demos and later UIs.