记录一下,方便以后翻阅~
有了上一章学到的任务基础知识,本章开始学习如何使用FreeRTOS有关创建任务和删除任务相关的API函数。
1. 任务创建和删除API函数
FreeRTOS最基本的功能是任务管理,而任务管理最基本的操作就是创建和删除任务,相关API函数如下:
1.1 函数xTaskCreate()
此函数用来创建一个任务。
任务需要RAM来保存与任务有关的状态信息(任务控制块),任务也需要一定的RAM来作为任务堆栈。如果使用函数xTaskCreate()来创建任务,那么这些所需的RAM会自动从FreeRTOS的堆中分配,因此必须提供内存管理文件(前章节有说过,默认使用heap_4.c这个内存管理文件),且宏configSUPPORT_DYNAMIC_ALLOCATION必须为1。如果使用函数xTaskCreateStatic()创建,这些RAM需要用户提供。
新创建的任务默认是就绪态,如果当前没有比它更高优先级的任务运行,那么此任务就会立即进入运行态开始运行,不管在任务调度器启动前还是启动后,都可以创建任务。
函数原型如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 任务函数
const char* const pcName, // 任务名字,用于追踪和调试,名字长度不能超过configMAX_TASK_NAME_LEN
const unit16_t usStackDepth, // 任务堆栈大小,实际申请到的大小是usStackDepth的4倍,其空闲任务堆栈大小为configMINIMAL_STACK_SIZE
void* const pvParameters, // 传递给任务函数的参数
UBaseType_t uxPriority, // 任务优先级,0 ~ configMAX_PRIORITIES-1
TaskHandle_t *const pxCreateTask ) // 任务句柄,任务创建后会返回此任务的任务句柄,这个句柄是任务的任务堆栈。此参数用来保存这个任务句柄,其他API函数可能会使用这个句柄
返回值: pdPASS: 任务创建成功。
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任务创建失败,因为堆内存不足!
1.2 函数xTaskCreateStatic()
此函数也是用来创建任务的。
使用此函数创建的任务所需的RAM需要用户来提供。如要使用此函数,将configSUPPORT_STATIC_ALLOCATION定义为1。
函数原型如下:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, // 任务函数
const char * const pcName, // 任务名字,用于追踪和调试,名字长度不能超过configMAX_TASK_NAME_LEN
const uint32_t ulStackDepth, // 任务堆栈大小,由于是静态方法创建任务,任务堆栈由用户给出,此参数是一个数组的大小。
void * const pvParameters, // 传递给任务函数的参数
UBaseType_t uxPriority, // 任务优先级,0 ~ configMAX_PRIORITIES-1
StackType_t * const puxStackBuffer, // 任务堆栈,一般为数组,数组类型为StackType_t类型
StaticTask_t * const pxTaskBuffer ) // 任务控制块
返回值:
NULL: 创建任务失败,puxStackBuffer或pxTaskBuffer为NULL时会导致该错误发生
其他值:创建任务成功,返回任务的任务句柄
1.3 函数xTaskCreateRestricted()
此函数也是用来创建任务的。
此函数要求使用的MCU有MPU(内存保护单元),用此函数创建的任务会受到MPU保护,其他功能和函数xTaskCreate()一样。
函数申明如下:
BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition, TaskHandle_t *pxCreatedTask )
入口参数:
pxTaskDefinition: 指向一个结构体TaskParemeter_t,这个结构体描述了任务的任务函数、堆栈大小、优先级等。在task.h中定义。
pxCreatedTask:任务句柄
返回值:
pdPASS:任务创建成功
其他值:任务创建失败,很有可能是因为FreeRTOS的堆太小
1.4 函数vTaskDelete()
删除一个用函数xTaskCreate()或xTaskCreateStatic()创建的任务,被删除的任务不再存在,不再进入运行态。任务被删除后不能再使用此任务的句柄,如果此任务是用xTaskCreate()创建的,那么在此任务被删除以后,此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此调用函数函数vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。
只有由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数pvPortMalloc()分配了500字节的内存,那么在此任务删除以后用户也必须调用函数vPortFree()将这500字节内存释放掉,否则会导致内存泄漏。
函数原型如下:
vTaskDelete( TaskHandle_t xTaskToDelete )
入口参数:xTaskToDelete: 要删除的任务的任务句柄
返回值:无
2. 任务创建和删除实验(动态方法)
2.1 实验目的
学习xTaskCreate()和vTaskDelete()这两个函数的用法。

