在FreeRTOS中,队列(Queue)和信号量(Semaphore)是两种常用的通信机制,用于在任务之间传递数据或同步。然而,当需要同时等待多个队列或信号量时,队列集(Queue Set)提供了一种更为灵活的解决方案。本文将详细介绍FreeRTOS中的队列集,并通过示例代码展示其使用方法。
目录
队列集概述
队列集允许任务同时等待多个队列或信号量中的任何一个变为可用。队列集本身是一个集合,可以包含多个队列和信号量。当集合中的任意一个队列或信号量有数据可用时,任务将被唤醒,并可以确定是哪个队列或信号量变为可用。
队列集的创建和使用
- 创建队列集:首先,创建一个队列集并指定其大小,即可以包含的队列和信号量的总数。
- 创建并添加队列和信号量:将队列和信号量创建并添加到队列集中。
- 等待队列集事件:任务可以阻塞等待队列集中的任何一个队列或信号量变为可用。
- 处理事件:任务被唤醒后,需要检查哪个队列或信号量变为可用,并进行相应处理。
示例代码
以下是一个示例代码,展示了如何在FreeRTOS中使用队列集:
#include <FreeRTOS.h>
#include <task.h>
#include <queue.h>
#include <semphr.h>
// 定义队列和信号量句柄
QueueHandle_t xQueue1, xQueue2;
SemaphoreHandle_t xBinarySemaphore;
QueueSetHandle_t xQueueSet;
void vSenderTask1(void *pvParameters)
{
const char *pcMessage = "Message from Sender 1";
for (;;)
{
// 发送消息到队列1
xQueueSend(xQueue1, &pcMessage, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vSenderTask2(void *pvParameters)
{
const char *pcMessage = "Message from Sender 2";
for (;;)
{
// 发送消息到队列2
xQueueSend(xQueue2, &pcMessage, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1500));
}
}
void vReceiverTask(void *pvParameters)
{
QueueSetMemberHandle_t xActivatedMember;
char *pcReceivedMessage;
for (;;)
{
// 等待队列集中的任意一个队列或信号量变为可用
xActivatedMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
// 检查哪个队列变为可用并处理消息
if (xActivatedMember == xQueue1)
{
xQueueReceive(xQueue1, &pcReceivedMessage, 0);
printf("Received from Queue 1: %s\n", pcReceivedMessage);
}
else if (xActivatedMember == xQueue2)
{
xQueueReceive(xQueue2, &pcReceivedMessage, 0);
printf("Received from Queue 2: %s\n", pcReceivedMessage);
}
else if (xActivatedMember == xBinarySemaphore)
{
xSemaphoreTake(xBinarySemaphore, 0);
printf("Binary Semaphore Taken\n");
}
}
}
int main(void)
{
// 创建队列1和队列2
xQueue1 = xQueueCreate(10, sizeof(char *));
xQueue2 = xQueueCreate(10, sizeof(char *));
// 创建二值信号量
xBinarySemaphore = xSemaphoreCreateBinary();
// 创建队列集
xQueueSet = xQueueSetCreate(3); // 队列集大小为3,可以包含两个队列和一个信号量
// 将队列和信号量添加到队列集
xQueueAddToSet(xQueue1, xQueueSet);
xQueueAddToSet(xQueue2, xQueueSet);
xQueueAddToSet(xBinarySemaphore, xQueueSet);
// 创建发送和接收任务
xTaskCreate(vSenderTask1, "Sender Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(vSenderTask2, "Sender Task 2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(vReceiverTask, "Receiver Task", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动失败
for (;;);
return 0;
}
关键点总结
- 队列集的创建:使用
xQueueSetCreate
创建队列集,指定其大小。 - 添加队列和信号量:使用
xQueueAddToSet
将队列和信号量添加到队列集中。 - 等待队列集事件:使用
xQueueSelectFromSet
函数阻塞等待队列集中的任何一个队列或信号量变为可用。 - 处理事件:检查
xQueueSelectFromSet
返回的成员是哪个队列或信号量,并进行相应处理。
队列集在嵌入式编程中非常适用于需要同时等待多个事件源的情况,特别是在任务需要处理来自多个队列或信号量的数据时。以下是一些常见的应用场景及举例说明:
应用场景
- 多传感器数据处理:一个任务需要从多个传感器获取数据,并根据传感器数据执行不同的操作。
- 多通道通信管理:一个任务需要同时处理来自多个通信通道(如UART、SPI、I2C)的数据。
- 综合事件处理:一个任务需要处理来自多个不同类型事件(如按钮按下、传感器触发、定时器超时等)的信号。
示例一:多传感器数据处理
假设有一个嵌入式系统连接了多个传感器(如温度传感器和湿度传感器),系统需要同时监控这些传感器的数据并根据不同的传感器数据执行相应操作。
#include <FreeRTOS.h>
#include <task.h>
#include <queue.h>
#include <semphr.h>
// 队列句柄
QueueHandle_t xTempQueue, xHumidityQueue;
QueueSetHandle_t xQueueSet;
void vTempSensorTask(void *pvParameters)
{
int tempData;
for (;;)
{
// 模拟从温度传感器读取数据
tempData = ReadTemperatureSensor();
// 发送数据到温度队列
xQueueSend(xTempQueue, &tempData, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vHumiditySensorTask(void *pvParameters)
{
int humidityData;
for (;;)
{
// 模拟从湿度传感器读取数据
humidityData = ReadHumiditySensor();
// 发送数据到湿度队列
xQueueSend(xHumidityQueue, &humidityData, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1500));
}
}
void vDataProcessingTask(void *pvParameters)
{
QueueSetMemberHandle_t xActivatedMember;
int receivedData;
for (;;)
{
// 等待队列集中的任意一个队列变为可用
xActivatedMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
// 检查哪个队列变为可用并处理数据
if (xActivatedMember == xTempQueue)
{
xQueueReceive(xTempQueue, &receivedData, 0);
ProcessTemperatureData(receivedData);
}
else if (xActivatedMember == xHumidityQueue)
{
xQueueReceive(xHumidityQueue, &receivedData, 0);
ProcessHumidityData(receivedData);
}
}
}
int main(void)
{
// 创建温度队列和湿度队列
xTempQueue = xQueueCreate(10, sizeof(int));
xHumidityQueue = xQueueCreate(10, sizeof(int));
// 创建队列集
xQueueSet = xQueueSetCreate(2);
// 将队列添加到队列集
xQueueAddToSet(xTempQueue, xQueueSet);
xQueueAddToSet(xHumidityQueue, xQueueSet);
// 创建传感器读取任务和数据处理任务
xTaskCreate(vTempSensorTask, "Temp Sensor Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(vHumiditySensorTask, "Humidity Sensor Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(vDataProcessingTask, "Data Processing Task", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动失败
for (;;);
return 0;
}
示例二:多通道通信管理
假设有一个嵌入式系统需要处理来自多个通信通道(如UART和SPI)的数据,并将处理后的数据发送到相应的模块。
#include <FreeRTOS.h>
#include <task.h>
#include <queue.h>
#include <semphr.h>
// 队列句柄
QueueHandle_t xUARTQueue, xSPIQueue;
QueueSetHandle_t xQueueSet;
void vUARTTask(void *pvParameters)
{
char uartData[50];
for (;;)
{
// 模拟从UART读取数据
ReadUART(uartData, sizeof(uartData));
// 发送数据到UART队列
xQueueSend(xUARTQueue, &uartData, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void vSPITask(void *pvParameters)
{
char spiData[50];
for (;;)
{
// 模拟从SPI读取数据
ReadSPI(spiData, sizeof(spiData));
// 发送数据到SPI队列
xQueueSend(xSPIQueue, &spiData, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vCommProcessingTask(void *pvParameters)
{
QueueSetMemberHandle_t xActivatedMember;
char receivedData[50];
for (;;)
{
// 等待队列集中的任意一个队列变为可用
xActivatedMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
// 检查哪个队列变为可用并处理数据
if (xActivatedMember == xUARTQueue)
{
xQueueReceive(xUARTQueue, &receivedData, 0);
ProcessUARTData(receivedData);
}
else if (xActivatedMember == xSPIQueue)
{
xQueueReceive(xSPIQueue, &receivedData, 0);
ProcessSPIData(receivedData);
}
}
}
int main(void)
{
// 创建UART队列和SPI队列
xUARTQueue = xQueueCreate(10, sizeof(char[50]));
xSPIQueue = xQueueCreate(10, sizeof(char[50]));
// 创建队列集
xQueueSet = xQueueSetCreate(2);
// 将队列添加到队列集
xQueueAddToSet(xUARTQueue, xQueueSet);
xQueueAddToSet(xSPIQueue, xQueueSet);
// 创建UART任务、SPI任务和通信处理任务
xTaskCreate(vUARTTask, "UART Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(vSPITask, "SPI Task", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(vCommProcessingTask, "Comm Processing Task", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动失败
for (;;);
return 0;
}
关键点总结
- 多传感器数据处理:队列集可用于同时监控多个传感器的数据输入,并根据不同的数据执行相应的操作。
- 多通道通信管理:队列集可用于同时处理来自多个通信通道的数据,确保数据处理的实时性和同步性。
- 综合事件处理:队列集可以综合处理来自不同类型事件的信号,简化了任务的事件管理逻辑。