任务创建和删除

[English]

任务管理是 FreeRTOS 的核心功能,涉及任务的创建、调度与状态管理。

动态创建任务

xTaskCreate() 用于动态方式创建任务。它会自动分配任务的控制块和堆栈空间,并将任务初始化为就绪状态,等待调度器调度执行。

API 原型

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
                        const char * const pcName,
                        const configSTACK_DEPTH_TYPE uxStackDepth,
                        void *pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t *pxCreatedTask );
参数解释

传入参数

参数功能

参数说明

pvTaskCode

任务入口函数的指针。

该函数实现任务的具体功能,通常是一个无限循环,任务函数不应返回或退出,但可以删除自己。

pcName

任务的名称。

主要用于调试,最大名称长度由 configMAX_TASK_NAME_LEN 决定。

usStackDepth

任务堆栈的大小。

word 为单位。根据处理器的字长,实际堆栈空间大小会根据该值自动计算。

pvParameters

传递给任务的参数。

需要确保参数在任务执行时仍然有效,不能传递局部变量的地址。

uxPriority

创建任务时的优先级。

数值越大,优先级越高。优先级不能超过 configMAX_PRIORITIES - 1

pxCreatedTask

用于返回创建的任务句柄。

此为可选参数,可用于后续对该任务进行删除、挂起、恢复等操作。如果不想传入该参数,可以将其设置为 NULL

返回值说明

返回值

说明

pdPASS

任务创建成功。

errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY

内存分配失败。

备注

如果任务堆栈或控制块内存不足,会导致任务创建失败。可通过增大 configTOTAL_HEAP_SIZE 或减小栈深度调整。

示例 1:创建两个优先级相同的任务

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// 任务 1 函数
void vTask1 (void *pvParameters)
{
    // 任务函数的主体通常为死循环
    while (1) {
        printf("Task 1 is running\n");
        vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1 s
    }
}

// 任务 2 函数
void vTask2 (void *pvParameters)
{
    while (1) {
        printf("Task 2 is running\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(vTask2, "Task2", 2048, NULL, 1, NULL);
}

以上代码执行结果如下:

Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running

本示例创建了两个优先级相同的任务,每个任务周期性打印自身信息,并延时 1 秒。由于两个任务优先级一致, FreeRTOS 调度器采用时间片轮转策略,使它们轮流运行。每个任务执行一次后主动进入延时状态(阻塞态),释放 CPU 使用权,调度器便选择另一个处于就绪状态的任务运行。 这种调度方式确保两个任务交替执行,输出结果有序。延时结束后,任务重新进入就绪态,等待被调度执行。通过这一机制,即便任务优先级相同,系统仍可实现多个任务之间的公平调度与稳定运行。

示例 2:创建两个优先级不同的任务

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// 任务 1 函数
void vTask1 (void *pvParameters)
{
    while (1) {
        printf("Task 1 is running\n");
    }
}

// 任务 2 函数
void vTask2 (void *pvParameters)
{
    while (1) {
        printf("Task 2 is running\n");
    }
}

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, NULL, 1, NULL);
    xTaskCreate(vTask2, "Task2", 2048, NULL, 2, NULL);
}

以上代码执行结果如下:

Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running

在该示例中,创建了两个优先级不同的任务,其中任务 2 的优先级高于任务 1。由于任务 2 在执行过程中未主动释放 CPU (未调用 vTaskDelay() 或其他阻塞函数),调度器始终优先执行该高优先级任务,导致低优先级的任务 1 无法获得运行机会。

在任务 2 中增加如下延迟函数:

vTaskDelay(pdMS_TO_TICKS(10));

执行结果如下:

Task 2 is running
Task 1 is running
Task 1 is running
Task 1 is running
Task 1 is running
Task 1 is running
Task 2 is running
Task 1 is running
Task 1 is running
Task 1 is running

通过在高优先级任务中调用 vTaskDelay() ,任务在每次执行后会主动挂起一段时间,释放 CPU 使用权。在此期间,调度器将优先调度低优先级但处于就绪状态的任务运行。这样,低优先级的任务便获得了运行机会,从而实现两个不同优先级任务的交替执行。

示例 3:任务传递参数

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// 任务1函数
void vTask1 (void *pvParameters)
{
    const char *param = pvParameters; // 接受传递的参数
    while (1) {
        printf(param); // 打印参数内容
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

static const char *pcTextForTask1 = "Task 1 is running\n";
static const char *pcTextForTask2 = "Task 2 is running\n";

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, (void *)pcTextForTask1, 1, NULL);
    xTaskCreate(vTask1, "Task2", 2048, (void *)pcTextForTask2, 1, NULL);
}

