目录
- 1、什么是队列Queue
- 2、队列的多任务特性
- 2.1 多任务的访问:
- 2.2 队列读取阻塞:
- 2.3 写队列阻塞:
- 2.4 阻塞于多个队列:
- 3、队列的使用
- 3.1 创建队列--The xQueueCreate() API
- 3.2 写入队列
- 3.3 从队列中接收数据
- 3.4 删除队列
- 4、队列集
- 4.1 创建队列集
- 4.2 向队列集合添加队列
- 4.3 从队列中读取以确定集合中哪个队列包含数据
- 4.4 队例集合实例
- 5、队列的特殊用法,Mailbox
- 5.1 向邮箱写入数据
- 5.2 从邮箱中读取数据
- 6、总结
1、什么是队列Queue
Queues提供了一个任务到任务,任务到中断,以及中断到任务的通信机制。
学过数据结构的原理应该知道,队列是常用的数据结构的一种,是一种先入先出First IN First Out 的数据组织与操作形态的名称。因此在FreeRTOS中,队列Queue的本质也是一样的。对于其行为与组织形式,用原文的图解能更清楚地说明。
由上图可以清楚的看出,FreeRTOS的Queue就是一个实现了队列的存储组织形式以及完成了不同任务之能互相传递数据的操作实现。队列能保存有限数量的固定大小的数据项。队列能保存的项目的最大数叫“长度length”。如上例的长度是5。
为了适应不同的数据类型,因此当队列创建时,必须设置队列长度和每个数据项的大小。
2、队列的多任务特性
2.1 多任务的访问:
队列本身就是对象,任何知道它们存在的任务或 ISR 都可以访问这些对象。任意数量的任务可以写入同一个队列,任意数量的任务可以从同一个队列中读取。 在实践中,一个队列有多个写入者是很常见的,但一个队列有多个读取者的情况就少得多了。(常用于多写一读)
2.2 队列读取阻塞:
当一个任务试图从一个队列中读取数据项,那么可以为这个读取行为指定指定一个 阻塞blocked 时间。如果队列此时已经为空,这时这个任务将被保持在阻塞blocked状态,从而等待队列中具有可读的数据。当另一个任务或中断程序放置数据进队列后,这个被阻塞的任务会被移到Ready状态。这是
另一种情况,如果队列中具有可用的数据之前,读取队列的任务处于的阻塞状态的时间到了,这个阻塞的任务也会被自动移到Ready状态。从而不再阻塞等待读数据。
队列能有多个读取者,所以一个队列有可能有超过一个任务阻塞着等待数据。当这种情况下,当数据到时,只有一个任务将被解除阻塞。这个解除阻塞的任务总是那个有最高的优先级的等待数据的任务。如果阻塞任务都有相同的优先级,那么等待数据时间最长的任务将会解除阻塞。
2.3 写队列阻塞:
就像从队列中读取一样,任务写入队列时,能选择指定一个阻塞时间。这种情况下,如果队列已经满了,那么阻塞时间是任务应该保持在阻塞状态等待队列有可用空间的最大时间。
队列可以有多个写入者,所以一个满的队列有可能有超过一个任务阻塞着等待完成一个发送操作。当这种情况下,当队列上有空间可用时,只有一个任务将会解除阻塞。这个解除阻塞的任务将总是那个具有取高优先级的等待任务。如果阻塞任务都有相同的优先级,那么等待时间最长的那个任务将被解除阻塞。
2.4 阻塞于多个队列:
队列可以分组到集合中,允许任务进入阻塞状态以等待数据在集合中的任何队列上可用。 队列集在“从多个队列接收”中演示。
3、队列的使用
3.1 创建队列–The xQueueCreate() API
创建一个新的队列并返回一个引用队列的句柄。
注意事项:
队列用于在任务之间和任务与中断程序之间传递数据。
队列可以在调度器scheduler启动的前后创建。
configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,或者简单地保持未定义,才能使用此函数。
参数
参数 | 解释 |
---|---|
UBaseType_t uxQueueLength | 队列在任何时刻所能保存的数据项的最大数量 |
UBaseType_t uxItemSize | 队列中每个数据项的字节大小 |
返回值 | 解释 |
---|---|
返回值 | 如果返回NULL,则表明无法创建队列,因为没有足够的堆内存可供FreeRTOS分配队列数据结构和存储区域。如果返回的非NULL值表示已成功创建队列。返回的值应存储为创建的队列的句柄。 |
实例
3.2 写入队列
说明
将项目发送(写入)到队列的前面或后面。
xQueueSend()和xQueueSendToBack()执行相同的操作,因此是等效的。
两者都将数据发送到队列的后面。xQueueSend()是最初的版本,现在建议使用xQueueSendToBack()代替它。
参数
参数 | 解释 |
---|---|
xQueue | 将数据发送(写入)到的队列的句柄。队列句柄将从用于创建队列的xQueueCreate()或xQueueCreateStatic()调用中返回。 |
pvItemToQueue | 指向要复制到队列中的数据的指针。队列可以容纳的每个项目的大小在创建队列时设置,许多字节将从pvItemToQueue复制到队列存储区域。 |
xTicksToWait | 如果队列已满,则任务应保持在“阻止”状态以等待队列上的可用空间的最长时间。如果xTicksToWait为零且队列已满,则xQueueSend()、xQueueSendToFront()和xQueueSendToBack()将立即返回。块时间以刻度周期指定,因此它表示的绝对时间取决于滴答频率。pdMS_TO_TICKS()宏可用于将以毫秒为单位指定的时间转换为以刻度为单位的时间。如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(不超时)。 |
返回值
值 | 解释 |
---|---|
pdPASS | 如果数据已成功发送到队列,则返回pdPASS。如果指定了等待时间(xTicksToWait不为零),则返回pdPASS说明,在等待时间到期前,数据已成功写入队列。 |
errQUEUE_FULL | 如果由于队列已满而无法将数据写入队列,则返回该错误。另一种情况是,如果指定了阻塞时间(xTicksToWait不为零),则一直到阻塞时间到期,都无法将数据写入队列。 |
实例
3.3 从队列中接收数据
参数
参数 | 解释 |
---|---|
xQueue | 需要从中接收数据的队列的句柄。队列句柄将从用于创建队列的xQueueCreate()或xQueueCreateStatic()调用中返回。 |
pvBuffer | 指向将接收数据复制到其中的内存的指针。缓冲区的长度必须至少等于队列项目大小。项目大小将由用于创建队列的xQueueCreate()或xQueueCreateStatic()调用的uxItemSize参数设置。 |
xTicksToWait | 如果队列已经为空,则任务应保持在“阻塞”状态以等待队列上的数据可用的最长时间。如果xTicksToWait为零,则如果队列已为空,xQueueReceive()将立即返回。阻塞时间以tick周期指定,因此它表示的绝对时间取决于tick频率。pdMS_TO_TICKS()宏可用于将以毫秒为单位指定的时间转换为以tick为单位的时间。如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(不超时)。 |
返回值
值 | 解释 |
---|---|
pdPASS | 如果成功从队列中读取数据,则返回pdPASS。如果指定了阻塞时间(xTicksToWait不为零),在阻塞的时间到期之前已成功从队列中读取数据。 |
errQUEUE_EMPTY | 如果由于队列已为空而无法从队列中读取数据,则返回该出错。如果指定了阻塞时间(xTicksToWait不为零),则在阻塞时间到期后队列中仍无法读到数据。 |
实例
3.4 删除队列
概要:
该函数用于删除之前用xQueueCreate()或xQueueCreateStatic()创建的队列。也可被用来删除信号量。
参数
pxQueueToDelete : 正在删除的队列的句柄。也可以使用信号句柄。
注意事项
如果当前有任何任务阻塞在队列/信号量上,则不得删除队列/信号量
实例
4、队列集
应用程序的设计经常需要一个任务去接收不同大小的数据,不同含义的数据,不同来源的数据。在这种情况下,可以使
用“队列集”。
队列集允许任务从多个队列接收数据,而无需任务依次轮询每个队列以确定哪个队列(如果有)包含数据。
队列集提供了一种机制,允许RTOS任务同时阻止(挂起)来自多个RTOS队列或信号量的读取操作。
在使用队列集之前,必须使用对xQueueCreateSet()的调用显式创建队列集。创建后,可以使用对xQueueAddToSet()的调用将标准FreeRTOS队列和信号量添加到集合中。然后使用xQueueSelectFromSet()确定集合中包含的队列或信号量中的哪一个(如果有的话)处于队列读取或信号量获取操作将成功的状态。
使用队列集具体过程
1、创建一个队列集合。
2、向队列集合添加队列
信号量也可被添加到队列集中。集号量将在本书后面描述。
3、从队列集合中读取以确定集合中哪一个队列包含数据
当集合中的一个队列接收数据,接收队列的句柄被发送给队列集,并且当一个任务调用一个从队列集中读到的函数时,该
句柄被返回。因此,如果一个队列句柄被从队列集中返回,那么就知道被该句柄所引用的队列包含有数据,然后这个任务可
以直接从队列中读。
4.1 创建队列集
参数
参数 | 解释 |
---|---|
uxEventQueueLength | 队列集存储发生在队列上的事件和集中包含的信号量。uxEventQueueLength指定一次可以排队的最大事件数。为了绝对确定事件不会丢失,uxEventQueueLength必须设置为添加到集合中的队列长度之和,其中二进制信号量和互斥量的长度为1,计数信号量的长度由其最大计数值设置。例如: |
如果队列集包含长度为5的队列、长度为12的另一个队列和二进制信号量,则uxEventQueueLength应设置为(5+12+1)或18。 | |
如果队列集包含三个二进制信号量,则uxEventQueueLength应设置为(1+1+1)或3。 | |
如果队列集要保存最大计数为5的计数信号量和最大计数为3的计数信号,则uxEventQueueLength应设置为(5+3)或8。 |
返回值
返回值 | 解释 |
---|---|
NULL | 返回NULL,则说明没有创建队列集 |
除NULL以外的任何其它值 | 已成功创建队列集。返回的值是一个句柄,通过它可以引用创建的队列集。 |
注意事项
对包含互斥锁的队列集进行阻塞不会导致互斥锁持有者继承被阻塞任务的优先级。
添加到队列集的每个队列中的每个空间需要额外的4字节RAM。因此,不应将具有高最大计数值的计数信号量添加到队列集。
除非对xQueueSelectFromSet()的调用首先向队列集成员返回了句柄,否则不能对队列集的成员执行接收(对于队列)或接收(对于信号量)操作。
configUSE_QUEUE_SETS必须在FreeRTOSConfig.h中设置为1,xQueueCreateSet()API函数才能可用。
4.2 向队列集合添加队列
描述:
将队列或信号量添加到先前通过调用xQueueCreateSet()创建的队列集。
在针对队列集的成员队列执行接收(对于队列)或接收(对于信号量)操作之前,必须先通过xQueueSelectFromSet()函数取得队列集中的某个队列的句柄。
参数
参数 | 解释 |
---|---|
xQueueOrSemaphore | 要添加到队列集的队列或信号量的句柄(强制转换为QueueSetMemberHandle_t类型)。 |
xQueueSet | 要添加队列或信号量的队列集的句柄。 |
返回值
返回值 | 解释 |
---|---|
pdPASS | 队列或信号已成功添加到队列集。 |
pdFAIL | 无法将队列或信号量添加到队列集中,因为它已经是其他集合的成员。 |
4.3 从队列中读取以确定集合中哪个队列包含数据
描述
xQueueSelectFromSet()从队列集的成员中选择包含数据(在队列的情况下)或可获取数据(在信号量的情况下,)的队列或信号量。xQueueSelectFromSet()有效地允许任务同时阻止(挂起)队列集中所有队列和信号量的读取操作。
参数
参数 | 解释 |
---|---|
xQueueSet | 这个函数要操作的队列集合。 |
xTicksToWait | 调用该函数的任务阻塞着,以等待该函数获得集合中某队列或集号量可用的最大时间,以tick为单位计时 |
返回值
返回值 | 解释 |
---|---|
NULL | 返回NULL说明目前集合中的队列或信号量都不可用 |
任何其它值 | 队列集中包含有数据的队列句柄(强制转换为QueueSetMemberHandle_t类型),或队列集中包含数据的信号量句柄(强制转化为QueueSet MemberHandle_t类型)。 |
注意事项
对包含互斥锁的队列集进行阻塞不会导致互斥锁持有者继承被阻塞任务的优先级。除非对xQueueSelectFromSet()的调用首先向队列集成员返回了句柄,否则不能对队列集的成员执行receive(对于队列)或take(对于信号量)操作。
4.4 队例集合实例
使用队列集合的例子:
这个例子创建了两个发送任务和一个接收任务。发送任务在两个单独的队列中发送数据给接收任务,每个队列对应一个任务。这两个队列加入到一个队列集合中,并且接收任务从队列集合中读取并确定哪一个队列包含数据。
任务,队列,和队列集合,都是在main()函数中创建的。
接下来,实现两个发送数据的任务函数,每100毫秒,第一个发送任务使用xQueue1来发送字符指针给接收任务。每200毫秒,第二个发送任务用xQueue2发送字符指针给接收任务。字符指针是指向一个由发送任务定义的字符串。
发送任务写入的队列是相同的队列集合中的成员。每一次任务发送到其中一个队列中,这个队列的句柄被发送给队列集合,接收队列调用xQueueSelelctFromSet()从队列集合中读取队列句柄。在接收任务从队列集合中接收到队列句柄后,就知道这个句柄所引用的队列包含数据,所以直接从队列中读取数据。从队列中所读取的数据是一个指向字符串的指针,接收任务将其打印出来。
如果调用xQueueSelectFromSet()超时,那么它将返回NULL。例子中,xQueueSelectFromSet()以无限期方式被调用,所以永远不会超时,并只能返回一个有效的队列句柄。因此,接收任务不必检查该函数是否返回NULL。如果句柄所引用的队列包含数据,则 xQueueSelectFromSet()将只会返回一个队列句柄,所以当读取队列时不需要使用阻塞时间这个参数。
5、队列的特殊用法,Mailbox
"邮箱Mailbox"用于指长度为 1 的队列。邮箱用于保存可由任何任务或任何中断服务程序读取的数据。数据不通过邮箱传递,而是保留在邮箱中,直到被覆盖。发件人覆盖邮箱中的值。接收者从邮箱中读取值,但不从邮箱中删除值。
5.1 向邮箱写入数据
xQueueOverwrite()用于长度为1的队列,这意味着队列要么为空,要么已满。
参数
参数 | 解释 |
---|---|
xQueue | 数据要发送到的队列的句柄。 |
pvItemToQueue | 指向要放置在队列中的项目的指针。队列可以容纳的每个项目的大小在创建队列时设置,许多字节将从pvItemToQueue复制到队列存储区域。 |
返回的值
xQueueOverwrite()是一个调用xQueueGenericSend()的宏,因此具有与xQueueSendToFront()相同的返回值。然而,pdPASS是唯一可以返回的值,因为即使队列已满,xQueueOverwrite()也会写入队列。
5.2 从邮箱中读取数据
描述
从队列中读取项目,但不从队列中删除该项目。下次使用xQueueReceive()或xQueuePeek()从同一队列中获取项目时,将返回相同的项目。
参数
参数 | 解释 |
---|---|
xQueue | 要读取的队列的句柄 |
pvBuffer | 指向将从队列中读取的数据复制到其中的内存的指针。缓冲区的长度必须至少等于队列项目大小。项目大小将由用于创建队列的xQueueCreate()或xQueueCreateStatic()调用的uxItemSize参数设置。 |
xTicksToWait | 如果读不到数据时,任务进入阻塞等待多少时间。 |
返回值
返回值 | 解释 |
---|---|
pdPASS | 如果成功从队列中读取数据,则返回pdPASS。如果指定了阻塞时间(xTicksToWait不为零),则在阻塞时间到期前,成功返回了数据,则函数也返回pdPASS。 |
errQUEUE_EMPTY | 如果由于队列已为空而无法从队列中读取数据,则返回这个错误。如果指定了阻塞时间(xTicksToWait不为零),则在阻塞时间到期后仍读不到数据,则也返回这个错误提示。 |
6、总结
在FreeRTOS中,队列Queue做为任务间数据传递的一种重要机制,在实际编程过程中会经常使用。在单片机的使用过程中,一个外设的数据的传递即可能涉及到任务与任务之间,也可能是在任务与中断之间的数据传递场景。因此在这个过程中,同步必须考虑任务阻塞的问题,即什么条件下,发送数据进入阻塞,阻塞多长时间,接收数据的任务什么时候必须阻塞,阻塞多长时间。
队列机制在函数的调用以及队列的实现中已相当完美的解决了多任务情况下的任务阻塞以及任务调度的机制。
队列是一个通用机制,信号量也是队列的一种应用。因此,掌握了队列的实用,信号量也就信手拈来。