F429-SysTick(2026/7/22)

目录

[SysTick 系统定时器完整流程](#SysTick 系统定时器完整流程)

[1. 硬件层 --- Cortex-M4 内核内置 SysTick](#1. 硬件层 — Cortex-M4 内核内置 SysTick)

[24 位寄存器限制](#24 位寄存器限制)

[2. 配置层 --- 决定多久中断一次](#2. 配置层 — 决定多久中断一次)

[3. 中断服务层 --- 到点后干什么](#3. 中断服务层 — 到点后干什么)

[HAL_IncTick() --- stm32f4xx_hal.c](#HAL_IncTick() — stm32f4xx_hal.c)

[TimingDelay_Decrement() --- bsp_SysTick.c](#TimingDelay_Decrement() — bsp_SysTick.c)

[4. 应用层 --- 两个延时函数的实现](#4. 应用层 — 两个延时函数的实现)

[HAL_Delay(1ms 精度)--- stm32f4xx_hal.c](#HAL_Delay(1ms 精度)— stm32f4xx_hal.c)

[Delay_us / Delay_ms(10us 精度)--- bsp_SysTick.c](#Delay_us / Delay_ms(10us 精度)— bsp_SysTick.c)

[5. 两种延时的对比](#5. 两种延时的对比)

[6. 时间单位换算](#6. 时间单位换算)

[7. 本质一句话](#7. 本质一句话)

[8. 纯 HAL 路线 --- myproject 的实际配置](#8. 纯 HAL 路线 — myproject 的实际配置)

[调用链(三级跳水,从 main 到寄存器)](#调用链(三级跳水,从 main 到寄存器))

[最终落寄存器的代码 --- core_cm4.h](#最终落寄存器的代码 — core_cm4.h)

实际配置数值(180MHz)

[纯净 HAL 的中断服务 --- stm32f4xx_it.c](#纯净 HAL 的中断服务 — stm32f4xx_it.c)

[谁在调用 HAL_InitTick](#谁在调用 HAL_InitTick)

[9. 总结:SysTick 全流程一张图](#9. 总结:SysTick 全流程一张图)

硬件层

配置层

[ISR 层(软件分频)](#ISR 层(软件分频))

[应用层:减法 vs 加法](#应用层:减法 vs 加法)

核心理解


SysTick 系统定时器完整流程

1. 硬件层 --- Cortex-M4 内核内置 SysTick

寄存器 作用
STK_LOAD 重装载值
STK_VAL 当前计数值(递减)
STK_CTRL 控制寄存器(开关、时钟源、中断使能)

24 位寄存器限制

SysTick 的 LOAD 和 VAL 寄存器是 24 位的,最大装载值:

复制代码
STK_LOAD_max = 2²⁴ - 1 = 0xFFFFFF = 16,777,215

这对中断周期有两个硬约束:

最长中断周期(LOAD 取最大):

复制代码
T_max = (16,777,215 + 1) ÷ 180,000,000 ≈ 93.2ms
​
LOAD=16,777,215 → 一次中断要等约 93ms,再大装不下了

→ SysTick 单次定时无法超过 ~93ms(F429@180MHz 下)。更长的延时必须靠中断里软件重新装填多次中断累积计数 ------这就是 HAL 用 uwTick++ 累积、野火用 TimingDelay-- 倒数的原因。

最短中断周期(LOAD 取最小 1):

复制代码
T_min = (1 + 1) ÷ 180,000,000 ≈ 11ns
​
LOAD=1 → 每 2 个时钟周期中断一次,理论上可达 90MHz 中断频率,实际不可能(CPU 当场卡死)

→ 实际工程中 LOAD 至少几十到几百,否则中断开销会吞掉全部 CPU 时间。

LOAD 值与最大单次延时的对应(180MHz):

LOAD 周期 最大单次延时
180 1μs 16.7s(用 TimingDelay 倒到 0)
1,800 10μs 167s
180,000 1ms 16,777s(≈ 4.6 小时)
16,777,215 93.2ms 约 26 分钟(HAL_Delay 最大值 0xFFFFFFFFms ≈ 49 天,跨过 49 天后 uwTick 翻转,有专门处理)

总结:24 位决定了硬件单次最长约 93ms,但软件通过 硬件计数器 + 中断累加 的方式把延时扩展到了任意时长。硬件提供节拍,软件负责数拍子。

每个 CPU 时钟周期,硬件自动做的事:

复制代码
每个时钟周期:
  STK_VAL = STK_VAL - 1                ← 硬件自动减 1
​
当 STK_VAL 减到 0 时:
  ├─ 硬件自动:STK_VAL = STK_LOAD       ← 重装载
  ├─ 硬件自动:COUNTFLAG = 1            ← 置标志位
  └─ 硬件自动:跳转 SysTick_Handler()    ← 触发中断

硅片电路全自动循环------不减慢 CPU、不占执行指令。上电默认关闭(STK_CTRL=0),必须软件使能后才跑。硬件没有"默认中断周期",LOAD 值完全由你决定。


2. 配置层 --- 决定多久中断一次

复制代码
HAL_SYSTICK_Config(SystemCoreClock / N);
// LOAD = SystemCoreClock / N
//
// N = 1000    → LOAD = 180000  → 1ms 中断(HAL 默认)
// N = 100000  → LOAD = 1800    → 10us 中断(野火 BSP)
// N = 1000000 → LOAD = 180     → 1us 中断

中断周期 = (LOAD + 1) ÷ 系统时钟频率。

LOAD 越小 → 中断越频繁 → 精度越高(但中断开销也越大)。

HAL 用 /1000(1ms)是 ST 工程师写死的一个约定------方便编程、外设超时用毫秒计算、1ms 是"够细又不过于频繁"的权衡点。


3. 中断服务层 --- 到点后干什么

stm32f4xx_it.c 里的 SysTick_Handler()

复制代码
void SysTick_Handler(void)
{
    HAL_IncTick();              // uwTick += 1(HAL 的 1ms 计数器)
    TimingDelay_Decrement();    // TimingDelay--(野火 BSP 的 10us 计数器)
}

中断在后台静默执行,修改全局变量,主程序完全无感知。

HAL_IncTick() --- stm32f4xx_hal.c

复制代码
__IO uint32_t uwTick;                          // 全局滴答计数器
HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT;  // 默认 1(= 1ms/次)
​
__weak void HAL_IncTick(void)
{
    uwTick += uwTickFreq;   // 每次中断 uwTick 加 1ms
}

TimingDelay_Decrement() --- bsp_SysTick.c

复制代码
static __IO u32 TimingDelay;   // 野火 BSP 的私有计数器
​
void TimingDelay_Decrement(void)
{
    if (TimingDelay != 0x00)
        TimingDelay--;         // 每次中断减 1(= 10us)
}

4. 应用层 --- 两个延时函数的实现

HAL_Delay(1ms 精度)--- stm32f4xx_hal.c

复制代码
__weak void HAL_Delay(uint32_t Delay)
{
    uint32_t tickstart = HAL_GetTick();            // 记下起点 uwTick
    uint32_t wait = Delay + uwTickFreq;             // 目标差值(+1 保证不小于)

    while ((HAL_GetTick() - tickstart) < wait) { } // 死等 uwTick 涨到位
}

模式:递增计数器 + 差值比较。 uwTick 不断涨,主循环算"涨了多少",够数了就退出。

Delay_us / Delay_ms(10us 精度)--- bsp_SysTick.c

复制代码
void Delay_us(__IO u32 nTime)    // nTime = 10us 的倍数
{
    TimingDelay = nTime;          // 设初值
    while (TimingDelay != 0);    // 死等减到 0
}

#define Delay_ms(x) Delay_us(100*x)  // 1ms = 100 × 10us

模式:递减计数器 + 归零判断。 TimingDelay 每次中断减 1,主循环等到 0 就退出。


5. 两种延时的对比

HAL_Delay 野火 Delay_us
计数器 uwTick(递增) TimingDelay(递减)
分辨率 1ms 10μs
比较方式 (当前值 - 起点) >= 目标 值 == 0
__weak ✅ 可覆盖 ❌ 固定
额外用途 HAL 内部超时判断 用户精确时序

6. 时间单位换算

复制代码
1s = 1000ms          (1秒 = 一千毫秒)
1ms = 1000us         (1毫秒 = 一千微秒)
1s = 1,000,000us     (1秒 = 一百万微秒)

s → ms → us 都是 1000 进制,千倍关系。


7. 本质一句话

硬件到点敲门 → 中断函数改个变量 → 主循环等变量到位 → 延时结束。

硬件只管"到没到",变量改多少、怎么改、主循环怎么等------全部是几行 C 代码决定的。


8. 纯 HAL 路线 --- myproject 的实际配置

hal_first_project 工程为例,不走野火 BSP,纯 HAL 体系。

调用链(三级跳水,从 main 到寄存器)

复制代码
main.c: HAL_Init()
  └─ stm32f4xx_hal.c:176        HAL_InitTick(TICK_INT_PRIORITY)
       └─ stm32f4xx_hal.c:256    HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq))
            └─ stm32f4xx_hal_cortex.c:227-230   return SysTick_Config(TicksNumb);
                 └─ core_cm4.h:2022-2036         __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
                                                       ↓ 最终写寄存器

最终落寄存器的代码 --- core_cm4.h

复制代码
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)   // ticks = 180000
{
  // ① 检查 24 位上限
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)            // SysTick_LOAD_RELOAD_Msk = 0xFFFFFF
    return (1UL);

  // ② 写三个寄存器
  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                // STK_LOAD = 179999
  NVIC_SetPriority(SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); // 优先级 = 最低(15)
  SysTick->VAL   = 0UL;                                    // STK_VAL = 0(清零)
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk   |          // 时钟源 = HCLK(180MHz)
                   SysTick_CTRL_TICKINT_Msk     |          // 使能中断
                   SysTick_CTRL_ENABLE_Msk;                // 启动定时器
  return (0UL);
}

三个寄存器位的来源:

含义
SysTick_CTRL_CLKSOURCE_Msk (1 << 2) 时钟源 = 系统时钟 / 8
SysTick_CTRL_TICKINT_Msk (1 << 1) 减到 0 时产生中断
SysTick_CTRL_ENABLE_Msk (1 << 0) 启动计数器

实际配置数值(180MHz)

项目 来源
SystemCoreClock 180MHz=(180000000)hz SystemClock_Config() 配置
uwTickFreq 1(HAL_TICK_FREQ_1KHZ stm32f4xx_hal.c:112
Reload 值 180,000 - 1 = 179,999 180M / (1000/1) - 1
中断周期 1ms溢出一次 (179999 + 1) / 180M
中断优先级 15(最低) (1 << 4) - 1

纯净 HAL 的中断服务 --- stm32f4xx_it.c

复制代码
void SysTick_Handler(void)
{
    HAL_IncTick();   // uwTick += 1,不包含野火的 TimingDelay_Decrement()
}

这个工程不用野火 BSP,所以中断里只有 HAL 的 uwTick 累加,没有 TimingDelay 那套 10us 的减法逻辑。

谁在调用 HAL_InitTick

不止 HAL_Init() 会调,时钟频率改变时也会重新调

调用位置 时机
HAL_Init() --- stm32f4xx_hal.c:176 上电初始化
HAL_RCC_ClockConfig() --- stm32f4xx_hal_rcc.c:722 系统时钟变更
HAL_RCCEx_PeriphCLKConfig() --- stm32f4xx_hal_rcc_ex.c:3305 外设时钟变更

每次时钟变了,LOAD 值都要重新算------频率越高,SystemCoreClock / 1000 越大,中断还是 1ms;频率越低,LOAD 自动变小,保持 1ms 不变。这就是用公式而不是写死数字的好处。


9. 总结:SysTick 全流程一张图

硬件层

复制代码
Cortex-M4 内核内置 SysTick(24 位递减计数器)

CTRL ENABLE 位写 1 瞬间:
  VAL = LOAD(硬件自动装填)
  每个 HCLK 周期:VAL = VAL - 1
  减到 0:→ 触发中断 + 硬件自动重装 VAL = LOAD + 循环往复

上电默认关闭,没有默认周期——LOAD 值完全由你决定

配置层

复制代码
SysTick_Config(180000)     → LOAD=179999 → 1ms 中断(HAL 默认)
SysTick_Config(1800)       → LOAD=1799   → 10us 中断(野火 BSP)

公式:中断周期 = (LOAD + 1) / 系统时钟频率
时序:软件写 0 进 VAL → 硬件瞬间自动装填 → 从 LOAD 开始往下减

ISR 层(软件分频)

复制代码
SysTick 每 10us 中断一次 →
  ├─ TimingDelay--            (每次,给 Delay_us 用)
  └─ HAL_TickCounter++
      if (>= 100) HAL_IncTick()(每 100 次 = 1ms,给 HAL_Delay 用)

应用层:减法 vs 加法

减法模式 加法模式
函数 Delay_us(n) / Delay_ms(n) HAL_Delay(n)
变量 TimingDelay(私有) uwTick(全局共享)
模式 设初值 → 死等归零 记起点 → 死等差值到位
精度 10us 1ms
适合 一次性精确延时 全局时基,多消费者共用

同一个硬件,两个延时体系,纯软件分道,互不影响。

核心理解

  1. 硬件只是节拍器------到点了通知你(中断),怎么用是你的事

  2. 24 位寄存器单次最长 ~93ms,长延时靠"硬件发节拍 + 软件计数"

  3. 一个 10us 硬件中断,喂出两套延时体系:10us 减法 + 1ms 加法

  4. 主循环做的事永远是:设个初值 → 死等 ISR 把条件改到成立 → 走人

相关推荐
hongmai6668885 小时前
ESP32-C61-WROOM-1-N8R2:Wi-Fi 6与RISC-V融合的中坚力量
人工智能·单片机·嵌入式硬件·物联网·智能家居·risc-v
炸膛坦客14 小时前
单片机/C/C++八股:(二十六)IIC 专题(I²C)---- 上集
c语言·c++·单片机
华清远见IT开放实验室19 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
AI的探索之旅21 小时前
AI辅助原理图评审:电源去耦、BOOT引脚、VCAP——19项逐一核查,遗漏?不存在的
人工智能·vscode·嵌入式硬件
茯苓gao21 小时前
嵌入式开发笔记:EtherCAT协议从硬件到软件完整配置指南——从零搭建一套EtherCAT通信系统
笔记·嵌入式硬件·学习
GeekArch1 天前
第24讲:Vibe模式代码风格控制——适配Keil/STM32工程规范
人工智能·stm32·单片机·嵌入式硬件·mcu·决策树·ai编程
Freedom_my1 天前
STM32项目3
stm32·单片机·嵌入式硬件
国科安芯1 天前
星间光链路:AS32S601型抗辐射MCU在空间激光通信终端控制中的技术实现
服务器·网络·单片机·嵌入式硬件·物联网·安全·信息与通信
小李不困还能学1 天前
基于 51 单片机的8 路抢答器设计教程
单片机·嵌入式硬件·mongodb·抢答器