系列文章目录

持续更新…



前言

在嵌入式系统中,模拟信号的采集是日常任务之一,尤其在物联网设备、传感器应用等领域,如何将模拟信号转换为数字信号进行处理至关重要。ESP32 提供了丰富的 ADC(模数转换器)功能,能够通过多个输入通道,采集传感器信号并转换为数字值。本篇博文将详细介绍 ESP32 的 ADC 功能,讲解其硬件原理、常见配置及 API 用法,帮助开发者更好地进行模拟信号的采集和处理。

参考文档:ESP32-S3技术参考手册ESP32-S3编程指南


一、ADC概述

ESP32 的 ADC 系统由模拟前端与两套控制器组成,前端统一完成信号采样与逐次逼近转换,控制器则决定采样时序、触发方式、数据缓存及搬运策略。两条通路共享同一物理 ADC 核心,但分属不同电源域:RTC ADC 控制器位于 RTC 低功耗域,支持在 CPU 深睡或频繁唤醒情况下进行低速测量,适合与 ULP 协处理器配合实现定期检测;数字 ADC 控制器位于数字高性能域,支持高速采样、DMA 传输、多通道扫描以及 I2S 总线输出,适用于波形分析和音频级数据采集。
SAR ADC 概图
在这里插入图片描述
SAR ADC 的功能概况
在这里插入图片描述

1.RTC ADC控制器

该控制器常驻 RTC 域,可在低功耗模式下维持运行,并能通过软件触发、RTC 状态机信号或 ULP 协处理器指令启动采样,测量结果保存在 RTC 寄存器中供读取。它的典型特性是功耗低、延迟可控,适用于环境传感器、电池电压监测等低频任务。虽然本身不直接支持连续扫描或 DMA,但可以借助 ULP 定期触发实现多通道轮询。IDF 的 oneshot 驱动在 ESP32-S3 上即以 RTC 控制器为时钟源,支持衰减(0/2.5/6/11 dB)和位宽(9~12 位)配置,并可利用 eFuse 校准数据将码值换算成 mV 级电压。
RTC SAR ADC 的功能概况
在这里插入图片描述

2.数字 ADC控制器

该控制器运行在数字域,面向高吞吐场景,可配置单通道、扫描或交替模式采集。内部扫描表最多支持 16 条规则(包含通道号、衰减、位宽等),按表顺序轮询采样,并将每个样本与通道号、单元号等元信息打包输出。数据可经 I2S 总线或 DMA 直接搬运至内存,实现高速批量采集。IDF 的 continuous 驱动对接该通路,输出结构化帧(如 adc_digi_output_data_t),便于在环形缓冲中解析和处理。数字 ADC 控制器还优化了触发与交错采样机制,支持同时采集 ADC1 与 ADC2,提升带宽和采样均匀性。

3.ADC引脚

ESP32 的 ADC 引脚固定映射至通道,不可通过 GPIO 矩阵更改。ADC1 提供 8 个通道(GPIO3239),ADC2 提供 10 个通道(GPIO0、2、4、1215、25~27)。需注意 ADC2 与 Wi-Fi 模块共享硬件资源,在 Wi-Fi 工作期间普通应用无法使用 ADC2;在使用 ADC2 时,需通过 esp_wifi_stop() 停止 Wi-Fi 功能,确保 ADC2 可用。,因此对高速连续采样或双单元模式需提前规划资源冲突。选择通道时应结合衰减档位和位宽配置,并参考数据手册中的“满量程电压”曲线,确保量程与分辨率满足需求。
在这里插入图片描述

二、ADC类型定义及常用API

ESP32-S3 ADC类型定义

//=============================================================================
// ESP32-S/ESP32-S3 ADC 相关类型定义
// 收录自 adc_common.h / adc.h / adc_types.h
// 说明:通道与 GPIO 为固定映射;ADC2 可能与 Wi-Fi 共用,注意仲裁与冲突。
//=============================================================================
#include <stdint.h>
#include <stdbool.h>

