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 就是数着账等够次数。

posted @ 2026-01-26 20:13  wind_one  阅读(24)  评论(0)    收藏  举报