【stm32CubeMX裸机开发HAL库/LL库 (二)】——系统时基以及定时器的使用


前言

笔者使用的硬件开发平台为stm32f407ZGT6,配合CubeMX生成基本代码,使用Keil5/VScode开发编译,库函数为LL/HAL
承接前文【stm32CubeMX裸机开发HAL库/LL库 (一)】——使用CubeMX新建工程

一、Systick系统滴答定时器与HAL时基

CM4 内核的处理和 CM3 一样,内部都包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计到 0 时,将从 RELOAD寄存器中自动重装载定时初值。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。对具体硬件如何实现有兴趣的可以翻手册,本文不细讲了。

相信有了解过原子哥的stm32教程的同学都会发现,原子的时基文件都会使用这个定时器来编写软件延时函数,由于SysTick又属于芯片内部资源,所以这样的延时方案既不占用中断,也不占用系统定时器,在对于定时器外设资源紧张的开发板上是一个不错的选择。

而HAL库为了维护自身的时间基准,默认会选择SysTick定时器作为“基准时钟”,用于实现HAL_delay和各种time_out的时间基准

打开CubeMX系统配置界面,可以看到时基的选择可以多种多样,在不使用OS的情况下一般默认SysTick
在这里插入图片描述
接下来看看中断管理
进入NVIC(嵌套向量中断控制器)的配置界面,可以看到默认中断分组为4(即优先级寄存器的高4位全部用于抢占优先级分配,那么共有2^4=16个级别的抢占优先级0~15,0个级别的子优先级)。而SysTick的中断优先级默认设置为最低15,这也是为什么很多新手在一些特定场合使用HAL_Delay导致程序卡死的缘故——中断优先级太低了,导致一直被抢占,延时卡死。
在这里插入图片描述
在代码初始化中,贴心的HAL库也自动封装好了中断服务函数(中断服务函数集中存放在stm32f4xx_it.c里)和中断回调函数(对于老玩家作用鸡肋,效率不高,不如自己写),而且开发者可以根据自身习惯选择是否需要初始化。在这里插入图片描述
进入程序,可以看到
HAL库初始化时优先对时基进行了初始化,对SysTick定时器的频率进行了设置,之后才进行时钟配置。

API调用关系:
HAL_Init() → \rightarrow HAL_InitTick → \rightarrow HAL_SYSTICK_Config → \rightarrow SysTick_Config
文件跳转关系:
main → \rightarrow stm32f4xx_hal → \rightarrow stm32f4xx_hal_cortex → \rightarrow core_cm4
(可以看到,由于使用了SysTick,文件跳转也是从芯片外设文件到内核文件)
在这里插入图片描述

在这里插入图片描述
进入中断文件看看HAL_Delay是如何实现的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,HAL库定义了一个_IO类型的变量uwTick(_IO即volatile 类型,防止变量被优化,使其可以稳定地读取写入,此处是为了保证uwTick稳定地递增,保证时间准确),然后让其在中断服务函数中以1KHz的频率递增,即HAL_Delay最小延时时间为1ms。
在这里插入图片描述

同时,我们还注意到这些函数都是弱定义,即前面修饰了__weak,代表这些函数可以被重写,有想法的开发者可以自己写HAL_Delay,可以参考原子的方法实现us级延时,下面给出例子

static uint32_t fac_us=0;							//us延时倍乘数

/**
  * @brief  系统滴答定时器 SysTick 初始化
  * @param  ticks	
	*					SystemFrequency / 1000    1ms中断一次
	*					SystemFrequency / 100000	 10us中断一次
	*					SystemFrequency / 1000000 1us中断一次
  * @retval 无
  */
void bsp_systick_Init(uint8_t SYSCLK)
{
	HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
	fac_us=SYSCLK;
}

/**
  * @brief   ms延时程序,1ms为一个单位
  * @param  
  * @retval  无
  */
void delay_ms(__IO uint16_t nms)
{ 
	uint32_t i;
	for(i=0;i<nms;i++) delay_us(1000);
}

/**
  * @brief   us延时程序,1us为一个单位
  * @param  
  * @retval  无
  */
void delay_us(__IO uint32_t nus)
{ 
	uint32_t ticks;
	uint32_t told,tnow,tcnt=0;
	uint32_t reload=SysTick->LOAD;				//LOAD的值	    	 
	ticks=nus*fac_us; 						//需要的节拍数 
	told=SysTick->VAL;        				//刚进入时的计数器值
	while(1)
	{
		tnow=SysTick->VAL;	
		if(tnow!=told)
		{	    
			if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
			else tcnt+=reload-tnow+told;	    
			told=tnow;
			if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.
		}  
	};
}

二、使用定时器编写软件延时函数

