FreeRTOS 任务控制
文章由 FreeRTOS 系列博客整理而来,仅为学习记录,如有不妥,请告知。
FreeRTOS 任务控制
FreeRTOS 任务控制 API 函数主要实现任务延时、任务挂起、解除任务挂起、任务优先级获取和设置等功能。
相对延时
函数描述
```c
void vTaskDelay( portTickTypexTicksToDelay )
```
-
调用
vTaskDelay()
函数后,任务会进入阻塞状态,持续时间由vTaskDelay()
函数的参数xTicksToDelay
指定,单位是系统节拍时钟周期。常量portTICK_RATE_MS
用来辅助计算真实时间,此值是系统节拍时钟中断的周期,单位是毫秒。在文件FreeRTOSConfig.h
中,宏INCLUDE_vTaskDelay
必须设置成1,此函数才能有效。 -
vTaskDelay()
指定的延时时间是从调用vTaskDelay()
后开始计算的相对时间。比如vTaskDelay(100)
,那么从调用vTaskDelay()
后,任务进入阻塞状态,经过 100 个系统时钟节拍周期,任务解除阻塞。因此,vTaskDelay()
并不适用与周期性执行任务的场合。此外,其它任务和中断活动,会影响到vTaskDelay()
的调用(比如调用前高优先级任务抢占了当前任务),因此会影响任务下一次执行的时间。API 函数vTaskDelayUntil()
可用于固定频率的延时,它用来延时一个绝对时间。
参数描述
xTicksToDelay
:延时时间总数,单位是系统时钟节拍周期。
用法举例
voidvTaskFunction( void * pvParameters )
{
/* 阻塞500ms. */
constportTickType xDelay = 500 / portTICK_RATE_MS;
for( ;; )
{
/* 每隔500ms触发一次LED, 触发后进入阻塞状态 */
vToggleLED();
vTaskDelay( xDelay );
}
}
绝对延时
函数描述
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_txTimeIncrement );
- 任务延时一个指定的时间。周期性任务可以使用此函数,以确保一个恒定的频率执行。在文件
FreeRTOSConfig.h
中,宏INCLUDE_vTaskDelayUntil
必须设置成1,此函数才有效。 - 这个函数不同于
vTaskDelay()
函数的一个重要之处在于:vTaskDelay()
指定的延时时间是从调用vTaskDelay()
之后(执行完该函数)开始算起的,但是vTaskDelayUntil()
指定的延时时间是一个绝对时间。 - 调用
vTaskDelay()
函数后,任务会进入阻塞状态,持续时间由vTaskDelay()
函数的参数指定,单位是系统节拍时钟周期。因此vTaskDelay()
并不适用于周期性执行任务的场合。因为调用vTaskDelay()
到任务解除阻塞的时间不总是固定的并且该任务下一次调用vTaskDelay()
函数的时间也不总是固定的(两次执行同一任务的时间间隔本身就不固定,中断或高优先级任务抢占也可能会改变每一次执行时间)。 vTaskDelay()
指定一个从调用vTaskDelay()
函数后开始计时,到任务解除阻塞为止的相对时间,而vTaskDelayUntil()
指定一个绝对时间,每当时间到达,则解除任务阻塞。- 应当指出的是,如果指定的唤醒时间已经达到,
vTaskDelayUntil()
立刻返回(不会有阻塞)。因此,使用vTaskDelayUntil()
周期性执行的任务,无论任何原因(比如,任务临时进入挂起状态)停止了周期性执行, 使得任务少运行了一个或多个执行周期,那么需要重新计算所需要的唤醒时间。这可以通过传递给函数的指针参数pxPreviousWake
指向的值与当前系统时钟计数值比较来检测,在大多数情况下,这并不是必须的。 - 常量
portTICK_RATE_MS
用来辅助计算真实时间,此值是系统节拍时钟中断的周期,单位是毫秒。 - 当调用
vTaskSuspendAll()
函数挂起 RTOS 调度器时,不可以使用此函数。
参数描述
pxPreviousWakeTime
:指针,指向一个变量,该变量保存任务最后一次解除阻塞的时间。第一次使用前,该变量必须初始化为当前时间。之后这个变量会在vTaskDelayUntil()
函数内自动更新。xTimeIncrement
:周期循环时间。当时间等于 (*pxPreviousWakeTime + xTimeIncrement
) 时,任务解除阻塞。如果不改变参数xTimeIncrement
的值,调用该函数的任务会按照固定频率执行。
用法举例
//每10次系统节拍执行一次
void vTaskFunction( void * pvParameters )
{
static portTickType xLastWakeTime;
const portTickType xFrequency = 10;
// 使用当前时间初始化变量xLastWakeTime
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
//等待下一个周期
vTaskDelayUntil( &xLastWakeTime,xFrequency );
// 需要周期性执行代码放在这里
}
}
获取任务优先级
函数描述
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask );
获取指定任务的优先级。在文件 FreeRTOSConfig.h
中,宏 INCLUDE_vTaskPriorityGet
必须设置成 1,此函数才有效。
参数描述
xTask
:任务句柄。NULL 表示获取当前任务的优先级。
返回值
返回指定任务的优先级。
用法举例
voidvAFunction( void )
{
xTaskHandlexHandle;
// 创建任务,保存任务句柄
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用句柄获取创建的任务的优先级
if( uxTaskPriorityGet( xHandle ) !=tskIDLE_PRIORITY )
{
// 任务可以改变自己的优先级
}
// ...
// 当前任务优先级比创建的任务优先级高?
if( uxTaskPriorityGet( xHandle ) <uxTaskPriorityGet( NULL ) )
{
// 当前优先级较高
}
}
设置任务优先级
函数描述
void vTaskPrioritySet( TaskHandle_txTask, UBaseType_tuxNewPriority );
- 设置指定任务的优先级。如果设置的优先级高于当前运行的任务,在函数返回前会进行一次上下文切换。在文件
FreeRTOSConfig.h
中,宏INCLUDE_vTaskPrioritySet
必须设置成 1,此函数才有效。
参数描述
xTask
:要设置优先级任务的句柄,为 NULL 表示设置当前运行的任务。uxNewPriority
:要设置的新优先级。
用法举例
voidvAFunction( void )
{
xTaskHandlexHandle;
// 创建任务,保存任务句柄。
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用句柄来提高创建任务的优先级
vTaskPrioritySet( xHandle,tskIDLE_PRIORITY + 1 );
// ...
// 使用NULL参数来提高当前任务的优先级,设置成和创建的任务相同。
vTaskPrioritySet( NULL, tskIDLE_PRIORITY +1 );
}
任务挂起
函数描述
void vTaskSuspend( TaskHandle_txTaskToSuspend );
-
挂起指定任务。被挂起的任务绝不会得到处理器时间,不管该任务具有什么优先级。
-
调用
vTaskSuspend
函数是不会累计的:即使多次调用vTaskSuspend ()
函数将一个任务挂起,也只需调用一次vTaskResume ()
函数就能使挂起的任务解除挂起状态。在文件FreeRTOSConfig.h
中,宏INCLUDE_vTaskSuspend
必须设置成 1,此函数才有效。
参数描述
xTaskToSuspend
:要挂起的任务句柄。为 NULL 表示挂起当前任务。
用法举例
voidvAFunction( void )
{
xTaskHandlexHandle;
// 创建任务,保存任务句柄.
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用句柄挂起创建的任务.
vTaskSuspend( xHandle );
// ...
// 任务不再运行,除非其它任务调用了vTaskResume(xHandle )
//...
// 挂起本任务.
vTaskSuspend( NULL );
// 除非另一个任务使用handle调用了vTaskResume,否则永远不会执行到这里
}
恢复挂起的任务
函数描述
void vTaskResume( TaskHandle_txTaskToResume );
- 恢复挂起的任务。通过调用一次或多次
vTaskSuspend()
挂起的任务,可以调用一次vTaskResume ()
函数来再次恢复运行。在文件FreeRTOSConfig.h
中,宏INCLUDE_vTaskSuspend
必须置 1,此函数才有效。
参数描述
xTaskToResume
:要恢复运行的任务句柄。
用法举例
voidvAFunction( void )
{
xTaskHandle xHandle;
// 创建任务,保存任务句柄
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用句柄挂起创建的任务
vTaskSuspend( xHandle );
// ...
//任务不再运行,除非其它任务调用了vTaskResume(xHandle )
//...
// 恢复挂起的任务.
vTaskResume( xHandle );
// 任务再一次得到处理器时间
// 任务优先级与之前相同
}
恢复挂起的任务(在中断服务函数中使用)
函数描述
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume );
- 用于恢复一个挂起的任务,用在 ISR 中。通过调用一次或多次
vTaskSuspend()
函数而挂起的任务,只需调用一次xTaskResumeFromISR()
函数即可恢复运行。 xTaskResumeFromISR()
不可用于任务和中断间的同步,如果中断恰巧在任务被挂起之前到达,这就会导致一次中断丢失(任务还没有挂起,调用xTaskResumeFromISR()
函数是没有意义的,只能等下一次中断)。这种情况下,可以使用信号量作为同步机制。在文件FreeRTOSConfig.h
中,宏INCLUDE_vTaskSuspend
和INCLUDE_xTaskResumeFromISR
必须设置成1,此函数才有效。
参数描述
xTaskToResume
:要恢复运行的任务句柄。
返回值
如果恢复任务后需要上下文切换返回 pdTRUE,否则返回 pdFALSE。由 ISR 确定是否需要上下文切换。
用法举例
xTaskHandlexHandle; //注意这是一个全局变量
void vAFunction( void )
{
// 创建任务并保存任务句柄
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ... 剩余代码.
}
void vTaskCode( void *pvParameters )
{
for( ;; )
{
// ... 在这里执行一些其它功能
// 挂起自己
vTaskSuspend( NULL );
//直到ISR恢复它之前,任务会一直挂起
}
}
void vAnExampleISR( void )
{
portBASE_TYPExYieldRequired;
// 恢复被挂起的任务
xYieldRequired = xTaskResumeFromISR(xHandle );
if( xYieldRequired == pdTRUE )
{
// 我们应该进行一次上下文切换
// 注: 如何做取决于你具体使用,可查看说明文档和例程
portYIELD_FROM_ISR();
}
}