PROJECT 2 · ANALOG INPUT

Analog Inputs: Reading a Potentiometer

Measure a range of voltage (0–3.3 V) as a number 0–4095 with the ESP32's ADC.

Builds on: Project 1's digitalRead(), now reading a range of voltage instead of just two states.

⏱ ~20 min 📊 Beginner 🔖 Prerequisite: Project 1
⬇ Download the sketch Save Project_2_ESP32_Analog_Inputs.ino, then open it in the Arduino IDE (setup: Foundation 3).
🎲 Try it without hardware 📄 diagram.json Open the Wokwi simulator, then open or download diagram.json above and paste its wiring in to load the circuit.

🎯 Objective

By the end of this project, you will:

  • Understand the difference between digital and analog signals.
  • Learn how the ESP32's Analog-to-Digital Converter (ADC) works.
  • Read a potentiometer (variable resistor) and convert its analog voltage to a numeric value (0–4095).
  • Use the analogRead() function and the Serial Monitor to observe real-time sensor data.
  • Convert raw ADC readings into voltage using a simple proportional formula.

1Analog vs. Digital: How It Works

  • A digital input sees only HIGH or LOW (Project 1).
  • An analog input measures a continuous voltage 0–3.3 V and converts it to a number via the ADC.

The ESP32 ADC is 12‑bit, so readings run 0 … 4095:

0–3.3 V maps to 0–4095
0 V → 0 · 3.3 V → 4095 · in between → proportional.

2Choosing the ADC Pin

The ADC pin here

We use GPIO 4 ANALOG (ADC2, channel 0): the same physical pin that was a digital input in Project 1, now used with analogRead().

For later: GPIO 4 is on ADC2, which can't be read while Wi‑Fi is on. Fine here (no Wi‑Fi). In Wi‑Fi projects use an ADC1 pin (GPIO 32, 33, 34, 35, 36, 39).
⚠️ Common misconception: it's easy to assume any ADC-capable pin works the same in every project. GPIO 4 (ADC2) reads perfectly fine here, but the moment a later project calls WiFi.begin(), that same pin silently stops updating: no error, no crash, just a reading that freezes. If a sensor "stops working" only after you add Wi‑Fi, this is almost always why.

Which pins can do analog input?

Grab your ESP32 board and locate the pins highlighted with a red border in the figure below: these are your analog-capable pins:

ESP32 DEVKIT V1 pinout with ADC pins highlighted in red
ESP32 DEVKIT V1 (30‑pin version): ADC pins are outlined in red. The board has 15 ADC pins, each with 12‑bit resolution (values from 0 to 4095).

Notice that not every pin can read an analog signal. The red‑outlined pins are the ones connected to the internal ADC hardware. In this project we use GPIO 4, but you could use any of the other ADC pins just as well.

3Parts Required

QtyPartNotes
1ESP32 DEVKIT V1 · breadboard · jumper wires-
1Potentiometer (10 kΩ)3 legs: two ends + a middle wiper

4Wiring the Circuit

Potentiometer: outer legs to 3.3 V and GND, wiper to GPIO 4
Outer legs → 3.3 V and GND · middle leg (wiper) → GPIO 4.
  1. One outer leg → 3.3 V
  2. Other outer leg → GND
  3. Middle leg (wiper) → GPIO 4

Turning the knob taps off 0 V → 3.3 V, and the ADC reports the matching 0–4095 value.

5Code Walkthrough: Understanding the Sketch

Let's walk through the key parts of the sketch so you understand what each line does and why.

Step 1: Pin definition & variables

// The potentiometer's middle leg (wiper) connects to GPIO 4
const int potPin = 4;    // ADC2, channel 0, an analog-capable pin

int potValue = 0;           // Variable to store the ADC reading (0–4095)

const int potPin = 4; gives the pin a human-readable name. Using a constant (const) prevents accidentally changing the pin number later. The variable potValue will hold whichever value analogRead() returns.

Step 2: one-time preparation in setup()

void setup() {
  Serial.begin(115200);     // Open the serial connection at 115200 baud
  delay(500);                // give the Serial Monitor a moment to connect
  ...                        // then print the banner
}

Serial.begin(115200) opens a USB link between the ESP32 and your computer. Without this, the Serial Monitor would stay blank. Think of it as turning on the communication channel.

Step 3: loop() reads → converts → prints → repeats

void loop() {
  // 1️⃣ READ the analog voltage on GPIO 4
  potValue = analogRead(potPin);          // returns 0 .. 4095

  // 2️⃣ CONVERT the raw number to actual voltage (0–3.3 V)
  float voltage = potValue * 3.3 / 4095.0;

  // 3️⃣ PRINT both values to the Serial Monitor
  Serial.printf("Pot: %4d / 4095   (~%.2f V)\n", potValue, voltage);

  // 4️⃣ WAIT 500 ms before the next reading
  delay(500);
}

Key concepts: what each line really does

