“队列”提供了一种任务到任务、任务到中断和中断到任务的通信机制。
队列特性
数据存储
队列可以容纳有限数量的固定大小的数据项。队列可以容纳的最大项目数称为其“长度”。每个数据项的长度和大小都是在创建队列时设置的。
队列通常用作先进先出(FIFO)缓冲区,数据被写入队列的末尾(尾部),并从队列的前端(头部)删除。图31演示了向用作FIFO的队列写入数据和从中读取数据。也可以写入队列的前面,并覆盖已经在队列前面的数据。


有两种方法可以实现队列行为:
1.按复制排队
按复制排队意味着发送到队列的数据被逐字节复制到队列中。
2.按引用排队
引用排队意味着队列只保存指向发送到队列的数据的指针,而不是数据本身。
FreeRTOS使用复制队列方法。按复制排队被认为比按引用排队更强大、更简单,因为:
堆栈变量可以直接发送到队列,即使在声明它的函数退出后该变量将不存在。
数据可以发送到队列,而无需首先分配缓冲区来保存数据,然后将数据复制到分配的缓冲区中。
发送任务可以立即重用发送到队列的变量或缓冲区。
发送任务和接收任务完全解耦——应用程序设计人员不需要关心哪个任务“拥有”数据,或者哪个任务负责发布数据。
按副本排队不会阻止队列也用于按引用排队。例如,当排队的数据大小使得将数据复制到队列中不切实际时,可以将指向数据的指针复制到队列。
RTOS全权负责分配用于存储数据的内存。
在内存保护系统中,任务可以访问的RAM将受到限制。在这种情况下,只有当发送和接收任务都可以访问存储数据的RAM时,才能使用引用排队。按副本排队不受此限制;内核始终以完全权限运行,允许使用队列跨内存保护边界传递数据。
多任务访问
队列本身就是对象,任何知道队列存在的任务或ISR都可以访问队列。任意数量的任务都可以写入同一队列,任意数量的作业都可以从同一队列读取。在实践中,一个队列有多个写入器是很常见的,但一个队列中有多个读取器的情况要少得多。
阻塞队列读取
当任务尝试从队列中读取时,它可以选择指定“块”时间。如果队列已为空,则任务将保持在“阻塞”状态,等待队列中的数据可用。当另一个任务或中断将数据放入队列时,处于“阻塞”状态的任务会自动移动到“就绪”状态,等待队列中的数据可用。如果指定的阻止时间在数据可用之前到期,任务也将自动从“阻塞”状态移动到“就绪”状态。
队列可以有多个读取器,因此单个队列上可能有多个任务被阻止等待数据。在这种情况下,当数据可用时,只有一个任务将被解除阻止。未阻止的任务将始终是等待数据的最高优先级任务。如果被阻止的任务具有相同的优先级,则等待数据时间最长的任务将被解除阻止。
阻止队列写入
就像从队列读取一样,任务在写入队列时也可以选择指定块时间。在这种情况下,如果队列已满,则块时间是任务应保持在“已阻止”状态以等待队列上可用空间的最长时间。
队列可以有多个写入器,因此一个完整的队列上可能有多个任务被阻塞,等待完成发送操作。在这种情况下,当队列上的空间可用时,只有一个任务将被解锁。未阻塞的任务将始终是等待空间的最高优先级任务。如果被阻塞的任务具有相同的优先级,则等待空间时间最长的任务将被解除阻塞。
阻塞多个队列
队列可以分组到集合中,允许任务进入“阻塞”状态,等待集合中任何队列上的数据可用
使用一个队列
xQueueCreate()API函数
必须显式创建队列才能使用。
队列由句柄引用,句柄是QueueHandle_t类型的变量。xQueueCreate()API函数创建一个队列并返回一个引用其创建的队列的QueueHandle_t。
创建队列时,FreeRTOS从FreeRTOS堆中分配RAM。RAM用于保存队列数据结构和 包含在队列中。如果没有足够的堆RAM可用于创建队列,xQueueCreate()将返回NULL。
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
uxQueueLength
正在创建的队列一次可以容纳的最大项目数。
uxItemSize
可以存储在队列中的每个数据项的字节大小。
返回值
如果返回NULL,则无法创建队列,因为FreeRTOS没有足够的堆内存来分配队列数据结构和存储区域。
返回的非NULL值表示队列已成功创建。返回的值应作为创建队列的句柄存储。
创建队列后,可以使用xQueueReset()API函数将队列返回到其原始空状态
xQueueSendToBack()和xQueueSendToFront()API函数
正如预期的那样,xQueueSendToBack()用于将数据发送到队列的后部(尾部),xQueueSendToFront()用于向队列的前部(头部)发送数据。
xQueueSend()与xQueueSendToBack()完全相同。
注意:切勿从中断服务例程调用xQueueSendToFront()或xQueueSendToBack()。应使用中断安全版本xQueueSendToFrontFromISR()和xQueueSendToBackFromISR()来代替它们。
xQueueSendToFront() API 函数原型
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
xQueueSendToBack() API 函数原型
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
xQueue
数据被发送(写入)到的队列的句柄。队列句柄将从用于创建队列的xQueueCreate()调用中返回。
pvItemToQueue
指向要复制到队列中的数据的指针。
队列可以容纳的每个项目的大小是在创建队列时设置的,因此这许多字节将从pvItemToQueue复制到队列存储区域。
xTicksToWait
如果队列已满,则任务应保持在“已阻塞”状态以等待队列上可用空间的最长时间。
如果xTicksToWait为零且队列已满,xQueueSendToFront()和xQueueSendToBack()都将立即返回。
块时间以tick周期指定,因此它表示的绝对时间取决于tick频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以刻度为单位的时间。
如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(没有超时)。
返回值
有两种可能的返回值:
1.pdPASS
只有当数据成功发送到队列时,才会返回pdPASS。
如果指定了块时间(xTicksToWait不为零),则调用任务可能会被置于“已阻塞”状态,在函数返回之前等待队列中的空间可用,但数据在块时间到期之前已成功写入队列。
2.errQUEUE_FULL
如果由于队列已满而无法将数据写入队列,则将返回errQUEUE_FULL。
如果指定了阻塞时间(xTicksToWait不为零),则调用任务将被置于“阻塞”状态,等待另一个任务或中断在队列中腾出空间,但指定的阻塞时间在此之前已过期。
xQueueReceive()API函数
xQueueReceive()用于从队列中接收(读取)项目。收到的项目将从队列中删除。
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
xQueue
从中接收(读取)数据的队列句柄。
队列句柄将从用于创建队列的xQueueCreate()调用中返回。
pvBuffer
指向存储器的指针,接收到的数据将被复制到该存储器中。队列所包含的每个数据项的大小在创建队列时设置。pvBuffer指向的内存必须至少足够大,以容纳那么多字节。
xTicksToWait
如果队列已为空,则任务应保持在“阻塞”状态以等待队列上的数据可用的最长时间。
如果xTicksToWait为零,则xQueueReceive()将在以下情况下立即返回
队列已为空。
块时间以滴答周期指定,因此它表示的绝对时间取决于tick频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以刻度为单位的时间。
如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(不超时)。
返回值
有两种可能的返回值:
1.pdPASS
只有从队列中成功读取数据时,才会返回pdPASS。
如果指定了块时间(xTicksToWait不为零),则调用任务可能被置于“已阻止”状态,等待队列上的数据可用,但在块时间到期之前,数据已成功从队列中读取。
2.errQUEUE_EMPTY
如果由于队列已为空而无法从队列读取数据,则将返回errQUEUE_EMPTY。
如果指定了块时间(xTicksToWait不为零),则调用任务将被置于“已阻止”状态,以等待另一个任务或中断将数据发送到队列,但块时间在此之前已过期。
uxQueueMessagesWaiting()API函数
uxQueueMessagesWaiting()用于查询当前队列中的项目数。
注意:切勿从中断服务例程调用uxQueueMessagesWaiting()。应使用中断安全的uxQueueMessagesWaitingFromISR()来代替它。
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
xQueue
正在查询的队列的句柄。队列句柄将从用于创建队列的xQueueCreate()调用中返回。
返回值
正在查询的队列当前包含的项目数。如果返回零,则队列为空。
示例10。从队列接收时阻塞
此示例演示了创建队列、从多个任务向队列发送数据以及从队列接收数据。创建队列是为了保存int32_t类型的数据项。发送到队列的任务不指定块时间,而从队列接收的任务指定块时间。
发送到队列的任务优先级低于从队列接收的任务优先级。这意味着队列不应包含多个项目,因为一旦数据发送到队列,接收任务将取消阻止,抢先发送任务,并删除数据,使队列再次为空。
清单45显示了写入队列的任务的实现。创建了此任务的两个实例,一个连续将值100写入队列,另一个连续向同一队列写入值200。任务参数用于将这些值传递给每个任务实例。

清单46显示了从队列接收数据的任务的实现。接收任务指定了100毫秒的块时间,因此将进入阻塞状态等待数据可用。当队列中有数据可用,或者100毫秒后没有数据可用时,它将离开“阻塞”状态。在这个例子中,100毫秒的超时应该永远不会过期,因为有两个任务在不断地写入队列。

清单47包含main()函数的定义。这只是在启动调度程序之前创建队列和三个任务。创建队列最多可容纳五个int32_t值,即使任务的优先级被设置为队列一次永远不会包含多个项。

最低0.47元/天 解锁文章
1270

被折叠的 条评论
为什么被折叠?



