FreeRtos队列
文章目录
reeRTOS队列特性
FreeRTOS中的队列是一种重要的通信与同步机制,用于在任务之间传递数据。以下是FreeRTOS队列的主要特性:
数据存储
- 队列深度:队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。
- 数据单元:队列可以保存具有确定长度的数据单元。每个数据单元的大小在队列创建时设定。
- FIFO:通常情况下,队列被作为FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写入也是可能的。
- 字节拷贝:往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把队列中的数据拷贝删除。
可被多任务存取
- 独立权限:队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。
- 多方写入:一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。
读队列时阻塞
- 阻塞超时:当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。
- 解除阻塞:当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
- 优先级:由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。
写队列时阻塞
- 阻塞超时:任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。
- 优先级:由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。
FreeRTOS队列相关API函数
以下是FreeRTOS中常用的队列相关API函数及其详细说明,以表格形式呈现:
API函数名称 | 描述 | 参数 | 返回值 |
---|---|---|---|
xQueueCreate() | 创建一个新队列。 | - uxQueueLength :队列能够存储的最大单元数目,即队列深度。- uxItemSize :队列中数据单元的长度,以字节为单位。 | - NULL :表示没有足够的堆空间分配给队列而导致创建失败。- 非 NULL 值:表示队列创建成功。此返回值应当保存下来,以作为操作此队列的句柄。 |
xQueueSendToBack() | 将数据发送到队列尾。 | - xQueue :目标队列的句柄。- pvItemToQueue :发送数据的指针。- xTicksToWait :阻塞超时时间。 | - pdPASS :表示数据被成功发送到队列中。- errQUEUE_FULL :表示由于队列已满而无法将数据写入。 |
xQueueSendToFront() | 将数据发送到队列首。 | - xQueue :目标队列的句柄。- pvItemToQueue :发送数据的指针。- xTicksToWait :阻塞超时时间。 | - pdPASS :表示数据被成功发送到队列中。- errQUEUE_FULL :表示由于队列已满而无法将数据写入。 |
xQueueReceive() | 从队列中接收(读取)数据单元,并从队列中删除。 | - xQueue :被读队列的句柄。- pvBuffer :接收缓存指针。- xTicksToWait :阻塞超时时间。 | - pdPASS :表示成功从队列中读到数据。- errQUEUE_FULL :表示在读取时由于队列已空而没有读到任何数据。 |
xQueuePeek() | 从队列中接收数据单元,但不从队列中删除。 | - xQueue :被读队列的句柄。- pvBuffer :接收缓存指针。- xTicksToWait :阻塞超时时间。 | - pdPASS :表示成功从队列中读到数据。- errQUEUE_FULL :表示在读取时由于队列已空而没有读到任何数据。 |
uxQueueMessagesWaiting() | 查询队列中当前有效数据单元个数。 | - xQueue :被查询队列的句柄。 | 当前队列中保存的数据单元个数。返回0表明队列为空。 |
xQueueReset() | 重置队列到初始状态。 | - xQueue :被重置队列的句柄。 | - pdPASS :表示队列重置成功。- errQUEUE_FULL :表示队列重置失败。 |
xQueueIsQueueEmptyFromISR() | 在中断服务例程中查询队列是否为空。 | - xQueue :被查询队列的句柄。 | - pdTRUE :表示队列为空。- pdFALSE :表示队列不为空。 |
xQueueIsQueueFullFromISR() | 在中断服务例程中查询队列是否已满。 | - xQueue :被查询队列的句柄。 | - pdTRUE :表示队列已满。- pdFALSE :表示队列未满。 |
xQueueOverwrite() | 覆盖队列中的数据,即使队列已满。 | - xQueue :目标队列的句柄。- pvItemToQueue :发送数据的指针。 | - pdPASS :表示数据被成功覆盖到队列中。 |
xQueueOverwriteFromISR() | 在中断服务例程中覆盖队列中的数据,即使队列已满。 | - xQueue :目标队列的句柄。- pvItemToQueue :发送数据的指针。- pxHigherPriorityTaskWoken :指示是否有更高优先级的任务被唤醒。 | - pdPASS :表示数据被成功覆盖到队列中。 |
xQueueSendToBackFromISR() | 在中断服务例程中将数据发送到队列尾。 | - xQueue :目标队列的句柄。- pvItemToQueue :发送数据的指针。- pxHigherPriorityTaskWoken :指示是否有更高优先级的任务被唤醒。 | - pdPASS :表示数据被成功发送到队列中。 |
xQueueSendToFrontFromISR() | 在中断服务例程中将数据发送到队列首。 | - xQueue :目标队列的句柄。- pvItemToQueue :发送数据的指针。- pxHigherPriorityTaskWoken :指示是否有更高优先级的任务被唤醒。 | - pdPASS :表示数据被成功发送到队列中。 |
xQueueReceiveFromISR() | 在中断服务例程中从队列中接收数据单元,并从队列中删除。 | - xQueue :被读队列的句柄。- pvBuffer :接收缓存指针。- pxHigherPriorityTaskWoken :指示是否有更高优先级的任务被唤醒。 | - pdPASS :表示成功从队列中读到数据。- errQUEUE_FULL :表示在读取时由于队列已空而没有读到任何数据。 |
xQueuePeekFromISR() | 在中断服务例程中从队列中接收数据单元,但不从队列中删除。 | - xQueue :被读队列的句柄。- pvBuffer :接收缓存指针。 | - pdPASS :表示成功从队列中读到数据。- errQUEUE_FULL :表示在读取时由于队列已空而没有读到任何数据。 |
队列的基本操作
创建队列
在使用队列之前,首先需要创建一个队列。可以使用 xQueueCreate()
函数来创建队列。
xQueueHandle xQueueCreate(
unsigned portBASE_TYPE uxQueueLength, // 队列能够存储的最大单元数目
unsigned portBASE_TYPE uxItemSize // 队列中数据单元的长度,以字节为单位
);
示例:
xQueueHandle xQueue;
void createQueue() {
xQueue = xQueueCreate(5, sizeof(uint32_t)); // 创建一个可以存储5个uint32_t类型数据的队列
if (xQueue == NULL) {
// 队列创建失败的处理
}
}
发送数据到队列
可以使用 xQueueSendToBack()
或 xQueueSendToFront()
函数将数据发送到队列中。
xQueueSendToBack()
:将数据发送到队列的尾部。xQueueSendToFront()
:将数据发送到队列的头部。
portBASE_TYPE xQueueSendToBack(
xQueueHandle xQueue, // 目标队列的句柄
const void *pvItemToQueue, // 发送数据的指针
portTickType xTicksToWait // 阻塞超时时间
);
示例:
void vSenderTask(void *pvParameters) {
uint32_t dataToSend = 1234;
portBASE_TYPE xStatus;
for (;;) {
xStatus = xQueueSendToBack(xQueue, &dataToSend, portMAX_DELAY);
if (xStatus != pdPASS) {
// 发送失败的处理
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒发送一次
}
}
从队列接收数据
可以使用 xQueueReceive()
函数从队列中接收数据。
portBASE_TYPE xQueueReceive(
xQueueHandle xQueue, // 被读队列的句柄
void *pvBuffer, // 接收缓存指针
portTickType xTicksToWait // 阻塞超时时间
);
示例:
void vReceiverTask(void *pvParameters) {
uint32_t receivedData;
portBASE_TYPE xStatus;
for (;;) {
xStatus = xQueueReceive(xQueue, &receivedData, portMAX_DELAY);
if (xStatus == pdPASS) {
// 处理接收到的数据
printf("Received data: %lu\n", receivedData);
} else {
// 接收失败的处理
}
}
}
队列的中断安全版本
在中断服务例程(ISR)中,不能使用普通的队列发送和接收函数。FreeRTOS提供了中断安全版本的队列函数:
xQueueSendToBackFromISR()
xQueueSendToFrontFromISR()
xQueueReceiveFromISR()
示例:
void vISRHandler(void) {
uint32_t dataToSend = 5678;
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
xQueueSendToBackFromISR(xQueue, &dataToSend, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
taskYIELD(); // 如果有更高优先级的任务被唤醒,则进行任务切换
}
}
队列的查询
可以使用 uxQueueMessagesWaiting()
函数查询队列中当前有效数据单元的个数。
unsigned portBASE_TYPE uxQueueMessagesWaiting(
xQueueHandle xQueue // 被查询队列的句柄
);
示例:
void vQueryQueue(void) {
unsigned portBASE_TYPE uxMessagesWaiting = uxQueueMessagesWaiting(xQueue);
printf("Queue messages waiting: %lu\n", uxMessagesWaiting);
}
示例代码
以下是一个完整的示例,展示了如何创建队列、发送数据、接收数据以及查询队列状态。
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>
xQueueHandle xQueue;
void vSenderTask(void *pvParameters) {
uint32_t dataToSend = 1234;
portBASE_TYPE xStatus;
for (;;) {
xStatus = xQueueSendToBack(xQueue, &dataToSend, portMAX_DELAY);
if (xStatus != pdPASS) {
printf("Could not send to the queue.\n");
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒发送一次
}
}
void vReceiverTask(void *pvParameters) {
uint32_t receivedData;
portBASE_TYPE xStatus;
for (;;) {
xStatus = xQueueReceive(xQueue, &receivedData, portMAX_DELAY);
if (xStatus == pdPASS) {
printf("Received data: %lu\n", receivedData);
} else {
printf("Could not receive from the queue.\n");
}
}
}
int main(void) {
xQueue = xQueueCreate(5, sizeof(uint32_t));
if (xQueue == NULL) {
printf("Queue creation failed.\n");
return 1;
}
xTaskCreate(vSenderTask, "Sender", 1000, NULL, 1, NULL);
xTaskCreate(vReceiverTask, "Receiver", 1000, NULL, 2, NULL);
vTaskStartScheduler();
for (;;);
}
总结
队列是FreeRTOS中用于任务间通信和同步的重要机制。通过合理使用队列,可以实现高效的数据传递和任务同步。在使用队列时,需要注意内存管理和线程安全,尤其是在处理大型数据单元时。
工作于大型数据单元
在嵌入式系统中,处理大型数据单元时,直接将整个数据单元复制到队列中可能会导致内存开销过大,尤其是在内存资源有限的系统中。为了避免这种情况,可以使用指针来传递数据,而不是直接传递数据本身。
关键点
- 内存所有权明确:确保只有一个任务负责修改共享内存中的数据。在将指针发送到队列之前,只有发送任务可以访问共享内存;在从队列中读取指针之后,只有接收任务可以访问共享内存。
- 内存有效性:确保指针指向的内存空间在任务间传递时仍然有效。如果内存是动态分配的,应该只有一个任务负责释放内存。避免使用指向任务栈上分配的空间的指针,因为栈帧改变后,栈上的数据将不再有效。
使用指针传递大型数据单元
假设我们有一个大型数据结构 LargeData
,我们希望在任务间传递这个结构体。为了避免直接复制整个结构体,我们可以传递指向这个结构体的指针。
定义大型数据结构
首先,定义一个大型数据结构 LargeData
:
typedef struct {
uint8_t data[1024]; // 假设这是一个1KB的数据块
} LargeData;
2. 创建队列
创建一个队列,用于传递指向 LargeData
结构体的指针:
xQueueHandle xQueue;
void createQueue() {
xQueue = xQueueCreate(5, sizeof(LargeData *)); // 队列深度为5,每个数据单元是一个指向LargeData的指针
if (xQueue == NULL) {
printf("Queue creation failed.\n");
}
}
3. 发送数据
在发送任务中,创建一个 LargeData
结构体,并将其指针发送到队列中:
void vSenderTask(void *pvParameters) {
LargeData *pData = (LargeData *)pvPortMalloc(sizeof(LargeData));
if (pData == NULL) {
printf("Memory allocation failed.\n");
return;
}
portBASE_TYPE xStatus;
for (;;) {
// 假设pData已经初始化
xStatus = xQueueSendToBack(xQueue, &pData, portMAX_DELAY);
if (xStatus != pdPASS) {
printf("Could not send to the queue.\n");
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒发送一次
}
}
4. 接收数据
在接收任务中,从队列中接收指向 LargeData
结构体的指针,并处理数据:
void vReceiverTask(void *pvParameters) {
LargeData *pReceivedData;
portBASE_TYPE xStatus;
for (;;) {
xStatus = xQueueReceive(xQueue, &pReceivedData, portMAX_DELAY);
if (xStatus == pdPASS) {
// 处理接收到的数据
processLargeData(pReceivedData);
// 释放内存
vPortFree(pReceivedData);
} else {
printf("Could not receive from the queue.\n");
}
}
}
5. 处理接收到的数据
在接收任务中,处理接收到的 LargeData
结构体:
void processLargeData(LargeData *pData) {
// 处理数据的逻辑
// 例如,打印数据内容
for (int i = 0; i < sizeof(pData->data); i++) {
printf("%02x ", pData->data[i]);
}
printf("\n");
}
注意事项
- 内存管理:确保
LargeData
结构体的内存管理是正确的。如果LargeData
结构体是在堆上动态分配的,需要在适当的时候释放内存,以避免内存泄漏。 - 线程安全:如果
LargeData
结构体在多个任务中共享,确保对它的访问是线程安全的。可以使用互斥锁或其他同步机制来保护共享数据。 - 队列深度:根据系统的内存限制和任务的执行频率,合理设置队列的深度。过大的队列深度可能会导致内存不足,而过小的队列深度可能会导致任务频繁阻塞。
示例代码
以下是一个完整的示例,展示了如何创建队列、发送数据、接收数据以及处理接收到的数据。
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>
typedef struct {
uint8_t data[1024]; // 假设这是一个1KB的数据块
} LargeData;
xQueueHandle xQueue;
void createQueue() {
xQueue = xQueueCreate(5, sizeof(LargeData *)); // 队列深度为5,每个数据单元是一个指向LargeData的指针
if (xQueue == NULL) {
printf("Queue creation failed.\n");
}
}
void vSenderTask(void *pvParameters) {
LargeData *pData = (LargeData *)pvPortMalloc(sizeof(LargeData));
if (pData == NULL) {
printf("Memory allocation failed.\n");
return;
}
portBASE_TYPE xStatus;
for (;;) {
// 假设pData已经初始化
xStatus = xQueueSendToBack(xQueue, &pData, portMAX_DELAY);
if (xStatus != pdPASS) {
printf("Could not send to the queue.\n");
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒发送一次
}
}
void vReceiverTask(void *pvParameters) {
LargeData *pReceivedData;
portBASE_TYPE xStatus;
for (;;) {
xStatus = xQueueReceive(xQueue, &pReceivedData, portMAX_DELAY);
if (xStatus == pdPASS) {
// 处理接收到的数据
processLargeData(pReceivedData);
// 释放内存
vPortFree(pReceivedData);
} else {
printf("Could not receive from the queue.\n");
}
}
}
void processLargeData(LargeData *pData) {
// 处理数据的逻辑
// 例如,打印数据内容
for (int i = 0; i < sizeof(pData->data); i++) {
printf("%02x ", pData->data[i]);
}
printf("\n");
}
int main(void) {
createQueue();
xTaskCreate(vSenderTask, "Sender", 1000, NULL, 1, NULL);
xTaskCreate(vReceiverTask, "Receiver", 1000, NULL, 2, NULL);
vTaskStartScheduler();
for (;;);
}
总结
通过使用指针传递大型数据单元,可以有效地减少内存开销,并提高任务间通信的效率。在使用队列时,需要注意内存管理和线程安全,尤其是在处理大型数据单元时。