ESP32学习笔记_Peripherals(3)——ADC

摘要

本博客介绍了ESP32-S3芯片内置SAR ADC的原理、参考电压、分辨率、信号衰减等基础知识,并讲解了如何使用ESP-IDF驱动库实现ADC的连续采样(DMA)功能,演示了多通道模拟信号(如摇杆模块)的采集与处理流程

文章目录

ADC

ADC(模数转换器)的作用是将外部的模拟信号(如电压、传感器输出等)转换为数字信号,便于微控制器或处理器进行读取、处理和分析,是连接物理世界与数字系统的重要桥梁

参考资料:ESP-IDF开发指南-ADC单词读取
ESP-IDF-ADC连续读取(DMA)示例
ESP-IDF开发指南-ADC校准程序

采样方法

SAR ADC(Successive Approximation Register Analog-to-Digital Converter,逐次逼近寄存器模数转换器)是一种常见的模数转换器(ADC)架构,用于将模拟信号转换为数字信号;它以逐次逼近的方式工作,具有较高的转换速度和精度,广泛应用于嵌入式系统、传感器接口和数据采集系统中

工作原理

SAR ADC 的核心是一个逐次逼近寄存器(SAR)和一个比较器,其工作过程如下:

  1. 采样保持:输入的模拟信号通过采样保持电路保持稳定
  2. 逐次逼近
    • SAR 控制一个数字到模拟转换器(DAC),生成一个逼近的模拟电压
    • 比较器将输入信号与 DAC 输出的逼近电压进行比较
    • 根据比较结果,SAR 调整下一步的逼近值,逐步逼近输入信号
  3. 输出结果:经过多次比较后,SAR 最终确定输入信号的数字表示,并输出结果

ESP32-S3 内置了两个 12 位的 SAR ADC,可测量最多来自 20 个管脚的模拟信号,支持 12 位采样分辨率

参考电压

ESP32-S3 设计的 ADC 参考电压为 1100 mV,然而,不同芯片的真实参考电压可能会略有变化,范围在 1000 mV 到 1200 mV 之间

通过 ADC 校准驱动程序,可以降低参考电压不同带来的影响,获取更准确的输出结果

电压步长 = 参考电压 2 分辨率 − 1 = 1.1 V 4095 ≈ 0.268 mV \text{电压步长} = \frac{\text{参考电压}}{2^{\text{分辨率}} - 1} = \frac{1.1 \text{V}}{4095} \approx 0.268 \text{mV} 电压步长=2分辨率−1参考电压=40951.1V≈0.268mV

如果输入电压超过 1.1V,可能会导致 ADC 饱和,无法正确测量

采样示例

ESP32-S3 的 ADC 默认参考电压为 1.1V ,分辨率为 12 位 ,即数字值范围为 0 ~ 4095。每个数字单位(步长)对应的电压为:
电压步长 = 参考电压 2 分辨率 − 1 = 1.1 V 4095 ≈ 0.268 mV \text{电压步长} = \frac{\text{参考电压}}{2^{\text{分辨率}} - 1} = \frac{1.1\text{ V}}{4095} \approx 0.268 \text{ mV} 电压步长=2分辨率−1参考电压=40951.1 V≈0.268 mV

假设待测电压为 0.55V

  1. 第一次比较(确定最高位 - 第12位)
    • 尝试将第12位设为1,DAC输出 = 0.55V
    • 待测电压 = 0.55V
    • 待测电压等于DAC输出,第12位确定为1
    • 当前数字值:1000 0000 0000 = 2048
  2. 第二次比较(确定第11位)
    • 尝试将第11位设为1,DAC输出 = 0.55V + 0.275V = 0.825V
    • 待测电压 = 0.55V
    • 待测电压小于DAC输出,第11位确定为0
    • 当前数字值:1000 0000 0000 = 2048
  3. 第三次比较(确定第10位)
    • 尝试将第10位设为1,DAC输出 = 0.55V + 0.1375V = 0.6875V
    • 待测电压 = 0.55V
    • 待测电压小于DAC输出,第10位确定为0
    • 当前数字值:1000 0000 0000 = 2048
  4. 以此类推,剩余的低位也都确定为0

