ESP32下FreeRTOS实时操作系统使用

ESP32下FreeRTOS实时操作系统使用

文章目录

一、概述

​ 我们的ESP32是基于ESP-IDF来开发的,而ESP-IDF是基于FreeRTOS的框架,里面用到的组件,包括应用程序都是基于FreeRTOS来开发的,因此我们必须掌握FreeRTOS的使用,这里我们我深究FreeRTOS的原理,只关注FreeRTOS API接口的使用,在前面我们学习了Linux系统高级编程,如:文件、进程、进程间通信、线程、多线程、网络等,我们学习过更高级的Linux操作系统,那么使用这个FreeRTOS实时操作系统应该会很简单。

二、为什么要使用实时操作系统RTOS?

​ 在低端设备中程序基本可以分为:裸机和RTOS,针对简单的程序,我们使用裸机程序完全可以满足,一旦功能复杂,程序模块众多的时候,裸机程序很难满足我们的需求,因此我们需要使用操作系统。下面我们举个例子:

  1. 裸机程序:

    while(1){
         
         
        func1();
        func2();
        func3();
    }
    
  2. RTOS程序:

    while(1){
         
         
        func1();
    }
    
    while(1){
         
         
        func2();
    }
    
    while(1){
         
         
        func3();
    }
    

对于裸机程序我们需要把所有的功能模块写在一个while大循环里,可以想象其中任何一个功能模块发生阻塞或者延时等待时,其他的功能模块都要等待。而对于RTOS程序可以创建多个任务处理不同的功能模块,当模块A需要阻塞时,它可以放弃自己的执行时间片,把时间片让给其他任务,这样整个程序可以高效的运行。

三、FreeRTOS任务

3.1 什么是 FreeRTOS 任务?

在 FreeRTOS 中,任务(Task)是一个独立运行的程序代码块,它是实时操作系统的基本执行单元。FreeRTOS 的多任务机制允许多个任务“并行运行”(实际上是通过快速切换任务实现的,称为任务调度)。每个任务都有自己的运行逻辑和执行周期,FreeRTOS 会在后台管理这些任务的执行。


3.2 FreeRTOS 任务的特点

  1. 并发性
    • FreeRTOS 会在多个任务之间切换,使得它们看起来像是同时运行(多任务)。但在单核 MCU 上,任务的切换是由 FreeRTOS 调度器(Scheduler)以时间片轮转或优先级方式实现的。
  2. 独立性
    • 每个任务都是独立的,具有自己的上下文(寄存器值、堆栈等)。
  3. 优先级
    • 每个任务可以设置一个优先级(通常用整数表示),优先级越高,任务获得 CPU 时间的可能性越大。
  4. 状态管理
    • 任务可以处于不同的状态,如 运行(Running)就绪(Ready)阻塞(Blocked)挂起(Suspended)
  5. 堆栈
    • 每个任务都需要单独的堆栈来存储其运行时的上下文信息。

3.3 FreeRTOS 任务的生命周期

  1. 创建任务
    • 使用 xTaskCreatexTaskCreatePinnedToCore 创建一个任务,并指定任务函数、名称、堆栈大小、优先级等。
  2. 任务调度
    • FreeRTOS 调度器启动后,任务会根据优先级和状态被安排执行。
  3. 任务状态切换
    • FreeRTOS 会根据任务的运行情况、事件触发、延时(vTaskDelay)等条件切换任务的状态。
  4. 删除任务
    • 使用 vTaskDelete 删除不再需要的任务,并释放其分配的资源。

3.4 FreeRTOS 任务的状态

一个 FreeRTOS 任务可以处于以下几种状态:

在这里插入图片描述

  1. Running(运行中)
    • 当前任务正在 CPU 上执行。
  2. Ready(就绪)
    • 任务准备好运行,等待 CPU 分配时间片。
  3. Blocked(阻塞)
    • 任务正在等待某个事件(如延时、信号量、队列消息等)发生。
  4. Suspended(挂起)
    • 任务被暂停,直到另一个任务显式唤醒它。

3.5 FreeRTOS 任务与多线程的区别

