了解 ESP32 FreeRTOS:初学者指南
了解 ESP32 FreeRTOS:初学者指南
ESP32 FreeRTOS是什么?
ESP32 FreeRTOS是针对ESP32微控制器的一个实时操作系统(RTOS),它采用了FreeRTOS内核,可以帮助开发人员在ESP32芯片上进行多任务处理。简单来说,FreeRTOS提供了一种方式来管理软件任务并协调它们的执行。
ESP32是一个功能强大的嵌入式系统,可以用于构建各种物联网应用程序。其中,FreeRTOS是一个广泛使用的实时操作系统,它针对嵌入式系统的需求进行了优化。本文将介绍ESP32 FreeRTOS的基础知识,包括如何配置FreeRTOS内核、如何创建任务和使用消息队列进行任务通信。
对于学习单片机的人,ESP32 FreeRTOS可以让他们更轻松地编写和维护复杂的程序,而无需手动跟踪和调度任务。此外,它还可以帮助学习者更好地理解实时系统设计和多任务处理概念,并使他们能够更好地应对未来的嵌入式系统开发挑战。
如何使用FreeRTOS?
-
导入正确的FreeRTOS库
-
配置FreeRTOS内核,在编译器根据程序的内存分配堆栈大小、选择调度算法、启动任务通知等。
-
创建好任务后编写任务代码,可以使用FreeRTOS提供的API来管理任务和同步。
-
编译和调试
哪些常用的函数?
以下是对于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。
- 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) { // 任务1
for (;;) {
//
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) { // 任务2
for (;;) {
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() {
}
使用队列的步骤如下:
- 使用xQueueCreate()函数创建一个队列,并指定队列大小和元素大小。
- 在生产者任务中,使用xQueueSend()函数将数据加入到队列中。
- 在消费者任务中,使用xQueueReceive()函数从队列中获取数据。
- 可以使用uxQueueMessagesWaiting()函数查看队列中当前的消息数目,以及使用uxQueueSpacesAvailable()函数查看队列中剩余空间的数量。
xQueueSend()函数用于将数据加入到队列中,其参数包括队列句柄、要发送的数据指针和等待时间。下面是这个函数的详细说明:
-
myQueue:队列句柄,由xQueueCreate()函数返回。
-
&data:要发送的数据的指针。此处使用&符号获取数据变量的地址,并将其传递给xQueueSend()函数。
-
portMAX_DELAY:等待时间,表示如果队列已满,则一直阻塞任务,直到队列有空间可用。也可以指定一个数值,表示最多等待多少个系统时钟节拍,例如500表示最多等待500个时钟节拍后退出等待。
需要注意的是,在向队列中添加数据时,通常需要检查xQueueSend()函数返回的值,以确定数据是否成功加入到队列中。如果队列已满,则xQueueSend()函数将返回errQUEUE_FULL错误代码。如果队列操作成功,则返回pdPASS代码。可以根据返回值采取相应的处理措施,以确保程序的正确性。