STM32实战经验分享:深入理解HAL ADC模块

ADC基础知识参考:

51单片机外设篇:ADC_路溪非溪的博客-CSDN博客_51单片机 adc

阅读参考手册

自行查阅手册。

理论部分可直接参考:STM32—ADC详解_Aspirant-GQ的博客-CSDN博客_stm32adc

以下引用部分内容:

12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。

ADC的输入时钟不得超过14MHz,其时钟频率由PCLK2分频产生。

功能框图可以大体分为7部分,下面一一讲解:

1.电压输入范围
ADC所能测量的电压范围就是VREF- ≤ VIN ≤ VREF+,把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为: 0~3.3V。

为了提高转换的精确度,ADC使用一个独立的电源供电,过滤和屏蔽来自印刷电路板上的毛刺干扰。

关于参考电压:ADC参考电压有多重要?

2.输入通道
ADC的信号输入就是通过通道来实现的,信号通过通道输入到单片机中,单片机经过转换后,将模拟信号输出为数字信号。STM32中的ADC有着18个通道,其中16个外部通道,2个内部通道。

ADCx_IN0 ~ ADCx_IN15是外部GPIO端口复用。

温度传感器和内部参考电压是内部输入通道。

3.规则通道和注入通道

外部的16个通道在转换时又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4路(注入通道貌似使用不多),下面简单介绍一下俩种通道:
规则通道
规则通道顾名思义就是,最平常的通道、也是最常用的通道,规规矩矩的按照我们设定的转换顺序进行转换的通道。平时的ADC转换都是用规则通道实现的。
注入通道
注入通道是相对于规则通道的,注入通道可以在规则通道转换时,强行插入转换,相当于一个“中断通道”吧。当有注入通道需要转换时,规则通道的转换会停止,优先执行注入通道的转换,当注入通道的转换执行完毕后,再回到之前规则通道进行转换。换个角度说,注入通道只有在规则通道存在的情况下才会存在。

举个简单的例子:

假设你在家里的宅院内放了5个温度探头,室内放了3个温度探头;你需求时刻监督室外温度,但偶尔你也想看看室内的温度。此时你能够运用规则通道组循环扫描室外的5个探头并显现AD转化成果,当你想看室内温度时,经过一个按钮发动注入转化组(3个室内探头)并暂时显现室内温度,当你放开这个按钮后,体系又会回到规矩通道组持续检测室外温度。

显然,测量并显现室内温度的进程中止了测量并显现室外温度的进程。

在程序规划上,能够在初始化阶段就设置好不同的转化组,体系运转中不用再改变转化的设置,然后实现两类任务互不干扰和快速切换的目的。

上述例子由于速度较慢,不能彻底体现区分规矩组和注入组带来的优点,但在工业应用领域中有许多检测探头需要快速处理,这样对AD转化进行分组将简化处理程序并加快处理速度。

4.触发源
ADC转换的输入、通道、转换顺序都已经说明了,但ADC转换是怎么触发的呢?就像通信协议一样,都要规定一个起始信号才能传输信息,ADC也需要一个触发信号来实行模/数转换。
其一就是通过直接配置寄存器触发,通过配置控制寄存器CR2的ADON位,写1时开始转换,写0时停止转换。在程序运行过程中只要调用库函数,将CR2寄存器的ADON位置1就可以进行转换,比较好理解。
另外,还可以通过内部定时器或者外部IO触发转换,也就是说可以利用内部时钟让ADC进行周期性的转换,也可以利用外部IO使ADC在需要时转换,具体的触发由控制寄存器CR2决定。
 

5.转换时间
还有一点,就是转换时间的问题,ADC的每一次信号转换都要时间,这个时间就是转换时间,转换时间由输入时钟和采样周期来决定。
输入时钟
由于ADC在STM32中是挂载在APB2总线上的,所以ADC得时钟是由PCLK2(72MHz)经过分频得到的,分频因子由 RCC 时钟配置寄存器RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频,一般配置分频因子为8,即8分频得到ADC的输入时钟频率为9MHz。
采样周期
采样周期是确立在输入时钟上的,配置采样周期可以确定使用多少个ADC时钟周期来对电压进行采样,采样的周期数可通过 ADC采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置,ADC_SMPR2 控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~17。每个通道可以配置不同的采样周期,但最小的采样周期是1.5个周期,也就是说如果想最快时间采样就设置采样周期为1.5.
转换时间
转换时间=采样时间+12.5个周期
12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us。

6.数据寄存器

转换完成后的数据就存放在数据寄存器中,但数据的存放也分为规则通道转换数据和注入通道转换数据的。

规则数据寄存器
规则数据寄存器负责存放规则通道转换的数据,通过32位寄存器ADC_DR来存放。

当使用ADC独立模式(也就是只使用一个ADC,可以使用多个通道)时,数据存放在低16位中,当使用ADC多模式时高16位存放ADC2的数据。

需要注意的是ADC转换的精度是12位,而寄存器中有16个位来存放数据,所以要规定数据存放是左对齐还是右对齐

当同一个ADC但是使用多个通道转换数据时,会产生多个转换数据,然鹅数据寄存器只有一个,多个数据存放在一个寄存器中会覆盖数据导致ADC转换错误,所以我们经常在一个通道转换完成之后就立刻将数据取出来,方便下一个数据存放。一般开启DMA模式将转换的数据,传输在一个数组中,程序对数组读操作就可以得到转换的结果。

