FreeRTOS的队列管理

news2024/10/19 0:12:27

“队列”提供了一种任务到任务、任务到中断和中断到任务的通信机制。

队列特性

数据存储
队列可以容纳有限数量的固定大小的数据项。队列可以容纳的最大项目数称为其“长度”。每个数据项的长度和大小都是在创建队列时设置的。
队列通常用作先进先出(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值,即使任务的优先级被设置为队列一次永远不会包含多个项。

在这里插入图片描述

发送到队列的两个任务具有相同的优先级。这会导致两个发送任务依次向队列发送数据。示例10产生的输出如图32所示。

在这里插入图片描述

图33展示了执行顺序
在这里插入图片描述

从多个来源接收数据

在FreeRTOS设计中,任务从多个源接收数据是很常见的。接收任务需要知道数据来自哪里,以确定应该如何处理数据。一个简单的设计解决方案是使用单个队列来传输结构,该结构的字段中包含数据值和数据源。该方案如图34所示。

在这里插入图片描述

参见图34:
创建了一个队列,其中包含Data_t类型的结构。结构成员允许在一条消息中向队列发送数据值和枚举类型,以指示数据的含义。
中央控制器任务用于执行主要系统功能。这必须对队列上传递给它的系统状态的输入和更改做出反应。
CAN总线任务用于封装CAN总线接口功能。当CAN总线任务接收并解码了一条消息时,它会以Data_t结构将已解码的消息发送给控制器任务。传输结构的eDataID成员用于让控制器任务知道数据是什么——在所示的情况下,它是一个电机速度值。传输结构的lDataValue成员用于让控制器任务知道实际的电机速度值。
人机界面(HMI)任务用于封装所有HMI功能。
机器操作员可能可以通过多种方式输入命令和查询值,这些方式必须在HMI任务中检测和解释。当输入新命令时,HMI任务以Data_t结构将命令发送给控制器任务。传输结构的eDataID成员用于让控制器任务知道数据是什么——在所示的情况下,它是一个新的设定点值。传输结构的lDataValue成员用于让控制器任务知道实际的设定点值。

示例11。发送到队列时阻塞,并在队列上发送结构
示例11类似于示例10,但任务优先级相反,因此接收任务的优先级低于发送任务。此外,队列用于传递结构,而不是整数。
清单48显示了示例11使用的结构的定义。

在这里插入图片描述

在示例10中,接收任务具有最高优先级,因此队列永远不会包含多个项目。这是因为一旦数据被放入队列,接收任务就会优先于发送任务。在示例11中,发送任务具有更高的优先级,因此队列通常会满。这是因为,一旦接收任务从队列中删除一个项目,它就会被其中一个发送任务抢占,然后立即重新填充队列。然后,发送任务重新进入“阻塞”状态,等待队列上的空间再次可用。

清单49显示了发送任务的实现。发送任务指定了100毫秒的阻塞时间,因此每次队列满时,它都会进入“阻塞”状态,等待空间可用。当队列上有可用空间,或者100毫秒后没有可用空间时,它会离开“阻塞”状态。在这个例子中,100毫秒的超时应该永远不会过期,因为接收任务正在通过从队列中删除项目来不断腾出空间。

在这里插入图片描述

接收任务的优先级最低,因此只有当两个发送任务都处于“阻塞”状态时,它才会运行。只有当队列已满时,发送任务才会进入“阻塞”状态,因此只有当队列已经满时,接收任务才会执行。因此,即使它没有指定块时间,它也总是期望接收数据。
接收任务的实现如清单50所示。

static void vReceiverTask( void *pvParameters )
{
/* Declare the structure that will hold the values received from the queue. */
Data_t xReceivedStructure;
BaseType_t xStatus;
/* This task is also defined within an infinite loop. */
for( ;; )
{
/* Because it has the lowest priority this task will only run when the
sending tasks are in the Blocked state. The sending tasks will only enter
the Blocked state when the queue is full so this task always expects the
number of items in the queue to be equal to the queue length, which is 3 in
this case. */
if( uxQueueMessagesWaiting( xQueue ) != 3 )
{
vPrintString( "Queue should have been full!\r\n" );
}
/* Receive from the queue.
The second parameter is the buffer into which the received data will be
placed. In this case the buffer is simply the address of a variable that
has the required size to hold the received structure.
The last parameter is the block time - the maximum amount of time that the
task will remain in the Blocked state to wait for data to be available
if the queue is already empty. In this case a block time is not necessary
because this task will only run when the queue is full. */
xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
if( xStatus == pdPASS )
{
/* Data was successfully received from the queue, print out the received
value and the source of the value. */
if( xReceivedStructure.eDataSource == eSender1 )
{
vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );
}
else
{
vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );
}
}
else
{
/* Nothing was received from the queue. This must be an error as this
task should only run when the queue is full. */
vPrintString( "Could not receive from the queue.\r\n" );
}
}
}

