专栏前言

上一篇我们掌握了CAN总线工业通信,实现了强干扰场景下的高可靠多设备组网,解决了设备间数字通信的问题。

但嵌入式系统最终要和物理世界交互,传感器的电压信号、温度电流模拟量、音频波形、模拟控制输出,都需要 模拟-数字转换器(ADC)数字-模拟转换器(DAC) 作为桥梁。

很多新手做模拟采集总会遇到读数不准、漂移大、干扰多、输出带不动的问题,本质是不了解模拟外设的特性、校准方法和抗干扰设计。

ESP32集成了丰富的模拟外设:2路12位逐次逼近型ADC,支持最多20个通道、多种衰减量程、硬件校准、DMA连续采样;2路8位DAC,支持可调电压输出、DMA波形生成,配合定时器可实现任意波形输出。

本篇一次性讲透ESP-IDF下的ADC与DAC开发:
模拟接口核心原理 → ADC衰减与量程配置 → 硬件校准与精度优化 → 多通道单次采样实战 → DMA连续高频采样 → DAC电压输出与波形生成 → 抗干扰与可靠性设计 → 最佳实践与踩坑汇总
全程配可编译源码+架构图解,零基础也能实现工业级精准的模拟信号采集与输出。


下面是本篇完整的学习路线图:

模拟接口核心原理

ADC衰减与量程配置

硬件校准与精度优化

多通道单次采样实战

DMA连续高频采样

DAC电压输出与波形生成

抗干扰与可靠性设计

最佳实践与踩坑汇总

一、模拟接口核心基础

1. 什么是ADC与DAC

  • ADC(Analog to Digital Converter):将连续变化的模拟电压信号转换为离散的数字量,是采集传感器、电压、电流等模拟信号的核心入口。
  • DAC(Digital to Analog Converter):将数字量转换为连续的模拟电压信号,用于模拟控制、波形生成、音量调节、基准输出等场景。

2. ESP32模拟外设资源总览

ESP32内置2个ADC控制器和2个DAC通道,各自特性与适用场景不同:

外设分辨率通道数典型量程最高采样率核心说明
ADC112位(可配置9/10/11位)8通道(GPIO32~GPIO39)0~3.3V(配衰减)200ksps精度稳定,支持DMA,与WiFi无冲突,优先选用
ADC212位10通道0~3.3V(配衰减)200ksps与WiFi共享资源,WiFi启用时使用受限
DAC18位1通道(GPIO25)0VDD3P3(约03.2V)-输出阻抗高,适合高阻负载,需缓冲后驱动功率电路
DAC28位1通道(GPIO26)0~VDD3P3-特性同DAC1

3. 关键概念

  • 分辨率:数字量的位数,决定最小可分辨电压。12位ADC对应4096级,3.3V量程下约0.8mV/步;8位DAC对应256级,约13mV/步。
  • 量程:可测量或输出的电压范围,ADC通过内部衰减器扩大量程。
  • 采样率:每秒完成采样的次数,根据奈奎斯特定理,需至少为信号最高频率的2倍才能还原波形。
  • 量化误差:模拟量数字化的固有误差,为±1LSB(最低有效位),分辨率越高误差越小。

在这里插入图片描述

【配图1:ESP32模拟外设架构与通道分布图】
配图说明:展示芯片内部模拟前端、ADC1/ADC2控制器、DMA控制器、DAC模块的连接关系;标注各通道对应的GPIO引脚,直观呈现模拟外设的硬件架构。


下面是 ESP32 模拟外设的整体架构图:

ESP32 芯片内部

模拟前端
信号调理

ADC1 控制器
8通道 GPIO32~39

ADC2 控制器
10通道

DMA 控制器

RAM 缓冲区

定时器

DAC1 GPIO25

DAC2 GPIO26

模拟输出

外部模拟信号
传感器/电压/电流

外部负载
扬声器/电机/仪表

二、ADC详解与配置

1. 衰减配置与量程选择

ESP32的ADC输入并非直接支持0~3.3V满幅,内部集成了可编程衰减器,不同衰减对应不同的满量程电压,这是新手最容易踩的坑。

衰减配置满量程电压推荐线性区间选型说明
ADC_ATTEN_DB_0~1.1V0~1.0V无衰减,精度最高,适合小信号精密采集
ADC_ATTEN_DB_2_5~1.5V0~1.4V2.5dB衰减,中小量程场景
ADC_ATTEN_DB_6~2.2V0~2.0V6dB衰减,中量程场景
ADC_ATTEN_DB_11~3.9V0~3.3V11dB衰减,最常用,覆盖3.3V全量程

工程提示:实际满量程受芯片供电电压影响,3.3V供电下11dB衰减最大可测约3.3V;ADC输入绝对不能超过3.6V,否则会永久损坏芯片。

