4、ESP32模拟信号采集:ADC原理、ESP32 ADC使用、电压采集、光敏电阻实战
好,咱们进入第四章。这一章聊的是模拟信号采集,说白了就是让ESP32去“感受”真实世界的电压变化。
你想想看,数字世界只有0和1,但现实世界是连续的。温度、光照、声音,这些都是模拟量。要让ESP32理解它们,就得靠ADC——模数转换器。
4.1 ADC原理:从模拟到数字的桥梁
ADC,全称Analog-to-Digital Converter。它的任务很简单:把连续的电压值,转换成离散的数字值。
怎么转的呢?我打个比方。你有一把尺子,量程是0到10厘米,上面有1024个刻度。你量一个物体,看它落在哪个刻度上,就得到了一个数字。ADC也是这个道理。
ESP32的ADC是12位的。12位意味着什么?2的12次方,也就是4096个刻度。参考电压通常是3.3V。所以,它能分辨的最小电压变化是:
3.3V / 4096 ≈ 0.8mV
嗯,0.8毫伏的精度,对于大多数传感器来说,够用了。
核心公式:
测量电压 = (ADC读数 / 4096) × 参考电压
举个例子:ADC读数是2048,那电压就是 (2048/4096) × 3.3V = 1.65V
我在项目中遇到过一个问题:ADC读数跳得厉害,明明电压没变,数值却上下乱窜。后来发现是参考电压不稳定。所以,稳定的参考电压是ADC精度的前提。
4.2 ESP32 ADC硬件特性
ESP32有两个ADC模块:ADC1和ADC2。它们各有8个通道,但用法不太一样。
| 特性 | ADC1 | ADC2 |
|---|---|---|
| 通道数 | 8 (GPIO32-GPIO39) | 8 (GPIO0, 2, 4, 12-15, 25-27) |
| WiFi影响 | 不受影响 | WiFi开启时不可用 |
| 推荐使用 | ✅ 优先使用 | ❌ 尽量避开 |
⚠️ 重要提醒:
ADC2在WiFi开启时会被占用。如果你同时用WiFi和ADC,ADC2的读数会乱掉。我曾经在这个坑里爬了整整一天。
所以,我的建议是:所有ADC采集,一律用ADC1的通道。省心。
另外,ESP32的ADC输入电压范围是0到3.3V。千万别超了,否则芯片会烧。如果你要测更高的电压,得用分压电阻。
4.3 实战:用ESP32读取电压
好,理论说完了,咱们直接上手。先写一个最简单的电压读取程序。
4.3.1 硬件连接
你需要:
- 一个电位器(10kΩ就行)
- 几根杜邦线
接线方式:
- 电位器中间脚 → GPIO34 (ADC1_CH6)
- 电位器一脚 → 3.3V
- 电位器另一脚 → GND
4.3.2 代码实现
#include <driver/adc.h>
void setup() {
Serial.begin(115200);
// 配置ADC1的通道6
adc1_config_width(ADC_WIDTH_BIT_12); // 12位精度
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11); // 0-3.3V范围
}
void loop() {
int adc_value = adc1_get_raw(ADC1_CHANNEL_6);
float voltage = adc_value * (3.3 / 4095.0);
Serial.print("ADC值: ");
Serial.print(adc_value);
Serial.print(" 电压: ");
Serial.print(voltage);
Serial.println(" V");
delay(500);
}
💡 个人经验:
注意我用了 ADC_ATTEN_DB_11 这个衰减参数。它表示输入范围是0-3.3V。如果你用 ADC_ATTEN_DB_0,范围只有0-1.1V。这个细节很容易忽略,我刚开始就吃过亏。
上传代码,打开串口监视器。转动电位器,你会看到ADC值和电压值跟着变化。嗯,就是这么简单。
4.4 光敏电阻实战:感知光照强度
光敏电阻,也叫光敏电阻器。它的阻值随光照强度变化:光越强,阻值越小;光越暗,阻值越大。
怎么用ADC读它呢?很简单,搭一个分压电路。
4.4.1 电路原理
把光敏电阻和一个固定电阻(比如10kΩ)串联,中间点接到ADC引脚。这样,光照变化会引起分压点电压变化,ADC就能读到了。
电路连接:
- 3.3V → 光敏电阻 → GPIO34 → 10kΩ电阻 → GND
4.4.2 代码实现
#define LIGHT_SENSOR_PIN 34
void setup() {
Serial.begin(115200);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
}
void loop() {
int light_value = adc1_get_raw(ADC1_CHANNEL_6);
// 将ADC值映射到0-100的百分比
int light_percent = map(light_value, 0, 4095, 0, 100);
Serial.print("光照强度: ");
Serial.print(light_percent);
Serial.println("%");
if (light_percent < 20) {
Serial.println("环境很暗,建议开灯");
} else if (light_percent < 60) {
Serial.println("环境正常");
} else {
Serial.println("环境很亮");
}
delay(1000);
}
📊 实际测试数据(我实测的):
| 环境 | ADC值 | 百分比 |
|---|---|---|
| 手电筒直射 | 3800 | 92% |
| 室内日光灯 | 2000 | 48% |
| 阴天窗边 | 1200 | 29% |
| 抽屉里(全黑) | 100 | 2% |
你看,数据很直观。用手遮住光敏电阻,数值会迅速下降。这就是模拟信号采集的魅力——你能真实地“触摸”到环境的变化。
4.5 避坑指南与优化技巧
做ADC采集,有几个坑是绕不开的。我一个个说。
4.5.1 噪声问题
ADC读数跳动,这是最常见的。原因可能是:
- 电源纹波太大
- 信号线太长,引入了干扰
- 参考电压不稳定
解决办法:
- 在ADC引脚对地加一个0.1μF的电容,滤除高频噪声
- 软件上做多次采样取平均
// 多次采样取平均,消除噪声
int read_adc_average(int channel, int samples) {
long sum = 0;
for (int i = 0; i < samples; i++) {
sum += adc1_get_raw(channel);
delay(1); // 每次采样间隔1ms
}
return sum / samples;
}
// 使用示例
int value = read_adc_average(ADC1_CHANNEL_6, 10);
💡 我的习惯:
我一般取10次平均,采样间隔1ms。这样既消除了噪声,又不会太慢。如果你对实时性要求高,可以减到5次。
4.5.2 非线性问题
ESP32的ADC在接近0V和接近3.3V时,线性度会变差。说白了,就是两端不准。
我曾经做过一个项目,要求测量0.1V到3.2V的电压。结果发现,低于0.2V时,ADC读数几乎不变。后来我查了手册才知道,ESP32的ADC在两端确实有非线性区。
解决办法:
- 避开两端,只使用中间80%的量程
- 或者做软件校准,用已知电压反推校正曲线
4.5.3 输入阻抗问题
ADC采样时,会从信号源吸取一点电流。如果信号源内阻太大,电压会被拉低,导致读数不准。
光敏电阻的阻值变化范围很大(几kΩ到几MΩ),这个问题尤其明显。我的建议是:
- 分压电阻选10kΩ左右,不要太大
- 如果信号源内阻很高,加一个运放做电压跟随器
4.6 本章小结
这一章我们干了三件事:
- 理解了ADC的原理——把模拟电压变成数字值
- 学会了ESP32的ADC配置——用ADC1,别用ADC2
- 实战了光敏电阻采集——搭电路、写代码、看数据
模拟信号采集是IoT项目的基础。你想想看,温度、湿度、光照、气压,哪个不是模拟量?掌握了ADC,就等于打开了感知世界的大门。
下一章,我们会把这些数据通过WiFi发到腾讯云上。到时候,你就能在手机上看实时数据了。嗯,那才是真正的“上云”。
📌 本章核心要点:
- ADC1优先,ADC2在WiFi下不能用
- 12位精度,0-4095对应0-3.3V
- 衰减参数用ADC_ATTEN_DB_11
- 多次采样取平均,消除噪声
- 光敏电阻用分压电路,简单可靠