Listing 50. The definition of the receiving task for Example 11

main()与前一个示例相比仅略有变化。创建队列以容纳三个Data_t结构,并颠倒发送和接收任务的优先级。main()的实现如清单51所示。

int main( void )
{
/* The queue is created to hold a maximum of 3 structures of type Data_t. */
xQueue = xQueueCreate( 3, sizeof( Data_t ) );
if( xQueue != NULL )
{
/* Create two instances of the task that will write to the queue. The
parameter is used to pass the structure that the task will write to the
queue, so one task will continuously send xStructsToSend[ 0 ] to the queue
while the other task will continuously send xStructsToSend[ 1 ]. Both
tasks are created at priority 2, which is above the priority of the receiver. */
xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL );
/* Create the task that will read from the queue. The task is created with
priority 1, so below the priority of the sender tasks. */
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
}
else
{
/* The queue could not be created. */
}
/* If all is well then main() will never reach here as the scheduler will
now be running the tasks. If main() does reach here then it is likely that
there was insufficient heap memory available for the idle task to be created.
Chapter 2 provides more information on heap memory management. */
for( ;; );
}

Listing 51. The implementation of main() for Example 11

示例11产生的输出如图35所示。

在这里插入图片描述
图36展示了发送任务的优先级高于接收任务的优先级所导致的执行顺序。表22提供了对图36的进一步解释,并描述了为什么前四条消息来自同一任务。

在这里插入图片描述
t1 任务发送器1执行并向队列发送3个数据项。

t2 队列已满,因此发送方1进入“阻塞”状态,等待其下一次发送完成。任务发送器2现在是能够运行的最高优先级任务,因此进入运行状态。

t3 任务发送器2发现队列已满,因此进入“阻塞”状态等待其第一次发送完成。任务接收器现在是能够运行的最高优先级任务,因此进入运行状态。

t4 优先级高于接收任务优先级的两个任务正在等待队列上的可用空间,导致任务接收器在从队列中删除一个项目后立即被抢占。任务发送器1和发送器2具有相同的优先级,因此调度器选择等待时间最长的任务作为将进入运行状态的任务——在本例中为任务发送器1。

t5 任务发送器1向队列发送另一个数据项。队列中只有一个空间,因此任务发送器1进入“已阻止”状态,等待其下一次发送完成。任务接收器再次是能够运行的最高优先级任务,因此进入运行状态。
任务发送器1现在已向队列发送了四个项目,任务发送器2仍在等待将其第一个项目发送到队列。

t6 优先级高于接收任务优先级的两个任务正在等待队列上的可用空间,因此任务接收器在从队列中删除一个项目后立即被抢占。这一次,发送器2的等待时间比发送器1长,因此发送器2进入运行状态。

t7任务发送器2向队列发送数据项。队列中只有一个空间,因此发件人2进入“已阻塞”状态,等待其下一次发送完成。
Sender 1和Sender 2都在等待队列上的可用空间,因此任务Receiver是唯一可以进入运行状态的任务。

处理大型或可变大小的数据

