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 的关系
ADC1GPIO 32、33、34、35、36、39✅ 随时可用
ADC2GPIO 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 精度口碑一般。两个事实:

  1. 非线性:同样 0.1V 的电压增量,在量程中部和两端对应的读数增量不同;
  2. 噪声:同一电压连续采样 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 只管电压,漂移补偿是传感器层的事

六、延伸阅读


版权声明:本文采用 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

BankPins (classic ESP32)Wi-Fi interaction
ADC1GPIO 32, 33, 34, 35, 36, 39✅ always available
ADC2GPIO 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:

AttenuationUsable range (approx)Use case
0 dB (ADC_0db)0 ~ 1.1VSmall 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.3VGeneral 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:

  1. Non-linearity: equal voltage steps don’t produce equal reading steps across the range;
  2. 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)

ScenarioVerdict
Choosing pinsADC1 only (classic ESP32 = GPIO 32–39)
Reads all zero after enabling Wi-FiYou’re on ADC2 — move to ADC1
Stuck at 4095Wire broken/floating, or above range
Readings jump ±50Normal noise — average 16 samples
Inaccurate at range extremesNon-linear zone — calibrate or avoid
Measuring 5V signalsDivide first (e.g. 10k+20k → 1/3) or the pin burns
Battery monitoring100k+100k divider into ADC1, ×2 in software
Sensor drifts with temperatureADC 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