//=============================================================================
// 1) ADC 单元类型
//=============================================================================
typedef enum {
    ADC_UNIT_1     = 1,   // SAR ADC1
    ADC_UNIT_2     = 2,   // SAR ADC2
    ADC_UNIT_BOTH  = 3,   // 同时使用 ADC1 与 ADC2(数字控制器/连续采样)
    ADC_UNIT_ALTER = 7,   // ADC1 与 ADC2 交替采样
    ADC_UNIT_MAX
} adc_unit_t;

//=============================================================================
// 2) 通用通道类型(逻辑通道号)
//   注:具体到 ADC1/ADC2 的映射见下文 adc1_channel_t / adc2_channel_t
//=============================================================================
typedef enum {
    ADC_CHANNEL_0 = 0,
    ADC_CHANNEL_1,
    ADC_CHANNEL_2,
    ADC_CHANNEL_3,
    ADC_CHANNEL_4,
    ADC_CHANNEL_5,
    ADC_CHANNEL_6,
    ADC_CHANNEL_7,
    ADC_CHANNEL_8,
    ADC_CHANNEL_9,
    ADC_CHANNEL_MAX
} adc_channel_t;

//=============================================================================
// 3) ADC1 通道类型(与 GPIO 的固定对应,目标不同映射不同)
//    * 经典 ESP32: CH0~7 分别映射 GPIO36~39, 32~35
//    * ESP32-S2/S3: CH0~7 -> GPIO1~8;部分芯片还包含 CH8/CH9 -> GPIO9/10
//=============================================================================
typedef enum {
    ADC1_CHANNEL_0 = 0,
    ADC1_CHANNEL_1,
    ADC1_CHANNEL_2,
    ADC1_CHANNEL_3,
    ADC1_CHANNEL_4,
    ADC1_CHANNEL_5,
    ADC1_CHANNEL_6,
    ADC1_CHANNEL_7,
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
    ADC1_CHANNEL_8,
    ADC1_CHANNEL_9,
#endif
    ADC1_CHANNEL_MAX
} adc1_channel_t;

//=============================================================================
// 4) ADC2 通道类型(与 GPIO 的固定对应;常与 Wi-Fi 共用,需仲裁/避让)
//    * 经典 ESP32: CH0~9 -> GPIO4,0,2,15,13,12,14,27,25,26
//    * ESP32-S2/S3: CH0~9 -> GPIO11~20(参考目标芯片引脚图)
//=============================================================================
typedef enum {
    ADC2_CHANNEL_0 = 0,
    ADC2_CHANNEL_1,
    ADC2_CHANNEL_2,
    ADC2_CHANNEL_3,
    ADC2_CHANNEL_4,
    ADC2_CHANNEL_5,
    ADC2_CHANNEL_6,
    ADC2_CHANNEL_7,
    ADC2_CHANNEL_8,
    ADC2_CHANNEL_9,
    ADC2_CHANNEL_MAX
} adc2_channel_t;

//=============================================================================
// 5) 衰减系数(影响满量程电压 / 量程,需要配合标定)
//=============================================================================
typedef enum {
    ADC_ATTEN_DB_0  = 0,  // 约 0~0.8V
    ADC_ATTEN_DB_2_5,     // 约 0~1.1V
    ADC_ATTEN_DB_6,       // 约 0~1.35V
    ADC_ATTEN_DB_11,      // 约 0~2.6V
    ADC_ATTEN_MAX
} adc_atten_t;

//=============================================================================
// 6) I2S 数据源选择(数字控制器经 DMA 输出到 I2S 时的数据来源)
//=============================================================================
typedef enum {
    ADC_I2S_DATA_SRC_IO_SIG = 0, // GPIO 模拟矩阵信号
    ADC_I2S_DATA_SRC_ADC,        // 来自 ADC
    ADC_I2S_DATA_SRC_MAX
} adc_i2s_source_t;

//=============================================================================
// 7) 位宽(采样有效位数)
//    典型默认 12bit;部分目标支持 9/10/11bit 配置;S2 支持 13bit
//=============================================================================
typedef enum {
    ADC_BITWIDTH_DEFAULT = 0, ///< Default ADC output bits, max supported width will be selected
    ADC_BITWIDTH_9  = 9,      ///< ADC output width is 9Bit
    ADC_BITWIDTH_10 = 10,     ///< ADC output width is 10Bit
    ADC_BITWIDTH_11 = 11,     ///< ADC output width is 11Bit
    ADC_BITWIDTH_12 = 12,     ///< ADC output width is 12Bit
    ADC_BITWIDTH_13 = 13,     ///< ADC output width is 13Bit
} adc_bitwidth_t;