CodeWhat it does
analogRead(potPin)Measures the voltage on GPIO 4 and returns an integer 0–4095. The ESP32's built-in ADC hardware compares the input voltage to the 3.3 V reference and produces a proportional digital number.
potValue * 3.3 / 4095.0A proportion: if 4095 = 3.3 V, then any reading × (3.3 ÷ 4095) gives the matching voltage. Example: 2048 × 3.3 ÷ 4095 ≈ 1.65 V (exactly half of 3.3 V).
Serial.printf()Prints formatted text to the monitor. %4d = integer, right-aligned in 4 columns; %.2f = decimal number with 2 digits after the dot.
delay(500)Pauses 500 ms (half a second). Without it, the ESP32 would read and print thousands of times per second: too fast to read.

Expected output on the Serial Monitor

==============================================
 Project 2: ESP32 Analog Inputs (ADC)
==============================================
Potentiometer -> GPIO 4
12-bit ADC: 0 = 0 V ... 4095 = 3.3 V
Turn the knob and watch the value change.
----------------------------------------------
Pot:    0 / 4095   (~0.00 V)
Pot: 2047 / 4095   (~1.65 V)
Pot: 4095 / 4095   (~3.30 V)
Try the Serial Plotter (Tools → Serial Plotter, 115200 baud): turning the knob draws a live graph, a great way to see the analog signal change smoothly as you turn.

6Understanding the ADC (Non-Linearity)

ESP32 ADC voltage vs reading, non-linear at the ends
Readings flatten near 0 V and 3.3 V: you can't tell ~3.2 V from ~3.3 V.

This matters for precise measurements; for a knob or light sensor it's fine.

Going further: analogReadMilliVolts() and attenuation

The ESP32 ships with a factory‑measured calibration curve for its ADC, and analogReadMilliVolts() applies it for you:

int millivolts = analogReadMilliVolts(potPin);   // calibrated, in mV

Where analogRead() gives you a raw 0–4095 number you then scale yourself (and scale wrong near the ends, per the graph above), analogReadMilliVolts() returns an already‑corrected voltage in millivolts, straightening out most of the non‑linearity you just saw.

The other half of the calibration is attenuation: how much the ADC's input range is scaled before conversion. The default lets the ESP32 read up to about 3.3 V, but you can narrow that window for more precision on a smaller range:

analogSetAttenuation(ADC_11db);   // default: ~0-3.3 V (what this project assumes)
// ADC_0db    ~0-1.1 V  (finest resolution, smallest range)
// ADC_2_5db  ~0-1.5 V
// ADC_6db    ~0-2.2 V
// ADC_11db   ~0-3.3 V  (widest range)

Call analogSetAttenuation() once in setup(), before you start reading. A lower attenuation trades range for precision: useful if your sensor's signal never actually reaches 3.3 V and you want more of the ADC's 4096 steps spent on the range it does use.

Going further: swap in a photoresistor (LDR)

The kit's photoresistor module reads exactly like the potentiometer you just wired: three pins, one of them an analog signal.

LDR module pinESP32
VCC3.3 V
GNDGND
AO (analog out)GPIO 4 (same pin, same code)

Swap the potentiometer for the LDR module on those three pins and the sketch above needs no changes: analogRead(potPin) now reports more light → higher voltage → a bigger number, instead of turning a knob. Some modules also break out a DO (digital out) pin, driven by an onboard comparator and trimmer, useful for a simple "is it dark" threshold without touching the ADC at all.

This is the sensor Project 13's idea menu (Smart room monitor, Light‑and‑sound alert) expects.

Predict, then check. Before turning the knob: what value does analogRead() report with the knob turned fully counterclockwise? Fully clockwise? Turn it to each end and check the Serial Monitor.

7Demonstration

Animated demo: turning the potentiometer sweeps the ADC value from 0 to 4095
Fully one way → 0 · fully the other → 4095.

🎯 Knowledge Check

1. What does analogRead() return on the ESP32?

2. Which ADC bank stops updating when Wi-Fi is active?

3. How do you convert an ADC reading to voltage?

4. Why does the ESP32's ADC produce non-linear readings near 0 V and 3.3 V?

5. What is the resolution of the ESP32's ADC?

9Wrapping Up: What You've Learned

In this project you've learned how to read analog inputs using the ESP32 with the Arduino IDE. Here's a summary of the key takeaways:

  • The ESP32 DEVKIT V1 DOIT board (30‑pin version) has 15 ADC pins you can use to read analog inputs.
  • These pins have a 12‑bit resolution, which means you can get values from 0 to 4095.
  • To read a value in the Arduino IDE, you simply use the analogRead() function.
  • The ESP32 ADC pins don't have a linear behavior at the extremes of the voltage range. You'll probably won't be able to distinguish between 0 and 0.1 V, or between 3.2 and 3.3 V. Keep that in mind when using the ADC pins for precise measurements.

Now try

  • Map the pot to LED brightness: Combine this project's analogRead() with Project 3's PWM to fade an LED using the potentiometer knob.
  • Swap in a photoresistor: Replace the potentiometer with an LDR (light-dependent resistor) using the same wiring pattern. The code stays the same; only the physical behavior changes.

10Troubleshooting

SymptomLikely causeFix
Stuck at 0 or 4095An outer leg not on 3.3 V / GNDRecheck both outer legs
Value jumps randomlyWiper not on GPIO 4Confirm middle leg → GPIO 4
Monitor blank / garbageWrong baudSet it to 115200
Never reaches 4095Normal ADC non‑linearityExpected near the top