ESP32学习笔记_Peripherals(3)——ADC
摘要
本博客介绍了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)和一个比较器,其工作过程如下:
- 采样保持:输入的模拟信号通过采样保持电路保持稳定
- 逐次逼近:
- SAR 控制一个数字到模拟转换器(DAC),生成一个逼近的模拟电压
- 比较器将输入信号与 DAC 输出的逼近电压进行比较
- 根据比较结果,SAR 调整下一步的逼近值,逐步逼近输入信号
- 输出结果:经过多次比较后,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
- 第一次比较(确定最高位 - 第12位)
- 尝试将第12位设为1,DAC输出 = 0.55V
- 待测电压 = 0.55V
- 待测电压等于DAC输出,第12位确定为1
- 当前数字值:1000 0000 0000 = 2048
- 第二次比较(确定第11位)
- 尝试将第11位设为1,DAC输出 = 0.55V + 0.275V = 0.825V
- 待测电压 = 0.55V
- 待测电压小于DAC输出,第11位确定为0
- 当前数字值:1000 0000 0000 = 2048
- 第三次比较(确定第10位)
- 尝试将第10位设为1,DAC输出 = 0.55V + 0.1375V = 0.6875V
- 待测电压 = 0.55V
- 待测电压小于DAC输出,第10位确定为0
- 当前数字值:1000 0000 0000 = 2048
- 以此类推,剩余的低位也都确定为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
/*** @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 dBADC_ATTEN_DB_6 = 2, ///<The input voltage of ADC will be attenuated extending the range of measurement by about 6 dBADC_ATTEN_DB_12 = 3, ///<The input voltage of ADC will be attenuated extending the range of measurement by about 12 dBADC_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 衰减[[#信号衰减]]
channel
IO 对应的 ADC 通道号,请参阅下文注意事项
unit
IO 所属的 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_0adc_pattern[i].channel = channel[i] & 0x7; // 设置通道为 channel 数组中的通道adc_pattern[i].unit = EXAMPLE_ADC_UNIT; // 设置单元为 ADC_UNIT_1adc_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 |
完整函数
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_ATTENadc_pattern[i].channel = channel[i] & 0x7; // 设置通道为 channel 数组中的通道adc_pattern[i].unit = EXAMPLE_ADC_UNIT; // 设置单元为 EXAMPLE_ADC_UNITadc_pattern[i].bit_width = EXAMPLE_ADC_BIT_WIDTH; // 设置位宽为 EXAMPLE_ADC_BIT_WIDTHESP_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=Dout⋅DmaxVmax
Vout | 数据输出结果,代表电压。 |
---|---|
Dout | ADC 原始数据读取结果。 |
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); // 初始化缓冲区为 0xccs_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 databreak;}}}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};
#endifstatic 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 conversionsvTaskNotifyGiveFromISR(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_0adc_pattern[i].channel = channel[i] & 0x7; // 设置通道为 channel 数组中的通道adc_pattern[i].unit = EXAMPLE_ADC_UNIT; // 设置单元为 EXAMPLE_ADC_UNITadc_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); // 初始化缓冲区为 0xccs_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 databreak;}}}ESP_ERROR_CHECK(adc_continuous_stop(handle));ESP_ERROR_CHECK(adc_continuous_deinit(handle));
}
相关文章:

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

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

黑马点评Reids重点详解(Reids使用重点)
目录 一、短信登录(redisseesion) 基于Session实现登录流程 🔄 图中关键模块解释: 利用seesion登录的问题 设计key的具体细节 整体访问流程 二、商户查询缓存 reids与数据库主动更新的三种方案 缓存穿透 缓存雪崩问题及…...

小米2025年校招笔试真题手撕(一)
一、题目 小A每天都要吃a,b两种面包各一个。而他有n个不同的面包机,不同面包机制作面包的时间各不相同。第i台面包机制作a面包 需要花费ai的时间,制作b面包则需要花费bi的时间。 为能尽快吃到这两种面包,小A可以选择两个不同的面包机x&…...

《软件工程》第 11 章 - 结构化软件开发
结构化软件开发是一种传统且经典的软件开发方法,它强调将软件系统分解为多个独立的模块,通过数据流和控制流来描述系统的行为。本章将结合 Java 代码示例、可视化图表,深入讲解面向数据流的分析与设计方法以及实时系统设计的相关内容。 11.1 …...
MongoDB基础知识(浅显)
一、MongoDB 核心概念 MongoDB 是一个 面向文档的 NoSQL 数据库,与传统的关系型数据库(如 MySQL)相比,最大的区别是它以 文档(Document)为存储单元,而不是表和行。 1. 数据库(Data…...

Neo4j(三) - 使用Java操作Neo4j详解
文章目录 前言一、创建项目二、导入依赖三、节点和关系数据打印四、创建节点与关系五、查询数据方法六、更新数据方法七、删除节点与关系方法八、合并数据方法九、完整代码1. 完整代码2. 项目下载 前言 本文介绍通过 Java 操作 Neo4j 图数据库的完整流程。主要涵盖开发环境搭建…...
MPI实现大数据Ring Broadcast逻辑
文章目录 MPI实现大数据Ring Broadcast逻辑Ring Broadcast基本原理MPI实现代码优化建议性能考虑 MPI实现大数据Ring Broadcast逻辑 Ring Broadcast是一种在并行计算中高效传播大数据的技术,特别适合在MPI环境中使用。下面我将介绍如何用MPI实现这种广播逻辑。 Rin…...

蓝桥杯3503 更小的数
问题描述 小蓝有一个长度均为 n 且仅由数字字符 0∼9 组成的字符串,下标从 0 到 n−1,你可以将其视作是一个具有 n 位的十进制数字 num,小蓝可以从 num 中选出一段连续的子串并将子串进行反转,最多反转一次。 小蓝想要将选出的子…...
高并发下使用防重表做防重案例
工作中遇到的重复数据产生的问题: 之前提供的一个批量复制商品的接口,产生了重复的商品数据。 针对于这个问题我想到了可以加一张防重表,在防重表中增加商品表的name和model字段作为唯一索引。 例如: CREATE TABLE product_uniq…...

算法-全排列
1、全排列函数的使用 举例:{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主题,适合用于产品展示型的外贸网站。 https://www.jianzhanpress.com/?p8556 Machine机器wordpress模板 宽屏简洁实用的wordpress外贸建站模板,适合工业机器生产、加工、制造的外贸…...

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

mongodb语法$vlookup性能分析
1 场景描述 mongodb有两个表department和user表, department表有_id,name,level,表有记录169w条 user表有_id,name,department_id,表有记录169w条,department_id没有创建索引,department_id是department的_id。 现…...

晶圆隐裂检测提高半导体行业效率
半导体行业是现代制造业的核心基石,被誉为“工业的粮食”,而晶圆是半导体制造的核心基板,其质量直接决定芯片的性能、良率和可靠性。晶圆隐裂检测是保障半导体良率和可靠性的关键环节。 晶圆检测 通过合理搭配工业相机与光学系统,…...
临床试验中的独立数据监查委员会
1. IDMC会议概况 1.1 核心职责 1.1.1 安全性监查 IDMC需评估不良事件(AE)和严重不良事件(SAE),确保受试者风险可控。这是其核心职责之一,通过严格的安全性监查,保障受试者的健康和安全,避免因试验带来的不可控风险。 1.1.2 有效性评估 在预先设定的期中分析中,IDMC要…...

在 LangChain 中集成 Mem0 记忆系统教程
目录 简介环境准备基础配置核心组件说明1. 提示模板设计2. 上下文检索3. 响应生成4. 记忆存储 工作流程解析使用示例关键特性完整代码与效果 简介 Mem0 是一个强大的记忆系统,可以帮助 AI 应用存储和检索历史对话信息。本教程将介绍如何在 LangChain 应用中集成 Me…...
PTA练习题
文章目录 L1-101 别再来这么多猫娘了!(字符串查找-替换)L2-049 鱼与熊掌(set/暴力/vector)L2-050 懂蛇语(字符串匹配)L2-051 满树的遍历(前序)L2-001 紧急救援(最短路) L…...

华润电力招聘认知能力测评及性格测评真题题库考什么?
华润电力招聘测评包含逻辑推理、数字推理、语言理解三大类型的问卷。共计58题。测评限时60分钟。其中逻辑推理、数字推理、语言推理分别限时20分钟,如逾时未完成相关测试,测试将自动终止,请注意测评时间。为了确保测评的连贯性,建…...

Maven Profile在插件与依赖中的深度集成
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...

手机平板等设备租赁行业MDM方案解析
目录 引言:MDM 在租赁行业的重要性日益凸显 用户场景:租赁公司面临的主要挑战 1. 设备丢失、逾期未还 2. 手动配置和恢复效率低 3. 非授权使用频繁 4. 时区设置混乱影响运维 5. 缺乏实时监管能力 EasyControl MDM:租赁设备的远程管控…...
【前端】使用HTTPS
在前端本地开发环境中使用 HTTPS 主要取决于你用的是哪个构建工具(如 Vite、Webpack、Vue CLI 等)。 目录 ViteWebpack本地生产环境 npx serve浏览器提示“不安全”解决方法上传github注意不要把key传上去 Vite npm install --save-dev types/node #安…...
Python应用“面向对象”小练习
大家好!面向对象编程是一种以 “对象” 为核心的编程思想。对象可以看作是具有特定属性和行为的实体。例如,一个学生可以是一个对象,他的属性包括姓名和年龄,行为可以是打招呼。 代码呈现: # 定义类和对象 class Student:def __init__(sel…...

如何调试CATIA CAA程序导致的CATIA异常崩溃问题
问题背景:我采用CATIA CAA编写了一个界面的小程序,功能运行成功,但是每次运行完,关闭CATIA的时候,都会弹出这个对话框,这个对话框的意思是CATIA运行崩溃,点击确定后,CATIA就会意外关…...

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

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

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

软件设计师“面向对象设计”真题考点分析——求三连
一、考点分值占比与趋势分析 综合知识历年考察统计 年份考题数分值占比考察重点2018334%继承类型、设计原则2019445.3%多态实现、类关系2020556.7%设计模式应用、接口隔离2021334%消息通信、封装特性2022668%开闭原则、组合模式2023556.7%模板方法、适配器模式2024445.3%单一…...
vue项目webpack、vite、rollup、parcel四种构建工具对比
以下是 Vue 项目中使用 Webpack 与其他主流构建工具(Vite、Rollup、Parcel)的对于项目的使用对比: 一、核心工具对比 特性WebpackViteRollupParcel构建原理Bundle-based(打包)ESM-based(原生模块)Bundle-based(专注库)Zero-config(自动分析)开发速度较慢(全量打包)…...
系统架构中的限流实践:构建多层防护体系(二)
系统架构中的限流实践:构建多层防护体系 一、接入层限流:流量拦截第一关二、应用层限流(服务内限流)Java生态方案对比三、分布式限流(跨服务限流)四、数据层限流(数据库/缓存限流)1. 数据库防护策略2. 缓存优化方案五、中间件层限流(消息队列/分布式服务)六、客户端限…...