//=============================================================================
// 8) 数字控制器转换模式(连续采样 / DMA 扫描模式)
//=============================================================================
typedef enum {
    ADC_CONV_SINGLE_UNIT_1 = 1,  // 仅 ADC1
    ADC_CONV_SINGLE_UNIT_2,      // 仅 ADC2
    ADC_CONV_BOTH_UNIT,          // 同步:ADC1+ADC2 同时采样
    ADC_CONV_ALTER_UNIT = 7      // 交替:ADC1 与 ADC2 轮流采样
} adc_digi_convert_mode_t;

//=============================================================================
// 9) 数字控制器「转换规则表项」(pattern)
//    指定每一步采样的 (atten/bit_width/channel/unit),最多 16 项轮询
//=============================================================================
typedef struct {
    uint8_t atten     : 2;  // adc_atten_t
    uint8_t bit_width : 2;  // adc_bits_width_t(某些目标保留)
    uint8_t channel   : 4;  // 0~9
    uint8_t unit      : 1;  // 0: ADC1, 1: ADC2(Type2 格式需要)
} adc_digi_pattern_table_t;

//=============================================================================
// 10) 数字控制器输出格式(写入 DMA 的单样本数据位宽组织)
//=============================================================================
typedef enum {
    ADC_DIGI_FORMAT_12BIT = 0,  // 12 位数据格式
    ADC_DIGI_FORMAT_11BIT       // 11 位数据格式(带 unit 等元信息)
} adc_digi_output_format_t;

//=============================================================================
// 11) 数字控制器输出数据(DMA 缓冲中单样本的解析结构)
//    Type1:12bit 数据 + 4bit 通道
//    Type2:11bit 数据 + 4bit 通道 + 1bit 单元(ADC1/ADC2)
//=============================================================================
typedef union {
    struct {                    // 与 ADC_DIGI_FORMAT_12BIT 对应
        uint16_t data    : 12;
        uint16_t channel : 4;
    } type1;
    struct {                    // 与 ADC_DIGI_FORMAT_11BIT 对应(S2/S3 常用)
        uint16_t data    : 11;
        uint16_t channel : 4;
        uint16_t unit    : 1;   // 0:ADC1 1:ADC2
    } type2;
    uint16_t val;
} adc_digi_output_data_t;

//=============================================================================
// 12) 数字控制器时钟配置(连续采样的采样时钟源与分频)
//    采样时钟 = (APLL 或 APB) / (div_num + div_a/div_b + 1)
//=============================================================================
typedef struct {
    bool     use_apll;   // true: APLL;false: APB
    uint32_t div_num;    // 1~255
    uint32_t div_b;      // 1~63
    uint32_t div_a;      // 0~63
} adc_digi_clk_t;

//=============================================================================
// 13) 数字控制器总配置(DMA 模式)
//    启动后按 pattern 表循环扫描;可设置转换次数限制/格式/时钟等
//=============================================================================
typedef struct {
    uint32_t                    conv_limit_en;    // 使能采样次数限制
    uint32_t                    conv_limit_num;   // 限制次数(1~255)
    uint32_t                    adc_pattern_len;  // pattern 项个数(<=16)
    const adc_digi_pattern_table_t *adc_pattern;  // pattern 表
    adc_digi_convert_mode_t     conv_mode;        // 转换模式
    adc_digi_output_format_t    format;           // 输出格式(11/12bit)
    adc_digi_clk_t              dig_clk;          // 采样时钟
} adc_digi_config_t;

//=============================================================================
// 14) ADC2 仲裁模式(当 RTC 控制器 / 数字控制器 / Wi-Fi 同时请求 ADC2)
//=============================================================================
typedef enum {
    ADC_ARB_MODE_SHIELD = 0, // 屏蔽一种客户端
    ADC_ARB_MODE_FIX,        // 固定优先级
    ADC_ARB_MODE_LOOP        // 循环轮转优先级
} adc_arbiter_mode_t;

