3、模拟信号采集与ADC:ESP32的ADC特性、模拟输入读取、光敏电阻与电位器实战、电压换算

好,咱们进入第三章。这一章聊的是模拟信号采集,说白了就是让ESP32能“看懂”真实世界的连续电压。

数字世界只有0和1,但现实世界里的温度、光照、声音,都是连续变化的模拟量。ESP32怎么感知这些?靠的就是ADC——模数转换器。

我个人习惯把ADC比作一把“电压尺子”。它把连续的电压值,量化为一个个离散的数字。这把尺子的精度,直接决定了你能分辨多小的电压变化。

3.1 ESP32的ADC特性

ESP32内部集成了两个12位的SAR ADC,分别是ADC1和ADC2。这里有个关键点:ADC2在Wi-Fi开启时会被占用。我早期做项目时踩过这个坑,Wi-Fi一开,ADC2的读数就全乱了。所以,我建议你优先使用ADC1的通道,省心。

特性 参数 备注
分辨率 12位 0~4095,共4096个量化等级
输入电压范围 0 ~ 3.3V 绝对不要超过3.3V,会烧引脚
ADC1通道 GPIO32~GPIO39 推荐使用,不受Wi-Fi影响
ADC2通道 GPIO0,2,4,12~15,25~27 Wi-Fi开启时读数不稳定
采样速率 最高约200ksps 实际应用中通常用不到这么快
⚠️ 重要警告: ESP32的ADC输入电压绝对不能超过3.3V。我曾经见过有人直接把5V传感器输出接到ADC引脚,结果芯片直接冒烟。如果你需要测量高于3.3V的电压,必须使用电阻分压电路。

3.2 模拟输入读取:从硬件到代码

先看硬件连接。电位器有三个引脚:两端接VCC和GND,中间抽头接ADC输入。光敏电阻则需要配合一个固定电阻组成分压电路。

这是光敏电阻的典型接法:

VCC (3.3V)
  │
  ├── 固定电阻 (10kΩ) ──┬── ADC引脚 (GPIO34)
  │                     │
  └── 光敏电阻 (LDR) ───┘
  │
 GND

代码怎么写?其实很简单。ESP-IDF和Arduino框架都提供了现成的API。我个人更喜欢用Arduino框架做原型验证,快。

// Arduino框架下的ADC读取示例
const int adcPin = 34;  // ADC1通道6

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);  // 设置分辨率为12位
  analogSetAttenuation(ADC_11db);  // 设置衰减,允许0~3.3V输入
}

void loop() {
  int rawValue = analogRead(adcPin);
  float voltage = rawValue * (3.3 / 4095.0);
  
  Serial.print("原始值: ");
  Serial.print(rawValue);
  Serial.print("  电压: ");
  Serial.print(voltage, 3);
  Serial.println(" V");
  
  delay(500);
}
💡 个人经验: 我习惯在正式项目中用ESP-IDF的adc1_config_width()和adc1_config_channel_atten()函数,控制更精细。但做快速验证时,Arduino的analogRead()确实方便。

3.3 电压换算:从原始值到真实电压

ADC读到的原始值是0~4095,怎么换算成实际电压?公式很简单:

实际电压 = 原始值 × (参考电压 / 4095)

参考电压默认是3.3V。但这里有个坑——ESP32的ADC线性度并不完美。你想想看,芯片内部的参考电压其实会有微小波动,尤其在供电电压不稳的时候。

我做过对比测试,发现ESP32的ADC在0.1V以下和3.2V以上区域,非线性比较明显。中间段(0.2V~3.0V)相对准确,误差大约在±2%以内。

输入电压 (V) 理论原始值 实测原始值(典型) 误差
0.5 620 610~625 ±1.6%
1.65 2048 2020~2060 ±1.0%
3.0 3727 3680~3740 ±1.3%
3.3 4095 4050~4095 ±1.1%
🔧 工程建议: 如果项目对精度要求高(比如电池电压监测),我建议做两点校准:用万用表测一个低电压和一个高电压,然后做线性修正。代码里加个偏移量和增益系数就行。

