OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 48|回复: 0

《ESP32-P4开发指南— V1.0》第二十四章 ADC实验

[复制链接]

1227

主题

1241

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5280
金钱
5280
注册时间
2019-5-8
在线时间
1347 小时
发表于 3 天前 | 显示全部楼层 |阅读模式
第二十四章 ADC实验

1)实验平台:正点原子DNESP32P4开发板

2)章节摘自【正点原子】ESP32-P4开发指南— V1.0

3)购买链接:https://detail.tmall.com/item.htm?id=873309579825

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子DNESP32S3开发板技术交流群:132780729


2.jpg

3.png

本章将学习ESP32-P4的ADC外设对电压进行采集。在本章节,通过ADC外设实现对可调电阻器的电压值进行测量,并把结果显示在LCD模块上。
本章分为如下几个小节:
24.1 ADC介绍
24.2 硬件设计
24.3 程序设计
24.4 下载验证


24.1 ADC介绍

24.1.1 ADC简介
生活中接触到的大多数信息是醉着时间连续变化的物理量,如声音、温度、压力等。表达这些信息的电信号,称为模拟信号(Analog Signal)。为了方便存储、处理,在计算机系统中,都是数字0和1信号,将模拟信号(连续信号)转换为数字信号(离散信号)的器件就叫模数转换器(Analog-to-Digital Convert,ADC)。
ADC转换器可分为:并行比较型A/D转换器(FLASH ADC)、逐次比较型A/D转换器(SAR ADC)和双积分式A/D转换器(Double Integral ADC)。
A/D转换过程通常为4步:采样、保持、量化和编码,如下图所示。

第二十四章 ADC实验448.png
图24.1.1.1 A/D转换过程图

采样:把时间连续变化的信号变换为时间离散的信号。
保持:保持采样信号,使有充分时间转换为数字信号。
量化:把采样保持电路的输出信号用单位量化电压的整数倍表示。
编码:把量化的结果用二进制代码表示。
采样和保持通常是在采样-保持电路中完成,量化和编码通常在ADC数字编码电路中完成。

24.1.2 ESP32-P4 ADC介绍
ESP32-P4集成了2个12位SAR ADC,分别是ADC1和ADC2,支持14个模拟通道输入。
下面我们通过分析ESP32-P4 SAR ADC架构图,了解其工作过程。架构图如下图所示。

第二十四章 ADC实验733.png
图24.1.2.1 ESP32-P4 SAR ADC框架图

ESP32-P4 SAR ADC框架包含:SAR ADC1和2、四个ADC控制器(HP ADC1、HP ADC2、LP ADC1和LP ADC2)、四个ADC控制器的Reader模块(HP Reader1/2、LP Reader1/2)和时钟管理(Clock Management)。注:这里只讲HP部分,不对LP部分讲解。
SAR ADC1和ADC2:SAR ADC1可通过8个通道进行电压检测,而SAR ADC2可通过6个通道进行电压检测,两个ADC共14个通道。这些模拟通道对应着具体的IO,并不是随意的IO都有模拟输入功能,具有模拟输入功能的引脚如下表所示。


第二十四章 ADC实验1050.png
表24.1.2.1 具备模拟输入功能的引脚

四个ADC控制器(HP ADC1、HP ADC2、LP ADC1和LP ADC2):用于管理SAR ADC1和ADC2。
HP ADC1/2控制器特点:① 提供多通道采集控制模块,支持多通道采集模块,可配置通道采样顺序 ② 提供模式控制模块MODE CNTL,支持双HP ADC采样 ③ 提供两个具有可配置过滤系数的过滤器Filter ④ 提供两个阈值监视器Threshold Monitor,当过滤的数据高于高阈值或低于低阈值时,可以触发中断 ⑤ 支持通过GDMA接口将转换结果连续传输到内存。
HP ADC1/2控制器包括:HP ADC FSM1/2、MODE_CNTL、定时器Timer、过滤器Filter0/1、阈值监视器Threshold Monitor和DMA。
HP ADC FSM1/2:① 从定时器接收采样使能信号 ② 确定在HP ADC1/2模式中定义的转换规则 ③ 驱动HP Reader1/2模块读取转换数据 ④ 将转换数据传输到过滤器
MODE_CNTL用于滤波定时器触发的采样信号,支持双HP ADC采样模式。
定时器Timer用于HP ADC1/2控制器产生采样使能信号的专用定时器。
过滤器Filter0和Filter1用于多通道采样模式下的滤波转换结果。
阈值监视器0/1:用于检测数据,当过滤的数据高于高阈值或低于阈值时触发中断。
时钟管理(Clock Management)对时钟源进行选择和分频。ADC控制器的时钟源可选择XTAL_CLK、RC_FAST_CLK和PLL_F80M_CLK。而HP ADC1/2控制器的时钟又可以分为HPADC_SARCLK和HPADC_CLK,前者是提供给HP ADC1/2和HP Reader1/2使用,而后者是提供给HP ADC FSM1/2使用。HPADC_SARCLK的频率会影响采样精度。当HPADC_SARCLK频率高于5MHz时,采样精度会降低。HPADC_SARCLK是从HPADC_CLK中分频出来的。
HP Reader1/2:由HP ADC FSM1/2驱动,从HP ADC1/2读取数据。
ESP32-P4的SAR ADC的分辨率为12位,所以AD转换后的值范围为0~4095,对应的电压范围为0mV ~ Vref。其中,Vref为SAR ADC内部电压。因此转换结果data可以用以下公式转换成模拟电压输出Vdata。


