FreeRTOS 队列
Queue 简介
数据存储
FreeRTOS的Queue是个FIFO先入先出的缓冲区。队列长度在队列创建时被指定。
上图展示了队列的使用方法。
在FreeRTOS的Queue实现中,采用的是复制而不是引用。这样的好处是:
- 数据可以直接发送到队列中保存,而不用担心原数据会被覆盖或修改
- 不用事先开辟缓冲区存储,直接复制到队列即可
- 发送和接收是独立的
- 也可以直接将指针作为元素发送,可以看做是引用
- FreeRTOS负责给数据分配存储空间
- 某些内存保护系统中,RAM的访问是被限制的。发送和接收任务不一定都可以访问RAM
多任务访问
队列可以被任何任务或者中断服务函数访问。任一任务都可以写入队列和读取队列。
读取等待
当任务企图读取队列时,可以指定一个阻塞时间等待数据。在这段时间中,任务会被阻塞。当队列中有新数据或者超时,被阻塞的任务会自动进入就绪态。一个队列会有很多受众,但是在这种情况下,只有一个任务会解除阻塞态,这个任务是所有等待任务中优先级最高的那个。如果任务优先级相同,则选取已经等待时间较长的那个任务解除阻塞。
写入等待
当队列已满,可以指定一个阻塞时间等待写入。同样,有可能存在多个任务等待写入队列。这时,只有一个任务可以解除阻塞。这个任务通常是优先级最高的任务,如果优先极相同,则选取等待时间最长的任务解除阻塞。
多队列阻塞
队列可以组成集合,一个任务可以等待一个队列集中所有的队列数据同时就绪。
APl
xQueueCreate() 创建队列
队列可以用句柄表示,数据类型是QueueHandle_t
在FreeRTOS V9.0 中,xQueueCreateStatic()可以用来创建一个静态队列,他的大小在编译就被指定。
xQueueSendToBack() and xQueueSendToFront() 插入元素
这两个API指定数据位置,插入队首还是队尾
xQueueSend()等同于xQueueSendToBack()
注意,这两个函数不能在中断中调用!!,如需中断中调用,须使用xQueueSendToFrontFromISR() 和 xQueueSendToBackFromISR()
xQueueReceive() 接收数据
注意,不能在中断中使用! 中断中须使用,xQueueReceiveFromISR()
uxQueueMessagesWaiting() 查询队列元素数
不能在中断使用!!,替代:uxQueueMessagesWaitingFromISR()
示例
图解
- Receiver优先级最高首先抢占CPU,由于队列为空,Receiver随即进入阻塞态
- Sender2发送数据到队列
- Receiver检测到队列中有数据,开始读数。队列随即变空,Receiver又进入阻塞态
- Sender1发送数据到队列
- Receiver检测到队列中有数据,开始读数。队列随即变空,Receiver又进入阻塞态
- …
从多个数据源接收数据 Receiving Data From Multiple Sources
由于一个队列可能接收来源不同的数据,如果我们想要知道每个数据的身份信息,一个较为简单的方式就是将结构体作为队列的元素,如下图所示
- eDataID代表数据身份信息
- IDataValue则是数据值
示例
定义数据元素结构体:
创建发送任务:
创建接收任务:
只有队列满时,才会处理
图解
- t2时刻队列已满,此时Receiver从队列中取出一个数据
- t4时刻队列出现一个空位,由于Sender1/2优先级相同,调度器将控制权交给等待时间最长的任务Sender1
- t5时刻,Sender1将队列填满,Receiver开始取数据
- …
处理大量的或者不同长度的数据 Working with Large or Variable Sized Data
处理大量数据
Queuing Pointers 队列指针
如果数据量很大,那么我们一般选择传递指针而不是复制数据。传递指针对于处理时间和内存消耗都是高效的。但是,需要确保两点:
-
指针指向的存储空间地址是有定义的
指针指向的地址,一般来说,只要相应发送和接收任务才可以写入和读取,而且需要是有效、连续的! -
指针指向的地址仍然有效
任何时候不要把任务堆栈的地址作为指针传递,因为任务堆栈会在任务修改时自动释放
示例
处理类型或长度不同的数据 Using a Queue to Send Different Types and Lengths of Data
将结构体和指针相结合,队列可以传递不同类型的数据
示例 FreeRTOS+TCP/IP
从多个队列中接收 Receiving From Multiple Queues
队列集 Queue Sets
队列集允许任务从多个队列中接收数据而不用知道哪个有数据。队列集并不比使用单一队列传递结构体更加高效,所以队列集一般只在必要的时候使用。
xQueueCreateSet() 创建队列集
xQueueAddToSet() 添加到队列集
将一个队列或者信号量添加到队列集
xQueueSelectFromSet() 从一个队列集中读取队列句柄
在读取数据前,我们需要先调用这个函数获取集合中某个队列或信号量的句柄,然后才能继续读取数据!!
示例 1
示例 2
队列集中包含了二值信号量,uint32_t变量,指针
利用队列创建邮箱 Using a Queue to Create a Mailbox
本文中,邮箱指的是长度为1的队列,之所以叫邮箱,是因为他们作用相似
- 发送-接收模式
-
邮件可以被任一个任务或ISR读取
API
xQueueOverwrite() 队列覆写
用来发送数据到队列,与xQueueSendToBack()的区别在于 可以覆写
这个函数应该仅被用于队列长度为1的情况
注意!不能在中断中使用!!
示例
xQueuePeek() 不取出元素查看
示例