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定時器。