【ESP32-IDF笔记】07-ADC 配置和使用
配置环境
Visual Studio Code :版本1.98.2
ESP32:ESP32-S3
ESP-IDF:V5.4
ESP32 ADC介绍
ADC介绍
ESP32-S3 集成了两个 12 位 SAR ADC,共支持 20 个模拟通道输入。为了实现更低功耗,ESP32-S3 的 ULP 协处理器也可以在睡眠方式下测量电压,此时,可通过设置阈值或其他触发方式唤醒 CPU。
如图 39.3-1 所示,SAR ADC 由四个专用控制器控制,包括:
-
一个数字控制器,支持高性能多通道扫描和 DMA 连续转换,包括:
– DIG ADC1 控制器 (Digital ADC1 Controller)
-
两个 RTC 控制器,支持在低功耗模式下工作和单次转换,包括:
– RTC ADC1 控制器 (RTC ADC1 Controller)
– RTC ADC2 控制器 (RTC ADC2 Controller)
-
一个内部控制器,即 PWDET 控制器 (Power/Peak Detect Controller),用于 RF 功率检测。注意,此控制器仅供 RF 内部使用
注意
ADC2_CH… 模拟功能不能和 Wi-Fi 同时使用。
ESP32-S3 的 DIG ADC2 控制器无法正常工作,该章节中已删除其相关信息,详见 。ESP32-S3 系列芯片勘误表
管脚分配
SAR ADC 管脚通过 IO MUX 与 GPIO1 ~ GPIO20、RTC_GPIO1 ~ RTC_GPIO20、触摸传感器接口、UART 接口、SPI接口、以及 USB_D- 和 USB_D+ 管脚复用。
ADC 特性
SAR ADC 具有以下特性:
-
四个控制器均配有独立的 ADC Reader 模块,见图 39.3-2
-
支持 DIG ADC1 控制器和 RTC ADC1 控制器通过软件选择的方式获取 SAR ADC1 的控制权
-
支持 RTC ADC2 控制器和 PWDET 控制器通过仲裁的方式按照指定的仲裁方式,轮流获取 SAR ADC2 的控制权
-
支持 12 位采样分辨率
-
支持采集最多 20 个管脚上的模拟电压
-
RTC ADC1/2 控制器:
- 支持单次转换
- 支持在低功耗模式下工作,如 Deep-sleep
- 可由 ULP 协处理器配置
-
DIG ADC1 控制器:
- 配有多通道扫描控制模块,支持多通道扫描模式
- 提供模式控制模块,支持单 SAR ADC 采样模式
- 在多通道扫描模式下,支持自定义扫描通道顺序
- 提供两个滤波器,滤波系数可配
- 支持阈值监控,采样值大于设置的高阈值或小于设置的低阈值将产生中断
- 支持 DMA
• PWDET 控制器:用于 RF 功率检测(仅供内部使用
SAR ADC 架构
SAR ADC 的主要元件与连接情况见图 39.3-2。
- —>:时钟信号
:时钟分频器、多路器、以及时钟作用的模块
如图 39.3-2 所示,SAR ADC 模块主要包括以下部分:
-
SAR ADC1:可对 10 个通道进行电压检测;
-
SAR ADC2:可对 10 个通道进行电压检测;
-
时钟管理 (Clock Management):对时钟源进行选择和分频:
-
DIG ADC1 控制器时钟源:可选择 APB_CLK 或 PLL_D2_CLK;
-
DIG ADC1 控制器分频时钟:
- DIGADC_SARCLK:SAR ADC1、SAR ADC2、Digital Reader1 的工作时钟;其中控制DIGADC_SARCLK 分频的 DIG_SAR_DIV 的分频系数至少是 2 分频,且 DIGADC_SARCLK 的频率不能超过 5 MHz,具体配置见 APB_SARADC_SAR_CLK_DIV;
- DIGADC_CLK:DIG ADC FSM1 的工作时钟。
-
RTC ADC 控制器时钟源:RTC_FAST_CLK
-
RTC ADC 控制器分频时钟:
- RTCADC_SARCLK:SAR ADC1、SAR ADC2、RTC Reader1 和 RTC Reader2 的工作时钟;其中控制 RTCADC_SARCLK 分频的 RTC_SAR_DIV 的分频系数至少是 2 分频,且 RTCADC_SARCLK 的频率不能超过 5 MHz;
-
-
仲裁器 (ADC2_ARBIT):用于选择使用 SAR ADC2 的控制器,可以是 RTC ADC2 控制器或 PWDET 控制器。仲裁器不仅仲裁控制信号,还会根据授权的控制器选择 SAR ADC2 的时钟。
-
定时器 (Timer):DIG ADC1 专用定时器,可发起采样使能信号。
-
DIG ADC FSM1:用于
- 接收定时器提供的采样使能信号
- 根据样式表生成 SAR ADC1 的采样配置
- 驱动 Digital Reader1 模块读取 ADC 采样值
- 并将采样值传输到滤波器、存储器等后续数据处理模块
-
Filter:滤波器 0 和滤波器 1,可对目标通道的采样数据进行滤波处理。
-
Threshold Monitor:阈值监控器 0 和阈值监控器 1。可在采样值大于设定的高阈值,或小于设定的低阈值时触发中断。
-
模式控制模块 (MODE CNTL):用于过滤定时器触发的采样信号,支持单 SAR ADC 采样模式。
-
Digital Reader1:由 DIG ADC FSM1 驱动,读取 SAR ADC1 的数值。
-
RTC Controller1/2:提供采样使能信号,根据配置驱动 RTC Reader1/2 模块读取 SAR ADC1/2 的采样值,并存储采样数据。
-
RTC Reader1/2:由 RTC Controller1/2 驱动,读取 SAR ADC1/2 的数值。
本章节数据是在 ADC 外接 100 nF 电容、输入为 DC 信号、25 °C 环境温度、Wi-Fi 关闭条件下的测量结果。
ADC 转换和衰减
SAR ADC 转换模拟信号时,通过该函数获取的 ADC 转换结果为原始数据。可以使用以下公式,根据 ADC 原始结果计算电压:
V o u t = D o u t ∗ V m a x / D m a x Vout = Dout * Vmax / Dmax Vout=Dout∗Vmax/Dmax
Vout | 数字输出结果,代表电压。 |
---|---|
Dout | ADC 原始数字读取结果。 |
Vmax | 可测量的最大模拟输入电压,与 ADC 衰减相关 |
Dmax | 输出 ADC 原始数字读取结果的最大值,即 2^位宽,位宽即之前配置的 adc\_oneshot\_chan\_cfg\_t::bitwidth 。 |
如需转换大于 Vref 的电压,信号输入 SAR ADC 前可进行衰减。衰减可配置为 0 dB、2.5 dB、6 dB 和 12dB。
衰减 | 输入范围 |
---|---|
0 dB | 0~1.1V |
2.5 dB | 0~2.5V |
6 dB | 0~2.2V |
12dB | 0~3.3V |
单次转换模式
简介
模数转换器集成于芯片,支持测量特定模拟 IO 管脚的模拟信号。
ESP32-S3 有 {SOC_ADC_PERIPH_NUM} 个 ADC 单元,可以在以下场景使用:
- 生成 ADC 单次转换结果
- 生成连续 ADC 转换结果
使用方法
资源分配
ADC 单次转换模式驱动基于 ESP32-S3 SAR ADC 模块实现,不同的 ESP 芯片可能拥有不同数量的独立 ADC。对于单次转换模式驱动而言,ADC 实例以 adc_oneshot_unit_handle_t
表示。
请设置所需的初始配置结构体 adc_oneshot_unit_init_cfg_t
安装 ADC 实例,具体如下:
-
adc_oneshot_unit_init_cfg_t::unit_id
选择 ADC。请参阅 技术规格书,了解对应 ADC 的专用模拟 IO 管脚。 -
adc_oneshot_unit_init_cfg_t::clk_src
选择 ADC 的时钟源。设置为 0 时,驱动程序将使用默认时钟源,详情请参阅adc_oneshot_clk_src_t
。 -
adc_oneshot_unit_init_cfg_t::ulp_mode
设置是否支持 ADC 在 ULP 模式下工作。
完成 ADC 初始配置后,使用已设置的初始配置结构体 adc_oneshot_unit_init_cfg_t
调用 adc_oneshot_new_unit()
。如果分配成功,该函数将返回 ADC 单元实例句柄。
该函数可能因参数无效、内存不足等原因返回错误代码。比如,当要分配的 ADC 实例已经注册时,该函数会返回 ESP_ERR_NOT_FOUND
错误。可用 ADC 数量可通过 SOC_ADC_PERIPH_NUM
查看。
如果不再需要先前创建的 ADC 单元实例,请调用 adc_oneshot_del_unit()
回收该实例,相关的硬件和软件资源也会回收。
在普通单次转换模式下创建 ADC 单元实例句柄
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_init_cfg_t init_config1 = {.unit_id = ADC_UNIT_1,.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
回收 ADC 单元实例
ESP_ERROR_CHECK(adc_oneshot_del_unit(adc1_handle));
配置 ADC 单元实例
创建 ADC 单元实例后,请设置 adc_oneshot_chan_cfg_t
配置 ADC IO 以测量模拟信号,具体如下:
-
adc_oneshot_chan_cfg_t::atten
,ADC 衰减。请参阅 技术规格书 >ADC 特性
。 -
adc_oneshot_chan_cfg_t::bitwidth
,原始转换结果的位宽。
备注
ADC IO 及其对应的 ADC 通道编号,请参阅 技术规格书。
此外,可以使用
adc_continuous_io_to_channel()
和adc_continuous_channel_to_io()
了解 ADC 通道和 ADC IO。
为使以上设置生效,请使用上述配置结构体调用 adc_oneshot_config_channel(),并指定要配置的 ADC 通道。函数 adc_oneshot_config_channel() 支持多次调用,以配置不同的 ADC 通道。驱动程序将在内部保存每个通道的配置。
配置两个 ADC 通道
adc_oneshot_chan_cfg_t config = {.bitwidth = ADC_BITWIDTH_DEFAULT,.atten = ADC_ATTEN_DB_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN0, &config));
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN1, &config));
读取转换结果
完成上述配置后,ADC 即可测量来自配置好的 ADC 通道的模拟信号。调用 adc_oneshot_read()
可以获取 ADC 通道的原始转换结果。
-
adc_oneshot_read()
可安全使用。ADC 由其他驱动程序/外设共享,请参阅 硬件限制。函数adc_oneshot_read()
使用互斥锁,避免与其他函数同时使用硬件,因此该函数不应在 ISR 上下文中使用。当 ADC 由其他驱动程序/外设占用时,该函数可能出错,并返回ESP_ERR_TIMEOUT
错误。此时,ADC 原始结果无效。
该函数可能因参数无效而调用失败。
通过该函数获取的 ADC 转换结果为原始数据。可以使用以下公式,根据 ADC 原始结果计算电压:
Vout = Dout * Vmax / Dmax (1)
其中:
Vout | 数字输出结果,代表电压。 |
---|---|
Dout | ADC 原始数字读取结果。 |
Vmax | 可测量的最大模拟输入电压,与 ADC 衰减相关,请参考 技术参考手册 > 片上传感器与模拟信号处理 。 |
Dmax | 输出 ADC 原始数字读取结果的最大值,即 2^位宽,位宽即之前配置的 adc_oneshot_chan_cfg_t::bitwidth 。 |
若需进一步校准,将 ADC 原始结果转换为以 mV 为单位的电压数据,请参ADC 校准章节。
读取原始结果
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN0, &adc_raw[0][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN0, adc_raw[0][0]);ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN1, &adc_raw[0][1]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN1, adc_raw[0][1]);
硬件限制
- 随机数生成器 (RNG) 以 ADC 为输入源。使用 ADC 单次转换模式驱动从 RNG 生成随机数时,随机性会减弱。
- 一个 ADC 单元每次只能在一种操作模式下运行,可以是连续模式或单次模式。
adc_oneshot_start()
提供了保护措施。 - Wi-Fi 也使用 ADC2,
adc_oneshot_read()
提供了 Wi-Fi 驱动与 ADC 单次转换模式驱动间的保护。
电源管理
启用电源管理,即启用 CONFIG_PM_ENABLE 时,系统在空闲状态下可能会调整系统时钟频率。然而,ADC 单次转换模式驱动以轮询例程运行,adc_oneshot_read()
会不断检查 CPU 是否完成读取,直到函数返回。在此期间,ADC 单次转换模式驱动程序所在的任务不会受阻塞。因此,在读取时时钟频率保持稳定。
IRAM 安全
flash 写入/擦除、OTA 等原因都可能导致 cache 禁用,此时,默认不应运行任何 ADC 单次转换模式驱动 API。如果在禁用 cache 时执行了 ADC 单次转换模式驱动 API,可能会出现类似 Illegal Instruction
或 Load/Store Prohibited
的错误。
线程安全
-
adc_oneshot_new_unit()
-
adc_oneshot_config_channel()
-
adc_oneshot_read()
上述函数均为线程安全,使用时,可以直接从不同的 RTOS 任务中调用以上函数,无需额外锁保护。
-
adc_oneshot_del_unit()
非线程安全。此外,与上文中线程安全的函数一起调用该函数时,可能导致线程安全函数的调用出错。
API 参考
头文件
头文件
#include "esp_adc/adc_oneshot.h"
组件:components/esp_adc/include/esp_adc/adc_oneshot.h
REQUIRES esp_adc
功能API
创建特定 ADC 单元的句柄
adc_oneshot_new_unit esp_err_t (const adc_oneshot_unit_init_cfg_t *init_config, adc_oneshot_unit_handle_t *ret_unit)
参数:
-
init_config – [in] 驱动程序初始配置
-
ret_unit – [out] ADC 单元手柄
返回:
-
ESP_OK:成功时
-
ESP_ERR_INVALID_ARG:参数无效
-
ESP_ERR_NO_MEM:无内存
-
ESP_ERR_NOT_FOUND:要申请的 ADC 外设已在使用
-
ESP_FAIL : 时钟源未正确初始化
备注
此 API 是线程安全的。有关更多详细信息,请参阅 ADC 编程指南
设置 ADC oneshot 模式所需的配置
esp_err_t adc_oneshot_config_channel(adc_oneshot_unit_handle_t handle、adc_channel_t channel、const adc_oneshot_chan_cfg_t *config)
参数:
-
handle – [in] ADC 手柄
-
channel – 要配置的 [in] ADC 通道
-
config – [in] ADC 配置
返回:
-
ESP_OK:成功时
-
ESP_ERR_INVALID_ARG:参数无效
获取一个 ADC 转换原始结果
esp_err_t adc_oneshot_read(adc_oneshot_unit_handle_t 句柄、adc_channel_t chan、int *out_raw)
参数:
-
handle – [in] ADC 手柄
-
chan – [in] ADC 通道
-
out_raw – [输出] ADC 转换原始结果
返回:* ESP_OK:成功时
- ESP_ERR_INVALID_ARG:参数无效
- ESP_ERR_TIMEOUT:超时,ADC 结果无效
注意:
不能在 ISR(中断) 上下文中调用此 API
删除 ADC 单元手柄)
esp_err_t adc_oneshot_del_unit(adc_oneshot_unit_handle_t handle )
参数:
- handle – [in] ADC 手柄
返回:* ESP_OK:成功时
- ESP_ERR_INVALID_ARG:参数无效
- ESP_ERR_NOT_FOUND:要放弃的 ADC 外设未使用
从给定的 GPIO 编号获取 ADC 通道
esp_err_t adc_oneshot_io_to_channel(int io_num, adc_unit_t *const unit_id, adc_channel_t *const channel )
参数:
-
io_num – [in] GPIO 编号
-
unit_id – [out] ADC 单元
-
channel – [out] ADC 通道
返回:
-
ESP_OK:成功时
-
ESP_ERR_INVALID_ARG:参数无效
-
ESP_ERR_NOT_FOUND: IO 不是有效的 ADC 焊盘
从给定的 ADC 通道获取 GPIO 编号
esp_err_t adc_oneshot_channel_to_io(adc_unit_t unit_id、adc_channel_t channel、int *const io_num)
参数:
-
unit_id – [in] ADC 单元
-
channel – [in] ADC 通道
-
io_num – [out] GPIO 编号
-
- – ESP_OK:成功时
- ESP_ERR_INVALID_ARG:参数无效
获取 ADC 校准结果的便捷功能
esp_err_t adc_oneshot_get_calibrated_result(adc_oneshot_unit_handle_t 句柄、adc_cali_handle_t cali_handle、adc_channel_t chan、int *cali_result)
这是一个多合一功能,它确实:
- oneshot 读取 ADC 原始结果
- 校准原始结果并将其转换为校准结果(以 mV 为单位)
参数:
-
handle – [在] ADC oneshot 句柄中,您应该调用 adc_oneshot_new_unit() 来获取此句柄
-
cali_handle – 在 ADC 校准手柄中,您应该在 adc_cali_scheme.h 中调用 adc_cali_create_scheme_x() 来创建手柄
-
chan – [in] ADC 通道
-
cali_result – [out] 校准的 ADC 结果(以 mV 为单位)
返回:
- ESP_OK 来自 adc_oneshot_read() 和 adc_cali_raw_to_voltage() 的其他返回错误
连续转换模式
简介
ESP32-S3 芯片集成了模数转换器 (ADC),支持测量特定模拟 IO 管脚的模拟信号。此外,ADC 还支持直接内存访问 (DMA) 功能,高效获取 ADC 转换结果。
ESP32-S3 具有 两 个 ADC 单元,可应用于以下场景:
- 生成单次 ADC 转换结果
- 生成连续 ADC 转换结果
ADC 连续转换模式驱动概念
ADC 连续转换模式驱动由多个转换帧组成。
- 转换帧:一个转换帧包含多个转换结果。转换帧大小以字节为单位,在
adc_continuous_new_handle()
中配置。 - 转换结果:一个转换结果包含多个字节,即
SOC_ADC_DIGI_RESULT_BYTES
。转换结果的数据结构由adc_digi_output_data_t
定义,包括 ADC 单元、ADC 通道以及原始数据。
使用步骤
资源分配
ADC 连续转换模式驱动基于 ESP32-S3 SAR ADC 模块实现,不同的 ESP 目标芯片可能拥有不同数量的独立 ADC。
请按照以下步骤设置配置结构体 adc_continuous_handle_cfg_t
,创建 ADC 连续转换模式驱动的句柄:
-
adc_continuous_handle_cfg_t::max_store_buf_size
:以字节为单位设置最大缓冲池的大小,驱动程序将 ADC 转换结果保存到该缓冲池中。缓冲池已满时,新的转换将丢失。 -
adc_continuous_handle_cfg_t::conv_frame_size
:以字节为单位设置 ADC 转换帧大小。 -
adc_continuous_handle_cfg_t::flags
:设置可以改变驱动程序行为的标志。-
flush_pool
:缓冲池满时自动清空缓冲池。
-
完成以上 ADC 配置后,使用已设置的配置结构体 adc_continuous_handle_cfg_t
调用 adc_continuous_new_handle()
。该函数可能将在特定情况下返回错误值,如无效参数、内存不足等。
函数返回 ESP_ERR_NOT_FOUND
时,表明 GDMA 空闲通道不足。
如果不再使用 ADC 连续转换模式驱动,请调用 adc_continuous_deinit()
将驱动去初始化。
IIR 滤波器
ADC 连续转换模式下支持使用两个 IIR 滤波器。请设置 adc_continuous_iir_filter_config_t
结构体并调用 adc_new_continuous_iir_filter()
,以创建 ADC IIR 滤波器。
-
adc_digi_filter_config_t::unit
:ADC 单元。 -
adc_digi_filter_config_t::channel
:将进行滤波的 ADC 通道。 -
adc_digi_filter_config_t::coeff
:滤波器系数。
调用 adc_del_continuous_iir_filter()
可以回收滤波器。
备注
在一个 ADC 通道上同时使用两个滤波器时,只有第一个滤波器会生效。
监视器
当 ADC 在连续转换模式下运行时,支持使用 2 个监视器。你可以在运行中的 ADC 通道上设置一到两个监视器阈值,一旦转换结果超出阈值,监视器将在每个采样循环中触发中断。请设置 adc_monitor_config_t
,并调用 adc_new_continuous_monitor()
以创建 ADC 监视器。
-
adc_monitor_config_t::adc_unit
:配置要监视的 ADC 通道所属的 ADC 单元。 -
adc_monitor_config_t::channel
:要监视的 ADC 通道。 -
adc_monitor_config_t::h_threshold
:高阈值,转换结果大于此值将触发中断,如果不使用此阈值,则将其设置为 -1。 -
adc_monitor_config_t::l_threshold
:低阈值,转换结果小于此值将触发中断,如果不使用此阈值,则将其设置为 -1。
创建监视器后,可以使用以下 API 操作监视器,构建你的应用程序。
-
adc_continuous_monitor_enable()
:启用监视器。 -
adc_continuous_monitor_disable()
:禁用监视器. -
adc_monitor_register_callbacks()
:注册用户回调函数,在 ADC 转换结果超出阈值时,执行相应操作。 -
adc_del_continuous_monitor()
:删除监视器,释放资源。
初始化 ADC 连续转换模式驱动
adc_continuous_handle_t handle = NULL;
adc_continuous_handle_cfg_t adc_config = {.max_store_buf_size = 1024,.conv_frame_size = 256,
};
ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &handle));
回收 ADC 单元
ESP_ERROR_CHECK(adc_continuous_deinit(handle));
配置 ADC
初始化 ADC 连续转换模式驱动后,设置 adc_continuous_config_t
配置 ADC IO,测量模拟信号:
-
adc_continuous_config_t::pattern_num
:要使用的 ADC 通道数量。 -
adc_continuous_config_t::adc_pattern
:每个要使用的 ADC 通道的配置列表,请参阅下文描述。 -
adc_continuous_config_t::sample_freq_hz
:期望的 ADC 采样频率,单位为 Hz。 -
adc_continuous_config_t::conv_mode
:连续转换模式。 -
adc_continuous_config_t::format
:转换模式结果的输出格式。
按照以下步骤设置 adc_digi_pattern_config_t
:
-
adc_digi_pattern_config_t::atten
:ADC 衰减。请参阅 技术规格书 中的ADC 特性
章节。 -
adc_digi_pattern_config_t::channel
:IO 对应的 ADC 通道号,请参阅下文注意事项。 -
adc_digi_pattern_config_t::unit
:IO 所属的 ADC 单元。 -
adc_digi_pattern_config_t::bit_width
:原始转换结果的位宽。
备注
对于 IO 对应的 ADC 通道号,请参阅 技术参考手册 获取 ADC IO 管脚的详细信息。另外,可以使用
adc_continuous_io_to_channel()
和adc_continuous_channel_to_io()
获取 ADC 通道和 ADC IO 的对应关系。
为使这些设置生效,请使用上述配置结构体,调用 adc_continuous_config()
。此 API 可能由于 ESP_ERR_INVALID_ARG
等原因返回错误。当它返回 ESP\_ERR\_INVALID\_STATE
时,意味着 ADC 连续转换模式驱动已经启动,此时不应调用此 API。
请参考 ADC 连续转换模式示例 peripherals/adc/continuous_read,查看相应配置代码。
请调用 adc_continuous_iir_filter_enable()
或 adc_continuous_iir_filter_disable()
,以启用或禁用 ADC IIR 滤波器。
请调用 adc_continuous_monitor_enable()
或 adc_continuous_monitor_disable()
,以启用或禁用 ADC 监视器。
ADC 控制
启动和停止
调用 adc_continuous_start()
,将使 ADC 开始从配置好的 ADC 通道测量模拟信号,并生成转换结果。
相反,调用 adc_continuous_stop()
则会停止 ADC 转换。
ESP_ERROR_CHECK(adc_continuous_stop(handle));
注册事件回调
调用 adc_continuous_register_event_callbacks()
,可以将自己的函数链接到驱动程序的 ISR 中。通过 adc_continuous_evt_cbs_t
可查看所有支持的事件回调。
-
adc_continuous_evt_cbs_t::on_conv_done
:当一个转换帧完成时,触发此事件。 -
adc_continuous_evt_cbs_t::on_pool_ovf
:当内部缓冲池已满时,触发此事件,新的转换结果将丢失。
由于上述回调函数在 ISR 中调用,请确保回调函数适合在 ISR 上下文中运行,且这些回调不应涉及阻塞逻辑。回调函数的原型在 adc_continuous_callback_t
中声明。
在调用 adc_continuous_register_event_callbacks()
时,还可以通过参数 user_data
注册自己的上下文,该用户数据将直接传递给回调函数。
此回调函数可能由于 ESP_ERR_INVALID_ARG
等原因返回错误。启用 CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE 时,如果回调函数失败并报错,可能是因为回调函数不在内部 RAM 中,请查看错误日志了解详情。此外,如果回调函数出现 ESP_ERR_INVALID_STATE
错误,表明 ADC 连续转换模式驱动已经启动,此时不应添加回调。
转换完成事件
当驱动程序完成一次转换后,会触发 adc_continuous_evt_cbs_t::on_conv_done
事件,并填充事件数据。事件数据包含一个指向转换帧缓冲区的指针,以及转换帧缓冲区大小。要了解事件数据结构,请参阅 adc_continuous_evt_data_t
。
注意:
- 数据缓冲区 adc_continuous_evt_data_t::conv_frame_buffer 由驱动程序本身维护,请勿释放此内存。
- 启用 Kconfig 选项 CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE 时,注册的回调函数以及回调函数中调用的函数应放置在 IRAM 中,涉及的变量也应放置在内部 RAM 中。
缓冲池溢出事件
ADC 连续转换模式驱动使用内部缓冲池保存转换结果,缓冲池满时将发生缓冲池溢出事件。此时,驱动程序不会继续填充事件数据。缓冲池溢出通常是因为调用 adc_continuous_read()
从池中读取数据的速度远低于 ADC 转换的速度。
读取转换结果
调用 adc_continuous_start()
启动 ADC 连续转换,调用 adc_continuous_read()
可以获取 ADC 通道的转换结果。注意提供缓冲区,获取原始结果。
函数 adc_continuous_read()
每次都会尝试以期望长度读取转换结果。
- 调用
adc_continuous_read()
可以请求读取指定长度的转换结果。但有时实际可用的转换结果可能少于请求长度,此时,函数仍会将数据从内部池移动到你提供的缓冲区中。因此,请查看out_length
的值,了解实际移动到缓冲区中的转换结果数量。 - 如果内部池中没有生成转换结果,函数将会阻塞一段时间,即
timeout_ms
,直到转换结果生成。如果始终没有转换结果生成,函数将返回ESP_ERR_TIMEOUT
。 - 如果 ADC 连续转换生成的结果填满了内部池,新产生的结果将丢失。下次调用
adc_continuous_read()
时,将返回ESP_ERR_INVALID_STATE
,提示此情况发生。
此 API 提供了一个读取所有 ADC 连续转换结果的机会。
API 参考
头文件
头文件
#include "esp_adc/adc_continuous.h"
组件:components/esp_adc/include/esp_adc/adc_continuous.h
REQUIRES esp_adc
功能函数
初始化 ADC continuous driver 并获取其句柄
esp_err_t adc_continuous_new_handle(const adc_continuous_handle_cfg_t *hdl_config, adc_continuous_handle_t *ret_handle)
参数:
-
hdl_config – [in] 指向 ADC 初始化配置的指针。指。
adc_continuous_handle_cfg_t
-
ret_handle – [out] ADC 连续模式驱动器手柄
返回:
-
ESP_ERR_INVALID_ARG 如果参数组合无效。
-
ESP_ERR_NOT_FOUND 未找到具有指定标志的空闲中断
-
ESP_ERR_NO_MEM 如果内存不足
-
ESP_OK 成功时
设置 ADC 连续模式所需的配置
esp_err_t adc_continuous_config(adc_continuous_handle_t handle 、const adc_continuous_config_t *config)
参数:
-
handle – [in] ADC 连续模式驱动器手柄
-
config – [in] 请参阅 。
adc_digi_config_t
返回:
-
ESP_ERR_INVALID_STATE:Driver state 无效,此时不应调用此 API
-
ESP_ERR_INVALID_ARG:如果参数组合无效。
-
ESP_OK:成功时
注册回调
esp_err_t adc_continuous_register_event_callbacks(adc_continuous_handle_t handle, const adc_continuous_evt_cbs_t *cbs, void *user_data)
参数:
-
handle – [in] ADC 连续模式驱动器手柄
-
cbs – [in] 回调函数组
-
user_data – [in] 用户数据,将直接投递给回调函数
返回:
-
ESP_OK:成功时
-
ESP_ERR_INVALID_ARG:参数无效
-
ESP_ERR_INVALID_STATE:Driver state 无效,此时不应调用此 API
注意:
- 用户可以通过调用此函数并将结构体中待注销的回调成员设置为 NULL 来注销之前注册的回调。
cbs
- 启用 CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE 后,回调本身及其调用的函数应放置在 IRAM 中。涉及的变量 (包括 ) 也应该在内部 RAM 中。
user_data
- 仅当 ADC 连续模式驱动程序未启动时,才能调用此 API。检查返回值以了解这一点。
启动 ADC
esp_err_t adc_continuous_start(adc_continuous_handle_t handle )
在连续模式下启动 ADC。在此之后,硬件开始工作
参数:
- handle – [in] ADC 连续模式驱动器手柄
返回:
-
ESP_ERR_INVALID_STATE Driver 状态无效。
-
ESP_OK 成功时
在连续模式下从 ADC 读取字节
esp_err_t adc_continuous_read(adc_continuous_handle_t handle, uint8_t *buf, uint32_t length_max, uint32_t *out_length、uint32_t timeout_ms)
参数:
-
handle – [in] ADC 连续模式驱动器手柄
-
buf – [out] 要从 ADC 读取的转换结果缓冲区。建议转换为 for 。请参阅此头文件中的小节以了解此概念。
adc_digi_output_data_tADC Conversion ResultsDriver Backgrounds
-
length_max – [in] 从 ADC 读取的 Conversion Results 的预期长度,以字节为单位。
-
out_length – [out] 通过此 API 从 ADC 读取的 Conversion Results 的实际长度,以字节为单位。
-
timeout_ms – [in] 等待通过此 API 获取数据的时间(以毫秒为单位)。
返回:
-
ESP_ERR_INVALID_STATE Driver 状态无效。通常这意味着 ADC 采样率比任务处理率快。
-
ESP_ERR_TIMEOUT作超时
-
ESP_OK 成功时
停止 ADC
esp_err_t adc_continuous_stop(adc_continuous_handle_t handle )
停止 ADC。在此之后,硬件将停止工作。
参数:
- handle – [in] ADC 连续模式驱动器手柄
返回:
-
ESP_ERR_INVALID_STATE Driver 状态无效。
-
ESP_OK 成功时
取消初始化
esp_err_t adc_continuous_deinit(adc_continuous_handle_t handle )
取消初始化 ADC continuous driver。
参数:
- handle – [in] ADC 连续模式驱动器手柄
返回:* ESP_ERR_INVALID_STATE Driver 状态无效。
- ESP_OK 成功时
刷新驱动程序内部池
esp_err_t adc_continuous_flush_pool(adc_continuous_handle_t 手柄)
参数:
- handle – [in] ADC 连续模式驱动器手柄
返回:
-
ESP_ERR_INVALID_STATE Driver 状态无效,则应在 API 处于 init 状态时调用此 API
-
ESP_ERR_INVALID_ARG:参数无效
-
ESP_OK 成功时
注意
- 不应在 ISR 上下文中调用此 API
从给定的 GPIO 编号获取 ADC 通道
esp_err_t adc_continuous_io_to_channel(int io_num, adc_unit_t *const unit_id, adc_channel_t *const channel )
参数:
-
io_num – [in] GPIO 编号
-
unit_id – [out] ADC 单元
-
channel – [out] ADC 通道
返回:
-
ESP_OK:成功时
-
ESP_ERR_INVALID_ARG:参数无效
-
ESP_ERR_NOT_FOUND: IO 不是有效的 ADC 焊盘
从给定的 ADC 通道获取 GPIO 编号
esp_err_t adc_continuous_channel_to_io(adc_unit_t unit_id、adc_channel_t 通道、int *const io_num)
参数:
-
unit_id – [in] ADC 单元
-
channel – [in] ADC 通道
-
io_num – [out] GPIO 编号
返回:
- ESP_OK:成功时
- ESP_ERR_INVALID_ARG:参数无效
ADC 校准
简介
在 ESP32-S3 中,模数转换器 (ADC) 比较输入的模拟电压和参考电压,以确定每一位数字输出结果。ESP32-S3 设计的 ADC 参考电压为 1100 mV。然而,不同芯片的真实参考电压可能会略有变化,范围在 1000 mV 到 1200 mV 之间。本文介绍了 ADC 校准驱动程序,可以降低参考电压不同带来的影响,获取更准确的输出结果。
安装和使用ADC 校准驱动程序的基本步骤
创建校准方案
ADC 校准驱动程序会提供 ADC 校准方案。对于驱动程序来说,每个 ADC 校准方案对应一个 ADC 校准句柄 adc_cali_handle_t
。
使用 adc_cali_check_scheme()
可以查看芯片支持的校准方案。若已了解芯片支持的校准方案,可以跳过该步骤,直接调用对应函数创建校准方案句柄。
使用自定义 ADC 校准方案时,可以选择调整函数 adc_cali_check_scheme()
,或直接跳过该步骤,调用自定义函数创建校准方案句柄。
ADC 校准曲线拟合方案
ESP32-S3 支持 ADC_CALI_SCHEME_VER_CURVE_FITTING
方案。要创建此方案,请先根据以下配置选项,设置 adc_cali_curve_fitting_config_t
。
-
adc_cali_curve_fitting_config_t::unit_id
,表示 ADC 原始结果来自哪个 ADC 单元。 -
adc_cali_curve_fitting_config_t::chan
,此选项保留以供扩展。校准方案仅因衰减程度而异,与通道选择无关。 -
adc_cali_curve_fitting_config_t::atten
,表示 ADC 原始结果的衰减程度。 -
adc_cali_curve_fitting_config_t::bitwidth
,表示 ADC 原始结果的位宽。
设置完上述配置结构体后,请调用 adc_cali_create_scheme_curve_fitting()
创建曲线拟合方案句柄。 由于 ESP_ERR_INVALID_ARG
或 ESP_ERR_NO_MEM
等原因,该函数调用可能失败。函数返回 ESP_ERR_NOT_SUPPORTED
时,说明你的开发板没有烧录校准方案所需的 eFuse 位。
与 eFuse 相关的 ADC 校准故障
函数 adc_cali_create_scheme_curve_fitting()
返回 ESP_ERR_NOT_SUPPORTED
时,代表开发板上校准方案所需的 eFuse 位不正确。
ESP-IDF 提供的 ADC 校准方案基于芯片上某些与 ADC 校准相关的 eFuse 位的值。乐鑫模组已在出厂时完成烧录,无需用户额外烧录。
创建曲线拟合方案句柄
ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");
adc_cali_curve_fitting_config_t cali_config = {.unit_id = unit,.atten = atten,.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &handle));
ADC 校准使用完毕后,请调用 adc_cali_delete_scheme_curve_fitting()
,删除校准方案句柄。
删除曲线拟合方案句柄
ESP_LOGI(TAG, "delete %s calibration scheme", "Curve Fitting");
ESP_ERROR_CHECK(adc_cali_delete_scheme_curve_fitting(handle));
备注
要使用自定义校准方案,可以通过提供创建函数,创建自己的校准方案句柄。请参阅
components/esp_adc/interface/adc_cali_interface.h
中的函数表adc_cali_scheme_t
,了解 ESP ADC 校准接口。
结果转换
对驱动程序进行完上述配置和初始化工作后,可以调用 adc_cali_raw_to_voltage()
,将原始 ADC 结果转换为校准结果,校准结果以 mV 为单位。该函数可能因参数无效而调用失败。如果函数返回 ESP_ERR_INVALID_STATE
,说明校准方案尚未创建。因此你需要创建一个校准方案句柄,通过 adc_cali_check_scheme()
可以了解当前芯片支持的校准方案;你也可以提供自定义校准方案,创建对应的校准方案句柄。
获取电压
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle, adc_raw[0][0], &voltage[0][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN0, voltage[0][0]);
线程安全
驱动程序会确保工厂函数 esp_adc_cali_new_scheme()
的线程安全,使用时,可以直接从不同的 RTOS 任务中调用此类函数,无需额外锁保护。
其他以 adc_cali_handle_t
作为第一个位置参数的函数均非线程安全,在没有设置互斥锁保护的任务中,应避免从多个任务中调用这类函数。
减少噪声
ESP32-S3 ADC 对噪声敏感,可能导致 ADC 读数出现较大偏差。根据不同使用场景,要减少噪声影响,你可能需要将旁路电容(如 100 nF 陶瓷电容)连接到 ADC 使用的输入管脚。此外,也可以通过多次采样,进一步减轻噪声的影响。
API 参考
头文件包含
- components/esp_adc/include/esp_adc/adc_cali.h
#include "esp_adc/adc_cali.h"
包含组件
REQUIRES esp_adc
函数功能
检查支持的 ADC 校准方案
esp_err_t adc_cali_check_scheme(adc_cali_scheme_ver_t *scheme_mask)
参数:
**scheme_mask** -- **[out]** 支持的 ADC 校准方案
返回:
-
ESP_OK:成功时
-
ESP_ERR_INVALID_ARG:参数无效
-
ESP_ERR_NOT_SUPPORTED:不支持校准方案
将 ADC 原始数据转换为校准电压
esp_err_t adc_cali_raw_to_voltage(adc_cali_handle_t 句柄、int raw、int *voltage)
参数:
-
手柄 – [in] ADC 校准手柄
-
raw – [in] ADC 原始数据
-
voltage - - [out] 校准的 ADC 电压(以 mV 为单位)
返回:* ESP_OK:成功时
- ESP_ERR_INVALID_ARG:参数无效
- ESP_ERR_INVALID_STATE:状态无效,方案未注册
应用示例
自定义文件
adc_config.h
#ifndef _ADC_CONFIG_H
#define _ADC_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif#include "esp_err.h"#include "esp_adc/adc_continuous.h"
#include "esp_adc/adc_oneshot.h"void adc_continuous_init(adc_channel_t *channel, uint8_t channel_num); //ADC 连续采集模式初始化
uint32_t adc_continuous_get_raw_data(adc_channel_t channel); //ADC 获取连续采集模式的原始数据
uint16_t adc_continuous_get_voltage(adc_channel_t channel); //ADC 获取连续采集模式的电压
esp_err_t adc_start_continuous(); //ADC 开始连续采集数据
esp_err_t adc_stop_continuous(); //ADC 停止连续采集数据void adc_oneshot_init(adc_channel_t *channel, uint8_t channel_num); //ADC 单次采集初始化
int adc_oneshot_get_raw_value(adc_channel_t channel); //ADC 获取单次采集模式的原始数据
uint16_t adc_oneshot_get_voltage(adc_channel_t channel); //ADC 获取单次采集模式的电压
#ifdef __cplusplus
}
#endif
#endif
adc_config.c
#include "adc_config.h"
#include "esp_log.h"
#include "esp_err.h"
#include <string.h>
#include <stdio.h>#define ADC_UNIT ADC_UNIT_1 //使用ADC1的通道,注意wifi功能和ADC2采集不能同时使用
// ADC衰减 输入范围
// ADC_ATTEN_DB_0 0~1.1V
// ADC_ATTEN_DB_2_5 0~2.5V
// ADC_ATTEN_DB_6 0~2.2V
// ADC_ATTEN_DB_12 0~3.3V
#define ADC_ATTEN ADC_ATTEN_DB_12 //ADC衰减 12dB,可测输入范围0~3.3V
#define ADC_BIT_WIDTH ADC_BITWIDTH_12 //ADC数据位宽 12bit,2^12= 4096#define ADC_CONV_MODE ADC_CONV_SINGLE_UNIT_1 //ADC 循环模式,仅使用ADC1进行循环
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE1
#define ADC_GET_CHANNEL(p_data) ((p_data)->type1.channel) //获取通道号
#define ADC_GET_DATA(p_data) ((p_data)->type1.data) //获取adc原始数据
#else
#define ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE2
#define ADC_GET_CHANNEL(p_data) ((p_data)->type2.channel) //获取通道号
#define ADC_GET_DATA(p_data) ((p_data)->type2.data) //获取adc原始数据
#endif#define ADC_CONV_SIZE 2*16 //每帧采16组数据,可以自定义,//但要注意,> 2 * (ADC通道数num + 1) * m,//举例 通道数num=3, 则建议 ADC_CONV_SIZE = 2 * (3+1) * m (m取非零正整数!)#define TAG "ADC->"static adc_continuous_handle_t adc_cont_handle=NULL; //连续ADC句柄
/// @brief ADC 校准初始化
/// @param unit [in] ADC单元
/// @param channel [in] ADC通道
/// @param atten [in] ADC衰减
/// @param out_handle [out] ADC校准句柄
/// @return 是否成功
static bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{adc_cali_handle_t handle = NULL;esp_err_t ret = ESP_FAIL;bool calibrated = false;#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTEDif (!calibrated) {ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");adc_cali_curve_fitting_config_t cali_config = {.unit_id = unit,.chan = channel,.atten = atten,.bitwidth = ADC_BITWIDTH_DEFAULT,};ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);if (ret == ESP_OK) {calibrated = true;}}
#endif#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTEDif (!calibrated) {ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");adc_cali_line_fitting_config_t cali_config = {.unit_id = unit,.atten = atten,.bitwidth = ADC_BITWIDTH_DEFAULT,};ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);if (ret == ESP_OK) {calibrated = true;}}
#endif*out_handle = handle;if (ret == ESP_OK) {ESP_LOGI(TAG, "Calibration Success");} else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");} else {ESP_LOGE(TAG, "Invalid arg or no memory");}return calibrated;
}/// @brief 连续采集一帧数据完成回调函数
/// @param handle 连续ADC句柄
/// @param edata 事件数据
/// @param user_data 用户数据
/// @return
static bool IRAM_ATTR s_conv_done_cb(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data){esp_err_t ret;uint32_t ret_num = 0;static uint8_t buffer[ADC_CONV_SIZE] = {0};memset(buffer, 0xcc, ADC_CONV_SIZE);ret = adc_continuous_read(handle, buffer, ADC_CONV_SIZE, &ret_num, 0);if (ret == ESP_OK) {for (int i = 0; i < ret_num; i += 2) { // 每个采样点占 2 字节adc_digi_output_data_t *p = (adc_digi_output_data_t*)&buffer[i];uint32_t chan_num = ADC_GET_CHANNEL(p);uint32_t raw_data = ADC_GET_DATA(p); }return true ;}else return false ;
}
/// @brief adc 连续采集模式初始化
/// @param channel //需要采集的通道数组
/// @param channel_num // 通道个数
void adc_continuous_init(adc_channel_t *channel, uint8_t channel_num)
{ESP_LOGI(TAG,"continuous_adc_init ");adc_continuous_handle_cfg_t adc_config = {.max_store_buf_size = 512, // DMA 缓冲区大小.conv_frame_size = ADC_CONV_SIZE, // 每帧数据大小};ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &adc_cont_handle));// 配置 ADC 通道adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0}; for (int i = 0; i < channel_num; i++) {adc_pattern[i].atten = ADC_ATTEN; //ADC 衰减adc_pattern[i].channel = channel[i] & 0x7; //通道号adc_pattern[i].unit = ADC_UNIT; //ADC单元adc_pattern[i].bit_width = ADC_BIT_WIDTH; //数据位宽ESP_LOGI(TAG, "adc_pattern[%d].atten is :%"PRIx8, i, adc_pattern[i].atten);ESP_LOGI(TAG, "adc_pattern[%d].channel is :%"PRIx8, i, adc_pattern[i].channel);ESP_LOGI(TAG, "adc_pattern[%d].unit is :%"PRIx8, i, adc_pattern[i].unit);}// 配置 ADC 连续模式adc_continuous_config_t dig_cfg = {.sample_freq_hz = 20 * 1000, // 采样率.conv_mode = ADC_CONV_MODE, // 转换模式.format = ADC_OUTPUT_TYPE, // 输出格式 .pattern_num = channel_num, // 通道个数.adc_pattern = adc_pattern, //通道};ESP_ERROR_CHECK(adc_continuous_config(adc_cont_handle, &dig_cfg));//校准adc_cali_handle_t adc1_cali_chan0_handle = NULL;for (int i = 0; i < channel_num; i++){adc_calibration_init(ADC_UNIT, channel[i]& 0x7, ADC_ATTEN, &adc1_cali_chan0_handle); //校准}#if 0 adc_continuous_evt_cbs_t cbs = { .on_conv_done = s_conv_done_cb, //一帧数据转换完成回调.on_pool_ovf = NULL, //DMA 缓冲满了回调};ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(adc_cont_handle, &cbs, NULL)); //注册回调函数#endifESP_LOGI(TAG,"continuous_adc_init DONE! ");}
/// @brief ADC 开始连续采集数据
/// @return
esp_err_t adc_start_continuous()
{return adc_continuous_start(adc_cont_handle);
}
/// @brief ADC 停止连续采集数据
/// @return
esp_err_t adc_stop_continuous()
{return adc_continuous_stop(adc_cont_handle);
}
/// @brief ADC 获取连续采集模式的原始数据
/// @param channel 指定的通道
/// @return 一帧数据对应通道数据的平均值
uint32_t adc_continuous_get_raw_data(adc_channel_t channel)
{uint32_t raw_data_ave=0;uint16_t ave_cnt=0;uint32_t ret_num = 0;static uint8_t buffer[ADC_CONV_SIZE] = {0};memset(buffer, 0xcc, ADC_CONV_SIZE);esp_err_t ret = adc_continuous_read(adc_cont_handle, buffer, ADC_CONV_SIZE, &ret_num, 0);if (ret == ESP_OK) {ESP_LOGI(TAG, "ret_num is %"PRIu32" bytes" ,ret_num);// 解析 TYPE2 格式的数据for (int i = 0; i < ret_num; i += 2) { // 每个采样点占 2 字节adc_digi_output_data_t *p = (adc_digi_output_data_t*)&buffer[i];uint32_t chan_num = ADC_GET_CHANNEL(p);if(chan_num==channel){ave_cnt++;raw_data_ave += ADC_GET_DATA(p);} }}else {ESP_LOGE(TAG, "Failed to read data: %d", ret);}raw_data_ave = raw_data_ave / ave_cnt; //求平均值ESP_LOGI(TAG,"channel: %d, Raw Ave Data: %ld", channel, raw_data_ave);return raw_data_ave;
}
/// @brief 获取ADC 获取连续采集模式的电压
/// @param channel 指定的通道
/// @return 电压值,单位mV
uint16_t adc_continuous_get_voltage(adc_channel_t channel)
{uint32_t raw_data = adc_continuous_get_raw_data(channel);uint16_t max_voltage=1;uint16_t max_bitwidth=1;uint16_t voltage = 0;if(ADC_BIT_WIDTH==ADC_BITWIDTH_DEFAULT){max_bitwidth = (1<<ADC_BITWIDTH_13)-1;}max_bitwidth = (1<<ADC_BIT_WIDTH)-1;switch (ADC_ATTEN){case ADC_ATTEN_DB_0: max_voltage = 1100;break;case ADC_ATTEN_DB_2_5: max_voltage = 2500;break;case ADC_ATTEN_DB_6: max_voltage = 2200;break;case ADC_ATTEN_DB_12: max_voltage = 3300;break; default:break; }ESP_LOGI(TAG,"max_voltage=%d,max_bitwidth%d",max_voltage,max_bitwidth);voltage = raw_data *max_voltage / max_bitwidth;ESP_LOGI(TAG,"voltage=%d mV",voltage);return voltage;
}adc_oneshot_unit_handle_t adc_oneshot_handle; //ADC单次采集 句柄/// @brief ADC 单次采集初始化
/// @param channel 通道数组
/// @param channel_num 通道个数
void adc_oneshot_init(adc_channel_t *channel, uint8_t channel_num)
{ ESP_LOGI(TAG,"adc_oneshot_init start!");// 初始化 ADC 单元adc_oneshot_unit_init_cfg_t init_config = {.unit_id = ADC_UNIT, // 使用 ADC1};ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_oneshot_handle));// 配置 ADC 通道adc_oneshot_chan_cfg_t channel_config = {.atten = ADC_ATTEN, // 衰减值.bitwidth = ADC_BIT_WIDTH, //ADC 数据位宽};adc_cali_handle_t adc1_cali_chan0_handle = NULL;for (uint8_t i = 0; i < channel_num; i++){ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_oneshot_handle, channel[i]&0x07, &channel_config));adc_calibration_init(ADC_UNIT, channel[i]&0x07, ADC_ATTEN, &adc1_cali_chan0_handle); //校准/* code */}ESP_LOGI(TAG,"adc_oneshot_init DONE! ");
}
/// @brief ADC 获取单次采集模式的原始数据
/// @param channel 指定的ADC通道
/// @return 原始数据
int adc_oneshot_get_raw_value(adc_channel_t channel)
{int adc_raw_value = 0;ESP_ERROR_CHECK(adc_oneshot_read(adc_oneshot_handle, channel, &adc_raw_value));ESP_LOGI(TAG,"ADC Raw Value: %d\n", adc_raw_value);return adc_raw_value;
}
/// @brief ADC 获取单次采集模式的电压
/// @param channel 指定的通道
/// @return 电压值,单位mV
uint16_t adc_oneshot_get_voltage(adc_channel_t channel)
{uint32_t raw_data = adc_oneshot_get_raw_value(channel);uint16_t max_voltage=1;uint16_t max_bitwidth=1;uint16_t voltage = 0;if(ADC_BIT_WIDTH==ADC_BITWIDTH_DEFAULT){max_bitwidth = (1<<ADC_BITWIDTH_13)-1;}max_bitwidth = (1<<ADC_BIT_WIDTH)-1;switch (ADC_ATTEN){case ADC_ATTEN_DB_0: max_voltage = 1100;break;case ADC_ATTEN_DB_2_5: max_voltage = 2500;break;case ADC_ATTEN_DB_6: max_voltage = 2200;break;case ADC_ATTEN_DB_12: max_voltage = 3300;break; default:break; }ESP_LOGI(TAG,"max_voltage=%d,max_bitwidth%d",max_voltage,max_bitwidth);voltage = raw_data *max_voltage / max_bitwidth;ESP_LOGI(TAG,"voltage=%d mV",voltage);return voltage;
}
app_main.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"///... 其他头文件
#include "adc_config.h"#define TAG "main"void app_main(void)
{///... 其他代码adc_channel_t channel[2] = {ADC_CHANNEL_2,ADC_CHANNEL_2};adc_continuous_init(channel,1); // ADC 连续模式adc_start_continuous(); // 启动ADC连续模式// adc_oneshot_init(channel,1); //ADC 单次模式vTaskDelay(1000 / portTICK_PERIOD_MS); //等待系统稳定uint16_t adc_voltage=0;///...其他代码while(1){///...其他代码adc_voltage = adc_continuous_get_voltage(ADC_CHANNEL_2); //采集连续// adc_voltage = adc_oneshot_get_voltage(ADC_CHANNEL_2); //单次采集ESP_LOGI(TAG,"voltage=%d mV",adc_voltage );vTaskDelay(500 / portTICK_PERIOD_MS);///...其他代码 }}