经过12次比较后,ADC输出的数字值为 1000 0000 0000 即 2048 ,,根据公式计算实际电压:
电压 = 数字值 4095 × 1.1 V = 2048 4095 × 1.1 V ≈ 0.55 V \text{电压} = \frac{\text{数字值}}{4095} \times 1.1 \text{ V} = \frac{2048}{4095} \times 1.1 \text{ V} \approx 0.55 \text{ V} 电压=4095数字值×1.1 V=40952048×1.1 V≈0.55 V

信号衰减

SAR ADC 转换模拟信号时,转换分辨率(12 位)电压范围为 0 mV ~ Vref,其中,Vref 为 SAR ADC 内部参考电压,出厂设定为 1100 mV

如需转换大于 Vref 的电压,信号输入 SAR ADC 前可进行衰减,衰减可配置为 0 dB、2.5 dB、6 dB 和 12dB

c 复制代码
/**
 * @brief ADC attenuation parameter. Different parameters determine the range of the ADC.
 */
typedef enum {
    ADC_ATTEN_DB_0   = 0,  ///<No input attenuation, ADC can measure up to approx.
    ADC_ATTEN_DB_2_5 = 1,  ///<The input voltage of ADC will be attenuated extending the range of measurement by about 2.5 dB
    ADC_ATTEN_DB_6   = 2,  ///<The input voltage of ADC will be attenuated extending the range of measurement by about 6 dB
    ADC_ATTEN_DB_12  = 3,  ///<The input voltage of ADC will be attenuated extending the range of measurement by about 12 dB
    ADC_ATTEN_DB_11 __attribute__((deprecated)) = ADC_ATTEN_DB_12,  ///<This is deprecated, it behaves the same as `ADC_ATTEN_DB_12`
} adc_atten_t;

连续采样模式 (DMA)

转换帧:一个转换帧包含多个转换结果。转换帧大小以字节为单位,在 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 连续转换模式驱动的句柄:
max_store_buf_size 以字节为单位设置最大缓冲池的大小,驱动程序将 ADC 转换结果保存到该缓冲池中。缓冲池已满时,新的转换将丢失
conv_frame_size 以字节为单位设置 ADC 转换帧大小
flags 设置可以改变驱动程序行为的标志
flush_pool 缓冲池满时自动清空缓冲池

c 复制代码
adc_continuous_handle_t handle = NULL; // 创建一个指向 ADC 连续模式句柄的指针  
  
adc_continuous_handle_cfg_t adc_config = {  
    .max_store_buf_size = 1024, // 设置最大存储缓冲区大小为 1024 字节  
    .conv_frame_size = EXAMPLE_READ_LEN, // 设置转换帧大小为 EXAMPLE_READ_LEN 字节  
};  
ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &handle)); // 创建新的 ADC 连续模式句柄

完成以上 ADC 配置后,使用已设置的配置结构体 adc_continuous_handle_cfg_t 调用 adc_continuous_new_handle (),该函数可能返回错误值,如无效参数、内存不足等

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

ESP_ERROR_CHECK (adc_continuous_deinit (handle));

配置 ADC

初始化 ADC 连续转换模式驱动后,设置 adc_continuous_config_t 配置 ADC IO,测量模拟信号:
pattern_num要使用的 ADC 通道数量
adc_pattern每个要使用的 ADC 通道的配置列表
sample_freq_hz期望的 ADC 采样频率,单位为 Hz
conv_mode连续转换模式
format 转换模式结果的输出格式

c 复制代码
adc_continuous_config_t dig_cfg = {  
    .sample_freq_hz = 20 * 1000, // 设置采样频率为 20 kHz
    .conv_mode = EXAMPLE_ADC_CONV_MODE, // 设置转换模式为单通道模式
    .format = EXAMPLE_ADC_OUTPUT_TYPE, // 设置输出格式为 EXAMPLE_ADC_OUTPUT_TYPE2
    };
