FreeRTOS任务创建与删除
任务的创建与删除本质就是调用API函数
- xTaskCreate()-动态方式创建任务
特点:任务的任务块,以及任务的栈空间所需内存由FreeRTOS自己分配
- xTaskCreateStatic()-静态方式创建任务
特点:任务的任务块,以及任务的栈空间所需内存由用户分配
- vTaskDelete()删除任务,删除创建好的任务
一.xTaskCreate()-动态方式创建任务
整体函数返回值
BaseType_t
:FreeRTOS 定义的基础类型(通常为typedef int BaseType_t
),表示函数执行状态:pdPASS
(或pdTRUE
):任务创建成功。- 错误码(如
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
):内存分配失败或参数错误。
最后一项也就是任务块,作用是操作系统管理任务的数据中心,它存储了任务的所有状态信息
1.任务步骤
2.解释TCB
注意:TCB结构体成员赋值也就是给任务控制块结构体成员赋值,
任务控制块是操作系统管理任务的数据中心,它存储了任务的所有状态信息,包括:
- 执行上下文:CPU 寄存器值(如 PC、SP、R0-R15)。
- 任务状态:就绪、运行、阻塞、挂起等。
- 优先级:决定任务调度顺序。
- 堆栈指针:指向任务专用的堆栈空间。
- 时间片计数:用于时间片轮转调度。
- 阻塞时间:任务等待事件的剩余时间。
- 链表节点:用于挂载到不同状态列表(就绪列表、阻塞列表等)。
3.实例教程
根据上面的第一步:将宏configSUPPORT_DYNAMIC_ALLOCATION配置为1
- 找到FreeRTOSConfig.h文件
一般默认为动态(也就是1)
- 创建任务,使用xTaskCreate
这里定义了一个启动任务,在这个任务创建其它任务。
void freertos_demo(void)
{ xTaskCreate((TaskFunction_t ) start_task, /* 任务入口函数指针 */(char * ) "start_task", /* 任务名称(用于调试) */(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, /* 任务堆栈大小 */(void * ) NULL, /* 传递给任务的参数 */(UBaseType_t ) START_TASK_PRIO, /* 任务优先级 */(TaskHandle_t * ) &start_task_handler /* 任务句柄 */ );/* 启动FreeRTOS调度器 */vTaskStartScheduler();
}
- 创建完之后,需要再定义任务堆栈大小,定义任务优先级、任务句柄
#define START_TASK_PRIO 1 //定义优先级
#define START_TASK_STACK_SIZE 128 //定义堆栈大小,实际占用512字节(32位系统)
TaskHandle_t start_task_handler; //定义句柄
void start_task( void * pvParameters ); //声明一下函数
- 在start_task里面定义要做的事情
void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 进入临界区 */xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );xTaskCreate((TaskFunction_t ) task2,(char * ) "task2",(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(TaskHandle_t * ) &task2_handler );xTaskCreate((TaskFunction_t ) task3,(char * ) "task3",(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK3_PRIO,(TaskHandle_t * ) &task3_handler );vTaskDelete(NULL);taskEXIT_CRITICAL(); /* 退出临界区 */
}
- 最后就是将自己的task1、task2、task3要做的事情再定义一遍,以下是完整代码:
/******************************************************************************************************* @file freertos.c* @author 正点原子团队(ALIENTEK)* @version V1.4* @date 2022-01-04* @brief FreeRTOS 移植实验* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司***************************************************************************************************** @attention** 实验平台:正点原子 F407电机开发板* 在线视频:www.yuanzige.com* 技术论坛:www.openedv.com* 公司网址:www.alientek.com* 购买地址:openedv.taobao.com******************************************************************************************************/#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"/******************************************************************************************************/
/*FreeRTOS配置*//* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128 // 实际占用512字节(32位系统)
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128 // 实际占用512字节(32位系统)
TaskHandle_t task1_handler;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128 // 实际占用512字节(32位系统)
TaskHandle_t task2_handler;
void task2( void * pvParameters );/* TASK3 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
void task3( void * pvParameters );
/******************************************************************************************************//*** @brief FreeRTOS例程入口函数* @param 无* @retval 无*/
void freertos_demo(void)
{ xTaskCreate((TaskFunction_t ) start_task,(char * ) "start_task",(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRIO,(TaskHandle_t * ) &start_task_handler );vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 进入临界区 ,用来关闭中断,任务切换就不会进行*/xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );xTaskCreate((TaskFunction_t ) task2,(char * ) "task2",(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(TaskHandle_t * ) &task2_handler );xTaskCreate((TaskFunction_t ) task3,(char * ) "task3",(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK3_PRIO,(TaskHandle_t * ) &task3_handler );vTaskDelete(NULL);taskEXIT_CRITICAL(); /* 退出临界区 ,开始任务切换*/
}/* 任务一,实现LED0每500ms翻转一次 */
void task1( void * pvParameters )
{while(1){printf("task1正在运行!!!\r\n");LED0_TOGGLE();vTaskDelay(500);}
}/* 任务二,实现LED1每500ms翻转一次 */
void task2( void * pvParameters )
{while(1){printf("task2正在运行!!!\r\n");LED1_TOGGLE();vTaskDelay(500);}
}/* 任务三,判断按键KEY0,按下KEY0删除task1 */
void task3( void * pvParameters )
{uint8_t key = 0;while(1){printf("task3正在运行!!!\r\n");key = key_scan(0);if(key == KEY0_PRES){if(task1_handler != NULL){printf("删除task1任务\r\n");vTaskDelete(task1_handler);task1_handler = NULL;}}vTaskDelay(10);}
}
注意:若是不加入临界区那么,这里的任务顺序是START_TASK_PRIO——>TASK1——>TASK2——>TASK3——>TASK3——>TASK3——>TASK3.......
加入临界区任务顺序是:TASK3——>TASK2——>TASK1——>TASK3——>TASK3......
二、xTaskCreateStatic()-静态方式创建任务
和动态不一样的点在于最后两个,因为是自己创建,所以堆栈空间自然是由自己分配
1.任务步骤
- 需将宏configSUPPORT_STATIC_ALLOCATION 配置为 1
- 定义空闲任务&定时器任务的任务堆栈及TCB
由于静态创建不使用堆内存,FreeRTOS 无法自动为空闲任务分配内存。因此,用户必须通过实现vApplicationGetIdleTaskMemory()
回调函数,手动提供空闲任务所需的 TCB 和堆栈空间。
- 实现两个接口函数 { vApplicationGetIdleTaskMemory( ) 提供空闲任务所需的 TCB 和堆栈空间 ,这个必须要有
{ vApplicationGetTimerTaskMemory ( ) 提供定时器任务的内存
- 定义函数入口参数
- 编写任务函数
注意:
- 空闲任务是系统必需的最低优先级任务,负责回收任务资源和实现低功耗模式。
- 软件定时器任务管理所有软件定时器的回调执行,简化了定时功能的实现。
- 在静态内存分配模式下,必须为这两个任务预分配内存并通过回调函数告知内核,否则系统无法正常启动或定时器功能失效。
2.实例教程
[1]空闲任务配置
调用函数:vApplicationGetIdleTaskMemory
/* 功能: 空闲任务内存分配入口参数:StaticTask_t **(指向任务控制块指针的指针)内核通过这个参数获取预分配的空闲任务控制块(TCB)的地址StackType_t **(指向任务堆栈指针的指针)内核通过这个参数获取预分配的空闲任务堆栈的起始地址。uint32_t *(指向无符号 32 位整数的指针)内核通过这个参数获取预分配的空闲任务堆栈的大小(单位:字,Word)。返回参数: 无
*/void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,StackType_t ** ppxIdleTaskStackBuffer,uint32_t * pulIdleTaskStackSize )
{* ppxIdleTaskTCBBuffer = &idle_task_tcb;* ppxIdleTaskStackBuffer = idle_task_stack;* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
这里一共有三个入口参数 ,都是要指向指针,所以要先提前定义。
/* 空闲任务配置 */
StaticTask_t idle_task_tcb;
StackType_t idle_task_stack[configMINIMAL_STACK_SIZE];
[2]软件定时器任务配置
同理
/* 功能: 软件定时器内存分配入口参数:StaticTask_t **(指向任务控制块指针的指针)内核通过这个参数获取预分配的定时器服务任务控制块(TCB)的地址StackType_t **(指向任务堆栈指针的指针)内核通过这个参数获取预分配的定时器服务任务堆栈的起始地址。uint32_t *(指向无符号 32 位整数的指针)内核通过这个参数获取预分配的定时器服务任务堆栈的大小(单位:字,Word)。返回参数: 无
*/
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,StackType_t ** ppxTimerTaskStackBuffer,uint32_t * pulTimerTaskStackSize )
{* ppxTimerTaskTCBBuffer = &timer_task_tcb;* ppxTimerTaskStackBuffer = timer_task_stack;* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
/* 软件定时器任务配置 */
StaticTask_t timer_task_tcb;
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
在上述两个配置当中,任务堆栈大小在 FreeRTOSConfig.h中可以查找到定义及大小。
[3] 创建开始任务
以下是静态创建函数原型,这里注意它的返回值,是一个任务句柄,而动态任务,返回的是一个是否成功的信息,为什么会不一样呢?
由于内存由用户静态分配,只要参数合法,函数不会失败(无需返回错误码)。
任务句柄直接返回:简化接口,直接将任务句柄作为返回值。
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const uint32_t ulStackDepth,void * const pvParameters,UBaseType_t uxPriority,StackType_t * const puxStackBuffer,StaticTask_t * const pxTaskBuffer )
以下对于参数定义,流程和动态方式一样,多了两个参数。
/* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
StackType_t start_task_stack[START_TASK_STACK_SIZE];
StaticTask_t start_task_tcb;
void start_task( void * pvParameters );/*** @brief FreeRTOS例程入口函数* @param 无* @retval 无*/
void freertos_demo(void)
{ /* 创建起始任务 - 系统第一个运行的任务 */start_task_handler = xTaskCreateStatic( /* */(TaskFunction_t ) start_task, /* 任务函数指针,指向起始任务的实现 */(char * ) "start_task", /* 任务名称,用于调试和可视化工具 */(uint32_t ) START_TASK_STACK_SIZE, /* 任务堆栈大小,单位为字(Word) */(void * ) NULL, /* 传递给任务函数的参数 */(UBaseType_t ) START_TASK_PRIO, /* 任务优先级,数值越大优先级越高 */(StackType_t * ) start_task_stack, /* 静态分配的任务堆栈数组 */(StaticTask_t * ) &start_task_tcb /* 任务控制块,存储任务状态信息 */);/* 启动FreeRTOS调度器*/vTaskStartScheduler();
}
[4]在初始任务里面创建其它任务
void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 进入临界区 */task1_handler = xTaskCreateStatic( (TaskFunction_t ) task1,(char * ) "task1", (uint32_t ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(StackType_t * ) task1_stack,(StaticTask_t * ) &task1_tcb );task2_handler = xTaskCreateStatic( (TaskFunction_t ) task2,(char * ) "task2", (uint32_t ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(StackType_t * ) task2_stack,(StaticTask_t * ) &task2_tcb );task3_handler = xTaskCreateStatic( (TaskFunction_t ) task3,(char * ) "task3", (uint32_t ) TASK3_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK3_PRIO,(StackType_t * ) task3_stack,(StaticTask_t * ) &task3_tcb );vTaskDelete(start_task_handler);taskEXIT_CRITICAL(); /* 退出临界区 */
}
注意:使用 taskENTER_CRITICAL()
和 taskEXIT_CRITICAL()
包裹的代码块就是临界区,其核心目的是 确保代码在执行期间不会被其他任务或中断打断。
[5]其余任务和动态一样
三、任务删除函数
void vTaskDelete(TaskHandle_t xTaskToDelete);
xTaskToDelete : 待删除任务的句柄(类似于ID卡)
被删除任务将会从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除
注意
1.当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)
2.动态任务删除时-----任务终止后,FreeRTOS 会自动调用vPortFree()
释放 TCB 和堆栈占用的内存,将其归还堆空间。
静态任务删除时-----空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存, 则需要由用户在任务被删除前提前释放,否则将导致内存泄露
删除任务流程
1、使用删除任务函数,需将宏INCLUDE_vTaskDelete 配置为 1
2、入口参数输入需要删除的任务句柄(NULL代表删除本身)
对于第四点解析:
假设系统中有三个任务:
- Task1:阻塞 20ms(即将超时的任务,位于链表头)
- Task2:阻塞 50ms
- Task3:阻塞 100ms
当删除 Task1 时:
- Task1 从阻塞链表移除。
- 调度器自动将 Task2 设为新的链表头(下一个超时任务为 50ms)。
- 系统滴答计数器将在 50ms 时唤醒 Task2,而非原计划的 20ms。