4、ADC采样与电压测量:ESP32 ADC特性(12位分辨率、衰减器设置)、分压电路设计、电压采集代码实现
各位同学,咱们今天来聊聊ADC采样和电压测量。说实话,这个知识点在智能家居项目里太常用了——你要监测电池电压、读取传感器输出、或者检测电源状态,都离不开它。ESP32的ADC模块我用了好几年,踩过不少坑,今天把这些经验都掏给你们。
4.1 ESP32 ADC的硬件特性
ESP32内置了两个12位的逐次逼近型ADC,分别叫ADC1和ADC2。12位分辨率意味着什么?说白了,就是把0~3.3V的电压范围,分成2^12=4096个台阶。每个台阶对应的电压是3.3V/4096≈0.8mV。嗯,这个精度对于大多数家用传感器来说,完全够用了。
但有个坑我必须先说——ADC2在Wi-Fi开启时会被占用。我在项目中遇到过,明明ADC2的引脚接好了传感器,一开Wi-Fi读数全乱套。后来查手册才发现,Wi-Fi驱动会抢占ADC2的控制权。所以我的习惯是:能用ADC1就别用ADC2。ADC1的8个通道(GPIO32~GPIO39)是独立工作的,不受Wi-Fi影响。
4.2 衰减器设置(Attenuation)
ESP32的ADC有个很实用的功能——衰减器。默认情况下(不设置衰减),ADC的测量范围是0~1.1V。但大多数时候我们需要测量0~3.3V的范围,这时候就要开启衰减。
衰减器有4个档位,我整理成了表格,方便你们查阅:
| 衰减设置 | 测量范围 | 适用场景 |
|---|---|---|
| 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,省事。但要注意,衰减越大,精度会稍微下降。如果你需要测量非常微弱的信号(比如光敏电阻),可以考虑用DB_0档位,配合外部运放放大信号。
4.3 分压电路设计
好,现在问题来了:你要测量一个12V的电池电压,但ESP32只能承受3.3V,怎么办?答案就是分压电路。
分压电路说白了就是两个电阻串联。输入电压加在串联电阻两端,从中间抽头取电压。公式很简单:
Vout = Vin × R2 / (R1 + R2)
举个例子,我要测12V电池,想让Vout在3.3V以内。取R1=10kΩ,R2=3.3kΩ,那么:
Vout_max = 12V × 3.3 / (10 + 3.3) ≈ 2.98V
嗯,留了点余量,安全。实际项目中我还会加一个5.1V的齐纳二极管做钳位保护——万一电阻焊错了,也不至于烧ADC引脚。
另外,如果你要测量交流电(比如220V市电),千万别直接分压!必须用电压互感器隔离。这个后面讲功率测量时会详细说。
4.4 电压采集代码实现
理论讲完了,咱们直接上代码。这是我在智能家居项目中反复使用的ADC读取函数,经过多次优化,稳定性和精度都还不错。
#include <driver/adc.h>
#include <esp_adc_cal.h>
// 定义ADC通道和衰减
#define ADC_CHANNEL ADC1_CHANNEL_0 // GPIO36
#define ADC_ATTEN ADC_ATTEN_DB_11
#define ADC_WIDTH ADC_WIDTH_BIT_12
// 校准用参数
static esp_adc_cal_characteristics_t adc_chars;
void adc_init(void) {
// 1. 配置ADC宽度和衰减
adc1_config_width(ADC_WIDTH);
adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN);
// 2. 初始化校准特性
esp_adc_cal_characterize(
ADC_UNIT_1,
ADC_ATTEN,
ADC_WIDTH,
0, // 默认Vref,实际可用efuse值
&adc_chars
);
printf("ADC初始化完成\n");
}
uint32_t read_adc_voltage(void) {
// 读取原始ADC值(0~4095)
int raw = adc1_get_raw(ADC_CHANNEL);
// 转换为毫伏电压
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(raw, &adc_chars);
return voltage_mv;
}
void app_main(void) {
adc_init();
while(1) {
uint32_t mv = read_adc_voltage();
// 如果是分压后的电压,需要还原真实电压
// 假设分压比是10k:3.3k,还原系数 = (10+3.3)/3.3 ≈ 4.03
float real_voltage = mv * 4.03 / 1000.0;
printf("ADC原始值: %d, 测量电压: %dmV, 真实电压: %.2fV\n",
adc1_get_raw(ADC_CHANNEL), mv, real_voltage);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
这段代码有几个关键点:
- 使用校准函数:esp_adc_cal_raw_to_voltage() 会根据芯片内部的efuse值做校准,比手动计算准得多。我对比过,校准后误差能控制在±2%以内。
- 分压还原:代码里我写死了4.03的系数,实际项目中建议用宏定义或者从配置文件中读取,方便调试。
- 采样间隔:我设了1秒读一次,对于电压监测足够了。如果你要测快速变化的信号,可以缩短到10ms,但记得做软件滤波。
4.5 实际项目中的注意事项
最后再唠叨几点我在项目中踩过的坑:
- 参考电压不稳定:ESP32的ADC参考电压是芯片内部的,会随温度漂移。如果你需要高精度测量,建议用外部基准电压源(比如TL431)。
- 引脚阻抗问题:ADC引脚的输入阻抗不高,如果信号源内阻太大(比如超过10kΩ),读数会偏低。我一般会在ADC引脚前加一个电压跟随器(运放)。
- 多通道切换:如果你要用多个ADC通道,切换后要等一小段时间(约10μs)再读数,让采样电容稳定下来。
- 数字电源噪声:ESP32的ADC对数字噪声很敏感。我习惯在ADC引脚附近加一个0.1μF的滤波电容,效果立竿见影。
好了,ADC采样这部分就讲到这里。下节课咱们会把这些知识用到实际项目中——做一个电池电压监测模块。你们先把代码跑通,有问题随时问我。