FreeRTOS 的任务与传统的操作系统中的线程有一些相似之处,但也有以下区别:

  1. FreeRTOS 更轻量,适用于资源受限的嵌入式系统。
  2. 任务的调度是实时性的,优先保证高优先级任务的运行。
  3. 没有标准的进程/线程隔离机制,任务之间共享整个内存空间。

3.6 常用 FreeRTOS 任务函数

3.6.1 任务创建函数xTaskCreate()原型和头文件:

原型

BaseType_t xTaskCreate(
    TaskFunction_t pvTaskCode,  			// 任务函数指针
    const char * const pcName, 				// 任务名称
    configSTACK_DEPTH_TYPE usStackDepth,	// 堆栈大小
    void *pvParameters,        				// 任务参数
    UBaseType_t uxPriority,    				// 任务优先级
    TaskHandle_t *pxCreatedTask 			// 任务句柄
);

参数

  • pvTaskCode:指向任务函数的指针,该函数的原型为 void TaskFunction(void *pvParameters)
  • pcName:任务的名称(字符串),主要用于调试,可设置为 NULL。
  • usStackDepth:任务堆栈的大小(以字为单位,通常是 4 字节一个字)。
  • pvParameters:传递给任务的参数,可为 NULL。
  • uxPriority:任务的优先级,数值越大优先级越高。
  • pxCreatedTask:返回任务的句柄,可用于后续操作(如删除任务),可设置为 NULL。

返回值

  • 如果创建成功,返回 pdPASS;否则返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY

头文件

#include "freertos/FreeRTOS.h"	// 包含FreeRTOS头文件
#include "freertos/task.h"		// 包含任务管理头文件

3.6.2 任务创建函数xTaskCreatePinnedToCore()原型和头文件:(仅在 ESP-IDF 中可用)

原型

BaseType_t xTaskCreatePinnedToCore(
    TaskFunction_t pvTaskCode,
    const char * const pcName,
    const uint32_t usStackDepth,
    void *pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t *pxCreatedTask,
    const BaseType_t xCoreID
);

参数

  • pvTaskCode:指向任务函数的指针,该函数的原型为 void TaskFunction(void *pvParameters)

  • pcName:任务的名称(字符串),主要用于调试,可设置为 NULL。

  • usStackDepth:任务堆栈的大小(以字为单位,通常是 4 字节一个字)。

  • pvParameters:传递给任务的参数,可为 NULL。

  • uxPriority:任务的优先级,数值越大优先级越高。

  • pxCreatedTask:返回任务的句柄,可用于后续操作(如删除任务),可设置为 NULL。

  • xCoreID:指定运行任务的 CPU 核心,01(在双核芯片上),可以用 tskNO_AFFINITY 表示任何核心。

头文件

#include "freertos/FreeRTOS.h"	// 包含FreeRTOS头文件
#include "freertos/task.h"		// 包含任务管理头文件

3.6.3 任务延时函数vTaskDelay()原型和头文件:

原型

void vTaskDelay(const TickType_t xTicksToDelay);

参数

  • xTicksToDelay:延时的时钟节拍数,使用宏 pdMS_TO_TICKS(ms) 将毫秒转为节拍数。

功能

  • 将当前任务置为阻塞状态,指定时间到达后进入就绪状态。

头文件

#include "freertos/FreeRTOS.h"	// 包含FreeRTOS头文件
#include "freertos/task.h"		// 包含任务管理头文件

3.6.4 任务删除函数vTaskDelete()原型和头文件:

原型

void vTaskDelete(TaskHandle_t xTaskToDelete);

参数

  • xTaskToDelete:需要删除的任务句柄,传递 NULL 表示删除调用该函数的任务本身。

功能

  • 删除指定任务并释放其资源。

头文件

#include "freertos/FreeRTOS.h"	// 包含FreeRTOS头文件
#include "freertos/task.h"		// 包含任务管理头文件

3.6.5 暂停任务函数vTaskSuspend()原型和头文件:

原型

void vTaskSuspend(TaskHandle_t xTaskToSuspend);

参数

  • xTaskToSuspend:需要挂起的任务句柄,传递 NULL 表示挂起调用此函数的任务。

功能

  • 暂停任务的执行,任务进入挂起状态。

头文件

#include "freertos/FreeRTOS.h"	// 包含FreeRTOS头文件
#include "freertos/task.h"		// 包含任务管理头文件

