1.用systick作为时基
用systick作为时基的时候,HAL_Init中的HAL_InitTick函数会通过HAL_SYSTICK_Config函数对systick中断的时间配置为1ms。也就是说用systick作为时基,就要利用到systick的中断,在SysTick_Handler这个中断中,每进一次中断就加一次uwTick的值。例如F429的频率为180MHz,那么在设置SysTick->LOAD的值的时候,就应该设置为180K,才能让中断为1ms一次。这样说比较抽象,看代码讲解吧。
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
这个就是HAL_Init在执行过程中会执行到的HAL_InitTick函数,需要说明的是,这个是默认选择了systick为时基的,如果我们在用stm32cubemx设置时基为tim的时候,这个弱定义的函数就会被覆盖,重新定义的HAL_InitTick函数是下面第2部分用tim作为时基用到的。
以systick为时基用到的就是上面的函数实现,只看第一个if语句
首先SystemCoreClock在还没有执行SystemClock_Config这个系统时钟配置之前,被默认设置为16M,当然这个16M不用管,因为在执行完SystemClock_Config后,会被赋值为180M。这个180M是在SystemClock_Config函数里面设置的,当然是因为你在配置时钟的时候做了相应的设置,然后经过HAL_RCC_ClockConfig这个函数,在这个函数中对SystemClock_Config做了相应的赋值,才得到的180M。
/* Update the SystemCoreClock global variable */
SystemCoreClock = HAL_RCC_GetSysClockFreq() >> AHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE)>> RCC_CFGR_HPRE_Pos];
/* Configure the source of time base considering new system clocks settings */
HAL_InitTick (uwTickPrio);
return HAL_OK;
上面的语句就是HAL_RCC_ClockConfig函数内对SystemClock_Config赋值的语句,可以看到,在赋值完以后,又调用了HAL_InitTick函数,也就是说又要调用一次HAL_SYSTICK_Config来配置一下systick的中断,上面没讲到怎么配置中断,那就直接看代码,最后HAL_SYSTICK_Config函数会进到下面的函数中,可以看到在赋值重装载寄存器,并且设置了systick中断优先级,最后就是使能中断了,这里重装载寄存器值是180K,因为180MHz周期是1/180us,要是的HAL_Delay是1ms的延时,那就得1/180us * 180k = 1ms,也就是计数值跑完刚好1ms。
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* Reload value impossible */
}
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
2.用tim作为时基
tim作时基的情况,会重新有个文件,比如笔者用的tim1,HAL_InitTick的实现是在stm32f4xx_hal_timbase_tim.c文件中,而一systick为时基的时候,HAL_InitTick是在stm32f4xx_hal.c文件中实现的,而且还是个弱定义的,也就是为了不以systick为时基的时候能去重新定义做的准备。
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
RCC_ClkInitTypeDef clkconfig;
uint32_t uwTimclock = 0U;
uint32_t uwPrescalerValue = 0U;
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 = 2*HAL_RCC_GetPCLK2Freq();
/* 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;
htim1.Init.Prescaler = uwPrescalerValue;
htim1.Init.ClockDivision = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
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);
if (status == HAL_OK)
{
/* Enable the TIM1 global Interrupt */
HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
/* Configure the TIM IRQ priority */
HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
status = HAL_ERROR;
}
}
}
/* Return function status */
return status;
}
这里和以systick的方式看起来不一样,其实还是一样的,其实HAL_InitTick的目的都一样就是为将时基配为1ms然后供给给HAL_Delay来做1ms的延时,那么既然如此,那是不是可以推测出这里tim就是要设置一个1ms进一次中断的配置,进一次tim的中断,就还是将HAL_Delay里面那个用来对比延时时长的全局变量加1,这个很好验证,就是观察一下tim的中断处理函数里面做了什么,HAL库里就直接看tim的回调函数就行了:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM1) {
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
/* USER CODE END Callback 1 */
}
HAL_IncTick从函数名都能看出,这就是在对tick数进行增加
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
也就是这个uwTick数在进一次tim中断后就加1,进一次就是1ms,HAL_Delay里
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
用HAL_GetTick来获取这个值,然后用每次获取的这个值减去刚进入HAL_Delay的时候的那个时间的这个变量的值做减法,就是进了多少次tim的中断,也就是过了多少ms,用这个两个的差值与HAL_Delay传入的参数值(这个参数值就是延时多少ms的数值)做对比,当比它大的时候,结束while循环,也就是延时时间到了,退出函数。
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while((HAL_GetTick() - tickstart) < wait)
{
}
}