FreeRTOS基础学习(二)任务

目录

一. 什么是任务(Task)?

二.FreeRTOS任务常见函数

1.xTaskCreate() 创建新任务

(1)函数原型

(2)返回值

(3)各个参数详细解释

2、vTaskDelete() 删除一个任务

(1)函数原型

(2)什么场景会用到删除任务?

(3)代码示例

3、vTaskDelay(TickType_t xTicksToDelay)将一个任务挂起指定时间

(1)函数原型

(2)使用场景

(3)代码示例:

(4)注意事项

4、vTaskSuspend() / vTaskResume() – 挂起和恢复任务

(1)函数原型

(2)使用场景

(3)代码示例

(4)注意事项

5、vTaskPrioritySet() – 动态修改任务优先级

(1)函数原型

(2)使用场景

(3)代码示例

三、FreeRTOS 任务间间是如何通讯?

1.队列(Queue)

2.信号量(Semaphore)

3.软件定时器(Timer)

4.事件组(Event Group)


一. 什么是任务(Task)?

在 FreeRTOS 中,任务(Task)是一个独立执行的程序单元,通常表现为一个具有无限循环的函数。每个任务都有独立的任务栈、任务控制块(TCB)、优先级和状态信息。

任务的基本状态包括:

  1. 就绪态(Ready):任务已准备好运行,等待调度器分配 CPU。

  2. 运行态(Running):任务正在占用 CPU 执行。

  3. 阻塞态(Blocked):任务在等待某个事件(如延时、队列、信号量等),暂不参与调度。

  4. 挂起态(Suspended):任务被挂起,必须手动恢复,调度器不会考虑它。

  5. 删除态(Deleted):任务被删除,系统可回收其资源(可选状态,某些资料也称其为“未创建态”)。

二.FreeRTOS任务常见函数

1.xTaskCreate() 创建新任务
(1)函数原型

创建freeRTOS任务函数:

BaseType_t xTaskCreate(
  TaskFunction_t pxTaskCode,    // 任务函数指针
  const char * const pcName,    // 任务名称(调试用)
  configSTACK_DEPTH_TYPE usStackDepth,  // 任务栈大小,单位是字节
  void *pvParameters,           // 传递给任务的参数
  UBaseType_t uxPriority,       // 任务优先级(数值越大,优先级越高)
  TaskHandle_t *pxCreatedTask   // 返回的任务句柄(可为 NULL)
);

xTaskCreate()函数定义在头文件:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
(2)返回值

返回值是任务创建是否成功:

  • pdPASS(值为 1):任务创建成功;

  • errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(值为 0):任务创建失败(可能是因为堆不够用了);

(3)各个参数详细解释

pxTaskCode:你定义的任务函数,具体的任务函数格式如下:

void vTaskFunction(void *pvParameters)(这个格式必须固定);

详细任务函数是这样写的:

void vTaskFunction(void *pvParameters)
{
    while (1) {
        // 任务循环执行的内容
        vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时 1 秒
    }
}
//注意:这个任务函数不能返回,要用 while(1) 保持循环;
//所有任务都是并发调度的,所以必须显式释放 CPU 控制权,如使用 vTaskDelay()等;

pcName:任务的名字,需要是一个字符串,用于标识任务;

③usStackDepth:分配的栈大小(stack size),决定了任务运行时的“工作空间”大小。单位是字节!

如果你创建了一个任务:xTaskCreate(vTask1, "MyTask", 2048, NULL, 1, NULL);代表分配了2048 B的栈空间给这个任务;

那我们在创建任务的时候,该给这个栈分配多大的空间呢?大致分为3个区间:

  • 简单任务(只做打印、延时)→ 1024 ~ 2048 通常够用;

  • 稍复杂的任务(包含字符串处理、调用库函数)→ 2048 ~ 4096;

  • 非常复杂的任务(涉及协议栈、大数组、递归)→ 4096 以上;

当你每创建一个任务,FreeRTOS 就会为它在内存中分配一段独立的栈空间。所以,这里每个 FreeRTOS 任务都有自己的独立栈空间,任务之间栈空间互不干扰,保证了任务上下文切换的可靠性。

④pvParameters:传递给任务的参数,可以是结构体或基本类型的指针;这个参数是传给任务函数的输入参数,你可以把它看作是任务函数的“启动传参”。

