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采集的流程串起来了,从硬件连接到软件换算,每一步都标清楚了。

ADC模拟信号采集全流程 传感器/电位器 分压电路 ESP32 ADC引脚 设置分辨率(12位) 设置衰减器(11dB) 选择ADC1通道 adc1_get_raw() 读取原始值(0~4095) 电压换算:V = raw/4095 × Vref 物理量换算:查表/拟合/线性 输出:角度/光照强度/温度/电压值 注意:ADC2会被Wi-Fi占用,建议所有模拟采集使用ADC1(GPIO32~39)

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这部分内容就到这里。记住一句话:模拟信号采集,硬件电路决定上限,软件换算决定精度。把分压电路和换算公式搞扎实了,后面做传感器驱动就轻松多了。