3.6.6 恢复挂起任务函数vTaskResume()原型和头文件:

原型

void vTaskResume(TaskHandle_t xTaskToResume);

参数

  • xTaskToResume:需要恢复的任务句柄。

功能

  • 将挂起的任务恢复到就绪状态。

头文件

#include "freertos/FreeRTOS.h"	// 包含FreeRTOS头文件
#include "freertos/task.h"		// 包含任务管理头文件

3.6.7 返回指定任务优先级函数uxTaskPriorityGet()原型和头文件:

原型

UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);

参数

  • xTask:需要获取优先级的任务句柄,传递 NULL 表示获取调用该函数的任务的优先级。

返回值

  • 返回指定任务的优先级。

头文件

#include "freertos/FreeRTOS.h"	// 包含FreeRTOS头文件
#include "freertos/task.h"		// 包含任务管理头文件

3.6.8 设置任务新优先级函数vTaskPrioritySet()原型和头文件:

原型

void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);

参数

  • xTask:需要更改优先级的任务句柄,传递 NULL 表示更改调用该函数的任务的优先级。
  • uxNewPriority:新优先级。

功能

  • 设置任务的新优先级。

头文件

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

3.6.9 获取当前正在运行任务的句柄函数xTaskGetCurrentTaskHandle()原型和头文件:

原型

TaskHandle_t xTaskGetCurrentTaskHandle(void);

功能

  • 获取当前正在运行任务的句柄。

头文件

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

3.7 任务创建实例

以下是一个使用 FreeRTOS 创建任务的示例程序,该程序适用于 ESP32(使用 ESP-IDF 开发框架)。

3.7.1 示例说明:
  • 创建两个任务:
    • Task1:每秒打印 “Task 1 Running”
    • Task2:每两秒打印 “Task 2 Running”
  • 使用 vTaskDelay() 进行任务间的延时
  • app_main() 中创建任务

3.7.2 完整代码:
#include <stdio.h>                  // 包含标准输入输出头文件
#include "freertos/FreeRTOS.h"      // 包含FreeRTOS头文件
#include "freertos/task.h"          // 包含任务管理头文件
#include "esp_log.h"                // 包含日志头文件

// 定义任务句柄(可选,用于后续控制任务)
TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

// 任务1:每秒打印一次
void Task1(void *pvParameters) {
   
   
    while (1) {
   
   
        ESP_LOGI("Task1", "Task 1 Running");
        vTaskDelay(pdMS_TO_TICKS(1000));        // 1000ms (1秒)
    }
}

// 任务2:每两秒打印一次
void Task2(void *pvParameters) {
   
   
    while (1) {
   
   
        ESP_LOGI("Task2", "Task 2 Running");    
        vTaskDelay(pdMS_TO_TICKS(2000));        // 2000ms (2秒)
    }
}

// FreeRTOS 主程序
void app_main(void) {
   
   
    ESP_LOGI("Main", "Starting FreeRTOS Tasks");

    // 创建任务1
    xTaskCreate(
        Task1,          // 任务函数
        "Task1",        // 任务名称
        2048,           // 任务堆栈大小(单位:字)
        NULL,           // 任务参数
        1,              // 任务优先级
        &Task1Handle    // 任务句柄
    );

    // 创建任务2
    xTaskCreate(
        Task2,          // 任务函数
        "Task2",        // 任务名称
        2048,           // 任务堆栈大小
        NULL,           // 任务参数
        2,              // 任务优先级(比Task1高)
        &Task2Handle    // 任务句柄
    );
}

3.7.3 代码解析:
  1. 头文件
    • #include "freertos/FreeRTOS.h" → FreeRTOS 核心库
    • #include "freertos/task.h" → 任务管理库
    • #include "esp_log.h" → ESP-IDF 日志库(仅适用于 ESP32)
    • #include <stdio.h> → 标准输入输出库(通用)
  2. 任务 Task1Task2
    • ESP_LOGI("Task1", "Task 1 Running"); → 通过 ESP_LOGI 记录日志
    • vTaskDelay(pdMS_TO_TICKS(1000)); → 任务休眠 1 秒
  3. 创建任务 xTaskCreate()
    • 参数:
      • 任务函数Task1Task2
      • 任务名称(字符串)
      • 堆栈大小2048,通常足够)
      • 任务参数(这里传 NULL
      • 优先级12,优先级 21 高)
      • 任务句柄(可用于管理任务)
  4. ESP-IDF 任务调度
    • app_main() 是 ESP32 的入口函数,相当于 main()
    • xTaskCreate() 创建任务后,FreeRTOS 任务调度开始运行。

