3. FreeRTOS任务创建与管理:任务函数原型、任务句柄、任务堆栈配置、任务优先级设定
好,咱们今天聊聊FreeRTOS里最核心的东西——任务创建与管理。说实话,我刚开始接触RTOS时,觉得任务不就是个函数嘛,有啥好讲的?后来在项目里踩了坑才明白,这里面的门道真不少。任务函数怎么写、堆栈给多大、优先级怎么设,每一个细节都可能让你的系统跑得飞起,或者莫名其妙死机。
3.1 任务函数原型——你的任务长什么样
在FreeRTOS里,每个任务本质上就是一个永不返回的C函数。它的原型长这样:
void vTaskFunction(void *pvParameters);
注意看,返回值是void,参数是一个void指针。这意味着你可以传任何东西进去——整数、结构体指针、甚至字符串地址。我个人习惯把参数强转成需要的类型,比如:
void vLEDTask(void *pvParameters) {
uint8_t led_pin = (uint8_t)pvParameters;
// 任务代码...
}
任务函数内部通常是一个死循环。为什么?因为任务一旦返回,FreeRTOS会调用vTaskDelete(NULL)来清理它。如果你不想任务被删,就老老实实写个while(1)。我在项目中遇到过新手把任务写成顺序执行,跑完一遍就退出了,结果系统直接卡死——嗯,这是个经典坑。
vTaskDelete()自杀。
3.2 任务句柄——任务的身份证
任务句柄,说白了就是任务的ID。你创建了一个任务,系统会给你一个TaskHandle_t类型的变量,以后你想操作这个任务(比如删除、挂起、改变优先级),都得靠它。
声明方式很简单:
TaskHandle_t xLEDTaskHandle = NULL;
创建任务时,把句柄的地址传进去:
xTaskCreate(vLEDTask, "LED", 128, (void*)LED_PIN, 1, &xLEDTaskHandle);
之后想删除任务?直接:
vTaskDelete(xLEDTaskHandle);
我曾经在一个多传感器采集项目里,用句柄来动态管理任务。某个传感器坏了,我就删掉对应的采集任务,等传感器恢复再重新创建。句柄在这里就像遥控器,指哪打哪。
3.3 任务堆栈配置——给任务留多少"内存"
每个任务都有自己的堆栈,用来保存局部变量、函数调用上下文等。堆栈大小以字(Word)为单位,在32位处理器上一个字是4字节。所以128的堆栈实际占用512字节。
堆栈给多大?这是个灵魂拷问。给大了浪费RAM,给小了一运行就崩。我一般这样估算:
- 简单任务(比如点个灯):64-128字(256-512字节)
- 中等任务(带点计算、调用几个函数):256-512字(1-2KB)
- 复杂任务(带浮点运算、大量局部变量):1024字以上(4KB+)
FreeRTOS提供了一个API来查堆栈使用情况:
UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(xTaskHandle);
这个函数返回任务堆栈剩余的最小值(以字为单位)。如果这个值接近0,说明堆栈快爆了,赶紧加大。我在调试一个电机控制任务时,发现堆栈高水位标记只剩4个字,吓得我赶紧翻倍——再晚一点估计就栈溢出了。
3.4 任务优先级设定——谁先跑谁后跑
FreeRTOS是一个抢占式实时内核。优先级高的任务会打断优先级低的任务。优先级数值越小,优先级越低(0是最低)。
设定优先级时,我遵循几个原则:
- 实时性要求高的任务给高优先级——比如电机控制、传感器读取
- 计算密集型任务给低优先级——比如数据记录、日志输出
- 避免优先级反转——高优先级任务等待低优先级任务持有的资源,这会导致系统响应变慢
举个例子,一个平衡车项目里,我这样分配优先级:
| 任务名称 | 优先级 | 说明 |
|---|---|---|
| 姿态控制 | 5 | 最高,必须实时响应 |
| 电机驱动 | 4 | 次高,跟随控制指令 |
| 蓝牙通信 | 2 | 中等,允许一定延迟 |
| LED显示 | 1 | 最低,有空再刷 |
你想想看,如果姿态控制任务被LED显示任务抢占了CPU,那平衡车还怎么站得住?优先级设定说白了就是给任务排个队,紧急的先走。
3.5 任务创建实战——把理论串起来
说了这么多,咱们写个完整的例子。创建一个LED闪烁任务和一个按键扫描任务:
TaskHandle_t xLEDHandle = NULL;
TaskHandle_t xKeyHandle = NULL;
void vLEDTask(void *pvParameters) {
(void)pvParameters;
while(1) {
GPIO_Toggle(LED_PIN);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vKeyTask(void *pvParameters) {
(void)pvParameters;
while(1) {
if(GPIO_Read(KEY_PIN) == 0) {
// 按键按下,处理...
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
int main(void) {
// 硬件初始化...
xTaskCreate(vLEDTask, "LED", 128, NULL, 1, &xLEDHandle);
xTaskCreate(vKeyTask, "Key", 256, NULL, 2, &xKeyHandle);
vTaskStartScheduler();
while(1); // 正常情况下不会跑到这里
}
注意看,按键任务的优先级是2,LED任务是1。按键响应比LED闪烁更重要,所以优先级高一点。堆栈方面,按键任务因为调用了更多函数,我给了256字,LED任务128字就够了。
vTaskList()或uxTaskGetTaskNumber()查看任务状态,名字能帮你快速定位问题。
嗯,任务创建与管理就聊到这儿。说白了就是三件事:写好任务函数、配好堆栈大小、设对优先级。这三样搞定了,你的RTOS系统就稳了一半。剩下的,就是让任务之间好好协作——那是咱们后面章节要聊的内容。