13. SysTick—系统定时器¶
本章参考资料《dm00046982-stm32-cortexm4-mcus-and-mpus-programming-manual-stmicroelectronics》编程手册 4.5章节 SysTick timer (STK)章节。
因为SysTick是属于Cortex-M内核的外设,有关寄存器的定义和部分库函数都在core_cm4.h这个头文件中实现。 所以学习SysTick的时候可以参考这两个资料,一个是关于Systick外设说明文档,一个是外设操作接口源码。
13.1. SysTick简介¶
SysTick—系统定时器是属于Cortex-M内核中的一个外设,内嵌在NVIC中。 系统定时器是一个24bit的向下递减的计数器,计数器每计数一次的时间为1/SYSCLK, 一般我们设置系统时钟SYSCLK等于209M。当重装载数值寄存器的值递减到0的时候, 系统定时器就产生一次中断,以此循环往复。
因为SysTick是属于Cortex-M内核的外设,所有基于Cortex-M内核的单片机都具有这个系统定时器, 使得软件在Cortex-M单片机中可以很容易的移植。系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。 HAL库中也使用到了Systick定时器,用于软件延时。
13.2. SysTick寄存器介绍¶
SysTick—系统定时有4个寄存器,简要介绍如下。在使用SysTick产生定时的时候, 只需要配置前三个寄存器,最后一个校准寄存器不需要使用。
表 12‑1 SysTick寄存器汇总
寄存器名称 |
寄存器描述 |
CTRL |
SysTick控制及状态寄存器 |
LOAD |
SysTick重装载数值寄存器 |
VAL |
SysTick当前数值寄存器 |
CALIB |
SysTick校准数值寄存器 |
表 12‑2 SysTick控制及状态寄存器
位段 |
名称 |
类型 |
复位值 |
描述 |
---|---|---|---|---|
16 |
COUNTFLAG |
R/W |
0 |
如果在上次读取本寄存器 后, SysTick 已经计到 了 0,则该位为 1。 |
2 |
CLKSOURCE |
R/W |
0 |
时钟源选择位,0=外部 时钟,1=处理器时钟A HB |
1 |
TICKINT |
R/W |
0 |
1=SysTick倒数 计数到 0时产生 SysTick异常请 求,0=数到 0 时无动作。也可以通过读 取COUNTFLAG标 志位来确定计数器是否递 减到0 |
0 |
ENABLE |
R/W |
0 |
SysTick 定时器的使能位 |
表 12‑3 SysTick 重装载数值寄存器
位段 |
名称 |
类型 |
复位值 |
描述 |
23:0 |
RELOAD |
R/W |
0 |
当倒数计数至零时,将被重装载的值 |
表 12‑4 SysTick当前数值寄存器
位段 |
名称 |
类型 |
复位值 |
描述 |
---|---|---|---|---|
23:0 |
CURRENT |
R/W |
0 |
读取时返回当前倒计数的 值,写它则使之清零,同 时还会清除在SysTi ck控制及状态寄存器中 的COUNTFLAG 标志 |
表 12‑5 SysTick校准数值寄存器
位段 |
名称 |
类型 |
复位值 |
描述 |
---|---|---|---|---|
31 |
NOREF |
R |
0 |
指示是否有参考时钟提供 给处理器 0:提供参考时钟 1:不提供参考时钟 如果器件不提供参考时钟 ,SYST_CSR.C LKSOURCE标志位 为1,不可改写。 |
30 |
SKEW |
R |
1 |
S指示TENMS的值是 否精确 0:TENMS是精确值 1:TENMS不是精确 值或者不提供 不精确的TENMS值可 以影响作为软件实时时钟 节拍器的适用性。 |
23:0 |
TENMS |
R |
0 |
重新加载 10ms (100Hz) 计时的值, 受系统时钟偏差的错误。 如果值读取为零, 校准值未知。 |
系统定时器的校准数值寄存器在定时实验中不需要用到。有关各个位的描述这里引用手册里面的英文版本, 比较晦涩难懂,暂时不知道这个寄存器用来干什么。有研究过的朋友可以交流,起个抛砖引玉的作用。
13.3. STM32CubeIDE的SysTick初始化¶
在之前的实验中我们已经使用过了Systick定时器,那就是HAL_Dealy函数,HAL_Dealy的 延时机制就是依据Systick实现的,在STM32CubeIDE创建的工程中,默认已经为我们开启了 Systick定时器。接下来我们将分析整个Systick的初始化过程。
13.3.1. 代码分析¶
我们将根据程序上电的执行顺序一步一步解析Systick是如何被初始化的, main—> HAL_Init—> HAL_InitTick—> HAL_SYSTICK_Config—> SysTick_Config—> HAL_NVIC_SetPriority
13.3.1.1. HAL_Init函数¶
HAL_Init函数是main执行的第一个函数,详细代码如 HAL_Init函数 所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | HAL_StatusTypeDef HAL_Init(void)
{
#if defined (CORE_CM4)
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
#endif
SystemCoreClock = HAL_RCC_GetSystemCoreClockFreq();
if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
{
return HAL_ERROR;
}
HAL_MspInit();
return HAL_OK;
}
|
第4行,设置中断优先级分组。中断优先级分组设置在整个代码中只需要设置一次即可, 在Systick中同样需要设置中断有优先级。
第7行,获取系统的时钟,我们知道Systick定时器的初始化和系统时钟有着莫大的关系, 需要根据系统时钟设置初始化Systick定时器。
第9行,调用HAL_InitTick初始化Systick定时器。
13.3.1.2. HAL_InitTick函数¶
HAL_InitTick函数传入的参数是TICK_INT_PRIORITY(值为0),用来设置中断的优先级,设置 为0表示设置为最高的中断优先级。HAL_InitTick函数详细代码如 HAL_InitTick函数 所示(省略与A7代码相关内容)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
#if defined (CORE_CM4)
if ((uint32_t)uwTickFreq == 0U)
{
return HAL_ERROR;
}
if (HAL_SYSTICK_Config(SystemCoreClock /(1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
#endif /* CORE_CM4 */
return HAL_OK;
}
|
第4-7行,uwTickFreq变量用来表示设置Systick每秒钟中断的频率,在stm32mp1xx_hal.c中声明。当 设置每秒的中断频率为0显然是错的直接返回。
第9-12行,配置Systick定时器,这个函数只是对SysTick_Config函数进行了分装, SysTick属于cortex-m内核的外设,STM32MP157有关的寄存器定义和库函数都在内核相关的库文件core_cm4.h中。 即SysTick_Config函数定义在core_cm4.h中,稍后进行讲解。
第14-22行,设置中断优先级。首先对我们的传入的参数进行了出错判断。当设置的中断优先级大于16时,设置的中断 优先级是错误的。
当我们重新配置rcc时钟时,SystemCoreClock的值会重新更新,当调用HAL_RCC_OscConfig函数时, HAL_InitTick函数将会被调用重新配置Systick定时器。同时HAL_InitTick使用__weak作为修饰, 如果有必要我们可以重新编写HAL_InitTick函数。
13.3.1.3. SysTick_Config函数¶
HAL_SYSTICK_Config的内容很简单,就是调用SysTick_Config。接下来我们详细介绍下SysTick_Config函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
// 不可能的重装载值,超出范围
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) {
return (1UL);
}
// 设置重装载寄存器
SysTick->LOAD = (uint32_t)(ticks - 1UL);
// 设置中断优先级
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
// 设置当前数值寄存器
SysTick->VAL = 0UL;
// 设置系统定时器的时钟源为AHBCLK
// 使能系统定时器中断
// 使能定时器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0UL);
}
|
形参ticks用来设置重装载寄存器的值,最大不能超过重装载寄存器的值224, 当重装载寄存器的值递减到0的时候产生中断,然后重装载寄存器的值又重新装载往下递减计数, 以此循环往复。紧随其后设置好中断优先级,最后配置系统定时器的时钟为AHBCLK, 使能定时器和定时器中断,这样系统定时器就配置好了。
SysTick_Config()库函数主要配置了SysTick中的三个寄存器:LOAD、VAL和CTRL,有关具体的部分看代码注释即可。
在SysTick_Config()库中也设置中断优先级,这里是core_cm4.c文件的内容,为了库的完整性在正常的情况下我们不会去修改 任何库提供的函数内容,因此我们需要在此外的内容上额外设置一个可设置的Systick的中断优先级。
13.3.1.4. SysTick定时时间的计算¶
SysTick定时器的计数器是向下递减计数的,计数一次的时间为1/SystemCoreClock, 当重装载寄存器中的值VALUE的值减到0的时候,产生中断。可知中断一次的时间为 VALUE /SystemCoreClock,当我们将SystemCoreClock /(1000U / uwTickFreq)作为参数传递给SysTick_Config函数时, 可得到中断的时间为 uwTickFreq/1000,hal库中uwTickFreq的值设置为1,即每1ms将产生一次中断。
13.3.1.5. SysTick中断服务函数¶
当Systick产生中断之后将执行SysTick_Handler中断服务函数。
1 2 3 4 | void SysTick_Handler(void)
{
HAL_IncTick();
}
|
在Systick中断函数中调用了HAL_IncTick函数,HAL_IncTick函数 内容如下
1 2 3 4 | __weak void HAL_IncTick(void)
{
uwTick += (uint32_t)uwTickFreq;
}
|
HAL_IncTick函数中只对uwTick进行加法操作,uwTick是一个全局变量。可理解为用于计数系统的运行时间,单位为ms。 HAL_IncTick函数同样也是个使用__weak弱定义的函数,当我们需要使用Systick实现其他功能时可重新编写这个函数。
13.3.1.6. HAL_Delay函数的实现¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 __weak void HAL_Delay(uint32_t Delay) { uint32_t tickstart = HAL_GetTick(); uint32_t wait = Delay; if (wait < HAL_MAX_DELAY) { wait += (uint32_t)(uwTickFreq); } while ((HAL_GetTick() - tickstart) < wait) { } }
第3行,使用HAL_GetTick函数用来获取uwTick变量的值。
第6-9行,添加频率以保证最小的等待时间,当我们的uwTickFreq设置为比较大时,如HAL_TICK_FREQ_100HZ(具体的数值为10), 意味着每10ms将产生一次Systick中断,而此时传入的参数Delay如果小于10,保证满足最小的等待时间。
第12行,当前系统运行时间与函数刚进来的是的运行时间相差超过wait时,则跳出while循环。
在这里我们可以发现,HAL_Delay函数的延时并不是那么准确的,它的准确度与Systick的中断频率息息相关, 最大的误差为±一个Systick中断间隔。
同样HAL_Delay函数的定义是以__weak修饰的函数, 在有必要时可重新编写整个HAL_Delay函数。