3. FreeRTOS任务管理在RA上的实现
在RA系列MCU上使用FreeRTOS,任务管理是工程落地的第一道门槛。FreeRTOS本身只提供与硬件无关的调度内核,而任务真正运行起来,还需要RA的启动代码、时钟节拍、堆内存和中断向量配合。本节围绕任务创建、任务状态、优先级、栈分配、调度器启动,以及用板载LED与按键验证多任务切换这条主线,把关键实现细节讲清楚。
任务创建:静态与动态两条路径
FreeRTOS创建任务的核心API是 xTaskCreate()(动态)和 xTaskCreateStatic()(静态)。动态方式由内核从FreeRTOS堆中分配任务控制块TCB和栈空间,适合快速原型;静态方式由用户提供 StaticTask_t 和 StackType_t 数组,不依赖堆,适合对内存确定性要求高的量产项目。RA工程中,若使用FSP配置,通常会在 FreeRTOSConfig.h 中把 configSUPPORT_DYNAMIC_ALLOCATION 和 configSUPPORT_STATIC_ALLOCATION 按需打开。
一个典型的动态创建示例如下:
#define LED_TASK_STACK 256
#define KEY_TASK_STACK 256
TaskHandle_t xLedTask = NULL;
TaskHandle_t xKeyTask = NULL;
BaseType_t ret;
ret = xTaskCreate(vLedTask, "LED", LED_TASK_STACK, NULL, 2, &xLedTask);
configASSERT(ret == pdPASS);
ret = xTaskCreate(vKeyTask, "KEY", KEY_TASK_STACK, NULL, 3, &xKeyTask);
configASSERT(ret == pdPASS);
这里 LED_TASK_STACK 的单位是“字”,在Cortex-M上1字等于4字节,因此256表示约1KB栈空间。任务名主要用于调试,句柄用于后续挂起、恢复或删除任务。
任务状态与优先级
FreeRTOS任务有四种基本状态:运行态(Running)、就绪态(Ready)、阻塞态(Blocked)和挂起态(Suspended)。同一时刻,单核RA上只有一个任务处于运行态,其余就绪任务按优先级排队。FreeRTOS采用抢占式调度,优先级数值越大,优先级越高。若 configUSE_TIME_SLICING 为1,同优先级任务会按时间片轮转。
在RA上,优先级范围由 configMAX_PRIORITIES 决定,常见配置为8或16。按键任务需要及时响应,可设为3;LED任务周期性闪烁,设为2即可。空闲任务优先级为0,由调度器自动创建。若某个任务长期占用CPU且不主动阻塞,低优先级任务会被“饿死”,因此任务中应使用 vTaskDelay() 或等待信号量、队列等机制让出CPU。
栈分配:大小与溢出检测
栈是任务最容易出问题的地方。栈太小会覆盖相邻内存,导致HardFault或随机跑飞;栈太大则浪费RAM。RA6M5等型号RAM较充裕,但RA2系列资源紧张,需要精打细算。估算栈大小时,要考虑函数调用深度、局部变量、中断嵌套以及printf等库函数的开销。经验上,简单LED任务128~256字,带串口打印或浮点运算的任务建议512字以上。
FreeRTOS提供两种栈溢出检测:configCHECK_FOR_STACK_OVERFLOW 设为1时检查栈指针是否越界,设为2时还会在栈末尾填充魔术字并校验。开启后必须实现 vApplicationStackOverflowHook(),在其中点亮错误LED或打印任务名,便于定位。
xTaskCreate() 以字为单位。两者换算关系为:字节数 = 字数 × 4。配置时不要混淆。调度器启动与RA启动流程
所有任务创建完成后,调用 vTaskStartScheduler() 启动调度器。该函数会配置SysTick或RA的AGT作为系统节拍,创建空闲任务和定时器任务,然后开始调度。在RA的FSP工程中,通常由 freertos_start() 或 vTaskStartScheduler() 在 main() 末尾触发。启动后,main() 不再返回,控制权交给调度器。
系统节拍频率由 configTICK_RATE_HZ 决定,常用1000Hz,即1ms一个tick。节拍越快,时间精度越高,但中断开销也越大。RA的SysTick由Cortex-M内核提供,FSP会自动配置;若使用AGT,需要在FSP的Stacks中使能FreeRTOS定时器。
用LED与按键验证多任务切换
验证多任务切换最直观的方式是:一个任务控制LED闪烁,另一个任务扫描按键并改变闪烁频率或切换LED状态。按键任务通过队列或任务通知把事件传给LED任务,观察LED响应是否及时、是否出现卡顿。
示例逻辑:LED任务每500ms翻转一次P405;按键任务每20ms扫描一次P004,检测到按下后通过队列发送新周期值。若LED任务能按新周期变化,说明任务间通信和调度正常;若按键无响应,需检查优先级是否被高优先级任务长期占用,或栈是否溢出。
常见问题与排查思路
任务创建失败时,先检查堆是否耗尽。xPortGetFreeHeapSize() 可查看剩余堆空间,若接近0,需增大 configTOTAL_HEAP_SIZE 或改用静态创建。任务运行后卡死,优先怀疑栈溢出,开启 configCHECK_FOR_STACK_OVERFLOW 并在钩子函数中记录任务名。若按键响应迟钝,检查按键任务是否被低优先级任务阻塞,或扫描周期是否过长。若LED闪烁频率异常,确认 vTaskDelay() 的参数是否以tick为单位,以及 configTICK_RATE_HZ 是否与预期一致。
最后,建议在调试阶段打开 configUSE_TRACE_FACILITY 和 configUSE_STATS_FORMATTING_FUNCTIONS,通过 vTaskList() 打印各任务状态、优先级、剩余栈和运行计数,快速判断调度是否均衡。RA的SWD调试配合FreeRTOS感知插件,还能在IDE中直接查看任务列表和栈使用情况,比单纯点灯更高效。