4. 模拟信号采集:ADC原理、ESP32的ADC特性、光敏电阻与电位器读数实验
从这一章开始,我们正式接触「模拟世界」。前面几章我们玩的都是数字信号——要么高电平,要么低电平,非0即1。但现实世界不是这样的。温度是连续的,光照是渐变的,声音是波动的。要让ESP32理解这些连续变化的物理量,就得靠ADC——模数转换器。
说白了,ADC就是把连续的电压值,翻译成芯片能处理的离散数字。我刚开始做嵌入式那会儿,总觉得ADC很简单,不就是读个电压嘛。后来踩了不少坑才明白,ADC的水其实挺深的。
4.1 ADC工作原理:从模拟到数字的桥梁
ADC的核心思想,用一句话概括就是:用有限个数字阶梯,去逼近一条连续的模拟曲线。
常见的ADC架构有好几种,比如逐次逼近型(SAR)、积分型、流水线型。ESP32内部用的是逐次逼近型ADC(SAR ADC)。这种架构的优点是功耗低、速度快,非常适合物联网设备。
它的工作流程像一场「猜数字」游戏:
- 采样保持电路先「拍一张快照」,把当前时刻的电压值固定下来。
- 内部DAC从中间值开始,输出一个猜测电压。
- 比较器对比猜测电压和输入电压:猜高了就降,猜低了就升。
- 逐位逼近,直到精度达到ADC的位数上限。
举个例子,一个10位的SAR ADC,需要猜10次,每次确定一个比特位。猜完10次,一个完整的数字值就出来了。
关键参数速查表
| 参数 | 含义 | 典型值(ESP32) |
|---|---|---|
| 分辨率 | 数字输出的位数,决定阶梯数量 | 12位(0~4095) |
| 参考电压 | ADC能测量的最大电压 | 0~3.3V(内部参考) |
| 采样率 | 每秒能完成的转换次数 | 最高约200ksps |
| 量化误差 | 模拟值与数字值之间的固有误差 | ±0.5 LSB |
分辨率12位意味着什么?3.3V被分成4096份,每一份大约是0.8mV。也就是说,ESP32能分辨出0.8mV的电压变化。这个精度对于大多数传感器来说,已经够用了。
4.2 ESP32的ADC特性:别被数据手册骗了
ESP32的ADC模块有两个独立的SAR ADC:ADC1和ADC2。它们各有8个通道,但ADC2的部分通道会被Wi-Fi占用。我个人习惯只用ADC1,省心。
这里有个重要的坑,我必须提前告诉你:ESP32的ADC线性度并不完美。尤其是在电压接近0V或接近3.3V的两端,非线性失真比较明显。我曾经在一个光照监测项目里,发现ADC读数在低照度区间偏差很大,排查了半天才发现是ADC本身的非线性问题。
⚠️ 重要警告:ESP32 ADC的电压测量范围
ESP32的ADC标称输入范围是0~3.3V。但实际测试表明,当输入电压低于0.1V或高于3.1V时,ADC的线性度会显著下降。建议将有效测量范围控制在0.15V~3.1V之间,以保证精度。
另外,绝对不要给ADC引脚输入超过3.3V的电压,否则会烧毁芯片。
还有一个特性很多人不知道:ESP32的ADC在不同衰减(Attenuation)设置下,测量范围不同。衰减本质上是一个前置分压器,用来扩展测量范围。
| 衰减设置 | 理论测量范围 | 推荐使用场景 |
|---|---|---|
| ADC_ATTEN_DB_0 | 0~1.1V | 高精度小信号 |
| ADC_ATTEN_DB_2_5 | 0~1.5V | 中等信号 |
| ADC_ATTEN_DB_6 | 0~2.2V | 常用范围 |
| ADC_ATTEN_DB_11 | 0~3.3V | 全量程(推荐) |
我一般默认用ADC_ATTEN_DB_11,覆盖0~3.3V全范围。虽然精度会稍微牺牲一点,但胜在通用。
4.3 硬件准备:光敏电阻与电位器
这一节的实验,我们需要两个最基本的模拟传感器:
- 光敏电阻(LDR):阻值随光照强度变化。暗的时候阻值大(MΩ级),亮的时候阻值小(kΩ级)。
- 电位器(Potentiometer):手动旋转改变分压比,输出0~3.3V可调电压。
接线方式很简单:
- 光敏电阻与一个10kΩ固定电阻串联,组成分压电路。中间抽头接ESP32的ADC引脚(比如GPIO34)。
- 电位器的两端分别接3.3V和GND,中间抽头接另一个ADC引脚(比如GPIO35)。
为什么光敏电阻要串联一个固定电阻?因为ADC测量的是电压,不是电阻。光敏电阻本身阻值变化范围很大,如果不加固定电阻分压,电压变化范围会非常窄,ADC读不出有效数据。
💡 经验之谈:分压电阻的选值
光敏电阻的暗阻和亮阻相差很大。我建议用万用表先测一下光敏电阻在典型光照下的阻值,然后选择与它阻值相近的固定电阻。这样分压电路的灵敏度最高。10kΩ是一个比较通用的起点,但如果你在暗光环境下使用,可能需要换成100kΩ甚至1MΩ。
4.4 代码实现:读取ADC值并转换为电压
好,理论说完了,我们直接上代码。ESP32的ADC驱动在Arduino框架下封装得很好,几行代码就能跑起来。
#include <driver/adc.h>
#include <esp_adc_cal.h>
// ADC通道定义
#define LDR_ADC_CHANNEL ADC1_CHANNEL_6 // GPIO34
#define POT_ADC_CHANNEL ADC1_CHANNEL_7 // GPIO35
// ADC特性结构体,用于电压校准
static esp_adc_cal_characteristics_t adc_chars;
void setup() {
Serial.begin(115200);
// 配置ADC1的位宽为12位
adc1_config_width(ADC_WIDTH_BIT_12);
// 配置衰减为11dB,测量范围0~3.3V
adc1_config_channel_atten(LDR_ADC_CHANNEL, ADC_ATTEN_DB_11);
adc1_config_channel_atten(POT_ADC_CHANNEL, ADC_ATTEN_DB_11);
// 初始化ADC校准特性
esp_adc_cal_characterize(
ADC_UNIT_1,
ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12,
1100, // 默认Vref,单位mV
&adc_chars
);
Serial.println("ADC初始化完成");
}
void loop() {
// 读取原始ADC值(0~4095)
int ldr_raw = adc1_get_raw(LDR_ADC_CHANNEL);
int pot_raw = adc1_get_raw(POT_ADC_CHANNEL);
// 将原始值转换为电压(mV)
uint32_t ldr_voltage = esp_adc_cal_raw_to_voltage(ldr_raw, &adc_chars);
uint32_t pot_voltage = esp_adc_cal_raw_to_voltage(pot_raw, &adc_chars);
// 打印结果
Serial.print("光敏电阻 - 原始值: ");
Serial.print(ldr_raw);
Serial.print(" 电压: ");
Serial.print(ldr_voltage);
Serial.println(" mV");
Serial.print("电位器 - 原始值: ");
Serial.print(pot_raw);
Serial.print(" 电压: ");
Serial.print(pot_voltage);
Serial.println(" mV");
Serial.println("-------------------");
delay(500);
}
这段代码做了三件事:配置ADC参数、读取原始值、转换为实际电压。注意我用了esp_adc_cal_raw_to_voltage()这个函数,它内部做了校准补偿,比直接用raw * 3300 / 4095要准确得多。
你可能会问:为什么不用简单的比例换算?因为ESP32的ADC内部有非线性和增益误差,直接用比例换算会引入较大偏差。官方提供的校准库就是用来修正这些误差的。
4.5 实验现象与数据分析
把代码烧录到ESP32,打开串口监视器,你会看到类似这样的输出:
光敏电阻 - 原始值: 2450 电压: 1980 mV
电位器 - 原始值: 1024 电压: 830 mV
-------------------
光敏电阻 - 原始值: 3100 电压: 2500 mV
电位器 - 原始值: 2048 电压: 1650 mV
-------------------
用手遮住光敏电阻,原始值会下降;用手电筒照射,原始值会上升。旋转电位器,数值会平滑变化。
这里有个细节值得注意:光敏电阻的ADC值变化是非线性的。因为光敏电阻的阻值与光照强度是对数关系,而不是线性关系。如果你需要精确的光照度值(单位lux),还需要做查表或曲线拟合。
📊 数据解读要点
- ADC原始值范围:0~4095,对应0~3.3V
- 电压值范围:0~3300 mV
- 光敏电阻:电压越高,说明光照越强(电阻越小)
- 电位器:电压与旋转角度成正比(线性关系)
4.6 避坑指南:我踩过的ADC坑
做ADC实验,有几个坑几乎每个人都会遇到。我把自己踩过的列出来,你遇到了可以直接对照排查。
- 读数跳动太大:ESP32的ADC对电源噪声很敏感。如果供电不稳定,读数会上下跳动几十甚至上百。解决办法是在ADC引脚对地加一个0.1μF的滤波电容,或者用软件做多次采样取平均。
- Wi-Fi开启后ADC2读数异常:ADC2的部分通道与Wi-Fi共用硬件资源。如果你同时使用Wi-Fi和ADC2,ADC2的读数会变成随机值。解决方案很简单——只用ADC1。
- 电压超过3.3V烧坏引脚:这个我说过很多次了。ADC引脚没有过压保护,超过3.3V必烧。如果被测电压可能超过3.3V,必须用电阻分压或运放做电平转换。
- 校准后仍有偏差:ESP32的ADC校准是基于芯片内部eFuse中存储的个体参数。但不同芯片之间仍有差异。如果要求高精度,建议外接精密参考电压源做两点校准。
4.7 本章知识结构图
下面这张图总结了ADC从物理量到数字量的完整链路,以及ESP32特有的注意事项。你可以把它当作本章的「思维导图」。
4.8 小结与思考
这一章我们走通了从模拟信号到数字信号的完整链路。你学会了:
- ADC的工作原理——逐次逼近,用数字阶梯逼近模拟曲线。
- ESP32的ADC特性——12位分辨率,注意线性度问题,优先用ADC1。
- 硬件接线——光敏电阻需要分压电路,电位器直接分压。
- 代码实现——配置、读取、校准、输出,四步走。
- 常见坑——电源噪声、Wi-Fi冲突、过压、校准偏差。
现在你可以用光敏电阻做一个简单的光线检测器,或者用电位器做一个手动输入设备。这些基础能力,后面做传感器数据采集和云平台上报时都会用到。