(6条消息) 使用SysTick作为HAL的基础时钟
HAL需要设置一个定时器作为基础时钟。基础时钟通过定时溢出中断产生嘀嗒信号,嘀嗒信号的缺省频率是1000Hz,也就是基础时钟的定时周期是1ms。基础时钟主要用于实现延时函数HAL_Delay(),或在一些有超时(timeout)设置的函数里确定延时。
在不使用FreeRTOS的时候,STM32CubeMX里默认地将基础时钟源设置为SysTick定时器,如图1所示。SysTick是Cortex-M内核自带的一个24位的定时器,将SysTick作为HAL的基础时钟后,在NVIC中会自动启用SysTick的中断,并且优先级设置为最高,如图2所示。可以修改SysTick的中断优先级,但是不能在图2中禁用SysTick中断。
图1 在SYS中设置HAL的基础时钟源为SysTick
图2 使用SysTick作为HAL基础时钟源的NVIC设置
1. 基础时钟的初始化
在STM32CubeMX生成的初始化代码中,HAL_Init()是在main()函数中执行的第一行代码。HAL_Init()对SysTick定时器进行设置,使其定时中断周期为1ms。函数HAL_Init()的代码如下。HAL_StatusTypeDef HAL_Init(void){/* Configure Flash prefetch, Instruction cache, Data cache */#if (INSTRUCTION_CACHE_ENABLE != 0U)__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();#endif /* INSTRUCTION_CACHE_ENABLE */#if (DATA_CACHE_ENABLE != 0U)__HAL_FLASH_DATA_CACHE_ENABLE();#endif /* DATA_CACHE_ENABLE */#if (PREFETCH_ENABLE != 0U)__HAL_FLASH_PREFETCH_BUFFER_ENABLE();#endif /* PREFETCH_ENABLE *//* 设置中断优先级分组策略 */HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);/* 使用SysTick作为基础时钟,配置嘀嗒周期为1ms */HAL_InitTick(TICK_INT_PRIORITY);HAL_MspInit(); /* 底层硬件初始化*/return HAL_OK;}
其中,执行的HAL_InitTick(TICK_INT_PRIORITY)是对SysTick定时器进行定时周期和中断的设置,HAL_InitTick()是在文件stm32f4xx_hal.c中用__weak修饰符定义的弱函数,代码如下:__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority){/* 配置SysTick定时周期为1ms */if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U){return HAL_ERROR;}/* 配置SysTick定时器的中断优先级 */if (TickPriority < (1UL << __NVIC_PRIO_BITS)){HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);uwTickPrio = TickPriority;}else{return HAL_ERROR;}return HAL_OK;}
作为弱函数,HAL_InitTick()可以被重新实现。在使用SysTick作为基础时钟时,使用的就是文件stm32f4xx_hal.c中的HAL_InitTick()。
函数HAL_Init()中最后调用的函数HAL_MspInit()也是一个弱函数,在HAL驱动中就是个空函数。在STM32CubeMX生成的初始化代码中,在文件stm32f4xx_hal_msp.c中重新实现了这个函数,功能就是启用了RCC的时钟信号,并设置中断优先级分组策略,也就是图2中用户设置的优先级分组策略。void HAL_MspInit(void){__HAL_RCC_SYSCFG_CLK_ENABLE();__HAL_RCC_PWR_CLK_ENABLE();HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);}
所以,执行函数HAL_Init()后,就设置了SysTick定时器的定时周期和中断优先级。默认的SysTick定时周期为1ms,产生的嘀嗒信号频率为1000Hz。
2. 基础时钟的中断处理
在文件stm32f4xx_it.c中自动生成了SysTick定时器中断的ISR函数,其代码如下:void SysTick_Handler(void){HAL_IncTick();}
在SysTick定时器的定时溢出中断里就执行了函数HAL_IncTick(),这是在文件stm32f4xx_hal.c中实现的函数,函数的代码如下:__weak void HAL_IncTick(void){uwTick += uwTickFreq;}
它的功能就是使得全局变量uwTick递增,这个变量就是嘀嗒信号的计数值。当嘀嗒信号频率为1000Hz时,uwTickFreq的值为1;当嘀嗒信号频率为100Hz时,uwTickFreq的值为10。
在文件stm32f4xx_hal.c中还定义了操作滴答定时器的两个函数,用于暂停和恢复滴答定时器,都是用__weak定义的弱函数,代码如下:__weak void HAL_SuspendTick(void){ /* 禁止 SysTick 中断 */SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;}__weak void HAL_ResumeTick(void){ /* 开启 SysTick 中断 */SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;}
常用的延时函数HAL_Delay()就是利用嘀嗒信号来实现的,函数HAL_Delay()的代码如下:__weak void HAL_Delay(uint32_t Delay){uint32_t tickstart = HAL_GetTick();//获取嘀嗒信号当前计数值uint32_t wait = Delay;if (wait < HAL_MAX_DELAY) //最少延时1ms{wait += (uint32_t)(uwTickFreq); // uwTickFreq默认值为1}while((HAL_GetTick() - tickstart) < wait){}}
程序中调用的函数HAL_GetTick()的功能就是返回全局变量uwTick,也就是嘀嗒信号的当前计数值。函数HAL_Delay()的输入参数Delay是以毫秒为单位的延时时间。延时的原理就是先读取嘀嗒信号的当前计数值保存到变量tickstart,计算在此基础上延时所需要的计数值差量wait,然后在while循环中不断用函数HAL_GetTick()读取滴答信号当前技术值,计算相对于tickstart的差量,当差量超过wait时就达到了延时时间。
在HAL库中使用SysTick作为基础定时器时,其作用就是用于产生嘀嗒信号计数值,然后用于延时计算。如果不需要用到延时计算,停掉SysTick定时器对系统运行是没有什么影响的。例如,在使用低功耗设计时,为了使系统进入睡眠模式后不被SysTick的中断唤醒,就停掉了SysTick定时器。