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.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));
}
### STM32F1xx HAL MSP ADC Initialization Example Code and Configuration Methods For configuring the Analog-to-Digital Converter (ADC) on STM32F1 series microcontrollers using the Hardware Abstraction Layer (HAL), it is essential to understand how the initialization process integrates with the Microcontroller Support Package (MSP). The file `stm32f1xx_hal_conf.h` contains configurations necessary for initializing peripherals, including ADCs, within user applications[^1]. The following Python-like pseudocode demonstrates a simplified version of what an ADC initialization function might look like when integrated into the HAL framework: ```c void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* Enable clock for ADC */ __HAL_RCC_ADC1_CLK_ENABLE(); /* Configure GPIO pin : PC0 Channel */ __HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); } ``` This code snippet enables the ADC peripheral&#39;s clock through RCC (Reset and Clock Control) settings before setting up the corresponding analog input channel as required by the application. When working specifically with ADC configuration via STM32CubeMX or manually coding based on its guidelines, one should also consider several key points regarding setup options such as resolution selection, sampling time adjustment per channel, continuous conversion mode enabling/disabling among others[^2]. Additionally, advanced features like multi-channel data acquisition can be implemented efficiently utilizing DMA transfers combined with timer triggers for precise timing control during conversions[^3]. --related questions-- 1. How does changing the sample rate affect ADC performance? 2. What are common pitfalls encountered while configuring multiple channels in ADC setups? 3. Can you explain more about integrating external interrupts with ADC operations? 4. In which scenarios would someone prefer using polling over interrupt-driven methods for reading ADC values?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值