第二十四章 ADC实验2088.png

要转换大于Vref电压,需要对输入信号进行衰减。衰减可配置为0db、2.5db、6db和12db。
接下来,通过一个例子了解一下ADC的工作原理,如下图所示。


第二十四章 ADC实验2171.png
图24.1.2.2 ADC工作原理

通过ADC_TIMER_EN位设置使能定时器,定时器产生sample_start信号。该信号驱动FSM模块开始工作,先启动HP ADC,然后确认当前pr指向的转换规则(通道和衰减),输出与转换规则相对应的en_pin和atten信号到模拟端,最后初始化sar_start信号并开始采样。当FSM接收到从HP Reader发送reader_done信号后,停止采样,将转换结果传输到过滤器中,然后阈值监视器通过GDMA将过滤结果传输到内存中,更新pr并等待下一次采集。

24.2 硬件设计

24.2.1 例程功能
使用ADC外设采集ADC通道3(IO19)上面的电压,在LCD上面显示ADC转化值以及换算成电压后的电压值。使用跳线帽将P4端子中的AIN和RV排针连接,使得IO19连接到电位器上,然后将ADC采集到的数据和转换后的电压值在LCD屏中显示。用户可以通过调节电位器的旋钮改变电压值。

24.2.2 硬件资源
1)LED灯
        LED        0        - IO51
2)RGBLCD / MIPILCD(引脚太多,不罗列出来)
3)ADC
        RV-AIN(IO19)(在P4端口,使用跳线帽将RV和AIN相连)

24.2.3 原理图
ADC器件相关原理图,如下图所示。

第二十四章 ADC实验2723.png
图24.2.3.1 ADC原理图

本实验,我们通过ADC的通道3(IO19)来采集外部电压值,开发板有一个电位器,可调节的电压范围是:0.5~2.5V。我们可以通过跳线帽将IO19与电位器连接,如下图所示:

第二十四章 ADC实验2829.png
图24.2.3.2 IO19(对应AIN排针)与电位器示意图

使用跳线帽将AIN和RV排针连接好后,并下载程序后,就可以用螺丝刀调节电位器变换多种电压值进行测量。
有的朋友可能还想测量其它地方的电压值,我们只需要1根杜邦线,一端接到AIN排针上,另外一端就接你要测试的电压点。一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。

24.3 程序设计

24.3.1 ADC的IDF驱动
ADC外设驱动位于ESP-IDF下的components/esp_adc目录下。使用ADC功能,必须先导入以下头文件:
  1. <font size="3">#include "esp_adc/adc_oneshot.h"</font>
  2. <font size="3">#include "esp_adc/adc_cali.h"</font>
  3. <font size="3">#include "esp_adc/adc_cali_scheme.h"</font>
复制代码
本例程主要实现的是ADC的单次转换,当然还有ADC连续多次转换,这两者用到的函数是不一样的。
接下来,作者将介绍一些常用的函数,这些函数的描述及其作用如下:
1,创建ADC单元函数adc_oneshot_new_unit
该函数用于创建一个特定ADC单元句柄,其函数原型如下:
  1. esp_err_t adc_oneshot_new_unit(const adc_oneshot_unit_init_cfg_t *init_config, adc_oneshot_unit_handle_t *ret_unit);
复制代码
函数形参:

1.png
表24.3.1.1 adc_oneshot_new_unit函数形参描述