// 仲裁器配置(仅 ADC2)
typedef struct {
    adc_arbiter_mode_t mode;  // 工作模式(仅 ADC2)
    uint8_t rtc_pri;          // RTC 控制器优先级(0~2,数值大优先)
    uint8_t dig_pri;          // 数字控制器优先级(0~2)
    uint8_t pwdet_pri;        // Wi-Fi 功率检测优先级(0~2)
} adc_arbiter_t;

//=============================================================================
// 15) 数字控制器中断类型(DMA 模式)
//=============================================================================
typedef enum {
    ADC_DIGI_INTR_MASK_MONITOR  = 0x1,  // 监视器触发
    ADC_DIGI_INTR_MASK_MEAS_DONE= 0x2,  // 一次测量完成
    ADC_DIGI_INTR_MASK_ALL      = 0x3
} adc_digi_intr_t;

//=============================================================================
// 16) 数字控制器滤波器索引(可对若干通道使能 IIR 滤波)
//=============================================================================
typedef enum {
    ADC_DIGI_FILTER_IDX0 = 0,
    ADC_DIGI_FILTER_IDX1,
    ADC_DIGI_FILTER_IDX_MAX
} adc_digi_filter_idx_t;

// 滤波模式(IIR 一阶,2/4/8/16/64 分母系数,数值越大平滑越强)
typedef enum {
    ADC_DIGI_FILTER_IIR_2 = 0,   // 系数 2
    ADC_DIGI_FILTER_IIR_4,       // 系数 4
    ADC_DIGI_FILTER_IIR_8,       // 系数 8
    ADC_DIGI_FILTER_IIR_16,      // 系数 16
    ADC_DIGI_FILTER_IIR_64,      // 系数 64
    ADC_DIGI_FILTER_IIR_MAX
} adc_digi_filter_mode_t;

// 滤波器配置(绑定到某个单元/通道)
typedef struct {
    adc_unit_t              adc_unit;   // ADC1/ADC2
    adc_channel_t           channel;    // 目标通道
    adc_digi_filter_mode_t  mode;       // IIR 模式
} adc_digi_filter_t;

//=============================================================================
// 17) 数字控制器监视器(阈值比较器)
//=============================================================================
typedef enum {
    ADC_DIGI_MONITOR_IDX0 = 0,
    ADC_DIGI_MONITOR_IDX1,
    ADC_DIGI_MONITOR_IDX_MAX
} adc_digi_monitor_idx_t;

typedef enum {
    ADC_DIGI_MONITOR_HIGH = 0,  // 大于阈值触发
    ADC_DIGI_MONITOR_LOW,       // 小于阈值触发
    ADC_DIGI_MONITOR_MAX
} adc_digi_monitor_mode_t;

// 监视器配置(越界即触发中断)
typedef struct {
    adc_unit_t               adc_unit;   // ADC1/ADC2
    adc_channel_t            channel;    // 目标通道
    adc_digi_monitor_mode_t  mode;       // 触发模式
    uint32_t                 threshold;  // 阈值(按当前输出位宽)
} adc_digi_monitor_t;

//=============================================================================
// 18) I2S 编码格式(ADC->DMA 搬运到 I2S 的打包方式,对应 11/12bit)
//=============================================================================
typedef enum {
    ADC_ENCODE_12BIT = 0,  // [15:12] 通道,[11:0] 数据
    ADC_ENCODE_11BIT       // [15] 单元,[14:11] 通道,[10:0] 数据
} adc_i2s_encode_t;

//=============================================================================
// 备注:本文件只罗列“类型定义”。实际使用还需配套 IDF 驱动:
//  - 单次采样:adc_oneshot_*(RTC 控制器通路,适合低功耗/低频)
//  - 连续采样:adc_continuous_* / adc_digi_*(数字控制器+DMA,适合多通道/高吞吐)
//  - 若使用 ADC2 且启用 Wi-Fi,请结合 adc_arbiter_* 做优先级或避让规划。
//=============================================================================

ADC常用API

//=============================================================================
// ESP32-S3 ADC 常用 API 列表
//=============================================================================

