没志青年
发布于 2025-06-16 / 32 阅读
0

FreeRTOS 延时函数

由于FreeRTOS是抢占式的,任务需主动让出CPU,否则会卡死。

vTaskDelay

void vTaskDelay(TickType_t xTicksToDelay);

延时的是系统节拍,需使用 pdMS_TO_TICKS() 宏将毫秒ms转换为系统节拍。时基通常为1ms,所以也不用这个函数转换了,直接写,单位是ms。

适用于非周期性的任务。

为什么这个是相对延时?

另一个任务执行复杂代码的话耗时,等这个任务需要唤醒的时候没唤醒,就比预期唤醒的时间推迟了。

这个是没有精度的,大致的。

vTaskDelayUntil

这个用于一个任务的周期性执行,不会因为某次延时执行,不会累计误差。

vTaskDelayUntil() 的周期是严格准确的(绝对时间准确) ,若时间到了但是被其它任务抢占了,任务会“稍微滞后”执行,但下一个周期仍然会按原计划时间运行。

假设任务周期10ms,如果在某次醒来时被别的高优先级任务抢占:

  • 你可能实际在【第 12ms】才执行

  • 但下一次 vTaskDelayUntil 仍然会使用:LastWakeTime + 10ms

固定的节拍:

10ms, 20ms, 30ms, 40ms …

不会因为某次延时而变成:

12ms, 22ms, 32ms, 42ms …

void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement);

相比于 vTaskDelay,vTaskDelayUntil 可防止由于任务执行时间不同步而造成周期漂移。

比如:

for(;;)
{
    执行逻辑(); // 比如执行花了 200ms
    vTaskDelay(pdMS_TO_TICKS(1000));  // 每次都延时 1000ms
}

这个实际上延时了1200ms,而且每次执行所占用的时间会发生抖动变化,造成周期混乱,并不是周期执行了。

另一种写法:vTaskDelay(100 / portTICK_PERIOD_MS); 表示延时100ms

使用 vTaskDelayUntil 改进:

void vTaskFunction(void *pvParameters)
{
    TickType_t xLastWakeTime;
    const TickType_t xFrequency = pdMS_TO_TICKS(1000);  // 1秒周期

    xLastWakeTime = xTaskGetTickCount(); // 初始化唤醒时间

    for(;;)
    {
        // 执行任务操作
        执行逻辑();

        // 精准周期性延时
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
}