4. ADC与模拟输入:analogRead函数、电位器读取电压、光敏电阻与光照传感器、ADC衰减与精度校准

各位同学,欢迎来到第四章。这一章我们聊聊模拟输入。说白了,就是让ESP32能「感知」真实世界的连续变化——比如旋钮转了多少度、光线暗了几分。

数字信号只有0和1,但现实世界是连续的电压。ADC(模数转换器)就是连接这两个世界的桥梁。ESP32内部集成了两个12位的SAR ADC,总共可以接18个通道。嗯,这里要注意,不是所有引脚都能做ADC,具体引脚号我建议你查一下官方数据手册,别焊完板子才发现引脚不对。

4.1 analogRead函数:最基础的模拟读取

ESP32的Arduino核心提供了analogRead(pin)函数。用法很简单,但背后有些细节值得说。

int adc_value = analogRead(36);  // 读取GPIO36的模拟值
Serial.println(adc_value);       // 输出0-4095之间的数值

返回值范围是0到4095,对应12位分辨率。为什么是4095?2的12次方减1,这个数字你应该记住。

我的习惯:每次用analogRead之前,先调用一次analogReadResolution(12)显式设置分辨率。虽然默认就是12位,但写清楚能让代码更可维护。我在项目里吃过默认配置被意外改动的亏。

4.2 电位器读取电压:从旋钮到数值

电位器是最直观的模拟输入器件。三根引脚:两端接VCC和GND,中间抽头接ADC引脚。旋转旋钮,分压比变化,ADC读数跟着变。

我刚开始做智能家居面板时,用电位器模拟调光旋钮。代码很简单:

const int POT_PIN = 34;
int pot_value = 0;

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
}

void loop() {
  pot_value = analogRead(POT_PIN);
  float voltage = pot_value * (3.3 / 4095.0);
  Serial.printf("ADC: %d, Voltage: %.2fV\n", pot_value, voltage);
  delay(100);
}

这里有个坑:ESP32的ADC参考电压默认是3.3V,但实际VDD可能不是精确的3.3V。你想想看,如果板子供电是3.28V,那算出来的电压就有偏差。我建议你用万用表实测一下VDD,然后修正公式里的参考电压值。

电位器阻值 适用场景 注意事项
10kΩ 通用模拟输入 功耗适中,最常用
1kΩ 低阻抗场景 功耗较大,注意发热
100kΩ 省电场景 ADC输入阻抗匹配需注意
我曾经踩过的坑:用100kΩ电位器接ADC,发现读数跳得厉害。后来查资料才知道,ESP32的ADC输入阻抗建议不超过10kΩ。高阻抗源会导致采样电容充电不完全,读数不稳定。解决办法是加一级运放跟随器,或者直接换10kΩ电位器。

4.3 光敏电阻与光照传感器

光敏电阻(LDR)的阻值随光照变化。暗的时候几MΩ,亮的时候几百Ω。配合一个固定电阻组成分压电路,就能把光照变成电压。

典型电路:LDR接VCC,固定电阻(比如10kΩ)接GND,中间抽头接ADC。这样光照越强,LDR阻值越小,ADC读数越高。

const int LDR_PIN = 35;
int ldr_value = 0;

void setup() {
  Serial.begin(115200);
}

void loop() {
  ldr_value = analogRead(LDR_PIN);
  // 简单映射:0-4095 -> 0-100%
  int light_percent = map(ldr_value, 0, 4095, 0, 100);
  Serial.printf("Light: %d%%\n", light_percent);
  delay(500);
}

不过说实话,这种简单映射不太准。LDR的阻值-光照曲线是非线性的。如果你需要精确的勒克斯值,我建议用数字光照传感器(比如BH1750),或者自己标定一条曲线。

项目经验:我在做智能窗帘项目时,用LDR检测环境光。发现一个问题:窗户边的光照变化很快,直接读取ADC值抖动很大。解决方案是加一个简单的滑动平均滤波:
filtered_value = 0.8 * filtered_value + 0.2 * raw_value;
这样既能响应变化,又不会太跳。

4.4 ADC衰减与精度校准

ESP32的ADC默认输入范围是0~3.3V。但如果你需要测量更高的电压怎么办?ADC提供了衰减选项,通过analogSetAttenuation()函数设置。

衰减设置 测量范围 适用场景
ADC_0db 0~1.1V 高精度小信号
ADC_2_5db 0~1.5V 中等精度
ADC_6db 0~2.2V 常用范围
ADC_11db 0~3.3V 默认模式,全量程
analogSetAttenuation(ADC_11db);  // 设置0~3.3V量程
analogRead(36);                   // 读取值对应0~3.3V

这里有个重要的事:衰减越大,精度越低。ADC_0db的精度最好,但只能测到1.1V。ADC_11db能测3.3V,但精度会差一些。我一般这样选:如果信号范围确定,尽量用小衰减,把信号调理到合适的电压区间。

说到精度,ESP32的ADC其实有个「原罪」——它的线性度不太好。尤其是靠近0V和靠近满量程的区域,误差可能达到几十mV。官方后来推出了ADC校准方案,通过读取芯片内部eFuse中存储的校准参数来修正。

校准方法:ESP32 IDF提供了esp_adc_cal库,可以自动读取校准参数。Arduino环境下,你可以用analogSetVRef(1100)设置内部参考电压,或者用esp_adc_cal_characterize()函数获取校准后的特性。我建议量产项目一定要做校准,否则不同板子之间的读数差异会让你头疼。

下面这张图总结了ADC模拟输入的核心流程:

ESP32 ADC模拟输入知识体系 模拟信号源 电位器 / LDR / 传感器 分压/调理电路 匹配ADC输入阻抗 ESP32 ADC 12位 / 0-4095 ADC配置参数 分辨率: 9/10/11/12位 衰减: 0/2.5/6/11 dB 精度校准 eFuse校准参数 esp_adc_cal库 软件处理 analogRead() 滤波/映射 核心要点 ① 输入阻抗匹配:源阻抗 ≤ 10kΩ ② 衰减与精度权衡:小衰减高精度,大衰减宽量程
重要提醒:ESP32的ADC在WiFi开启时会有额外的噪声干扰。如果你发现WiFi一开,ADC读数就开始跳,别慌。这是芯片内部射频部分耦合过来的。解决办法:在ADC采样时暂时关闭WiFi(不现实),或者用硬件滤波(加RC低通滤波),或者在软件里做多次采样取平均。我一般用后者,简单有效。

最后总结一下本章的核心:analogRead只是表面功夫,真正的功夫在电路匹配和校准上。你想想看,一个不稳定的ADC读数,后面再好的算法也是白搭。所以,花时间把前端电路调好,把校准参数配好,比什么都重要。