【STM32】标准库与HAL库对照学习教程十一--ADC模数转换
STM32全部教程
:【STM32】标准库与HAL库对照学习系列教程大全
一、前言
本篇是对STM32F1中的ADC的讲解,在本篇文章中你将了解什么是ADC、STM32中的ADC以及STM32关于ADC的配置,本篇使用标准库与HAL库进行对照配置,希望您能在本篇中有所收获。 |
二、准备工作
- STM32F103开发板(我用的是普中的STM32F103ZE开发板)
- cubemx软件、keil 5(MDK)
- 开发板原理图
三、ADC简介
ADC英文名analog to digital converter,模数转换器,它可以将模拟信号转换为数字信号。
按照其转换原理主要分为逐次逼近型、双积分型、电压频率转换型三种。
四、STM32F1中的ADC
1、STM32F1中的ADC介绍
STM32F103 系列一般都有 3个ADC,这些 ADC 可以独立使用,也可以使用双重,这样可以提高采样率。
STM32F1 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它具有多达 18个复用通道,可测量来自16 个外部源、2 个内部源信号。
这些通道的 A/D 转换可以单次、连续、扫描或间断模式执行。
ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。
ADC具有模拟看门狗特性,可以使用程序检测输入电压是否超出用户定义的阀值上限或者下限。
2、ADC的结构框图
图片来源于STM32F1xx中文参考手册 ADC章节
3、ADC结构解析
从上图中可以分析ADC的工作模式。
这部分的图片均来源于STM32F1xx中文参考手册 ADC章节
(1)引脚输入电压
引脚输入的ADC转换电压范围由VDDA、VSSA、VREF+、VREF- 决定。它们的定义由下图所示。
通常我们把 VSSA和 VREF- 接GND,把 VREF+和 VDDA 接 3.3V。
因此引脚输入的ADC转换电压范围为:VREF- ≤ VIN ≤ VREF+
等价于:0 ≤ VIN ≤ 3.3V(VIN表示输入引脚的电压)
(2)输入通道
STM32 的 ADC的输入通道多达 18 个,其中外部的 16 个通道就是框图中的 ADCx_IN0…ADCx_IN5(x=1/2/3,表示ADC数)。
这 16个通道分别对应着不同的 IO口, 具体是哪一个 IO口可以从数据手册查询到。
其中 ADC1 还有2个内部通道:ADC1 的通道16连接到了芯片内部的温度传感器,通道17连接到了内部参考电压 VREFINT。ADC2和ADC3的通道16、17全部连接到了内部的VSS。下图为ADC转换通道对应的引脚。参考普中PPT教程。
(3)转换通道
外部16个通道在转换时,可选择2个通道组:规则通道组和注入通道组。其中规则通道组最多有16路,注入通道组最多有 4 路。
- 规则通道组:从名字来理解,规则通道就是一种规规矩矩的通道,类似于正常执行的程序,通常我们使用的都是这个通道。
- 注入通道组:从名字来理解,注入即为插入,类似于中断。当程序正常往下执行时,中断可以打断程序的执行。同样如果在规则通道转换过程中,有注入通道插入,那么就要先转换完注入通道,等注入通道转换完成后再回到规则通道的转换流程。
每个组包含一个转换序列,该序列可按任意顺序在任意通道上完成。
(4)ADC触发源
①
ADC转化的触发源有很多,我们常用软件编写程序设置ADC控制寄存器ADC_CR2的 ADON位为1,直接使能ADC。
②
关于ADC的触发源由ADC 控制寄存器2-ADC_CR2 的 EXTSEL[2:0]和 JEXTSEL[2:0]位来控制。EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[2:0]用于选择注入通道的触发源。
③
触发源选定好之后,触发源是否要激活,则由 ADC 控制寄存器ADC_CR2 的 EXTTRIG 和 JEXTTRIG 这两位来激活。
(5)ADC时钟与转换时间
①
ADC 输入时钟ADC_CLK 由 APB2经过分频产生,最大值是14MHz,分频因子由 RCC 时钟配置寄存器 RCC_CFGR 的位15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频,但没有 1 分频。
我们知道APB2总线时钟为72M,而ADC最大工作频率为14M,所以一般设置分频因子为6,这样ADC的输入时钟ADC_CLK的频率为12M,周期为1/ADC_CLK。
②
ADC要完成对输入电压的采样需要若干个输入时钟ADC_CLK的周期。
采样的周期数可通过ADC 采样时间寄存器 ADC_SMPR1和ADC_SMPR2 中的 SMP[2:0]位设置。
ADC_SMPR2控制的是通道 0到9,ADC_SMPR1 控制的是通道 10到17。每个通道可以分别用不同的时间采样。
其中采样周期最小是1.5个。
ADC 的总转换时间的公式如下:
Tconv = 采样时间 + 12.5个周期
当ADC_CLK为14MHz时,采样时间为1.5个周期。
总的采样时间=(1.5+12.5)/14000000=1us
但一般ADC_CLK为12MHz,将上面的14000000换为12000000
采样时间约为1.17us。
(6)对齐方式
由于STM32F1的ADC是12位转换精度,4095为最大值,因此 电压值=(转换的值/4095)*3.3。
数据寄存器是16位,所以ADC在存放数据的时候就有左对齐和右对齐区分。
如果是左对齐,AD转换完成数据存放在 ADC_DR 寄存器的[4:15]位内;如果是右对齐,则存放在 ADC_DR 寄存器的[0:11]位内。
具体选择何种存放方式,需通过ADC_CR2 的 11 位 ALIGN 设置。
(7)中断
当发生如下事件且使能相应中断标志位时,ADC能产生中断。
- 转换结束,规则转换与注入转换结束
- 模拟看门狗事件
- DMA请求
五、ADC的转换模式
1、单次转换
ADC只执行一次转换,转换后停止。
转换结果将被存在 ADC_DR 寄存器。
2、连续转换
ADC 结束一轮转换后立即启动新一轮的转换。
两个模式由ADC控制寄存器 2(ADC_CR2)的CONT位决定。
3、扫描模式
ADC扫描 选择的所有规则通道和注入通道,在每个组的每个通道上执行单次转换。如果开启了连续转换,转换完所有通道后,又会重新从第一个通道开始转换。
模式由ADC控制寄存器 1(ADC_CR1)的SCAN位决定
4、间断模式
触发一次,转换一个通道,在触发,在转换。在所选的所有转换通道,由触发信号启动新一轮的转换,直到转换完成为止。
模式由ADC控制寄存器 1(ADC_CR1)的JDISCEN位与DISCEN决定
六、硬件电路
请参考自己开发板的硬件电路,没有下面的电路,可以找一个可调的电源接到引脚上(注意电压要小于3,3V)
七、标准库配置ADC实验
1、配置步骤
(1)使能端口时钟和ADC时钟,设置引脚模式为模拟输入
(2)设置ADC的分频因子
(3)初始化ADC参数,包括ADC工作模式、规则序列等
(4)使能ADC并校准
(5)设置规则序列通道以及采样周期
(6)读取ADC转换值
2、配置工程
(1)复制上一章的工程,并重命名为11、ADC单通道转换。
(2)进入工程文件,进入APP文件,新建ADC用来存放与ADC相关的文件。
(3)打开工程,新建文件,并命名为adc.h与adc.c。
①
②
(4)添加文件到目录,并添加头文件路径。
①
②
(5)要使用ADC需要添加相应的文件。
①
②
3、实验程序
main.h
#include "Delay.h"
#include "usart.h"
#include "stdio.h"
#include "adc.h"
/*************************************************
*函数名: main
*函数功能: 主函数
*输入: 无
*返回值: 无
**************************************************/
int main()
{
float Voltage; //转换的电压值
SysTick_Init(72);
USART1_Init(9600);
ADC1_CH1_Init(); //ADC初始化
while(1)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //转换使能
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); //获取标志位
Voltage = ((float)ADC_GetConversionValue(ADC1)/4095.0)*3.3; //获取并计算电压值
printf("转换的电压为 %.2f V\r\n",Voltage); //将电压值输出打印
ADC_ClearFlag(ADC1, ADC_FLAG_EOC); //清空标志位
Delay_ms(500);
}
}
adc.h
#ifndef ADC_H_
#define ADC_H_
#include "stm32f10x.h"
void ADC1_CH1_Init(void); //初始化ADC函数
#endif
adc.c
#include "adc.h"
/*************************************************
*函数名: ADC1_CH1_Init
*函数功能: ADC通道1初始化函数
*输入: 无
*返回值: 无
**************************************************/
void ADC1_CH1_Init()
{
GPIO_InitTypeDef GPIO_InitStruct;
ADC_InitTypeDef ADC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1, ENABLE); //使能端口与ADC时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //ADC时钟6分频,分频后为12MHz
//初始化引脚
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1; //PA1引脚
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; //模拟输入模式
GPIO_Init(GPIOA, &GPIO_InitStruct); //引脚初始化
//初始化ADC参数
ADC_InitStruct.ADC_NbrOfChannel = 1; //转换1个通道
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; //独立模式
ADC_InitStruct.ADC_ScanConvMode = DISABLE; //扫描模式失能
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; //连续转换失能
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部触发,使用软件触发
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
ADC_Init(ADC1, &ADC_InitStruct); //ADC初始化
ADC_Cmd(ADC1, ENABLE); //使能ADC
ADC_StartCalibration(ADC1); //开始指定 ADC1 的校准状态
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_1Cycles5); //设置规则通道序列并设置采样时间为1.5个周期
}
4、实验效果
这里通过改变移动滑动变阻改变输入引脚电压的值。
八、HAL库配置ADC实验
1、使用cubemx配置工程
(1) 打开cubemx,新建工程,选择自己的芯片。
(2) 配置RCC,选择外部高速时钟。
(3) 配置时钟树。
(4) 设置ADC引脚。
(5) 设置ADC时钟分频为6分频。
(5) ADC参数配置。
备注:Rank那里还可以设置转换周期,Discontinuous Conversion Mode是间断模式的设置
(6) 打开串口
(7) 工程文件配置并生成工程
①
②
2、常用函数介绍
• HAL_ADC_Start(); //开启ADC
• HAL_ADC_Start_IT(); //开启ADC与中断
• HAL_ADC_Start_DMA();//开启ADC的DMA模式
• HAL_ADC_Stop(); //关闭ADC
• HAL_ADC_Stop_IT(); //关闭ADC与中断
• HAL_ADC_Stop_DMA(); //关闭ADC的DMA模式
• HAL_ADCEx_Calibration_Start(&hadcx); //ADC校准
• HAL_ADC_GetValue();//获取ADC转换值
• HAL_ADC_PollForConversion(); //等待转换结束函数
hadcx:hadc1、hadc2、hadc3
Timeout:最大等待时间
ADC中断回调函数
• HAL_ADC_ConvCpltCallback()
• HAL_ADC_ConfigChannel() 配置规则组通道
• HAL_ADC_AnalogWDGConfig()//看门狗配置
3、实验程序
①
#include<stdio.h>
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
②
float Voltage; //电压
HAL_ADC_Start(&hadc1); //开启ADC转换
HAL_ADC_PollForConversion(&hadc1,50); //等待ADC转换结束
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC)) //获取转换状态
{
Voltage = ((float)HAL_ADC_GetValue(&hadc1)/4095.0)*3.3; //获取并计算电
printf("Voltage = %.2f\r\n",Voltage); //打印输出电压值
}
HAL_Delay(500);
4、实验效果
这里通过改变移动滑动变阻改变输入引脚电压的值
到这里就结束啦!