freertos队列

队列简介

摘自freertos官网

队列传输方式

队列是任务间通信的主要形式。它们可以用于在任务之间 以及中断和任务之间发送消息。在大多数情况下,队列用作线程安全的 FIFO(先进先出)缓冲区, 新数据被发送到队列的后面,但也可以发送到前面。
queue_animation.gif
向队列中写入和从队列中读取。此示例中创建队列来保存 5 个项目,并且 队列永远不会满。

FreeRTOS 队列使用模型既简单又灵活, 这两者通常是不可兼得的。消息通过队列以副本的方式发送, 这意味着数据(可以是更大的缓冲区的指针)本身被复制到队列中, 而不是队列始终只存储对数据的引用。这是最好的方法,因为:

已经包含在 C 语言变量(整数、 小结构体等)中的小消息可以直接送入队列。没有 必要为消息分配一个缓冲区, 然后将变量复制到分配的缓冲区中。同样,可以直接从队列中将消息读取到 C 变量中 。

此外,以这种方式向队列发送消息, 允许发送任务立即覆盖发送到队列的变量或缓冲区, 即使发送的消息仍在队列中。

由于变量中包含的数据已复制到队列中, 变量本身可以重复使用。不要求发送消息的任务 和接收消息的任务约定哪个任务拥有该消息, 以及哪个任务负责在不需要该消息时 将其清空。

使用通过复制传递数据的队列不会导致无法将队列 用于通过引用传递数据。当消息的大小达到一定程度, 将整条消息逐字节复制到队列中是不现实的, 此时可将消息定义为保存若干指针并复制消息的 一个指针至队列。

内核独自负责分配用于队列存储区的内存 。

可变大小的消息可以通过定义队列来保存结构体, 其中包含一个指向队列消息的成员, 以及另一个保存队列消息大小的成员。

单个队列可用于接收不同的消息类型, 以及来自多个地点的消息, 方法是将队列定义为保存一个结构体,该结构的一个成员持有消息类型, 另一个成员保存消息数据(或消息数据的一个指针)。如何解释数据 取决于消息类型。

正是使用这种方式,管理 FreeRTOS-Plus-TCP IP 堆栈的任务才能使用一个队列来接收 ARP 定时器事件、 从以太网硬件接收的数据包、 从应用程序接收的数据包、网络故障事件等的通知。

该实现适用于在内存保护环境中使用 。一个被限制在受保护的内存区域的任务可以将数据传递给一个被限制在不同的受保护内存区域的任务, 因为通过调用队列发送函数 来调用 RTOS 将提高微控制器的权限等级 。队列存储区 仅可由 RTOS 访问(具有完整权限)。

提供一个单独的 API 用于中断内部。将 RTOS 任务中使用的 API 与中断服务程序中使用的 API 分开, 意味着 RTOS API 函数的实现 不承担每次执行时检查其调用上下文的开销。 使用单独的中断 API 也意味着,在大多数情况下,创建 RTOS 感知的中断服务程序对终端用户而言更简单—— 与其他 RTOS 产品相比。

从任何意义上来说,API 都更加简单。

队列阻塞

队列 API 函数允许指定阻塞时间。

当一个任务试图从一个空队列中读取时,该队列将 进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中的数据变得可用,或者阻塞时间过期。

当一个任务试图写入到一个满队列时,该队列将 进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行) 直到队列中出现可用空间,或者阻塞时间过期。

如果同一个队列上有多个处于阻塞状态的任务, 那么具有最高优先级的任务将最先解除阻塞。

参见用户文档的队列管理部分,获取队列相关 API 函数的列表。在 FreeRTOS/Demo/Common/Minimal 目录下搜索文件,您将会看到多个用法 示例。

请注意,中断只能使用以 “FromISR” 结尾的 API 函数。

队列创建

