4、模拟信号采集:ADC原理、ESP32的ADC通道、模拟电压读取、内部温度传感器

模拟信号采集,说白了就是把现实世界里的连续电压、电流、温度这些物理量,变成芯片能认的0和1。ESP32作为一颗面向物联网的SoC,内部集成了两个SAR ADC模块,精度12位,最高采样率能到2 MHz。嗯,这个配置在MCU里算相当能打了。

我记得刚入行那会儿,做一款工业数据采集器,用的还是外挂ADC芯片,板子面积大、走线麻烦、成本还高。现在ESP32直接片上搞定,省心不少。但省心不代表可以随便用——ADC的坑,我踩过不少。

4.1 ADC工作原理:从模拟到数字的“翻译”过程

ADC(模数转换器)的核心任务,就是把连续的模拟电压,映射成离散的数字码。ESP32用的是逐次逼近型(SAR)架构,你可以把它想象成一个“二分法猜数字”的游戏:

  1. 采样保持电路先把当前电压“冻结”住。
  2. 内部DAC从中间值开始,跟输入电压比大小。
  3. 比较器告诉结果:大了还是小了?
  4. 逐位逼近寄存器根据比较结果,调整下一位。
  5. 12位精度,需要12个时钟周期完成一次转换。

关键指标:

  • 分辨率:12位 → 2^12 = 4096个量化等级
  • 参考电压:ESP32默认0~3.3V,也可外部输入
  • 量化步长:3.3V / 4096 ≈ 0.806 mV/LSB
  • 采样率:最高2 MHz(但实际受WiFi/蓝牙影响)

为什么会强调量化步长?因为你的传感器输出变化如果小于0.8 mV,ADC是读不出来的。我在项目里遇到过用1mV/V精度的压力传感器,结果信号调理没做好,读数一直在跳——后来加了运放才稳住。

4.2 ESP32的ADC通道:两个控制器,18个通道

ESP32内部有两个ADC模块:ADC1和ADC2。ADC1有8个通道,ADC2有10个通道。但注意,ADC2的通道跟WiFi共用硬件资源——WiFi一开,ADC2就没法用了。这是个非常容易踩的坑。

ADC模块 通道数 对应GPIO WiFi影响 推荐用途
ADC1 8 GPIO32~39 无影响 常规传感器采集
ADC2 10 GPIO0,2,4,12~15,25~27 WiFi启用时不可用 仅限无WiFi场景

我曾经踩过的坑:做一款WiFi温湿度采集器,用了ADC2的GPIO4接模拟输出温湿度传感器。调试时好好的,一开WiFi数据全乱。查了两天才发现是ADC2被WiFi占用了。后来全部改到ADC1的GPIO32~39,问题解决。

所以我的建议是:只要你的项目用到了WiFi,ADC通道一律选ADC1的GPIO32~39。别给自己找麻烦。

4.3 模拟电压读取:从硬件连接到代码实现

读取模拟电压,硬件上就三件事:传感器输出脚接ADC GPIO、GND共地、参考电压稳定。软件上,ESP-IDF和Arduino都提供了现成的API。

4.3.1 硬件连接要点

  • 传感器输出阻抗不能太高,否则ADC采样保持电容充不满,读数偏低。一般建议输出阻抗 < 10 kΩ。
  • 如果传感器输出有高频噪声,加一个0.1μF电容到GND做滤波。
  • 参考电压VREF默认是3.3V,如果外部供电不稳,读数会跟着飘。

4.3.2 Arduino框架代码示例

// 读取ADC1通道0(GPIO36)
const int adcPin = 36;
int rawValue = 0;
float voltage = 0.0;

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);  // 设置12位精度
  analogSetAttenuation(ADC_11db);  // 衰减11dB,量程0~3.3V
}

void loop() {
  rawValue = analogRead(adcPin);
  voltage = rawValue * (3.3 / 4095.0);
  Serial.printf("原始值: %d, 电压: %.3f V\n", rawValue, voltage);
  delay(100);
}

衰减设置说明:ESP32的ADC输入范围默认只有0~1.1V。要读取0~3.3V,必须设置衰减。常用选项:

  • ADC_0db:0~1.1V
  • ADC_2_5db:0~1.5V
  • ADC_6db:0~2.2V
  • ADC_11db:0~3.3V(最常用)

4.3.3 ESP-IDF框架代码示例

#include "driver/adc.h"
#include "esp_adc_cal.h"

