当前位置: 首页 > news >正文

采用STM32CubeMX和HAL库的模数转换器应用实例

目录

STM32的ADC配置流程

模/数(A/D)转换器应用的硬件设计

模/数(A/D)转换器应用的软件设计

1. 通过STM32CubeMX新建工程

2. 通过Keil MDK实现工程


STM32的ADC功能繁多,比较基础实用的是单通道采集,实现开发板上电位器的动触点输出引脚电压的采集,并通过串口输出至PC端串口调试助手。单通道采集适用AD转换完成中断,在中断服务函数中读取数据,不使用DMA传输,在多通道采集时才使用DMA传输。

STM32的ADC配置流程

STM32的ADC功能较多,可以DMA、中断等方式进行数据的传输,结合标准库并根据实际需要,按步骤进行配置,可以大大提高ADC的使用效率。 使用ADC1的通道1进行AD转换,这里需要说明一下,使用到的库函数分布在stm32f1xx_adc.c文件和stm32f1xx_adc.h文件中。下面讲解其详细设置步骤:

(1)开启PA口时钟和ADC1时钟,设置PA1为模拟输入。 STM32F103ZET6的ADC通道1在PA1上,所以,先要使能PORTA的时钟,然后设PA1为模拟输入。同时要把PA1复用为ADC,所以要使能ADC1时钟。使能GPIOA时钟和ADC1时钟都很简单,具体方法为:

__HAL_RCC_ADC1_CLK_ENABLE();//使能ADC1时钟
__HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIOA时钟
初始化PA1为模拟输入,关键代码为:
GPIO_InitTypeDef GPIO_Initure;	
GPIO_Initure.Pin=GPIO_PIN_1;	                   //PA1
GPIO_Initure.Mode=GPIO_MODE_ANALOG;   //模拟
GPIO_Initure.Pull=GPIO_NOPULL;          	//不带上下拉
HAL_GPIO_Init(GPIOA,&GPIO_Initure);

(2)初始化 ADC,设置 ADC 时钟分频系数,分辨率,模式,扫描方式,对齐方式等信息。 在HAL库中,初始化ADC是通过函数HAL_ADC_Init实现的,该函数声明为: HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef* hadc); 该函数只有一个入口参数 hadc,为ADC_HandleTypeDef结构体指针类型,结构体定义为:

typedef struct
{
ADC_TypeDef	                      *Instance;//ADC1/ ADC2/ ADC3
ADC_InitTypeDef	            Init;//初始化结构体变量
DMA_HandleTypeDef	            *DMA_Handle; //DMA 方式使用
HAL_LockTypeDef                              Lock;
__IO HAL_ADC_StateTypeDef          State;
__IO uint32_t	                                   ErrorCode;
}ADC  HandleTypeDef;	
该结构体定义和其他外设比较类似,我着重看第二个成员变量Init含义,它是结构体ADC_InitTypeDef类型,结构体ADC_InitTypeDef定义为:
typedef struct
{
uint32_t  DataAlign;//对齐方式:左对齐还是右对齐:ADC_DATAALIGN_RIGHT
uint32_t  ScanConvMode;//扫描模式DISABLE
uint32_t  ContinuousConvMode;//开启连续转换模式或者单次转换模式 DISABLE
uint32_t  NbrOfConversion; //规则序列中有多少个转换1
uint32_t  DiscontinuousConvMode;//不连续采样模式DISABLE
uint32_t  NbrOfDiscConversion;//不连续采样通道数
uint32_t  ExternalTrigConv;//外部触发方式ADC_SOFTWARE_START}ADC_InitTypeDef;

直接把每个成员变量含义注释在结构体定义的后面,请大家仔细阅读上面注释。 这里需要说明一下,和其他外设一样,HAL 库同样提供了 ADC 的 MSP 初始化函数,一般情况下,时钟使能和 GPIO 初始化都会放在MSP初始化函数中。函数声明为: void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc);

(3)开启 AD 转换器。 在设置完了以上信息后,就开启AD转换器了(通过ADC_CR2寄存器控制)。 HAL_ADC_Start(&ADC1_Handler);//开启ADC

(4)配置通道,读取通道 ADC 值。 在上面的步骤完成后,ADC就算准备好了。接下来要做的就是设置规则序列1里面的通道,然后启动ADC转换。在转换结束后,读取转换结果值值就是了。 设置规则序列通道以及采样周期的函数是: HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef* hadc,ADC_ChannelConfTypeDef* sConfig);

该函数有两个入口参数,第一个就不用多说了,接下来看第二个入口参数 sConfig,它ADC_ChannelConfTypeDef 结构体指针类型,结构体定义如下:

typedef struct {  

uint32_t  Channel;//ADC 通道

uint32_t  Rank;    //规则通道中的第几个转换

uint32_t  SamplingTime;    //采样时间

}ADC_ChannelConfTypeDef;

