3、ADC模拟量采集:ESP32 ADC特性、电压读取、光敏电阻与电位器实验

ADC,全称模数转换器。说白了,就是把连续的模拟电压,变成单片机能认的离散数字。

ESP32 的 ADC 跟 Arduino 那种 10 位分辨率不太一样。它有两个 ADC 模块,ADC1 和 ADC2,都是 12 位的。12 位意味着什么?2 的 12 次方,4096 个量化等级。输入电压范围默认是 0 ~ 3.3V,对应的数字量就是 0 ~ 4095。

嗯,这里要注意。ADC2 在 Wi-Fi 开启时会被占用。我早期做项目时踩过这个坑——Wi-Fi 一开,ADC2 的读数全乱套了。所以我的习惯是:只要项目里用了 Wi-Fi,模拟量采集一律走 ADC1。

3.1 ESP32 ADC 硬件特性速览

先看一张表,把关键参数理清楚。

参数 说明
分辨率 12 位(0 ~ 4095),可通过函数配置为 9~12 位
输入电压范围 默认 0 ~ 3.3V,可衰减至 0 ~ 1.1V(11dB 衰减)
ADC1 可用通道 GPIO32 ~ GPIO39(共 8 个)
ADC2 可用通道 GPIO0、2、4、12 ~ 15、25 ~ 27(共 10 个)
采样速率 理论最高 200 ksps,实际建议 1 kHz 以内
非线性误差 约 ±2 LSB,首尾两端非线性较明显

为什么我要强调首尾两端非线性?因为 ESP32 的 ADC 在接近 0V 和接近 3.3V 时,读数会飘。我曾经用万用表量着 3.28V,ADC 读数却已经到 4095 了。所以实际项目中,我一般只使用 0.1V ~ 3.2V 这个区间,两头各留一点余量。

3.2 核心 API 与配置流程

ESP-IDF 里操作 ADC,主要用 adc1_config_width() 和 adc1_config_channel_atten() 这两个函数。配置流程其实就三步:

  1. 设置分辨率:调用 adc1_config_width(ADC_WIDTH_BIT_12),告诉硬件我要 12 位精度。
  2. 配置衰减:调用 adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11),设置通道和衰减系数。
  3. 读取数值:循环里调用 adc1_get_raw(ADC1_CHANNEL_6),拿到原始数字量。

衰减系数这块,我多说两句。ESP32 提供了四种衰减档位:

衰减设置 可测电压范围 典型场景
0 dB 0 ~ 1.1V 高精度小信号
2.5 dB 0 ~ 1.5V 传感器输出 1.2V 左右
6 dB 0 ~ 2.2V 中间档位,不常用
11 dB 0 ~ 3.3V 最常用,覆盖全量程

我个人习惯,只要不确定输入电压范围,一律用 11 dB 衰减。虽然分辨率会稍微损失一点,但至少不会烧引脚。

3.3 实战:读取电位器电压

电位器是最简单的模拟量输入设备。三个引脚:两端接 3.3V 和 GND,中间抽头接 GPIO34(ADC1_CHANNEL6)。

来看代码:

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/adc.h>

