摘要
本博客介绍了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)和一个比较器,其工作过程如下:
- 采样保持:输入的模拟信号通过采样保持电路保持稳定
- 逐次逼近 :
- 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
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 衰减[[#信号衰减]]
channel
IO 对应的 ADC 通道号,请参阅下文注意事项
unit
IO 所属的 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));
}