滴答定时器
# 系统滴答定时器
# 什么是滴答定时器
SysTick 是一个 24 位定时器,属于 Cortex内核中的一个外设,类似 NVIC。用于提供时间基准,多为操作系统所使用,常用于对时间要求严格的情况。SysTick 的输入源可以是 HCLK 或者 HCLK 分频8倍(好像这是一个bug,时钟源仅仅只是 HCLK 而已)
由于是 24 位,所以滴答定时器每次最多可以提供 STK_VAL
(SysTick current value register) 中,只能向下计数,也就是倒计数。每接收到一个 HCLK 脉冲,STK_VAL
的值就会向下减 1,当减到 0 时,硬件会自动将重装载寄存器 STK_LOAD
(可以设定,跟 STK_VAL
初始值相等) 中保存的数值加载到 STK_VAL
,使其重新计数。并且,系统滴答定时器就产生一次中断,以此循环往复,只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息
# HAL库的配置
# 初始化与设定频率
HAL 库对于滴答定时器的处理与更新其实非常巧妙,具体怎样巧妙请接着看:
在系统初始化函数 HAL_Init()
中,系统在时钟为初始化前调用 HAL_InitTick()
首先初始化了滴答定时器,令其为 1ms 产生一个中断,且设置中断优先级为最高
而在系统时钟初始化后,也就是在 HAL_RCC_ClockConfig()
的最后,重新用新的时钟参数初始化了滴答定时器,令其中断产生的频率始终维持在 1ms 产生一次
在使用函数 HAL_SetTickFreq()
对滴答定时器中断频率进行修改时也是通过更改 HAL_InitTick()
中使用的全局变量 uwTickFreq
后再次执行 HAL_InitTick()
实现的
# 设定函数
前面提到的系统初始化与时钟初始化后调用的函数 HAL_InitTick()
就是初始化滴答定时器的函数,它能够使中断频率始终维持在 1ms 一次,我们来看一下它的定义:
/**
* @brief This function configures the source of the time base.
* 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 reconfigured by HAL_RCC_ClockConfig().
* @note In the default implementation, SysTick timer is the source of time base.
* It is used to generate interrupts at regular time intervals.
* Care must be taken if HAL_Delay() is called from a peripheral ISR process,
* The SysTick interrupt must have higher priority (numerically lower)
* than the peripheral interrupt. Otherwise the caller ISR process will be blocked.
* The function is declared as __weak to be overwritten in case of other
* implementation in user file.
* @param TickPriority Tick interrupt priority.
* @retval HAL status
*/
__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;
}
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
27
28
29
30
31
32
33
34
35
36
37
38
第一个 if
语句的参数中的函数 HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq))
就是关键所在,其中的参数 SystemCoreClock
是关键中的关键,我们来看一下库对其定义:
/* This variable is updated in three ways:
1) by calling CMSIS function SystemCoreClockUpdate()
2) by calling HAL API function HAL_RCC_GetHCLKFreq()
3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency
Note: If you use this function to configure the system clock; then there
is no need to call the 2 first functions listed above, since SystemCoreClock
variable is updated automatically.
*/
uint32_t SystemCoreClock = 16000000;
2
3
4
5
6
7
8
9
在注释中写到的三种出发条件其中一种发生时,这个变量的值将会被更新,而每次我们更新时钟频率后,均会触发条件 3 致使这个值更新,更新后的值一定为最新的 HCLK 时钟频率,这个更新函数 SystemCoreClockUpdate()
定义在 system_stm32f1xx.c
中
而函数 HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq))
设定了滴答定时器两次中断之间的节拍数,也就相当于 STK_LOAD
寄存器中的参数。其中 uwTickFreq
默认值为 1 (代表 1KHz,为了配合 uwTickFreq
算出中断频率设为 1)
例如 HCLK 频率为 72MHz,SystemCoreClock
括号内的值计算结果为
# 正点原子的delay.c
目前对于此文件研究较浅(由于还没有在开发中应用操作系统)
# delay_init
这个函数 SYSTICK
参数是系统时钟的频率 nMHz 中的数值 n,没有支持系统时有效语句只有:
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); //SysTick频率为 HCLK
fac_us = SYSCLK; //不论是否使用 OS,fac_us都需要使用
2
第一个语句就是将滴答定时器的时钟源设置为 HCLK,但是其实默认也就是 HCLK
第二个语句中:fac_us
这个参数实际上是在后面用到的乘以需要的延迟的微秒数的乘数,最终才会得到延迟相应的微秒数所需要的节拍数
# delay_us
这是一个采用了时钟摘取法的微秒级的延时函数,实际上思路就使刚刚提到的使用滴答定时器产生节拍的频率数 fac_us
乘以需要延迟的微秒数 tick
得出一共需要的节拍数。函数内的计数器向下计数,技术结束后结束。
为什么 fac_us
的值就是前面提到的 SYSTICK
,它由于是 nMHz 中的 n 所以可以视为一个“微秒频率 “。
这样就达到了毫秒级的延迟!