静态队列创建

  1. 定义静态队列结构
    在使用静态队列之前,需要先定义一个StaticQueue_t类型的静态变量来存储队列的上下文信息。这个变量将在队列创建时被初始化,并且不会被释放。

    1
    2
    // xQueueBuffer用来保存队列结构体
    StaticQueue_t xQueueBuffer;
  2. 初始化队列属性
    创建一个QueueDefinition结构体(实际上是QueueHandle_t类型的指针),并设置队列的大小和其他属性。

    1
    2
    // xQueue1 用来控制队列
    QueueHandle_t xQueue1;
  3. 队列buff定义
    用于存储队列数据

    1
    2
    3
    4
    5
    // 静态队列创建---
    #define QUEUE_LENGTH 5
    #define ITEM_SIZE sizeof(uint32_t)
    // ucQueueStorage 用来保存队列的数据
    uint8_t ucQueueStorage[QUEUE_LENGTH * ITEM_SIZE]; // 大小为:队列长度 * 数据大小
  4. 创建队列
    使用xQueueCreateStatic函数创建队列,并将静态队列的上下文信息传递给它。你需要指定队列可以容纳的最大消息数量以及每个消息的数据类型。同时,提供一个指向之前定义的StaticQueue_t类型的指针。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 创建队列: 可以容纳QUEUE_LENGTH个数据,每个数据大小是ITEM_SIZE
    xQueue1 = xQueueCreateStatic(QUEUE_LENGTH,
    ITEM_SIZE,
    ucQueueStorage,
    &xQueueBuffer);
    if ( xQueue1 != NULL )
    {
    // 队列创建成功
    }
    else
    {
    // 创建失败
    }

动态队列创建

  1. 定义队列的属性
    你需要定义队列的最大长度(即它可以容纳的最大元素数量)以及队列中存放的数据类型。例如,如果队列将用于发送整数,则数据类型可以是intuint32_t等。

  2. 创建队列
    使用xQueueCreate()函数来创建队列实例。这个函数需要两个参数:队列的最大长度和队列中的项的大小(以字节为单位)。返回值是一个指向队列类型的指针。

1
QueueHandle_t xQueue = xQueueCreate(QUEUE_LENGTH, sizeof(YourDataType));

其中QUEUE_LENGTH是队列的最大长度,sizeof(YourDataType)是指定的元素大小。

  1. 检查队列是否成功创建
    队列创建后,应该检查返回值是否为NULL,以确认队列是否成功创建。
1
2
3
if (xQueue == NULL) {
// 队列创建失败处理
}

队列发送接收数据

队列发送数据

  1. 准备要发送的数据
    首先准备好要发送的数据,这通常是一个结构体或者简单的变量类型。

  2. 发送数据
    使用xQueueSend()函数将数据发送到队列中。这个函数有阻塞模式和非阻塞模式。

    • 阻塞模式:如果队列已满,则任务会挂起等待直到队列中有空间。
    • 非阻塞模式:如果队列已满,函数会立即返回失败。
    1
    2
    3
    4
    5
    6
    7
    8
    if (xQueueSend(xQueue, &myData, portMAX_DELAY) == pdTRUE)
    {
    // 数据成功发送
    }
    else
    {
    // 发送失败,可能是因为队列已满
    }

    如果你想在有限的时间内等待队列有空间(非阻塞模式),你可以使用第三个参数传递一个延迟值,例如使用100 / portTICK_PERIOD_MS表示等待100毫秒。

队列接收数据

在FreeRTOS中,队列不仅用于任务间的数据传输,还用于同步不同任务的执行。当一个任务想要从队列中读取数据时,它可以通过调用xQueueReceive()函数来实现。下面是接收队列数据的一般步骤:

准备接收数据

  1. 定义接收缓冲区
    在接收数据之前,你需要定义一个缓冲区来存放接收到的数据。这个缓冲区的大小应该与发送数据时的数据大小相匹配。

  2. 声明队列句柄
    你需要拥有队列的句柄,这个句柄通常是在创建队列时得到的。

