一、系统滴答定时器介绍

【信盈达】- 关于STM32如何使用系统滴答定时器实现精准延时_哔哩哔哩_bilibili

458b52f3-fa96-459d-a128-c80c1793eeb5

SysTickARM Cortex-M 处理器内建的一个定时器,通常用于生成定时中断,用于实现操作系统的时间片轮转调度、延时功能或定期的定时任务。SysTick 定时器是一个 24 位递减计数器,通常用来生成周期性的中断,以便进行周期性任务的调度。

SysTick 定时器的工作原理:

  1. 24 位计数器
    • SysTick 定时器的核心是一个 24 (16,777,216)位的递减计数器。计数器从一个预定值递减到 0。当计数器到达 0 时,会触发一个中断。
    • 计数器可以通过设置其初始值来指定时间间隔。
  2. 中断控制
    • SysTick 定时器生成的中断可以用来进行定时任务的执行,例如在 RTOS 中,SysTick 用来生成时间片,以进行任务调度。
    • 也可以用于延时函数(如延时 1 毫秒、10 毫秒等),控制时间的流逝。
  3. 计时精度
    • SysTick 的精度通常取决于系统时钟频率。常见的情况下,系统时钟频率为 72 MHz 或 48 MHz,因此 SysTick 的中断周期可以非常精确。

image-20250302151454173

SysTick_Type结构体

image-20250226144449751

  1. CTRL(SysTick 控制与状态寄存器):

    • 偏移量: 0x000
    • 类型: __IOM uint32_t(读写寄存器)
    • 描述: 该寄存器用于控制和查看 SysTick 定时器的状态。通过设置不同的控制位,能够启动、停止定时器,配置中断等。

    常见控制位:

    • ENABLE (位 0): 启动或停止 SysTick 定时器。
    • TICKINT (位 1): 启用或禁用 SysTick 溢出中断。
    • CLKSOURCE (位 2): 选择 SysTick 定时器的时钟源(HCLK 或外部时钟)。
    • COUNTFLAG (位 16): 当计数器溢出时,设置为 1。

    image-20250302151934500

  2. LOAD(SysTick 重载值寄存器):

    • 偏移量: 0x004
    • 类型: __IOM uint32_t(读写寄存器)
    • 描述: 该寄存器用于设置定时器的重载值。SysTick 定时器每当计数器减至零时,会根据 LOAD 寄存器的值重新加载并开始新的计数。

    用途: 通过设置该寄存器的值来控制定时器的溢出周期。例如,如果你想让定时器每 1 毫秒溢出一次,你可以设置该寄存器的值为 SystemCoreClock / 1000

image-20250302151952720

  1. VAL(SysTick 当前值寄存器):

    • 偏移量: 0x008
    • 类型: __IOM uint32_t(读写寄存器)
    • 描述: 该寄存器保存当前 SysTick 定时器的计数值。每当计数器从 LOAD 的值开始计数,直到它到达零时,VAL 会自动重载为 LOAD 的值,并且会触发中断(如果启用了中断)。

    用途: 读取此寄存器可以获取当前定时器的剩余计数值。它可用于计算经过的时间或检查定时器的状态。

  2. CALIB(SysTick 校准寄存器):

    • 偏移量: 0x00C
    • 类型: __IM uint32_t(只读寄存器)
    • 描述: 该寄存器提供关于系统时钟的校准信息,包括 SysTick 定时器的校准值。它通常用于获取定时器的精度和所使用的时钟的频率。

    用途: 这个寄存器通常用于调试和校准目的,帮助开发者了解系统时钟的频率和定时器的精度。

二、系统滴答定时器用于延时函数

1.1delay_init()

1
2
3
4
5
void delay_init(void)
{
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); //设置 SysTick 时钟源为 HCLK,即系统时钟。
HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq));//配置 SysTick 定时器的重载值,使其每 1 毫秒触发一次中断。
}

1.2 delay_us()

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "delay.h"

#define SYS_CLK 100 // 100次计数/us 100MHZ

#define OS_SUPPORT 0 // 是否可以使用操作系统相关的功能

#if OS_SUPPORT
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=SysTick->LOAD; // 当前定时器的重载值寄存器的值
ticks=nus*SYS_CLK; // 计算设定的延时需要计数多少次
delay_osschedlock(); // 锁定调度
told=SysTick->VAL; // 开始延时时的计数值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told) tcnt+=told-tnow; // 将两差值累加进tcnt(计数器没溢出)
else tcnt+=reload-tnow+told; // 将两差值累加进tcnt(计数器溢出)
told=tnow; // 更新told
if(tcnt>=ticks) break; // 达到预定的时钟周期数时退出
}
};
delay_osschedunlock(); // 释放调度锁
}
#else
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=SysTick->LOAD; // 当前定时器的重载值寄存器的值
tcnt = 0;
ticks=nus*SYS_CLK; // 计算设定的延时需要计数多少次
told=SysTick->VAL; // 开始延时时的计数值 100 000 000/s 100 000/ms 100/us
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told) tcnt+=told-tnow; // 将两差值累加进tcnt(计数器没溢出)
else tcnt+=reload-tnow+told; // 将两差值累加进tcnt(计数器溢出)
told=tnow; // 更新told
if(tcnt>=ticks) break; // 达到预定的时钟周期数时退出
}
}
}
#endif


1.3 delay_ms()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#if OS_SUPPORT 						    								   
void delay_ms(u16 nms)
{
if(delay_osrunning&&delay_osintnesting==0)
{
if(nms>=fac_ms)
{
delay_ostimedly(nms/fac_ms);
}
nms%=fac_ms;
}
delay_us((u32)(nms*1000));
}
#else
void delay_ms(uint32_t nms)
{
while(nms--)
delay_us(1000);
}
#endif

三、HAL_Delay()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__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)
{
}
}

注意:第9行默认将延时增加了1ms,weak函数可以根据自己的需求更改