// 初始化 ADC 单次采样单元(ADC1 或 ADC2)
// 参数:
// - init_cfg:指向 adc_oneshot_unit_init_cfg_t 结构体的指针,
//   该结构体包含初始化配置,如 ADC 单元 ID 和 ULP 模式。
// - unit_handle:指向 adc_oneshot_unit_handle_t 类型的指针,用于返回创建的 ADC 单元句柄。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_NO_MEM:内存不足。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_oneshot_new_unit(const adc_oneshot_unit_init_cfg_t *init_cfg, adc_oneshot_unit_handle_t *unit_handle);

// 删除 ADC 单次采样单元
// 参数:
// - unit_handle:要删除的 ADC 单元句柄。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_oneshot_del_unit(adc_oneshot_unit_handle_t unit_handle);

// 配置 ADC 通道属性(衰减、位宽等)
// 参数:
// - unit_handle:要配置的 ADC 单元句柄。
// - chan_cfg:指向 adc_oneshot_chan_cfg_t 结构体的指针,
//   该结构体包含通道配置,如衰减和位宽。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_oneshot_config_channel(adc_oneshot_unit_handle_t unit_handle, const adc_oneshot_chan_cfg_t *chan_cfg);

// 读取 ADC 通道的原始数值
// 参数:
// - unit_handle:要读取的 ADC 单元句柄。
// - channel:要读取的 ADC 通道。
// - raw_value:指向 int 类型的指针,用于返回读取到的原始值。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
// - ESP_ERR_TIMEOUT:操作超时。
esp_err_t adc_oneshot_read(adc_oneshot_unit_handle_t unit_handle, adc_channel_t channel, int *raw_value);

// 初始化 ADC 连续采样句柄
// 参数:
// - cfg:指向 adc_continuous_handle_cfg_t 结构体的指针,
//   该结构体包含连续采样配置,如最大存储缓冲区大小。
// - handle:指向 adc_continuous_handle_t 类型的指针,用于返回创建的 ADC 连续采样句柄。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_NO_MEM:内存不足。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_continuous_new_handle(const adc_continuous_handle_cfg_t *cfg, adc_continuous_handle_t *handle);

// 删除 ADC 连续采样句柄
// 参数:
// - handle:要删除的 ADC 连续采样句柄。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_continuous_deinit(adc_continuous_handle_t handle);

// 配置 ADC 连续采样通道属性
// 参数:
// - handle:要配置的 ADC 连续采样句柄。
// - chan_cfg:指向 adc_continuous_chan_cfg_t 结构体的指针,
//   该结构体包含通道配置,如衰减和位宽。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_continuous_config_channel(adc_continuous_handle_t handle, const adc_continuous_chan_cfg_t *chan_cfg);

// 启动 ADC 连续采样
// 参数:
// - handle:要启动的 ADC 连续采样句柄。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_continuous_start(adc_continuous_handle_t handle);

// 停止 ADC 连续采样
// 参数:
// - handle:要停止的 ADC 连续采样句柄。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_continuous_stop(adc_continuous_handle_t handle);

// 读取 ADC 连续采样结果
// 参数:
// - handle:要读取的 ADC 连续采样句柄。
// - buffer:指向存储读取数据的缓冲区。
// - size:缓冲区的大小。
// - bytes_read:指向 size_t 类型的指针,用于返回实际读取的字节数。
// - ticks_to_wait:等待时间,单位为系统节拍。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
// - ESP_ERR_TIMEOUT:操作超时。
// - ESP_ERR_NO_MEM:内存不足。
esp_err_t adc_continuous_read(adc_continuous_handle_t handle, void *buffer, size_t size, size_t *bytes_read, TickType_t ticks_to_wait);

// 获取 ADC 校准方案(线性拟合)
// 参数:
// - config:指向 adc_cali_line_fitting_config_t 结构体的指针,
//   该结构体包含校准配置,如衰减和位宽。
// - handle:指向 adc_cali_handle_t 类型的指针,用于返回创建的校准句柄。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_NO_MEM:内存不足。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_cali_create_scheme_line_fitting(const adc_cali_line_fitting_config_t *config, adc_cali_handle_t *handle);

// 删除 ADC 校准方案
// 参数:
// - handle:要删除的校准句柄。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
esp_err_t adc_cali_delete_scheme(adc_cali_handle_t handle);

