一般更推荐第 1 种:main 只创建 app_main/init_task,然后 vTaskStartScheduler(),调度器跑起来后,在 app_main 这个任务上下文里再创建其他子任务,最后删除自己。
第 2 种"先把所有任务都创建好,再 vTaskStartScheduler()"也能用,但更适合简单、固定、初始化不依赖调度器的系统。
1. 两种流程
方式 1:启动任务模式
c
static void app_main(void *arg)
{
// 这里已经是任务上下文,调度器已经运行
// 可以创建队列、信号量、任务,可以阻塞、延时、打日志
xTaskCreate(task1, "task1", 1024, NULL, 3, NULL);
xTaskCreate(task2, "task2", 1024, NULL, 2, NULL);
vTaskDelete(NULL); // 启动任务完成使命,删除自己
}
int main(void)
{
// 只做最基础的裸机初始化
xTaskCreate(app_main, "app_main", 2048, NULL, 5, NULL);
vTaskStartScheduler();
for (;;); // 正常不会到这里
}
流程:
text
main
-> 创建 app_main 任务
-> vTaskStartScheduler()
-> 调度器启动,选择最高优先级就绪任务运行
-> 如果 app_main 优先级最高,则运行 app_main
-> app_main 创建其他子任务
-> app_main 删除自己
-> 调度器继续调度其他任务
注意:vTaskStartScheduler() 不是"上下文切换到 app_main 函数",而是启动调度器,然后由调度器选择第一个最高优先级就绪任务运行。只有 app_main 是最高优先级就绪任务时,才会先跑到它里面。
方式 2:先创建所有任务,再启动调度器
c
int main(void)
{
// 调度器还没启动,main 还是裸机上下文
xTaskCreate(task1, "task1", 1024, NULL, 3, NULL);
xTaskCreate(task2, "task2", 1024, NULL, 2, NULL);
vTaskStartScheduler();
for (;;);
}
流程:
text
main
-> 创建 task1、task2
-> vTaskStartScheduler()
-> 调度器启动,按优先级选择最高优先级就绪任务运行
-> 之后按 FreeRTOS 调度规则运行
2. 核心区别
| 对比项 | 方式 1:启动任务模式 | 方式 2:先建所有任务再启动 |
|---|---|---|
| 创建子任务时机 | 调度器已经运行 | 调度器还没运行 |
| 创建子任务上下文 | 任务上下文 | main 裸机上下文 |
| 能否调用阻塞 API | 能,如 vTaskDelay、队列阻塞 |
不能,调度器没启动 |
| 任务启动顺序 | 动态创建,动态就绪,可被抢占 | 启动时一次性按优先级选择 |
| 初始化灵活性 | 高,可依赖 RTOS 机制 | 低,基本只能裸机初始化 |
| 错误处理 | 可用日志、队列、看门狗、重启 | 受限,很多 RTOS API 不能用 |
| 额外任务开销 | 多一个启动任务,最后删除 | 无额外启动任务 |
| 启动确定性 | 稍弱,但可控 | 更直接、固定 |
| 可扩展性 | 好,适合复杂系统 | 一般,适合简单系统 |
| 典型场景 | ESP-IDF、复杂项目 | 简单裸机 FreeRTOS 项目 |