该结构体有四个成员变量,对于 STM32F1 只用到前面三个。Channel 用来设置 ADC 通道, Rank 用来设置要配置的通道是规则序列中的第几个转换,SamplingTime 用来设置采样时间。

使用实例为:     ADC1_ChanConf.Channel=ch;    //通道 ADC1_ChanConf.Rank=1;    //第 1 个序列,序列 1 ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_239CYCLES_5;    //采样时间 HAL_ADC_ConfigChannel(&ADC1_Handler,&ADC1_ChanConf);    //通道配置 配置好通道并且使能ADC后,接下来就是读取 ADC 值。这里采取的是查询方式读取,所以还要等待上一次转换结束。此过程HAL库提供了专用函数HAL_ADC_PollForConversion,函数定义为:

HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout); 等待上一次转换结束之后,接下来就是读取 ADC 值,函数为: uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc);

模/数(A/D)转换器应用的硬件设计

开发板板载一个贴片滑动变阻器,电路设计如图11-9所示。 贴片滑动变阻器的动触点连接至STM32芯片的ADC通道引脚。当我们使用旋转滑动变阻器调节旋钮时,其动触点电压也会随之改变,电压变化范围为0~3.3V,亦是开发板默认的ADC电压采集范围。

模/数(A/D)转换器应用的软件设计

ADC_InitTypeDef结构体成员用于设置ADC工作参数,并由外设初始化配置函数,比如MX_ADC1_Init()调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。初始化结构体定义在stm32f1xx_hal_adc.h文件中,初始化库函数定义在stm32f1xx_hal_adc.c文件中,编程时可以结合这两个文件内注释使用。

