ESP32 ADC Complete Beginner Guide: analogRead, Attenuation Ranges and Accuracy Traps
Prerequisite: the GPIO Complete Guide. GPIO only reads HIGH or LOW — but potentiometers, LDRs, soil sensors and battery voltages are continuous. That’s where the ADC comes in.
1. What is an ADC?
The ADC (Analog-to-Digital Converter) translates a continuous voltage into a number:
ESP32 ADC: 12-bit resolution
0V → reading 0
3.3V → reading 4095 (2¹² − 1)
1.65V → reading ~2048
- 12 bits = 4096 steps, ~0.8mV per step in theory (reality is noisier — see §5)
- Voltage back-conversion:
voltage = reading × 3.3 / 4095(approximately, at default 11dB attenuation)
ADC1 vs ADC2: the invisible landmine
| Bank | Pins (classic ESP32) | Wi-Fi interaction |
|---|---|---|
| ADC1 | GPIO 32, 33, 34, 35, 36, 39 | ✅ always available |
| ADC2 | GPIO 0, 2, 4, 12, 13, 14, 15, 25, 26, 27 | ❌ dead once Wi-Fi starts |
⚠️ Iron rule: wire all analog sensors to ADC1 (GPIO 32–39). A sketch that works standalone and reads zeros after
WiFi.begin()is this exact bug. Verify any pin in the Pinout Explorer.
Newer chips (S3/C3) remap the banks (S3’s ADC1 = GPIO1–10), but the ADC2/Wi-Fi conflict persists — check the datasheet.
2. analogRead in five minutes
1. Reading a potentiometer (the classic)
#define POT_PIN 32 // must be an ADC1 pin!
void setup() {
Serial.begin(115200);
}
void loop() {
int raw = analogRead(POT_PIN); // 0 ~ 4095
float voltage = raw * 3.3 / 4095.0;
Serial.printf("raw: %d, voltage: %.2fV\n", raw, voltage);
delay(200);
}
Turning the pot should sweep the reading smoothly from 0 to 4095.
2. Attenuation: why the default range is 3.3V
The ESP32 ADC’s native range is only ~1.1V; a front-end attenuator extends it. Arduino defaults to 11dB:
| Attenuation | Usable range (approx) | Use case |
|---|---|---|
| 0 dB (ADC_0db) | 0 ~ 1.1V | Small signals, best accuracy |
| 2.5 dB (ADC_2_5db) | 0 ~ 1.5V | — |
| 6 dB (ADC_6db) | 0 ~ 2.2V | — |
| 11 dB (default ADC_11db) | 0 ~ 3.3V | General sensors |
// arduino-esp32 3.x
analogSetPinAttenuation(POT_PIN, ADC_11db);
Warning: at 11dB the response is markedly non-linear near 0V and 3.3V; the middle is decent. For high accuracy, shrink the range and divide externally.
3. The accuracy truth: ESP32 ADCs aren’t precise
Honest talk — the classic ESP32 ADC has a mediocre accuracy reputation:
- Non-linearity: equal voltage steps don’t produce equal reading steps across the range;
- Noise: 10 consecutive reads of the same voltage can swing ±20–50 counts.
Three field-proven countermeasures
#1: Multi-sample averaging (simplest, works)
int readAdcStable(int pin, int samples = 16) {
long sum = 0;
for (int i = 0; i < samples; i++) {
sum += analogRead(pin);
delayMicroseconds(100);
}
return sum / samples; // averaging 16 reads tames the noise
}
#2: Calibration (factory eFuse reference)
Each chip ships with a two-point calibration (~1100mV) that esp_adc_cal applies automatically:
#include "esp_adc_cal.h"
float readVoltageCalibrated(int pin) {
esp_adc_cal_characteristics_t chars;
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &chars);
uint32_t mv = esp_adc_cal_raw_to_voltage(analogRead(pin), &chars);
return mv / 1000.0;
}
#3: Lower the bar — for soil moisture or brightness where only the trend matters, map() straight to a percentage:
int moisture = map(readAdcStable(32), 4095, 1500, 0, 100); // dry→wet to 0~100%
moisture = constrain(moisture, 0, 100);
4. Project: battery voltage monitoring (every battery project needs it)
The ADC tops out at 3.3V; measuring an 18650 (3.0–4.2V) requires a divider:
Battery(+) ──[100kΩ]──●──[100kΩ]── GND
│
GPIO33 (an ADC1 pin)
- Divided voltage = battery × 0.5 → 4.2V becomes 2.1V ✅ safe
- 100kΩ values keep the standing drain tiny (~21µA)
#define BAT_PIN 33
#define BAT_DIVIDER 0.5
void setup() { Serial.begin(115200); }
void loop() {
float pinVolt = readAdcStable(BAT_PIN) * 3.3 / 4095.0;
float batVolt = pinVolt / BAT_DIVIDER;
Serial.printf("battery: %.2fV\n", batVolt);
delay(1000);
}
💡 Advanced: power the divider only during sampling (a MOSFET switch) and cut it fully otherwise — saves tens of µA in deep-sleep projects (see the upcoming deep-sleep article).
5. Pitfall cheat sheet (bookmark this)
| Scenario | Verdict |
|---|---|
| Choosing pins | ADC1 only (classic ESP32 = GPIO 32–39) |
| Reads all zero after enabling Wi-Fi | You’re on ADC2 — move to ADC1 |
| Stuck at 4095 | Wire broken/floating, or above range |
| Readings jump ±50 | Normal noise — average 16 samples |
| Inaccurate at range extremes | Non-linear zone — calibrate or avoid |
| Measuring 5V signals | Divide first (e.g. 10k+20k → 1/3) or the pin burns |
| Battery monitoring | 100k+100k divider into ADC1, ×2 in software |
| Sensor drifts with temperature | ADC reads voltage; drift compensation is a sensor-layer job |
6. Further reading
- 🧭 Previous: GPIO Complete Guide — digital in/out fundamentals
- ⏭️ Next: PWM & LEDC dimming (breathing LEDs / servos, coming soon)
- 🔍 Tool: Pinout Explorer — hover any pin to see its ADC bank
License: CC BY-SA 4.0. Repost & remix with attribution: ESPDIY Maker Lab - espdiy.com