您的位置:首页 > 汽车 > 时评 > 找回老网站_重庆vr全景制作_营销推广费用预算表_兰州怎么提高网站的排名

找回老网站_重庆vr全景制作_营销推广费用预算表_兰州怎么提高网站的排名

2024/10/20 20:58:08 来源:https://blog.csdn.net/ptw1992/article/details/142984102  浏览:    关键词:找回老网站_重庆vr全景制作_营销推广费用预算表_兰州怎么提高网站的排名
找回老网站_重庆vr全景制作_营销推广费用预算表_兰州怎么提高网站的排名

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

队列特性

数据存储
队列可以容纳有限数量的固定大小的数据项。队列可以容纳的最大项目数称为其“长度”。每个数据项的长度和大小都是在创建队列时设置的。
队列通常用作先进先出(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中邮箱(队列)的项目。
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com