【第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 天然低功耗的秘诀。

相关推荐
刻BITTER几秒前
VirtualBox 安装Armbian x86 虚拟机
linux·嵌入式硬件
想唱rap5 分钟前
应用层协议与序列化
linux·运维·服务器·网络·数据结构·c++·算法
iCxhust7 分钟前
微机原理实践教程(汇编篇)---A002流水灯
汇编·单片机·嵌入式硬件·51单片机·微机原理
资深流水灯工程师8 分钟前
UART 通讯DMA+IDLE模式笔记
笔记·单片机·嵌入式硬件
无敌昊哥战神17 分钟前
【LeetCode 37】解数独 (Sudoku Solver) —— 回溯法详解 (Python/C/C++)
c语言·c++·python·算法·leetcode
jinyishu_1 小时前
链表经典OJ题
c语言·数据结构·算法·链表
LCG元1 小时前
STM32实战:基于STM32F103的SPI通信驱动W25Qxx Flash存储
stm32·单片机·嵌入式硬件
澈2071 小时前
C++引用与指针:核心区别全解析
开发语言·数据结构·c++
iCxhust1 小时前
【无标题】8086/8088裸机对于学习微机原理的重要意义
汇编·单片机·嵌入式硬件·嵌入式·微机原理
爱编码的小八嘎1 小时前
C语言完美演绎9-14
c语言