FreeRTOS 在 STM32 上的移植和开发十分便捷,而任务管理是其核心功能之一。掌握任务的创建、删除、挂起与恢复,是熟练使用 FreeRTOS 的基础。以下将从这几个方面展开,结合 STM32 平台进行详细讲解。

---

STM32 实战:深入 FreeRTOS 任务管理——创建、删除、挂起与恢复详解

在多任务嵌入式系统中,如何有效地管理和调度任务是系统稳定运行的核心。FreeRTOS 作为一个轻量级且广泛使用的实时操作系统,提供了一套强大而清晰的任务管理机制。对于初学者来说,理解并掌握任务的创建、删除、挂起与恢复是踏入 FreeRTOS 世界的第一步。本文将以 STM32 平台为基础,通过理论和代码实例,详细解析这几个核心操作。

一、任务基础与状态机

在 FreeRTOS 中,每个任务都是一个独立的执行线程,拥有自己的栈空间和优先级。宏观上它们看似同时运行,实则是由调度器根据策略快速切换 。任务是竞争系统资源的最小运行单元,通常运行在一个死循环中 。

FreeRTOS 中的任务主要有四种状态 :

· 运行态:任务正在实际使用 CPU。
· 就绪态:任务具备运行能力(未被阻塞或挂起),但因为有同优先级或更高优先级的任务正在运行,所以暂时未获得 CPU。
· 阻塞态:任务正在等待某个事件(如延时超时、等待信号量或消息队列)。
· 挂起态:任务被强制暂停执行。类似于阻塞态,但挂起态不是由等待事件自动触发的,而是通过调用 vTaskSuspend() 函数主动进入的。要退出挂起态,只能调用 vTaskResume() 或 xTaskResumeFromISR() 。

二、任务的创建与删除

在 FreeRTOS 中,任务创建主要分为动态创建和静态创建两种方式。动态创建(xTaskCreate)使用 FreeRTOS 的堆内存自动分配任务控制块和栈,更为常用;静态创建(xTaskCreateStatic)则需要用户手动分配内存 。

1. 动态创建任务:xTaskCreate()

这是最常用的创建方式。使用前需确保 FreeRTOSConfig.h 中的宏 configSUPPORT_DYNAMIC_ALLOCATION 为 1 。

函数原型:

```c
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,           // 任务函数指针
                        const char * const pcName,           // 任务名称(调试用)
                        const uint16_t usStackDepth,         // 任务栈大小,单位是word
                        void * const pvParameters,           // 任务函数传入参数
                        UBaseType_t uxPriority,              // 任务优先级
                        TaskHandle_t * const pxCreatedTask ); // 返回的任务句柄
```

参数详解 :

· pxTaskCode:指向任务实现函数的指针。
· pcName:任务描述性名称,主要用于调试,长度由 configMAX_TASK_NAME_LEN 定义。
· usStackDepth:注意:在 STM32(32位平台)中,单位是 字(word) ,即 4 字节。如果设置栈大小为 128,实际分配的栈空间是 512 字节 。
· pvParameters:任务函数的输入参数。
· uxPriority:任务优先级。数值越大,优先级越高(0 为最低优先级)。
· pxCreatedTask:用于存储创建成功的任务句柄。该句柄可用于后续删除、挂起操作。如果不需要,可设为 NULL。
· 返回值:返回 pdPASS 表示创建成功,返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 表示内存不足创建失败 。

代码示例:

```c
TaskHandle_t myTaskHandle = NULL;

void vMyTask(void *pvParameters) {
    while(1) {
        // 任务逻辑
        vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒
    }
}

void main(void) {
    // 创建任务
    if( xTaskCreate( vMyTask, "My Task", 128, NULL, 2, &myTaskHandle ) == pdPASS ) {
        // 创建成功
    }
    // 启动调度器
    vTaskStartScheduler(); 
}
```

vTaskStartScheduler() 启动后,FreeRTOS 会自动创建空闲任务,如果使能了软件定时器,还会创建定时器服务任务 。

2. 删除任务:vTaskDelete()

当任务不再需要时,可以通过删除任务来回收内存(动态创建的任务)。需要注意的是,删除任务后,任务所占用的堆栈和控制块内存会在空闲任务中被释放。因此,必须给空闲任务运行的机会 。

