> 文章列表 > 了解 ESP32 FreeRTOS:初学者指南

了解 ESP32 FreeRTOS:初学者指南

了解 ESP32 FreeRTOS:初学者指南

了解 ESP32 FreeRTOS:初学者指南

  • ESP32 FreeRTOS是什么?
  • 如何使用FreeRTOS?
  • 哪些常用的函数
    • xTaskCreate()
    • vTaskDelete()
    • vTaskDelay()
    • xTicksToDelay()
    • xSemaphoreCreateBinary()
    • xSemaphoreGive()
    • xSemaphore:要释放的信号量的句柄。
    • xSemaphore:要获取的信号量的句柄。
    • xQueueCreate()
    • xQueueSend()
    • xQueueReceive()
  • 简单示例:创建两个任务并打印任务名称
  • 使用队列示例

ESP32 FreeRTOS是什么?

ESP32 FreeRTOS是针对ESP32微控制器的一个实时操作系统(RTOS),它采用了FreeRTOS内核,可以帮助开发人员在ESP32芯片上进行多任务处理。简单来说,FreeRTOS提供了一种方式来管理软件任务并协调它们的执行。

ESP32是一个功能强大的嵌入式系统,可以用于构建各种物联网应用程序。其中,FreeRTOS是一个广泛使用的实时操作系统,它针对嵌入式系统的需求进行了优化。本文将介绍ESP32 FreeRTOS的基础知识,包括如何配置FreeRTOS内核、如何创建任务和使用消息队列进行任务通信。

对于学习单片机的人,ESP32 FreeRTOS可以让他们更轻松地编写和维护复杂的程序,而无需手动跟踪和调度任务。此外,它还可以帮助学习者更好地理解实时系统设计和多任务处理概念,并使他们能够更好地应对未来的嵌入式系统开发挑战。

在这里插入图片描述

如何使用FreeRTOS?

  1. 导入正确的FreeRTOS库

  2. 配置FreeRTOS内核,在编译器根据程序的内存分配堆栈大小、选择调度算法、启动任务通知等。

  3. 创建好任务后编写任务代码,可以使用FreeRTOS提供的API来管理任务和同步。

  4. 编译和调试
    在这里插入图片描述

哪些常用的函数?

以下是对于ESP32使用FreeRTOS常用函数的详细介绍:

xTaskCreate()

函数原型:TaskHandle_t xTaskCreate(TaskFunction_t pvTaskCode, const char *const pcName, uint32_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pxCreatedTask)

功能:创建一个新的任务,并将其加入到FreeRTOS任务调度器中。

参数:

  • pvTaskCode:指向任务函数的指针。
  • pcName:任务名称。可选参数,可以为NULL。
  • usStackDepth:任务的堆栈大小。单位为字节。
  • pvParameters:传递给任务函数的参数。可选参数,可以为NULL。
  • uxPriority:任务的优先级。
  • pxCreatedTask:指向一个TaskHandle_t变量的指针。可选参数,如果不需要返回任务句柄,则可以为NULL。

返回值:如果成功创建了任务,则返回任务句柄;否则返回NULL。

vTaskDelete()

函数原型:void vTaskDelete(TaskHandle_t xTaskToDelete)

功能:删除指定的任务。

参数:

  • xTaskToDelete:要删除的任务的句柄。

返回值:无。

vTaskDelay()

函数原型:void vTaskDelay(const TickType_t xTicksToDelay)

功能:延时指定的时间。

参数:

xTicksToDelay()

延时的时间,以FreeRTOS系统滴答计数器的节拍数为单位。

返回值:无。

xSemaphoreCreateBinary()

函数原型:SemaphoreHandle_t xSemaphoreCreateBinary(void)

功能:创建一个二进制信号量。

参数:无。

返回值:如果成功创建了信号量,则返回信号量句柄;否则返回NULL。

xSemaphoreGive()

函数原型:BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)

功能:释放一个二进制信号量或者计数信号量的资源。

参数:

xSemaphore:要释放的信号量的句柄。

返回值:如果释放成功,则返回pdTRUE;否则返回pdFALSE。

  1. xSemaphoreTake()

函数原型:BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)

功能:获取一个二进制信号量或者计数信号量的资源。

参数:

xSemaphore:要获取的信号量的句柄。

  • xTicksToWait:等待获取信号量的超时时间,以FreeRTOS系统滴答计数器的节拍数为单位。如果该参数设置为0,则非阻塞地尝试获取信号量。

返回值:如果成功获取了信号量,则返回pdTRUE;如果等待超时,则返回pdFALSE。

xQueueCreate()

函数原型:QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)

功能:创建一个队列。

参数:

  • uxQueueLength:队列的可容纳元素个数。
  • uxItemSize:队列中一个元素的大小(以字节为单位)。

返回值:如果成功创建了队列,则返回队列的句柄;否则返回NULL。

xQueueSend()

函数原型:BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait)

功能:将消息发送到队列中。

参数:

  • xQueue:要发送消息的队列的句柄。
  • pvItemToQueue:指向要发送的数据的指针。
  • xTicksToWait:等待发送消息的超时时间,以FreeRTOS系统滴答计数器的节拍数为单位。如果该参数设置为0,则非阻塞地尝试发送消息。

