2、任务创建与管理:任务的概念、任务状态、任务优先级、任务创建函数xTaskCreate、任务删除函数vTaskDelete
好,咱们今天聊聊FreeRTOS里最核心的东西——任务。你想想看,一个嵌入式系统如果只能跑一个死循环,那能干的事太有限了。任务,就是让系统能同时处理多件事的基础。
任务到底是什么?
说白了,任务就是一段独立的程序,有自己的栈空间,有自己的执行流。我习惯把任务想象成一个独立的小人,每个小人都在干自己的活。有的在采集传感器数据,有的在刷新屏幕,有的在处理按键输入。这些小人在FreeRTOS的调度下,轮流使用CPU。
每个任务在代码里就是一个永不返回的C函数。嗯,这里要注意,任务函数里绝对不能有return语句,否则系统会崩溃。我刚开始学的时候就在这栽过跟头。
void vTaskFunction(void *pvParameters)
{
// 任务初始化代码
for(;;)
{
// 任务主体代码
vTaskDelay(pdMS_TO_TICKS(100));
}
// 这里绝对不能写return
}
任务的状态
任务在运行过程中,会在几种状态之间切换。我画个图你就能明白:
| 状态 | 说明 | 进入方式 |
|---|---|---|
| 运行态 | 正在使用CPU | 调度器选中该任务 |
| 就绪态 | 可以运行,但CPU被别的任务占着 | 任务创建后、从阻塞/挂起恢复 |
| 阻塞态 | 等待某个事件或时间 | 调用vTaskDelay、等待队列/信号量 |
| 挂起态 | 暂停执行,不参与调度 | 调用vTaskSuspend |
我在项目中遇到过一个问题:一个任务明明该执行了,但就是没反应。查了半天,发现是任务在等待一个永远不会到来的信号量,一直卡在阻塞态。所以调试的时候,先看看任务到底在哪个状态,能省不少时间。
任务优先级
优先级决定了任务谁先跑。数值越大,优先级越高。FreeRTOS支持抢占式调度,高优先级的任务就绪后,会立刻抢走CPU。
我个人习惯把实时性要求高的任务设高优先级,比如电机控制、通信处理。把界面刷新、日志输出这类任务设低优先级。但要注意,优先级不要设太多,一般3-5级就够了。我曾经见过一个项目设了20级优先级,调试起来简直噩梦。
任务创建函数 xTaskCreate
这是最常用的任务创建方式。原型长这样:
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, // 任务函数指针
const char * const pcName, // 任务名称(调试用)
configSTACK_DEPTH_TYPE usStackDepth, // 栈深度,单位是字
void *pvParameters, // 传给任务的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t *pxCreatedTask // 返回的任务句柄
);
返回值是pdPASS表示成功,pdFAIL表示失败(通常是栈不够)。
举个例子:
TaskHandle_t xLEDTaskHandle = NULL;
void vLEDTask(void *pvParameters)
{
(void)pvParameters; // 避免编译器警告
for(;;)
{
GPIO_Toggle(LED_PIN);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void main(void)
{
// 创建任务
if(xTaskCreate(
vLEDTask,
"LED",
128, // 栈大小,单位是字
NULL,
2, // 优先级
&xLEDTaskHandle
) != pdPASS)
{
// 创建失败处理
Error_Handler();
}
vTaskStartScheduler();
}
任务删除函数 vTaskDelete
任务干完活了,或者出错了,可以用这个函数把它干掉。
void vTaskDelete(TaskHandle_t xTaskToDelete);
参数传NULL表示删除自己。注意,删除自己后,vTaskDelete函数不会返回,后面的代码永远不会执行。
我建议在删除任务前,先释放它占用的资源,比如动态分配的内存、打开的硬件外设等。否则会造成资源泄漏。
void vCleanupTask(void *pvParameters)
{
// 做一些清理工作
for(;;)
{
// 收到删除指令
if(xReceivedDeleteCommand)
{
// 先释放资源
vReleaseHardware();
vPortFree(pvBuffer);
// 再删除自己
vTaskDelete(NULL);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
任务句柄的使用
任务句柄就像任务的身份证。创建任务时拿到句柄,后面就能用它来操作任务:
- vTaskDelete(xTaskHandle) — 删除指定任务
- vTaskSuspend(xTaskHandle) — 挂起任务
- vTaskResume(xTaskHandle) — 恢复任务
- uxTaskPriorityGet(xTaskHandle) — 获取任务优先级
- vTaskPrioritySet(xTaskHandle, uxNewPriority) — 设置任务优先级
我个人习惯在创建任务时就把句柄保存成全局变量,方便其他地方引用。但要注意,如果任务被删除了,句柄就失效了,再用它操作任务会出问题。
总结一下
任务管理是FreeRTOS的基石。记住几个要点:
- 任务函数不能返回,必须是个死循环
- 优先级要合理设置,别搞太多级
- 栈大小要留够余量,用HighWaterMark来调优
- 删除任务前记得释放资源
- 任务句柄失效后别再用
嗯,这些就是我这些年用FreeRTOS做项目积累下来的经验。下一节咱们聊聊任务间的通信,那又是另一个有意思的话题了。