2.2 实验设计
设计三个任务:
start_task:用来创建其他两个任务;
task1_task:此任务运行5次以后会调用vTaskDelete()删除任务task2_task,此任务也会控制LED0闪烁,且周期性刷新LCD指定区背景色;
task2_task:此任务为普通的应用任务,控制LED1闪烁,且周期性刷新LCD指定区背景色。
2.3 硬件
1) 正点原子战舰v3开发板(其他板子应该也可以,主要涉及USART,LED,LCD);
2) JLINK仿真器。
2.4 代码解读
如果不考虑usart、led、lcd的函数(不是本章的重点),那么主要的函数都在main.c文件里:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lcd.h"
#include "FreeRTOS.h"
#include "task.h"
#define START_TASK_PRIO 1 // 任务优先级
#define START_STK_SIZE 128 // 任务堆栈大小
TaskHandle_t StartTask_Handler; // 任务句柄
void start_task(void *pvParameters); // 任务函数
#define TASK1_TASK_PRIO 2 // 任务优先级
#define TASK1_STK_SIZE 128 // 任务堆栈大小
TaskHandle_t Task1Task_Handler; // 任务句柄
void task1_task(void *pvParameters); // 任务函数
#define TASK2_TASK_PRIO 3 // 任务优先级
#define TASK2_STK_SIZE 128 // 任务堆栈大小
TaskHandle_t Task2Task_Handler; // 任务句柄
void task2_task(void *pvParameters); // 任务函数
// LCD刷屏时使用的颜色
int lcd_discolor[14]={
WHITE,BLACK,BLUE,BRED,GRED,GBLUE,RED,MAGENTA,GREEN,CYAN,YELLOW,BROWN,BRRED,GRAY };
// 主函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 设置系统中断优先级分组4
delay_init(); // 延时函数初始化
uart_init(115200); // 初始化串口
LED_Init(); // 初始化LED
LCD_Init(); // 初始化LCD
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"WarShip v3");
LCD_ShowString(30,30,200,16,16,"FreeRTOS");
LCD_ShowString(30,50,200,16,16,"Task Creat and Del");
LCD_ShowString(30,70,200,16,16,"leisure");
LCD_ShowString(30,90,200,16,16,"2021/3/14");
// 创建开始任务
xTaskCreate( (TaskFunction_t )start_task, // 任务函数,调用函数xTaskCreate()创建start_task任务
(const char* )"start_task", // 任务名称
(uint16_t )START_STK_SIZE, // 任务堆栈大小
(void* )NULL, // 传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, // 任务优先级
(TaskHandle_t* )&StartTask_Handler); // 任务句柄
vTaskStartScheduler(); // 调用 vTaskStartScheduler()开启FreeRTOS任务调度器,FreeRTOS开始运行
}
// 开始任务任务函数
void start_task(void *pvParameters) // start_task任务的任务函数,此函数中还创建了另外两个任务,start_task任务职责是用来创建其他任务或信号量、消息队列等,创建完成后就可以删除掉该函数
{
taskENTER_CRITICAL(); // 进入临界区
// 创建TASK1任务
xTaskCreate( (TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
// 创建TASK2任务
xTaskCreate( (TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); // 删除开始任务,函数vTaskDelete()参数就是start_task任务的任务句柄!
taskEXIT_CRITICAL(); // 退出临界区
}
// task1任务函数
void task1_task(void *pvParameters) // task1_task任务函数
{
u8 task1_num=0;
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); // 画一个矩形
LCD_DrawLine(5,130,115,130); // 画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"Task1 Run:000");
while(1)
{
task1_num++;
LED0=!LED0;
printf("任务1已经执行:%d次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task2Task_Handler); // 任务1执行5次删除任务2
printf("任务1删除了任务2!\r\n");
}
LCD_Fill(6,131,114,313,lcd_discolor[task1_num%14]); // 填充区域
LCD_ShowxNum(86,111,task1_num,3,16,0x80); // 显示任务执行次数
vTaskDelay(1000); // 延时1s,也就是1000个时钟节拍
}
}
// task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
POINT_COLOR = BLACK;
LCD_DrawRectangle(125,110,234,314); // 画一个矩形
LCD_DrawLine(125,130,234,130); // 画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Task2 Run:000");
while(1)
{
task2_num++;
LED1=!LED1;
printf("任务2已经执行:%d次\r\n",task2_num);
LCD_ShowxNum(206,111,task2_num,3,16,0x80); // 显示任务执行次数
LCD_Fill(126,131,233,313,lcd_discolor[13-task2_num%14]); // 填充区域
vTaskDelay(1000); // 延时1s,也就是1000个时钟节拍
}
}
前面也说了,主要的函数都在main.c文件里,因为本例程里面的任务数量少,如果后期工程较大,建议将任务有关的东西,如任务堆栈、任务句柄、任务函数申明等放到一个专用的头文件里,这样方便管理。
2.5 实验结果
如上图所示,当task1运行5次以内时,task1和task2运行次数一样,当task1运行大于5次时,task2在运行第5次时被task1删除任务了,因此数值不再更新!串口调试助手上实验结果如下所示:
上图中,我们还可以知道一件事:一开始任务1和任务2是同时运行的,但任务2的优先级比任务1高,所以先输出任务2的信息,再输出任务1的信息。当任务1运行5次后删除任务2,之后只剩下任务1在运行。
3. 任务创建和删除实验(静态方法)
3.1 实验目的
学习xTaskCreateStatic()函数用法,本次任务的堆栈、任务控制块由用户来指定。
3.2 实验设计
参考上面的实验设计。
3.3 硬件
参考上面的硬件。
3.4 系统配置
使用静态方法创建任务的时候需要将宏configSUPPORT_STATIC_ALLOCATION设置为1,同时使用静态方法的话需要用户实现两个函数vApplicationGetIdleTaskMemory()和vApplicationGetTimerTaskMemory()。通过这两个函数来给空闲任务和定时器服务任务的任务堆栈和任务控制块分配内存,这两个函数本案例在main.c文件里定义。
3.5 main.c文件代码解读
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "timer.h"
#include "lcd.h"
#include "FreeRTOS.h"
#include "task.h"
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; // 空闲任务任务堆栈
static StaticTask_t IdleTaskTCB; // 空闲任务控制块
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH]; // 定时器服务任务堆栈
static StaticTask_t TimerTaskTCB; // 定时器服务任务控制块
#define START_TASK_PRIO 1 // 任务优先级
#define START_STK_SIZE 128 // 任务堆栈大小
StackType_t StartTaskStack[START_STK_SIZE]; // 任务堆栈,静态创建任务需要提供任务堆栈,这里定义一个数组为任务堆栈,为StackType_t类型
StaticTask_t StartTaskTCB; // 任务控制块,类型为StaticTask_t!
TaskHandle_t StartTask_Handler; // 任务句柄
void start_task(void *pvParameters); // 任务函数
#define TASK1_TASK_PRIO 2 // 任务优先级
#define TASK1_STK_SIZE 128 // 任务堆栈大小
StackType_t Task1TaskStack[TASK1_STK_SIZE]; // 任务堆栈
StaticTask_t Task1TaskTCB; // 任务控制块
TaskHandle_t Task1Task_Handler; // 任务句柄
void task1_task(void *pvParameters); // 任务函数
#define TASK2_TASK_PRIO 3 // 任务优先级
#define TASK2_STK_SIZE 128 // 任务堆栈大小
StackType_t Task2TaskStack[TASK2_STK_SIZE]; // 任务堆栈
StaticTask_t Task2TaskTCB; // 任务控制块
TaskHandle_t Task2Task_Handler; // 任务句柄
void task2_task(void *pvParameters); // 任务函数
// LCD刷屏时使用的颜色
int lcd_discolor[14]={
WHITE,BLACK,BLUE,BRED,GRED,GBLUE,RED,MAGENTA,GREEN,CYAN,YELLOW,BROWN,BRRED,GRAY};
/* 获取空闲任务地任务堆栈和任务控制块内存,因为本例程使用的静态内存,因此空闲任务的任务堆栈和任务控制块的内存就应该由用户来提供,
FreeRTOS提供了接口函数vApplicationGetIdleTaskMemory()实现此函数即可。
ppxIdleTaskTCBBuffer:任务控制块内存
ppxIdleTaskStackBuffer:任务堆栈内存
pulIdleTaskStackSize:任务堆栈大小 */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer=&IdleTaskTCB;
*ppxIdleTaskStackBuffer=IdleTaskStack;
*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}
/* 获取定时器服务任务的任务堆栈和任务控制块内存
ppxTimerTaskTCBBuffer:任务控制块内存
ppxTimerTaskStackBuffer:任务堆栈内存
pulTimerTaskStackSize:任务堆栈大小 */
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskTCBBuffer=&TimerTaskTCB;
*ppxTimerTaskStackBuffer=TimerTaskStack;
*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;
}
// 主函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 设置系统中断优先级分组4
delay_init(); // 延时函数初始化
uart_init(115200); // 初始化串口
LED_Init(); // 初始化LED
LCD_Init(); // 初始化LCD
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"WarShip v3");
LCD_ShowString(30,30,200,16,16,"FreeRTOS");
LCD_ShowString(30,50,200,16,16,"Task Creat and Del");
LCD_ShowString(30,70,200,16,16,"leisure");
LCD_ShowString(30,90,200,16,16,"2021/3/14");
// 创建开始任务
StartTask_Handler=xTaskCreateStatic((TaskFunction_t )start_task, // 任务函数,调用xTaskCreateStatic()函数创建任务
(const char* )"start_task", // 任务名称
(uint32_t )START_STK_SIZE, // 任务堆栈大小
(void* )NULL, // 传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(StackType_t* )StartTaskStack, //任务堆栈,将定义的任务堆栈数组传递给函数
(StaticTask_t* )&StartTaskTCB); //任务控制块,将定义的任务控制块传递给函数
vTaskStartScheduler(); // 开启任务调度
}
// 开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); // 进入临界区
// 创建TASK1任务
Task1Task_Handler=xTaskCreateStatic((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint32_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(StackType_t* )Task1TaskStack,
(StaticTask_t* )&Task1TaskTCB);
// 创建TASK2任务
Task2Task_Handler=xTaskCreateStatic((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint32_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(StackType_t* )Task2TaskStack,
(StaticTask_t* )&Task2TaskTCB);
vTaskDelete(StartTask_Handler); // 删除开始任务
taskEXIT_CRITICAL(); // 退出临界区
}
// task1任务函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); // 画一个矩形
LCD_DrawLine(5,130,115,130); // 画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"Task1 Run:000");
while(1)
{
task1_num++;
LED0=!LED0;
printf("任务1已经执行:%d次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task2Task_Handler);
printf("任务1删除了任务2!\r\n");
}
LCD_Fill(6,131,114,313,lcd_discolor[task1_num%14]); // 填充区域
LCD_ShowxNum(86,111,task1_num,3,16,0x80); // 显示任务执行次数
vTaskDelay(1000); // 延时1s,也就是1000个时钟节拍
}
}
// task2任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
POINT_COLOR = BLACK;
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); // 画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Task2 Run:000");
while(1)
{
task2_num++;
LED1=!LED1;
printf("任务2已经执行:%d次\r\n",task2_num);
LCD_ShowxNum(206,111,task2_num,3,16,0x80); // 显示任务执行次数
LCD_Fill(126,131,233,313,lcd_discolor[13-task2_num%14]); // 填充区域
vTaskDelay(1000); // 延时1s,也就是1000个时钟节拍
}
}
main.c文件里的函数vApplicationGetIdleTaskMemory()和vApplicationGetTimerTaskMemory()不是很复杂,用户定义静态的任务堆栈和任务控制块内存,然后将这些内存传递给函数参数。最后创建空闲任务和定时器服务任务的API函数会调用这两个函数来获取内存。
3.6 实验结果
实验结果与任务创建和删除实验(动态方法)的实验结果一样。