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

news2025/5/30 15:29:07

摘要

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

文章目录

    • 摘要
    • ADC
      • 采样方法
        • 工作原理
        • 参考电压
        • 采样示例
        • 信号衰减
      • 连续采样模式 (DMA)
        • 初始化
          • 资源分配
          • 配置 ADC
            • 引脚配置(ESP32-S3)
          • 启动 ADC
        • 读取数据
      • 示例:读取 HW-504 摇杆数据

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.1V0.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 V0.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 V0.55 V

信号衰减

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

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

/**
 * @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 缓冲池满时自动清空缓冲池

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 转换模式结果的输出格式

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原始转换结果的位宽

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 选择
GPIO10SAR ADC1GPIO110SAR ADC2
GPIO21SAR ADC1GPIO121SAR ADC2
GPIO32SAR ADC1GPIO132SAR ADC2
GPIO43SAR ADC1GPIO143SAR ADC2
GPIO54SAR ADC1GPIO154SAR ADC2
GPIO65SAR ADC1GPIO165SAR ADC2
GPIO76SAR ADC1GPIO176SAR ADC2
GPIO87SAR ADC1GPIO187SAR ADC2
GPIO98SAR ADC1GPIO198SAR ADC2
GPIO109SAR ADC1GPIO209SAR ADC2

完整函数

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
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=DoutDmaxVmax

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

完整函数

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 摇杆数据

在这里插入图片描述

硬件设计原理图,可以通过面包板+杜邦线进行实验
在这里插入图片描述

摇杆数据读取
在这里插入图片描述
完整代码

/*
 * 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));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2386721.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT学习一

对于选择qmake还是cmake&#xff0c;现在写的暂时先用qmake 1.命名规范和快捷键 2.按钮控件常用API //创建第一个按钮QPushButton * btn new QPushButton;//让btn对象 依赖在mywidget窗口中btn->setParent(this);//显示文本btn->setText("第一个按钮");//创建…

黑马点评Reids重点详解(Reids使用重点)

目录 一、短信登录&#xff08;redisseesion&#xff09; 基于Session实现登录流程 &#x1f504; 图中关键模块解释&#xff1a; 利用seesion登录的问题 设计key的具体细节 整体访问流程 二、商户查询缓存 reids与数据库主动更新的三种方案 缓存穿透 缓存雪崩问题及…

小米2025年校招笔试真题手撕(一)

一、题目 小A每天都要吃a,b两种面包各一个。而他有n个不同的面包机&#xff0c;不同面包机制作面包的时间各不相同。第i台面包机制作a面包 需要花费ai的时间&#xff0c;制作b面包则需要花费bi的时间。 为能尽快吃到这两种面包&#xff0c;小A可以选择两个不同的面包机x&…

《软件工程》第 11 章 - 结构化软件开发

结构化软件开发是一种传统且经典的软件开发方法&#xff0c;它强调将软件系统分解为多个独立的模块&#xff0c;通过数据流和控制流来描述系统的行为。本章将结合 Java 代码示例、可视化图表&#xff0c;深入讲解面向数据流的分析与设计方法以及实时系统设计的相关内容。 11.1 …

Neo4j(三) - 使用Java操作Neo4j详解

文章目录 前言一、创建项目二、导入依赖三、节点和关系数据打印四、创建节点与关系五、查询数据方法六、更新数据方法七、删除节点与关系方法八、合并数据方法九、完整代码1. 完整代码2. 项目下载 前言 本文介绍通过 Java 操作 Neo4j 图数据库的完整流程。主要涵盖开发环境搭建…

蓝桥杯3503 更小的数

问题描述 小蓝有一个长度均为 n 且仅由数字字符 0∼9 组成的字符串&#xff0c;下标从 0 到 n−1&#xff0c;你可以将其视作是一个具有 n 位的十进制数字 num&#xff0c;小蓝可以从 num 中选出一段连续的子串并将子串进行反转&#xff0c;最多反转一次。 小蓝想要将选出的子…

算法-全排列

1、全排列函数的使用 举例&#xff1a;{1,2,3}的全排列 #include<iostream> #include<bits/stdc.h> using namespace std; typedef long long ll; int main(){ll a[3] {1, 2, 3};do{for (ll i 0; i < 3;i){cout << a[i] << " ";}cout…

最好用的wordpress外贸主题

产品展示独立站wordpress主题 橙色的首页大banner外贸英文wordpress主题&#xff0c;适合用于产品展示型的外贸网站。 https://www.jianzhanpress.com/?p8556 Machine机器wordpress模板 宽屏简洁实用的wordpress外贸建站模板&#xff0c;适合工业机器生产、加工、制造的外贸…

2025 河北ICPC( D. 金泰园(二分)-- C.年少的誓约(公式转化))

文章目录 2025 河北ICPCD. 金泰园&#xff08;二分&#xff09;C.年少的誓约(公式转化)总结 2025 河北ICPC 题目链接&#xff1a; Attachments - The 9th Hebei Collegiate Programming Contest - Codeforces sdccpc20250522 - Virtual Judge 赛时&#xff1a;5道 D. 金泰…

mongodb语法$vlookup性能分析

1 场景描述 mongodb有两个表department和user表&#xff0c; department表有_id,name,level&#xff0c;表有记录169w条 user表有_id,name,department_id&#xff0c;表有记录169w条&#xff0c;department_id没有创建索引&#xff0c;department_id是department的_id。 现…

晶圆隐裂检测提高半导体行业效率

半导体行业是现代制造业的核心基石&#xff0c;被誉为“工业的粮食”&#xff0c;而晶圆是半导体制造的核心基板&#xff0c;其质量直接决定芯片的性能、良率和可靠性。晶圆隐裂检测是保障半导体良率和可靠性的关键环节。 晶圆检测 通过合理搭配工业相机与光学系统&#xff0c…

在 LangChain 中集成 Mem0 记忆系统教程

目录 简介环境准备基础配置核心组件说明1. 提示模板设计2. 上下文检索3. 响应生成4. 记忆存储 工作流程解析使用示例关键特性完整代码与效果 简介 Mem0 是一个强大的记忆系统&#xff0c;可以帮助 AI 应用存储和检索历史对话信息。本教程将介绍如何在 LangChain 应用中集成 Me…

华润电力招聘认知能力测评及性格测评真题题库考什么?

华润电力招聘测评包含逻辑推理、数字推理、语言理解三大类型的问卷。共计58题。测评限时60分钟。其中逻辑推理、数字推理、语言推理分别限时20分钟&#xff0c;如逾时未完成相关测试&#xff0c;测试将自动终止&#xff0c;请注意测评时间。为了确保测评的连贯性&#xff0c;建…

Maven Profile在插件与依赖中的深度集成

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

手机平板等设备租赁行业MDM方案解析

目录 引言&#xff1a;MDM 在租赁行业的重要性日益凸显 用户场景&#xff1a;租赁公司面临的主要挑战 1. 设备丢失、逾期未还 2. 手动配置和恢复效率低 3. 非授权使用频繁 4. 时区设置混乱影响运维 5. 缺乏实时监管能力 EasyControl MDM&#xff1a;租赁设备的远程管控…

如何调试CATIA CAA程序导致的CATIA异常崩溃问题

问题背景&#xff1a;我采用CATIA CAA编写了一个界面的小程序&#xff0c;功能运行成功&#xff0c;但是每次运行完&#xff0c;关闭CATIA的时候&#xff0c;都会弹出这个对话框&#xff0c;这个对话框的意思是CATIA运行崩溃&#xff0c;点击确定后&#xff0c;CATIA就会意外关…

SQL查询效率以及索引设计

1. SQL 查询效率与数据库缓冲池机制 1.1. 数据库缓冲池&#xff08;Buffer Pool&#xff09; 磁盘 I/O 需要消耗的时间很多&#xff0c;而在内存中进行操作&#xff0c;效率则会高很多&#xff0c;为了能让数据表或者索引中的数据随时被我们所用&#xff0c;DBMS 会申请占用内…

day37打卡

知识点回顾&#xff1a;浙大疏锦行 过拟合的判断&#xff1a;测试集和训练集同步打印指标模型的保存和加载 仅保存权重保存权重和模型保存全部信息checkpoint&#xff0c;还包含训练状态 早停策略 作业&#xff1a;对信贷数据集训练后保存权重&#xff0c;加载权重后继续训练50…

分布式缓存:证明分布式系统的 CAP 理论

文章目录 Pre一、分布式系统背景与特点二、CAP 三要素详解三、CAP 定理的反证证明四、CP 架构与 AP 架构对比典型场景 五、CAP 理论在系统设计中的应用六、总结 Pre 分布式缓存&#xff1a;CAP 理论在实践中的误区与思考 分布式缓存&#xff1a;BASE理论实践指南 分布式 - 从…

软件设计师“面向对象设计”真题考点分析——求三连

一、考点分值占比与趋势分析 综合知识历年考察统计 年份考题数分值占比考察重点2018334%继承类型、设计原则2019445.3%多态实现、类关系2020556.7%设计模式应用、接口隔离2021334%消息通信、封装特性2022668%开闭原则、组合模式2023556.7%模板方法、适配器模式2024445.3%单一…