队列指针
如果队列中存储的数据量很大,那么最好使用队列来传输指向数据的指针,而不是逐字节将数据本身复制到队列中或从队列中复制出来。传输指针在处理时间和创建队列所需的RAM量方面都更有效。然而,在排队时,必须格外小心,以确保:

1.所指RAM的所有者已明确界定。
当通过指针在任务之间共享内存时,必须确保两个任务不会同时修改内存内容,也不会采取任何其他可能导致内存内容无效或不一致的操作。理想情况下,只有发送任务才应该被允许访问内存,直到指向内存的指针被排队,只有接收任务应该被允许在从队列接收到指针后访问内存。

2.所指向的RAM仍然有效。
如果指向的内存是动态分配的,或者是从预分配的缓冲区池中获得的,那么应该只有一个任务负责释放内存。释放内存后,任何任务都不应尝试访问内存。
永远不应该使用指针来访问在任务堆栈上分配的数据。堆栈帧更改后,数据将无效。

例如,清单52、清单53和清单54演示了如何使用队列将指向缓冲区的指针从一个任务发送到另一个任务:
 清单52创建了一个最多可容纳5个指针的队列。
 清单53分配一个缓冲区,向缓冲区写入一个字符串,然后将指向缓冲区的指针发送到队列。
 清单54从队列接收指向缓冲区的指针,然后打印缓冲区中包含的字符串。

在这里插入图片描述
在这里插入图片描述
使用队列发送不同类型和长度的数据
前面的部分已经展示了两种强大的设计模式;将结构发送到队列以及将指针发送到队列。结合这些技术,任务可以使用单个队列从任何数据源接收任何数据类型。FreeRTOS+TCP TCP/IP栈的实现提供了一个如何实现这一点的实例。
在自己的任务中运行的TCP/IP堆栈必须处理来自许多不同来源的事件。不同的事件类型与不同类型和长度的数据相关联。TCP/IP任务之外发生的所有事件都由IPStackEvent_t类型的结构描述,并发送到队列上的TCP/IP任务。IPStackEvent_t结构如清单55所示。IPStackEvent_t结构的pvData成员是一个指针,可用于直接保存值或指向缓冲区。

在这里插入图片描述

TCP/IP事件及其相关数据示例包括:
 eNetworkRxEvent:已从网络接收到数据包。
从网络接收到的数据使用IPStackEvent_t类型的结构发送到TCP/IP任务。该结构的eEventType成员设置为eNetworkRxEvent,该结构的pvData成员用于指向包含接收到数据的缓冲区。一个伪代码示例如清单56所示。
在这里插入图片描述

eTCPAcceptEvent:套接字用于接受或等待来自客户端的连接。
Accept事件从调用FreeRTOS_Accept()的任务发送到TCP/IP任务,使用IPStackEvent_t类型的结构。该结构的eEventType成员设置为eTCPAcceptEvent,该结构的pvData成员设置为接受连接的套接字的句柄。一个伪代码示例如清单57所示。

在这里插入图片描述eNetworkDownEvent:网络需要连接或重新连接。
网络关闭事件使用IPStackEvent_t类型的结构从网络接口发送到TCP/IP任务。该结构的eEventType成员设置为eNetworkDownEvent。网络故障事件与任何数据都没有关联,因此不使用结构的pvData成员。一个伪代码示例如清单58所示
在这里插入图片描述

在TCP/IP任务中接收和处理这些事件的代码如清单59所示。可以看出,从队列接收到的IPStackEvent_t结构的eEventType成员用于确定如何解释pvData成员。

在这里插入图片描述

从多个队列接收

队列集

通常,应用程序设计需要一个任务来接收不同大小的数据、不同含义的数据和来自不同来源的数据。上一节演示了如何使用接收结构的单个队列以整洁高效的方式实现这一点。
然而,有时应用程序的设计者会遇到限制其设计选择的约束,因此需要为某些数据源使用单独的队列。例如,集成到设计中的第三方代码可能会假设存在专用队列。在这种情况下,可以使用“队列集”。