接收数据

  1. 阻塞方式接收数据
    使用xQueueReceive()函数以阻塞方式接收数据。这意味着如果队列为空,该函数会挂起当前任务,直到队列中有数据为止。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    QueueHandle_t xQueue; // 这应该是你的队列句柄
    QueueItem_t xReceivedItem; // 这是用来存放接收到数据的缓冲区
    if (xQueueReceive(xQueue, &xReceivedItem, portMAX_DELAY) == pdTRUE)
    {
    // 成功接收数据
    // 处理接收到的数据
    }
    else
    {
    // 接收失败,通常不应该走到这里
    }
  2. 非阻塞方式接收数据
    如果你不希望因为队列为空而使任务挂起,可以使用非阻塞的方式来接收数据。这时,你可以设置xQueueReceive()函数的延迟参数为0。

    1
    2
    3
    4
    5
    6
    7
    8
    if (xQueueReceive(xQueue, &xReceivedItem, 0) == pdTRUE)
    {
    // 成功接收数据
    }
    else
    {
    // 队列为空
    }
  3. 带超时的接收数据
    有时候,你可能希望在一定时间内等待队列中的数据。这种情况下可以设置一个有限的延迟值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    TickType_t xDelay = 100 / portTICK_PERIOD_MS; // 设置100ms的超时
    if (xQueueReceive(xQueue, &xReceivedItem, xDelay) == pdTRUE)
    {
    // 成功接收数据
    }
    else
    {
    // 超时或接收失败
    }

注意事项

  • 当使用xQueueReceive()函数时,确保传入的缓冲区地址是有效的,并且有足够的空间来存放接收到的数据。
  • xQueueReceive()函数的最后一个参数是指定等待时间的Tick数,portMAX_DELAY表示无限期等待,0表示立即返回。

队列集

队列集的概念

在传统的FreeRTOS中,当一个任务阻塞在一个队列上等待数据时,如果数据没有到达,任务就会一直等待,即使有其他队列中有可用的数据也不会去处理。队列集则允许任务在等待期间同时监听多个队列或信号量,这样当任何一个队列中有数据到达时,任务都可以被唤醒去处理。

队列集的特点

  1. 多路复用:队列集可以看作是一个多路复用器,允许多个队列或信号量的句柄被组合在一起。当队列集中任意一个队列或信号量上有事件发生时,任务可以从阻塞状态恢复。

  2. 减少上下文切换:通过允许任务同时监听多个队列,减少了因频繁切换任务而产生的开销。

队列集使用

  1. 定义队列集结构
    在FreeRTOS中,队列集是由QueueSetHandle_t类型的变量来表示的。在创建队列集之前,需要先声明一个QueueSetHandle_t类型的变量来保存队列集的句柄。

  2. 创建队列集
    使用xQueueCreateSet()函数来创建一个新的队列集。这个函数需要两个参数:一是队列集可以包含的最大队列数,二是用于存储队列集句柄的变量的地址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    QueueSetHandle_t xQueueSet;
    const UBaseType_t uxMaxQueueNumberWithinSet = 5;
    xQueueSet = xQueueCreateSet(uxMaxQueueNumberWithinSet);
    if (xQueueSet != NULL)
    {
    // 队列集创建成功
    }
    else
    {
    // 创建失败,可能是因为内存不足或其他原因
    }
  3. 向队列集中添加队列
    创建好队列集之后,可以使用vQueueAddToSet()函数将已有的队列添加到队列集中。这个函数需要三个参数:一个是队列句柄,二是队列集句柄,三是可选的优先级。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    QueueHandle_t xQueueToBeAdded;
    xQueueToBeAdded = xQueueCreate(10, sizeof(MyType_t));
    if (xQueueToBeAdded != NULL)
    {
    vQueueAddToSet(xQueueToBeAdded, xQueueSet, ( BaseType_t ) 1 );
    }
    else
    {
    // 创建队列失败
    }
  4. 从队列集中选择队列
    可以使用xQueueSelectFromSet()函数从队列集中选择一个可以读写的队列。这个函数返回的是可以操作的队列的句柄。如果队列集中没有符合条件的队列,则该函数会阻塞调用者直到有队列可用或者超时。

  5. 删除队列集中的队列
    如果不再需要某个队列,可以使用vQueueRemoveFromSet()函数将其从队列集中移除。

  6. 销毁队列集
    当不再需要队列集时,可以使用vQueueDeleteSet()函数来销毁队列集,并释放与之关联的所有资源。

队列实例演示

