警告

This document is not updated for ESP32P4 yet, so some of the content may not be correct.

This warning was automatically inserted due to the source file being in the add_warnings_pages list.

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

[English]

## 简介

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

ESP32-P4 具有 两 个 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-P4 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()` 将驱动去初始化。

#### 初始化 ADC 连续转换模式驱动

    adc_continuous_handle_cfg_t adc_config = {
        .max_store_buf_size = 1024,
        .conv_frame_size = 100,
    };
    ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config));

#### 回收 ADC 单元

    ESP_ERROR_CHECK(adc_continuous_deinit());

### 配置 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_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());

### 注册事件回调

调用 `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 生成随机数时，随机性会减弱。

### 电源管理

启用电源管理，即启用 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 不一定线程安全，但驱动程序提供了共享硬件互斥，详情请参阅 硬件限制。

## 应用示例

- ADC 连续转换模式示例：peripherals/adc/continuous_read。

## 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::[anonymous] 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

提供有关此文档的反馈