FreeRTOS多任务创建与删除教程
概述
本教程介绍FreeRTOS多任务的创建与删除方法,主要涉及两个核心函数:
- 任务创建:
xTaskCreate()
- 任务删除:
vTaskDelete()
实践步骤
1. 准备工程文件
复制005工程并重命名为006
2. 创建多个任务
打开工程,修改任务创建代码,分别创建任务1、任务2和任务3,并修改相应的任务句柄:
xTaskCreate(myTask1, "myTask1", 128, NULL, 2, &myTaskHandler1);
xTaskCreate(myTask2, "myTask2", 128, NULL, 2, &myTaskHandler2);
xTaskCreate(myTask3, "myTask3", 128, NULL, 2, &myTaskHandler3);
3. 编写任务函数
为三个任务分别实现任务函数:
void myTask1(void *arg)
{
while(1)
{
printf("myTask1 running\n");
vTaskDelay(500);
}
}
void myTask2(void *arg)
{
while(1)
{
printf("myTask2 running\n");
vTaskDelay(500);
}
}
void myTask3(void *arg)
{
while(1)
{
printf("myTask3 running\n");
vTaskDelay(500);
}
}
4. 定义任务句柄
在文件顶部定义三个任务的句柄:
TaskHandle_t myTaskHandler1;
TaskHandle_t myTaskHandler2;
TaskHandle_t myTaskHandler3;
5. 解决串口输出混乱问题
编译工程并运行,发现串口输出是混乱的。这是因为每个任务都在使用printf函数,而printf是非线程安全的。当多个任务同时调用printf时,输出会相互干扰。
6. 使用临界区保护printf
使用临界区保护printf函数,防止输出被打断。临界区可以简单理解为关闭和开启中断的操作。在FreeRTOS中,通过taskENTER_CRITICAL()
和taskEXIT_CRITICAL()
这对宏函数来实现进入和退出临界区。
修改任务函数如下:
void myTask1(void *arg)
{
while(1)
{
taskENTER_CRITICAL();
printf("myTask1 running\n");
taskEXIT_CRITICAL();
vTaskDelay(500);
}
}
void myTask2(void *arg)
{
while(1)
{
taskENTER_CRITICAL();
printf("myTask2 running\n");
taskEXIT_CRITICAL();
vTaskDelay(500);
}
}
void myTask3(void *arg)
{
while(1)
{
taskENTER_CRITICAL();
printf("myTask3 running\n");
taskEXIT_CRITICAL();
vTaskDelay(500);
}
}
7. 观察任务执行顺序
重新编译并运行程序,观察到打印信息已经正常显示。有趣的是,虽然任务创建顺序是1、2、3,但执行顺序却是3、2、1。这是因为FreeRTOS在调度相同优先级的任务时,会按照特定规则进行。
8. 实现任务删除功能
使用vTaskDelete()
函数删除任务。如果要删除其他任务,需传入目标任务的句柄;如果要删除当前任务自身,则传入NULL参数。
修改myTask1函数,实现计数删除功能:
void myTask1(void *arg)
{
uint8_t i = 0;
while(1)
{
taskENTER_CRITICAL();
printf("myTask1 running\n");
taskEXIT_CRITICAL();
i++;
if(i == 1)
{
vTaskDelete(myTaskHandler3); // 删除任务3
}
if(i == 2)
{
vTaskDelete(NULL); // 删除自身(任务1)
}
vTaskDelay(500);
}
}
9. 观察任务删除效果
编译并运行修改后的程序,观察到:
- 第一次循环:任务3、2、1依次运行,当任务1执行时删除了任务3
- 第二次循环:只有任务2和任务1运行,当任务1执行时删除了自身
- 之后只有任务2在运行
10. 使用启动任务创建其他任务
FreeRTOS中另一种常用的任务创建方式是使用一个启动任务来创建其他所有任务。在main函数中修改:
xTaskCreate(startTask, "startTask", 128, NULL, 2, &startTaskHandler);
11. 定义启动任务句柄
在文件顶部添加启动任务句柄定义:
TaskHandle_t startTaskHandler;
12. 编写启动任务函数
在main函数前实现启动任务函数:
void startTask(void *arg)
{
// 在这里创建其他任务
}
13. 在启动任务中创建其他任务
将原来直接创建的三个任务移动到启动任务函数中:
void startTask(void *arg)
{
xTaskCreate(myTask1, "myTask1", 128, NULL, 2, &myTaskHandler1);
xTaskCreate(myTask2, "myTask2", 128, NULL, 2, &myTaskHandler2);
xTaskCreate(myTask3, "myTask3", 128, NULL, 2, &myTaskHandler3);
}
14. 启动任务完成后删除自身
启动任务创建完其他任务后,可以删除自身:
vTaskDelete(NULL);
15. 完善启动任务函数
为了观察启动任务的执行,添加打印信息:
void startTask(void *arg)
{
taskENTER_CRITICAL();
printf("startTask running\n");
taskEXIT_CRITICAL();
xTaskCreate(myTask1, "myTask1", 128, NULL, 2, &myTaskHandler1);
xTaskCreate(myTask2, "myTask2", 128, NULL, 2, &myTaskHandler2);
xTaskCreate(myTask3, "myTask3", 128, NULL, 2, &myTaskHandler3);
vTaskDelete(NULL);
}
16. 观察启动任务执行效果
编译并运行修改后的程序,观察到任务执行顺序变为1、2、3,与创建顺序一致。这种方式与直接创建的行为有所不同。
总结
-
多任务创建方式:
- 可以直接创建多个任务
- 也可以通过一个启动任务来创建其他任务(这是更常用的方式)
-
任务删除:
- 使用
vTaskDelete()
函数删除任务 - 删除自身时,参数传入NULL
- 删除其他任务时,传入相应任务的句柄
- 使用
-
线程安全:
- printf函数是非线程安全的,在多任务环境中使用时需要保护
- 可以使用临界区(
taskENTER_CRITICAL()
和taskEXIT_CRITICAL()
)来保护非线程安全的代码
-
任务执行顺序:
- 直接创建的相同优先级任务,执行顺序可能与创建顺序不同
- 通过启动任务创建的相同优先级任务,执行顺序通常与创建顺序一致
项目开源
本项目已开源到GitHub,欢迎访问获取更多FreeRTOS学习资源:
https://github.com/Despacito0o/FreeRTOS/blob/main/README_zh.md