# 模数转换器 (ADC) 连续转换模式驱动

[English]

## 简介

ESP32 芯片集成了模数转换器 (ADC)，支持测量特定模拟 IO 管脚的模拟信号。此外，ADC 还支持直接内存访问 (DMA) 功能，高效获取 ADC 转换结果。

ESP32 具有 两 个 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 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` 时，表明 I2S0 外设正在使用中，详情请参阅 硬件限制。

如果不再使用 ADC 连续转换模式驱动，请调用 `adc_continuous_deinit()` 将驱动去初始化。

#### 初始化 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 控制

#### 启动和停止

调用 `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_digi_pattern_config_t::bit_width`。

若需进一步校准，将 ADC 原始结果转换为以 mV 为单位的电压数据，请参考 模数转换器 (ADC) 校准驱动程序。

### 硬件限制

- 一个 ADC 单元一次只能运行一种操作模式，即连续模式或单次模式。`adc_continuous_start()` 提供了保护措施。

- 随机数生成器 (RNG) 以 ADC 为输入源。使用 ADC 连续转换模式驱动从 RNG 生成随机数时，随机性会减弱。

- Wi-Fi 也使用 ADC2，`adc_continuous_start()` 提供了 Wi-Fi 驱动和 ADC 连续转换模式驱动之间的保护。

- ADC 连续转换模式驱动使用 I2S0 外设作为硬件 DMA FIFO。因此，如果 I2S0 已在使用中，`adc_continuous_new_handle()` 将返回 `ESP_ERR_NOT_FOUND`。

- ESP32 DevKitC：由于存在外部自动烧录电路，GPIO 0 不能用于 ADC 连续转换模式。

- ESP-WROVER-KIT：由于部分 GPIO 管脚可能已经用于其他目的，GPIO 0、2、4 和 15 不能用于 ADC 连续转换模式。

### 电源管理

启用电源管理，即启用 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 开发板上使用 ADC 连续读取模式（DMA 模式），通过片上 ADC 模块从 GPIO 管脚读取数据。

## API 参考

### Header File

- components/esp_adc/include/esp_adc/adc_continuous.h

- 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 on `esp_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` for `ADC Conversion Results`. See the subsection `Driver 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 flags

    Driver flags.

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`.

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.

  Public Members

  uint8_t *conv_frame_buffer

    Pointer to conversion result buffer for one conversion frame.

  uint32_t size

    Conversion frame size.

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 the `conversion frame` concept.

  adc_continuous_callback_t on_pool_ovf

    Event callback, invoked when the internal pool is full.

### 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

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。