函数返回值:
ESP_OK表示ADC单元创建成功。
ESP_ERR_INVALID_ARG表示错误参数。
ESP_ERR_NO_MEM表示内存不足。
ESP_ERR_NOT_FOUND表示ADC外设在使用。
ESP_FAIL表示时钟源初始化不正确。
init_config为指向ADC驱动配置结构体指针。接下来,笔者将介绍adc_oneshot_unit_cfg_t结构体中各个成员,如下代码所示:
  1. typedef struct {
  2.     adc_unit_t unit_id;             /* 选择ADC */
  3.     adc_oneshot_clk_src_t clk_src;  /* 选择ADC的时钟源 */
  4.     adc_ulp_mode_t ulp_mode;        /* 是否支持ADC在ULP模式下工作 */
  5. } adc_oneshot_unit_init_cfg_t;
复制代码
adc_oneshot_unit_cfg_t结构体用于配置ADC的参数,以下对各个成员做的简单介绍。
1)unit_id:
用于选择ADC,可选ADC_UNIT_1或ADC_UNIT_2。
2)clk_src:
选择ADC的时钟源。设置为0时,将会使用默认时钟源,也可不对该成员配置。
3)ulp_mode:
是否支持ADC在ULP模式下工作。
ret_unit为指向ADC单元句柄结构体的指针。
2,配置ADC单元的通道函数adc_oneshot_config_channel
该函数用于设置ADC单次转换模式即配置ADC通道,其函数原型如下:
  1. esp_err_t adc_oneshot_config_channel(adc_oneshot_unit_handle_t handle, adc_channel_t channel, const adc_oneshot_chan_cfg_t *config);
复制代码
函数形参:

2.png
表24.3.1.2 adc_oneshot_config_channel函数形参描述

函数返回值:
ESP_OK表示配置成功。
ESP_ERR_INVALID_ARG表示错误参数。
handle为ADC单元句柄,在调用adc_oneshot_new_unit函数时将返回ADC单元句柄。
channel为ADC通道,可从adc_channel_t枚举类型中选择ADC_CHANNEL_0~9。注意:这些ADC通道有对应的IO。
config为指向ADC IO配置结构体的指针,adc_oneshot_chan_cfg_t结构体其定义如下:
  1. typedef struct {
  2.     adc_atten_t atten;              /* ADC衰减 */
  3.     adc_bitwidth_t bitwidth;        /* 原始转换结果的位宽 */
  4. } adc_oneshot_chan_cfg_t;
复制代码
adc_oneshot_chan_cfg_t结构体用于配置ADC IO,以下对各个成员做的简单介绍。
1)atten:
ADC衰减,adc_atten_t为枚举类型,其中有五个选择,比如ADC_ATTEN_DB_0、ADC_ATTEN_DB_2_5、ADC_ATTEN_DB_6、ADC_ATTEN_DB_12和ADC_ATTEN_DB_11(实际是DB_11)。不同的ADC衰减配置对应着不同的电压范围,根据需求进行配置即可。
2)bitwidth:
原始转换结果的位宽,对于ESP32-P4的ADC是12位,选择ADC_BITWIDTH_12。
注意:该函数支持多次调用,以配置不同的ADC通道。
3,读取ADC转换结果函数adc_oneshot_read
该函数用于读取已经配置好的ADC通道转换结果,其函数原型如下:
  1. esp_err_t adc_oneshot_read(adc_oneshot_unit_handle_t handle, adc_channel_t chan, int *out_raw);
复制代码
函数形参:

3.png
表24.3.1.3 adc_oneshot_read函数形参描述

函数返回值:
ESP_OK表示读取数据成功。
ESP_ERR_INVALID_ARG表示参数有误。
ESP_ERR_TIMEOUT表示操作超时。        
通过该函数获取到ADC转换的原始数据,然后通过前面提及到的换算公式进行计算,最终得到以mV为单位的电压数据。


24.3.2 程序流程图

第二十四章 ADC实验5565.png
图24.3.2.1 ADC实验程序流程图

24.3.3 程序解析
在15_adc例程中,作者在15_adc\components\BSP路径下新增了1个文件夹ADC,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. ADC驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ADC驱动源码包括两个文件:adc.c和adc.h。
下面先解析adc.h的程序。对ADC通道做了相关定义。
  1. <font size="3">#define ADC_UNIT_X                ADC_UNIT_1/* ADC单元:ADC1(ADC_UNIT_1)/ADC2(ADC_UNIT_2) */</font>
  2. <font size="3">#define ADC_CHAN                  ADC_CHANNEL_3       /* ADC1通道3对应GPIO19 */</font>
  3. <font size="3">#define ADC_ATTEN           ADC_ATTEN_DB_12     /* ADC衰减 */</font>
  4. <font size="3">#define ADC_BITWIDTH         ADC_BITWIDTH_12     /* ADC分辨率 */</font>