dig_cfg.pattern_num = channel_num; // 设置模式数量为通道数量
dig_cfg.adc_pattern = adc_pattern; // 设置 ADC 模式为 adc_pattern 数组,要先创建 adc_digi_pattern_config_t 再赋值这个

逐个配置 ADC 通道 adc_digi_pattern_config_t
atten ADC 衰减[[#信号衰减]]
channelIO 对应的 ADC 通道号,请参阅下文注意事项
unitIO 所属的 ADC 单元
bit_width原始转换结果的位宽

c 复制代码
adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0}; // 创建一个 ADC 数字模式配置数组  
for (int i = 0; i < channel_num; i++) // 逐个配置通道  
    adc_pattern[i].atten = EXAMPLE_ADC_ATTEN; // 设置衰减为 ADC_ATTEN_DB_0
    adc_pattern[i].channel = channel[i] & 0x7; // 设置通道为 channel 数组中的通道
    adc_pattern[i].unit = EXAMPLE_ADC_UNIT; // 设置单元为 ADC_UNIT_1
    adc_pattern[i].bit_width = EXAMPLE_ADC_BIT_WIDTH; // 设置位宽为 SOC_ADC_DIGI_MAX_BITWIDTH(12 位)
    
    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_continuous_config () 使这些设置生效

此 API 可能由于 ESP_ERR_INVALID_ARG 等原因返回错误;当它返回 ESP_ERR_INVALID_STATE 时,意味着 ADC 连续转换模式驱动已经启动

引脚配置(ESP32-S3)
管脚/信号 通道 ADC 选择
GPIO1 0 SAR ADC1 GPIO11 0 SAR ADC2
GPIO2 1 SAR ADC1 GPIO12 1 SAR ADC2
GPIO3 2 SAR ADC1 GPIO13 2 SAR ADC2
GPIO4 3 SAR ADC1 GPIO14 3 SAR ADC2
GPIO5 4 SAR ADC1 GPIO15 4 SAR ADC2
GPIO6 5 SAR ADC1 GPIO16 5 SAR ADC2
GPIO7 6 SAR ADC1 GPIO17 6 SAR ADC2
GPIO8 7 SAR ADC1 GPIO18 7 SAR ADC2
GPIO9 8 SAR ADC1 GPIO19 8 SAR ADC2
GPIO10 9 SAR ADC1 GPIO20 9 SAR ADC2

完整函数

c 复制代码
static void continuous_adc_init(adc_channel_t *channel, uint8_t channel_num, adc_continuous_handle_t *out_handle)
{
    adc_continuous_handle_t handle = NULL; // 创建一个指向 ADC 连续模式句柄的指针

    adc_continuous_handle_cfg_t adc_config = {
        .max_store_buf_size = 1024, // 设置最大存储缓冲区大小为 1024 字节
        .conv_frame_size = EXAMPLE_READ_LEN, // 设置转换帧大小为 EXAMPLE_READ_LEN 字节
    };
    ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &handle)); // 创建新的 ADC 连续模式句柄

    adc_continuous_config_t dig_cfg = {
        .sample_freq_hz = 20 * 1000, // 设置采样频率为 20 kHz
        .conv_mode = EXAMPLE_ADC_CONV_MODE, // 设置转换模式为单通道模式
        .format = EXAMPLE_ADC_OUTPUT_TYPE, // 设置输出格式为 EXAMPLE_ADC_OUTPUT_TYPE2
    };

    adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0}; // 创建一个 ADC 数字模式配置数组
    dig_cfg.pattern_num = channel_num; // 设置模式数量为通道数量
    for (int i = 0; i < channel_num; i++) // 逐个配置通道
    {
        adc_pattern[i].atten = EXAMPLE_ADC_ATTEN; // 设置衰减为 EXAMPLE_ADC_ATTEN
        adc_pattern[i].channel = channel[i] & 0x7; // 设置通道为 channel 数组中的通道
        adc_pattern[i].unit = EXAMPLE_ADC_UNIT; // 设置单元为 EXAMPLE_ADC_UNIT
        adc_pattern[i].bit_width = EXAMPLE_ADC_BIT_WIDTH; // 设置位宽为 EXAMPLE_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);
    }
    dig_cfg.adc_pattern = adc_pattern; // 设置 ADC 模式为 adc_pattern 数组
    ESP_ERROR_CHECK(adc_continuous_config(handle, &dig_cfg)); // 配置 ADC 连续模式

    *out_handle = handle;
}
启动 ADC
c 复制代码
ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(handle, &cbs, NULL)); // 注册事件回调函数
ESP_ERROR_CHECK(adc_continuous_start(handle)); // 启动 ADC 连续模式
读取数据

