记录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
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我来详细讲解一下ESP32-C3、 esp-idf 4.3.5和xTaskCreate函数。 ESP32-C3是一款由乐鑫公司开发的低功耗、高性能的Wi-Fi和蓝牙SoC。它采用了RISC-V架构的CPU,拥有单核处理器、最高主频为160MHz,支持Wi-Fi 4和蓝牙5.0协议。ESP32-C3还集成了丰富的外设模块,如UART、SPI、I2C、PWM、ADC、DAC、定时器等,方便开发者进行各种应用的开发。 esp-idf是ESP32系列的官方开发框架,它基于FreeRTOS实时操作系统,提供了许多API和工具,方便开发者进行开发调试。esp-idf包括三个主要组件:ESP32硬件抽象层、FreeRTOS和ESP-IDF库。开发者可以使用ESP-IDF库中的函数,方便快捷地进行各种设备驱动和网络通信的开发。 xTaskCreate是esp-idf提供的一个函数,用于创建一个新的FreeRTOS任务。任务是ESP32中的基本执行单元,每个任务都有自己的栈空间和优先级,可以独立运行。调用xTaskCreate函数时,需要指定任务函数、任务名称、任务堆栈大小、任务参数等参数。例如: ```c void task_function(void *pvParameters) { // 任务代码 } void app_main() { // 创建一个名为task_name的任务,栈大小为2048字节,参数为NULL,优先级为5 xTaskCreate(task_function, "task_name", 2048, NULL, 5, NULL); } ``` 在任务函数中,可以使用vTaskDelete函数删除任务。例如: ```c void task_function(void *pvParameters) { // 任务代码 vTaskDelete(NULL); // 删除任务 } ``` 希望这些解释能够对您有所帮助。如果您还有其他问题,请随时问我。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MagicKingC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值