4、模拟信号采集:ADC原理、ESP32的ADC通道、光敏电阻读取、电压换算
好,咱们进入第四章。前面几章我们一直在跟数字信号打交道——GPIO要么高要么低,I2C传的是字节。但真实世界不是非0即1的,温度、光照、声音,这些都是连续变化的模拟量。ESP32怎么理解这些?靠的就是ADC——模数转换器。
这一章,我会带你从ADC的原理讲起,然后直接上手ESP32的ADC通道,接一个光敏电阻,把光照强度变成电压值读出来。最后,我们还得把ADC的原始读数换算成真实的电压。嗯,这部分坑不少,我当年第一次用ESP32的ADC时就被坑过,后面会跟你细说。
4.1 ADC是什么?为什么需要它?
ADC,全称Analog-to-Digital Converter,模数转换器。说白了,就是把连续变化的电压,变成单片机能理解的数字。
你想想看,ESP32的CPU只能处理0和1,它没法直接理解“2.5伏”这个模拟量。ADC的作用就是每隔一段时间,去“看一眼”输入引脚上的电压,然后给它分配一个数字编号。这个编号的精度,取决于ADC的分辨率。
| 分辨率 | 数字范围 | 可区分的电压等级数 |
|---|---|---|
| 8-bit | 0 ~ 255 | 256 |
| 10-bit | 0 ~ 1023 | 1024 |
| 12-bit | 0 ~ 4095 | 4096 |
| 16-bit | 0 ~ 65535 | 65536 |
ESP32的ADC默认是12-bit分辨率。这意味着,如果参考电压是3.3V,它能将0~3.3V的电压范围,划分成4096个等级。每个等级对应的电压步进大约是3.3V / 4096 ≈ 0.8mV。这个精度,对于大多数传感器采集来说,完全够用了。
核心概念:ADC的“位数”越高,能分辨的电压变化就越细微。12-bit的ESP32,理论上能感知到0.8mV的电压变化。
4.2 ESP32的ADC通道与硬件限制
ESP32内部集成了两个ADC模块:ADC1和ADC2。它们各有8个通道,但ADC2的部分通道会被Wi-Fi占用,这是个非常容易踩的坑。
我个人习惯是:只要能用ADC1,就绝不用ADC2。为什么?因为Wi-Fi一开,ADC2的某些通道读数就会变得不稳定,甚至直接罢工。我在一个项目中就吃过这个亏,当时用ADC2通道6接了一个电流传感器,Wi-Fi一连接,数据全乱套了。排查了半天才发现是通道冲突。
| ADC模块 | 可用通道 | 对应GPIO | 注意事项 |
|---|---|---|---|
| ADC1 | CH0 ~ CH7 | GPIO36, 37, 38, 39, 32, 33, 34, 35 | Wi-Fi不影响,推荐使用 |
| ADC2 | CH0 ~ CH7 | GPIO4, 0, 2, 15, 13, 12, 14, 27 | Wi-Fi开启时,部分通道不可用 |
避坑指南:我曾经在一个需要同时采集传感器数据和连接Wi-Fi的项目中,因为使用了ADC2的GPIO12,导致Wi-Fi连接后传感器读数跳变。后来全部切换到ADC1的GPIO32~39,问题才解决。记住:有Wi-Fi的场景,只用ADC1。
4.3 实战:读取光敏电阻
理论说完了,咱们直接动手。光敏电阻(LDR)的阻值会随光照强度变化——光照越强,阻值越低。我们把它和一个固定电阻串联,组成分压电路,中间点接到ESP32的ADC引脚上。这样,光照变化就会引起电压变化,ADC就能读出来了。
电路连接非常简单:
- 光敏电阻一端接3.3V
- 光敏电阻另一端(信号端)接一个10kΩ电阻到GND
- 信号端同时接到ESP32的GPIO34(ADC1_CH6)
下面是一段完整的Arduino代码,用来读取光敏电阻的ADC值并换算成电压:
// 光敏电阻读取示例 - ESP32 ADC
#define LDR_PIN 34 // ADC1_CH6
void setup() {
Serial.begin(115200);
analogReadResolution(12); // 设置ADC分辨率为12位
// 可选:设置衰减,允许测量更高电压
// analogSetAttenuation(ADC_11db); // 0~3.6V范围
}
void loop() {
int adcValue = analogRead(LDR_PIN); // 读取原始ADC值 (0~4095)
float voltage = adcValue * (3.3 / 4095.0); // 换算成电压
Serial.print("ADC原始值: ");
Serial.print(adcValue);
Serial.print(" | 电压值: ");
Serial.print(voltage, 3); // 保留3位小数
Serial.println(" V");
delay(500);
}
这段代码跑起来后,你用手电筒照光敏电阻,串口输出的电压值会明显上升;用手挡住,电压值会下降。嗯,这就是模拟信号采集最直观的体验。
小技巧:如果你发现电压值始终偏低或偏高,可以试试调整衰减(attenuation)。ESP32的ADC默认衰减是0dB,测量范围约0~1.1V。调用analogSetAttenuation(ADC_11db)后,测量范围可以扩展到0~3.6V,更适合3.3V系统。
4.4 电压换算与校准
上面代码里的换算公式 voltage = adcValue * (3.3 / 4095.0),看起来没问题吧?但实际用起来,你会发现读数跟万用表测出来的电压总有偏差。为什么?
因为ESP32的ADC线性度不够好,而且不同芯片之间还有差异。3.3V参考电压本身也不是绝对精确的。我做过对比测试,在2.0V附近,ADC读数换算出来的电压,跟实际电压可能差0.05V~0.1V。这在一些对精度要求高的场景下,是不能接受的。
解决办法有两个:
- 软件校准:用万用表测出几个点的实际电压,然后做线性拟合,修正换算公式。
- 硬件校准:使用外部精密电压基准芯片(如TL431),给ADC提供一个已知的参考电压。
对于大多数物联网项目,软件校准就够用了。下面是一个简单的两点校准示例:
// 两点校准法
// 假设你测得:
// 实际电压1.0V时,ADC读数为1240
// 实际电压2.5V时,ADC读数为3100
float calibratedVoltage(int adcValue) {
// 斜率 = (2.5 - 1.0) / (3100 - 1240)
float slope = 1.5 / 1860.0;
// 截距 = 1.0 - slope * 1240
float intercept = 1.0 - slope * 1240;
return adcValue * slope + intercept;
}
经验之谈:ESP32的ADC在低电压段(0~0.5V)和高电压段(3.0~3.3V)的非线性最严重。如果你需要高精度测量,尽量让传感器信号落在0.5V~2.8V这个区间内。我一般会通过调整分压电阻,把信号“搬”到这个区间再送ADC。
4.5 本章知识体系
为了让你更直观地理解本章的知识脉络,我画了一张流程图,把ADC采集的完整链路串起来:
从这张图可以看得很清楚:物理量(光照)→ 传感器(LDR)→ 分压电路 → ADC采样 → 软件换算与校准。每一步都有它的作用,缺一不可。
4.6 本章小结
这一章我们干了三件事:
- 理解了ADC的原理——把连续电压变成离散数字,分辨率决定了精度。
- 摸清了ESP32的ADC通道——ADC1和ADC2的区别,以及Wi-Fi带来的坑。
- 动手读取了光敏电阻——从电路搭建到代码实现,再到电压换算和校准。