3.7.4 运行效果:

当程序运行时,串口日志输出如下:

I (1000) Task1: Task 1 Running
I (2000) Task2: Task 2 Running
I (2000) Task1: Task 1 Running
I (4000) Task2: Task 2 Running
I (4000) Task1: Task 1 Running
...

说明:

  • Task1 每 1 秒打印一次
  • Task2 每 2 秒打印一次

通过任务的使用,FreeRTOS 可以在单片机中实现并发处理和实时响应。它非常适合需要同时管理多个任务(如传感器读取、通信协议处理、UI 更新等)的嵌入式项目。

四、FreeRTOS队列

FreeRTOS 队列(Queue) 是一种 任务间通信机制,允许任务之间或者任务与中断服务例程(ISR)之间安全地交换数据。队列可以存储不同数据类型,并支持先进先出(FIFO)的存取方式。


4.1 FreeRTOS 队列的特点

任务间通信:用于不同任务之间传递数据。
中断安全:可以从中断服务例程(ISR)中发送或接收数据(需使用特定 API)。
同步机制:队列可以用作同步工具,任务可以阻塞等待数据。
数据缓存:允许多个数据项排队等待接收。


4.2 常用 FreeRTOS 队列函数

4.2.1 创建队列函数xQueueCreate()原型和头文件:

原型

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

功能:创建一个新的队列,并返回该队列的句柄。

🔹 参数说明

  • uxQueueLength:队列最多可容纳的元素个数。
  • uxItemSize:队列中每个元素的大小(字节)。

🔹 返回值

  • 成功:返回队列句柄(QueueHandle_t)。
  • 失败:返回 NULL(可能是内存不足)。

🔹 头文件

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"

🔹 示例

QueueHandle_t myQueue = xQueueCreate(5, sizeof(int));  // 创建存储 5 个整数的队列

4.2.2 发送数据到队列函数xQueueSend()原型和头文件:

原型

BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);

功能:将数据放入队列(如果队列已满,可选择阻塞等待)。

🔹 参数说明

  • xQueue:目标队列的句柄。
  • pvItemToQueue:指向要发送数据的指针
  • xTicksToWait:如果队列已满,阻塞的时间(单位:tick,portMAX_DELAY 表示无限等待)。

🔹 返回值

  • pdPASS(成功)。
  • errQUEUE_FULL(队列已满,超时未能放入数据)。

🔹 头文件

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"

🔹 示例

int value = 100;
xQueueSend(myQueue, &value, portMAX_DELAY);

4.2.3 发送数据到队列(中断安全版本)函数xQueueSendFromISR()原型和头文件:

在 FreeRTOS 中,普通的 xQueueSend() 不能在中断服务程序(ISR) 中使用。要在 ISR(中断服务程序)中安全地向队列发送数据,需要使用中断安全版本 API

原型

BaseType_t xQueueSendFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
);

功能
🔹 在 ISR(中断服务程序)中安全地将数据发送到队列
🔹 如果队列已满,数据不会被添加,并立即返回。

🔹 参数说明

  • xQueue:目标队列的句柄(必须已创建)。
  • pvItemToQueue:指向要发送数据的指针(数据的大小由 xQueueCreate() 设定)。
  • pxHigherPriorityTaskWoken指向一个变量,如果任务切换应该发生,该变量会被设置为 pdTRUE

🔹 返回值

  • pdPASS 数据成功放入队列。
  • errQUEUE_FULL(队列已满,超时未能放入数据)。

🔹 头文件

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"

4.2.4 从队列接收数据函数xQueueReceive()原型和头文件:

原型

BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);

功能:从队列接收数据(如果队列为空,可选择阻塞等待)。

🔹 参数说明

  • xQueue:目标队列的句柄。
  • pvBuffer:用于存储接收数据的指针
  • xTicksToWait:如果队列为空,阻塞的时间(单位:tick,portMAX_DELAY 表示无限等待)。

🔹 返回值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值