STM32 HAL 中 HAL_Delay 实现原理笔记
STM32 HAL 中 HAL_Delay 实现原理笔记
关键词:SysTick / uwTick / uwTickFreq / HAL_Delay
一、HAL 时间系统的核心结论(一句话版)
HAL_Delay 是基于 SysTick 中断的阻塞式延时函数,
通过比较系统 tick(uwTick)的差值实现延时,
默认 1 个 tick = 1 ms。
二、SysTick 是什么?
- SysTick 是 ARM Cortex-M 内核自带的 24 位定时器
- 使用 CPU 主频计数
- 计数到 0 触发 SysTick 中断
HAL 默认配置
SysTick_Config(SystemCoreClock / 1000);
含义:
-
SysTick 中断频率 = 1 kHz
-
每 1 ms 触发一次 SysTick 中断
1️⃣ 从最底层开始:什么是
SystemCoreClock = 72 MHz?SystemCoreClock = 72 000 000 Hz含义只有一句话:
CPU 每 1 秒钟走 72,000,000 个时钟周期
也就是:
1 个时钟周期 = 1 / 72,000,000 秒 ≈ 13.9 ns
2️⃣ SysTick 是“按时钟周期数数”的
非常关键的一点(一定要记住):
SysTick 不知道“毫秒”“秒”
它只会数:时钟周期
你给它一个数:
SysTick->LOAD = N;它的行为只有一个:
每来 1 个 CPU 时钟 → 计数器减 1
3️⃣ 那
SystemCoreClock / 1000是在算什么?SystemCoreClock / 1000 = 72,000,000 / 1000 = 72,000这一步的含义是:
算出“1 毫秒里有多少个 CPU 时钟周期”
因为:
1 秒 = 1000 ms所以:
1 ms = 72,000,000 / 1000 = 72,000 个周期也就是数够 72000 个时钟周期 → 产生一次中断 → 刚好 1 ms
三、uwTick 是什么?
1️⃣ 定义
__IO uint32_t uwTick;
2️⃣ 初始值
- 系统上电后:
uwTick = 0
3️⃣ 增长方式(唯一来源)
void SysTick_Handler(void)
{
HAL_IncTick();
}
void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
四、uwTickFreq 是什么?为什么是 1?
1️⃣ 默认值
#define HAL_TICK_FREQ_1KHZ 1U
uwTickFreq = HAL_TICK_FREQ_1KHZ; // = 1
2️⃣ 含义
每发生一次 SysTick 中断,uwTick 增加 1
结合 SysTick = 1ms:
1 次 uwTick 增加 = 1 ms
五、uwTick 的物理意义
| 时间 | uwTick |
|---|---|
| 上电 | 0 |
| 1 ms | 1 |
| 10 ms | 10 |
| 100 ms | 100 |
| 1 s | 1000 |
uwTick = 100表示:系统已运行约 100 ms
⚠️ 这是一个累计计数器,不是瞬时变量
六、HAL_Delay 的源码(核心)
void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
if (wait < HAL_MAX_DELAY)
{
wait += uwTickFreq;
}
while ((HAL_GetTick() - tickstart) < wait)
{
}
}
//HAL_GetTick()内部
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
//改变uwTick的函数
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
//HAL_IncTick()调用位置
void SysTick_Handler(void)
{
HAL_IncTick();
}
七、HAL_Delay 的逐行解释
1️⃣ 记录起始时间
tickstart = HAL_GetTick();
- 固定一个时间起点
- 只关心 时间差,不关心绝对时间
2️⃣ 计算需要等待的 tick 数
wait = Delay + uwTickFreq;
为什么要加uwTickFreq:
时间点 A:
uwTick = 100时间点 A 和 B 之间:
刚进入 HAL_Delay()
tickstart = HAL_GetTick(); // tickstart = 100时间点 B:
SysTick 中断发生
uwTick++ → uwTick = 101
作用:
- 防止“刚进入函数就遇到 SysTick”
- 保证延时 ≥ Delay
3️⃣ while 忙等待
while ((HAL_GetTick() - tickstart) < wait);
- CPU 空转
- 只能依靠 SysTick 中断推动时间前进
八、HAL_Delay(1) 为什么等于 1ms?
前提条件
uwTickFreq = 1
SysTick = 1ms
执行过程示例
假设当前:
uwTick = 100
调用:
HAL_Delay(1);
执行流程:
tickstart = 100
wait = 1 + 1 = 2
SysTick 第 1 次 → uwTick = 101
101 - 100 = 1 < 2 → 继续等
SysTick 第 2 次 → uwTick = 102
102 - 100 = 2 ≥ 2 → 退出
结论
HAL_Delay(1) = 等待 uwTick 至少增加 1 次
= 等待至少 1 个 SysTick
= 延时 ≥ 1 ms
九、为什么讲解中常用 “uwTick = 100”?
- 这是一个 示例假设值
- 表示系统已运行一段时间
- 实际可能是任何值(0 / 500 / 3200 / …)
HAL_Delay 只关心差值,不关心具体数值
十、HAL_Delay 的本质总结
| 项目 | 说明 |
|---|---|
| 时间来源 | SysTick 中断 |
| 核心变量 | uwTick |
| 延时方式 | while 忙等待 |
| 是否阻塞 | 是 |
| 精度 | ms 级 |
| 是否适合中断 | ❌ 不适合 |
十一、常见易错点(考试高频)
❌ 1. 在中断中使用 HAL_Delay
- SysTick 可能无法响应
- uwTick 不增加
- 程序死锁
❌ 2. 改了 uwTickFreq 还以为是 ms
HAL_SetTickFreq(HAL_TICK_FREQ_100HZ); // uwTickFreq = 10
HAL_Delay(1); // 实际 ≈ 10ms
十二、考试标准一句话答案(可直接写)
HAL_Delay 是基于 SysTick 中断的阻塞延时函数,通过记录起始 tick 并比较系统 tick 差值实现延时,其延时精度取决于 uwTickFreq,默认 1 tick 为 1 ms。
十三、终极理解版比喻
SysTick 每 1ms 敲一次钟,
uwTick 就记一笔账,
HAL_Delay 就是数着账等够次数。

浙公网安备 33010602011771号