5.7 Delay函数
Delay函数
在 FreeRTOS 中,常用的延时函数有两个:
vTaskDelay()vTaskDelayUntil()
它们的共同点是:
都会让任务进入阻塞态,等待一段时间后再变为就绪态。
不同点在于:
vTaskDelay()是相对延时vTaskDelayUntil()是绝对延时
1 vTaskDelay()
函数原型如下:
void vTaskDelay(const TickType_t xTicksToDelay);其中:
xTicksToDelay表示任务要延时的 Tick 数
它的含义是:
从调用 vTaskDelay() 开始,任务至少要等待指定个数的 Tick 中断,之后才会重新进入就绪态。
例如:
vTaskDelay(500);表示任务至少等待 500 个 Tick。
1.1 特点
vTaskDelay() 是“从当前时刻开始往后延时”。
因此,如果任务在调用延时函数之前执行了一些不确定时长的代码,那么任务的实际运行周期就会受到影响。
也就是说:
使用 vTaskDelay(n) 时,进入和退出延时函数之间的时间间隔至少是 n 个 Tick,但整个任务周期可能会越来越不稳定。
2 vTaskDelayUntil()
函数原型如下:
BaseType_t xTaskDelayUntil(TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement);其中:
pxPreviousWakeTime:上一次被唤醒的时间xTimeIncrement:周期增量,即每次延时多少个 Tick
它的含义是:
任务会阻塞到指定的绝对时刻,等到时间到了才重新进入就绪态。
例如:
vTaskDelayUntil(&preTime, 500);表示任务要等到 preTime + 500 这个时刻才会恢复就绪。
2.1 特点
vTaskDelayUntil() 是按固定时间点唤醒任务,而不是从“当前时刻”重新开始计时。
因此它更适合让任务周期性运行。
也就是说:
使用 vTaskDelayUntil(&pre, n) 时,前后两次任务被唤醒的时间间隔更稳定,更适合做周期任务。
3 两者的区别
3.1 vTaskDelay()
- 相对延时
- 从当前时刻开始计时
- 容易受前面代码执行时间影响
- 周期可能不稳定
3.2 vTaskDelayUntil()
- 绝对延时
- 按固定时刻唤醒
- 周期更稳定
- 更适合周期性任务
4 示例程序分析
本节示例代码的作用是:
比较 vTaskDelay() 和 vTaskDelayUntil() 的实际阻塞时间,并把结果显示在 LCD 上。
核心代码如下:
void LcdPrintTask(void *params)
{
struct TaskPrintInfo *pInfo = params;
uint32_t cnt = 0;
int len;
BaseType_t preTime;
uint64_t t1, t2;
preTime = xTaskGetTickCount();
while (1)
{
if (g_LCDCanUse)
{
g_LCDCanUse = 0;
len = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name);
len += LCD_PrintString(len, pInfo->y, ":");
LCD_PrintSignedVal(len, pInfo->y, cnt++);
g_LCDCanUse = 1;
mdelay(cnt & 0x3);
}
t1 = system_get_ns();
//vTaskDelay(500);
vTaskDelayUntil(&preTime, 500);
t2 = system_get_ns();
LCD_ClearLine(pInfo->x, pInfo->y+2);
LCD_PrintSignedVal(pInfo->x, pInfo->y+2, t2-t1);
}
}5 示例代码的执行过程
5.1 preTime = xTaskGetTickCount();
这句代码先获取当前系统 Tick 计数值,作为第一次唤醒时间的参考点。
也就是说:
preTime 记录了任务开始计时的基准时刻。
5.2 任务先执行打印操作
在 while(1) 循环中,任务先在 LCD 上打印字符串和计数值。
另外还执行了:
mdelay(cnt & 0x3);这表示每次循环前面的处理时间并不完全固定,而是会有一点变化。
这正好可以用来观察两种延时函数在“前置执行时间不固定”时的差别。
5.3 记录延时前后的时间
代码中:
t1 = system_get_ns();
vTaskDelayUntil(&preTime, 500);
t2 = system_get_ns();表示:
t1:进入延时函数前的时间t2:延时结束后的时间
因此:
t2 - t1表示任务实际阻塞的时间长度。
6 为什么这个例子能比较两种 Delay
因为前面有打印和 mdelay(),所以每次调用延时函数之前,任务已经消耗的时间并不固定。
这时:
6.1 如果使用 vTaskDelay(500)
任务会从“当前时刻”开始重新等待 500 个 Tick。
这样一来,每次循环前面花掉的时间都会叠加进去,导致整个周期越来越依赖前面代码执行了多久。
6.2 如果使用 vTaskDelayUntil(&preTime, 500)
任务会始终按固定的绝对时间点唤醒。
即使前面打印和 mdelay() 花的时间有变化,整体周期仍然更稳定。
所以这个例子就是想说明:
vTaskDelayUntil() 更适合周期性任务,因为它能让任务尽量按照固定节拍运行。
7 本节结论
7.1 vTaskDelay()
适合一般延时场景。
它是相对延时,简单直接,但不适合严格周期任务。
7.2 vTaskDelayUntil()
适合周期性任务。
它是绝对延时,能够让任务按照更稳定的周期运行。
8 小结
FreeRTOS 中有两个常用延时函数:vTaskDelay() 和 vTaskDelayUntil()。
前者表示从当前时刻开始延时指定 Tick 数,属于相对延时;后者表示阻塞到指定绝对时刻,属于绝对延时。
在本节示例中,通过记录延时前后的纳秒时间,比较了两种延时方式的实际阻塞效果。结果说明:
如果希望任务周期性、稳定地运行,应优先使用 vTaskDelayUntil()。
