13.5.1 FreeRTOS延时函数
1.延时函数vTaskDelay()
延时函数在应用中非常重要,当执行延时函数的时候,就会进行任务切换,并且此任务就会进入阻塞态,直到延时完成,任务重新进入就绪态。在应用程序中适当引入延时函数可以实现任务间的切换。若使用此函数,则宏INCLUDE_vTaskDelay必须为1。函数代码如下。

主要语句功能如下。
①延时节拍判断,如本例中TicksToDelay=500。
②调用函数vTaskSuspendAll()挂起任务调度器。
③将当前延时的任务添加到延时列表中。
④调用函数xTaskResumeAll()恢复任务调度器。
⑤如果函数xTaskResumeAll()没有进行任务调度,那么在这里就得进行任务调度。调用函数portYIELD_WITHIN_API()进行一次任务调度。此时在就绪列表中还有Task1_led和Task2_led两个任务,根据调度算法,Task2_led的优先级最高,因此获得CPU使用权。
2.添加到延时列表函数prvAddCurrentTaskToDelayedList()
函数prvAddCurrentTaskToDelayedList()用于将当前任务添加到等待列表中,函数的主要代码如下。


主要语句功能如下。
①xConstTickCount=xTickCount:保存当前时钟节拍,xTickCount是全局变量,SysTick节拍定时器每中断一次,变量xTickCount加1。
②调用uxListRemove()函数将当前任务的列表项从就绪列表中移除。如果从就绪列表中删除列表项后,返回值uxNumberOfItems=0,就还从就绪列表uxTopReadyPriority中脱离就绪,同时添加到延时列表pxDelayedTaskList中。
③将当前任务从就绪列表中移除,uxTopReadyPriority中对应位清零,即脱离就绪。portRESET_READY_PRIO RITY()函数原型如下。(https://www.daowen.com)

例如,有3个任务的uxTopReadyPriority=0x0000 0006,要将当前优先级uxPriority=2的任务脱离就绪,计算过程如下。
uxReadyPriorities&=~(1 UL<<uxPriority)
uxReadyPriorities=0x0000 0006&~(1<<0x0000 0002)
uxReadyPriorities=0x0000 0006&0xFFFF FFFB
uxReadyPriorities=0x0000 0002
所以,脱离就绪后,uxTopReadyPriority=0x0000 0002。
注意,本例中由于列表pxReadyTasksLists[2]中有两个列表项,虽然Task3_led的列表项已移出,但优先级为2的列表中还有Task2_led的列表项在pxReadyTasksLists[2]中,所以就绪列表仍不变,uxTopReadyPriority=6。
④延时时间为最大值portMAX_DELAY,并且如果xCanBlockIndefinitely不等于pdFALSE,直接将当前任务添加到挂起列表xSuspendedTaskList中,任务就不用添加到延时列表中。
⑤当前节拍数xConstTickCount加上延时时间值xTicksToWait。
⑥xTimeToWake写入任务列表项xItemValue中。这一项非常重要,优先级的列表项就是通过此值升序排列的,参见图13.12。
⑦和⑧如果xTimeToWake小于xConstTickCount,说明发生了溢出。全局变量xTickCount是32位的数据类型,因此在用xTickCount计算任务唤醒时间点xTimeToWake的时候,肯定会出现溢出的现象。FreeRTOS针对此现象专门做了处理,在FreeROTS中定义了两个延时列表xDelayedTaskList1和xDelayedTaskList2,并且也定义了两个指针pxDelayedTaskList和pxOverflowDelayedTaskList来访问这两个列表。在初始化列表函数prvInitialiseTaskLists()中,指针pxDelayedTaskList指向列表xDelayedTaskList1,指针pxOverflowDelayedTaskList指向列表xDelayedTaskList2。这样一来,如果发生溢出,就将任务添加到pxOverflowDelayedTaskList所指向的列表中,如果发生溢出,就将任务添加到pxDelayedTaskList所指向的列表中。
⑨xNextTaskUnblockTime是个全局变量,保存着距离下一个要取消阻塞态的任务的最小时间点值。如果xTimeToWake小于xNextTaskUnblockTime,说明有个更小的时间点来了。
综上所述,FreeRTOS通过延时函数vTaskDelay()来实现任务的切换,这是一种相对延时模式。还有一种绝对延时函数vTaskDelayUntil(),阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用绝对延时函数vTaskDelayUntil()。
函数vTaskDelayUntil()的原型请读者自行分析,使用方法参考如下代码。

综上所述,根据13.2.1小节应用案例3个任务代码可知,任务Task3_led由于调用系统服务vTaskDelay(500)被阻塞起来,现在FreeRTOS调度优先级更高的任务Task2_led运行,若执行到vTaskDelay(200)处,该任务也会被阻塞起来。由于执行代码的时间很短,所以调度器很快再一次调度任务Task1_led执行,执行到vTaskDelay(100)处,该任务也会被阻塞起来。此时CPU完全空闲,FreeRTOS安排空闲任务prvIdleTask运行。