队列集允许任务从多个队列接收数据,而无需任务依次轮询每个队列以确定哪些队列(如果有的话)包含数据。

与使用接收结构的单个队列实现相同功能的设计相比,使用队列集从多个源接收数据的设计不那么整洁,效率也较低。因此,建议仅在设计约束绝对必要时使用队列集。

以下部分描述了如何使用由设置的队列:
1.创建队列集。
2.向集合中添加队列。
信号量也可以添加到队列集中。
3.从队列集中读取以确定该集中的哪些队列包含数据。
当作为集合成员的队列接收数据时,接收队列的句柄被发送到队列集合,并在任务调用从队列集合读取的函数时返回。因此,如果从队列集中返回队列句柄,则该句柄引用的队列已知包含数据,然后任务可以直接从队列中读取。

通过在FreeRTOSConfig.h中将configUSE_Queue_SETS编译时配置常数设置为1,可以启用队列设置功能。

xQueueCreateSet()API函数
必须显式创建队列集才能使用。
队列集由句柄引用,句柄是QueueSetHandle_t类型的变量。xQueueCreateSet()API函数创建一个队列集并返回一个引用其创建的队列集的QueueSetHandler _t。

The xQueueCreateSet() API 函数原型

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );

uxEventQueueLength
当作为队列集成员的队列接收数据时,接收队列的句柄会被发送到队列集。uxEventQueueLength定义了创建的队列集在任何时候可以容纳的最大队列句柄数。
队列句柄仅在队列集内的队列接收数据时发送到队列集。如果队列已满,则无法接收数据,因此如果队列集中的所有队列都已满,就无法向队列集发送队列句柄。因此,队列集一次必须容纳的最大项目数是该集中每个队列的长度之和。

例如,如果集合中有三个空队列,并且每个队列的长度为5,那么在集合中的所有队列都已满之前,集合中的队列总共可以接收15个项目(3个队列乘以每个5个项目)。在该示例中,uxEventQueueLength必须设置为15,以确保队列集可以接收发送给它的每个项目。

信号量也可以添加到队列集中。本书稍后将介绍二进制和计数信号量。为了计算必要的uxEventQueueLength,二进制信号量的长度为1,计数信号量的大小由信号量的最大计数值给出。

再举一个例子,如果队列集包含一个长度为3的队列和一个二进制信号量(长度为1),则uxEventQueueLength必须设置为4(3加1)。

返回值
如果返回NULL,则无法创建队列集,因为FreeRTOS没有足够的堆内存来分配队列集数据结构和存储区域。
返回的非NULL值表示队列集已成功创建。返回的值应作为创建的队列集的句柄存储。

xQueueAddToSet()API函数
xQueueAddToSet()将队列或信号量添加到队列集中。

The xQueueAddToSet() API 函数原型

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet );

xQueueOrSemaphore
正在添加到队列集中的队列或信号量的句柄。
队列句柄和信号量句柄都可以转换为QueueSetMemberHandle_t类型。

xQueueSet
要添加队列或信号量的队列集的句柄。

返回值
有两种可能的返回值:
1.pdPASS
只有当队列或信号量成功添加到队列集中时,才会返回pdPASS。
2.pdFAIL
如果无法将队列或信号量添加到队列集中,则将返回pdFAIL。
队列和二进制信号量只有在为空时才能添加到集合中。计数信号量只能在计数为零时添加到集合中。队列和信号量一次只能是一个集合的成员。

xQueueSelectFromSet()API函数

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

当作为集合成员的队列或信号量接收到数据时,接收队列或信号的句柄会被发送到队列集合,并在任务调用xQueueSelectFromSet()时返回。如果从xQueueSelectFromSet()的调用返回句柄,则已知句柄引用的队列或信号量包含数据,调用任务必须直接从队列或信号中读取。

注意:除非队列或信号量的句柄首先从xQueueSelectFromSet()调用中返回,否则不要从属于集合的队列或信号中读取数据。每次调用xQueueSelectFromSet()返回队列句柄或信号量句柄时,只能从队列或信号量中读取一个项目。

