一、概述
FreeRTOS提供了两个系统延时函数:相对延时函数vTaskDelay()和绝对延时vTaskDelayUntil()。
这两个延时函数和自己实现的延时函数不同,这两个延时函数一旦被调用,当前任务会立马进入阻塞状态,而自己写的延时函数(以for循环等形式实现的软件延时)会被当做有效任务而一直执行。
- 相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束;
- 绝对延时是指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务。换句话说:任务以固定的频率执行。
二、vTaskDelay()
调用vTaskDelay()函数后,任务会进入阻塞状态,vTaskDelay()函数的参数xTicksToDelay表示延时多少个系统节拍时钟周期。在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelay 必须设置成1,此函数才能有效。
const portTickType xDelay = pdMS_TO_TICKS(500);
vTaskDelay(xDelay);
vTaskDelay(pdMS_TO_TICKS(500)); //延迟500ms
vTaskDelay()指定的延时时间是从调用vTaskDelay()后开始计算的相对时间。
比如vTaskDelay(100),那么从调用vTaskDelay()后,任务进入阻塞状态,经过100个系统时钟节拍周期,任务解除阻塞。因此,vTaskDelay()并不适用与周期性执行任务的场合。此外,其它任务和中断活动,会影响到vTaskDelay()的调用(比如调用前高优先级任务抢占了当前任务),因此会影响任务下一次执行的时间。
三、vTaskDelayUntil()
API函数vTaskDelayUntil()可用于固定频率的延时,它用来延时一个绝对时间。
在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelayUntil 必须设置成1,此函数才有效。
这个函数不同于vTaskDelay()函数的一个重要之处在于:vTaskDelay()指定的延时时间是从调用vTaskDelay()之后(执行完该函数)开始算起的,而vTaskDelayUntil()指定一个绝对时间,每当时间到达,则解除任务阻塞。
void vTaskDelayUntil( TickType_t * pxPreviousWakeTime, TickType_t xTimeIncrement );
参数 | 说明 |
pxPreviousWakeTime | 指针,指向一个变量(指针说明这个变量即可以当做输入类型的,也可以当做输出类型的)。该变量保存任务最后一次解除阻塞的时间。第一次使用前,该变量必须初始化为当前时间。之后这个变量会在vTaskDelayUntil()函数内自动更新。 |
xTimeIncrement | 周期循环时间。当时间等于(*pxPreviousWakeTime + xTimeIncrement)时,任务解除阻塞。如果不改变参数xTimeIncrement的值,调用该函数的任务会按照固定频率执行。 |
如果指定的唤醒时间已经达到,vTaskDelayUntil()立刻返回(不会有阻塞)。因此,使用vTaskDelayUntil()周期性执行的任务,无论任何原因(比如,任务临时进入挂起状态)停止了周期性执行,使得任务少运行了一个或多个执行周期,那么需要重新计算所需要的唤醒时间。这可以通过传递给函数的指针参数pxPreviousWake指向的值与当前系统时钟计数值比较来检测,在大多数情况下,这并不是必须的。
使用示例:
总共有四个任务:
idle task:优先级0
task1 :coutinuous task,优先级1
task2:coutinuous task,优先级1
task3:使用vTaskDelayUntil函数的周期性任务,优先级2
其中,task1、task2和task3的任务内容如下:
void vPeriodicTask( void *pvParameters )//task3
{
TickType_t xLastWakeTime;
const TickType_t xDelay3ms = pdMS_TO_TICKS( 3 );
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
vPrintString( "Periodic task is running\r\n" );
vTaskDelayUntil( &xLastWakeTime, xDelay3ms );
}
}
void vContinuousProcessingTask( void *pvParameters )//task2 task1
{
char *pcTaskName;
pcTaskName = ( char * ) pvParameters;
for( ;; )
{
vPrintString( pcTaskName );
}
}
直接结果:
分析:
四、总结
vTaskDelayUntil函数比vTaskDelay函数多了一个记录任务本次被唤醒的时刻的变量,因此如果想要实现控制任务能够周期性运行的话,vTaskDelayUntil函数是一种比较简单的方法。
对于绝对延时函数,如果任务不是最高优先级,则仍然能周期性的将任务解除阻塞,但是解除阻塞的任务不一定能获得CPU权限,因此任务主体代码也不会总是精确周期性执行。
如果要想精确周期性执行某个任务,可以使用系统节拍钩子函数vApplicationTickHook(),它在系统节拍中断服务函数中被调用,因此这个函数中的代码必须简洁。
ref:
https://blog.csdn.net/zhzht19861011/article/details/51705148
https://blog.csdn.net/zhzht19861011/article/details/50454591