本示例创建的两个个队列,长度都为5,还创建了1个队列集长的为2*5,并将两个队列加入到了队列集中,在创建的两个任务中,一个任务用于检测两个不同按键按下,来写两个不同的队列,另一个任务则通过来度队列集来获取那个队列有数据了,然后读取队列中的数据打印出接收数据的剩余可用空间。在接收任务中没收到一个数据都会进行1s的延时,若我们快速的连续按下按钮,就可以看到数据异步读取,和队列满的情况,而且在这里通过队列集,可以很好的判断出接收的数据来自哪个队列。

  • 整体代码如下
    可以以直接下载github的总工程文件查看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_RtosKey.h"
#include "queue.h"

/****************************************queue_start*********************************** */

// 静态队列创建---
#define QUEUE_LENGTH 5
#define ITEM_SIZE sizeof(uint32_t)
// ucQueueStorage 用来保存队列的数据
uint8_t ucQueueStorage[QUEUE_LENGTH * ITEM_SIZE]; // 大小为:队列长度 * 数据大小
// xQueueBuffer用来保存队列结构体
StaticQueue_t xQueueBuffer;
// xQueue1 用来控制队列
QueueHandle_t xQueue1;

// 动态队列创建----
QueueHandle_t xQueue2;

QueueSetHandle_t XqueueSet1;

void queueSendfun(void *p);
void queueResfun(void *p);

void queueInit(void)
{

// 创建队列: 可以容纳QUEUE_LENGTH个数据,每个数据大小是ITEM_SIZE
xQueue1 = xQueueCreateStatic(QUEUE_LENGTH,
ITEM_SIZE,
ucQueueStorage,
&xQueueBuffer);

xQueue2 = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);

xTaskCreate(queueSendfun, "q1", 128, NULL, 3, NULL);
xTaskCreate(queueResfun, "q2", 128, NULL, 3, NULL);

// 创建队列集
XqueueSet1 = xQueueCreateSet(QUEUE_LENGTH * 2);
// 队列加入队列集
xQueueAddToSet(xQueue1, XqueueSet1);
xQueueAddToSet(xQueue2, XqueueSet1);

KeyInit();

vTaskStartScheduler();
}

void queueSendfun(void *p)
{
uint16_t i = 0;
uint16_t j = 0;
static PressEvent btn1_event_val = NONE_PRESS;
static PressEvent btn2_event_val = NONE_PRESS;
BaseType_t re = pdFALSE;
while (1)
{
// 判断按键按下
if (btn1_event_val != get_button_event(&MKEY1))
{
btn1_event_val = get_button_event(&MKEY1);
if (btn1_event_val == PRESS_DOWN)
{
re = xQueueSend(xQueue1, &i, 0); // 发送数据到队列
printf("q1send:%d,pd:%d\r\n", i, re);
i++;
}
}

if (btn2_event_val != get_button_event(&MKEY2))
{
btn2_event_val = get_button_event(&MKEY2);
if (btn2_event_val == PRESS_DOWN)
{
re = xQueueSend(xQueue2, &j, 0);
printf("q2send:%d,pd:%d\r\n", j, re);
j++;
}
}
// vTaskDelay(1);
}
}

void queueResfun(void *p)
{
uint32_t res;
BaseType_t re = pdFALSE;
QueueSetMemberHandle_t quet = NULL;
while (1)
{
quet = xQueueSelectFromSet(XqueueSet1, portMAX_DELAY); // 从队列集里面读取数据 没有数据就阻塞

if (quet == xQueue1)
{
/* code */
re = xQueueReceive(xQueue1, &res, 0); // 从队列里面读取数据 一定有数据不用等待
HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
printf("q1res:%d,pd:%d,avlb:%d\r\n", res, re, /*获取队列剩余可用空间*/ uxQueueSpacesAvailable(xQueue1));
vTaskDelay(1000);
}
else if (quet == xQueue2)
{
/* code */
re = xQueueReceive(xQueue2, &res, 0);
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
printf("q2res:%d,pd:%d,avlb:%d\r\n", res, re, uxQueueSpacesAvailable(xQueue1));
vTaskDelay(1000);
}
else
{
printf("fialed\r\n");
}
}
}

/****************************************queue_end*********************************** */

这里是这个文章freertos的总仓库 github https://github.com/freedom413/FreeRtosDemo.git