FreeRTOS之:任务管理
/* Type definitions. */
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE long
typedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
需要包含的头文件:#include "task.h"
xTaskCreate函数原型:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
参数:
①pxTaskCode,是一个任务的函数指针
typedef void (*TaskFunction_t)( void * );
②pcName,任务的名字,有最大长度限制,包括 ‘\0’结束符,最大长度是 config_MAX_TASK_NAME_LEN,如果超过最大,会自动截断
③usStackDepth,指定任务的栈的大小,单位是字(word)= 4 字节,不是字节(byte),用户通过定义 configMINIMAL_STACK_SIZE 来决定空闲任务用的栈空间大小
④pvParameters,传递到创建任务的函数中的参数值
⑤uxPriority,任务的优先级,取值[0,configMAX_PRIORITIES-1],没有最大限制,configMAX_PRIORITIES 变量值时系统之前设置好的,如果优先级取值大于区间,将会取区间里的最大值
⑥pxCreatedTask,用于传出任务的句柄,可以用来改变任务的优先级,或者删除任务,如果用不到,用户可以设为 NULL
返回值:
pdTRUE,创建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,创建失败,一般是由于内存堆空间不足所致
void func(void * pvParameters);
【1】函数中没有return
【2】一个任务函数可以用来创建若干个任务,创建的每个任务都有独立的栈空间
【3】函数模板:
func(void * pvParameters)
{
...
for(;;)
{
}
vTaskDelete(NULL);/* 一般不会执行到这里,传入 NULL 表示删除当前任务 */
}
需要包含的头文件:task.h
vTaskDelete函数原型:
void vTaskDelete( TaskHandle_t xTaskToDelete )
参数:
xTaskToDelete,要删除的任务,是NULL时,删除自己
说明:
【1】需要配置 INCLUDE_vTaskDelete = 1,才能使用这个函数,从 RTOS 实时内核管理中移除任务,要删除的任务将从就绪、封锁、挂起事件列表中移除。
【2】空闲任务负责释放内核分配的内存,任务自己占用的内存需要应用程序自己显示的释放。
需要包含头文件:task.h
vTaskDelay函数原型:
void vTaskDelay( const TickType_t xTicksToDelay )
参数:
xTicksToDelay,延时多少个心跳周期,延迟的任务进入阻塞态,经过指定的心跳周期后,转移到就绪态,将毫秒单位转换成心跳周期单位使用常量 portTICK_RATE_MS ,比如要延时 250ms,则 xTicksToDelay = 250 / portTICK_RATE_MS
需要包含头文件:task.h
vTaskDelayUntil函数原型:
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement )
参数:
pxPreviousWakeTime,用于保存任务上一次离开阻塞态的时刻,这个时刻用于作为参考点计算任务下一次离开阻塞态的时刻,这个值自动更新,只需要付初始值即可,一般是 pxPreviousWakeTime = xTaskGetTickCount()
xTimeIncrement,循环周期时间,要延时 250ms,则 xTimeIncrement = 250 / portTICK_RATE_MS
说明:
【1】需要配置 INCLUDE_vTaskDelayUntil = 1,才能使用这个函数
【2】使用的时候,需要将这个函数放在循环中例如for(;;){vPrintString(pcTaskName);vTaskDelayUntil(...);}
【3】执行完任务后,任务本身进入阻塞态,等待时间的到达
【4】既然要作为周期任务,优先级可能要设置成最高才能实现
需要包含头文件:task.h
vTaskSuspend函数原型:
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
参数:
xTaskToSuspend,要挂起的任务句柄,是NULL挂起调用此函数的任务
说明:
【1】需要配置 INCLUDE_vTaskSuspend = 1 ,才能使用这个函数
【2】挂起的任务不再占用控制器的处理时间
需要包含的头文件:task.h
vTaskResume函数原型:
void vTaskResume( TaskHandle_t xTaskToResume )
参数:
xTaskToResume,要唤醒的任务句柄,唤醒(7)的任务
说明:
【1】需要配置 INCLUDE_vTaskSuspend = 1 才能使用此函数
需要包含的头文件:task.h
xTaskResumeFromISR函数原型:
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )
参数:
xTaskToResume,要唤醒的任务句柄
返回值:
pdTRUE,成功
pdFALSE,失败
说明:
【1】需要配置 INCLUDE_xTaskResumeFromISR = 1 和 INCLUDE_vTaskSuspend = 1 才能使用此函数
【2】当唤醒成功将引起上下文切换,失败用于 ISR 确定是否上下文切换
需要包含的头文件:task.h
vTaskSetApplicationTaskTag函数原型:
void vTaskSetApplicationTaskTag( TaskHandle_t xTask, TaskHookFunction_t pxHookFunction )
参数:
xTask,要分配标签的任务,NULL是调用函数的任务
pxHookFunction,分配给任务的标签值
说明:
【1】需要配置 configUSE_APPLICATION_TASK_TAG = 1 才能使用此函数
【2】分配的标签只对应用程序有用,内核不使用
需要包含的头文件:task.h
vTaskStartScheduler函数原型:
void vTaskStartScheduler( void )
说明:
【1】当此函数调用时,还有一个空闲任务自动被创建,这个函数调用成功后不会返回,直到执行任务调用 vTaskEndScheduler
【2】函数调用失败的可能原因是可供给空闲任务的 RAM 不足
需要包含头文件:task.h
vTaskEndScheduler函数原型:
void vTaskEndScheduler( void )
说明:
【1】所有创建的任务将自动删除,并且多任务将停止
【2】此函数导致所有由内核分配的资源释放,但是由应用程序分配的资源需要应用程序自己去释放
vTaskSuspendAll函数原型:
void vTaskSuspendAll( void )
说明:
【1】任务调用此函数后,此任务将继续执行,不会有任何被切换的危险,直到调用 xTaskResumeAll() 函数重启内核
【2】当调用了此函数后,之后将不能在出现影响上下文切换的的函数(比如:vTaskDelayUntil(),xQueueSend()等)
需要包含的头文件是:task.h
uxTaskPriorityGet函数原型:
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
参数:
xTask,需要操作的任务,NULL表示自己
返回值:
任务的优先级
说明:
【1】此函数的使用需要配置 INCLUDE_uxTaskPriorityGet = 1
需要包含头文件:task.h
vTaskPrioritySet函数原型:
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
参数:
xTask,任务
uxNewPriority,要设置的优先级
说明:
【1】需要设置 INCLUDE_vTaskPrioritySet = 1
【2】如果更改的优先级高于当前的任务的优先级,上下文的切换发生在此函数返回前
要能够选择下一个运行的任务,调度器需要在每个时间片的结束时刻运行自己本身。一个称为心跳(tick,有些地方被称为时钟滴答,本文中一律称为时钟心跳)中断的周期性中断用于此目的。时间片的长度通过心跳中断的频率进行设定,心跳中断频率由FreeRTOSConfig.h 中的编译时配置常量 configTICK_RATE_HZ 进行配置。比如说,如果 configTICK_RATE_HZ 设为 1000(HZ),则时间片长度为 1ms。
心跳计数(tick count)值表示的是从调度器启动开始,心跳中断的总数,并假定心跳计数器不会溢出。用户程序在指定延迟周期时不必考虑心跳计数溢出问题,因为时间连
贯性在内核中进行管理。
将调度器本身的执行时间在整个执行流程中体现出来的过程如下图中红色部分
任务的运行状态被分为了 Running、Ready、Blocked、Suspended。
Running可以被翻译成 运行状态
这种状态表示内核正在执行的状态
Ready被翻译成 准备好状态
这种状态表示任务准备好了,只要由内核调用,就能当下执行
Blocked被翻译成 阻塞状态
处于等待某个事件的任务叫做阻塞状态,处于阻塞等待的任务可以被两类事件唤醒:时间超时、其他任务的同步事件,处于此种状态的任务不参与调度。
Suspended被翻译成 暂停状态
处于暂停状态的任务不参与调度,一般应用程序用不到这个状态。
任务所有状态构成的状态机如下:
当调用 vTaskStartScheduler() 函数后,空闲任务就被自动的创建了,空闲任务是一个非常短小的循环,其任务优先级是最小的 0,其他任务可以设置成和空闲任务相同的优先级
通过空闲任务钩子函数,可以直接在空闲任务中添加应用程序相关的功能,空闲任务钩子函数会被空闲任务每循环一次就自动调用一次。空闲任务中可以做的事情可以但不完全是:可以测量设计的系统有多少富裕的处理时间、在系统空闲的时候让系统自动进入省电模式。
空闲任务钩子函数一定要注意的是,当应用程序用到vTaskDelete() 函数,一定要能尽快返回,因为在任务被删除后,空闲任务负责回收资源。
空闲任务钩子函数原型:
void vApplicationIdleHook(void)
说明:
【1】需要配置 configUSE_IDLE_HOOK = 1
【2】函数的名字不能变,一定要是上边函数的名字
【3】当配置了变量/宏,可以使用此函数后,在main.c中直接声明,并实现其中的功能就可以使用了
当我们同时创建了两个相同的任务,然后任务中都有一段250ms的vTaskDelay延时,任务2优先级2,任务1优先级1,同时运行,则执行的图表表示如下:
【1】表中我们可以看到,任务优先级高的却接近了空闲状态,空闲状态下是时间线。
【2】vTaskDelay延时就相当于放弃任务被调度的权利多长时间。
行为说明:
程序实现如下:
void vTask1( void *pvParameters )
{
unsigned portBASE_TYPE uxPriority;
uxPriority = uxTaskPriorityGet( NULL ); // 返回自己的优先级
for( ;; )
{
vPrintString( "Task1 is running\r\n" );
vPrintString( "About to raise the Task2 priority\r\n" );
vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );
}
}
void vTask2( void *pvParameters )
{
unsigned portBASE_TYPE uxPriority;
uxPriority = uxTaskPriorityGet( NULL );
for( ;; )
{
vPrintString( "Task2 is running\r\n" );
vPrintString( "About to lower the Task2 priority\r\n" );
vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
}
}
xTaskHandle xTask2Handle;
int main( void )
{
xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );
vTaskStartScheduler();
for( ;; );
}
图表表示如下:
【1】从图表中可以看出任务高的优先级在高的位置,这种方式,我们理解的更容易
【2】对任务的优先级的再次更改需要任务创建时的任务句柄(最后一个参数)
【3】到这里我们可以看到,图表好的形式如下:
行为说明:
程序实现:
void vContinuousProcessingTask( void *pvParameters )
{
char *pcTaskName;
pcTaskName = ( char * ) pvParameters;
for( ;; )
{
vPrintString( pcTaskName );
}
}
void vPeriodicTask( void *pvParameters )
{
portTickType xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
vPrintString( "Periodic task is running\r\n" );
vTaskDelayUntil( &xLastWakeTime, ( 10 / portTICK_RATE_MS ) );
}
}
图表表示如下:
【1】两个相同优先级的任务在图中也分了高低,是为了好区分
【2】此图表表示的不是简单的创建两个相同的打印的函数,至少,保证了打印能完成才去调度,在图中红色段也至少含有几个tick