Arduino FreeRTOS是指在Arduino平台上运行FreeRTOS实时操作系统。它允许作者在设备上实现多任务并行处理,从而提高程序的灵活性和响应性。
一、主要特点
1、任务间通信机制
在实时操作系统(RTOS)中,任务间通信主要通过消息队列、信号量、事件标志等机制来实现。有效的通信机制允许不同任务之间传递数据和控制信息,从而协调工作。
2、同步机制
任务同步是确保多个任务能够在合适的时机访问共享资源的过程。常见的同步机制包括互斥锁、信号量和事件标志,能够有效防止数据竞争和资源冲突。
二、应用
1、使用消息队列实现任务间通信
#include <Arduino.h>
// 定义结构体类型,用于存储 x, y, z
typedef struct {
float x;
float y;
float z;
} SensorData;
// 定义队列句柄
QueueHandle_t xQueue;
// 任务1 - 生产者任务
void producerTask(void *pvParameters) {
SensorData data;
while (1) {
// 生成数据
data.x = random(1000) / 1000.0f; // 生成一个随机的浮动数
data.y = random(1000) / 1000.0f;
data.z = random(1000) / 1000.0f;
// 发送数据到队列
if (xQueueSend(xQueue, &data, portMAX_DELAY) == pdPASS) {
Serial.print("Produced: x = ");
Serial.print(data.x);
Serial.print(", y = ");
Serial.print(data.y);
Serial.print(", z = ");
Serial.println(data.z);
}
// 暂停任务1一段时间
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
// 任务2 - 消费者任务
void consumerTask(void *pvParameters) {
SensorData receivedData;
while (1) {
// 从队列接收数据
if (xQueueReceive(xQueue, &receivedData, portMAX_DELAY) == pdPASS) {
Serial.print("Consumed: x = ");
Serial.print(receivedData.x);
Serial.print(", y = ");
Serial.print(receivedData.y);
Serial.print(", z = ");
Serial.println(receivedData.z);
}
// 暂停任务2一段时间
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void setup() {
// 初始化串口
Serial.begin(115200);
while (!Serial) { }
// 创建队列,队列长度为10,每个队列项的大小为 SensorData 结构体
xQueue = xQueueCreate(10, sizeof(SensorData));
// 检查队列是否创建成功
if (xQueue == NULL) {
Serial.println("Failed to create queue!");
while (1);
}
// 创建任务
xTaskCreate(producerTask, "Producer Task", 2048, NULL, 1, NULL);
xTaskCreate(consumerTask, "Consumer Task", 2048, NULL, 1, NULL);
}
void loop() {
// FreeRTOS调度器管理任务,loop中不需要执行任何代码
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
代码解释:
- 队列创建:xQueue = xQueueCreate(10, sizeof(SensorData)); 创建了一个长度为10、每个元素为SensorData结构体类型的队列。
-
生产者任务:producerTask会每隔1秒生成
x
,y
,z
数据,将它们封装成SensorData
结构体,然后通过xQueueSend
发送到队列中。 - 消费者任务:consumerTask会每隔0.5秒从队列中接收数据并打印出来。
- 任务创建:在setup()函数中,使用xTaskCreate创建了两个任务,分别是生产者和消费者任务。
注意事项:
- 每个任务通过vTaskDelay进行调度,使得系统能够在多任务之间切换。
- xQueueSend和xQueueReceive是用于将数据发送到队列和从队列接收数据的FreeRTOS API。
- portMAX_DELAY表示阻塞直到数据可用。
2、使用信号量进行任务同步
#include <Arduino.h>
// 定义信号量
SemaphoreHandle_t xSemaphore = NULL;
// 第一个任务:打印消息
void Task1(void *pvParameters) {
while (1) {
// 等待信号量
if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdTRUE) {
Serial.println("Task 1 Running...");
// 模拟任务执行
delay(1000);
}
}
}
// 第二个任务:释放信号量
void Task2(void *pvParameters) {
while (1) {
// 每两秒释放一次信号量
xSemaphoreGive(xSemaphore);
Serial.println("Task 2 Release...");
// 模拟任务执行
delay(2000);
}
}
void setup() {
// 启动串口
Serial.begin(115200);
// 创建信号量
xSemaphore = xSemaphoreCreateBinary();
if (xSemaphore != NULL) {
// 创建任务
xTaskCreate(Task1, "Task1", 2048, NULL, 1, NULL);
xTaskCreate(Task2, "Task2", 2048, NULL, 1, NULL);
} else {
Serial.println("create Fail!");
}
}
void loop() {
// FreeRTOS任务调度会在任务之间自动切换,因此loop()中可以为空
}
代码解释:
- 信号量创建:xSemaphoreCreateBinary() 创建一个二值信号量,最初未获取。
- 任务1:在Task1中,任务会通过xSemaphoreTake()等待信号量。如果信号量已经被释放,任务就会执行打印操作。
- 任务2:在 Task2 中,任务每两秒通过 xSemaphoreGive() 释放信号量,使得任务1得以执行。
- FreeRTOS任务调度:xTaskCreate() 用来创建两个任务,FreeRTOS会根据任务优先级和系统状态自动调度执行。
工作流程:
- Task2 每两秒释放一次信号量,允许Task1执行打印操作。
- Task1 会在获得信号量后打印消息,并等待下一次信号量的释放。
3、使用事件标志进行任务同步
#include <Arduino.h>
// 创建一个事件组
EventGroupHandle_t xEventGroup;
// 定义事件标志
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
// Task 1:发送事件标志
void Task1(void *pvParameters) {
while (true) {
// 延迟 2 秒
vTaskDelay(2000 / portTICK_PERIOD_MS);
// 设置事件标志 BIT_0,表示任务1完成了某个操作
xEventGroupSetBits(xEventGroup, BIT_0);
Serial.println("Task1: Event BIT_0 set.");
// 延迟 3 秒
vTaskDelay(3000 / portTICK_PERIOD_MS);
// 设置事件标志 BIT_1,表示任务1完成了另一个操作
xEventGroupSetBits(xEventGroup, BIT_1);
Serial.println("Task1: Event BIT_1 set.");
}
}
// Task 2:等待事件标志
void Task2(void *pvParameters) {
while (true) {
// 等待事件标志 BIT_0 或 BIT_1 被设置
EventBits_t bits = xEventGroupWaitBits(xEventGroup, BIT_0 | BIT_1, pdTRUE, pdFALSE, portMAX_DELAY);
// 根据不同的事件标志执行不同的操作
if (bits & BIT_0) {
Serial.println("Task2: Received BIT_0.");
}
if (bits & BIT_1) {
Serial.println("Task2: Received BIT_1.");
}
}
}
void setup() {
// 初始化串口
Serial.begin(115200);
// 创建事件组
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL) {
Serial.println("Error creating event group.");
return;
}
// 创建两个任务
xTaskCreatePinnedToCore(Task1, "Task1", 2048, NULL, 1, NULL, 0); // 任务1
xTaskCreatePinnedToCore(Task2, "Task2", 2048, NULL, 1, NULL, 1); // 任务2
}
void loop() {
// loop() 中可以放置其他任务或者需要循环的代码
}
代码解析:
- 事件组 (xEventGroup):xEventGroup用来存储和管理多个事件标志位。
- 事件标志:我们定义了两个事件标志BIT_0和BIT_1,分别代表任务1中的两个操作。
- 任务 1:每隔一定时间,任务1会调用xEventGroupSetBits()设置一个或多个事件标志,表示任务1执行了某些操作。
- 任务 2:任务2会通过xEventGroupWaitBits()等待事件标志的设置。一旦事件标志被设置,它会根据标志执行相应的操作。
- 串口输出:通过Serial.println()输出事件触发的情况,便于调试。
工作流程:
- 任务1每隔一段时间设置事件标志,任务2一直在等待这些事件标志的触发,并根据触发的标志做出相应反应。
- 任务间的同步完全依赖于事件标志,任务2只有在任务1设置标志后才能继续执行相应的操作。
三、简述
1、消息队列
实现资源共享,各个任务间的数据可以共用,类似于平常用到的全局变量。
2、信号量
释放和获取,只有先释放信号量才能获取到信号量,信号量可以用来同步多个任务的执行。比如一个任务可能需要等待另一个任务完成某些操作后才能继续执行,信号量提供了一种机制来实现这一同步。
3、事件标志
事件标志允许任务等待另一个任务的某个操作完成,从而保证任务按顺序执行。比如,一个任务需要等待另一个任务完成数据采集,才能继续处理数据。通过等待和设置事件标志,任务可以同步进行,避免出现竞态条件。
4、信号量和事件标志使用场景差异
-
信号量的常见用途
- 用于 互斥,如确保同一时间只有一个任务可以访问共享资源。
- 用于 任务间的通知,例如一个任务完成某项工作后通知另一个任务继续执行。
- 用于 资源计数,比如限制并发任务数量或同时访问的资源数。
-
事件标志的常见用途
- 用于 任务间事件通知,例如一个任务完成某个操作后,通过设置事件标志通知其他任务。
- 用于 复杂同步,例如等待多个事件标志同时发生,或者等待任意一个标志。
- 用于 多状态控制,例如表示任务的不同执行阶段或不同的系统状态。
5、总结
本章节的内容只是对FreeRTOS任务间通信与同步机制的一个简单介绍,目的是方便个人记录和理解。在实际开发中,FreeRTOS提供了更多高级功能和配置选项,建议读者查阅官方文档和相关资源,以深入了解和掌握这些机制。
希望这个总结能帮助大家更好地理解和应用FreeRTOS的任务间通信与同步机制。如果有任何问题或需要进一步的解释,请随时查阅FreeRTOS的官方文档或其他专业资源。