// 将 ADC 原始值转换为电压(单位:mV)
// 参数:
// - raw_value:ADC 读取到的原始值。
// - chars:指向 esp_adc_cal_characteristics_t 结构体的指针,
//   该结构体包含校准特性,如 ADC 宽度和衰减。
// - voltage:指向 uint32_t 类型的指针,用于返回转换后的电压值。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
// - ESP_ERR_TIMEOUT:操作超时。
esp_err_t esp_adc_cal_raw_to_voltage(int raw_value, const esp_adc_cal_characteristics_t *chars, uint32_t *voltage);

// 获取 ADC 校准特性结构体
// 参数:
// - config:指向 esp_adc_cal_characterize_config_t 结构体的指针,
//   该结构体包含校准配置,如 ADC 宽度和衰减。
// - chars:指向 esp_adc_cal_characteristics_t 结构体的指针,
//   用于返回校准特性。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
// - ESP_ERR_TIMEOUT:操作超时。
esp_err_t esp_adc_cal_characterize(const esp_adc_cal_characterize_config_t *config, esp_adc_cal_characteristics_t *chars);

// 检查 ADC 校准方案支持情况
// 参数:
// - scheme:要检查的校准方案。
// - is_supported:指向 bool 类型的指针,用于返回是否支持该方案。
// 返回值:
// - ESP_OK:成功。
// - ESP_ERR_INVALID_ARG:参数无效。
// - ESP_ERR_INVALID_STATE:当前状态不允许操作。
// - ESP_ERR_TIMEOUT:操作超时。
esp_err_t adc_cali_check_scheme(adc_cali_scheme_t scheme, bool *is_supported);


三、ADC示例程序(ESP32-S3)

ADC 单次采样模式示例

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"

static adc_oneshot_unit_handle_t adc_handle;

void ADC_Oneshot_Task(void *pvParameter)
{
    int raw_value;
    while (1)
    {
        // 读取 ADC1 通道 9 的原始值
        esp_err_t ret = adc_oneshot_read(adc_handle, ADC_CHANNEL_9, &raw_value);
        if (ret == ESP_OK)
        {
            ESP_LOGI("ADC", "ADC1_CH9 raw=%d", raw_value);
        }
        else
        {
            ESP_LOGW("ADC", "ADC read failed");
        }
        vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒读取一次
    }
}

void ADC_Init()
{
    // 初始化 ADC1 单次采样单元
    adc_oneshot_unit_init_cfg_t init_cfg = {
        .unit_id = ADC_UNIT_1,
        .ulp_mode = ADC_ULP_MODE_DISABLE,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_cfg, &adc_handle));

    // 配置 ADC1 通道 9 的属性(衰减、位宽等)
    adc_oneshot_chan_cfg_t chan_cfg = {
        .atten = ADC_ATTEN_DB_11,        // 约 0~2.6V
        .bitwidth = ADC_WIDTH_BIT_12,    // 12 位分辨率
    };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_9, &chan_cfg));
}

void app_main(void)
{
    ADC_Init();
    xTaskCreatePinnedToCore(ADC_Oneshot_Task, "ADC Oneshot Task", 2048, NULL, 5, NULL, 0);
}

初始化 ADC 单次采样单元
使用 adc_oneshot_new_unit() 创建 ADC 单次采样单元句柄。
配置 adc_oneshot_unit_init_cfg_t 结构体,指定 ADC 单元 ID 和 ULP 模式。
配置 ADC 通道属性:使用 adc_oneshot_config_channel() 配置 ADC 通道的衰减和位宽。在此示例中,选择 ADC1 的通道 9,衰减设置为 11 dB(约 0~2.6V),位宽设置为 12 位。
读取 ADC 数据:在任务中使用 adc_oneshot_read() 函数读取 ADC 通道的原始值。读取成功后,通过日志输出原始数据。
任务调度:使用 FreeRTOS 的 xTaskCreatePinnedToCore() 创建任务,定期读取 ADC 数据。

ADC 连续采样模式(DMA 模式)示例

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

#include "esp_adc/adc_continuous.h"

static adc_continuous_handle_t adc_handle;

