What you will learn
- Connect a potentiometer as a voltage divider powered from 3V3.
- Explain 12-bit readings, endpoint clipping and the limits of raw ADC data.
- Scale input values from 0–4095 to the 8-bit PWM range 0–255.
- Use the pin-based LEDC API for Arduino-ESP32 3.x.
- Check behaviour at the endpoints and midpoint.
Before you start
Complete courses 01 and 02: sketch upload, Serial Monitor, LED polarity and a series resistor.
Reference setup
- Board in Arduino IDE
- ESP32 Dev Module
- Arduino-ESP32
- 3.3.12
- Serial Monitor
- 115200 baud
This is a reference profile, not an identification of the pictured board. Adapt the GPIO mapping and verify the circuit before using ESP32-C3/S2/S3 or a differently labelled board.
What we will build
Build a light dimmer that changes an LED's brightness when you turn a potentiometer. The Serial Monitor shows the input reading and output value, connecting a physical adjustment with a visible result.
Learning objectives
- Connect a potentiometer as a voltage divider.
- Read an analogue input and distinguish a raw reading from a voltage.
- Convert the range 0–4095 into the range 0–255.
- Control an LED using PWM.
- Schedule sampling and reporting at different intervals.
Prerequisites
Complete courses 01 and 02 first. You should be able to upload a sketch, open the Serial Monitor and connect an LED with a series resistor. This wiring profile uses a classic ESP32 development board with an ESP32-WROOM-32 module. It does not cover ESP32-C3, S2 or S3 boards. Locate the printed GPIO labels: numbers in this course are GPIO numbers, not physical header positions.
Equipment
| Component | Quantity | Specification and notes |
|---|---|---|
| Development board with a classic ESP32-WROOM-32 module | 1 | Match the reference GPIO labels; check physical header positions on the actual board. |
| USB data and power cable | 1 | Use the connector fitted to your board; the pictured kit lists Micro-USB. |
| Solderless breadboard | 1 | The kit lists 830 tie points. Check whether the power rails are split. |
| Red LED | 1 | Anode A and cathode K; identify polarity on the actual part. |
| 220 Ω resistor | 1 | One current-limiting resistor per LED branch; use the kit’s 220 Ω resistors. |
| 10 kΩ potentiometer | 1 | Two end terminals and a wiper W; identify their physical positions on the actual component. |
| Jumper wires | 6 | Use male-to-male or female-to-male leads to suit the board headers. Approximate quantity. |
Use a 10 kΩ potentiometer. Identify its two end terminals and its wiper before connecting it. Check the markings or documentation for the actual component. The wiper is the terminal whose resistance to the ends changes as you turn the control.
How it works
The potentiometer ends connect to 3V3 and GND. Its wiper provides a voltage between them, depending on its position. The ADC converts that voltage into a number. The sketch selects 12-bit resolution: 4096 possible values from 0 to 4095.
analogRead() returns an uncalibrated reading. For the classic ESP32, the documented measurement range with ADC_11db is approximately 0.15–3.1 V. Our potentiometer spans 0–3.3 V, so the reading can reach a limit before the control reaches its mechanical end. This is expected here; the raw number is not a precision voltage measurement. Espressif: ADC
PWM switches the LED on and off rapidly. This sketch uses 5000 Hz and an 8-bit control value from 0 to 255. Zero turns it off; 255 requests maximum on-time. A value of 128 gives approximately half the on-time, but perceived brightness is not a linear scale.
Wiring
Wiring diagram
The drawing shows electrical connections by GPIO label, not the board’s physical header layout. Power the board through USB.
| From | To | Connection |
|---|---|---|
ESP32.GPIO25 | R1.1 | Control the LED branch. |
R1.2 | D1.A | Connect the 220 Ω resistor to the LED anode. |
D1.K | ESP32.GND | Connect the cathode to common ground. |
ESP32.3V3 | RV1.END_A | One end of the 10 kΩ potentiometer. |
ESP32.GND | RV1.END_B | The other potentiometer end. |
RV1.W | ESP32.GPIO34 | Wiper to the ADC1 input. |
- Disconnect USB before changing connections. Insert the LED and potentiometer without accidentally joining different terminals in one connected breadboard group.
- Connect one potentiometer end to 3V3 and the other to GND. Connect its wiper to GPIO34.
- Connect GPIO25 to a 220 Ω resistor, the resistor's free end to the LED anode, and the LED cathode to GND.
- Check the common ground and any power rails you use. Some breadboard rails have a break in the middle.
- Compare every connection with the diagram and connection table before reconnecting USB.
Swapping the potentiometer's two end connections reverses its direction of operation; the wiper remains on GPIO34. Use 3V3 for this circuit. Do not connect the potentiometer output to 5 V.
Arduino setup
In Arduino IDE 2.x, install esp32 by Espressif Systems, target version 3.3.12. Select ESP32 Dev Module and your board's port. Follow the official installation instructions if the board package is missing. No additional libraries are needed.
Download the Arduino example (.zip) and extract it. Open potentiometer_pwm.ino inside the matching folder. Select Verify, then Upload. Open the Serial Monitor at 115200 baud. The specified version is the course's target; compilation results and physical board checks are recorded separately.
Complete sketch
#include <Arduino.h>
constexpr uint8_t LED_PIN = 25;
constexpr uint8_t POT_PIN = 34;
constexpr uint32_t PWM_FREQUENCY_HZ = 5000;
constexpr uint8_t PWM_RESOLUTION_BITS = 8;
constexpr uint32_t SAMPLE_INTERVAL_MS = 20;
constexpr uint32_t REPORT_INTERVAL_MS = 200;
bool pwmReady = false;
uint32_t lastSampleMs = 0;
uint32_t lastReportMs = 0;
uint16_t rawValue = 0;
uint8_t duty = 0;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
pinMode(POT_PIN, INPUT);
analogReadResolution(12);
analogSetPinAttenuation(POT_PIN, ADC_11db);
// Arduino-ESP32 3.x uses the GPIO pin in ledcAttach() and ledcWrite().
pwmReady = ledcAttach(LED_PIN, PWM_FREQUENCY_HZ, PWM_RESOLUTION_BITS);
if (!pwmReady) {
Serial.println("ERROR: PWM setup failed. Check the board and ESP32 core.");
return;
}
ledcWrite(LED_PIN, 0);
lastSampleMs = millis();
lastReportMs = lastSampleMs;
Serial.println("WB Maker Bridge - Turn the potentiometer to dim the LED.");
}
void loop() {
if (!pwmReady) {
delay(100);
return;
}
const uint32_t now = millis();
if (now - lastSampleMs >= SAMPLE_INTERVAL_MS) {
lastSampleMs = now;
rawValue = analogRead(POT_PIN);
duty = (static_cast<uint32_t>(rawValue) * 255U) / 4095U;
ledcWrite(LED_PIN, duty);
}
if (now - lastReportMs >= REPORT_INTERVAL_MS) {
lastReportMs = now;
Serial.print("ADC: ");
Serial.print(rawValue);
Serial.print(" | PWM: ");
Serial.println(duty);
}
}
The displayed sketch and its download use the same Arduino file. Save your own copy before experimenting.
Understanding the sketch
Find the constants for input GPIO34, output GPIO25, frequency 5000 Hz and 8-bit resolution. The ADC and PWM are configured once in setup().
ledcAttach() configures PWM on a pin, and ledcWrite() sets its duty value. This example uses the Arduino-ESP32 3.x API, where these calls take a pin. Older examples using ledcSetup() and ledcAttachPin() need adaptation. Espressif: LEDC · Migration to 3.x
Approximately every 20 ms, the sketch samples the input and calculates raw reading × 255 / 4095. Integer arithmetic discards the fractional part. A raw input of 2048 therefore produces 127. This is a calculation example: the physical midpoint need not produce exactly 2048.
Serial reporting runs approximately every 200 ms. These separate intervals provide responsive adjustment and readable output. Scheduling uses millis(), so there is no long blocking pause between readings.
Run and observe
Turn the potentiometer slowly across its range. The LED should move from off or very dim to the greatest brightness allowed by this circuit. Compare the change with the Serial Monitor values. Hold the control still for about ten seconds and note any small variations.
| Check | Record | What you examine |
|---|---|---|
| Minimum setting | Raw input and PWM | The lower limit |
| Approximate midpoint | Input, PWM and apparent brightness | Reading-to-output scaling |
| Maximum setting | Where 4095 first appears | Clipping near the upper end |
| Stationary control | Smallest and largest readings | Reading stability |
Troubleshooting
| Problem | Check and correction |
|---|---|
| Values change but the LED stays dark | Check GPIO25, LED polarity, its resistor and the common ground. |
| Reading is always 0 or 4095 | Check the wiper, GPIO34 and separate potentiometer terminals. A short flat section near an endpoint is expected. |
| Readings jump widely | Check loose connections and whether the wiper actually reaches the input. |
ledcAttach is not defined | Check the ESP32 board selection and package version 3.3.12. |
| Serial output is missing or unreadable | Check the port and 115200 baud; press EN/RESET if necessary. |
Independent challenge
Make two separate versions. In the first, brightness decreases as the reading increases. In the second, the maximum PWM value is 128. Change only the output calculation, then check both endpoints.
Check your understanding
- Why does the wiper connect to an analogue input?
- Does a PWM value of 128 request a constant 1.65 V output?
- Why can the reading reach 4095 before the mechanical end?
Answer guidance and worked solution
The wiper provides a changing voltage, which the ADC converts into a number. PWM changes the on-time of a digital output; 128 does not request a constant output voltage. The ADC can reach its limit early because its documented measurement range is narrower than the potentiometer's full travel to 3.3 V.
For the reversed version, subtract the calculated duty value from 255: raw input 0 gives 255, and input 4095 gives 0. For the limited version, calculate raw reading × 128 / 4095. The endpoints become 0 and 128. This limits on-time to approximately half the maximum, without implying exactly half the perceived brightness.
Downloads
Download the sketch and diagram. Record your board model, package version and observations.
