RTOS系统3-任务管理
参考:FreeRTOS使用教程
关于打印调试信息:
修改FreeRTOSConfig.h中的
//调试信息打印
#define configUSE_TRACE_FACILITY 1 //默认值是0
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 //增加这一行
并且配置print和usart,使usart能打印调试信息,:
static void vTaskLed1(void *pvParameters)
{
uint8_t pcWriteBuffer[500];
while(1)
{
LED1_ON;
vTaskDelay(500);
LED1_OFF;
vTaskDelay(500);
vTaskList((char *)&pcWriteBuffer);
printf("tsak state priority stacks number\r\n");
printf("%s\r\n", pcWriteBuffer);
}
}
1、任务函数
- 任务函数必须返回void,且必须带有一个void指针参数:void *pvParameters
- 例如:void ATaskFunction(void *pvParameters);
- 任务都是自己职权范围内的一个小程序,其具有程序入口,通常会运行在一个死循环中,也不会退出;
- 任务不允许任何形式的返回(绝对不能有return语句,也不能执行的函数末尾,当然也可以运行完任务显示的删除它vTaskDelete(NULL))
- 一个任务函数可以创建若干个任务,任务之间均独立执行;
1.1、创建任务函数:xTaskCreate()
首先看一下该函数的定义:
pvTaskCode:创建的任务将要执行的函数名,该函数定义是包含一个void参数指针;
pcName: 具有描述性的任务名,仅仅用来腐竹调试,RTOS不会调用他
usStackDepth:告诉内核给该任务分配多大的栈空间,单位:字
例如,32位的机器,栈空间宽为32,若usStackDepth值为100,那么会分配3200位空间, 即400字节。
pvParameters:任务函数接受一个指向void的指针(void*),pvParameters的值是传递到任务中的值
uxPriority: 指定任务的优先级,优先级的上限在初始化中就应该设置好
pvCreatedTask:pvCreatedTask用于传出任务的句柄。这个句柄将在 API 调用中对该创建出来的任务进行 引 用,比如改变任务优先级,或者删除任务。如果应用程序中不会用到这个任务的句柄,则 pxCreatedTask 可以被设为 NULL
返回值: 1、pdTRUE 任务创建成果
2、errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 内存堆空间不足
示例1:
按下key1,LED停止闪烁;按下KEY2,LED继续闪烁,工程下载
int main(void)
{
BSP_init();
AppTaskCreate();//创建任务
vTaskStartScheduler();//启动调度器
while(1);
}
顶层任务创建两个任务:
static void AppTaskCreate(void)
{
xTaskCreate(vTaskLed1,
"Task Led1",
512,
NULL,
1,
&xHandleTaskLED1);
xTaskCreate(vTaskKey,"Task Key",512.,NULL,2,&xHandleTaskKey);
}
void vTaskLed1(void *pvParameters)
{
while(1)
{
LED2_ON;
vTaskDelay(500);
LED2_OFF;
vTaskDelay(500);
}
}
//按键任务
void vTaskKey(void *pvParameters)
{
while(1){
if(Key_Scan(KEY1_GPIO_PORT,KEY1_PIN)==KEY_ON)
vTaskSuspend(xHandleTaskLED1);//操作xHandleTaskLED1句柄,控制LED任务
if(Key_Scan(KEY2_GPIO_PORT,KEY2_PIN)==KEY_ON)
vTaskResume(xHandleTaskLED1);
vTaskDelay(20);
}
}
1.2 任务参数
如果需要传入任务参数,程序如下:
static const char *pcTextForTask1="Task1 is running \t\n"
int main(void)
{
xTaskCreate(vTaskFunction,"Task 1",1000,(void*)pcTextForTask1,1,NULL);//传入参数也可以为NULL
vTaskStartScheduler();
}
1.3 任务优先级
1.3.1 优先级数目设置
FreeRTOSConfig.h文件中的设定了最多可具有的优先级数目
#define configMAX_PRIORITIES ( 5 )
/* 任务能使用最大优先级个数,数字越大优先级越
高,范围为:0~configMAX_PRIORITIES-1
低优先级号表示任务的优先级低,优先级号 0 表示最低优先级。
1.3.2 任务切换(滴答时钟)
滴答时钟的配置同样在FreeRTOSConfig.h中设置:
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
/* SysTick中断周期,单位为HZ,1000HZ即1ms中断一次 */
1.4 非运行状态
非运行状态的子状态有:阻塞状态、挂起状态、就绪状态
1.4.0事件驱动
一个事件驱动任务只会在事件发生后触发工作(处理),而在事件没有发生时是不能进入运行态的。调度器总是选择所有能够进入运行态的任务中具有最高优先级的任务。一个高优先级但不能够运行的任务意味着不会被调度器选中,而代之以另一个优先级虽然更低但能够运行的任务。因此,采用事件驱动任务的意义就在于任务可以被创建在许多不同的优先级上,并且最高优先级任务不会把所有的低优先级任务饿死。
1.4.1阻塞状态
如果一个任务正在等待某个事件,则称这个任务处于”阻塞态(blocked)”。
任务可以进入阻塞态以等待以下两种不同类型的事件:
1. 定时(时间相关)事件
这类事件可以是延迟到期或是绝对时间到点。比如说某个任务可以进入阻塞态以延迟 10ms。
2. 同步事件
源于其它任务或中断的事件。比如说,某个任务可以进入阻塞态以等待队列中有数据到来。同步事件囊括了所有板级范围内的事件类型。 (FreeRTOS 的队列,二值信号量,计数信号量,互斥信号量(recursive semaphore,递归信号量, 都可以用来实现同步队列)
任务也可以在进入阻塞态以等待同步事件时指定一个等待超时时间,这样可以有效地实现阻塞状态下同时等待两种类型的事件1.4.2挂起状态
处于挂起状态的任务对调度器而言是不可见的。让一个任务进入挂起状态的唯一办法就是调用 vTaskSuspend() API 函数;而 把 一 个 挂 起 状 态 的 任 务 唤 醒 的 唯 一 途 径 就 是 调 用 vTaskResume() 或vTaskResumeFromISR() API 函数。
1.4.3就绪状态
如果任务处于非运行状态,但既没有阻塞也没有挂起,则这个任务处于就绪(ready,准备或就绪)状态。处于就绪态的任务能够被运行,但只是”准备(ready)”运行,而当前尚未运行。
完整的状态转移图:
1.4.4 阻塞状态示例
1.vTaskDelay(count)函数
这里就需要思考示例1的一个问题:
示例1中LED闪烁的任务优先级为1,按键任务的优先级为2,那么按键任务优先级高于LED任务,所以为什么不一直运行按键任务?
答:按键任务程序末有一段代码 vTaskDelay(20),当运行到该段代码时,任务阻塞,阻塞时间为20个滴答心跳周期,由此执行其他任务,当阻塞结束后,再次返回到按键任务来。
常数 portTICK_RATE_MS 可以用来将以毫秒为单位的时间值转 换为以心跳周期为单位的时间值 |
2.vTaskDelayUntil(&xLastWakeTime,count)函数
区别:
函数 vTaskDelay()的参数用来指定任务在调用 vTaskDelay()到切出阻塞态整个过程包含多少个心跳周期,其任务保持在阻塞态的时间量有函数的入口参数指定,但任务离开阻塞态的时刻实际上是相对于 vTaskDelay()被调用那一刻的。
vTaskDelayUntil()的参数就是用来指定任务离开阻塞态进入就绪态那一刻的精确心跳计数值。 由于调用此函数的任务解除阻塞的时间是绝对时刻,比起相对于调用时刻的相对时间更精确(即比调用 vTaskDelay()可以实现更精确的周期性)。 函数调用如下:void vTaskDelayUntil( portTickType * pxPreviousWakeTime, portTickType xTimeIncrement );
示例程序(指示灯任务):
static portTickType xLastWakeTime;
int main(void)
{
BSP_init();
xLastWakeTime=xTaskGetTickCount();//获取心跳值
AppTaskCreate();//创建任务
vTaskStartScheduler();
while(1);
}
//指示灯任务
void vTaskLed1(void *pvParameters)
{
while(1)
{
LED1_ON;
vTaskDelayUntil(&xLastWakeTime,500);
LED1_OFF;
vTaskDelayUntil(&xLastWakeTime,500);
}
}
使用vTaskDelayUntil()API函数能保证任务的执行频率更加精确。1.5空闲任务与钩子函数
当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务 ;空闲任务拥有最低优先级(优先级 0)以保证其不会妨碍具有更高优先级的应用任务进入运行态;运行在最低优先级可以保证一旦有更高优先级的任务进入就绪态,空闲任务就会立即切出运行态。
1.5.1空闲任务钩子函数
通过空闲任务钩子函数(或称回调, hook, or call-back),可以直接在空闲任务中添加应用程序相关的功能。空闲任务钩子函数会被空闲任务每循环一次就自动调用一次。
通常用于:
1、执行低优先级,后台需要不断处理的代码
2、测试系统处理能力的裕量(测量空余时间)
3、将处理器配置到低功耗模式,空闲时自动省电
空闲任务的钩子函数使用规则:
1、决不能阻塞或挂起!
2、如果应用程序用到了 vTaskDelete() AP 函数,则空闲钩子函数必须能够尽快返回。因为在任务被删除后,空闲任务负责回收内核资源。如果空闲任务一直运行在钩子函数中,则无法进行回收工作
函数的使用:
FreeRTOSConfig.h 中的配置常量 configUSE_IDLE_HOOK 必须定义为 1
#define configUSE_IDLE_HOOK 1 //默认为0 ,为1时空闲函数需要自己编写了
空闲任务钩子函数:
void vApplicationIdleHook( void )
{
/* This hook function does nothing but increment a counter. */
ulIdleCycleCount++;
}
1.6改变任务优先级
改变优先级:vTaskPrioritySet() API 函数
查询优先级:uxTaskPriorityGet() API 函数
示例代码:LED1、2交替闪烁
void vTaskLed1(void *pvParameters)
{
unsigned portBASE_TYPE uxPriority;
uxPriority=uxTaskPriorityGet(NULL);//获取当前任务的优先级,所以参数NULL
while(1)
{
LED2_OFF;//绿灯灭
LED1_ON;//红灯亮
for(int i=0;i<10000000;i++)
;
vTaskPrioritySet(xHandleTaskLED2,(uxPriority+1));//此时任务LED2优先级更高,因此会去执行任务 LED2
}
}
void vTaskLed2(void *pvParameters)
{
unsigned portBASE_TYPE uxPriority;
uxPriority=uxTaskPriorityGet(NULL);
while(1)
{
LED1_OFF;
LED2_ON;
for(int i=0;i<10000000;i++)//延时
;
vTaskPrioritySet(NULL,(uxPriority-2));//自身优先级-2,因此参数是NULL,此时任务LED1优先级更高了,因此去执行该任务
}
}
1.7 删除任务
vTaskDelete(xTaskHandle pxTaskToDelete)
pxTaskToDelete:被删除的任务的句柄,删除自身参数为NULL
任务删除后需要由空闲函数来释放被删除任务的内存!
1.8 调度问题
1.8.1固定优先级抢占式调度
特点:
- 每个任务都赋予了一个优先级。
- 每个任务都可以存在于一个或多个状态。
- 在任何时候都只有一个任务可以处于运行状态。
- 调度器总是在所有处于就绪态的任务中选择具有最高优先级的任务来执行。
1.8.2 选择任务优先级
作为一种通用规则,完成硬实时功能的任务优先级会高于完成软件时功能任务的优
先级。但其它一些因素,比如执行时间和处理器利用率,都必须纳入考虑范围,以保证
应用程序不会超过硬实时的需求限制。
1.8.3 协作式调度
FreeRTOS 可以选择采用协作式调度。
采用一个纯粹的协作式调度器,只可能在运行态任务进入阻塞态或是运行态任务显式调用 taskYIELD()时,才会进行上下文切换。任务永远不会被抢占,而具有相同优先级的任务也不会自动共享处理器时间。协作式调度的这作工作方式虽然比较简单,但可能会导致系统响应不够快