ESP32 ADC 模拟输入完全入门:analogRead、衰减量程与精度陷阱
前置知识:已读完 GPIO 完全入门。GPIO 只能读「高/低」两种电平,但电位器、光敏、土壤湿度、电池电压这些连续变化的量怎么办?答案就是本文的主角——ADC。
一、ADC 是什么?
ADC(Analog-to-Digital Converter,模数转换器)把连续的模拟电压「翻译」成数字:
ESP32 ADC:12 位分辨率
0V → 读数 0
3.3V → 读数 4095(2¹² − 1)
1.65V → 读数约 2048
- 12 位 = 4096 级刻度,最小分辨约 0.8mV(理论值,实际受噪声影响见第五节)
- 反过来换算电压:
电压 = 读数 × 3.3 / 4095(默认 11dB 衰减时近似成立)
ADC1 与 ADC2:新手最容易踩的「隐形雷」
| 通道 | 引脚(经典 ESP32) | 与 Wi-Fi 的关系 |
|---|---|---|
| ADC1 | GPIO 32、33、34、35、36、39 | ✅ 随时可用 |
| ADC2 | GPIO 0、2、4、12、13、14、15、25、26、27 | ❌ Wi-Fi 启用后全部失效 |
⚠️ 铁律:所有模拟传感器只接 ADC1(GPIO 32~39)。很多人程序单独跑正常,一开
WiFi.begin()读数全变 0 或报错,就是这个原因。引脚能力可用 Pinout 交互查询器 悬停确认。
S3 / C3 等新芯片的 ADC 通道划分不同(如 S3 的 ADC1 为 GPIO1~10),但「ADC2 与 Wi-Fi 互斥」的规律依然存在,查手册确认。
二、analogRead 五分钟上手
1. 读电位器(最经典示例)
#define POT_PIN 32 // 必须是 ADC1 引脚!
void setup() {
Serial.begin(115200);
}
void loop() {
int raw = analogRead(POT_PIN); // 0 ~ 4095
float voltage = raw * 3.3 / 4095.0; // 换算电压
Serial.printf("读数: %d, 电压: %.2fV\n", raw, voltage);
delay(200);
}
拧动电位器,串口打印应在 0→4095 间平滑变化。
2. attenuation(衰减):为什么默认只能测到 3.3V?
ESP32 的 ADC 原生量程只有约 1.1V,靠前置衰减器扩展。Arduino 框架默认 11dB:
| 衰减 | 可测范围(约) | 适用场景 |
|---|---|---|
| 0 dB(ADC_0db) | 0 ~ 1.1V | 测小信号,精度最好 |
| 2.5 dB(ADC_2_5db) | 0 ~ 1.5V | — |
| 6 dB(ADC_6db) | 0 ~ 2.2V | — |
| 11 dB(默认 ADC_11db) | 0 ~ 3.3V | 通用传感器 |
// arduino-esp32 3.x 设置方式
analogSetPinAttenuation(POT_PIN, ADC_11db);
警告:11dB 挡在接近 0V 和 3.3V 的两端非线性明显(见下节),中间段较准。需要高精度时优先缩小量程 + 外部分压。
三、精度真相:ESP32 的 ADC 并不「准」
必须坦诚:经典 ESP32 的 ADC 精度口碑一般。两个事实:
- 非线性:同样 0.1V 的电压增量,在量程中部和两端对应的读数增量不同;
- 噪声:同一电压连续采样 10 次,读数可能跳动 ±20~50。
三招实战对策
第 1 招:多次采样取平均(最简单有效)
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; // 16 次平均可显著压低噪声
}
第 2 招:校准(工厂 eFuse 参考电压)
每颗芯片出厂时烧录了两点校准电压(约 1100mV 处),IDF 提供 esp_adc_cal 自动修正:
#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 招:需求侧妥协——土壤湿度、亮度检测这类「看趋势」的应用,直接 map() 到百分比就够用,无需纠结绝对精度:
int moisture = map(readAdcStable(32), 4095, 1500, 0, 100); // 干→湿映射到 0~100%
moisture = constrain(moisture, 0, 100);
四、实战:电池电压监测(每个电池项目都要用)
ESP32 的 ADC 最多测 3.3V,测 18650(3.0~4.2V)必须分压:
电池(+) ──[100kΩ]──●──[100kΩ]── GND
│
GPIO33(接 ADC1)
- 分压后电压 = 电池电压 × 0.5,4.2V 时约 2.1V ✅ 安全
- 用 100kΩ 大阻值是为了降低持续漏电流(约 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("电池: %.2fV\n", batVolt);
delay(1000);
}
💡 进阶:精确做法是仅在采样瞬间给分压电路供电(加一颗 MOS 开关),日常完全断电,深睡项目可再省几十微安——深睡主题见本专栏后续文章。
五、避坑速查表(收藏级)
| 场景 | 结论 |
|---|---|
| 选引脚 | 只用 ADC1(经典 ESP32 = GPIO 32~39) |
| 开 Wi-Fi 后读数全 0 | 用了 ADC2 引脚,换 ADC1 |
| 读数 4095 不动 | 接线断开/悬空,或超过量程 |
| 读数跳变 ±50 | 正常噪声,16 次采样平均 |
| 量程两端不准 | 非线性区,用 esp_adc_cal 或避开两端 |
| 测 5V 信号 | 必须分压(如 10k+20k → 1/3),否则烧引脚 |
| 电池监测 | 100k+100k 分压到 ADC1,软件×2 |
| 传感器电阻随温度漂移 | ADC 只管电压,漂移补偿是传感器层的事 |
六、延伸阅读
- 🧭 上一篇:GPIO 完全入门——数字输入输出基础
- ⏭️ 下一篇:PWM 与 LEDC 调光(呼吸灯/舵机,即将上线)
- 🔍 工具:Pinout 交互式引脚查询器——悬停查看每颗引脚的 ADC 通道归属
版权声明:本文采用 CC BY-SA 4.0 协议授权。转载与二创请注明出处:ESPDIY 创客工坊 - espdiy.com
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