调用 adc_continuous_start() 启动 ADC 连续转换,调用 adc_continuous_read() 可以获取 ADC 通道的转换结果,提供缓冲区,获取原始结果;此 API 提供了一个读取所有 ADC 连续转换结果的机会

调用 adc_continuous_read() 可以请求读取指定长度的转换结果,函数 adc_continuous_read() 每次都会尝试以期望长度读取转换结果,但有时实际可用的转换结果可能少于请求长度,此时,函数仍会将数据从内部池移动到你提供的缓冲区中,查看 out_length 的值,了解实际移动到缓冲区中的转换结果数量

如果内部池中没有生成转换结果,函数将会阻塞一段时间,即 timeout_ms,直到转换结果生成;如果始终没有转换结果生成,函数将返回 ESP_ERR_TIMEOUT

如果 ADC 连续转换生成的结果填满了内部池,新产生的结果将丢失,下次调用 adc_continuous_read() 时,将返回 ESP_ERR_INVALID_STATE,提示此情况发生。

从上述函数读取的 ADC 转换结果为原始数据,使用以下公式根据 ADC 原始结果计算电压,:

V o u t = D o u t ⋅ V m a x D m a x V_{out} = D_{out} \cdot \frac{V_{max}}{D_{max}} Vout=Dout⋅DmaxVmax