ADC_InitTypeDef结构体如下。
typedef struct
{
uint32_t DataAlign; // ADC 数据寄存器对齐格式
FunctionalState ScanConvMode; /* ADC 扫描(多通道)
FunctionalState ContinuousConvMode; // ADC 单次转换或者连续转换选择
uint32_t NbrOfConversion; // ADC 工作模式选择
FunctionalState DiscontinuousConvMode; // ADC 单次转换或者连续转换选择
Uint32_t NbrOfDiscConversion; // ADC 采集通道数
uint32_t ExternalTrigConv; // ADC 转换触发信号选择
} ADC_InitTypeDef;

这些结构体成员说明如下,其中括号内的文字是对应参数在STM32 HAL库中定义的宏:

(1)DataAlign:转换结果数据对齐模式,可选右对齐ADC_DataAlign_Right或者左对齐ADC_DataAlign_Left。一般选择右对齐模式。

(2)ScanConvMode:可选参数为ENABLE和DISABLE,配置是否使用扫描。如果是单通道AD转换使用DISABLE,如果是多通道AD转换使用ENABLE。

(3)ContinuousConvMode:指定是以单一模式(一次转换)还是以常规组的连续模式执行转换,在所选触发发生之后(软件启动或外部触发)。此参数可设置为ENABLE(启用)或DISABLE(禁用)。

(4)NbrOfConversion:指定将在常规组序列器中转换的列组数。要使用常规组序列器并转换多个列组,必须启用参数“ScanConvMode”。此参数必须是介于Min_Data=1和Max_Data=16之间的数字。

(5)DiscontiousConvMode:指定是否按完整序列/不连续序列(主序列细分为连续部分)执行常规组的转换序列。仅当序列器已启用(参数“ScanConvMode”)时,才使用不连续模式。如果序列器被禁用,此参数将被丢弃。只有在禁用连续模式时,才能启用间断模式。如果启用连续模式,则放弃此参数设置。此参数可设置为ENABLE(启用)或DISABLE(禁用)。

(6)NbrOfDiscConversion:指定将细分常规组的主序列(参数NbrOfConversion)的不连续转换数。如果禁用了参数“DisconnectousConvMode”,则放弃此参数。此参数必须是介于Min_Data=1和Max_Data=8之间的数字。

(7)ExternalTrigConv:外部触发选择,可根据项目需求配置触发来源。实际上,我们一般使用软件自动触发。 配置完这些结构体成员值,调用库函数 HAL_ADC_Init()即可把结构体的配置写入到寄存器中。

下面讲述如何通过STM32CubeMX新建工程、如何通过Keil MDK实现工程。

1. 通过STM32CubeMX新建工程

通过STM32CubeMX新建工程的步骤如下:

(1)新建文件夹   Demo目录下新建文件夹ADC,这是保存本章新建工程的文件夹。

(2)新建STM32CubeMX工程 在STM32CubeMX开发环境中新建工程。

(3)选择MCU或开发板 Commercial Part Number和MCUs/MPUs List选择STM32F103ZET6,选择Start Project启动工程。

(4)保存STM32Cube MX工程 使用STM32CubeMX菜单File→Save Project,保存工程。

(5)生成报告 使用STM32CubeMX菜单File→Generate Report生成当前工程的报告文件。

(6)配置MCU时钟树 STM32CubeMX Pinout & Configuration子页面下,选择System Core→RCC,High Speed Clock(HSE)根据开发板实际情况,选择Crystal/Ceramic Resonator(晶体/陶瓷晶振)。

STM32CubeMX切换到Clock Configuration子页面下,根据开发板外设情况配置总线时钟。此处配置PLL Source Mux为HSE,PLLMul为9倍频72MHz,System Clock Mux为PLLCLK,APB1 Prescaler为X2,其余默认设置即可。

(7)配置MCU外设

STM32CubeMX Pinout & Configuration子页面下选择System Core→GPIO,对使用的GPIO口进行设置。ADC1输入端口PC1配置为ADC1_IN11。

在STM32CubeMX中配置完USART1后,会自动完成相关GPIO口的配置,不需用户配置。 配置完ADC端口后,时钟配置页面会提示错误,STM32CubeMX切换到Clock Configuration子页面下,需要修改ADC时钟为12MHz,ADC Prescaler选择/6

STM32CubeMX Pinout & Configuration子页面下选择Analog→ADC1,对ADC1进行设置。IN11为配置GPIO口PC1时自动选择,ADC Parameter Settings默认配置即可

STM32CubeMX Pinout & Configuration子页面下分别配置USART1、NVIC模块,方法同SPI部分。 切换到STM32CubeMX Pinout & Configuration子页面下选择System Core→NVIC,修改Priority Group为2 bits for pre-emption priority(2位抢占优先级),Enabled栏勾选USART1 global interrupt和ADC1 and ADC2 global interrupts,分别修改Preemption Priority(抢占优先级)和Sub Priority(子优先级)

Code Generation页面Select for init sequence ordering栏勾选USART1 global interrupt和ADC1 and ADC2 global interrupts,NVIC Code Generation配置页面

(8)配置工程 STM32CubeMX Project Manager子页面Project栏下Toolchain/IDE选择MDK-Arm,Min Version选择V5,可生成Keil MDK工程;选择STM32CubeIDE,可生成STM32CubeIDE工程。

(9)生成C代码工程 STM32CubeMX主页面,单击GENERATE CODE按钮生成C代码工程。

2. 通过Keil MDK实现工程

通过Keil MDK实现工程的步骤如下:

(1)打开工程   打开ADC\MDK-Arm文件夹下的工程文件。

(2)编译STM32CubeMX自动生成的MDK工程 在MDK开发环境中通过菜单Project→Rebuild all target files或工具栏      Rebuild按钮编译工程。

(3)STM32CubeMX自动生成的MDK工程 main.c文件中函数main()依次调用了HAL_Init()函数用于复位所有外设,初始化Flash接口和Systick定时器。SystemClock_Config()函数用于配置各种时钟信号频率。MX_GPIO_Init()函数初始化GPIO引脚。 文件gpio.c包含了函数MX_GPIO_Init()的实现代码,如下。

void MX_GPIO_Init(void)
{/* GPIO Ports Clock Enable */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();
}

MX_USART1_UART_Init()是USART1的初始化函数。 main()函数外设初始化新增MX_ADC1_Init(),它是ADC1的初始化函数。MX_ADC1_Init()是在文件adc.c中定义的函数,实现STM32CubeMX配置的ADC1设置。单通道采集不需要扫描、启动连续转换、使用内部软件触发无须外部触发事件、使用右对齐数据格式、转换通道为1,采样时间为55CYCLES_5。

MX_ADC1_Init()实现的代码如下。
void MX_ADC1_Init(void)
{ADC_ChannelConfTypeDef sConfig = {0};/** Common config*/hadc1.Instance = ADC1;hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;hadc1.Init.ContinuousConvMode = ENABLE;hadc1.Init.DiscontinuousConvMode = DISABLE;hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;if (HAL_ADC_Init(&hadc1) != HAL_OK){Error_Handler();}/** Configure Regular Channel*/sConfig.Channel = ADC_CHANNEL_11;sConfig.Rank = ADC_REGULAR_RANK_1;sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){Error_Handler();}/* USER CODE BEGIN ADC1_Init 2 *//* USER CODE END ADC1_Init 2 */
} 

MX_ADC1_Init()函数调用了HAL_ADC_Init(),继而调用了adc.c中实现的HAL_ADC_MspInit(),初始化ADC1相关的时钟和GPIO口。HAL_ADC_MspInit()函数实现如下。

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(adcHandle->Instance==ADC1){/* ADC1 clock enable */__HAL_RCC_ADC1_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();/**ADC1 GPIO ConfigurationPC1     ------> ADC1_IN11*/
GPIO_InitStruct.Pin = GPIO_PIN_1;GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);}
}
函数MX_NVIC_Init()实现中断的初始化,代码如下。
static void MX_NVIC_Init(void)
{/* USART1_IRQn interrupt configuration */HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);HAL_NVIC_EnableIRQ(USART1_IRQn);
/* ADC1_2_IRQn interrupt configuration */HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
}