2. 三种采样模式

  • 单次采样:软件触发一次转换一次,适合低速、低频信号采集,代码简单、占用资源少。
  • 连续采样:ADC自动连续转换,通过DMA搬运数据,适合高频波形采集,CPU占用极低。
  • 扫描模式:自动依次转换多个配置好的通道,适合多传感器同时采集,统一调度。

三种采样模式的选择流程如下:

确定采样需求

信号频率高?

单次采样
软件触发,代码简单

需要多通道同时采集?

连续采样
DMA搬运,CPU占用低

扫描模式
自动依次转换多通道

低频/低速场景
传感器巡检

高频波形采集
音频/振动

多传感器同时采集
统一调度

3. 硬件校准原理

ESP32的ADC存在固有偏移误差和增益误差,出厂时每个芯片的校准参数已烧录在eFuse中,可读取参数进行硬件校正,无需额外校准电路即可将精度从±5%提升到±1%左右。

  • 两点校准:基于零点和满量程点的参数,同时修正偏移和增益误差。
  • Vref校准:基于内部参考电压的校准值,修正增益误差。

4. 核心API

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

// 配置ADC分辨率
esp_err_t adc1_config_width(adc_bits_width_t width);

// 配置指定通道的衰减
esp_err_t adc1_config_channel_atten(adc1_channel_t channel, adc_atten_t atten);

// 读取通道原始采样值
int adc1_get_raw(adc1_channel_t channel);

// 初始化校准特征,读取eFuse参数
esp_adc_cal_value_t esp_adc_cal_characterize(
    adc_unit_t adc_num,
    adc_atten_t atten,
    adc_bits_width_t bit_width,
    uint32_t default_vref,
    esp_adc_cal_characteristics_t *chars
);

// 原始值转换为真实电压(单位:mV)
uint32_t esp_adc_cal_raw_to_voltage(uint32_t raw, const esp_adc_cal_characteristics_t *chars);

在这里插入图片描述

【配图2:ADC衰减量程与精度特性对比图】
配图说明:用条形图对比四种衰减的满量程电压;曲线展示不同量程下的线性度,标注推荐工作区间,直观呈现衰减配置的选型依据。


三、实战1:多通道ADC采集与硬件校准

实现功能:ADC1的两个通道采集模拟电压,使用eFuse硬件校准,多次采样取平均降噪,输出真实电压值。

完整可编译源码

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

#define TAG "ADC_DEMO"

#define ADC_UNIT    ADC_UNIT_1
#define ADC_WIDTH   ADC_WIDTH_BIT_12  // 12位分辨率
#define ADC_ATTEN   ADC_ATTEN_DB_11   // 11dB衰减,覆盖0~3.3V

#define CHAN_1      ADC1_CHANNEL_6   // GPIO34
#define CHAN_2      ADC1_CHANNEL_7   // GPIO35
#define SAMPLE_CNT  16               // 单次平均采样次数

// 校准特征结构体
static esp_adc_cal_characteristics_t adc_chars;

// ADC初始化与硬件校准
static void adc_init_calibrate(void)
{
    // 1. 配置分辨率
    adc1_config_width(ADC_WIDTH);

    // 2. 配置通道衰减
    adc1_config_channel_atten(CHAN_1, ADC_ATTEN);
    adc1_config_channel_atten(CHAN_2, ADC_ATTEN);

    // 3. 读取eFuse校准参数
    esp_adc_cal_value_t cal_type = esp_adc_cal_characterize(
        ADC_UNIT,
        ADC_ATTEN,
        ADC_WIDTH,
        1100,  // 默认参考电压1100mV
        &adc_chars
    );

    if(cal_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
        ESP_LOGI(TAG, "校准方式:eFuse Vref");
    } else if(cal_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
        ESP_LOGI(TAG, "校准方式:eFuse两点校准");
    } else {
        ESP_LOGW(TAG, "无出厂校准,使用默认参考电压");
    }
}

// 读取指定通道的校准后电压(单位:mV)
static uint32_t adc_read_voltage(adc1_channel_t chan)
{
    uint32_t raw_sum = 0;
    // 多次采样取平均,抑制随机噪声
    for(int i = 0; i < SAMPLE_CNT; i++) {
        raw_sum += adc1_get_raw(chan);
    }
    uint32_t raw_avg = raw_sum / SAMPLE_CNT;

    // 转换为真实电压
    return esp_adc_cal_raw_to_voltage(raw_avg, &adc_chars);
}

