记录8:ESP32-C3的ADC使用
0、前期准备
1、参考首篇文章搭建好esp32环境
2、准备好一块esp32开发开发板(本作者使用了esp32c3作为开发平台)
1、知识储备
1.1 概述
ADC:中文名称模数转换器 ,作用:是用于将模拟信号转换为数字信号。常见有积分型、逐次逼近型和并联比较型。ESP32C3用的就是逐次逼近型,因此本文对逐次逼近型进行说明。
逐次逼近型模数转换器(SAR ADC)原理:基于数学上的逐次逼近法的核心思想,通多次比较逐渐接近真实值,因此当SAR ADC的位越多表示比较次数越多,精度越高。
接下来举个例子,更加直观理解SAR ADC的工作原理。
采集前,Sa切换到Vin,Sb断开;采集时,Sa切换到Vin,Sb闭合(给所有电容充电);采样后,Sa切换到VREF,Sb断开。
现假设输入Vin值为1.2V,VREF为 3.3V,并且已经采样完毕,现在进行电压比较。
1、S1~S11开关切换到地,VCOMP=-Uvin(-1.2V)
2、S1切换,根据基尔霍夫电压定律和电容电荷守恒得:
-Uverf+ U(s1)-U(s2~s11) = 0 # 基尔霍夫电压定律
CU(s1)+CU(s2)/2+...+CU(s11)/512=2CUvin # 电容电荷守恒
U(s2)=...=U(s11) # 电容并联
U(s2~s11) = -VCOMP
解得
VCOMP=-Uvin+Uverf/2 代入数值得 VCOMP=0.45,又因为电容正极实际是接地的,因此该点电位为负值,因此比较器输出 0 (最高位)
3、 由于上次比较器输出 0,因此S1切到地,S2切换,同理可得 VCOMP=-Uvin+Uverf/4 代入数值得 VCOMP=−0.375, 此时比较器输出 1
4、 保持S2不变, S3切换, 同理可得 VCOMP=-Uvin+3Uverf/8 带入数值得 VCOMP=0.0375, 此时比较器输出 0
5、 S3切到地, S4切换, 同理可得 VCOMP=-Uvin+5Uverf/16 带入数值得 VCOMP=−0.16875, 此时比较器输出 1
6、 保持S4不变, S5切换, 同理可得 VCOMP=-Uvin+11Uverf/32 带入数值得 VCOMP=−0.065625, 此时比较器输出 1
7、 保持S5不变, S6切换, 同理可得 VCOMP=-Uvin+21Uverf/64 带入数值得 VCOMP=−0.1171875, 此时比较器输出 1
8、 保持S6不变, S7切换, 同理可得 VCOMP=-Uvin+41Uverf/128 带入数值得 VCOMP=−0.14296875, 此时比较器输出 1
9、 保持S7不变, S8切换, 同理可得 VCOMP=-Uvin+81Uverf/256 带入数值得 VCOMP=−0.155859375, 此时比较器输出 1
10、保持S8不变, S9切换, 同理可得 VCOMP=-Uvin+161Uverf/512 带入数值得 VCOMP=−0.162304688, 此时比较器输出 1
11、保持S9不变, S10切换,同理可得 VCOMP=-Uvin+321Uverf/1024 带入数值得 VCOMP=−0.165527344, 此时比较器输出 1
12、保持S10不变,S11切换,同理可得 VCOMP=-Uvin+322Uverf/1024 带入数值得 VCOMP=−0.162304688, 此时比较器输出 1 (最低位)
13、此时寄存器的值为 0x2FF(767) 得出测量的结果为 3.3 * 767/2047 = 1.236492427 约等于 1.2
14、到此完毕
图1(来源百度搜索)
1.2 功能架构
ESP32C3拥有两个ADC单元,支持当次结果转换和连续结果转换。其中SAR ADC1可对五个通道进行电压检测。SAR ADC2可对一个通道进行电压检测,也可对内部电压等信号进行检测。对应的GPIO引脚可看下图2:
图2(来源乐鑫官方手册)
#### 1.3 ADC配置流程介绍
1、初始化配置结构体;
2、配置ADC通道;
3、读取转换结构。
1、初始化配置结构体
adc的结构体配置涉及到了两个结构体,分别为初始化结构体和配置结构体
初始化结构体
// 单次读取初始化结构体说明
typedef struct {
adc_unit_t unit_id; // ADC 编号 取值 :ADC_UNIT_1 和 ADC_UNIT_2
adc_oneshot_clk_src_t clk_src; // 时钟源 取值: ADC_DIGI_CLK_SRC_APB 和 ADC_DIGI_CLK_SRC_DEFAULT(默认配置)
adc_ulp_mode_t ulp_mode; /* 设置是否支持 ADC 在 ULP 模式下工作,取值:ADC_ULP_MODE_DISABLE、
ADC_ULP_MODE_FSM和ADC_ULP_MODE_RISCV */
} adc_oneshot_unit_init_cfg_t;
// 相关函数介绍
// 初始化ADC,并且返回操作句柄
esp_err_t adc_oneshot_new_unit(const adc_oneshot_unit_init_cfg_t *init_config, adc_oneshot_unit_handle_t *ret_unit);
/*
参数:
init_config : 配置结构体
ret_unit :返回的ADC操作句柄
返回值:
ESP_OK(成功),ESP_ERR_INVALID_ARG(无效参数)、ESP_ERR_NO_MEM(内存不足)、ESP_ERR_NOT_FOUND(找不到ADC外设)、ESP_FAIL(时钟未初始化)*/
//连续读取初始化结构体说明
typedef struct {
uint32_t max_store_buf_size; // 存放转换结构的数组最大长度
uint32_t conv_frame_size; // 转换帧大小
} adc_continuous_handle_cfg_t;
// 相关函数介绍
// 创建操作句柄
esp_err_t adc_continuous_new_handle(const adc_continuous_handle_cfg_t *hdl_config, adc_continuous_handle_t *ret_handle);
/*
参数:
hdl_config : 配置结构体
ret_handle :返回的ADC操作句柄
返回值:
ESP_OK(成功),ESP_ERR_INVALID_ARG(无效参数)、ESP_ERR_NO_MEM(内存不足)、ESP_ERR_NOT_FOUND(找不到ADC外设)*/
使用例子:
// 单次转换
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_init_cfg_t adc1_init_config = {
.unit_id = ADC_UNIT_1,
.clk_src = ADC_DIGI_CLK_SRC_DEFAULT,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
adc_oneshot_new_unit(&adc1_init_config, &adc1_handle);
// 连续转换
adc_continuous_handle_t adc1_handle;
adc_continuous_handle_cfg_t adc1_init_config = {
.max_store_buf_size = 1024,
.conv_frame_size = 256,
};
adc_continuous_new_handle(&adc1_init_config, &adc1_handle);
配置结构体
// 单次读取配置结构体说明
typedef struct {
adc_atten_t atten; /* ADC 衰减配置。主要用于转换大于 Vref 的电压,在信号输入 SAR ADC 前进行衰减,取值有: ADC_ATTEN_DB_0(0db)、 ADC_ATTEN_DB_2_5(2.5db)、ADC_ATTEN_DB_6(6db)、ADC_ATTEN_DB_12(12db)*/
adc_bitwidth_t bitwidth; /* ADC 原始转换结果的位宽,ADC_BITWIDTH_DEFAULT(芯片支持最大位宽)、ADC_BITWIDTH_9、ADC_BITWIDTH_10、
ADC_BITWIDTH_11、ADC_BITWIDTH_12和ADC_BITWIDTH_13 */
} adc_oneshot_chan_cfg_t;
// 相关函数介绍
// 配置通道
esp_err_t adc_oneshot_config_channel(adc_oneshot_unit_handle_t handle, adc_channel_t channel,
const adc_oneshot_chan_cfg_t *config);
/*
参数:
handle :ADC操作句柄
channel : ADC 通道
config :配置结构体
返回值:ESP_OK(成功),ESP_ERR_INVALID_ARG(无效参数)*/
//连续读取配置结构体说明
typedef struct {
uint32_t pattern_num; // ADC 通道数
adc_digi_pattern_config_t *adc_pattern; // ADC 通道配置数组
uint32_t sample_freq_hz; // 采样频率
adc_digi_convert_mode_t conv_mode; /* 连续转换模式 取值有:ADC_CONV_SINGLE_UNIT_1(只使用ADC1),
ADC_CONV_SINGLE_UNIT_2(只使用ADC2)、ADC_CONV_BOTH_UNIT(ADC1、ADC2同时使用)
和 ADC_CONV_ALTER_UNIT(ADC1和ADC2交替使用)*/
adc_digi_output_format_t format; /* 转换结果的输出格式,取值有:ADC_DIGI_OUTPUT_FORMAT_TYPE1 和
ADC_DIGI_OUTPUT_FORMAT_TYPE2 ,具体格式见adc_digi_output_data_t 结构体*/
} adc_continuous_config_t;
typedef struct {
uint8_t atten; // ADC采样通道的衰减配置
uint8_t channel; // ADC采用通道
uint8_t unit; // ADC的编号
uint8_t bit_width; // 转换的位宽
} adc_digi_pattern_config_t;
// ESP32C3 ADC数据格式结构体
//(注意:每一芯片的都不一样,具体可查看 components/hal/include/hal/adc_types.h)
typedef struct {
union {
struct {
uint32_t data: 12; // ADC 读取的数据
uint32_t reserved12: 1; // 保留位
uint32_t channel: 3; /* ADC通道编号, channel < ADC_CHANNEL_MAX 数据有效
channel > ADC_CHANNEL_MAX 数据无效 */
uint32_t unit: 1; // ADC编号 0: ADC1; 1: ADC2.
uint32_t reserved17_31: 15; // 保留位
} type2;
uint32_t val;
};
} adc_digi_output_data_t;
// 相关函数介绍
// 配置 ADC
esp_err_t adc_continuous_config(adc_continuous_handle_t handle, const adc_continuous_config_t *config);
/*
参数:
handle :ADC操作句柄
config :配置结构体
返回值:ESP_OK(成功),ESP_ERR_INVALID_ARG(无效参数)、ESP_ERR_INVALID_STATE(无效状态)*/
使用例子
// 单次转换
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = ADC_ATTEN_DB_0,
};
adc_oneshot_config_channel(adc1_handle, ADC_CHANNEL_0, &config);
// 连续转换
adc_continuous_handle_t adc1_handle;
adc_digi_pattern_config_t adc_pattern;
memset(&adc_pattern,0,sizeof(adc_digi_pattern_config_t));
dig_cfg.pattern_num = 1;
adc_pattern.atten = ADC_ATTEN_DB_0;
adc_pattern.channel = ADC_CHANNEL_0 & 0x7;
adc_pattern.unit = ADC_UNIT_1;
adc_pattern.bit_width = SOC_ADC_DIGI_MAX_BITWIDTH;
dig_cfg.adc_pattern = &adc_pattern;
adc_continuous_config(handle, &dig_cfg);
2、ADC 校准(看情况使用)
esp-idf 根据不同的芯片会提供不同的 ADC 校准方式,对于驱动程序来说,每个 ADC 校准方案对应一个 ADC 校准句柄 adc_cali_handle_t,可通过 adc_cali_check_scheme函数 查看芯片支持的校准方式。其中目前作者使用的ESP32C3 支持 ADC_CALI_SCHEME_VER_CURVE_FITTING (曲线拟合)进行校准
// 相关结构体介绍
typedef struct adc_cali_scheme_t *adc_cali_handle_t;
struct adc_cali_scheme_t {
esp_err_t (*raw_to_voltage)(void *arg, int raw, int *voltage);
/*
参数:
arg : ADC 校准的上下文
raw : ADC 原始数据
voltage : 校准后电压
返回值: ESP_OK(成功)、ESP_ERR_INVALID_ARG(无效参数)、ESP_ERR_INVALID_STATE(无效状态)
*/
void *ctx;
};
// 曲线拟合
typedef struct {
adc_unit_t unit_id; // ADC 编号
adc_channel_t chan; // ADC 通道, for chips with SOC_ADC_CALIB_CHAN_COMPENS_SUPPORTED, calibration can be per channel
adc_atten_t atten; // ADC 衰减
adc_bitwidth_t bitwidth; // ADC 标准输出的位宽
} adc_cali_curve_fitting_config_t;
// 相关函数
// 创建曲线拟合校准
esp_err_t adc_cali_create_scheme_curve_fitting(const adc_cali_curve_fitting_config_t *config, adc_cali_handle_t *ret_handle);
/*
参数:
config : 配置结构体
ret_handle :返回的操作句柄
返回值:
ESP_OK(成功),ESP_ERR_INVALID_ARG(无效参数)、ESP_ERR_NO_MEM(内存不足)、ESP_ERR_NOT_SUPPORTED(校准方案所需的 eFuse 位不正确)
(注意: ESP_ERR_NOT_SUPPORTED 该错误,ESP-IDF 提供的 ADC 校准方案基于芯片上某些与 ADC 校准相关的 eFuse 位的值,乐鑫模组已在出厂时完成烧录,无需用户额外烧录)
*/
// 删除曲线拟合校准
esp_err_t adc_cali_delete_scheme_curve_fitting(adc_cali_handle_t handle);
/*
参数:
handle :操作句柄
返回值:
ESP_OK(成功)、ESP_ERR_INVALID_ARG(无效参数)
*/
// ADC 校准输出电压值
esp_err_t adc_cali_raw_to_voltage(adc_cali_handle_t handle, int raw, int *voltage);
/*
参数:
handle :操作句柄
raw :ADC的值
voltage :转换后的电压值
返回值:
ESP_OK(成功)、ESP_ERR_INVALID_ARG(无效参数)、ESP_ERR_INVALID_STATE(无效状态)
*/
// 还有一种线性拟合 ADC_CALI_SCHEME_VER_LINE_FITTING(ESP32 和 ESP32S2 支持) 在这就不介绍了,有兴趣的同学可以给我留意,如果人多了,可以考虑单独
// 出一篇文章来介绍
使用例子
adc_cali_handle_t handle = NULL;
esp_err_t ret = ESP_FAIL;
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.chan = channel,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
3、数据读取
相关函数介绍
// 单次读取
// 读取函数
esp_err_t adc_oneshot_read(adc_oneshot_unit_handle_t handle, adc_channel_t chan, int *out_raw);
/*
handle :ADC 操作句柄
chan :ADC 通道
out_raw :读取到的结果
返回值:ESP_OK(成功),ESP_ERR_INVALID_ARG(无效参数)、ESP_ERR_TIMEOUT(超时)*/
// 连续读取
// 读取函数
esp_err_t adc_continuous_read(adc_continuous_handle_t handle, uint8_t *buf, uint32_t length_max, uint32_t *out_length,
uint32_t timeout_ms);
/*
handle :ADC 操作句柄
buf :读取结构的存放数组
length_max :ADC读取的转换结果的最大长度
out_length :ADC读取的转换结果的实际长度
timeout_ms :超时时间
返回值:ESP_OK(成功),ESP_ERR_INVALID_ARG(无效参数)、ESP_ERR_TIMEOUT(超时)*/
2、新建工程
idf.py create-project project_adc # 新建工程
cd project_uart
idf.py set-target esp32c3 # 设置工程使用的芯片
3、查看原理图确定ADC引脚
4、编写程序
单次读取参考代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_caps.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#define TAG "test_adc_oneshot"
static int adc_raw[10];
static int voltage[10];
void app_main(void)
{
// 创建句柄
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1,
};
adc_oneshot_new_unit(&init_config1, &adc1_handle);
// adc配置
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = ADC_ATTEN_DB_0,
};
adc_oneshot_config_channel(adc1_handle, ADC_CHANNEL_0, &config);
// 校准配置
adc_cali_handle_t adc1_cali_chan0_handle = NULL;
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.chan = ADC_CHANNEL_0,
.atten = ADC_ATTEN_DB_0,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_chan0_handle);
while (1) {
// 读取数据
adc_oneshot_read(adc1_handle, ADC_CHANNEL_0, &adc_raw);
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1, ADC_CHANNEL_0, adc_raw);
adc_cali_raw_to_voltage(adc1_cali_chan0_handle, adc_raw, &voltage);
ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", ADC_UNIT_1, ADC_CHANNEL_0, voltage);
vTaskDelay(pdMS_TO_TICKS(1000));
}
adc_oneshot_del_unit(adc1_handle);
adc_cali_delete_scheme_curve_fitting(adc1_cali_chan0_handle);
}
5、编译下载
# 编译
idf.py build
# 烧录
idf.py -p /dev/ttyUSB0 flash monitor
作者:MagicKingC