FreeRTOS 队列

时间:2024-04-14 16:54:07

FreeRTOS 队列

Queue 简介

数据存储

FreeRTOS的Queue是个FIFO先入先出的缓冲区。队列长度在队列创建时被指定。
FreeRTOS 队列
上图展示了队列的使用方法。

在FreeRTOS的Queue实现中,采用的是复制而不是引用。这样的好处是:

  • 数据可以直接发送到队列中保存,而不用担心原数据会被覆盖或修改
  • 不用事先开辟缓冲区存储,直接复制到队列即可
  • 发送和接收是独立的
  • 也可以直接将指针作为元素发送,可以看做是引用
  • FreeRTOS负责给数据分配存储空间
  • 某些内存保护系统中,RAM的访问是被限制的。发送和接收任务不一定都可以访问RAM

多任务访问

队列可以被任何任务或者中断服务函数访问。任一任务都可以写入队列和读取队列。

读取等待

当任务企图读取队列时,可以指定一个阻塞时间等待数据。在这段时间中,任务会被阻塞。当队列中有新数据或者超时,被阻塞的任务会自动进入就绪态。一个队列会有很多受众,但是在这种情况下,只有一个任务会解除阻塞态,这个任务是所有等待任务中优先级最高的那个。如果任务优先级相同,则选取已经等待时间较长的那个任务解除阻塞。

写入等待

当队列已满,可以指定一个阻塞时间等待写入。同样,有可能存在多个任务等待写入队列。这时,只有一个任务可以解除阻塞。这个任务通常是优先级最高的任务,如果优先极相同,则选取等待时间最长的任务解除阻塞。

多队列阻塞

队列可以组成集合,一个任务可以等待一个队列集中所有的队列数据同时就绪。

APl

xQueueCreate() 创建队列

队列可以用句柄表示,数据类型是QueueHandle_t
FreeRTOS 队列
在FreeRTOS V9.0 中,xQueueCreateStatic()可以用来创建一个静态队列,他的大小在编译就被指定。

xQueueSendToBack() and xQueueSendToFront() 插入元素

这两个API指定数据位置,插入队首还是队尾
xQueueSend()等同于xQueueSendToBack()
注意,这两个函数不能在中断中调用!!,如需中断中调用,须使用xQueueSendToFrontFromISR() 和 xQueueSendToBackFromISR()
FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列

xQueueReceive() 接收数据

注意,不能在中断中使用! 中断中须使用,xQueueReceiveFromISR()
FreeRTOS 队列
FreeRTOS 队列

uxQueueMessagesWaiting() 查询队列元素数

不能在中断使用!!,替代:uxQueueMessagesWaitingFromISR()
FreeRTOS 队列
FreeRTOS 队列

示例

FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列
图解
FreeRTOS 队列

  • Receiver优先级最高首先抢占CPU,由于队列为空,Receiver随即进入阻塞态
  • Sender2发送数据到队列
  • Receiver检测到队列中有数据,开始读数。队列随即变空,Receiver又进入阻塞态
  • Sender1发送数据到队列
  • Receiver检测到队列中有数据,开始读数。队列随即变空,Receiver又进入阻塞态

从多个数据源接收数据 Receiving Data From Multiple Sources

由于一个队列可能接收来源不同的数据,如果我们想要知道每个数据的身份信息,一个较为简单的方式就是将结构体作为队列的元素,如下图所示
FreeRTOS 队列

  • eDataID代表数据身份信息
  • IDataValue则是数据值

示例

定义数据元素结构体:
FreeRTOS 队列
创建发送任务:
FreeRTOS 队列
创建接收任务:
FreeRTOS 队列
只有队列满时,才会处理
FreeRTOS 队列
图解
FreeRTOS 队列

  • t2时刻队列已满,此时Receiver从队列中取出一个数据
  • t4时刻队列出现一个空位,由于Sender1/2优先级相同,调度器将控制权交给等待时间最长的任务Sender1
  • t5时刻,Sender1将队列填满,Receiver开始取数据

处理大量的或者不同长度的数据 Working with Large or Variable Sized Data

处理大量数据

Queuing Pointers 队列指针

如果数据量很大,那么我们一般选择传递指针而不是复制数据。传递指针对于处理时间和内存消耗都是高效的。但是,需要确保两点:

  • 指针指向的存储空间地址是有定义的
    指针指向的地址,一般来说,只要相应发送和接收任务才可以写入和读取,而且需要是有效、连续的!
  • 指针指向的地址仍然有效
    任何时候不要把任务堆栈的地址作为指针传递,因为任务堆栈会在任务修改时自动释放

示例

FreeRTOS 队列
FreeRTOS 队列

处理类型或长度不同的数据 Using a Queue to Send Different Types and Lengths of Data

将结构体和指针相结合,队列可以传递不同类型的数据

示例 FreeRTOS+TCP/IP

FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列

从多个队列中接收 Receiving From Multiple Queues

队列集 Queue Sets

队列集允许任务从多个队列中接收数据而不用知道哪个有数据。队列集并不比使用单一队列传递结构体更加高效,所以队列集一般只在必要的时候使用。

xQueueCreateSet() 创建队列集

FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列

xQueueAddToSet() 添加到队列集

将一个队列或者信号量添加到队列集
FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列

xQueueSelectFromSet() 从一个队列集中读取队列句柄

在读取数据前,我们需要先调用这个函数获取集合中某个队列或信号量的句柄,然后才能继续读取数据!!
FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列

示例 1

FreeRTOS 队列
FreeRTOS 队列
FreeRTOS 队列

示例 2

队列集中包含了二值信号量,uint32_t变量,指针
FreeRTOS 队列

利用队列创建邮箱 Using a Queue to Create a Mailbox

本文中,邮箱指的是长度为1的队列,之所以叫邮箱,是因为他们作用相似

  • 发送-接收模式
  • 邮件可以被任一个任务或ISR读取
    FreeRTOS 队列

API

xQueueOverwrite() 队列覆写

用来发送数据到队列,与xQueueSendToBack()的区别在于 可以覆写
这个函数应该仅被用于队列长度为1的情况
注意!不能在中断中使用!!
FreeRTOS 队列
FreeRTOS 队列
示例
FreeRTOS 队列

xQueuePeek() 不取出元素查看

FreeRTOS 队列
示例
FreeRTOS 队列