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

相关推荐
没有医保李先生14 小时前
esp32和stm32的工程宏定义
stm32·单片机·嵌入式硬件
不想看见40414 小时前
Valid Parentheses栈和队列--力扣101算法题解笔记
开发语言·数据结构·c++
炸膛坦客14 小时前
单片机/C/C++八股:(十五)内存对齐、结构体内存对齐
c语言·开发语言·单片机
老约家的可汗14 小时前
C/C++内存管理探秘:从内存分布到new/delete的底层原理
c语言·c++
计算机安禾14 小时前
【C语言程序设计】第37篇:链表数据结构(一):单向链表的实现
c语言·开发语言·数据结构·c++·算法·链表·蓝桥杯
szxinmai主板定制专家14 小时前
基于ZYNQ MPSOC船舶数据采集仪器设计(一)总体设计方案,包括振动、压力、温度、流量等参数
arm开发·人工智能·嵌入式硬件·fpga开发
SUNNYSPY00115 小时前
65R380-ASEMI超结MOS管TO-252封装
单片机
2501_9181269115 小时前
学习所有6502写游戏存档的语句
汇编·嵌入式硬件·学习·游戏·个人开发
皮卡狮15 小时前
高阶数据结构:AVL树
数据结构·算法
普中科技15 小时前
【普中STM32F1xx开发攻略--标准库版】-- 第 38 章 RS485 通信实验
stm32·单片机·嵌入式硬件·开发板·通信·rs485·普中科技