The xQueueSelectFromSet() API 函数原型

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
const TickType_t xTicksToWait );

xQueueSet
队列集的句柄,从中接收(读取)队列句柄或信号量句柄。队列集句柄将从用于创建队列集的xQueueCreateSet()调用中返回。

xTicksToWait
如果队列集中的所有队列和信号量都为空,则调用任务应保持在Blocked状态以等待从队列集中接收队列或信号量句柄的最长时间。
如果xTicksToWait为零,则如果集合中的所有队列和信号量都为空,xQueueSelectFromSet()将立即返回。
块时间以滴答周期指定,因此它表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以刻度为单位的时间。
如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(不超时)。

返回值
非NULL的返回值将是已知包含数据的队列或信号量的句柄。如果指定了块时间(xTicksToWait不为零),则调用任务可能被置于“已阻塞”状态,以等待集合中的队列或信号量提供数据,但在块时间到期之前,已成功从队列集中读取句柄。句柄以QueueSetMemberHandle_t类型返回,该类型可以转换为QueueHandle_t或SemaphoreHandle_t。

如果返回值为NULL,则无法从队列集中读取句柄。如果指定了块时间(xTicksToWait不为零),则调用任务将被置于“已阻止”状态,以等待另一个任务或中断将数据发送到集合中的队列或信号量,但块时间在此之前已过期。

示例12。使用队列集
此示例创建了两个发送任务和一个接收任务。发送任务在两个单独的队列上向接收任务发送数据,每个任务一个队列。这两个队列被添加到一个队列集中,接收任务从队列集中读取数据,以确定两个队列中的哪一个包含数据。
任务、队列和队列集都是在main()中创建的——其实现见清单63。

在这里插入图片描述

第一个发送任务使用xQueue1每100毫秒向接收任务发送一个字符指针。第二个发送任务使用xQueue2每200毫秒向接收任务发送一个字符指针。字符指针被设置为指向标识发送任务的字符串。这两个发送任务的实现如清单64所示。

在这里插入图片描述

发送任务写入的队列是同一队列集的成员。
每次任务发送到其中一个队列时,队列的句柄都会发送到队列集。
接收任务调用xQueueSelectFromSet()从队列集中读取队列句柄。在接收任务从集合中接收到队列句柄后,它知道接收到的句柄引用的队列包含数据,因此直接从队列中读取数据。
它从队列读取的数据是一个指向字符串的指针,接收任如果对xQueueSelectFromSet()的调用超时,则将返回NULL。在示例12中,xQueueSelectFromSet()的调用具有不确定的块时间,因此永远不会超时,并且只能返回有效的队列句柄。因此,在使用返回值之前,接收任务不需要检查xQueueSelectFromSet()是否返回NULL。
xQueueSelectFromSet()只会在句柄引用的队列包含数据时返回队列句柄,因此从队列读取时不需要使用块时间。
接收任务的实现如清单65所示。

在这里插入图片描述
图37显示了示例12产生的输出。可以看出,接收任务从两个发送任务接收字符串。vSenderTask1()所使用的块时间是vSenderTack2()所用块时间的一半,这导致vSenderTak1()发送的字符串的打印频率是vSenderDask2()发送字符串的两倍。

在这里插入图片描述

队列集用例

示例12展示了一个非常简单的案例;队列集只包含队列,它包含的两个队列都用于发送字符指针。在实际应用程序中,队列集可能同时包含队列和信号量,并且队列可能并不都包含相同的数据类型。在这种情况下,在使用返回值之前,有必要测试xQueueSelectFromSet()返回的值。清单66演示了当集合具有以下成员时,如何使用xQueueSelectFromSet()返回的值:
1.二进制信号量。
2.从中读取字符指针的队列。
3.从中读取uint32_t值的队列。
清单66假设队列和信号量已经创建并添加到队列集中。

清单66。使用包含队列和信号量的队列集