在了解了HAL库使用内核定时器SysTick编写延时函数后,我们也可以使用ST芯片的外设定时器仿写。好在手上的ZGT6外设量大管饱,不过为了节约资源还是选用基本定时器TIM7来完成。接下来进入配置环境:

TIM7 包含一个 16 位自动重载计数器,因此其重装载值最大计数为2^16=65536,我们在配置时输入65536-1=65535
查阅时钟树和手册可知:TIM7挂载在APB1总线桥上,在主频拉满168MHz的情况下,它的最大频率为84MHz,我们设置84-1=83分频数,将其降为1MHz
在这里插入图片描述

在这里插入图片描述
按图所示配置参数,使能中断
在这里插入图片描述
在这里插入图片描述

为了更清除地明白外设如何初始化的,这里选择LL库演示,HAL库的方法会在最后给出
在这里插入图片描述
完成以上配置初始化工程,接下来写代码

为了与CubeMX生成的代码做区分,在根目录下新建目录Src作为开发者自己的源码
在这里插入图片描述
在Src目录内新建文件bsp_timer.c/h
(注意:文件前缀命名有利于区分文件功能,bsp是板级支持包,(board support package)是介于主板硬件和操作系统之间的一层,主要目的是为了支持操作系统,使之能够更好的运行于硬件主板,建议养成良好的命名习惯)
编写以下代码:

bsp_timer.c

#include "bsp_timer.h"

#define TIMx TIM7				//宏定义方便替换定时器

volatile uint32_t SystemTimerCnt;		//定义计数变量,使用volatile修饰,类似uwTick

/**
	* @brief  初始化定时器
	* @retval None
	*/
void bsp_Timer_Init(void)
{
	LL_TIM_EnableIT_UPDATE(TIMx);			//使能定时器向上计数
	LL_TIM_ClearFlag_UPDATE(TIMx);			//清除向上计数溢出标志位
	LL_TIM_EnableCounter(TIMx);				//使能定时器开始计数
}


/**
	* @brief  获取定时器时间
	* @param  None
	* @retval 实时时间
	*/
uint32_t Get_SystemTimer(void)
{
	return TIMx->CNT + SystemTimerCnt * 0xffff;
}

/**
	* @brief  更新定时器时间
	* @not    将此函数加入定时器中断服务函数中
	* @param  None
	* @retval None
	*/
void Update_SystemTick(void)
{
	if(LL_TIM_IsActiveFlag_UPDATE(TIMx) == SET)		//判断定时器是否溢出
	{
		LL_TIM_ClearFlag_UPDATE(TIMx);				//清除向上计数溢出标志位
		SystemTimerCnt++;							//计数
	}
}

/**
* @brief  微秒级软件堵塞延时
* @param  cnt : 延时时数
* @retval None
*/
void delay_us_nos(uint32_t cnt)
{
	uint32_t temp = cnt  + microsecond();

	while(temp >= microsecond());
}

/**
* @brief  毫秒级软件堵塞延时
* @param  cnt : 延时时数
* @retval None
*/
void delay_ms_nos(uint32_t cnt)
{
	uint32_t temp = cnt * 1000 + microsecond();
	while(temp >= microsecond());
}

bsp_timer.h

#ifndef  __BSP_TIMER_H
#define  __BSP_TIMER_H
#ifdef  __cplusplus
extern "C"{
#endif
#include "main.h"

#define microsecond()    Get_SystemTimer()

void bsp_Timer_Init(void);
void Update_SystemTick(void);
uint32_t Get_SystemTimer(void);
void delay_ms_nos(uint32_t cnt);
void delay_us_nos(uint32_t cnt);


#ifdef  __cplusplus
}
#endif
#endif 


完成之后按如下步骤添加头文件路径和C文件
在这里插入图片描述
在这里插入图片描述
将定时器更新函数加入中断服务函数
在这里插入图片描述
可以看到CubeMX生成的stm32f4xx_it.c文件将内核部分的中断和外设中断分隔开来
在这里插入图片描述
将头文件引入后,初始化定时器,闪个灯
在这里插入图片描述

Debug中也可以看到变量在递增
在这里插入图片描述

最后回填个坑,HAL库怎么用?

初始化定时器

HAL_TIM_Base_Start_IT(&htim7);

在中断回调函数里执行相关操作
回调函数的好处就是在任何地方都可以写,不用全局变量满天飞,但HAL库封装的回调效率太低,建议不使用,强迫症的朋友可以在CubeMX设置关掉

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM7)
	{
		//此处省略操作部分...........
	}
}

其它逻辑参考上面代码操作,HAL库封装的较为高级,很多操作都代劳了,对新手比较友好。但玩到后面要往细了写,还得用寄存器操作语句,因为HAL不会把所有操作都能封装,人是活的,得动脑[Doge]

本人水平有限,如有错误,欢迎批评指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值