【STM32单片机学习】第11章 基础重点—SysTick定时器
2025-11-04 来源:bilibili
本章实验的目的让读者熟悉STM32F103的SysTick定时器,SysTick定时器和NVIC一样,都属于Cortex-M3的内核外设资源。SysTick定时器比较简单,借此机会感受HAL库和寄存器之间调用关系,以及SysTick定时器的中断处理。本章阅读提示:11.1 关于(介绍STM32的SysTick定时器工作方式和寄存器,需要理解)11.2 硬件设计(SysTick定时器不涉及硬件)11.3 软件设计(讲解如何配置SysTick定时器、SysTick定时器的中断函数如何处理,需要理解)11.4 实验效果(展示实验效果,操作即可)

11.1 关于SysTick定时器
SysTick定时器(又名系统滴答定时器)是存在于Cortex-M3的一个定时器,只要是ARM Cotex-M系列内核的MCU都包含这个定时器。使用内核的SysTick定时器来实现延时,可以不占用系统定时器,节约资源。由于SysTick是在CPU核内部实现的,跟MCU外设无关,因此它的代码可以在不同厂家之间移植。
本章将使用系统滴答定时器实现延时函数,注意SysTick用于了HAL库的毫秒级延时函数“HAL_Delay()”,不建议日常使用SysTick去作为其它用途,这里只作为演示。
SysTick定时器是一个24位递减定时器,即计数器可以从最大值224开始,每个时钟周期减1,当减到0时,会产生Systick异常,同时再自动重载定时初值,开始新一轮计数。通过设置这个定时初值,就可以实现得到指定时间。如下图 11.1.1 所示,y为定时器初值,然后随着时间增加,值逐渐减小,直至为0,再重新加载初值,如此往复,x1、x2、x3这些时间段,就是我们需要的延时时间。

图 11.1.1 Systick定时器工作示意图
假设STM32F103工作在72MHz,即72000000Hz,意味着1s时间内,会计数72000000次。那么1ms则计数72000000/1000=72000次。这个72000就可以作为系统滴答定时器的初始值,将这个值写入系统滴答定时器,定时器在每个时钟周期减1,减到0时,就刚好是1ms,同时产生中断通知,再次加载72000如此反复。HAL库提供“HAL_SYSTICK_Config()”函数去设置这个初始值。
系统滴答定时器控制寄存器比较少,整体比较简单,借助本次机会详细分析一下寄存器和HAL之间是调用关系。系统滴答定时器只有四个控制寄存器:STK_CTRL,STK_LOAD,STK_VAL和STK_CALIB。因为系统滴答定时器属于Cotex-M3内核的外设,相关寄存器介绍不在《参考手册》,而在《3_STM32F10xx Cortex-M3编程手册》,后简称《编程手册》。
系统滴答定时器控制和状态寄存器(STK_CTRL)

重点关注Bit[0],用于使能系统滴答定时器,Bit[1]使能系统滴答定时器中断,Bit[2]系统滴答时钟的时钟来源。
系统滴答定时器加载值寄存器(STK_LOAD)

Bit[23:0],一共24位,用来设置系统滴答定时器的初始值,因此范围为1~ 16777216。
系统滴答定时器当前值寄存器(STK_VAL)

Bit[23:0],一共24位,用来获取当前系统滴答定时器的计数值。
系统滴答定时器校准值寄存器(STK_CALIB)