/* The handle of the queue from which character pointers are received. */
QueueHandle_t xCharPointerQueue;
/* The handle of the queue from which uint32_t values are received. */
QueueHandle_t xUint32tQueue;
/* The handle of the binary semaphore. */
SemaphoreHandle_t xBinarySemaphore;
/* The queue set to which the two queues and the binary semaphore belong. */
QueueSetHandle_t xQueueSet;
void vAMoreRealisticReceiverTask( void *pvParameters )
{
QueueSetMemberHandle_t xHandle;
char *pcReceivedString;
uint32_t ulRecievedValue;
const TickType_t xDelay100ms = pdMS_TO_TICKS( 100 );
for( ;; )
{
/* Block on the queue set for a maximum of 100ms to wait for one of the members of
the set to contain data. */
xHandle = xQueueSelectFromSet( xQueueSet, xDelay100ms );
/* Test the value returned from xQueueSelectFromSet(). If the returned value is
NULL then the call to xQueueSelectFromSet() timed out. If the returned value is not
NULL then the returned value will be the handle of one of the set’s members. The
QueueSetMemberHandle_t value can be cast to either a QueueHandle_t or a
SemaphoreHandle_t. Whether an explicit cast is required depends on the compiler. */
if( xHandle == NULL )
{
/* The call to xQueueSelectFromSet() timed out. */
}
else if( xHandle == ( QueueSetMemberHandle_t ) xCharPointerQueue )
{
/* The call to xQueueSelectFromSet() returned the handle of the queue that
receives character pointers. Read from the queue. The queue is known to contain
data, so a block time of 0 is used. */
xQueueReceive( xCharPointerQueue, &pcReceivedString, 0 );
/* The received character pointer can be processed here... */
}
else if( xHandle == ( QueueSetMemberHandle_t ) xUint32tQueue )
{
/* The call to xQueueSelectFromSet() returned the handle of the queue that
receives uint32_t types. Read from the queue. The queue is known to contain
data, so a block time of 0 is used. */
xQueueReceive(xUint32tQueue, &ulRecievedValue, 0 );
/* The received value can be processed here... */
}
Else if( xHandle == ( QueueSetMemberHandle_t ) xBinarySemaphore )
{
/* The call to xQueueSelectFromSet() returned the handle of the binary semaphore.
Take the semaphore now. The semaphore is known to be available so a block time
of 0 is used. */
xSemaphoreTake( xBinarySemaphore, 0 );
/* Whatever processing is necessary when the semaphore is taken can be performed
here... */
}
}
}

使用队列创建一个邮箱

嵌入式社区内对术语没有达成共识,“邮箱”在不同的RTOS中意味着不同的东西。在本书中,术语邮箱用于指长度为1的队列。队列可能被描述为邮箱,因为它在应用程序中的使用方式,而不是因为它与队列在功能上有区别:
 队列用于将数据从一个任务发送到另一个任务,或从中断服务例程发送到任务。发送方将一个项目放入队列,接收方将该项目从队列中删除。数据通过队列从发送方传递到接收方。
 邮箱用于保存任何任务或任何中断服务例程都可以读取的数据。数据不会通过邮箱,而是保留在邮箱中,直到被覆盖。发件人会覆盖邮箱中的值。接收方从邮箱中读取值,但不会从邮箱中删除该值。

本章介绍了允许将队列用作邮箱的两个队列API函数。

清单67显示了创建的用作邮箱的队列。

在这里插入图片描述

xQueueOverwrite()API函数
与xQueueSendToBack()API函数一样,xQueueOverwrite()API函数将数据发送到队列。与xQueueSendToBack()不同,如果队列已满,则xQueueOverwrite()将覆盖队列中已有的数据。
xQueueOverwrite()只能用于长度为1的队列。这种限制避免了在队列已满的情况下,函数的实现需要任意决定覆盖队列中的哪个项目。
注意:切勿从中断服务例程调用xQueueOverwrite()。应使用中断安全版本xQueueOverwriteFromISR()来代替它

The xQueueOverwrite() API 函数原型

