HAL库中使用“SysTick定时器”用作精确延时和有无RTOS系统有关。HAL默认是使用SysTick定时器作为时基定时器,在没有RTOS系统支持时,我们可以使用SysTick定时器用作精确延时。但是带有ROTS系统,我一样可以使用SysTick定时器用作精确延时,此时HAL库要使用TIM1作为时基定时器。
1、不带RTOS系统的HAL库延时程序
不带RTOS系统的HAL库,无需使用其它定时器作为时基定时器。但有的人喜欢用TIM1作为时基定时器,而将SysTick定时器用作精确延时。这种写法,有点浪费硬件资源。
#include "delay.h"
static uint8_t fac_us=0; //us延时倍乘数
static uint16_t fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
void My_delay_us(__IO uint32_t nCount);
//systick中断服务函数,1ms中断一次
//在调用HAL_Init()时,SysTick定时器初始化为使能中断
void SysTick_Handler(void)
{
}
//函数功能:delay函数初始化
//在调用HAL_Init()时,已经对SysTick定时器初始化了,因此这里无需初始化
void delay_init(void)
{
uint32_t reload;
fac_us=SystemCoreClock/1000000;
//相当于将170MHz经过1000000分频用作SysTick的输入时钟,即周期为1us
reload=SystemCoreClock/1000000;
//相当于将170MHz经过1000000分频用作SysTick的输入时钟,即周期为1us
reload*=1000;
//SysTick溢出时间为1000*1us=1ms
//reload为24位寄存器,最大值:16777216,在170MHz下,约合0.098s左右
fac_ms=1;
}
//函数功能:延时nus微妙
//nus:要延时的us数.
//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=SysTick->LOAD;
//读取SysTick的LOAD寄存器的值
ticks=nus*fac_us; //计算需要的节拍数
told=SysTick->VAL;//刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;//读SYSTICK计数器值
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //SysTick定时器是一个递减的计数器.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
//函数功能:延时nms毫秒
//nms:要延时的ms数
//nms:0~65535
void delay_ms(uint32_t nms)
{
delay_us((uint32_t)(nms*1000));//普通方式延时
}
2、带FrssRTOS系统的HAL库延时程序
在使用HAL库时,若又使用了FrssRTOS系统,因为他们都使用了SysTick定时器,所以,我们用TIM1作为时基定时器给HAL库使用,而将SysTick定时器让给FreeRTOS系统和用作程序精确延时。
#include "delay.h"
#include "FreeRTOS.h"
#include "task.h"
static uint8_t fac_us=0; //us延时倍乘数
static uint16_t fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
extern void xPortSysTickHandler(void);//xPortSysTickHandler()在port.c中
void My_delay_us(__IO uint32_t nCount);
//systick中断服务函数,1ms中断一次
//在FreeRTOSConfig.h中,有一个宏定义”#define xPortSysTickHandler SysTick_Handler“
//xPortSysTickHandler()的函数体位于port.c中
//我们在这里实现SysTick_Handler(),那个宏定义就被我屏蔽了
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
xPortSysTickHandler();
//若带FreeRTOS系统,则xPortSysTickHandler()在port.c中
}
}
//函数功能:SysTick定时器初始化
//SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8
//这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!
//SYSCLK:系统时钟频率
//在port.c中有一个vPortSetupTimerInterrupt(),用来配置SysTick定时器
//由于vPortSetupTimerInterrupt()没有被调用,所以不会被执行
//我们使用delay_init()来替换vPortSetupTimerInterrupt(),使程序具有可读性
void delay_init(void)
{
uint32_t reload;
fac_us=SystemCoreClock/1000000;
//相当于将170MHz经过1000000分频用作SysTick的输入时钟,即周期为1us
reload=SystemCoreClock/1000000;
//相当于将170MHz经过1000000分频用作SysTick的输入时钟,即周期为1us
reload*=1000000/configTICK_RATE_HZ;
//在“FreeRTOSConfig.h”中configTICK_RATE_HZ为1000
//SysTick溢出时间为1000*1us=1ms
//reload为24位寄存器,最大值:16777216,在170MHz下,约合0.098s左右
fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位
SysTick->VAL = 0UL; //设置计数器值为0,Load the SysTick Counter Value
HAL_SYSTICK_Config(reload);//配置SysTick定时器
// SysTick->LOAD = reload;
//设置计数reload个输入时钟,则溢出一次
// SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
// SysTick_CTRL_TICKINT_Msk |
// SysTick_CTRL_ENABLE_Msk;
//SysTick_CTRL_CLKSOURCE_Msk表示设置STK_CTRL寄存器bit2(CLKSOURCE),CLKSOURCE=1使用AHB时钟
//SysTick_CTRL_TICKINT_Msk表示使能SYSTICK中断
//SysTick_CTRL_ENABLE_Msk表示使能SysTick计数
//Enable SysTick IRQ and SysTick Timer
}
//函数功能:延时nus
//nus:要延时的us数.
//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=SysTick->LOAD;
//读取SysTick的LOAD寄存器的值
ticks=nus*fac_us; //计算需要的节拍数
told=SysTick->VAL;//刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;//读SYSTICK计数器值
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(uint32_t nms)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
{
vTaskDelay(nms/fac_ms); //FreeRTOS延时
}
nms%=fac_ms;
}
delay_us((uint32_t)(nms*1000)); //普通方式延时
}
HAL库使用TIM1作为时基定时器程序:
//stm32g4xx_hal_timebase_TIM.c
//HAL time base based on the hardware TIM.
#include "stm32g4xx_hal.h"
#include "stm32g4xx_hal_tim.h"
TIM_HandleTypeDef HTIM1;
/**
* @brief This function configures the TIM1 as a time base source.
* The time source is configured to have 1ms time base with a dedicated
* Tick interrupt priority.
* @note This function is called automatically at the beginning of program after
* reset by HAL_Init() or at any time when clock is configured, by HAL_RCC_ClockConfig().
* @param TickPriority: Tick interrupt priority.
* @retval HAL status
*/
//HAL_InitTick()供HAL_Init()调用
//函数功能:将TIM1配置为向上计数,1ms中断一次
//TickPriority=TICK_INT_PRIORITY,滴答中断优先级为TICK_INT_PRIORITY=0
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
RCC_ClkInitTypeDef clkconfig;
uint32_t uwTimclock = 0;
uint32_t uwPrescalerValue = 0;
uint32_t pFLatency;
HAL_StatusTypeDef status;
/* Enable TIM1 clock */
__HAL_RCC_TIM1_CLK_ENABLE();
/* Get clock configuration */
HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);
/* Compute TIM1 clock */
uwTimclock = HAL_RCC_GetPCLK2Freq();
//读取PCLK2的时钟频率,Return the PCLK2 frequency
//若PCLK2的分频器值为1,则和SystemCoreClock的值相等
/* Compute the prescaler value to have TIM1 counter clock equal to 1MHz */
uwPrescalerValue = (uint32_t) ((uwTimclock / 1000000U) - 1U);
/* Initialize TIM1 */
HTIM1.Instance = TIM1;
/* Initialize TIMx peripheral as follow:
+ Period = [(TIM1CLK/1000) - 1]. to have a (1/1000) s time base.
+ Prescaler = (uwTimclock/1000000 - 1) to have a 1MHz counter clock.
+ ClockDivision = 0
+ Counter direction = Up
*/
HTIM1.Init.Period = (1000000U / 1000U) - 1U;
//定时器周期999
HTIM1.Init.Prescaler = uwPrescalerValue;
//设置TIM1预分频器为uwPrescalerValue
HTIM1.Init.ClockDivision = 0;
//设置时钟分频系数,TIM1_CR1中的CKD[9:8]=00b,tDTS=ttim_ker_ck;
//溢出时间为(999+1)*1*170/170000000/1
HTIM1.Init.CounterMode = TIM_COUNTERMODE_UP;
status = HAL_TIM_Base_Init(&HTIM1);
if (status == HAL_OK)
{
/* Start the TIM time Base generation in interrupt mode */
status = HAL_TIM_Base_Start_IT(&HTIM1);
//使能HTIM1更新中断
if (status == HAL_OK)
{
/* Enable the TIM1 global Interrupt */
HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn);//使能TIM1产生中断
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{//若使用“NVIC中断分组4”,STM32G4XX uses 4 Bits for the Priority Levels
//HAL库使用中断优先级组为4
//在“stm32g474xx.h”中定义“__NVIC_PRIO_BITS=4”
//Interrupt and exception vectors
/* Configure the TIM IRQ priority */
HAL_NVIC_SetPriority(TIM1_UP_TIM16_IRQn, TickPriority, 0U);
//设置NVIC中断分组4:4位抢占优先级,0位响应优先级
//选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
//这里设置TIM1中断优先级为TickPriority
uwTickPrio = TickPriority;//记录滴答时钟的中断优先级
}
else
{//若没有使用“NVIC中断分组4”,则设置status
status = HAL_ERROR;
}
}
}
/* Return function status */
return status;
}
/**
* @brief Suspend Tick increment.
* @note Disable the tick increment by disabling TIM1 update interrupt.
* @param None
* @retval None
*/
void HAL_SuspendTick(void)
{
/* Disable TIM1 update Interrupt */
__HAL_TIM_DISABLE_IT(&HTIM1, TIM_IT_UPDATE);//不使能定时器更新中断
}
/**
* @brief Resume Tick increment.
* @note Enable the tick increment by Enabling TIM1 update interrupt.
* @param None
* @retval None
*/
void HAL_ResumeTick(void)
{
/* Enable TIM1 Update interrupt */
__HAL_TIM_ENABLE_IT(&HTIM1, TIM_IT_UPDATE);//使能定时器更新中断
}
/**
* @brief Period elapsed callback in non blocking mode
* @note This function is called when TIM1 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
//HAL_TIM_IRQHandler()调用HAL_TIM_PeriodElapsedCallback(),触发条件是TIM产生“向上溢出中断”
//TIM1_UP_TIM16_IRQHandler()调用了HAL_TIM_IRQHandler()函数
//TIM_DMAPeriodElapsedCplt也调用HAL_TIM_PeriodElapsedCallback()
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
HAL_IncTick();
}
}
/**
* @brief This function handles TIM1 update interrupt and TIM16 global interrupt.
*/
//TIM1更新中断和TIM16全局中断,他们公用一个中断源
void TIM1_UP_TIM16_IRQHandler(void)
{
HAL_TIM_IRQHandler(&HTIM1);
}