| 2. 绝对延时函数vTaskDelayUntil() 
 考虑下面的任务,任务B首先调用绝对延时函数vTaskDelayUntil ()进入阻塞状态,阻塞时间到达后,执行任务主体代码。系统中除了任务B外,还有其它任务,但是任务B的优先级最高。
 
 对于这样一个任务,执行过程如图2-1所示。当任务B获取CPU使用权后,先调用系统延时函数vTaskDelayUntil()使任务进入阻塞状态。任务B进入阻塞后,其它任务得以执行。FreeRTOS内核会周期性的检查任务A的阻塞是否达到,如果阻塞时间达到,则将任务A设置为就绪状态。由于任务B的优先级最高,会抢占CPU,接下来执行任务主体代码。任务主体代码执行完毕后,会继续调用系统延时函数vTaskDelayUntil()使任务进入阻塞状态,周而复始。
 void vTaskB( void * pvParameters )
 
 
 {
 
 
 static portTickType xLastWakeTime;
 
 
 const portTickType xFrequency = pdMS_TO_TICKS(500);
 
 
 
 
 
 // 使用当前时间初始化变量xLastWakeTime ,注意这和vTaskDelay()函数不同
 
 
 xLastWakeTime = xTaskGetTickCount();
 
 
 
 
 
 for( ;; )
 
 
 {
 
 
 /* 调用系统延时函数,周期性阻塞500ms */
 
 
 vTaskDelayUntil( &xLastWakeTime,xFrequency );
 
 
 
 
 
 //  ...
 
 
 //  这里为任务主体代码,周期性执行.注意这和vTaskDelay()函数也不同
 
 
 //  ...
 
 
 
 
 
 }
 
 
 }
 
 
 
 从图2-1可以看出,从调用函数vTaskDelayUntil()开始,每隔固定+
 -周期,任务B的主体代码就会被执行一次,即使任务B在执行过程中发生中断,也不会影响这个周期性,只是会缩短其它任务的执行时间!所以这个函数被称为绝对延时函数,它可以用于周期性的执行任务A的主体代码。
 
 
 图2-1:绝对延时函数执行示意图 函数vTaskDelayUntil()是如何做到周期性的呢,我们来看一下源码。
 
 与相对延时函数vTaskDelay不同,本函数增加了一个参数pxPreviousWakeTime用于指向一个变量,变量保存上次任务解除阻塞的时间。这个变量在任务开始时必须被设置成当前系统节拍中断次数(见上文的任务B举例),此后函数vTaskDelayUntil()在内部自动更新这个变量。
 void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
 
 
 {
 
 
 TickType_t xTimeToWake;
 
 
 BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
 
 
 
 
 
 vTaskSuspendAll();
 
 
 {
 
 
 /* 保存系统节拍中断次数计数器 */
 
 
 const TickType_t xConstTickCount = xTickCount;
 
 
 
 
 
 /* 计算任务下次唤醒时间(以系统节拍中断次数表示)   */
 
 
 xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
 
 
 
 
 
 /* *pxPreviousWakeTime中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了 */
 
 
 if( xConstTickCount < *pxPreviousWakeTime )
 
 
 {
 
 
 /*只有当周期性延时时间大于任务主体代码执行时间,才会将任务挂接到延时列表.*/
 
 
 if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
 
 
 {
 
 
 xShouldDelay = pdTRUE;
 
 
 }
 
 
 }
 
 
 else
 
 
 {
 
 
 /* 也都是保证周期性延时时间大于任务主体代码执行时间 */
 
 
 if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
 
 
 {
 
 
 xShouldDelay = pdTRUE;
 
 
 }
 
 
 }
 
 
 
 
 
 /* 更新唤醒时间,为下一次调用本函数做准备. */
 
 
 *pxPreviousWakeTime = xTimeToWake;
 
 
 
 
 
 if( xShouldDelay != pdFALSE )
 
 
 {
 
 
 /* 将本任务加入延时列表,注意阻塞时间并不是以当前时间为参考,因此减去了当前系统节拍中断计数器值*/
 
 
 prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
 
 
 }
 
 
 }
 
 
 xAlreadyYielded = xTaskResumeAll();
 
 
 
 
 
 /* 强制执行一次上下文切换 */
 
 
 if( xAlreadyYielded == pdFALSE )
 
 
 {
 
 
 portYIELD_WITHIN_API();
 
 
 }
 
 
 }
 
 
 
 由于变量xTickCount可能会溢出,所以程序必须检测各种溢出情况,并且要保证延时周期不得小于任务主体代码执行时间。这很好理解,不可能出现每5毫秒执行一个需要20毫秒才能执行完的任务。
 如果我们以横坐标表示变量xTickCount的范围,则横坐标左端为0,右端为变量xTickCount所能表示的最大值。在如图2-2所示的三种情况下,才可以将任务加入延时列表。图2-2中,*pxPreviousWakeTime和xTimeToWake之间表示任务周期性延时时间,*pxPreviousWakeTime和xConstTickCount之间表示任务B主体代码执行时间。
 图2-2中第一种情况处理系统节拍中断计数器(xConstTickCount)和唤醒时间计数器(xTimeToWake)溢出情况;第二种情况处理唤醒时间计数器(xTimeToWake)溢出情况;第三种情况处理常规无溢出的情况。从图中可以看出,不管是溢出还是无溢出,都要求在下次唤醒任务之前,当前任务主体代码必须被执行完。表现在图2-2中,就是变量xTimeToWake总是大于变量xConstTickCount(每溢出一次的话相当于加上一次最大值Max)。
 
 
 图2-2:将任务加入延时列表的三种情况 计算的唤醒时间合法后,就将当前任务加入延时列表,同样延时列表也有两个。每次系统节拍中断,中断服务函数都会检查这两个延时列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换。
 
 |