3.4 光敏电阻与电位器实战

咱们把两个实战案例放在一起对比,你就能看出ADC的应用套路了。

3.4.1 电位器:角度或位置检测

电位器本质上是一个可变电阻分压器。旋转旋钮,中间抽头的电压从0V到3.3V线性变化。代码里直接读ADC,换算成电压,再映射到角度(0~270度或0~300度)。

// 电位器角度映射
int raw = analogRead(34);
float voltage = raw * (3.3 / 4095.0);
float angle = map(raw, 0, 4095, 0, 300);  // 假设电位器旋转范围300度
Serial.print("角度: ");
Serial.print(angle);
Serial.println("°");

3.4.2 光敏电阻:光照强度检测

光敏电阻的阻值随光照变化——光照越强,阻值越低。但它的变化是非线性的,而且不同型号差异很大。我一般不会直接拿ADC值当光照强度,而是先做标定。

标定方法很简单:在暗室和强光下分别记录ADC值,然后建立映射关系。

// 光敏电阻简易标定
// 暗室(0 lux): ADC ≈ 3800
// 强光(1000 lux): ADC ≈ 500

int raw = analogRead(34);
int lux = map(raw, 3800, 500, 0, 1000);  // 注意:数值是反的
lux = constrain(lux, 0, 1000);

Serial.print("光照强度: ");
Serial.print(lux);
Serial.println(" lux");
⚠️ 避坑指南: 光敏电阻的响应速度很慢,大约几十毫秒。我曾经在做一个光控开关时,没加软件滤波,结果读数跳得厉害。后来加了个简单的滑动平均滤波(取最近10次读数的平均值),效果立竿见影。

3.5 知识体系:ADC信号链路

下面这张图,是我画的一个ADC信号链路。从模拟信号到数字量,再到应用层,每一步都有讲究。

ADC信号链路与知识体系 模拟信号源 电位器 / 光敏电阻 信号调理 分压 / 滤波 ESP32 ADC 12位 / 0~4095 软件读取 analogRead() 电压范围:0~3.3V 分辨率:12位 衰减设置:11dB 通道选择:ADC1 🔑 核心要点 • 优先使用ADC1通道(GPIO32~39),避免Wi-Fi干扰 • 输入电压绝对不超过3.3V,必要时用电阻分压 • 电压换算公式:V = raw × (3.3 / 4095),但要注意非线性区

3.6 实战中的几个坑

最后,我把自己这些年踩过的坑总结一下,你遇到了能少走弯路。

  • 引脚电压过高:ESP32的ADC引脚耐压只有3.3V。我见过有人把12V传感器直接接上去,结果芯片当场报废。一定要用电阻分压或运放调理。
  • Wi-Fi干扰ADC2:前面说过,ADC2在Wi-Fi开启时读数不准。我早期一个项目用了GPIO12做模拟输入,Wi-Fi一开数据就乱跳,排查了两天才发现是通道问题。
  • ADC非线性:ESP32的ADC在两端区域非线性明显。如果你需要高精度测量,建议避开0~0.1V和3.2~3.3V这两个区间,或者做软件校准。
  • 电源噪声:ESP32的ADC参考电压来自VDD。如果供电有纹波,ADC读数也会跟着抖。我习惯在ADC引脚对地加一个0.1μF的电容,能滤掉不少高频噪声。
  • 软件滤波:单次ADC读数不可靠,尤其是光敏电阻这种慢变信号。我一般用滑动平均或中值滤波,取10~20次读数的平均值,效果很好。
💡 我的习惯: 每次做ADC项目,我都会先用万用表测一下实际电压,和ADC读数对比。如果偏差超过2%,就做一次两点校准。这个习惯帮我避免了好几次生产批次差异导致的问题。