返回值:如果成功发送了消息,则返回pdPASS;否则返回errQUEUE_FULL或者errQUEUE_BLOCKED(如果xTicksToWait大于0)。

xQueueReceive()

函数原型:BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait)

功能:从队列中接收消息。

参数:

  • xQueue:要接收消息的队列的句柄。
  • pvBuffer:

简单示例:创建两个任务并打印任务名称

/*
如何创建freertos任务
如何分配内存、任务优先级
创建任务后loop循环还能不能使用
*/
#include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif//创建任务函数
void Task1(void *pvParameters);
void Task2(void *pvParameters);void setup() {// put your setup code here, to run once:Serial.begin(115200);xTaskCreatePinnedToCore(Task1, "Task1"  // 任务名称,1024  // 任务栈大小,NULL  // 任务参数指针,2  // 任务优先级大小 -- 值越大优先级越大,NULL  // 任务句柄指针,ARDUINO_RUNNING_CORE);  // 处理器核心编号xTaskCreatePinnedToCore(Task2, "Task2"  // 任务名称,1024  // 任务栈大小,NULL  // 任务参数指针,1  // 任务优先级大小 -- 值越大优先级越大,NULL  // 任务句柄指针,ARDUINO_RUNNING_CORE);  // 处理器核心编号
}void loop() {// 空闲?Serial.println("loop");delay(1000);
}void Task1(void *pvParameters) {  // 任务1for (;;) {//Serial.println("task1");vTaskDelay(pdMS_TO_TICKS(1000));if (digitalRead(12) == HIGH) {Serial.println("GPIO12 is LOW. Deleting task...");vTaskDelete(NULL); // 删除当前任务}}
}void Task2(void *pvParameters) {  // 任务2for (;;) {Serial.println("task2");vTaskDelay(pdMS_TO_TICKS(1000));}
}

部分代码理解:

#if CONFIG_FREERTOS_UNICORE 
#define ARDUINO_RUNNING_CORE 0 
#else #define ARDUINO_RUNNING_CORE 1 
#endif

当FreeRTOS配置为单核模式时,ARDUINO_RUNNING_CORE宏被定义为0,表示应用程序在主核心上运行。而当FreeRTOS配置为双核模式时,ARDUINO_RUNNING_CORE宏被定义为1,表示应用程序在第二个核心上运行。

在ESP32上,可以使用两个独立的处理器核心来运行应用程序和操作系统。在双核模式下,一个核心运行FreeRTOS调度程序,另一个核心则可用于运行用户应用程序。这种方式可以提高系统性能和响应速度。

使用队列示例

#include <Arduino.h>
#include "freertos/queue.h"// 定义结构体类型
typedef struct {int id;char name[20];
} data_t;// 定义队列句柄和队列长度
QueueHandle_t queue;
const int queueLen = 10;void task1(void *pvParameters) {// 定义结构体变量并初始化data_t data = {1, "John"};while (1) {// 将结构体数据发送到队列中data.id++;xQueueSend(queue, &data, portMAX_DELAY);vTaskDelay(1000 / portTICK_PERIOD_MS);}
}void task2(void *pvParameters) {while (1) {// 从队列中接收数据data_t data;xQueueReceive(queue, &data, portMAX_DELAY);// 打印接收到的数据Serial.printf("ID: %d, Name: %s\\n", data.id, data.name);}
}void setup() {Serial.begin(115200);// 创建队列queue = xQueueCreate(queueLen, sizeof(data_t));// 创建两个任务xTaskCreate(task1, "Task 1", 10000, NULL, 1, NULL);xTaskCreate(task2, "Task 2", 10000, NULL, 2, NULL);
}void loop() {
}

在这里插入图片描述

使用队列的步骤如下:

  1. 使用xQueueCreate()函数创建一个队列,并指定队列大小和元素大小。
  2. 在生产者任务中,使用xQueueSend()函数将数据加入到队列中。
  3. 在消费者任务中,使用xQueueReceive()函数从队列中获取数据。
  4. 可以使用uxQueueMessagesWaiting()函数查看队列中当前的消息数目,以及使用uxQueueSpacesAvailable()函数查看队列中剩余空间的数量。

xQueueSend()函数用于将数据加入到队列中,其参数包括队列句柄、要发送的数据指针和等待时间。下面是这个函数的详细说明:

  • myQueue:队列句柄,由xQueueCreate()函数返回。

  • &data:要发送的数据的指针。此处使用&符号获取数据变量的地址,并将其传递给xQueueSend()函数。

  • portMAX_DELAY:等待时间,表示如果队列已满,则一直阻塞任务,直到队列有空间可用。也可以指定一个数值,表示最多等待多少个系统时钟节拍,例如500表示最多等待500个时钟节拍后退出等待。

需要注意的是,在向队列中添加数据时,通常需要检查xQueueSend()函数返回的值,以确定数据是否成功加入到队列中。如果队列已满,则xQueueSend()函数将返回errQUEUE_FULL错误代码。如果队列操作成功,则返回pdPASS代码。可以根据返回值采取相应的处理措施,以确保程序的正确性。

在这里插入图片描述