BaseType_t xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue );

xQueue
数据被发送(写入)到的队列的句柄。队列句柄将从用于创建队列的xQueueCreate()调用中返回。

pvItemToQueue
指向要复制到队列中的数据的指针。
队列可以容纳的每个项目的大小是在创建队列时设置的,因此这许多字节将从pvItemToQueue复制到队列存储区域。

返回值
xQueueOverwrite()即使队列已满,也会写入队列,因此pdPASS是唯一可能的返回值。

清单69显示了xQueueOverwrite()用于写入清单67中创建的邮箱(队列)。

在这里插入图片描述

xQueuePeek()API函数

xQueuePeek()用于从队列中接收(读取)一个项目,而不从队列中删除该项目。xQueuePeek()从队列头部接收数据,而不修改存储在队列中的数据或数据在队列中存储的顺序。
注意:切勿从中断服务例程调用xQueuePeek()。应使用中断安全版本xQueuePeekFromISR()来代替它。

xQueuePeek()与xQueueReceive()具有相同的函数参数和返回值。

xQueuePeek() API函数原型

BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );

清单71显示了xQueuePeek()用于接收发布到清单69中邮箱(队列)的项目。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2218101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

游戏逆向基础-跳出游戏线程发包

附加游戏后下断点 bp ws2_32.send send函数断下后,可以看到数据地址每次都一样 可以说明这个游戏是线程发包,所以直接在数据窗口中转到这个地址,然后对这个地址下硬件写入断点。 下了硬件写入断点后可以一层一层往上面跟,确定写…

RHCE--at,crontab例行性工作

一:安装at (1)配置yum仓库:以配置网络源举例: 先在/etc/yum.repos.d/ 目录下创建一个以.repo结尾的文件 vim /etc/yum.repos.d/aliyun.repo 写入可以在阿里云镜像站查找appstream和baseos的地址阿里巴巴开源镜像站…

tensorflow案例2--猴痘病识别,一道激活函数的bug

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 文章目录 1、bug2、模型构建1、数据处理1、导入库2、查看数据目录3、加载数据4、数据展示 2、内存优化3、模型构建4、模型训练1、超参数设置2、模型训练 5、结…

通过前端UI界面创建VUE项目

通过前端UI界面创建VUE项目,是比较方面的一种方式,下面我们详细分析一下流程: 1、找到合适目录 右键鼠标,点击在终端打开 2、开始创建 输入 vue ui 浏览器弹出页面 3、点击Create项目 显示已有文件列表,另外可以点击…

Docker部署一款小巧又强大的的自托管网站监控工具Uptime Kuma

文章目录 前言1.关于Uptime Kuma2.安装Docker3.本地部署Uptime Kuma4.使用Uptime Kuma5.cpolar内网穿透工具安装6.创建远程连接公网地址7.固定Uptime Kuma公网地址 💡 推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默&#…

CVE-2024-36971漏洞修复----Debian 10.13 内核升级

CVE-2024-36971漏洞修复---Debian 10.13 内核升级 1. 下载内核2. 安装依赖包3. 二进制安装3.1 上传3.2 解压3.3 修改配置文件3.4 编译3.5 安装内核及模块 4. 重启服务器并确认升级成功 1. 下载内核 到kernel.org下载新版的Kernel 由于开发那边不想让Kernel跨大版本,所以就升级…

【优选算法】——双指针(上篇)!

🌈个人主页:秋风起,再归来~🔥系列专栏:C刷题算法总结🔖克心守己,律己则安 目录 前言:双指针 1. 移动零(easy) 2. 复写零(easy) 3…

VSCode C/C++跳转到定义、自动补全、悬停提示突然失效

昨天像往常一样用vscode连接云服务器写代码,突然发现跳转到定义、自动补全、悬停提示功能全部不能正常使用了,今天折腾了一上午,看了一大堆教程,最后可算是解决了,因为大家说不定会遇到和我一样的问题,所以…

【工具篇】MLU运行XInference部署手册

