模数转换器 (ADC) 连续转换模式驱动
简介
ESP32-C61 芯片集成了模数转换器 (ADC),支持测量特定模拟 IO 管脚的模拟信号。此外,ADC 还支持直接内存访问 (DMA) 功能,高效获取 ADC 转换结果。
ESP32-C61 具有 一 个 ADC 单元,可应用于以下场景:
生成单次 ADC 转换结果
生成连续 ADC 转换结果
本指南介绍了 ADC 连续转换模式。
ADC 连续转换模式驱动概念
ADC 连续转换模式驱动由多个转换帧组成。
转换帧:一个转换帧包含多个转换结果。转换帧大小以字节为单位,在
adc_continuous_new_handle()
中配置。转换结果:一个转换结果包含多个字节,即
SOC_ADC_DIGI_RESULT_BYTES
。转换结果的数据结构由adc_digi_output_data_t
定义,包括 ADC 单元、ADC 通道以及原始数据。
功能概述
下文将分节概述安装 ADC 连续转换模式驱动、并从一组 ADC 通道连续读取 ADC 转换结果的基本步骤:
资源分配:介绍初始化 ADC 连续转换模式驱动所需设置的参数,以及如何将驱动去初始化。
配置 ADC:介绍如何将 ADC 配置为在连续转换模式下工作。
ADC 控制:介绍 ADC 控制函数。
注册事件回调:介绍如何将特定用户代码链接到 ADC 连续转换模式事件回调函数。
读取转换结果:介绍如何获取 ADC 转换结果。
硬件限制:介绍与 ADC 相关的硬件限制。
电源管理:介绍电源管理的相关内容。
IRAM 安全:介绍与 IRAM 安全相关的函数。
线程安全:介绍由驱动程序认证为线程安全的 API。
资源分配
ADC 连续转换模式驱动基于 ESP32-C61 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_continuous_monitor_register_event_callbacks()
:注册用户回调函数,在 ADC 转换结果超出阈值时,执行相应操作。adc_del_continuous_monitor()
:删除监视器,释放资源。
特别地,监视器功能可用于实现过零检测。由于 ADC 无法直接处理负输入信号,可以通过 直流偏置(DC bias) 来实现过零检测。
首先,通过电路将直流偏置添加到输入信号中,以将负信号“移位”到 ADC 的测量范围内。关于 ADC 的测量范围,请参考 技术参考手册 中的片上传感器与模拟信号处理章节。例如,添加一个 1 V 的偏置可以将 -1 V 至 +1 V 的信号变换到 0 V 至 2 V 的范围。然后,通过设置合适的高阈值与低阈值,ADC 可以检测输入信号是否接近零,从而识别信号的相位变化。详情请参考下面的示例代码。
// 初始化 ADC 监视器句柄
adc_monitor_handle_t adc_monitor_handle = NULL;
// 配置 ADC 监视器
adc_monitor_config_t zero_crossing_config = {
.adc_unit = EXAMPLE_ADC_UNIT_1, // 指定要监视的 ADC 单元
.channel = EXAMPLE_ADC_CHANNEL_0, // 指定要监视的 ADC 通道
.h_threshold = 1100, // 设置监视的高阈值为接近偏置值,请根据实际情况进行调整
.l_threshold = 900, // 设置监视的低阈值为接近偏置值,请根据实际情况进行调整
};
// 创建 ADC 监视器
ESP_ERROR_CHECK(adc_new_continuous_monitor(&zero_crossing_config, &adc_monitor_handle));
// 注册回调函数
adc_monitor_evt_cbs_t zero_crossing_cbs = {
.on_over_high_thresh = example_on_exceed_high_thresh,
.on_below_low_thresh = example_on_below_low_thresh,
};
ESP_ERROR_CHECK(adc_continuous_monitor_register_event_callbacks(adc_monitor_handle, &zero_crossing_cbs, NULL));
// 启用 ADC 监视器
ESP_ERROR_CHECK(adc_continuous_monitor_enable(adc_monitor_handle));
// 禁用并删除 ADC 监视器
ESP_ERROR_CHECK(adc_continuous_monitor_disable(adc_monitor_handle));
ESP_ERROR_CHECK(adc_del_continuous_monitor(adc_monitor_handle));
初始化 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::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 连续转换结果的机会。
从上述函数读取的 ADC 转换结果为原始数据。要根据 ADC 原始结果计算电压,可以使用以下公式:
Vout = Dout * Vmax / Dmax (1)
其中:
Vout |
数据输出结果,代表电压。 |
---|---|
Dout |
ADC 原始数据读取结果。 |
Vmax |
可测量的最大模拟输入电压,与 ADC 衰减相关,请参考 技术参考手册 中的片上传感器与模拟信号处理章节。 |
Dmax |
输出 ADC 原始数据读取结果的最大值,即 2^位宽,位宽即之前配置的 |
若需进一步校准,将 ADC 原始结果转换为以 mV 为单位的电压数据,请参考 模数转换器 (ADC) 校准驱动程序。
硬件限制
一个 ADC 单元一次只能运行一种操作模式,即连续模式或单次模式。
adc_continuous_start()
提供了保护措施。随机数生成器 (RNG) 以 ADC 为输入源。使用 ADC 连续转换模式驱动从 RNG 生成随机数时,随机性会减弱。
电源管理
启用电源管理,即启用 CONFIG_PM_ENABLE 时,系统在空闲状态下,可能会调整 APB 时钟频率,这可能会改变 ADC 连续转换的行为。
然而,通过获取类型为 ESP_PM_APB_FREQ_MAX
的电源管理锁,ADC 连续转换模式驱动可以阻止这种改变。调用 adc_continuous_start()
启动连续转换后即可获取该锁。同样,调用 adc_continuous_stop()
停止转换后将释放该锁。因此,必须确保 adc_continuous_start()
和 adc_continuous_stop()
成对出现,否则电源管理将失效。
IRAM 安全
ADC 连续转换模式驱动的所有 API 均非 IRAM 安全。禁用 cache 时,不应运行这类 API。启用 Kconfig 选项 CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE 可确保驱动的内部 ISR 处理程序为 IRAM 安全,此时即使禁用 cache,驱动仍然会将转换结果保存到其内部缓冲池中。
线程安全
ADC 连续转换模式驱动的 API 不一定线程安全,但驱动程序提供了共享硬件互斥,详情请参阅 硬件限制。
应用示例
peripherals/adc/continuous_read 演示了如何在 ESP32-C61 开发板上使用 ADC 连续读取模式(DMA 模式),通过片上 ADC 模块从 GPIO 管脚读取数据。
API 参考
Header File
This header file can be included with:
#include "esp_adc/adc_continuous.h"
This header file is a part of the API provided by the
esp_adc
component. To declare that your component depends onesp_adc
, add the following to your CMakeLists.txt:REQUIRES esp_adc
or
PRIV_REQUIRES esp_adc
Functions
-
esp_err_t adc_continuous_new_handle(const adc_continuous_handle_cfg_t *hdl_config, adc_continuous_handle_t *ret_handle)
Initialize ADC continuous driver and get a handle to it.
- 参数
hdl_config -- [in] Pointer to ADC initialization config. Refer to
adc_continuous_handle_cfg_t
.ret_handle -- [out] ADC continuous mode driver handle
- 返回
ESP_ERR_INVALID_ARG If the combination of arguments is invalid.
ESP_ERR_NOT_FOUND No free interrupt found with the specified flags
ESP_ERR_NO_MEM If out of memory
ESP_OK On success
-
esp_err_t adc_continuous_config(adc_continuous_handle_t handle, const adc_continuous_config_t *config)
Set ADC continuous mode required configurations.
- 参数
handle -- [in] ADC continuous mode driver handle
config -- [in] Refer to
adc_digi_config_t
.
- 返回
ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment
ESP_ERR_INVALID_ARG: If the combination of arguments is invalid.
ESP_OK: On success
-
esp_err_t adc_continuous_register_event_callbacks(adc_continuous_handle_t handle, const adc_continuous_evt_cbs_t *cbs, void *user_data)
Register callbacks.
备注
User can deregister a previously registered callback by calling this function and setting the to-be-deregistered callback member in the
cbs
structure to NULL.备注
When CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. Involved variables (including
user_data
) should be in internal RAM as well.备注
You should only call this API when the ADC continuous mode driver isn't started. Check return value to know this.
- 参数
handle -- [in] ADC continuous mode driver handle
cbs -- [in] Group of callback functions
user_data -- [in] User data, which will be delivered to the callback functions directly
- 返回
ESP_OK: On success
ESP_ERR_INVALID_ARG: Invalid arguments
ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment
-
esp_err_t adc_continuous_start(adc_continuous_handle_t handle)
Start the ADC under continuous mode. After this, the hardware starts working.
- 参数
handle -- [in] ADC continuous mode driver handle
- 返回
ESP_ERR_INVALID_STATE Driver state is invalid.
ESP_OK On success
-
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)
Read bytes from ADC under continuous mode.
- 参数
handle -- [in] ADC continuous mode driver handle
buf -- [out] Conversion result buffer to read from ADC. Suggest convert to
adc_digi_output_data_t
forADC Conversion Results
. See the subsectionDriver Backgrounds
in this header file to learn about this concept.length_max -- [in] Expected length of the Conversion Results read from the ADC, in bytes.
out_length -- [out] Real length of the Conversion Results read from the ADC via this API, in bytes.
timeout_ms -- [in] Time to wait for data via this API, in millisecond.
- 返回
ESP_ERR_INVALID_STATE Driver state is invalid. Usually it means the ADC sampling rate is faster than the task processing rate.
ESP_ERR_TIMEOUT Operation timed out
ESP_OK On success
-
esp_err_t adc_continuous_stop(adc_continuous_handle_t handle)
Stop the ADC. After this, the hardware stops working.
- 参数
handle -- [in] ADC continuous mode driver handle
- 返回
ESP_ERR_INVALID_STATE Driver state is invalid.
ESP_OK On success
-
esp_err_t adc_continuous_deinit(adc_continuous_handle_t handle)
Deinitialize the ADC continuous driver.
- 参数
handle -- [in] ADC continuous mode driver handle
- 返回
ESP_ERR_INVALID_STATE Driver state is invalid.
ESP_OK On success
-
esp_err_t adc_continuous_flush_pool(adc_continuous_handle_t handle)
Flush the driver internal pool.
备注
This API is not supposed to be called in an ISR context
- 参数
handle -- [in] ADC continuous mode driver handle
- 返回
ESP_ERR_INVALID_STATE Driver state is invalid, you should call this API when it's in init state
ESP_ERR_INVALID_ARG: Invalid arguments
ESP_OK On success
-
esp_err_t adc_continuous_io_to_channel(int io_num, adc_unit_t *const unit_id, adc_channel_t *const channel)
Get ADC channel from the given GPIO number.
- 参数
io_num -- [in] GPIO number
unit_id -- [out] ADC unit
channel -- [out] ADC channel
- 返回
ESP_OK: On success
ESP_ERR_INVALID_ARG: Invalid argument
ESP_ERR_NOT_FOUND: The IO is not a valid ADC pad
-
esp_err_t adc_continuous_channel_to_io(adc_unit_t unit_id, adc_channel_t channel, int *const io_num)
Get GPIO number from the given ADC channel.
- 参数
unit_id -- [in] ADC unit
channel -- [in] ADC channel
io_num -- [out] GPIO number
- -- ESP_OK: On success
ESP_ERR_INVALID_ARG: Invalid argument
Structures
-
struct adc_continuous_handle_cfg_t
ADC continuous mode driver initial configurations.
Public Members
-
uint32_t max_store_buf_size
Max length of the conversion results that driver can store, in bytes.
-
uint32_t conv_frame_size
Conversion frame size, in bytes. This should be in multiples of
SOC_ADC_DIGI_DATA_BYTES_PER_CONV
.
-
uint32_t flush_pool
Flush the internal pool when the pool is full.
-
struct adc_continuous_handle_cfg_t::[anonymous] flags
Driver flags.
-
uint32_t max_store_buf_size
-
struct adc_continuous_config_t
ADC continuous mode driver configurations.
Public Members
-
uint32_t pattern_num
Number of ADC channels that will be used.
-
adc_digi_pattern_config_t *adc_pattern
List of configs for each ADC channel that will be used.
-
uint32_t sample_freq_hz
The expected ADC sampling frequency in Hz. Please refer to
soc/soc_caps.h
to know available sampling frequency range
-
adc_digi_convert_mode_t conv_mode
ADC DMA conversion mode, see
adc_digi_convert_mode_t
.
-
adc_digi_output_format_t format
ADC DMA conversion output format, see
adc_digi_output_format_t
.
-
uint32_t pattern_num
-
struct adc_continuous_evt_data_t
Event data structure.
备注
The
conv_frame_buffer
is maintained by the driver itself, so never free this piece of memory.
-
struct adc_continuous_evt_cbs_t
Group of ADC continuous mode callbacks.
备注
These callbacks are all running in an ISR environment.
备注
When CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. Involved variables should be in internal RAM as well.
Public Members
-
adc_continuous_callback_t on_conv_done
Event callback, invoked when one conversion frame is done. See the subsection
Driver Backgrounds
in this header file to learn about theconversion frame
concept.
-
adc_continuous_callback_t on_pool_ovf
Event callback, invoked when the internal pool is full.
-
adc_continuous_callback_t on_conv_done
Macros
-
ADC_MAX_DELAY
ADC read max timeout value, it may make the
adc_continuous_read
block forever if the OS supports.
Type Definitions
-
typedef struct adc_continuous_ctx_t *adc_continuous_handle_t
Type of adc continuous mode driver handle.
-
typedef bool (*adc_continuous_callback_t)(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data)
Prototype of ADC continuous mode event callback.
- Param handle
[in] ADC continuous mode driver handle
- Param edata
[in] Pointer to ADC continuous mode event data
- Param user_data
[in] User registered context, registered when in
adc_continuous_register_event_callbacks()
- Return
Whether a high priority task is woken up by this function