🎀 文章作者:二土电子
🐸 期待大家一起学习交流!
一、任务创建和删除API函数
FreeRTOS 的任务创建和删除相关的API函数有下面几个
- xTaskCreate()
使用动态的方法创建一个任务 - xTaskCreateStatic()
使用静态的方法创建一个任务 - xTaskCreateRestricted()
创建一个使用 MPU 进行限制的任务,相关内存使用动态内存分配 - vTaskDelete()
删除一个任务
MPU意思是Memory Protect Unit,即为存储保护单元,它是位于存储器内部的一个可编程的区域,定义了存储器的属性和存储器的访问权限。这里就不再对MPU做深入了解了。
1.1 xTaskCreate()函数
由于该函数是动态方法创建任务,因此它的任务内存需要使用“pvPortMalloc”去申请。所以在使用该函数创建任务时,对应的“configSUPPORT_DYNAMIC_ALLOCATION”宏定义要打开。
#define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持动态内存申请
下面来看一下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
任务函数 - pcName
任务名字。任务名字不能超过“configMAX_TASK_NAME_LEN” - usStackDepth
任务堆栈大小。需要注意的是,实际的任务堆栈大小是该值 * 4字节,在上一篇已经介绍过了。
其中空闲任
务的任务堆栈大小为“configMINIMAL_STACK_SIZE” - pvParameters
传递给任务函数的参数 - uxPriotiry
任务优先级。范围是0~ configMAX_PRIORITIES-1 - pxCreatedTask
任务句柄。任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他 API 函数可能会使用到这个句柄。任务句柄通俗来讲,就是任务的标识,用来区分是哪个任务。
该函数有两种返回值
- pdPASS
任务创建成功 - errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
导致任务创建失败(通常可能是任务堆内存不足)1.2 xTaskCreateStatic()函数
该函数是使用静态方法创建任务,需要将“configSUPPORT_STATIC_ALLOCATION”宏定义打开。该函数有以下参数
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 )
- pxTaskCode
任务函数 - pcName
任务名字。任务名字不能超过“configMAX_TASK_NAME_LEN” - usStackDepth
任务堆栈大小。,由于是静态方法创建任务,所以任务堆栈由用户给出,一般是个数组,该参数值就是这个数组的大小 - pvParameters
传递给任务的参数 - uxPriotiry
任务优先级。范围是0~ configMAX_PRIORITIES-1 - puxStackBuffer
任务堆栈。一般为数组。数组类型要为 StackType_t 类型 - pxTaskBuffer
任务控制块
该函数的返回值如下
- NULL
任务创建失败(puxStackBuffer 或 pxTaskBuffer 为 NULL 的时候会导致这个错误的发生) - 其他值
任务创建成功,返回任务的任务句柄1.3 vTaskDelete()函数
使用任务删除函数时,需要开启“INCLUDE_vTaskDelete”宏定义。
删除一个用函数 xTaskCreate()或者 xTaskCreateStatic0创建的任务,被删除了的任务不再存在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄。
如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数 vTaskDelete()删除任务以后,必须给空闲任务一定的运行时间。
只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPotMalloc()分配了 500 字节的存,那么在此任务被删除以后用户也必须调用函数 vPortFree()将这 500 字节的内存释放掉,否则会导致内存泄露。
该函数的输入参数如下,无返回值
- xTaskToDelete
要删除的任务的任务句柄。如果在某函数中需要调用该函数删除自身,那么该参数写NULL即可。
二、任务创建和删除(动态方法)
下面根据正点原子提供的一个实验项目,学习一下上面任务创建与删除函数的使用。
2.1 任务要求
创建三个任务,分别是start_task,task1_task,task2_task,这三个任务的功能如下
- start_task
用来创建其他两个任务 - task1_task
此任务运行5次以后,调用任务删除函数vTaskDelete()删除task2_task。该任务也会控制LED0闪烁 - task2_task
该任务控制LED1的闪烁2.2 程序设计
裸机状态下的主程序如下
int main(void)
{
Med_Mcu_Iint(); // 系统初始化
while (1)
{
// 具体处理
}
}
使用操作系统时,首先要创建任务
void xxx_task (void *pxCreatedTask); // 任务函数
int main (void)
{
Med_Mcu_Iint(); // 系统初始化
//创建任务
xTaskCreate((TaskFunction_t )pxTaskCode, //任务函数
(const char* )pcName, //任务名称
(uint16_t )usStackDepth, //任务堆栈大小
(void* )pvParameters, //传递给任务函数的参数
(UBaseType_t )uxPriority, //任务优先级
(TaskHandle_t* )&pxCreatedTask); //任务句柄
}
void xxx_task (void *pxCreatedTask)
{
while (1)
{
}
}
有了上面的框架,只需要给参数赋值即可。对于任务堆栈,任务优先级等参数,可以用宏定义的方式写。需要注意的是,优先级0不能使用。因为空闲任务需要使用优先级0。
在开启任务调度器时,会自动创建一个空闲任务,该任务的任务优先级为优先级0。最高优先级也不可以使用。
因为使用软件定时器时,系统需要有一个定时器任务来维护这个软件定时器。这个定时器任务的任务优先级是最高优先级。
2.2.1 创建开始任务
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 120
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task (void *pxCreatedTask);
//创建任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
void start_task (void *pxCreatedTask)
{
//创建任务1
xTaskCreate((TaskFunction_t )taks1_task, //任务函数
(const char* )"taks1_task", //任务名称
(uint16_t )TASK1_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK1_TASK_PRIO, //任务优先级
(TaskHandle_t* )&TASK1Task_Handler); //任务句柄
//创建任务2
xTaskCreate((TaskFunction_t )taks2_task, //任务函数
(const char* )"taks2_task", //任务名称
(uint16_t )TASK2_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK2_TASK_PRIO, //任务优先级
(TaskHandle_t* )&TASK2Task_Handler); //任务句柄
// 开始任务只需要执行一次,执行完成后删除即可
vTaskDelete(StartTask_Handler); //删除开始任务
}
2.2.2 创建任务1和任务2
关于任务1和任务2的一些宏定义,这里就不再详细列出来了,下面是任务1和任务2的函数
void taks1_task (void *pxCreatedTask)
{
u8 task1Cunt = 0; // 任务1运行次数计数变量
while (1)
{
task1Cunt = task1Cunt + 1; // task1运行次数加1
Med_Led_StateReverse(LED0); // LED0状态取反
vTaskDelay(500); // 延时500ms
// 运行5次后
if (task1Cunt >= 5 && TASK2Task_Handler != NULL)
{
// 删除任务2
vTaskDelete(TASK2Task_Handler);
TASK2Task_Handler = NULL;
}
}
}
void taks2_task (void *pxCreatedTask)
{
while (1)
{
Med_Led_StateCtrl(LED1,LED_ON); // 点亮LED1
vTaskDelay(200); // 延时200ms
Med_Led_StateCtrl(LED1,LED_OFF); // 熄灭LED1
vTaskDelay(800); // 延时800ms
}
}
需要注意的是,在删除任务时,需要判断一下要删除任务的任务句柄是否为无效(NULL)。
2.2.3 开启任务调度器
vTaskStartScheduler(); //开启任务调度
三、总结
在其他任务中删除另一个任务,可能会有很多后遗症,所以删除时最好是任务子自删除。
3.1 删除任务后任务句柄不是NULL
最初删除任务2的程序如下
// 运行5次后
if (task1Cunt >= 5)
{
// 删除任务2
vTaskDelete(TASK2Task_Handler);
}
发现在运行5次任务1之后,程序就卡住了。打断点发现,程序卡在了删除任务2那里。一直在删除任务2。在删除任务2之前加入判断,判断任务2的任务句柄是否为NULL。
// 运行5次后
if (task1Cunt >= 5 && TASK2Task_Handler != NULL)
{
// 删除任务2
vTaskDelete(TASK2Task_Handler);
}
发现程序依旧会卡在删除任务2这里。用Debug查看删除任务2后任务2的任务句柄的值。发现在删除任务2之后,任务2的任务句柄依旧是有值的。冲浪后发现,大家也遇到了这种情况。同时,也学到了一种简单粗暴的处理方法。在删除任务后,将被删除任务的任务句柄修改为NULL。
// 运行5次后
if (task1Cunt >= 5 && TASK2Task_Handler != NULL)
{
// 删除任务2
vTaskDelete(TASK2Task_Handler);
TASK2Task_Handler = NULL;
}
修改完成后,程序运行正常。