SysTick 是 Cortex-M 内核提供的24 位向下计数定时器。配置好计数周期后,硬件周期性请求中断,CPU 进入SysTick_Handler(),我们在其中实现软件计时。
1. 设计思路:先确定中断周期,再累计次数
以“LED 每隔 1 秒翻转一次”为例:
SysTick 每 1 ms 中断一次 ↓ 软件计数加 1 ↓ 主程序发现经过 1000 ms ↓ 翻转 LED这样,中断只负责提供时间基准,主程序负责具体任务。
2. 如何计算中断周期?
选择 HCLK 作为 SysTick 时钟时:
ticks = HCLK频率 × 期望周期(秒) LOAD = ticks - 1 中断周期 = (LOAD + 1) / HCLK频率假设 HCLK 为 168 MHz,需要每 1 ms 中断一次:
ticks = 168,000,000 × 0.001 = 168000 LOAD = 167999使用库函数:
SysTick_Config(SystemCoreClock / 1000U);常用配置:
| 配置参数 | 中断周期 |
|---|---|
SystemCoreClock / 1000 | 1 ms |
SystemCoreClock / 100000 | 10 μs |
SystemCoreClock / 1000000 | 1 μs |
前提是SystemCoreClock与实际 HCLK 一致;不能整除时存在取整误差。
3. 初始化配置了什么?
SysTick_Config(ticks)主要完成以下操作:
SysTick->LOAD = ticks - 1U; // 设置计数周期 SysTick->VAL = 0U; // 清除当前计数值 // 设置中断优先级 NVIC_SetPriority(SysTick_IRQn, 15U); // 选择时钟、允许中断、启动计数器 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;CTRL的低三位分别控制:
| 位 | 设置为 1 的作用 |
|---|---|
CLKSOURCE,第 2 位 | 选择 HCLK |
TICKINT,第 1 位 | 允许计数到 0 时请求中断 |
ENABLE,第 0 位 | 启动计数器 |
选择 HCLK 不会改变 HCLK 的频率,系统时钟需要提前配置。
4. 推荐框架:中断计时,主循环处理任务
下面以 STM32F407 为例。假设工程已经提供时钟配置、LED 初始化和翻转函数,并由本示例独立使用 SysTick。
#include "stm32f4xx.h" static volatile uint32_t system_ms = 0; // 初始化:每 1 ms 请求一次中断 void SysTick_Init(void) { if (SysTick_Config(SystemCoreClock / 1000U) != 0U) { while (1) { // 配置参数超出范围 } } } // 中断:只更新时间 void SysTick_Handler(void) { system_ms++; } int main(void) { SystemClock_Config(); LED_GPIO_Config(); SysTick_Init(); uint32_t last_ms = system_ms; while (1) { uint32_t now = system_ms; if ((uint32_t)(now - last_ms) >= 1000U) { last_ms = now; LED_Toggle(); // 使用工程实际的 LED 翻转函数 } // 这里可以继续处理其他任务 } }这个框架没有空循环等待延时,主程序可以继续执行其他任务。LED 每秒翻转一次,完整亮灭周期约为 2 秒。
5. 必须记住的重点
- 硬件计数和软件计数不同:
LOAD决定一次中断隔多久,system_ms累计中断次数。 LOAD = ticks - 1:计算周期时使用LOAD + 1。- 共享计时变量使用
volatile:保证主程序读取中断更新后的值。 - 中断函数保持简短:更新时间或置标志,具体任务放在主循环。
- 不要在中断里等待 SysTick 延时:如果 SysTick 无法抢占当前中断,可能一直等待。
- 周期按需求选择:LED 等毫秒级任务使用 1 ms 节拍即可,过于频繁的中断会增加 CPU 开销。
- 24 位限制:
ticks最大为2²⁴;168 MHz 下最大单次周期约 99.86 ms,更长时间用软件累计。 - HAL 工程注意已有 SysTick:通常已经用于 1 ms 系统节拍,应在现有框架中扩展,并保留
HAL_IncTick()等必要处理。