void Task1(void *pvParameters)
{
    char *message = (char *)pvParameters;//强转;
    while(1) {
        printf("任务收到消息: %s\n", message);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
void app_main(void)
{
    char *msg = "我是传给任务的参数";
    xTaskCreate(Task1, "MyTask", 2048, msg, 1, NULL);
}

⑤ uxPriority:用来指定任务的“优先级”的,也就是任务的重要程度,数值越大,表示优先级越高,能更早被调度运行,它的值大小通常从 0 ~ configMAX_PRIORITIES - 1 ;

pxCreatedTask:返回的任务句柄,如果你想后续操作这个任务(比如挂起它、删除它、修改优先级等),你就需要获取这个任务的“身份ID”—这个ID就是任务句柄。pxCreatedTask 是一个指针,FreeRTOS 会把新创建的任务句柄写进它指向的位置,如果你不需要以后操作这个任务,可以传 NULL;(第4个参数是属于输入参数,而第6个这个是输出) ;

(4)创建任务的代码示例

示例 1:创建两个任务

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

// 任务1函数
void task1(void *pvParameters)
{
    while (1) {
        printf("Task 1 is running...\n");
        vTaskDelay(pdMS_TO_TICKS(1000));  // 延时1秒--100个tick
    }
}

// 任务2函数
void task2(void *pvParameters)
{
    while (1) {
        printf("Task 2 is running...\n");
        vTaskDelay(pdMS_TO_TICKS(1500));  // 延时1.5秒
    }
}

void app_main(void)
{
    // 创建任务1
    xTaskCreate(task1, "Task1", 2048, NULL, 1, NULL);
    // 创建任务2
    xTaskCreate(task2, "Task2", 2048, NULL, 1, NULL);
}

示例 2:你有两个任务:

  • Task A(数据采集任务):一直循环采集传感器数据;

  • Task B(控制任务):定时挂起或恢复 Task A,比如在某些条件下暂停采集(例如用户按了某个按钮或温度过高)

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

static const char *TAG = "TASK_CTRL";

TaskHandle_t sensorTaskHandle = NULL; // 用于存储 sensor task 的任务句柄

void sensorTask(void *pvParameters)
{
    while (1)
    {
        ESP_LOGI(TAG, "采集传感器数据...");
        vTaskDelay(pdMS_TO_TICKS(1000)); // 每1秒采集一次
    }
}

void controlTask(void *pvParameters)
{
    while (1)
    {
        ESP_LOGI(TAG, "控制任务:挂起采集任务");
        vTaskSuspend(sensorTaskHandle); // 挂起传感器采集任务

        vTaskDelay(pdMS_TO_TICKS(5000)); // 停5秒

        ESP_LOGI(TAG, "控制任务:恢复采集任务");
        vTaskResume(sensorTaskHandle); // 恢复采集任务

        vTaskDelay(pdMS_TO_TICKS(10000)); // 10秒后再次挂起
    }
}

void app_main(void)
{
    // 创建采集任务,并返回任务句柄
    xTaskCreate(sensorTask, "SensorTask", 2048, NULL, 5, &sensorTaskHandle);

    // 创建控制任务(不需要句柄)
    xTaskCreate(controlTask, "ControlTask", 2048, NULL, 6, NULL);
}
2、vTaskDelete() 删除一个任务
(1)函数原型
void vTaskDelete(TaskHandle_t xTaskToDelete);//无返回值

参数xTaskToDelete指的是要删除的任务的句柄(TaskHandle)。如果这个参数是 NULL,表示删除当前正在运行的任务。

任务一旦被删除,其占用的栈空间和任务控制块(TCB)会被系统回收。

在删除任务后句柄不会自动置为 NULL,你可以手动置为 NULL 防止误用:

vTaskDelete(TaskAHandle);

TaskAHandle = NULL;

删除指定任务是送句柄:vTaskDelete(xHandle); 如果是删除当前任务,可以写vTaskDelete(NULL); 。

(2)什么场景会用到删除任务?

①任务完成其工作后自动释放资源

  • 有些任务是“临时”性质的,比如一次性执行某个操作(文件写入、数据上传、传感器采样处理等),完成后不再需要。

  • 这种任务执行完毕后调用 vTaskDelete(NULL) 自杀,节省系统资源。

  • 示例:传感器采样任务,采样并上传完数据后,自动删除自己。

②根据系统运行状态动态管理任务

  • 某些任务只在特定条件下需要存在,比如功能开关、工作模式切换等。

  • 关闭某模块时,删除相关任务,释放CPU和内存,节省功耗。

  • 示例:蓝牙任务只在蓝牙打开时创建,关闭时删除。

③ 错误恢复和重启任务

  • 如果某个任务出现异常或卡死,系统可能选择删除并重新创建任务来“重启”它。

  • 示例:通信任务检测到长时间无响应,删除并重建任务恢复状态。

④ 内存管理需要

  • 任务创建时会分配堆栈内存,频繁创建的任务如果不删除会导致内存泄漏。

  • 删除任务可以及时释放堆栈和任务控制块(TCB)的内存。

⑤ 动态任务管理系统

  • 有些系统设计成动态创建和删除任务,根据用户操作或运行需求灵活调度。

  • 例如多用户设备,为每个用户创建任务,用户退出时删除任务。

(3)代码示例
/*任务:
*1、创建两个任务
*2、任务 A 每秒打印一次信息
 *3、任务 B 启动后等待 5 秒,然后删除任务 A
 */
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

TaskHandle_t TaskAHandle = NULL;

void TaskA(void *pvParameters)
{
    while (1)
    {
        printf("Task A is running...\n");
        vTaskDelay(pdMS_TO_TICKS(1000));  // 延迟 1 秒
    }
}

void TaskB(void *pvParameters)
{
    printf("Task B is running... Will delete Task A in 5 seconds.\n");
    vTaskDelay(pdMS_TO_TICKS(5000));  // 延迟 5 秒

    printf("Task B deleting Task A now.\n");
    vTaskDelete(TaskAHandle);         // 删除任务 A
    TaskAHandle = NULL;//防止误用;

    printf("Task B finished its job. Suspending itself.\n");
    vTaskSuspend(NULL);               // 挂起自己,不再执行
}

void app_main(void)
{
    xTaskCreate(TaskA, "TaskA", 2048, NULL, 2, &TaskAHandle);
    xTaskCreate(TaskB, "TaskB", 2048, NULL, 2, NULL);
}
3、vTaskDelay(TickType_t xTicksToDelay)将一个任务挂起指定时间
(1)函数原型
void vTaskDelay(const TickType_t xTicksToDelay);//无返回值
(2)使用场景
  • 周期性任务;

  • 延时启动、稳定时序;

(3)代码示例
void sensorTask(void *pvParam)
{
    while (1)
    {
        printf("读取传感器数据\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
(4)注意事项
  • 延时单位是 tick,建议配合 pdMS_TO_TICKS(ms) 使用;

  • 延时过程中,任务进入 Blocked 状态,允许其他任务运行;

  • 它并不精确,是相对于进入时刻“起跳”的。

4、vTaskSuspend() / vTaskResume() – 挂起和恢复任务
(1)函数原型
void vTaskSuspend(TaskHandle_t xTaskToSuspend);//无返回值
void vTaskResume(TaskHandle_t xTaskToResume);//无返回值
(2)使用场景
  • 暂停后台任务;

  • 控制任务在特定事件前后运行;

(3)代码示例
TaskHandle_t ledTaskHandle = NULL;

void ledTask(void *pvParam)
{
    while (1)
    {
        printf("LED任务执行中\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void app_main()
{
    xTaskCreate(ledTask, "LED Task", 2048, NULL, 3, &ledTaskHandle);
    vTaskDelay(pdMS_TO_TICKS(5000));
    vTaskSuspend(ledTaskHandle);  // 暂停任务
    vTaskDelay(pdMS_TO_TICKS(5000));
    vTaskResume(ledTaskHandle);   // 恢复任务
}
(4)注意事项
  • xTaskToSuspend/Resume 不能为 NULL;

  • 挂起状态下任务不会被调度;

  • 推荐使用任务句柄来管理。

5、vTaskPrioritySet() – 动态修改任务优先级
(1)函数原型
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);//无返回值
(2)使用场景
  • 实时响应需求高时临时提升优先级;

  • 多任务之间协调顺序。

(3)代码示例
vTaskPrioritySet(myTaskHandle, 8);  // 提高任务优先级,最新的优先级是8;
三、FreeRTOS 任务间间是如何通讯?

FreeRTOS提供了以下几类通讯机制:队列、信号量、互斥量、事件组、软件定时器、任务通知;

1.队列(Queue)

队列是FreeRTOS中最常见的任务通讯机制之一,主要用于任务之间或中断和任务之间传递数据,它本质上是一个先进先出的数据结构(FIFO)。

详细介绍请跳转笔记《FreeRTOS基础学习(三)消息队列》

2.信号量(Semaphore)

信号量是一种二进制或计数型“标志量”,本身不传数据,只传“状态”或“通知”。用于协调多个任务或中断对共享资源的访问,防止资源冲突。它就像一个“通行证”,拿到信号量 = 拿到通行证,可以进入访问资源;如果没拿到,那就等待通行证释放。FreeRTOS提供了3种主要类型的信号量:二值信号量、计数信号量、互斥信号量。

详细介绍请跳转笔记《FreeRTOS基础学习(四)信号量》

3.软件定时器(Timer)

软件定时器是一个定时工具,时间到时自动触发回调函数。用于延迟执行某些逻辑,比如周期性检查、重试操作、状态超时处理等。它不依赖于任务,后台由 FreeRTOS 的定时器服务任务统一调度。

 详细介绍请跳转笔记《FreeRTOS基础学习(五)软件定时器》

4.事件组(Event Group)

事件组是 是一个 32 位的标志位(每个位都可代表一个事件),用来管理和同步多个事件标志(每个位代表一个事件)。每个事件位是 uint32_t 类型的一个位(共32位),可以用来表示不同事件的发生状态。 

详细介绍请跳转笔记《FreeRTOS基础学习(六)事件组》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值