3. 模拟信号采集:ADC原理、ESP32的ADC特性、电位器与光敏电阻数据采集、电压换算
模拟信号采集,说白了就是把自然界里连续变化的物理量(比如温度、光照、声音)变成单片机能认的0和1。ESP32虽然有Wi-Fi和蓝牙,但它的ADC(模数转换器)才是连接真实世界的眼睛。这一章,我们就把它彻底搞明白。
3.1 ADC基本原理:从模拟到数字的桥梁
ADC的核心任务很简单:测量输入电压,然后给这个电压分配一个数字编号。比如一个10位的ADC,它能输出0~1023共1024个数字。如果参考电压是3.3V,那么0对应0V,1023对应3.3V,中间的值按比例映射。
我刚开始做嵌入式时,总觉得ADC就是读个数值,没什么技术含量。直到有一次做工业传感器采集,发现数值跳得厉害,才意识到ADC的精度、采样率、参考电压这些参数有多重要。
ADC核心公式:
实际电压 = (ADC读数 / ADC最大值) × 参考电压
例如:10位ADC读数为512,参考电压3.3V,则实际电压 = (512 / 1023) × 3.3 ≈ 1.65V
3.2 ESP32的ADC特性:你需要知道的坑
ESP32的ADC模块有两个独立单元:ADC1和ADC2。ADC1有8个通道(GPIO32~39),ADC2有10个通道,但ADC2会被Wi-Fi占用。所以,我个人的习惯是:所有模拟采集都用ADC1,省得后面调试时莫名其妙读不到数据。
| 参数 | 说明 | 注意事项 |
|---|---|---|
| 分辨率 | 12位(0~4095) | 可通过函数设置为10位或12位 |
| 参考电压 | 内部参考约1.1V | 实际测量范围受衰减器设置影响 |
| 衰减器 | 可设置0dB、2.5dB、6dB、11dB | 11dB衰减时最大可测约3.3V |
| 非线性 | 中间段较准,两端有偏差 | 建议避开0~0.1V和3.2~3.3V区域 |
| 采样率 | 最高约200ksps | 实际应用中建议降低采样率以稳定 |
我曾经踩过的坑:ESP32的ADC在靠近0V和3.3V时非线性非常严重。有一次做电池电压监测,3.3V输入时读数只有3950(12位),换算后只有3.18V,误差超过3%。后来我加了分压电阻,把测量范围压缩到0.2V~3.0V,精度才恢复正常。
3.3 电位器数据采集:动手第一步
电位器是最简单的模拟输入设备。它就是一个可变电阻,中间抽头的电压随旋转角度变化。接法很简单:一脚接3.3V,一脚接GND,中间脚接ADC输入。
下面是我常用的初始化代码,加了衰减器设置和分辨率配置:
#include <driver/adc.h>
void adc_init() {
// 配置ADC1通道0(GPIO36)
adc1_config_width(ADC_WIDTH_BIT_12); // 12位分辨率
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); // 11dB衰减,可测0~3.3V
}
uint32_t read_potentiometer() {
uint32_t raw = adc1_get_raw(ADC1_CHANNEL_0);
return raw; // 返回0~4095
}
我的调试技巧:第一次读取ADC时,建议先读5次取平均,因为ESP32上电后前几次读数往往不稳定。我习惯在初始化后加一个200ms的延时,再开始采集。
3.4 光敏电阻数据采集:感知光线变化
光敏电阻(LDR)的阻值随光照强度变化。暗的时候阻值大(约1MΩ),亮的时候阻值小(约1kΩ)。我们需要把它和固定电阻组成分压电路,才能让ADC读到变化的电压。
推荐电路:LDR接GND,10kΩ电阻接3.3V,中间节点接ADC输入。这样光线越强,LDR阻值越小,ADC读数越高。
#define LDR_CHANNEL ADC1_CHANNEL_3 // GPIO39
float read_light_intensity() {
uint32_t raw = adc1_get_raw(LDR_CHANNEL);
// 转换为电压值
float voltage = (raw / 4095.0) * 3.3;
return voltage; // 电压越高,光线越强
}
你想想看,光敏电阻的响应速度其实很慢(几十毫秒),所以不需要高速采样。我一般每100ms读一次就够了,还能省点CPU资源。
3.5 电压换算:从原始值到真实物理量
ADC读到的原始值(0~4095)只是数字,要变成有意义的物理量,必须经过换算。换算分两步:先转电压,再转物理量。
第一步:原始值转电压
公式很简单:电压 = (原始值 / 4095) × 参考电压。但这里有个细节:ESP32的参考电压不是精确的3.3V,而是内部1.1V经过衰减器放大后的值。实际测量时,我建议用万用表测一下3.3V引脚的实际电压,代入公式会更准。
第二步:电压转物理量
以光敏电阻为例,电压和光照强度不是线性关系。我一般先做标定:用照度计测几个点,然后做查表或拟合曲线。简单场景下,直接用电压值做阈值判断就够了。
| 场景 | 换算方法 | 精度 |
|---|---|---|
| 电位器(角度) | 线性映射:角度 = (raw / 4095) × 300° | 高(约0.3°分辨率) |
| 光敏电阻(光照) | 查表或指数拟合 | 中(受环境温度影响) |
| 电池电压 | 分压比换算:Vbat = Vmeas × (R1+R2)/R2 | 中高(需校准分压电阻) |
| 温度传感器(NTC) | Steinhart-Hart方程 | 高(需精确电阻值) |
电压换算完整示例:
float adc_to_voltage(uint32_t raw, float vref) {
return (raw / 4095.0) * vref;
}
float voltage_to_angle(float voltage, float vmax) {
return (voltage / vmax) * 300.0; // 假设电位器旋转300°
}
// 使用示例
uint32_t raw = adc1_get_raw(ADC1_CHANNEL_0);
float v = adc_to_voltage(raw, 3.28); // 实测3.28V
float angle = voltage_to_angle(v, 3.28);
printf("角度: %.1f°\n", angle);
3.6 知识结构图:ADC采集全流程
下面这张图把整个ADC采集的流程串起来了,从硬件连接到软件换算,每一步都标清楚了。
3.7 避坑指南与经验总结
做ADC采集这么多年,我总结了几条铁律,分享给你:
- 参考电压要实测:不要相信3.3V就是精确的3.3V。用万用表量一下,把实际值代入公式。
- 多次采样取平均:ESP32的ADC噪声不小,我一般采16次取中位值,效果比单纯平均好。
- 避开两端区域:0~0.1V和3.2~3.3V的非线性最严重,设计电路时尽量让信号落在0.2V~3.0V之间。
- 分压电阻要选1%精度:5%的电阻误差会直接吃掉你的测量精度,省不得。
- 加个小电容滤波:在ADC引脚对地加一个0.1μF的电容,能明显减少高频噪声。
我曾经遇到一个奇怪的问题:同一个ADC通道,用手摸一下读数就变了。后来发现是GPIO悬空导致的。所有不用的ADC引脚,要么接地,要么配置为输出,别让它悬空。
嗯,ADC这部分内容就到这里。记住一句话:模拟信号采集,硬件电路决定上限,软件换算决定精度。把分压电路和换算公式搞扎实了,后面做传感器驱动就轻松多了。