RTOS-F429-HAL-绝对延时和相对延时(2026/7/31)

目录

[一:vTaskDelayUntil() --- 绝对延时](#一:vTaskDelayUntil() — 绝对延时)

1:函数原型

2:怎么用

[3:跟 vTaskDelay 的语法对比](#3:跟 vTaskDelay 的语法对比)

[二:rtos\9\ 改动清单](#二:rtos\9\ 改动清单)

1:freertos_demo.c

2:main.c

3:时间轴推演

[task2(prio=5, 绝对延时) task1(prio=3, 相对延时)](#task2(prio=5, 绝对延时) task1(prio=3, 相对延时))

[task2(prio=3, 绝对延时) task1(prio=5, 相对延时)](#task2(prio=3, 绝对延时) task1(prio=5, 相对延时))

三:生活化解释

[1:vTaskDelay --- "从现在开始算,睡多久"](#1:vTaskDelay — "从现在开始算,睡多久")

[2:vTaskDelayUntil --- "定一个固定的闹钟时间"](#2:vTaskDelayUntil — "定一个固定的闹钟时间")

3:一张图对比

4:什么时候用哪个


一:vTaskDelayUntil() --- 绝对延时

1:函数原型

复制代码
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
                      const TickType_t xTimeIncrement );
形参 含义
pxPreviousWakeTime 上一次醒来的绝对时间 。第一次调用前必须用 xTaskGetTickCount() 初始化。函数内部每次调用自动更新这个值。
xTimeIncrement 周期时长(单位:tick)。每次醒来后间隔多久再醒。

返值:无。

需要宏INCLUDE_vTaskDelayUntil = 1 ✅ 我们已开

2:怎么用

复制代码
TickType_t xLastWakeTime = xTaskGetTickCount();  // 记下"现在是几点"
​
while (1)
{
    // 干活...
    vTaskDelayUntil(&xLastWakeTime, 500);  // 下次醒 = xLastWakeTime + 500
    //          ↑                    ↑
    //     它帮你 +500 更新          周期 500 个 tick
}

第一次:xLastWakeTime = 100(假设当前时间)→ 睡到 100+500=600 第二次:xLastWakeTime = 600 → 睡到 600+500=1100 第三次:xLastWakeTime = 1100 → 睡到 1100+500=1600

内部自动帮你做了 xLastWakeTime += 500,你不用手动加。

3:跟 vTaskDelay 的语法对比

复制代码
// 相对延时:就一个参数,睡多久
vTaskDelay(500);
​
// 绝对延时:多一个"上次几点醒的"变量来锚定节奏
TickType_t wake = xTaskGetTickCount();
vTaskDelayUntil(&wake, 500);   // wake 必须传地址,函数内部会更新它

二:rtos\9\ 改动清单

1:freertos_demo.c

行号 内容
37-38 task1(prio=3)、task2(prio=5)------高优先级抢占低优先级
88-98 task1:vTaskDelay(500) 相对延时,周期会漂
108-119 task2:vTaskDelayUntil(&xLastWakeTime, 500) 绝对延时,不漂

2:main.c

行号 内容
5-7 去掉 Key.h/TIM6.h(本实验不需要按键和运行时间统计)
18-20 去掉 Key_Init()/ConfigureTimeForRunTimeStats()
27 printf 标题 → vTaskDelay vs vTaskDelayUntil Test!

代码很简洁。用示波器看 PH10(红灯, task1) 和 PH11(绿灯, task2):

  • task2 绿灯:精确 500ms 一个周期,不漂

  • task1 红灯:~500ms 但有抖动,因为会被 task2 抢或者起步晚了

没示波器的话看着灯闪也行,肉眼分不出,重点是用逻辑分析仪/示波器看差异。

3:时间轴推演

task2(prio=5, 绝对延时) task1(prio=3, 相对延时)
复制代码
​
t=0:   task2(prio=5) 跑
         LED1翻 → delay_ms(20) → vTaskDelayUntil → 睡到 t=500
​
t=20:  task1(prio=3) 拿到 CPU(task2 在睡)
         LED0翻 → delay_ms(20) → vTaskDelay(500) → 睡到 t=20+500=540
​
t=500: task2 准时醒
         LED1翻 → delay_ms(20) → t=520
         vTaskDelayUntil → 睡到 t=1000
​
t=540: task1 醒(task2 在睡,没人抢)
         LED0翻 → delay_ms(20) → t=560
         vTaskDelay(500) → 睡到 t=1060
​
​
task1 周期:第一次翻灯 t=20,第二次 t=540 → 520ms
​
task2 周期:第一次翻灯 t=0,第二次 t=500 → 500ms
​
差的那 20ms 不是被抢了 CPU——task2 醒的时候 task1 本来就在睡。差的是开头 task2 先占了 20ms,task1 天生起步晚了 20ms。之后两家的唤醒时间永远错开,谁也不抢谁,各过各的。
​
那之前说的"抢占导致波形拉长"是上一节的场景——task1 优先级更高的时候才会发生(task1 在跑 delay_ms 期间,task2 的绝对时间到了,但 task2 优先级低,抢不进去,只能等 task1 腾出手,所以 task2 的 500ms 才变成 ~520ms)。
​
这一节的结论更简单:task2 优先级高就先跑,占了前面 20ms,task1 起步晚,周期自然长。
task2(prio=3, 绝对延时) task1(prio=5, 相对延时)
复制代码
两个任务都做同一件事:翻转 LED → delay_ms(20) 忙等 → 延时。区别只在延时方式。
​
​
t=0  调度器启动
     task1(prio=5,更高) 先跑
       ├── LED0 翻转(瞬间)
       ├── delay_ms(20)        → 忙等 20ms,CPU 不释放
       └── vTaskDelay(500)     → 阻塞,记录"500ms 后叫醒我",将于t=520ms时醒来。
     
t≈20 task2(prio=3,更低) 终于等到 CPU
       ├── xLastWakeTime = xTaskGetTickCount()  → 记下"现在是 20"
       ├── LED1 翻转
       ├── delay_ms(20)        → 忙等 20ms,t≈40
       └── vTaskDelayUntil(&wake, 500)
            → 计算:下次醒 = 20 + 500 = t=520
            → 现在才 t=40,睡 480ms
​
t=520  task1 的 vTaskDelay(500) 到期,醒了
       → task1 优先级 5 > task2 优先级 3,         //task1 先跑!,把task2抢占了,两人同时从t=520ms醒来!!!!!!!!!!
       ├── LED0 翻转
       └── delay_ms(20)        → t≈540
​
t=540  task2 的 vTaskDelayUntil 也到期了(应该 t=520 醒的,被 task1 拖到 540)
       → 已经错过了 20ms!立刻跑
       ├── LED1 翻转
       ├── delay_ms(20)        → t≈560
       └── vTaskDelayUntil:xLastWakeTime += 500 → 下次醒 = 20+500+500 = t=1020
           实际睡多久?1020 - 560 = 460ms  ← ★ 比 500 短!
​
t≈1040 task1 又醒了(520+500=1020 + delay_ms 20 = t≈1040)
       → 又抢占!
为什么示波器上看到的是 ~496ms 而不是 500ms?
看上图——task2 每次都被 task1 拖后腿,但 vTaskDelayUntil 死守着绝对时间点。你迟到了它就缩短下一段睡眠来追回来,所以示波器上 task2 的 LED 周期会围绕 500ms 上下抖动,有时候是 496ms(在追)、有时候是 504ms(被拖)。
​
这就是绝对延时的意义:长期来看平均周期精确是 500ms,不漂移。反观 task1 的 vTaskDelay,每次都在漂,100 次以后可能漂出几十毫秒。
​
一句话
vTaskDelay = 倒计时闹钟,晚起了就整体漂移。vTaskDelayUntil = 固定时刻闹钟,晚起了就自己缩短下一次睡眠来追回去,长期节奏永远不变。你示波器看到的 496ms 就是因为 task2 在"追"被 task1 拖后腿的进度。

三:生活化解释

1:vTaskDelay --- "从现在开始算,睡多久"

复制代码
vTaskDelay(500);   // 从现在起,睡 500ms

生活例子:午休闹钟

复制代码
你说:"从现开始睡 30 分钟" → 设了个倒计时 30 分钟
 BUT:你在床上刷了 10 分钟手机才真的闭眼
 → 实际醒来是 40 分钟后(刷手机也算在了"睡之前"的时间里)
 → 你的午休节奏漂移了

代码里的后果

复制代码
一个任务每 100ms 打印一次:
​
第 1 次:t=0    → 打印 → vTaskDelay(100)    → t=100ms 醒来
第 2 次:t=100  → 打印 → 但!打印花了 5ms    → vTaskDelay(100) → t=205ms 醒来
第 3 次:t=205  → 打印(5ms) → vTaskDelay(100)  → t=310ms 醒来
第 4 次:t=310  → ...
​
每次都在漂移,100 次以后可能差了半秒!

2:vTaskDelayUntil --- "定一个固定的闹钟时间"

复制代码
TickType_t last_wake = xTaskGetTickCount();  // 记下"起床时间"
while (1)
{
    // 干活...
    vTaskDelayUntil(&last_wake, 100);  // 不管刚才干了多久,下次必须是 last_wake+100ms
}

生活例子:学校上课铃

复制代码
上课铃每隔 45 分钟响一次——不管你是准时回到座位还是磨蹭了 3 分钟:
  第一节:9:00 响铃 → 上课 45 分钟
  第二节:9:45 响铃 ← 固定!不是"从第一节下课开始 +45"
  第三节:10:30 响铃 ← 固定!

即使你课间拖堂了 5 分钟,下一节的**响铃时间不变**。

代码里的效果

复制代码
第 1 次:t=0    → 打印(5ms) → vTaskDelayUntil(&wake, 100) → t=100 准时醒
第 2 次:t=100  → 打印(5ms) → vTaskDelayUntil(&wake, 100) → t=200 准时醒
第 3 次:t=200  → 打印(5ms) → vTaskDelayUntil(&wake, 100) → t=300 准时醒

不管每次打印花了 5ms 还是 50ms,下一次醒来的**绝对时间点不变**!

3:一张图对比

复制代码
vTaskDelay(相对):
  干活→睡100ms→干活→睡100ms→干活→睡100ms→...
  ↑              ↑ 漂了5ms     ↑ 漂了10ms
  节奏越来越偏

vTaskDelayUntil(绝对):
  t=0  t=100  t=200  t=300  t=400  ← 固定的绝对时间点
  │     │      │      │      │
  干活  干活   干活   干活   干活
  ↑     ↑      ↑      ↑      ↑
  每次都在同一个节拍上,不漂移

4:什么时候用哪个

场景 用哪个
无所谓精确周期,随便睡一下 vTaskDelay(100)
周期性采集数据(ADC 采样) vTaskDelayUntil()
周期性发送心跳包 vTaskDelayUntil()
按键扫描、LED 闪烁 vTaskDelay(10) 够用了

一句话vTaskDelay = "从刚才睡醒开始倒计时",每次会漂移。vTaskDelayUntil = "定好下一次醒来的绝对时间",不管中间折腾了多久,节奏永远不乱。

相关推荐
caishenzhibiao5 小时前
期货先行者主图 同花顺期货通指标
java·c语言·c#
NoteStream8 小时前
【c语言基础】C语言常见概念
c语言·开发语言·编辑器
caishenzhibiao8 小时前
先行者副图 同花顺期货通指标
java·c语言·c#
回眸不遇8 小时前
将 Docker虚拟磁盘文件ext.vhdx迁移出C盘 ,更换到D盘
c语言·docker·容器
冻柠檬飞冰走茶8 小时前
PTA基础编程题目集 7-27 冒泡法排序(C语言实现)
c语言·开发语言·数据结构·算法
wuyk5558 小时前
77.嵌入式 C 语言中 enum 枚举的工程化实践:告别魔法数字,提升代码可维护性
c语言·开发语言·stm32·单片机·嵌入式硬件
lightqjx9 小时前
【Linux系统】基础 IO
linux·c语言·重定向·缓冲区·文件io·文件的系统调用
亿道电子Emdoor10 小时前
【Arm】MSP和PSP的区别简介
c语言·arm开发·单片机·mcu
2301_7779983411 小时前
Linux线程同步与互斥(三):信号量与环形队列实现生产者消费者模型
linux·c语言·c++