复制代码
接下来,解析一下adc.c的程序,首先来看一下ADC的初始化函数adc_init,代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief       初始化ADC</font>
  3. <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url]       无</font>
  4. <font size="3"> * @retval      无</font>
  5. <font size="3"> */</font>
  6. <font size="3">void adc_init(void)</font>
  7. <font size="3">{</font>
  8. <font size="3">    adc_oneshot_unit_init_cfg_t adc_config = {  /* 初始化配置结构体 */</font>
  9. <font size="3">        .unit_id  = ADC_UNIT_X,                 /* ADC单元:ADC1/ADC2 */</font>
  10. <font size="3">        .ulp_mode = ADC_ULP_MODE_DISABLE,       /* 不支持ADC在ULP模式下工作 */</font>
  11. <font size="3">};</font>
  12. <font size="3">/* ADC初始化(单次转换模式) */</font>
  13. <font size="3">    ESP_ERROR_CHECK(adc_oneshot_new_unit(&adc_config, &adcx_handle));                </font>

  14. <font size="3">    /* 配置 ADC */</font>
  15. <font size="3">    adc_oneshot_chan_cfg_t config = {</font>
  16. <font size="3">        .atten    = ADC_ATTEN,                  /* ADC衰减 */</font>
  17. <font size="3">        .bitwidth = ADC_BITWIDTH,               /* ADC分辨率 */</font>
  18. <font size="3">};</font>
  19. <font size="3">/* 配置ADC通道 */</font>
  20. <font size="3">    ESP_ERROR_CHECK(adc_oneshot_config_channel(adcx_handle, ADC_CHAN, &config));     </font>

  21. <font size="3">    /* ADC校准 */</font>
  22. <font size="3">    adc_calibration_init(ADC_UNIT_X,ADC_CHAN,ADC_ATTEN,&adcx_cali_chanx_handle);</font>
  23. <font size="3">}</font>

复制代码
在ADC初始化函数中,首先对adc_config变量的成员进行赋值,设置好ADC单元,然后调用adc_oneshot_new_unit函数对ADC进行初始化,配置为单次转换模式。后面通过对config变量的成员进行赋值,设置ADC衰减和分辨率,通过adc_oneshot_config_channel函数对ADC通道进行配置,最后通过adc_calibration_init函数对ADC进行校准。完成ADC初始化后,便可以进行数据读取。
下面看一下adc_get_result_voltage函数,实现ADC转换且进行多次采样后排序去除最高和最低值再做均值滤波,代码如下:
  1. <font size="3">#define LOST_VAL    1</font>

  2. <font size="3">/**</font>
  3. <font size="3"> * @brief         获取ADC转换的电压数值且进行多次采样后排序去除最高和最低值再做均值滤波后的结果</font>
  4. <font size="3"> * [url=home.php?mod=space&uid=60778]@note[/url]          ESP32P4 ADC对噪声敏感,可能导致ADC读数出现较大偏差</font>
  5. <font size="3"> * @note          软件上:可通过多次采样进一步降低噪声影响;硬件上:可加旁路电容连在在ADC使用引脚上</font>
  6. <font size="3"> * @param         times   : 获取次数</font>
  7. <font size="3"> * @retval        通道ch的times次转换电压平均值</font>
  8. <font size="3"> */</font>
  9. <font size="3">int adc_get_result_voltage(uint32_t times)</font>
  10. <font size="3">{</font>
  11. <font size="3">    uint32_t sum = 0;</font>
  12. <font size="3">    int voltage = 0;</font>
  13. <font size="3">    uint16_t temp_val = 0;</font>

  14. <font size="3">    /* 申请存放ADC原始数据buffer */</font>
  15. <font size="3">    int *rawdata = heap_caps_malloc(times * sizeof(int), MALLOC_CAP_INTERNAL);     </font>
  16. <font size="3">    if (NULL == rawdata)</font>
  17. <font size="3">    {</font>
  18. <font size="3">        ESP_LOGE("adc", "Memory for adc is not enough");</font>
  19. <font size="3">    }</font>

  20. <font size="3">    for (uint32_t t = 0; t < times; t++)                            /* 多次ADC采样 */</font>
  21. <font size="3">    {</font>
  22. <font size="3">        adc_oneshot_read(adcx_handle, ADC_CHAN, &rawdata[t]);        /* 读取原始数据 */</font>
  23. <font size="3">        vTaskDelay(pdMS_TO_TICKS(5));</font>
  24. <font size="3">    }</font>

  25. <font size="3">    for (uint16_t i = 0; i < times - 1; i++)                        /* 对数据进行排序 */</font>
  26. <font size="3">    {</font>
  27. <font size="3">        for (uint16_t j = i + 1; j < times; j++)</font>
  28. <font size="3">        {</font>
  29. <font size="3">            if (rawdata</font><span style="font-size: medium;"> > rawdata[j])                           /* 升序排列 */
  30.             {
  31. <span style="font-style: italic;"><span style="font-style: normal;">                temp_val   = rawdata;
  32.                 rawdata</span><span style="font-style: normal;"> = rawdata[j];
  33.                 rawdata[j] = temp_val;
  34.             }
  35.         }
  36.     }

  37.     for (uint32_t i = LOST_VAL; i < times - LOST_VAL; i++)        /* 去掉两端的丢弃值 */
  38.     {
  39.         sum += rawdata</span><span style="font-style: normal;">;         /* 累加去掉丢弃值以后的数据 */
  40.     }

  41. ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adcx_cali_chanx_handle,
  42. (sum / (times - 2 * LOST_VAL)), &voltage));

  43.     return voltage;         /* 返回平均值 */
  44. }</span></span></span>