以上代码执行结果如下:

Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running

在不使用参数传递的情况下,通常需要为每个任务分别编写独立的任务函数。通过任务参数传递机制,可以仅使用一个任务函数,通过传入不同参数创建多个功能相似的任务。这种方式具有以下优势:

  • 减少重复代码:避免为逻辑相似的任务编写多个函数。

  • 提高可维护性和扩展性:任务逻辑统一,便于后期修改与扩展。

  • 简化任务创建流程:统一的任务函数和可变参数,使任务创建更灵活。

但在使用过程中应注意以下几点:

  • 确保传递给任务的参数在任务执行期间保持有效。如果参数是局部变量或动态分配的内存,确保它们不会在运行时被销毁或释放。

  • 确保在任务函数中正确转换 pvParameters 的类型:

    1. 在创建任务时,首先需要为传递给任务的参数定义正确的类型,例如 int , float , char * 等,或是结构体类型,具体取决于任务的需求。

    2. 在调用 xTaskCreate 时,将任务参数转换为 void * 类型,并通过 pvParameters 传递参数。

    3. 在任务函数内部,必须将 void * 类型的参数转换为正确的类型,以便任务能够正确地使用该参数。转换过程需要根据实际的参数类型进行类型匹配。

    4. 对于基本数据类型的值,例如 int ,必须传递该变量的地址,而不是其值本身。因此,在传递一个整数类型的参数时,必须使用取地址符 & ,将参数的地址传递给 xTaskCreate,而不是将该变量的值直接传递。

静态创建任务

xTaskCreateStatic() 用于静态方式创建任务,适用于禁用动态内存分配或对内存控制要求较高的应用场景。与动态创建任务不同,静态创建需要调用者显式提供任务栈和任务控制块所需的内存。

API 原型

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                const char * const pcName,
                                const uint32_t ulStackDepth,
                                void * const pvParameters,
                                UBaseType_t uxPriority,
                                StackType_t * const puxStackBuffer,
                                StaticTask_t * const pxTaskBuffer );
参数解释

传入参数

参数功能

参数说明

pxTaskCode

任务入口函数的指针。

该函数实现任务的具体功能,通常是一个无限循环,任务函数不应返回或退出,但可以删除自己。

pcName

任务的名称。

主要用于调试,最大名称长度由 configMAX_TASK_NAME_LEN 决定。

ulStackDepth

任务堆栈的大小。

word 为单位。根据处理器的字长,实际堆栈空间大小会根据该值自动计算。

pvParameters

传递给任务的参数。

需要确保参数在任务执行时仍然有效,不能传递局部变量的地址。

uxPriority

创建任务时的优先级。

数值越大,优先级越高。优先级不能超过 configMAX_PRIORITIES - 1

puxStackBuffer

任务使用的栈空间指针。

指向预先分配好的 StackType_t 类型数组,大小为 ulStackDepth ,不能是临时局部变量。

pxTaskBuffer

任务控制块的内存指针。

指向 StaticTask_t 类型的变量,任务控制信息存储在此结构中,不能是临时局部变量。

返回值说明

返回值

说明

返回创建的任务句柄

任务创建成功。该句柄用于后续操作,例如任务管理或删除。

NULL

内存分配失败。

备注

如果 puxStackBufferpxTaskBuffer 之一为 NULL ,任务不会被创建,函数将返回 NULL

  • 如果没有为任务分配足够的堆栈内存,或者堆栈缓冲区是 NULLpuxStackBuffer 将为 NULL

  • 如果没有为任务的控制块分配内存,或者提供的缓冲区是 NULLpxTaskBuffer 将为 NULL

在调用任务创建函数时,确保提供有效的内存地址并且内存空间足够,以避免任务创建失败。

示例 1:创建两个优先级相同的任务

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define STACK_DEPTH 2048    // 定义任务堆栈的大小
StackType_t xStackTask1[STACK_DEPTH];    // 任务 1 使用的栈空间,大小为 STACK_DEPTH
StaticTask_t xTaskBuffer1;               // 任务控制块 1

StackType_t xStackTask2[STACK_DEPTH];    // 任务 2 使用的栈空间,大小为 STACK_DEPTH
StaticTask_t xTaskBuffer2;               // 任务控制块 2