(4)新建用户文件   不需要新建用户文件,adc.c和usart.c都由STM32CubeMX自动生成。

(5)编写用户代码 usart.h和usart.c文件声明和定义使用到的变量、宏定义。usart.c文件MX_USART1_UART_Init()函数开启USART1接收中断。stm32f1xx_it.c对USART1_IRQHandler()函数添加接收数据的处理。

adc.c文件MX_I2C1_Init()函数启用ADC1,以中断方式开始常规组的转换。   /* USER CODE BEGIN ADC1_Init 2 */   HAL_ADC_Start_IT(&hadc1);   /* USER CODE END ADC1_Init 2 */ 中断服务函数一般定义在stm32f1xx_it.c文件内,HAL_ADC_IRQHandler()是HAL中自带的一个中断服务函数,处理过程中会指向一个回调函数去添加用户代码,这里使用HAL_ADC_ConvCpltCallback()转换完成中断,在ADC转换完成后就会进入中断服务函数,再进入回调函数,在回调函数内直接读取ADC转换结果保存在变量ADC_ConvertedValue(在main.c中定义) 中。

ADC_GetConversionValue()函数是获取ADC转换结果值的库函数,只有一个形参为ADC句柄,该函数返回一个16位的ADC转换结果值。

AD采样回调处理函数HAL_ADC_ConvCpltCallback如下。
/* USER CODE BEGIN 1 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{/* 获取结果 */ADC_ConvertedValue = HAL_ADC_GetValue(AdcHandle);
}
/* USER CODE END 1 */
main.c文件添加对ADC的操作。通过STM32内部ADC1读取通道11(PC1)上面的电压,将ADC转换值发送到串口。/* Infinite loop *//* USER CODE BEGIN WHILE */while (1){Delay(0x1fffff);ADC_Vol =(float)ADC_ConvertedValue/4096*(float)3.3; // 读取转换的AD值printf("\r\n The current AD value = 0x%04X \r\n", ADC_ConvertedValue); printf("\r\n The current AD value = %f V \r\n",ADC_Vol);  
/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */

(6)重新编译工程   重新编译添加代码后的工程。

(7)配置工程仿真与下载项 在MDK开发环境中通过菜单Project→Options for Target或工具栏      配置工程。 打开Debug选项卡,选择使用的仿真下载器ST-Link Debugger。Flash Download下勾选Reset and Run选项。单击确定。

(8)下载工程   连接好仿真下载器,开发板上电。 在MDK开发环境中通过菜单Flash→Download或工具栏    下载工程。 工程下载完成后,连接串口,打开串口调试助手,查看串口收发是否正常,转动电位器,查看串口显示的采样电压是否正常。串口调试助手调试界面(ADC)


http://www.mrgr.cn/news/63042.html

相关文章:

  • 界面控件Kendo UI for Angular 2024 Q3亮点 - 全新的页面模板
  • 01背包模板 | 学习总结
  • aab上架谷歌市场流程(apk)
  • Servlet 3.0 新特性全解
  • git入门教程6:git基本版本控制
  • 关于前端程序员使用Idea快捷键配置的说明
  • C# 广播技术——发现局域网设备技术——
  • 计算机的错误计算(一百四十)
  • C语言指针和数组相关习题
  • Python中如何处理异常情况?
  • C++算法练习-day30——111.二叉树的最小深度
  • C++算法练习-day29——104.二叉树的最大深度
  • C语言 | Leetcode C语言题解之第523题连续的子数组和
  • Podman+Minikube:MacBook 运行 Kubernetes 最佳实践
  • vmvare启动freebsd操作系统密码忘记了怎么办?
  • 哪里可以找到无版权抖音视频素材?
  • lanqiaoOJ 1110:小王子单链表 ← STL list
  • Python | Leetcode Python题解之第524题通过删除字母匹配到字典里最长单词
  • mysql 的内连接、左连接、右连接有什么区别?
  • 3000字帮你彻底搞懂Java抽象类与接口的区别(含JDK8接口新增三种方法与丰富案例)
  • 如何在Ubuntu 18.04上使用uWSGI和Nginx为Flask应用提供服务
  • 【数据结构-邻项消除】力扣1717. 删除子字符串的最大得分
  • 如何找到车在路上行驶的视频素材
  • 数据结构之顺序表(C语言)
  • Java | Leetcode Java题解之第523题连续的子数组和
  • JavaScript实现将阿拉伯数字转换成中文或大写中文