注入数据寄存器
注入通道转换的数据寄存器有4个,由于注入通道最多有4个,所以注入通道转换的数据都有固定的存放位置,不会跟规则寄存器那样产生数据覆盖的问题。

ADC_JDRx 是 32 位的,低 16 位有效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2 的 11 位 ALIGN 设置。

7.中断

从框图中可以知道数据转换完成之后可以产生中断,有三种情况:
规则通道转换完成中断
规则通道数据转换完成之后,可以产生一个中断,可以在中断函数中读取规则数据寄存器的值。这也是单通道时读取数据的一种方法。
注入通道转换完成中断
注入通道数据转换完成之后,可以产生一个中断,并且也可以在中断中读取注入数据寄存器的值,达到读取数据的作用。
模拟看门狗事件
当输入的模拟量(电压)不在阈值范围内就会产生看门狗事件,就是用来监视输入的模拟量是否正常。
 

当然,在转换完成之后也可以产生DMA请求,从而将转换好的数据从数据寄存器中读取到内存中。

注意:

同时只能测一路通道,通过扫描的形式来实现多路输入;

同一个通道在同一时间只能属于一个ADC,即三个ADC不能同时作用于同一个通道;

规则通道就是常规通道用法,注入通道类似于抢占;

连续模式一般要配合DMA,要不然CPU一直处在中断模式;

ADC开关控制 

通过设置
ADC_CR2
寄存器的
ADON
位可给
ADC
上电。当第一次设置
ADON
位时,它将
ADC
从断电状态下唤醒。

ADC
上电延迟一段时间后
(t
STAB
)
,再次设置
ADON
位时开始进行转换。

通过清除ADON位可以停止转换,并将ADC置于断电模式。在这个模式中,ADC几乎不耗电(仅几个μA)。

硬件原理图

这里用了个NTC电阻RT1,温度的改变会引起电阻值的改变,从而改变A点的电位,R11是一个分压电阻。当温度变高(可用手触摸使得温度升高),RT1阻值降低,分压也相应变小,从而使得A点电位变低。

则根据以上电路可知,当温度升高,PC3端口读到的电压会降低。

配置MX

这里选择用ADC1的通道13,即IN13。

注:因为规则转换共用一个数据寄存器,虽然连续转换更接近我们的要求,不过连续转换可能来不及读数据,所以采用默认的单次转换。把数据读出来后,再开启读取。

配置时钟,不能超过14MHz

确认无误后,点击生成代码。

程序编写

ADC相关的库函数:

/* Blocking mode: Polling */
HAL_StatusTypeDef       HAL_ADC_Start(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef       HAL_ADC_Stop(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef       HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout);
HAL_StatusTypeDef       HAL_ADC_PollForEvent(ADC_HandleTypeDef* hadc, uint32_t EventType, uint32_t Timeout);

/* Non-blocking mode: Interruption */
HAL_StatusTypeDef       HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef       HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);

/* Non-blocking mode: DMA */
HAL_StatusTypeDef       HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
HAL_StatusTypeDef       HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);

/* ADC retrieve conversion value intended to be used with polling or interruption */
uint32_t                HAL_ADC_GetValue(ADC_HandleTypeDef* hadc);

/* ADC IRQHandler and Callbacks used in non-blocking modes (Interruption and DMA) */
void                    HAL_ADC_IRQHandler(ADC_HandleTypeDef* hadc);
void                    HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc);
void                    HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc);
void                    HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc);
void                    HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc);

ADC的三种⼯作⽅式及优缺点

1.查询模式:查询模式下,占⽤CUP时间较多,cup效率较低。

2.中断模式:相⽐查询模式⼤⼤释放了cup,提⾼了cup的利⽤率。

3.DMA模式:该模式下基本不占⽤cup,能直接将ADC采集的数据存储到存储器。

这里,使用查询模式,流程如下:

①开启ADC:调⽤HAL_ADC_Start(),开启ADC。

②等待EOC标志位:调⽤查询函数HAL_ADC_PollForConversion(),等待ADC转化结束,CUP在这段时间内不能⼲其他事,所以查询 ⽅式降低了CUP的使⽤率。

③读取寄存器数据:调⽤HAL_ADC_GetValue()。

添加两个文件,useradc.c和useradc.h

useradc.h

#ifndef _USERADC_H_
#define _USERADC_H_

#include <stdint.h>

//确定要实现的led功能
typedef struct
{
    float (*getVol)(void);
} useradc_handler;

//将结构体声明出去
extern useradc_handler useradcHandler;

#endif

useradc.c

#include "myapplication.h"

static float GetVol(void);
    
useradc_handler useradcHandler = 
{
    GetVol
};

static float GetVol(void)
{
    uint32_t adcResultNum = 0;
    
    HAL_ADC_Start(&hadc1);
    if(HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)
    {
        adcResultNum = HAL_ADC_GetValue(&hadc1);
    }
    return 3.3/4096*adcResultNum;
}

调用

static void Run(void)
{
    HAL_Delay(1000);
    printf("voltage is %f\n\r", useradcHandler.getVol());
} 

可以看到,在串口中输出了电压值

当用吹风机给NTC加热时,电压持续下降

物联沃分享整理
物联沃-IOTWORD物联网 » STM32实战经验分享:深入理解HAL ADC模块

发表评论