这个寄存器没用到,可以不用管。此外,当处理器在调试期间被暂停(halt)时,系统滴答定时器也将暂停运作。
在理解系统滴答定时器的工作方式,了解系统滴答定时器的寄存器基本信息后,就可以尝试编写程序了。
11.2 硬件设计
系统滴答定时器属于Cortex-M3内核资源,不涉及外部硬件电路。实验中会用到LED灯,电路设计参考前面LED点灯实验。
11.3 软件设计
11.3.1.1 软件设计思路
实验目的:使用系统滴答定时器实现自定义延时。
1) 分析HAL库的系统滴答定时器配置函数;
2) 初始化系统滴答定时器(设置计数初值、使能等);
3) 封装延时函数,设置系统滴答定时器中断处理函数;
4) 主函数调用验证;
本实验配套代码位于“5_程序源码4_基础重点—SysTick定时器”。
11.3.1.2 软件设计讲解
1) 分析HAL库的系统滴答定时器配置函数
在HAL库中,使用“HAL_SYSTICK_Config()”函数配置SysTick的初始值。
代码段 11.3.1 SysTick配置函数(stm32f1xx_hal_cortex.c)
/**
* @brief Initializes the System Timer and its interrupt, and starts the System Tick Timer.
* Counter is in free running mode to generate periodic interrupts.
* @param TicksNumb: Specifies the ticks Number of ticks between two interrupts.
* @retval status: - 0 Function succeeded.
* - 1 Function failed.
*/
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
return SysTick_Config(TicksNumb);
}
该函数调用“SysTick_Config()”函数,函数内容如下代码段 11.3.2所示。
代码段 11.3.2 SysTick配置函数(core_cm3.h)
/* ################################## SysTick function ############################################ */
/**
ingroup CMSIS_Core_FunctionInterface
defgroup CMSIS_Core_SysTickFunctions SysTick Functions
brief Functions that configure the System.
@{
*/
#if defined (__Vendor_SysTickConfig) && (__Vendor_SysTickConfig == 0U)
/**
brief System Tick Configuration
details Initializes the System Timer and its interrupt, and starts the System Tick Timer.
Counter is in free running mode to generate periodic interrupts.
param [in] ticks Number of ticks between two interrupts.
return 0 Function succeeded.
return 1 Function failed.
note When the variable <b>__Vendor_SysTickConfig</b> is set to 1, then the
function <b>SysTick_Config</b> is not included. In this case, the file <b><i>device</i>.h</b>
must contain a vendor-specific implementation of this function.
*/
__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 */
}
#endif
24~27行:判断传入的SysTick初始值是否大于最大值224;
29行:设置SysTick初始值;
30行:设置SysTick中断的优先级,默认为最低;
31行:将SysTick当前计数值清零;
32~34行:设置SysTick的控制和状态寄存器,展开对应的宏,值为“(1<<2) | (1<<1) | (1)”,结合前面STK_CTRL寄存器介绍,可知这里使能了SysTick,使能了SysTick中断,时钟源为AHB。当系统时钟为72MHz时,AHB不分频,也为72MHz,则SysTick的时钟也为72MHz。
通过对“HAL_SYSTICK_Config()”函数分析,可知只需要传入SysTick初始值,其它的都默认已经设置完成了。
2) 初始化系统滴答定时器
假设当MCU工作在72MHz,SysTick也工作在72MHz。时钟在1s内完成周期性变化的次数叫做频率(单位:Hz),因此72MHz则表示1秒SysTick计数72000000次,即1毫秒计数72000次。
因此,如果将72000传入“HAL_SYSTICK_Config()”函数,则SysTick从72000减到0,花费时间为1毫秒,创建函数“SysTickInit()”初始化系统滴答定时器,如代码段 11.3.3 所示。
代码段 11.3.3 初始化SysTick(driver_systick.c)
/*
* 函数名:void SysTickInit(uint32_t cycle)
* 输入参数:cycle,设置系统滴答时钟周期
* 输出参数:无
* 返回值:无
* 函数作用:初始化系统滴答时钟的频率和中断优先级
*/
void SysTickInit(uint32_t cycle)
{
uint32_t init_t = 0;
init_t = SystemCoreClock/cycle;
/* 时间(单位:s)=1/频率(单位:HZ)
* SystemCoreClock频率: 72MHz = 72,000,000
* 即MCU 1秒会计数72,000,000次
* 1ms则计数 72MHz/1000 = 72000次
* 72000就是滴答时钟的初始值,它向下计数72000次,计数将变为0,就会产生一次中断
* 滴答时钟初始值范围:1~16777216
*
* SystemCoreClock/1000: 1ms中断一次
* SystemCoreClock/100000: 10us中断一次
* SystemCoreClock/1000000: 1us中断一次
*/
if(HAL_SYSTICK_Config(init_t) != HAL_OK)
{
Error_Handler();
}
// 设置滴答定时器中断优先级:最高
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
// 使能滴答定时器中断
HAL_NVIC_EnableIRQ(SysTick_IRQn);
}
12行:使用HAL库提供的全局变量“SystemCoreClock”获取当前系统时钟,再根据传入的cycle,计算出SysTick的初始值;
25~28行:使用“HAL_SYSTICK_Config()”函数设置SysTick的初始值,并检测是否设置成功;
31行:设置滴答定时器中断优先级,这里设置为最高。前面分析“HAL_SYSTICK_Config()”函数,知道该函数也会设置中断优先级,这里重新设置为最高优先级,在当前示例里,SysTick中断的优先级不重要;
33行:使能SysTick中断;这里是使能NVIC,而“HAL_SYSTICK_Config()”函数使能的是SysTick;
为了方便修改SysTick的初始值,这里定义几个常见的延时周期,如代码段 11.3.4 所示。当需要延时周期为1毫秒时,传入“CYCLE_1MS”给“SysTickInit()”,则SysTick计数到零花费1毫秒
代码段 11.3.4 定义延时周期(driver_systick.h)
#define CYCLE_100MS 10
#define CYCLE_10MS 100
#define CYCLE_1MS 1000
#define CYCLE_100US 10000
#define CYCLE_10US 100000
#define CYCLE_1US 1000000
3) 封装延时函数,设置系统滴答定时器中断处理函数
创建延时函数“SysTickDelay()”,在该函数里设置自定义全局变量systick_t的初始值,SysTick每计数完一次则进入SysTick中断,将全局变量systick_t的值减1,如代码段 11.3.6 所示。一直到systick_t变为零,结束延时,如代码段 11.3.5 所示。
代码段 11.3.5 SysTick延时函数(driver_systick.c )
/*
* 函数名:void SysTickDelay(uint16_t m)
* 输入参数:m-延时时间
* 输出参数:无
* 返回值:无
* 函数作用:滴答定时器实现的延时函数
*/
void SysTickDelay(uint32_t m)
{
systick_t = m;
while(systick_t != 0);
}
代码段 11.3.6 SysTick中断处理函数(stm32f1xx_it.c)
/*
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
HAL_IncTick();
if(systick_t)
{
systick_t--;
}
}
4) 主函数调用验证
代码段 11.3.7 SysTick延时点灯(main.c)
/*
* 初始化滴答时钟
* 通过改变传入参数改变滴答时钟的频率,即SysTickDelay(1)的时长
*/
SysTickInit(CYCLE_1MS);
// 初始化LED
LedGpioInit();
while(1)
{
/* 通过延时一段时间让LED亮灭实现LED闪烁,可以通过示波器打LED的引脚反转周期,精确看时间是否与设置的一致*/
BLED(ON); // 点亮LED
SysTickDelay(1000); // 延时CYCLE_1MS*1000=1s
BLED(OFF); // 熄灭LED
SysTickDelay(1000); // 延时CYCLE_1MS*1000=1s
}
5行:初始化SysTick,这里传入CYCLE_1MS,则延时函数“SysTickDelay()”的单位为1毫秒;
7~16行:初始化LED,调用延时函数“SysTickDelay()”,传入1000,则延时为1秒;
11.4 实验效果
本实验对应配套资料的“5_程序源码4_基础重点—SysTick定时器”。打开工程后,编译,下载,可以看到蓝色LED灯间隔1秒,交替闪烁。读者可修改代码段 11.3.7 中的第5行时钟周期,或者13、15行的延时时间,改变LED灯的闪烁间隔时间。
通过LED展示SysTick的延时结果不够严谨,有条件的读者可以使用示波器或逻辑分析仪,触碰LED灯焊盘的引脚,测试翻转时间,如图 11.4.1 所示,分别修改延时时间10us、1ms、1s后逻辑分析仪测量值。

图 11.4.1 逻辑分析仪测试SysTick延时
- 意法半导体中国本地造STM32微控制器启动规模量产
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 基于机智云与STM32的智能拐杖安全监测系统在养老物联网中的应用
- 内置全栈安全,一站式满足CRA法案与IEC 62443标准——米尔STM32MP257核心板
- 如何用 STM32 FLASH 实现等效 100 万次擦写的 EEPROM 功能?
- 实战解析:通过一个小项目掌握STM32所有外设
- STM32学了两年半,却还是不会做项目
- 意法半导体推出最新STM32MP21微处理器,兼具高性价比、低功耗、高灵活性
- 基于STM32的矿井作业环境监测系统设计与实现
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 有源晶振与无源晶振的六大区别详解
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 蓝牙信道探测技术原理与开发套件实践
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析




