为什么FreeRTOS中不用普通delay函数,而多使用vTaskDelay和vTaskDelayUntil
摘要:本文对比了FreeRTOS中的vTaskDelay、vTaskDelayUntil与普通延时函数的区别。普通延时采用忙等待,完全占用CPU;vTaskDelay是相对延时,让出CPU执行权;vTaskDelayUntil实现绝对延时,保证周期任务精确执行。选择建议:非周期性任务(如按键消抖)使用vTaskDelay,周期性任务(如传感器采集)必须用vTaskDelayUntil以避免周期漂移
摘要:本文对比了FreeRTOS中的vTaskDelay、vTaskDelayUntil与普通延时函数的区别。普通延时采用忙等待,完全占用CPU;vTaskDelay是相对延时,让出CPU执行权;vTaskDelayUntil实现绝对延时,保证周期任务精确执行。选择建议:非周期性任务(如按键消抖)使用vTaskDelay,周期性任务(如传感器采集)必须用vTaskDelayUntil以避免周期漂移。核心判断依据是任务是否需要固定周期重复执行。
目录
一、vTaskDelay、vTaskDelayUntil与普通延时函数的区别
一、vTaskDelay、vTaskDelayUntil与普通延时函数的区别
1.delay(ms):
普通延时函数通常基于 SysTick 定时器的忙等延时(也叫 “自旋等待”),通过空循环消耗时间,期间 CPU 被完全占用,无法执行其他任务;
void delay(uint32_t ms) {
uint32_t start = get_tick();
while (get_tick() - start < ms); // 阻塞式等待
}
2.vTaskDelay:
将当前任务置于阻塞状态(Blocked State),并等待指定数量的时钟节拍(Ticks)。FreeRTOS 调度器会切换到其他就绪的任务执行,直到延时时间到期,该任务才重新进入 “就绪态” 等待调度。
特点:(1)相对延时:从调用时刻开始计算延时时间。
(2)主动让出CPU:任务阻塞期间,调度器会切换到其他就绪任务
void vTaskDelay(const TickType_t xTicksToDelay);//函数原型
vTaskDelay(100 / portTICK_PERIOD_MS); // 使用函数时,如:延时100ms
3.vTaskDelayUntil:
基于上一次唤醒时间,计算下一次唤醒时刻,实现固定周期执行。
特点:(1)绝对延时:自动补偿任务执行时间,保证周期稳定性。
(2)防累积误差:适用于周期性任务(如每100ms执行一次)
//函数原型
void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement);
//函数使用例子
TickType_t last_wake_time = xTaskGetTickCount();//系统时间查询函数
while (1) {
// 任务逻辑,这个任务可能执行1ms,5ms或10ms等
vTaskDelayUntil(&last_wake_time, 100 / portTICK_PERIOD_MS); // 精确周期执行
}
注:如果执行完任务只花了10ms,此时时间是xTaskGetTickCount()+10,精确周期执行是100ms,会再等待到xTaskGetTickCount()+100,才结束延时。
也就是说这个例子下,执行完任务后,未到xTaskGetTickCount()+100,都会等到这个时间才结束循环
边界情况处理:如果你的周期性任务执行时间超过了设定的周期(比如 100ms 周期的任务,执行时间用了 120ms),vTaskDelayUntil会立刻返回(不会阻塞),任务会 “赶工” 执行 —— 此时需要在项目中做容错处理
4. 核心区别总结
| 特性 | 普通延时 | vTaskDelay |
vTaskDelayUntil |
|---|---|---|---|
| CPU占用 | 100%占用 | 0%(任务阻塞) | 0%(任务阻塞) |
| 延时类型 | 绝对阻塞 | 相对延时 | 绝对延时(周期补偿) |
| 适用场景 | 裸机程序 | 非周期性等待 | 周期性任务 |
| 调度影响 | 无任务切换 | 触发任务切换 | 触发任务切换 |
二、延时函数如何的选择

1.非周期性延时 → 选 vTaskDelay
当你需要的是一次性、偶发、无固定周期的延时,比如 “某个事件触发后延时一段时间再执行下一步”,优先用vTaskDelay—— 它用法简单,无需额外初始化。
实际项目示例:
按键按下后延时消抖(单次触发,无需固定周期);
设备上电初始化后,等待传感器就绪(仅初始化阶段执行一次);
串口发送指令后,延时等待从机响应(偶发,响应后才会再次触发);
错误处理时,延时几秒后重试(非周期性重试)。
2.周期性任务 → 选 vTaskDelayUntil
当你需要任务以固定周期重复执行(比如每隔 100ms 采集一次传感器、每隔 10ms 执行一次 PID 控制),必须用vTaskDelayUntil—— 它能抵消任务执行时间的影响,保证周期精准,避免 “周期漂移”。
举个直观的例子:如果用vTaskDelay(100)实现 100ms 周期的传感器采集,假设采集逻辑执行了 10ms,那么实际周期会变成10ms(执行)+ 100ms(延时)= 110ms,每次执行都会漂移;而vTaskDelayUntil会自动调整延时时间(100ms - 10ms = 90ms),最终周期仍严格是 100ms。
实际项目示例:
传感器定时采集(如 100ms / 次、1s / 次);
闭环控制(如 PID 算法 10ms / 次);
显示屏刷新(如 50ms / 次);
数据上报到服务器(如 5s / 次);
电机转速检测(如 20ms / 次)。
总结:
(1)非周期性、单次 / 偶发的延时需求(如按键消抖、初始化等待),优先选vTaskDelay,用法简单且满足需求;
(2)需要精准固定周期执行的任务(如传感器采集、PID 控制),必须选vTaskDelayUntil,避免周期漂移;
(3)核心判断依据:是否需要任务以固定周期重复执行—— 是则用vTaskDelayUntil,否则用vTaskDelay。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)