复制代码
该函数的实现,主要调用adc_oneshot_read函数实现。多次获取ADC原始值,去除最大和最小值后取平均,用来提高准确度,最后通过adc_cali_raw_to_voltage函数把平均后的ADC原始数值转换成电压值。若想要提高ADC的稳定性,软件上可以通过多次采样进一步降低噪声影响;硬件上可加旁路电容连在ADC使用引脚上。
3. CMakeLists.txt文件
本例程的功能实现主要依靠IIC驱动和AT24C02驱动。要在main函数中,成功调用AT24C02文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
  1. set(src_dirs
  2.                    LED
  3.                         LCD
  4.                    ADC)

  5. set(include_dirs
  6.                   LED
  7.                         LCD
  8.                    ADC)

  9. set(requires
  10.                    driver
  11.                    esp_lcd
  12.                    esp_adc)

  13. idf_component_register(        SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

  14. component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
复制代码
4. main.c驱动代码
在main.c里面编写如下代码。
  1. void app_main(void)
  2. {
  3.     esp_err_t ret;
  4.     int adc_data = 0;
  5.     uint16_t voltage = 0;
  6.     float adc_vol = 0;

  7.     ret = nvs_flash_init();     /* 初始化NVS */
  8.     if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
  9.     {
  10.         ESP_ERROR_CHECK(nvs_flash_erase());
  11.         ESP_ERROR_CHECK(nvs_flash_init());
  12.     }

  13.     led_init();                 /* LED初始化 */
  14.     lcd_init();                 /* LCD初始化 */
  15.     adc_init();                 /* ADC初始化 */

  16.     lcd_show_string(10, 50,  200, 16, 16, "ESP32-P4", RED);
  17.     lcd_show_string(10, 70,  200, 16, 16, "ADC TEST", RED);
  18.     lcd_show_string(10, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);
  19.     lcd_show_string(10, 130, 200, 16, 16, "ADC_VOL:0.000V", BLUE);

  20.     while (1)
  21.     {
  22.         voltage = adc_get_result_voltage(10);               /* 获取平均电压数值 */
  23.         adc_vol = (float)voltage / 1000;
  24.         adc_data = (int)adc_vol;                            /* 获取整数部分 */
  25.         lcd_show_xnum(74, 130, adc_data, 1, 16, 0, BLUE);/* 显示电压值的整数部分 */

  26.         adc_vol -= adc_data;
  27.         adc_vol *= 1000;           /* 小数部分放大1000倍方便显示 */
  28.         lcd_show_xnum(90, 130, adc_vol, 3, 16, 0x80,BLUE);/* 显示电压值的小数部分 */

  29.         LED0_TOGGLE();
  30.         vTaskDelay(pdMS_TO_TICKS(500));
  31.     }
  32. }
复制代码
在app_main函数中,初始化ADC后,将每隔500毫秒获取一次ADC值,并将读到的ADC值(数字量)显示在LCD屏上,同时将其转换为模拟量后的电压值也显示在LCD屏上。LED0闪烁,以提示程序正在运行。
ADC值的显示简单介绍一下:首先在LCD屏上固定位置显示了小数点,然后在后面计算步骤中,先计算出整数部分在小数点前面显示,然后计算出小数部分,在小数点后面显示。这样便可以在LCD屏上显示转换结果的整数和小数部分。

24.4 下载验证
将程序下载到开发板后,可以看到LED0不停闪烁,提示程序已经在运行了,可看到LCD显示的内容如图24.4.1所示:

第二十四章 ADC实验10750.png
图24.4.1 ADC实验程序运行效果图
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-1-2 11:46

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表