// 任务 1 函数
void vTask1 (void *pvParameters)
{
    // 任务函数的主体通常为死循环
    while (1) {
        printf("Task 1 is running\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 任务 2 函数
void vTask2 (void *pvParameters)
{
    while (1) {
        printf("Task 2 is running\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main(void)
{
    xTaskCreateStatic(vTask1, "Task1", STACK_DEPTH, NULL, 1, xStackTask1, &xTaskBuffer1);
    xTaskCreateStatic(vTask2, "Task2", STACK_DEPTH, NULL, 1, xStackTask2, &xTaskBuffer2);
}

以上代码执行结果如下:

Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running

本示例通过 xTaskCreateStatic() 以静态方式创建了两个优先级相同的任务,两个任务在运行过程中交替打印输出,系统采用时间片轮转调度机制,使任务 1 与任务 2 持续交替执行。

xTaskCreate 动态创建任务的方法相比,静态创建由用户显式提供的任务控制块 StaticTask_t 和任务栈空间 StackType_t[] ,无需依赖系统堆内存,具备更高的内存可控性和可靠性,特别适用于内存资源受限或禁止动态分配的嵌入式环境。而动态方法则由系统在堆中自动分配任务资源,使用上更为灵活,但存在堆空间不足或碎片化带来的创建失败风险。

静态与动态任务创建在内存管理机制和资源生命周期上存在本质区别:静态方式下任务所使用的资源由用户负责分配和管理,任务终止后不会自动释放;动态方式下资源由系统自动管理,任务删除时可自动释放相应内存,适合任务数量动态变化的场景。

在使用 xTaskCreateStatic() 创建任务时,应注意以下要点:

  • 多个任务可以设置相同的栈大小,具体值应根据任务的实际需求确定。

  • 每个任务必须使用独立的栈空间,栈数组不可在多个任务间共享。

  • 每个任务必须使用独立的任务控制块,不可共享。

  • 任务控制块参数必须通过取地址符 & 传入,否则无法将任务的控制信息正确写入该结构体,导致任务句柄无效或创建失败。

  • 所有栈数组和任务控制块应为全局或静态变量,避免因局部变量作用域结束被释放。

  • 静态创建的任务可通过 vTaskDelete() 删除,但其栈空间和控制块不会被系统回收,用户应确保资源有效性。

其他使用情况说明

在使用静态方式创建任务时,除了需要手动提供任务的栈空间与任务控制块外,任务优先级设置和参数传递方式均与动态创建任务完全一致。

  • 设置不同优先级任务:通过 xTaskCreateStatic() 函数中的 uxPriority 参数,可以为不同任务分别指定其优先级值。优先级的定义与调度行为与动态创建方式相同,高优先级的任务在调度器中将获得更高的执行优先权。

  • 为任务传递参数:可通过 pvParameters 参数向任务传入任意类型的指针(例如字符串、结构体或整型地址)。在任务函数中通过其形参接收并转换为适当类型后使用,与动态创建任务时的参数传递机制一致。

由于这两类使用场景在接口调用逻辑和行为上与动态创建任务相同,仅在任务创建函数的名称及栈空间分配方式上有所差异,因此不再单独提供完整代码示例。如需了解不同优先级调度与任务参数使用的详细实现,请参考前文中动态创建任务相关示例。

删除任务

vTaskDelete() 用于从 RTOS 内核中移除指定任务。被删除的任务将从所有相关的调度管理结构中解除,包括就绪列表、阻塞列表、挂起列表以及事件列表。该函数既可用于删除其他任务,也可用于任务自我删除。

API 原型

void vTaskDelete( TaskHandle_t xTaskToDelete );
参数解释

传入参数

参数功能

参数说明

xTaskToDelete

要删除的任务句柄。

用于任务自我删除时,传入参数 NULL ;用于删除其他任务时,传入目标任务的任务句柄。

示例 1:删除任务自身

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// 任务 1 函数
void vTask1 (void *pvParameters)
{
    int *cnt = pvParameters;
    while (1) {
        if (*cnt < 2) {
            (*cnt)++;
            printf("Task 1 is running\n");
        }
        else {
            printf("即将删除 Task 1 !\n");
            vTaskDelete(NULL);
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 任务 2 函数
void vTask2 (void *pvParameters)
{
    while (1) {
        printf("Task 2 is running\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

static int i = 0;

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, (void *)&i, 1, NULL);
    xTaskCreate(vTask2, "Task2", 2048, NULL, 1, NULL);
}

以上代码执行结果如下:

Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
即将删除 Task 1 !
Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running

在本示例中,系统创建了两个任务: Task1Task2。其中, Task1 接收一个整型变量的指针作为参数,用于计数控制。当计数值小于 2 时,任务每秒输出一次 Task 1 is running ,并将计数加 1;当计数达到 2 时,输出 即将删除 Task 1 ! ,随后调用 vTaskDelete(NULL) 删除自身。Task2 不接收任何参数,持续以 1 秒为周期输出 Task 2 is running

由于两个任务具有相同优先级,系统调度器采用时间片轮转策略,两个任务在运行初期交替执行。待 Task1 完成指定逻辑并成功删除后,系统中仅剩 Task2 继续独立运行。

该示例清晰地演示了如何通过任务参数实现数据共享,以及使用 vTaskDelete(NULL) 实现任务自删除的基本机制。适用于需要执行有限次操作后自动退出的任务场景。

备注

在任务自我删除时,其资源不会在调用 API 时立即释放,而是由空闲任务延迟完成释放。因此,系统设计中应确保空闲任务有足够的 CPU 时间执行,否则可能导致资源泄漏。

示例 2:删除其他任务

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t xHandleTask2 = NULL;

// 任务 1 函数
void vTask1 (void *pvParameters)
{
    int *cnt = pvParameters;
    while (1) {
        if (*cnt != 2) {
            printf("Task 1 is running\n");
        }
        else {
            if (xHandleTask2 != NULL) {
                printf("即将删除 Task 2 !\n");
                vTaskDelete(xHandleTask2);
                xHandleTask2 = NULL;
            }
        }
        (*cnt)++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 任务 2 函数
void vTask2 (void *pvParameters)
{
    while (1) {
        printf("Task 2 is running\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

static int i = 0;

void app_main(void)
{
    xTaskCreate(vTask1, "Task1", 2048, (void *)&i, 1, NULL);
    xTaskCreate(vTask2, "Task2", 2048, NULL, 1, &xHandleTask2);
}

以上代码执行结果如下:

Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
即将删除 Task 2 !
Task 1 is running
Task 1 is running
Task 1 is running
Task 1 is running

在本示例中,系统通过任务句柄 xHandleTask2 实现了从 Task1 删除 Task2 的操作。当 Task1 中计数变量的值达到指定条件时,调用 vTaskDelete(xHandleTask2) 删除 Task2 ,并将句柄置为 NULL ,防止后续重复操作。Task2 原本以 1 秒周期持续运行,被删除后停止输出,系统中仅剩 Task1 继续运行。

本示例展示了任务间通过句柄实现控制的基本用法,适用于需要在运行过程中动态终止其他任务的场景。相较于任务自删除方式,删除其他任务时应注意以下几点:

  • 任务句柄无需手动赋值,可以先将任务句柄变量声明并初始化为 NULL ,然后将其地址传入任务创建 API。任务创建成功后,该变量将被自动填充为新任务的句柄。

  • 任务句柄必须通过地址形式传入,即在变量前使用 & 取地址符,否则无法在外部获得有效句柄。

  • 任务创建必须成功,确保 xTaskCreate() 返回值为 pdPASS ,才能使用句柄执行删除操作。

  • 删除任务后应将句柄置空,防止再次使用已失效的任务引用,避免出现系统异常。

  • 任务间应建立明确的删除条件,避免因逻辑错误导致误删其他任务。

备注

当任务调用 vTaskDelete() 删除另一个任务时, FreeRTOS 会在函数内部自动释放该任务占用的 RTOS 内核资源,例如任务控制块和堆栈空间。

基于 ESP32 的双核架构,ESP-IDF 对标准 FreeRTOS 的任务创建机制进行了扩展,提供了支持多核任务调度的 API。开发者可以在创建任务时指定其运行核心,实现更灵活的资源调度与性能优化。具体内容可参阅 FreeRTOS (IDF) 文档的 任务 章节。

ESP-IDF 扩展:动态创建任务并绑定核心

xTaskCreatePinnedToCore() 是 ESP-IDF 提供的 FreeRTOS 扩展函数,用于创建具有特定核亲和性的任务,任务内存动态分配。相较于标准的 xTaskCreate(),该接口提供了更精细的任务调度控制,适用于多核系统中的高实时性场景。

API 原型

BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pxTaskCode,
                                    const char * const pcName,
                                    const uint32_t ulStackDepth,
                                    void * const pvParameters,
                                    UBaseType_t uxPriority,
                                    TaskHandle_t * const pxCreatedTask,
                                    const BaseType_t xCoreID );
参数解释

传入参数

参数功能

参数说明

pxTaskCode

任务入口函数的指针。

该函数实现任务的具体功能,通常是一个无限循环,任务函数不应返回或退出,但可以删除自己。

pcName

任务的名称。

主要用于调试。

ulStackDepth

任务堆栈的大小。

byte 为单位。

pvParameters

传递给任务的参数。

需要确保参数在任务执行时仍然有效,不能传递局部变量的地址。

uxPriority

创建任务时的优先级。

数值越大,优先级越高。

pxCreatedTask

用于返回创建的任务句柄。

此为可选参数,可用于后续对该任务进行删除、挂起、恢复等操作。如果不想传入该参数,可以将其设置为 NULL

xCoreID

用于指定任务使用的核心。

可设置为 0 (核心 0)、 1 (核心 1)、或 tskNO_AFFINITY (允许任务在任一核心运行)。

备注

  • xTaskCreate() 中的栈深度单位为 word (通常为 4 字节),而 xTaskCreatePinnedToCore() 中的栈深度单位为 byte。两者换算关系为:1 word = 4 byte (在 32 位架构下)。注意避免单位混淆导致栈空间不足。

  • 绑定核心后任务将始终运行在指定核心,不会自动迁移。

返回值说明

返回值

说明

pdPASS

任务创建成功,并添加该任务至就绪列表。

errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY

内存分配失败或传入了非法核心 ID 。

示例 1:创建三个优先级相同但绑定不同核心的任务

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/idf_additions.h"

// 任务 1 函数
void vTask1 (void *pvParameters)
{
    while (1) {
        printf("Task 1 is running on Core %d\n", xPortGetCoreID());
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vTask2 (void *pvParameters)
{
    const int *pcTaskIndex = pvParameters;
    while (1) {
        printf("Task %d is running on Core %d\n", *pcTaskIndex, xPortGetCoreID());
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

static const int pcTextForTask2 = 2;
static const int pcTextForTask3 = 3;

void app_main(void)
{
    xTaskCreatePinnedToCore(vTask1, "Task1", 2048, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(vTask2, "Task2", 2048, (void *)&pcTextForTask2, 1, NULL, 1);
    xTaskCreatePinnedToCore(vTask2, "Task3", 2048, (void *)&pcTextForTask3, 1, NULL, tskNO_AFFINITY);
}

以上代码执行结果如下:

Task 1 is running on Core 0
Task 3 is running on Core 0
Task 2 is running on Core 1
Task 1 is running on Core 0
Task 3 is running on Core 0
Task 2 is running on Core 1

该示例展示了如何使用 xTaskCreatePinnedToCore() 创建三个优先级相同的任务,并将它们分别绑定到核心 0、核心 1 ,以及未绑定核心(即不人为指定核心,允许调度器在任一核心上调度执行,行为与 xTaskCreate() 等效)。

同时,此示例使用传参方式创建任务。两个任务共用同一任务函数 vTask2 ,通过传入不同的整型常量地址进行区分。该参数传递方式在 xTaskCreate() 的示例 3 中已有介绍,此处不再赘述。

代码执行结果显示,每个任务在其预期的核心上运行, xPortGetCoreID() 的输出验证了任务的实际调度情况。与仅使用 xTaskCreate() 相比,该示例中由于任务优先级和行为相同,因此运行效果无显著差异,说明是否绑定核心在简单场景下对行为没有决定性影响。

备注

当使用 tskNO_AFFINITY 创建任务时,任务通常首次在当前核心执行,之后调度器可在任意核心上迁移调度该任务(除非设置 configUSE_CORE_AFFINITY)。

实际工程中,合理绑定任务到特定核心可带来以下优势:

  • 隔离关键任务:将高实时性或对时延敏感的任务绑定到一个相对空闲的核心上,避免系统任务的干扰。

  • 提升执行效率:减少任务在不同核心间的切换,提高 CPU 缓存命中率。

  • 提升调试效率:便于识别任务运行位置,辅助调试与性能分析。

  • 优化负载分布:通过手动调度实现多核资源合理利用与任务隔离。

ESP-IDF 扩展:静态创建任务并绑定核心

xTaskCreateStaticPinnedToCore() 是 ESP-IDF 提供的 FreeRTOS 扩展函数,用于创建具有特定核亲和性的任务,任务内存静态分配,即由用户提供。相较于标准的 xTaskCreateStatic(),该接口在静态资源分配的基础上,提供了对多核系统中任务执行位置的精确控制,适用于资源受限且对运行核心有明确要求的场景。

API 原型

TaskHandle_t xTaskCreateStaticPinnedToCore( TaskFunction_t pxTaskCode,
                                            const char * const pcName,
                                            const uint32_t ulStackDepth,
                                            void * const pvParameters,
                                            UBaseType_t uxPriority,
                                            StackType_t * const puxStackBuffer,
                                            StaticTask_t * const pxTaskBuffer,
                                            const BaseType_t xCoreID );
参数解释

传入参数

参数功能

参数说明

pxTaskCode

任务入口函数的指针。

该函数实现任务的具体功能,通常是一个无限循环,任务函数不应返回或退出,但可以删除自己。

pcName

任务的名称。

主要用于调试。

ulStackDepth

任务堆栈的大小。

byte 为单位。

pvParameters

传递给任务的参数。

需要确保参数在任务执行时仍然有效,不能传递局部变量的地址。

uxPriority

创建任务时的优先级。

数值越大,优先级越高。

puxStackBuffer

任务使用的栈空间指针。

指向预先分配好的 StackType_t 类型数组,大小为 ulStackDepth ,不能是临时局部变量。

pxTaskBuffer

任务控制块的内存指针。

指向 StaticTask_t 类型的变量,任务控制信息存储在此结构中,不能是临时局部变量。

xCoreID

用于指定任务使用的核心。

可设置为 0 (核心 0)、 1 (核心 1)、或 tskNO_AFFINITY (允许任务在任一核心运行)。

返回值说明

返回值

说明

返回创建的任务句柄

任务创建成功。该句柄用于后续操作,例如任务管理或删除。

NULL

内存分配失败。

示例 1:创建三个优先级相同但绑定不同核心的任务

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/idf_additions.h"

#define STACK_DEPTH     2048
// 定义各个任务的栈空间和任务控制块
StackType_t xStackTask1[STACK_DEPTH];
StaticTask_t xTaskBuffer1;

StackType_t xStackTask2[STACK_DEPTH];
StaticTask_t xTaskBuffer2;

StackType_t xStackTask3[STACK_DEPTH];
StaticTask_t xTaskBuffer3;

// 任务 1 函数
void vTask1 (void *pvParameters)
{
    while (1) {
        printf("Task 1 is running on Core %d\n", xPortGetCoreID());
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vTask2 (void *pvParameters)
{
    const int *pcTaskIndex = pvParameters;
    while (1) {
        printf("Task %d is running on Core %d\n", *pcTaskIndex, xPortGetCoreID());
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

static const int pcTextForTask2 = 2;
static const int pcTextForTask3 = 3;

void app_main(void)
{
    xTaskCreateStaticPinnedToCore(vTask1, "Task1", STACK_DEPTH, NULL, 1, xStackTask1, &xTaskBuffer1, 0);
    xTaskCreateStaticPinnedToCore(vTask2, "Task2", STACK_DEPTH, (void *)&pcTextForTask2, 1, xStackTask2, &xTaskBuffer2, 1);
    xTaskCreateStaticPinnedToCore(vTask2, "Task3", STACK_DEPTH, (void *)&pcTextForTask3, 1, xStackTask3, &xTaskBuffer3, tskNO_AFFINITY);
}

以上代码执行结果如下:

Task 1 is running on Core 0
Task 3 is running on Core 0
Task 2 is running on Core 1
Task 1 is running on Core 0
Task 3 is running on Core 0
Task 2 is running on Core 1

本示例展示了三个优先级相同的任务,分别绑定到不同的核心执行,并采用静态方式创建。示例中涉及的任务静态创建方法、任务绑定核心方式、参数传递的注意事项等内容,均已在前文的 xTaskCreateStatic()xTaskCreatePinnedToCore() 部分进行说明,可在相应章节中查阅详情。

ESP-IDF 扩展:删除任务

对于任务删除,ESP-IDF 虽对其内部实现进行了改进,但未修改 API 的原型或参数,因此此处不再单独展开说明。有关其内部实现部分可参考 FreeRTOS (IDF) 文档的 删除任务 章节。