文章目录 前言一、平台环境准备二、代码下载三、安装部署1.正常pip 安装 四、运行结果展示1.如果界面404或没有东西请这样做2.运行效果 前言 Xorbits Inference(Xinference)是一个功能强大、用途广泛的库,旨在为语言、语音识别和多模态模型提…

自监督学习:引领机器学习的新革命

引言 自监督学习(Self-Supervised Learning)近年来在机器学习领域取得了显著进展,成为人工智能研究的热门话题。不同于传统的监督学习和无监督学习,自监督学习通过利用未标注数据生成标签,从而大幅降低对人工标注数据…

数据库-01MYSQL-001MySQL知识点查漏补缺

MySQL知识点查漏补缺 数据库常识不常见知识点: 数据库常识 知识点001: between…and … 包含临界值。 知识点002:任何内容与null相加等于null。 知识点003:模糊查询涉及的函数有:like,between…and…, in/…

机器的“眼睛“:计算机视觉技术背后的魔法

计算机视觉,作为人工智能领域中的一颗璀璨明珠,正逐步改变着我们的生活方式。它赋予了机器“看”的能力,使得计算机能够从图像和视频中提取信息并进行分析,就像人类用眼睛和大脑来理解世界一样。本文将带你走进计算机视觉的世界&a…

解决linux服务器磁盘占满问题(详细,有效,100%解决)

应用场景: 在我们的日常开发中,我们的服务器总是在不知不觉中磁盘莫名奇妙少了很多空间,或者被占满了,如果这时候要想要存储什么文件,突然发现空间不够了。但我们通常也不知道那些文件占用的空间大,这时候…

ANSYS Workbench纤维混凝土3D

在ANSYS Workbench建立三维纤维混凝土模型可采用CAD随机几何3D插件建模后导入,模型包含球体粗骨料、圆柱体长纤维、水泥砂浆基体等不同组分。 在CAD随机几何3D插件内设置模型参数后运行,即可在AutoCAD内建立三维纤维混凝土模型,插件支持任意…

牛客习题—线性DP 【mari和shiny】C++

你好,欢迎阅读我的文章~ 个人主页:Mike 所属专栏:动态规划 mari和shiny mari和shiny ​ 分析: 使用动态规划的思路来解决。 思路: 分别统计s,sh,shy的数量即可。使用ss来统计字符s的数量,使…

LC1523.在区间范围内统计奇数数目

一开始没审题,居然构造了一个数组去做… 然后重新看,首先先想到的暴力解就是遍历low到high,然后每一个数都对二取余。但是这样的暴力解就没什么锻炼 那肯定再想一个思路,Low和high都有两种情况,要么是奇数&#xff0c…

30.第二阶段x86游戏实战2-遍历周围-C++遍历二叉树(玩家角色基址)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 本人写的内容纯属胡编乱造,全都是合成造假,仅仅只是为了娱乐,请不要…

衡石分析平台系统分析人员手册-应用查看

应用查看​ 应用创作界面展示了用户可以查看的所有应用。 用户可以使用平铺视图或列表视图查看应用。同时支持通过搜索、过滤、排序等方式快速查找应用。 应用视图​ 应用创作支持平铺视图和列表视图两种展示方式,默认以平铺视图的方式展示应用,用户可…

2024 蚂蚁SEO蜘蛛池对网站收录的帮助

《2024 蜘蛛池对网站收录还有效果吗?》 在网站优化的领域中,蜘蛛池曾经是一个备受关注的工具。然而,随着搜索引擎算法的不断演进,人们对于 2024 年蜘蛛池对网站收录是否还有效果产生了疑问。 一、什么是蜘蛛池? 蜘蛛池…

APQP在制造行业的应用:搭上数字化项目管理平台很nice

APQP(Advanced Product Quality Planning,即产品质量先期策划)最早由汽车行业引入,并因其在质量管理方面的显著效果而逐渐被其他制造业领域所采纳。 APQP提供了一种从产品设计的最初阶段到生产过程的全面质量管理框架,…