void adc_sample_task(void *pvParameters)
{
    while(1) {
        uint32_t v1 = adc_read_voltage(CHAN_1);
        uint32_t v2 = adc_read_voltage(CHAN_2);

        ESP_LOGI(TAG, "通道1:%4d mV  |  通道2:%4d mV", v1, v2);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    adc_init_calibrate();
    xTaskCreate(adc_sample_task, "adc_task", 4096, NULL, 3, NULL);
    ESP_LOGI(TAG, "ADC初始化与校准完成");
}

关键说明

  • 16次采样取平均是工业级最常用的软件降噪方法,有效减小随机噪声,对采样速度影响极小。
  • 优先使用ADC1,ADC2与WiFi共享资源,开启WiFi后会冲突,无法正常使用。
  • 输入信号尽量落在推荐线性区间,接近满量程时线性度下降,误差增大。

多通道 ADC 采集与硬件校准的完整流程如下:

eFuse Vref

eFuse两点校准

无出厂校准

配置ADC分辨率
12位

配置通道衰减
11dB覆盖0~3.3V

读取eFuse校准参数

校准类型判断

使用Vref校准

使用两点校准

使用默认参考电压

多次采样取平均
16次抑制噪声

原始值转真实电压
esp_adc_cal_raw_to_voltage

输出通道1/通道2电压

延时1秒

四、进阶:DMA连续高频采样

适合音频、振动、脉冲等高频波形采集场景,ADC自动连续转换,DMA硬件自动搬运数据到内存,全程无需CPU干预,效率极高。

工作原理

  1. ADC配置为连续转换模式,转换完成自动触发下一次采样
  2. DMA控制器自动将ADC数据寄存器的值搬运到RAM缓冲区
  3. 采用双缓冲区乒乓机制:处理缓冲区A的数据时,ADC继续往缓冲区B采样,交替切换,不丢失任何采样点
  4. 缓冲区满触发中断,通知CPU批量处理数据

DMA 双缓冲乒乓机制的工作流程如下:

ADC连续转换模式

转换完成触发DMA搬运

缓冲区A处理中?

ADC写入缓冲区B

ADC写入缓冲区A

缓冲区B满触发中断

缓冲区A满触发中断

CPU批量处理缓冲区B数据

CPU批量处理缓冲区A数据

适用场景

  • 音频采集、声纹检测
  • 振动信号分析、脉冲计数
  • 高速传感器数据采集
  • 示波器、逻辑分析仪类应用

工程提示:连续采样需配合合理的采样率和缓冲区大小,避免数据溢出和处理不及时。


五、DAC详解与波形输出

1. DAC核心特性

  • 8位分辨率,共256级输出电压
  • 输出范围:0 ~ VDD3P3 - 0.1V,3.3V供电下约0~3.2V
  • 输出阻抗高(约25kΩ),不能直接驱动低阻负载,必须加运放缓冲
  • 支持DMA连续输出,配合定时器可生成任意频率的波形

2. 核心API

#include "driver/dac.h"

// 使能DAC通道输出
esp_err_t dac_output_enable(dac_channel_t channel);

// 输出指定数字值,范围0~255
esp_err_t dac_output_voltage(dac_channel_t channel, uint8_t dac_value);

// 禁用DAC输出
esp_err_t dac_output_disable(dac_channel_t channel);

实战2:DAC电压输出与正弦波生成

实现功能:DAC1输出静态电压,预计算正弦波查找表,通过软件定时更新模拟连续波形输出。

#include <stdio.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/dac.h"
#include "esp_log.h"

#define TAG "DAC_DEMO"
#define DAC_CHAN DAC_CHANNEL_1  // GPIO25

#define SINE_POINTS 100  // 正弦波一个周期的点数
uint8_t sine_table[SINE_POINTS];

// 生成正弦波查找表
static void generate_sine_table(void)
{
    for(int i = 0; i < SINE_POINTS; i++) {
        float angle = 2 * 3.1415926f * i / SINE_POINTS;
        // 正弦值归一化到0~1,再映射到0~255
        float value = (sinf(angle) + 1.0f) / 2.0f;
        sine_table[i] = (uint8_t)(value * 255.0f);
    }
}

// 静态电压输出测试
static void dac_static_test(void)
{
    dac_output_enable(DAC_CHAN);
    dac_output_voltage(DAC_CHAN, 128);  // 输出约1.65V(中间值)
    ESP_LOGI(TAG, "输出静态电压:约1.65V");
}

// 连续正弦波输出
void dac_wave_task(void *pvParameters)
{
    uint32_t idx = 0;
    while(1) {
        dac_output_voltage(DAC_CHAN, sine_table[idx]);
        idx = (idx + 1) % SINE_POINTS;
        // 延时决定波形频率,100点+100us延时约100Hz
        esp_rom_delay_us(100);
    }
}

void app_main(void)
{
    generate_sine_table();
    dac_static_test();
    xTaskCreate(dac_wave_task, "dac_task", 4096, NULL, 4, NULL);
    ESP_LOGI(TAG, "DAC波形输出启动");
}

关键说明

  • 软件延时输出仅适合低频演示,高频连续波形需配合DMA+定时器实现,CPU占用更低、频率更精准。
  • DAC输出必须加电压跟随器(运放缓冲)才能驱动低阻负载,直接带载会导致电压大幅偏低。
  • 8位分辨率适合一般控制类场景,高精度需求需外接12位以上的独立DAC芯片。

在这里插入图片描述

【配图3:DAC波形生成与DMA传输流程图】
配图说明:展示查找表→DMA控制器→DAC→模拟输出的完整数据流,标注定时器触发、双缓冲乒乓操作,直观呈现连续波形生成的工作机制。


六、工程化:抗干扰与精度优化

1. 硬件抗干扰设计

  • 电源滤波:模拟电源引脚就近加100nF去耦电容+10μF电解电容,滤除电源噪声。
  • 输入滤波:ADC输入端加RC低通滤波,截止频率根据采样率选择,滤除高频干扰。
  • 布线规范:模拟走线与数字走线分开,避免平行走线,减少串扰;模拟地与数字地单点共地,避免地环流。
  • 过压保护:ADC输入端串联限流电阻+钳位二极管,防止过压损坏芯片。
  • 输出缓冲:DAC输出加运放电压跟随器,降低输出阻抗,提高带载能力和稳定性。

2. 软件精度优化

  • 多次平均:16/32次采样取平均,抑制随机噪声,性价比最高。
  • 滑动滤波:一阶低通滤波算法,平滑慢变信号,消除突发干扰。
  • 定期校准:系统空闲时执行零点和量程校准,修正温漂和时漂。
  • 异常校验:对采样值做范围判断,超出合理范围判定为传感器故障或开路。

3. 可靠性设计

  • 关键模拟参数增加超时检测,长时间无更新触发告警。
  • ADC和DAC外设初始化后做自检,验证外设工作正常。
  • 工业级场景增加外部基准电压源,替代内部基准,提升长期稳定性。

七、工程最佳实践

  1. ADC选型原则
    • 优先使用ADC1,避免与WiFi冲突;必须用ADC2时要和WiFi分时复用。
    • 在满足量程的前提下,尽量选小的衰减,精度更高。
    • 采样率按信号最高频率的5~10倍设计,留足余量。
    • 输入信号尽量控制在线性区间,不要接近满量程。
  2. DAC使用原则
    • 静态控制场景直接写寄存器,动态波形用DMA输出。
    • 必须加缓冲运放,禁止直接驱动低阻负载。
    • 8位分辨率满足一般控制需求,高精度场景外接高位DAC。
  3. 通用设计规范
    • 模拟电路远离高频数字电路、电源电路、功率电路。
    • 敏感模拟信号用屏蔽线,屏蔽层单端接地。
    • 量产产品增加校准工序,修正个体差异。

八、新手高频踩坑汇总

  1. ADC读数不准、偏差很大
    • 没有配置衰减,默认0dB衰减只能测1.1V以内,超过就饱和顶格。
    • 没有做硬件校准,芯片固有偏移和增益误差导致整体偏差。
    • 电源噪声大,没有滤波,读数跳变严重。
    • 输入信号超过量程,ADC饱和后读数保持最大值。
  2. ADC读数跳变、噪声大
    • 没有加输入滤波,高频干扰串入采样通道。
    • 采样次数太少,随机噪声没有被平均掉。
    • 模拟电源和数字电源混布,电源噪声耦合到模拟通路。
    • 走线太长,引入空间电磁干扰。
  3. DAC输出电压不对
    • 直接带低阻负载,输出阻抗高导致电压被拉低。
    • 供电电压不是标准3.3V,满量程随供电变化。
    • 输出值超出0~255范围,数据溢出错误。
  4. 连续采样丢数据、错位
    • 缓冲区太小,处理不及时导致新数据覆盖旧数据。
    • 没有用双缓冲乒乓机制,处理数据时采样不停止。
    • DMA优先级太低,被其他高优先级任务抢占。
  5. ADC2完全用不了
    • 开启了WiFi,ADC2与WiFi共享资源,硬件冲突。
    • 必须使用ADC2的场景,要在WiFi关闭时采样,采样完成再开WiFi。

下一篇预告(27篇)

ESP-IDF保姆级入门27|PWM与电机控制全解:LED PWM定时器、互补输出、死区控制、直流电机调速、步进电机驱动,掌握嵌入式电力电子控制基础,实现精准的电机驱动与功率控制!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