FreeRTOS-任务操作
- 有了前两节系统配置和任务基础了,接下来我们就使用FreeRTOS提供的一些API完成简单的任务操作。
一、任务创建和删除(静态方法)
在创建静态任务前,我们需要明确以下几点。
- FreeRTOS是一个轻量级操作系统,在运行操作系统的时候,我们需要为该操作系统分配一定空间,我们称为系统总堆。
- FreeRTOS每个任务都相当于一个函数,所以任务函数也需要有函数名、函数类型、形参。
- 由于每个任务都需要有自己独立的堆栈,所以需要从系统总堆中为其分配任务堆栈,堆栈大小由用户来指定。
- 每个任务又需要有任务控制块,而任务控制块也需要一定空间,这一部分空间来自于任务堆栈。
- 我们在前面提到过,FreeRTOS中有两个特殊的任务,一个是空闲任务,一个是定时器任务。其中空闲任务优先级最低,定时器任务优先级最高,而且一般我们都会让这两个特殊任务单独占用一个优先级。
- 所谓静态,其实就是静态管理内存,内存申请由开发者来完成。
- 经过上面的分析,接下来我们就开始分析如何用FreeRTOS提供的API完成上述任务。
- 为了能够使操作系统正常运行我们为操作系统分配一定空间,在FreeRTOSConifg.h文件中找到"configTOTAL_HEAP_SIZE"
这个宏,它指定了系统总堆栈大小,堆栈的分配由heap.c文件中的函数来操作。这里我们将其大小设置为20*1024,即20k。 - 下图是FreeRTOS静态任务创建函数定义。
从上面定义可以看出,首先需要定义configSUPPORT_STATIC_ALLOCATION,所以我们需要在"FreeRTOSConfig.h"文件中将configSUPPORT_STATIC_ALLOCATION定义为1,使系统支持静态创建任务。具体裁剪配置原理前面已经讲述过,这里不再赘述。接下来我们分析一下该函数类型、形参、返回值。
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
- TaskHandle_t 是函数的返回值,任务创建成功后,将返回该任务的任务句柄,任务句柄相当于取得了任务的控制权,以后的删除、挂起、解挂等操作都是操作任务句柄实现的。
- TaskFunction_t pxTaskCode 任务地址,即把创建任务名传进去即可。
- const char * const pcName 任务标识符,可以随便设置,但一般与任务名相同。
- const uint32_t ulStackDepth 任务堆栈大小
- void * const pvParameters 传递给任务函数的参数,一般无,即NULL
- UBaseType_t uxPriority 任务优先级
- StackType_t * const puxStackBuffer 为任务分配的堆栈
- StaticTask_t * const pxTaskBuffer 任务控制块
3.知道了静态任务创建函数各参数意义之后,接下来我们就开始创建任务,首先我们根据task.h文件中官方定义的任务框架如下图所示。
则我们在这里同样定义三个任务,一个开始任务,两个具体执行任务,开始任务中创建执行任务,因为执行任务只需要创建一次,所以开始任务创建一次后即删除。执行任务分别为控制LED0以0.5s的间隔状态反转执行5次后删除任务2,控制LED1以0.5s状态转换。所以函数分别定义如下:
/*开始任务*/
void start_task(void* pvParameters)
{
Task2_Task_Handler= xTaskCreateStatic((TaskFunction_t) task1,//任务名
(char* ) "task1",//任务表示符
(uint32_t ) TASK1_STACK_SIZE,//堆栈大小
(void* ) NULL,
(UBaseType_t ) TASK1_TASK_PRIO,//任务优先级
(StackType_t* ) task1Stack,//任务堆栈,实际就是一数组
(StaticTask_t* ) &task1TaskBuffer); //任务控制块
Task2_Task_Handler = xTaskCreateStatic((TaskFunction_t) task2,
(char* ) "task2",
(uint32_t ) TASK2_STACK_SIZE,
(void* ) NULL,
(UBaseType_t ) TASK2_TASK_PRIO,
(StackType_t* ) task2Stack,
(StaticTask_t* ) &task2TaskBuffer);
vTaskDelete(Start_Task_Handler); //任务创建只需要一次,所以创建完毕后立刻删除开始任务
//这里也可以用vTaskDelete(NULL);当不输入参数时默认删除当前任务
}
/*任务1 0.5s为间隔LED闪烁,执行5次后删除task2*/
void task1(void* pvParameters)
{
u8 t1_num=0;
while(1)
{
t1_num++;
if (t1_num == 5)
{
vTaskDelete(Task2_Task_Handler);//删除任务2
}
LED0 = ~LED0;
vTaskDelay(500);//FreeRTOS延时函数,延时期间会引起任务调度
}
}
/*任务2 0.5s为间隔LED闪烁*/
void task2(void* pvParameters)
{
while(1)
{
LED1 = ~LED1;
vTaskDelay(500);//FreeRTOS延时函数,延时期间会引起任务调度
}
}
- 上面的函数并不复杂,只是出现了很多定义的变量,现将其变量定义部分列出如下。
TaskHandle_t Start_Task_Handler;//开始任务句柄
#define START_STACK_SIZE 100 //开始任务堆栈大小
#define START_TASK_PRIO 1 //开始任务优先级
StackType_t startStack[START_STACK_SIZE];//开始任务堆栈
StaticTask_t startTaskBuffer; //开始任务控制块
void start_task(void* pvParameters); //函数声明
TaskHandle_t Task1_Task_Handler;//task1任务句柄
#define TASK1_STACK_SIZE 100 //task1 堆栈大小
#define TASK1_TASK_PRIO 3 //task1 优先级
StackType_t task1Stack[TASK1_STACK_SIZE];//task1 堆栈
StaticTask_t task1TaskBuffer; //task1 控制块
void task1(void* pvParameters); //task1 声明
TaskHandle_t Task2_Task_Handler;
#define TASK2_STACK_SIZE 100
#define TASK2_TASK_PRIO 2
StackType_t task2Stack[TASK2_STACK_SIZE];
StaticTask_t task2TaskBuffer;
void task2(void* pvParameters);
- 接下来就是main函数里的内容了
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
//创建task1 和task2 创建完成后删除start_task
Start_Task_Handler = xTaskCreateStatic((TaskFunction_t) start_task,
(char* ) "start_task",
(uint32_t ) START_STACK_SIZE,
(void* ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(StackType_t* ) startStack,
(StaticTask_t* ) &startTaskBuffer);
vTaskStartScheduler(); //开启任务调度
}
- 主函数并不复杂,通过调用start_task创建task1和task2两个任务,并删除start_task任务,以免重复创建。之后开启任务调度。
- 到了这里,任务创建就完成了,我们编译一下。
- 编译器报了两个错误(当我们支持了静态创建任务的时候就需要补充这两个函数的定义),意思是这两个函数未定义,通过观察发现,这两个函数是与空闲任务内存和定时器任务内存相关的。前面我们说过,空闲任务和定时器任务是两个特殊任务,所以我们同样需要为其分配内存。追溯其函数原型如下:
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,//任务控制块
StackType_t **ppxIdleTaskStackBuffer, //任务堆栈
uint32_t *pulIdleTaskStackSize )//任务堆栈大小
所以补充函数定义如下
static StackType_t IdleTaskStackBuffer[configMINIMAL_STACK_SIZE]; //空闲任务堆栈指针
static StaticTask_t IdleTaskTCBBuffer; //空闲任务控制块
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &IdleTaskTCBBuffer;//空闲任务控制块
*ppxIdleTaskStackBuffer = IdleTaskStackBuffer;//空闲任务堆栈
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; //空闲任务堆栈大小
}
- 定时器任务同样作此处理,这里不再赘述了。这样经过处理后,再次编译,没有报错。这样静态任务创建和删除测试就全部完成了。
2、任务创建和删除(动态方法)
- 相比上面的静态任务创建,动态方法创建任务就简单很多。主要体现在以下几点。
- 动态方法创建任务时,内存都有操作系统来管理,用户只需要指定申请空间大小和优先级即可。
- 动态方法创建任务不需要用户再为空闲任务和定时器任务申请空间,操作系统已经申请好了,申请空间大小和任务优先级可以在FreeRTOS.h文件中来修改和配置。
- 有了前面静态方法创建任务的基础,接下来我们分析一下动态方法创建任务所调用的API。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //任务名
const char * const pcName,//任务标识符
const uint16_t usStackDepth,//任务堆栈大小
void * const pvParameters,//任务形参
UBaseType_t uxPriority,//任务优先级
TaskHandle_t * const pxCreatedTask )//任务句柄
- 相比静态方法创建任务,动态方法创建任务的API更为简单了。这里直接给出相关代码,不再进行分析。
#define START_TASK_SIZE 120 // 开始任务堆栈大小
#define START_TASK_PRIO 1 // 开始任务优先级
TaskHandle_t StartTask_Handler;// 开始任务句柄
void StartTask(void* pvParameters);
#define TASK1_TASK_SIZE 120
#define TASK1_TASK_PRIO 2
TaskHandle_t Task1_Handler;
void Task1(void* pvParameters);
#define TASK2_TASK_SIZE 120
#define TASK2_TASK_PRIO 3
TaskHandle_t Task2_Handler;
void Task2(void* pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
//创建开始任务
xTaskCreate((TaskFunction_t )StartTask, //任务函数
(const char* )"StartTask", //任务名称
(uint16_t )START_TASK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
void StartTask(void* pvParameters)
{
xTaskCreate((TaskFunction_t )Task1, //任务函数
(const char* )"Task1", //任务名称
(uint16_t )TASK1_TASK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK1_TASK_PRIO, //任务优先级
(TaskHandle_t* )&Task1_Handler); //任务句柄
xTaskCreate((TaskFunction_t )Task2, //任务函数
(const char* )"Task2", //任务名称
(uint16_t )TASK2_TASK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK2_TASK_PRIO, //任务优先级
(TaskHandle_t* )&Task2_Handler); //任务句柄
vTaskDelete(StartTask_Handler);
}
void Task1(void* pvParameters)
{
u8 i=0;
while(1)
{
i++;
printf("Task1 running %d\r\n",i);
if (i==20)
{
vTaskDelete(Task2_Handler);
printf("Task2 Deleted!!!\r\n");
}
LED0 = ~LED0;
vTaskDelay(88);
}
}
void Task2(void* pvParameters)
{
u8 i=0;
while(1)
{
i++;
printf("Task2 running %d\r\n",i);
LED1 = ~LED1;
vTaskDelay(88);
}
}
- 代码逻辑很简单,task1每隔88ms LED状态翻转一次,翻转20次后删除task2,task2每隔88ms LED状态翻转一次,start_task中创建了task1和task2两个任务,创建完成后start_task任务删除。主函数中创建start_task任务后就开始调度器调度任务。
3、任务挂起和解挂
- 任务挂起
/*函数声明*/
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
任务挂起函数很简单,形参为任务句柄,当我们需要挂起某任务时,只需要传入该任务的形参,则该任务就会被挂起,调度器不会再调用该任务。
- 任务解挂
/*函数声明*/
void vTaskResume( TaskHandle_t xTaskToResume )
任务解挂同样也很简单,只需要传入任务句柄,就能将挂起的函数恢复,从而使调度器能够调用该任务。
到这里我们就将FreeRTOS任务相关的基础部分讲述完了。我们可以看到FreeRTOS为我们提供了静态和动态两种方法来创建任务,相对来说,动态方法创建任务更简单快捷,也是今后常用的一种方法。任务句柄是任务的控制器,我们利用任务句柄完成对任务的一系列操作。