【第20期】延时的艺术:HAL_Delay vs vTaskDelay

裸机与RTOS核心差异 :裸机的延时是**"死等"(烧电、霸占 CPU);RTOS 的延时是"挂起"**(让权、省电)。

1. 裸机延时:焦虑的失眠者 (HAL_Delay)

我们在学习 STM32 的第一天就用过 HAL_Delay()。来看看它的底层逻辑(简化版):

void HAL_Delay(uint32_t Delay) {

uint32_t tickstart = HAL_GetTick(); // 记录开始时间

// 死循环检查

while ((HAL_GetTick() - tickstart) < Delay) {

// 空转!CPU 在这里疯狂跑圈,什么有意义的事都没做。

// 就像一个人盯着手表看,每一秒都数着过。

}

}

后果

  1. 霸道 :当 CPU 执行 HAL_Delay(1000) 时,这 1 秒钟内,主循环里排在后面的所有任务(按键扫描、屏幕刷新)全部被迫暂停。整个系统处于"假死"状态。

  2. 烧电:虽然 CPU 没干正事,但它处于全速运行状态(Run Mode),电流消耗是最大的(例如 20mA)。

2. RTOS 延时:定闹钟睡觉 (vTaskDelay)

RTOS 的 vTaskDelay (FreeRTOS) 或 OS_Delay (uCOS) 完全不同。

当你在任务 A 中调用 vTaskDelay(1000) 时,操作系统内核会做一系列复杂的动作:

  1. 移出:调度器把任务 A 从**【就绪列表 (Ready List)】**中拿走。这意味着调度器下次挑选"谁来运行"时,根本不会看任务 A 一眼。

  2. 记录:调度器把任务 A 放入**【延时列表 (Delayed List)】**,并给它贴个条子:"在系统时间到达 X+1000 时叫醒我"。

  3. 让权 (Yield) :任务 A 此时交出 CPU 使用权。调度器立刻去【就绪列表】找优先级最高的任务 B。

  4. 切换:CPU 保存 A 的现场,恢复 B 的现场,开始运行 B。

直观感受: 任务 A 说:"我要睡 1 秒。" 然后它就真的"消失"了。CPU 转头去干别的事。直到 1 秒后,系统滴答中断(SysTick)发现时间到了,才会把任务 A 从"小黑屋"里放出来,重新回到【就绪列表】排队。


3. 神奇的空闲任务 (Idle Task)

你可能会问:"如果系统里只有任务 A,它延时了,CPU 把权交出来,交给谁呢?"

这时候,RTOS 会自动创建一个最低优先级的保底任务------空闲任务 (Idle Task)

当所有业务任务都处于"阻塞"或"延时"状态时,CPU 就会运行空闲任务。 更厉害的是,我们可以在空闲任务里植入低功耗代码(Hook 函数):

void vApplicationIdleHook(void) {

// 汇编指令 WFI (Wait For Interrupt)

// 它的作用是:CPU 暂停运行,停止取指,时钟停振,进入休眠。

// 直到下一个中断(比如 SysTick 或 串口中断)来临,CPU 才会瞬间醒来。

__WFI();

}

巨大的功耗差异

  • 裸机 Delay:CPU 100% 时间全速跑,电流 ~20mA。

  • RTOS Delay :如果任务大部分时间在 Delay,CPU 大部分时间在 Idle Task 里执行 WFI 睡觉。电流可能降到 ~5mA 甚至更低。


4. 这里的"坑":相对延时 vs 绝对延时

RTOS 通常提供两种延时 API,新手容易混淆。

  • vTaskDelay (相对延时)

    • 含义:从调用这一行代码的时刻开始,延时 N 个节拍。

    • 场景:简单的 Sleep

    • 缺点:如果有高优先级中断打断,或者任务执行本身耗时,会导致周期累计误差。比如你想每 1000ms 闪一次灯,结果变成了 1005ms, 1010ms...

  • vTaskDelayUntil (绝对延时)

    • 含义:从上一次唤醒的时刻算起,让整个任务周期严格等于 N 个节拍。

    • 场景:需要高精度周期的采样任务(如每 2ms 读取一次 ADC)。它会自动扣除任务执行本身消耗的时间,多退少补。


总结陈词

  1. HAL_Delay忙等待 (Busy Wait),既阻碍别人运行,又浪费电能,是 RTOS 编程的大忌。

  2. vTaskDelay阻塞 (Blocking),它是 RTOS 实现多任务并发调度的基础------只有你让出了 CPU,别人才能运行。

  3. Idle Task + WFI 是 RTOS 天然低功耗的秘诀。

相关推荐
NEWEVA__zzera2210 分钟前
AM32开源项目固件解析(STM32G071)
stm32·单片机·嵌入式硬件
Hello_Embed25 分钟前
RS485 双串口通信 + LCD 实时显示(中断版)
c语言·笔记·单片机·学习·操作系统·嵌入式
brave and determined27 分钟前
工程设计类学习(DAY7):回流焊变形全解析:PCB翘曲终极解决方案
嵌入式硬件·硬件设计·可靠性测试·嵌入式设计·pcb变形·pcb生产·pcb设计分析
小痞同学32 分钟前
【铁头山羊STM32】HAL库 2.UART部分
stm32·单片机·嵌入式硬件
wu_asia32 分钟前
方阵对角线元素乘积计算
数据结构·算法
乡野码圣1 小时前
【RK3588 Android12】高精度定时器hrtimer
单片机·嵌入式硬件
想逃离铁厂的老铁1 小时前
Day43 >> 300.最长递增子序列 + 674. 最长连续递增序列+ 718. 最长重复子数组
数据结构·算法
xiaobobo33301 小时前
c语言什么时候适合用三目运算什么时候适合用阶梯判断
c语言·三目运算·阶梯判断·花括号作用域
宵时待雨2 小时前
数据结构(初阶)笔记归纳4:单链表的实现
c语言·开发语言·数据结构·笔记·算法
wm10432 小时前
代码随想录第三天 链表
数据结构·链表