#define ADC_CHANNEL ADC1_CHANNEL_0  // GPIO36
#define ADC_WIDTH ADC_WIDTH_BIT_12
#define ADC_ATTEN ADC_ATTEN_DB_11

static esp_adc_cal_characteristics_t adc_chars;

void app_main() {
    // 1. 配置ADC
    adc1_config_width(ADC_WIDTH);
    adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN);
    
    // 2. 校准ADC(推荐)
    esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN, ADC_WIDTH, 0, &adc_chars);
    
    while (1) {
        uint32_t raw = adc1_get_raw(ADC_CHANNEL);
        uint32_t voltage = esp_adc_cal_raw_to_voltage(raw, &adc_chars);
        printf("原始值: %lu, 电压: %lu mV\n", raw, voltage);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

我个人习惯用ESP-IDF的校准函数。ESP32的ADC线性度其实一般,出厂时每个芯片都有个体差异。校准函数会读取eFuse中存储的出厂校准值,把读数修正到更接近真实电压。Arduino框架下也可以手动调用校准,但默认没开。

4.4 内部温度传感器:芯片自带的“体温计”

ESP32内部集成了一个温度传感器,用来监测芯片结温。精度嘛,只能说“够用”——±2°C左右,不能当精密温度计用,但用来做过热保护绰绰有余。

内部温度传感器的读取方式跟外部ADC通道不太一样。它不占用ADC1或ADC2的通道,而是通过一个专门的寄存器来读。

4.4.1 Arduino框架读取内部温度

#include "driver/temp_sensor.h"

void setup() {
  Serial.begin(115200);
  
  // 初始化内部温度传感器
  temp_sensor_config_t temp_sensor = {
    .dac_offset = TSENS_DAC_L2,  // 默认偏移
    .clk_div = 6                 // 时钟分频
  };
  temp_sensor_set_config(temp_sensor);
  temp_sensor_start();
}

void loop() {
  float temperature = 0;
  temp_sensor_read_celsius(&temperature);
  Serial.printf("芯片温度: %.2f °C\n", temperature);
  delay(1000);
}

4.4.2 ESP-IDF框架读取内部温度

#include "driver/temp_sensor.h"

void app_main() {
    temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
    temp_sensor_set_config(tsens);
    temp_sensor_start();
    
    float temp = 0;
    while (1) {
        temp_sensor_read_celsius(&temp);
        printf("芯片温度: %.2f °C\n", temp);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

注意:内部温度传感器读的是芯片结温,不是环境温度。芯片跑得越狠,温度越高。如果你发现温度读数比室温高十几度,别慌——那是正常的。我曾经用这个功能做设备过热自动降频,效果很好。

4.5 知识体系总览

下面这张图把本章的核心知识点串起来了。你跟着箭头走一遍,就能理解从模拟信号到数字读数的完整链路。

ESP32 ADC知识体系结构图 ESP32 模拟信号采集知识体系 模拟信号源 内部温度传感器 外部传感器 参考电压 ESP32 ADC硬件 ADC1(8通道) | ADC2(10通道) 温度传感器模块 专用寄存器读取 ADC配置参数 分辨率(12位) | 衰减(0~11dB) 采样率 | 校准(eFuse) 温度传感器配置 DAC偏移 | 时钟分频 启动/停止 软件读取 Arduino: analogRead() | ESP-IDF: adc1_get_raw() 软件读取 temp_sensor_read_celsius()

4.6 避坑指南与实战建议

最后,我把这些年做ADC采集踩过的坑,总结成几条实用建议:

  • WiFi项目只用ADC1:ADC2在WiFi启用时不可用,这是硬件限制,软件绕不过去。
  • 衰减一定要设:默认0~1.1V范围太小,大部分传感器输出都超过这个值。不设衰减,读数会饱和在4095。
  • 校准不是摆设:ESP32的ADC线性度一般,出厂校准数据存在eFuse里。用校准函数能把误差从±6%降到±2%左右。
  • 采样率别拉满:2 MHz是理论值,实际跑WiFi时ADC1的稳定采样率在200 kHz左右。拉太高读数会跳。
  • 内部温度传感器别当环境温度计:它测的是芯片结温,不是空气温度。芯片负载变化时,读数会跟着变。

我的个人习惯:每次做ADC采集,我都会在硬件上预留一个测试点,接一个已知电压(比如1.65V分压)。上电后先读这个测试点的值,跟理论值对比。如果偏差超过3%,就检查供电、衰减设置和校准是否开启。这个小习惯帮我省了不少调试时间。