使用此函数需要在 FreeRTOSConfig.h 中配置 #define INCLUDE_vTaskDelete 1 。

函数原型:

```c
void vTaskDelete( TaskHandle_t xTaskToDelete );
```

参数详解:

· xTaskToDelete:要删除的任务句柄。如果传入 NULL,则表示删除当前正在运行的任务(自杀)。

注意事项:
如果任务内部使用 pvPortMalloc 分配了内存,在删除任务前需要手动调用 vPortFree 释放,否则会造成内存泄漏 。

三、任务的挂起与恢复

挂起与恢复功能适用于需要暂停任务运行且后续恢复的场景,避免了删除重建带来的变量值丢失和内存开销 。

1. 挂起任务:vTaskSuspend()

挂起指定的任务,使其停止运行。进入挂起态后,任务对调度器不可见,只有调用恢复函数才能使其重回就绪态。

使用此函数需配置 #define INCLUDE_vTaskSuspend 1 。

函数原型:

```c
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
```

参数详解:

· xTaskToSuspend:要挂起的任务句柄。如果传入 NULL,则表示挂起当前正在运行的任务(自己挂自己)。

2. 恢复任务:vTaskResume()

用于恢复被挂起的任务。注意,此函数只能在任务代码中调用,不能在中断服务例程(ISR)中使用 。

函数原型:

```c
void vTaskResume( TaskHandle_t xTaskToResume );
```

3. 中断级恢复:xTaskResumeFromISR()

专门用于在中断服务程序中恢复被挂起的任务。如果恢复的任务优先级高于当前被打断的任务,则需要在中断退出后执行上下文切换 。

函数原型:

```c
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );
```

返回值:

· pdTRUE:表示恢复的任务优先级等于或高于当前运行任务,建议退出中断后进行上下文切换。
· pdFALSE:表示无需切换 。

重要提示:多次调用 vTaskSuspend 挂起同一个任务,只需调用一次 vTaskResume 即可恢复 。

四、实战例程与要点

以下是一个简单的控制逻辑示例,通过按键触发任务的管理:

场景假设

· 任务1:LED 闪烁(周期性执行)。
· 任务2:按键扫描。负责挂起、恢复或删除任务1。

核心代码框架:

```c
TaskHandle_t ledTaskHandle = NULL;

void vLEDTask(void *pvParameters) {
    while(1) {
        // 翻转LED
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void vKeyTask(void *pvParameters) {
    uint8_t keyState = 0;
    while(1) {
        keyState = HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin);
        if(keyState == 1) { // 假设按键按下
            // 挂起 LED 任务
            vTaskSuspend(ledTaskHandle);
        } else {
            // 恢复 LED 任务
            vTaskResume(ledTaskHandle);
        }
        vTaskDelay(pdMS_TO_TICKS(50)); // 消抖
    }
}

int main(void) {
    HAL_Init();
    // ... 其他初始化 ...

    // 创建 LED 任务
    xTaskCreate(vLEDTask, "LED", 128, NULL, 1, &ledTaskHandle);
    // 创建 按键 任务
    xTaskCreate(vKeyTask, "KEY", 128, NULL, 2, NULL);

    vTaskStartScheduler(); // 启动调度器

    while(1); // 不会运行到这里
}
```

五、总结与避坑指南

操作 核心API 关键配置宏 注意事项 
动态创建 xTaskCreate() configSUPPORT_DYNAMIC_ALLOCATION 栈大小单位是 word(32位下4字节),注意内存是否充足。
删除 vTaskDelete() INCLUDE_vTaskDelete 删除后内存由空闲任务回收,任务自己申请的内存需手动释放。
挂起 vTaskSuspend() INCLUDE_vTaskSuspend 传入NULL挂起自身;多次挂起一次恢复。
恢复 vTaskResume() INCLUDE_vTaskSuspend 不可在中断中使用。
中断恢复 xTaskResumeFromISR() INCLUDE_xResumeFromISR 注意返回值,必要时请求切换。

理解并熟练运用任务的创建、删除、挂起与恢复,是编写复杂稳定 FreeRTOS 应用的基础。在 STM32 平台上,通过简洁的 API 调用,我们可以灵活地控制任务的生命周期,实现功能强大的嵌入式系统。下一步,可以尝试结合队列和信号量,进一步探索任务间的通信与同步。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