Vout 数据输出结果,代表电压。
Dout ADC 原始数据读取结果。
Vmax 可测量的最大模拟输入电压,与 ADC 衰减相关[[#信号衰减]]
Dmax 输出 ADC 原始数据读取结果的最大值,即 2^位宽,位宽即之前配置的 bit_width

完整函数

c 复制代码
void app_main(void)
{
    esp_err_t ret; // 创建一个变量来存储函数返回值
    uint32_t ret_num = 0; // 创建一个变量来存储读取的字节数
    uint8_t result[EXAMPLE_READ_LEN] = {0}; // 创建一个缓冲区来存储读取的数据
    memset(result, 0xcc, EXAMPLE_READ_LEN); // 初始化缓冲区为 0xcc

    s_task_handle = xTaskGetCurrentTaskHandle(); // 获取当前任务句柄

    adc_continuous_handle_t handle = NULL; // 创建一个指向 ADC 连续模式句柄的指针
    continuous_adc_init(channel, sizeof(channel) / sizeof(adc_channel_t), &handle); // 初始化 ADC 连续模式

    adc_continuous_evt_cbs_t cbs = {
        .on_conv_done = s_conv_done_cb, // 注册转换完成的回调函数
    };
    ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(handle, &cbs, NULL)); // 注册事件回调函数
    ESP_ERROR_CHECK(adc_continuous_start(handle)); // 启动 ADC 连续模式

    while (1)
    {
        /**
         * This is to show you the way to use the ADC continuous mode driver event callback.
         * This `ulTaskNotifyTake` will block when the data processing in the task is fast.
         * However in this example, the data processing (print) is slow, so you barely block here.
         *
         * Without using this event callback (to notify this task), you can still just call
         * `adc_continuous_read()` here in a loop, with/without a certain block timeout.
         */
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 等待通知,直到 ADC 连续模式驱动完成转换

        char unit[] = EXAMPLE_ADC_UNIT_STR(EXAMPLE_ADC_UNIT); // 将"ADC_UNIT_1"转换为字符串

        while (1)
        {
            ret = adc_continuous_read(handle, result, EXAMPLE_READ_LEN, &ret_num, 0); // 读取 ADC 连续模式的数据
            if (ret == ESP_OK) // 检查读取是否成功
            {
                ESP_LOGI("TASK", "ret is %x, ret_num is %" PRIu32 " bytes", ret, ret_num);
                for (int i = 0; i < ret_num; i += SOC_ADC_DIGI_RESULT_BYTES)
                {
                    adc_digi_output_data_t *p = (adc_digi_output_data_t *)&result[i]; // 将读取的数据转换为 adc_digi_output_data_t 结构体指针
                    uint32_t chan_num = EXAMPLE_ADC_GET_CHANNEL(p); // 获取通道号
                    uint32_t data = EXAMPLE_ADC_GET_DATA(p); // 获取数据值
                    /* Check the channel number validation, the data is invalid if the channel num exceed the maximum channel */
                    if (chan_num < SOC_ADC_CHANNEL_NUM(EXAMPLE_ADC_UNIT)) // 检查通道号是否有效
                    {
                        ESP_LOGI(TAG, "Unit: %s, Channel: %" PRIu32 ", Value: %" PRIx32, unit, chan_num, data); // 打印通道号和数据值
                    }
                    else
                    {
                        ESP_LOGW(TAG, "Invalid data [%s_%" PRIu32 "_%" PRIx32 "]", unit, chan_num, data); // 打印无效数据的警告
                    }
                }
                /**
                 * Because printing is slow, so every time you call `ulTaskNotifyTake`, it will immediately return.
                 * To avoid a task watchdog timeout, add a delay here. When you replace the way you process the data,
                 * usually you don't need this delay (as this task will block for a while).
                 */
                vTaskDelay(1); // 添加延迟以避免任务看门狗超时
            }
            else if (ret == ESP_ERR_TIMEOUT)
            {
                // We try to read `EXAMPLE_READ_LEN` until API returns timeout, which means there's no available data
                break;
            }
        }
    }

    ESP_ERROR_CHECK(adc_continuous_stop(handle));
    ESP_ERROR_CHECK(adc_continuous_deinit(handle));
}

示例:读取 HW-504 摇杆数据

硬件设计原理图,可以通过面包板+杜邦线进行实验

摇杆数据读取

完整代码

c 复制代码
/*
 * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <string.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_adc/adc_continuous.h"

#define EXAMPLE_ADC_UNIT ADC_UNIT_1
#define _EXAMPLE_ADC_UNIT_STR(unit) #unit
#define EXAMPLE_ADC_UNIT_STR(unit) _EXAMPLE_ADC_UNIT_STR(unit)
#define EXAMPLE_ADC_CONV_MODE ADC_CONV_SINGLE_UNIT_1
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_12
#define EXAMPLE_ADC_BIT_WIDTH SOC_ADC_DIGI_MAX_BITWIDTH

#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define EXAMPLE_ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE1
#define EXAMPLE_ADC_GET_CHANNEL(p_data) ((p_data)->type1.channel)
#define EXAMPLE_ADC_GET_DATA(p_data) ((p_data)->type1.data)
#else
#define EXAMPLE_ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE2
#define EXAMPLE_ADC_GET_CHANNEL(p_data) ((p_data)->type2.channel)
#define EXAMPLE_ADC_GET_DATA(p_data) ((p_data)->type2.data)
#endif

#define EXAMPLE_READ_LEN 256

#if CONFIG_IDF_TARGET_ESP32
static adc_channel_t channel[2] = {ADC_CHANNEL_6, ADC_CHANNEL_7};
#else
static adc_channel_t channel[2] = {ADC_CHANNEL_2, ADC_CHANNEL_3};
#endif

static TaskHandle_t s_task_handle;
static const char *TAG = "EXAMPLE";
static uint32_t adc_read_cnt = 0; // 创建一个变量来存储 ADC 读取计数

// 将 ADC 连续模式转换完成的回调函数声明为静态函数
// 使用 IRAM_ATTR 修饰符,确保函数被放置在 IRAM 中以提高执行效率
static bool IRAM_ATTR s_conv_done_cb(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data)
{
    BaseType_t mustYield = pdFALSE; // 创建一个变量来存储任务是否需要切换的标志
    // Notify that ADC continuous driver has done enough number of conversions
    vTaskNotifyGiveFromISR(s_task_handle, &mustYield); // 通知任务, ADC 连续模式驱动已经完成足够数量的转换

    return (mustYield == pdTRUE);
}

static void continuous_adc_init(adc_channel_t *channel, uint8_t channel_num, adc_continuous_handle_t *out_handle)
{
    adc_continuous_handle_t handle = NULL; // 创建一个指向 ADC 连续模式句柄的指针

    adc_continuous_handle_cfg_t adc_config = {
        .max_store_buf_size = 1024,          // 设置最大存储缓冲区大小为 1024 字节
        .conv_frame_size = EXAMPLE_READ_LEN, // 设置转换帧大小为 EXAMPLE_READ_LEN 字节
    };
    ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &handle)); // 创建新的 ADC 连续模式句柄

    adc_continuous_config_t dig_cfg = {
        .sample_freq_hz = 20 * 1000,        // 设置采样频率为 20 kHz
        .conv_mode = EXAMPLE_ADC_CONV_MODE, // 设置转换模式为单通道模式
        .format = EXAMPLE_ADC_OUTPUT_TYPE,  // 设置输出格式为 EXAMPLE_ADC_OUTPUT_TYPE2
    };

    adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0}; // 创建一个 ADC 数字模式配置数组
    dig_cfg.pattern_num = channel_num;                                 // 设置模式数量为通道数量
    for (int i = 0; i < channel_num; i++)                              // 逐个配置通道
    {
        adc_pattern[i].atten = EXAMPLE_ADC_ATTEN;         // 设置衰减为 ADC_ATTEN_DB_0
        adc_pattern[i].channel = channel[i] & 0x7;        // 设置通道为 channel 数组中的通道
        adc_pattern[i].unit = EXAMPLE_ADC_UNIT;           // 设置单元为 EXAMPLE_ADC_UNIT
        adc_pattern[i].bit_width = EXAMPLE_ADC_BIT_WIDTH; // 设置位宽为 SOC_ADC_DIGI_MAX_BITWIDTH(12 位)

        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);
    }
    dig_cfg.adc_pattern = adc_pattern;                        // 设置 ADC 模式为 adc_pattern 数组
    ESP_ERROR_CHECK(adc_continuous_config(handle, &dig_cfg)); // 配置 ADC 连续模式

    *out_handle = handle;
}

void app_main(void)
{
    esp_err_t ret;                          // 创建一个变量来存储函数返回值
    uint32_t ret_num = 0;                   // 创建一个变量来存储读取的字节数
    uint8_t result[EXAMPLE_READ_LEN] = {0}; // 创建一个缓冲区来存储读取的数据
    memset(result, 0xcc, EXAMPLE_READ_LEN); // 初始化缓冲区为 0xcc

    s_task_handle = xTaskGetCurrentTaskHandle(); // 获取当前任务句柄

    adc_continuous_handle_t handle = NULL;                                          // 创建一个指向 ADC 连续模式句柄的指针
    continuous_adc_init(channel, sizeof(channel) / sizeof(adc_channel_t), &handle); // 初始化 ADC 连续模式

    adc_continuous_evt_cbs_t cbs = {
        .on_conv_done = s_conv_done_cb, // 注册转换完成的回调函数
    };
    ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(handle, &cbs, NULL)); // 注册事件回调函数
    ESP_ERROR_CHECK(adc_continuous_start(handle));                                // 启动 ADC 连续模式

    while (1)
    {
        /**
         * This is to show you the way to use the ADC continuous mode driver event callback.
         * This `ulTaskNotifyTake` will block when the data processing in the task is fast.
         * However in this example, the data processing (print) is slow, so you barely block here.
         *
         * Without using this event callback (to notify this task), you can still just call
         * `adc_continuous_read()` here in a loop, with/without a certain block timeout.
         */
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 等待通知,直到 ADC 连续模式驱动完成转换

        char unit[] = EXAMPLE_ADC_UNIT_STR(EXAMPLE_ADC_UNIT); // 将"ADC_UNIT_1"转换为字符串

        while (1)
        {
            ret = adc_continuous_read(handle, result, EXAMPLE_READ_LEN, &ret_num, 0); // 读取 ADC 连续模式的数据
            if (ret == ESP_OK)                                                        // 检查读取是否成功
            {
                ESP_LOGI("TASK", "ret is %x, ret_num is %" PRIu32 " bytes", ret, ret_num);
                for (int i = 0; i < ret_num; i += SOC_ADC_DIGI_RESULT_BYTES)
                {
                    adc_digi_output_data_t *p = (adc_digi_output_data_t *)&result[i]; // 将读取的数据转换为 adc_digi_output_data_t 结构体指针
                    uint32_t chan_num = EXAMPLE_ADC_GET_CHANNEL(p);                   // 获取通道号
                    uint32_t data = EXAMPLE_ADC_GET_DATA(p);                          // 获取数据值
                    /* Check the channel number validation, the data is invalid if the channel num exceed the maximum channel */
                    if (chan_num < SOC_ADC_CHANNEL_NUM(EXAMPLE_ADC_UNIT)) // 检查通道号是否有效
                    {
                        ESP_LOGI(TAG, "Unit: %s, Channel: %" PRIu32 ", Value: %" PRIx32, unit, chan_num, data);
                        uint32_t voltage = (data * 1100) / 4095;                                                                                    // 1100mV为参考电压,4095为12位ADC的最大值
                        ESP_LOGI(TAG, "Unit: %s, Channel: %" PRIu32 ", Value: %" PRIx32 ", Voltage: %" PRIu32 "mV", unit, chan_num, data, voltage); // 打印通道号、数据值和电压值
                        // adc_read_cnt++; // 增加 ADC 读取计数
                        // if (adc_read_cnt % 1000 == 0)
                        // {
                        //     ESP_LOGI(TAG, "ADC read count: %" PRIu32, adc_read_cnt); // 打印 ADC 读取计数
                        // }
                    }
                    else
                    {
                        ESP_LOGW(TAG, "Invalid data [%s_%" PRIu32 "_%" PRIx32 "]", unit, chan_num, data); // 打印无效数据的警告
                    }
                }
                /**
                 * Because printing is slow, so every time you call `ulTaskNotifyTake`, it will immediately return.
                 * To avoid a task watchdog timeout, add a delay here. When you replace the way you process the data,
                 * usually you don't need this delay (as this task will block for a while).
                 */
                vTaskDelay(1); // 添加延迟以避免任务看门狗超时
            }
            else if (ret == ESP_ERR_TIMEOUT)
            {
                // We try to read `EXAMPLE_READ_LEN` until API returns timeout, which means there's no available data
                break;
            }
        }
    }

    ESP_ERROR_CHECK(adc_continuous_stop(handle));
    ESP_ERROR_CHECK(adc_continuous_deinit(handle));
}
相关推荐
世事如云有卷舒1 分钟前
Linux驱动学习笔记(十)
linux·笔记·学习
moxiaoran57537 分钟前
uni-app学习笔记十五-vue3页面生命周期(二)
笔记·学习·uni-app
快乐肚皮17 分钟前
Redission学习专栏(一):快速入门及核心API实践
学习·实战·api·redission
Felix Du1 小时前
计算机网络 | 1.1 计算机网络概述思维导图
笔记·学习·计算机网络·程序人生·考研
nenchoumi31191 小时前
Mujoco 学习系列(番外一)MJX 部署与配置
人工智能·学习·机器学习·机器人
@PHARAOH1 小时前
WHAT - 学习 WebSocket 实时 Web 开发
websocket·网络协议·学习
之之为知知2 小时前
数学笔记三:特殊矩阵
笔记·学习·线性代数·算法·职场和发展·矩阵·职场发展
霸王蟹2 小时前
React 泛型组件:用TS来打造灵活的组件。
前端·学习·react.js·typescript·前端框架
代码小将2 小时前
java方法重写学习笔记
java·笔记·学习
行走的bug...3 小时前
makefile学习笔记
linux·笔记·学习