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 | 实际应用中通常用不到这么快 |
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);
}
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");
3.5 知识体系:ADC信号链路
下面这张图,是我画的一个ADC信号链路。从模拟信号到数字量,再到应用层,每一步都有讲究。
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次读数的平均值,效果很好。