void app_main(void)
{
    // 1. 配置 ADC1 为 12 位分辨率
    adc1_config_width(ADC_WIDTH_BIT_12);

    // 2. 配置通道 6(GPIO34),11dB 衰减,量程 0~3.3V
    adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);

    while (1) {
        // 3. 读取原始值
        int raw = adc1_get_raw(ADC1_CHANNEL_6);

        // 4. 换算为电压(mV)
        uint32_t voltage = esp_adc_cal_raw_to_voltage(raw, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11);

        printf("Raw: %4d, Voltage: %3d mV\n", raw, voltage);

        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

这里有个细节。早期 ESP-IDF 版本里,esp_adc_cal_raw_to_voltage() 这个函数需要先调用 esp_adc_cal_characterize() 做校准。但从 v4.4 开始,内部已经集成了查表逻辑,直接传参就能用。如果你用的是旧版本,记得加上校准步骤。

我的小技巧: 调试阶段,我习惯在串口打印原始值和电压值两个数据。如果发现电压值跳变超过 20mV,先别急着怀疑代码——检查一下面包板上的杜邦线是不是松了。我遇到过好几次,折腾半天,结果是线没插紧。

3.4 光敏电阻实验:感知环境光强

光敏电阻,也叫光敏电阻器(LDR)。它的阻值随光照强度变化——光照越强,阻值越低。

实际电路里,光敏电阻不能直接接 ADC。需要搭一个分压电路:光敏电阻和固定电阻(比如 10kΩ)串联,中间抽头接 ADC 引脚。

电路连接很简单:

  • 3.3V → 光敏电阻一端
  • 光敏电阻另一端 → GPIO35(ADC1_CHANNEL7)→ 10kΩ 电阻 → GND

代码跟电位器实验几乎一样,只是换了个通道:

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/adc.h>

void app_main(void)
{
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_11);

    while (1) {
        int raw = adc1_get_raw(ADC1_CHANNEL_7);
        uint32_t voltage = esp_adc_cal_raw_to_voltage(raw, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11);

        // 粗略换算:电压越高,光照越强
        float light_percent = (float)voltage / 3300.0 * 100.0;

        printf("Raw: %4d, Voltage: %3d mV, Light: %.1f%%\n", raw, voltage, light_percent);

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

为什么电压越高光照越强?因为光敏电阻受光照后阻值下降,分到的电压就低了。但我们的分压电路里,光敏电阻在上端,固定电阻在下端。光敏电阻阻值下降,意味着它分到的电压减少,固定电阻分到的电压增加——ADC 读到的就是固定电阻的电压。所以电压高,代表光照强。

避坑提醒: 光敏电阻的响应速度很慢,大概几十毫秒。如果你用高速采样去读,会发现数据抖动很大。我曾经在做一个光控窗帘项目时,用 100Hz 采样率去读光敏电阻,结果数据像心电图一样上下乱跳。后来把采样率降到 10Hz,加了个简单的滑动平均滤波,数据就稳了。

3.5 数据滤波:让读数更可靠

ADC 原始数据里,噪声是不可避免的。电源纹波、电磁干扰、甚至手指靠近引脚都会影响读数。我常用的滤波方法有两种:

  • 均值滤波:连续采样 N 次,取平均值。N 一般取 5~20,效果立竿见影。
  • 中值滤波:连续采样 N 次,排序后取中间值。对脉冲噪声特别有效。

来看一个简单的均值滤波实现:

#define SAMPLE_COUNT 10

int adc_read_filtered(adc1_channel_t channel)
{
    int sum = 0;
    for (int i = 0; i < SAMPLE_COUNT; i++) {
        sum += adc1_get_raw(channel);
    }
    return sum / SAMPLE_COUNT;
}

就这么几行代码,能把噪声从 ±15 LSB 压到 ±3 LSB 以内。性价比极高。

3.6 本章知识结构图

下面这张图,把本章的核心知识点串起来了。从硬件特性到 API 配置,再到两个实战案例和滤波方法,一目了然。

ESP32 ADC 模拟量采集知识结构 ADC 核心特性 硬件参数 API 配置 实战案例 12 位分辨率 ADC1 / ADC2 区别 adc1_config_width / atten 电位器实验 光敏电阻实验 数据滤波方法 均值滤波 中值滤波 滑动平均 ADC1 在 Wi-Fi 开启时不受影响 建议优先使用 ADC1 通道

3.7 几个容易踩的坑

最后,我把这些年做 ADC 采集踩过的坑列出来,你遇到了可以直接对照排查。

  • 引脚悬空读数飘忽:ADC 引脚不接任何东西时,读数会在 0~4095 之间乱跳。这不是硬件坏了,是正常现象。接上传感器就好了。
  • 分压电阻选太大:光敏电阻实验里,如果分压电阻选 100kΩ,ADC 输入阻抗不够,读数会偏低。我一般用 10kΩ 或 4.7kΩ。
  • Wi-Fi 干扰 ADC2:这个前面说过了。只要开了 Wi-Fi,ADC2 的通道就别用了。别问我怎么知道的——问就是烧过一版 PCB。
  • 电压换算公式别搞错:电压 = (raw / 4095.0) * 3.3 这个公式只适用于线性度好的区间。如果用了 11dB 衰减,建议用 esp_adc_cal_raw_to_voltage() 查表换算,更准。

本章核心要点:

  • ESP32 ADC 是 12 位,0~4095 对应 0~3.3V(11dB 衰减)
  • ADC1 和 ADC2 要分清,Wi-Fi 项目只用 ADC1
  • 电位器和光敏电阻都是分压电路,代码结构几乎一样
  • 数据滤波是 ADC 采集的标配,均值滤波最简单实用