void ADC_Continuous_Task(void *pvParameter)
{
    uint8_t buf[256];
    while (1)
    {
        uint32_t len = 0;
        if (adc_continuous_read(adc_handle, buf, sizeof(buf), &len, 20 / portTICK_PERIOD_MS) == ESP_OK)
        {
            uint32_t samples = len / 4;
            for (uint32_t i = 0; i < len; i += 4)
            {
                uint16_t raw = (buf[i] | (buf[i + 1] << 8)) & 0x0FFF; // 12位有效
                uint8_t meta = buf[i + 2];
                uint8_t ch = meta & 0x0F;
                // unit 固定为 ADC1,可忽略 (meta >> 4)
                if (i < 16)
                { // 仅打印前 4 个样本减少日志
                    ESP_LOGI("ADC", "ADC1_CH%d raw=%u", ch, raw);
                }
            }
            // ESP_LOGI("ADC", "frame bytes=%lu samples=%lu", (unsigned long)len, (unsigned long)samples);
        }
        else
        {
            ESP_LOGW("ADC", "No data");
        }
        vTaskDelay(100 / portTICK_PERIOD_MS);
        
    }
}

void ADC_Init()
{
    // 仅使用 ADC1 连续模式
    adc_digi_pattern_config_t pattern[1] = {
        {.atten = ADC_ATTEN_DB_11,
         .channel = ADC_CHANNEL_9, // 选择的 ADC1 通道 (GPIO 对应请查 pin 表)
         .unit = ADC_UNIT_1,
         .bit_width = ADC_BITWIDTH_12}};

    adc_continuous_handle_cfg_t handle_cfg = {
        .max_store_buf_size = 1024,
        .conv_frame_size = 256, // 必须是 pattern_num * 单样本字节(4) 的整数倍;这里 256 OK
    };
    ESP_ERROR_CHECK(adc_continuous_new_handle(&handle_cfg, &adc_handle));

    adc_continuous_config_t adc_cfg = {
        .sample_freq_hz = 10000,             // 10 kHz
        .conv_mode = ADC_CONV_SINGLE_UNIT_1, // 只支持 ADC1
        .format = ADC_DIGI_OUTPUT_FORMAT_TYPE2,
        .pattern_num = 1,
        .adc_pattern = pattern,
    };
    ESP_ERROR_CHECK(adc_continuous_config(adc_handle, &adc_cfg));
    ESP_ERROR_CHECK(adc_continuous_start(adc_handle));
}

void app_main(void)
{
    ADC_Init();
    xTaskCreatePinnedToCore(ADC_Continuous_Task, "ADC Continuous Task", 4096, NULL, 5, NULL, 0);
}

adc_continuous_read() 函数将从硬件缓冲区读取 ADC 转换结果,并将其存储到用户提供的缓冲区中。每个转换结果包含多个字节,具体格式取决于配置的输出格式。在示例代码中,使用了 ADC_DIGI_OUTPUT_FORMAT_TYPE2 格式,该格式的每个转换结果由 4 个字节组成,具体结构如下:
字节 0 和 1:12 位原始数据(低字节在前,高字节在后),高 4 位为零。
字节 2:包含通道号和 ADC 单元信息。
低 4 位表示通道号(0~9)。
高 4 位表示 ADC 单元(0:ADC1,1:ADC2)。
字节 3:未使用,可忽略。

adc_continuous_read() 函数返回的数据是原始的 ADC 数值。要将其转换为实际电压值,需要根据配置的衰减和位宽进行计算。例如,使用以下公式:

Vout = Dout * Vmax / Dmax
Vout:输出电压(单位:伏特)。
Dout:ADC 原始数字值。
Vmax:最大可测输入模拟电压,取决于配置的衰减。
Dmax:ADC 的最大输出数字值,等于 2^bit_width - 1,其中 bit_width 是配置的位宽。


总结

本文详细介绍了 ESP32 的 ADC 功能,包括硬件原理、常见配置和 API 用法,帮助开发者更高效地采集和处理模拟信号。ESP32 提供了单次采样和连续采样模式,支持多通道配置和高频采样。通过相关 API,开发者可以灵活配置采样参数,并准确读取和转换模拟信号为数字值。希望本文对 ESP32 开发者有所帮助,欢迎讨论与反馈。

Logo

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

更多推荐