3、FreeRTOS任务管理:任务创建与删除、任务优先级与调度策略、任务状态切换、空闲任务与钩子函数

各位同学,咱们今天聊聊FreeRTOS的任务管理。说实话,任务管理是RTOS的魂。你想想看,一个无人机飞控系统里,要同时处理传感器数据、控制算法、通信协议、电机驱动……如果没有任务管理,这些代码挤在一起,迟早要出乱子。

我在做飞控项目时,早期用过裸机跑循环,后来发现根本扛不住。一个传感器中断来了,另一个任务就被卡死。嗯,从那以后,我彻底转向了RTOS。今天的内容,就是FreeRTOS任务管理的核心。

3.1 任务创建与删除

任务创建,说白了就是告诉FreeRTOS:“嘿,我这里有一段代码,你帮我调度着跑。” 用xTaskCreate()这个API就行。我个人习惯把任务函数写成无限循环,里面放个vTaskDelay(),让出CPU。

// 任务函数原型
void vTaskFunction(void *pvParameters);

// 创建任务
xTaskCreate(
    vTaskFunction,       // 任务函数指针
    "TaskName",          // 任务名字(调试用)
    configMINIMAL_STACK_SIZE, // 栈深度
    NULL,                // 参数
    tskIDLE_PRIORITY + 1, // 优先级
    NULL                 // 任务句柄(可选)
);

删除任务用vTaskDelete()。注意,任务可以自己删除自己,传NULL就行。我在项目中遇到过一种情况:某个任务初始化失败,直接把自己删了,避免影响其他任务。这个做法很实用。

注意: 删除任务后,它占用的栈空间不会自动释放。如果你动态创建任务,记得在删除前确保没有内存泄漏。我曾经踩过这个坑,飞控跑着跑着内存就炸了。

3.2 任务优先级与调度策略

FreeRTOS支持抢占式调度。优先级高的任务先跑,同优先级的任务轮转。你想想看,无人机飞控里,电机控制任务的优先级肯定比日志打印高。否则电机响应慢了,飞机就翻了。

优先级 典型任务 说明
最高 电机控制 实时性要求极高,毫秒级响应
传感器采集 IMU、GPS数据,需要及时读取
控制算法 姿态解算、PID计算
日志、通信 可以容忍延迟

调度策略上,FreeRTOS默认是抢占式。但你可以配置成协作式(时间片轮转)。我个人建议飞控项目用抢占式,因为实时性要求摆在那里。协作式适合那些任务之间互相谦让的场景,但无人机可不会谦让。

小技巧: 优先级不要设太多层级。我一般只用3-5个优先级,多了反而增加调度开销。记住,优先级越高,任务被调度的机会越多,但也要小心优先级反转。

3.3 任务状态切换

FreeRTOS的任务有四种状态:运行态、就绪态、阻塞态、挂起态。说白了,就是任务在“干活”、“等着干活”、“睡着了”、“被关禁闭”之间切换。

  • 运行态: 当前正在使用CPU的任务。只有一个任务能处于运行态。
  • 就绪态: 任务准备好了,但CPU被别的任务占着。等调度器一换,它就上。
  • 阻塞态: 任务在等某个事件,比如延时、信号量、队列。这时候它不消耗CPU。
  • 挂起态: 任务被vTaskSuspend()挂起,除非被vTaskResume()唤醒,否则永远不跑。

我在项目中遇到过一个问题:某个传感器任务一直处于阻塞态,因为它的信号量没被释放。排查了半天,发现是另一个任务忘记释放信号量了。嗯,这种问题很隐蔽,调试时一定要看任务状态。

// 查看任务状态(调试用)
TaskStatus_t xTaskDetails;
vTaskGetInfo(xTaskHandle, &xTaskDetails, pdTRUE, eInvalid);
// xTaskDetails.eCurrentState 就是当前状态

3.4 空闲任务与钩子函数

空闲任务是FreeRTOS自动创建的,优先级最低。当所有其他任务都阻塞或挂起时,空闲任务就跑起来了。它主要做两件事:释放被删除任务的栈空间,以及进入低功耗模式。

钩子函数,说白了就是空闲任务里可以插入一段用户代码。你可以在FreeRTOSConfig.h里把configUSE_IDLE_HOOK设为1,然后实现vApplicationIdleHook()

void vApplicationIdleHook(void)
{
    // 这里可以放低功耗代码
    // 或者做一些后台清理工作
    // 注意:不要阻塞!不要调用可能导致阻塞的API!
}
重要: 钩子函数里不能调用vTaskDelay()xQueueReceive()等可能阻塞的API。否则空闲任务会被卡住,系统就崩了。我曾经在钩子里放了个延时,结果整个系统像死机一样,排查了好久才发现。

另外,空闲任务的栈大小可以配置。如果你的钩子函数比较复杂,记得把configMINIMAL_STACK_SIZE调大一点。我一般设成128或256字节,够用就行。

好了,今天的内容就到这里。任务创建与删除、优先级调度、状态切换、空闲任务,这四个点搞明白了,FreeRTOS的任务管理你就掌握了八成。下一章咱们聊聊任务间的同步与通信,那才是真正让飞控系统跑起来的关键。

课后练习: 试着写一个简单的飞控任务框架:一个传感器采集任务(高优先级)、一个控制算法任务(中优先级)、一个日志打印任务(低优先级)。看看它们如何协同工作。