目录

    • 测试环境如下
    • 概述
    • 队列基本知识
    • 多任务访问-读(多写单读)
    • 阻塞任务读取队列数据
    • 多任务访问-写
  • 单队列使用
      • 创建一个队列-xQueueCreate()
      • 复位一个队列-xQueueReset()
      • 发送尾部数据xQueueSendToBack()
      • 发送头部数据xQueueSendToFront()
      • 参数说明
      • 注意不要再中断的时候使用以上函数
      • 队列数据接收 -xQueueReceive()
      • uxQueueMessagesWaiting()
    • 案例测试 - 单任务发松数据(接收比发送优先级高)
    • 案例测试 - 多任务发送数据(接收比发送优先级低)
    • 队列数据较大时使用原则
    • 队列为指向数组指针案例
    • TCP/IP数据接收和发送(stm32不支持跳过)
  • 多队列使用说明
    • xQueueCreateSet()创建队列集合
    • xQueueAddToSet 队列或信号量添加到队列集中
    • xQueueSelectFromSet()选择读取队列
    • 使用队列集(多队列使用案例)
  • 使用邮箱
    • xQueueOverwrite()
    • xQueuePeek()

测试环境如下

测试环境如下
stm32F103C8T6
MDK keil5
stm32cube + FreeRTOS

概述

  • 如何创建队列。
  • 队列如何管理它包含的数据。
  • 如何向队列发送数据。
  • 如何从队列接收数据。
  • 在队列上阻塞意味着什么。
  • 如何阻塞多个队列。
  • 如何覆盖队列中的数据。
  • 如何清除队列。
  • 写入队列和从队列读取时任务优先级的影响。

队列基本知识

队列通常用作先进先出(FIFO)缓冲区
创建队列以允许任务A和任务B通信。 队列最多可容纳5个整数。
创建队列时,它不包含任何值,因此是空的。
发送一个数据时

发送两个数据时

接收一个数据

这里数据会将先前的数据再填充到尾部。
两种方式使用队列

  • 拷贝队列
  • 引用队列

FreeRTOS一般建议使用拷贝队列比引用更好,因为

  • 堆栈变量可以直接发送到队列,即使该变量在声明它的函数退出后不存在
  • 数据可以发送到队列,而不需要首先分配缓冲区来保存数据,然后将数据复制到分配的缓冲区中。
  • 发送任务可以立即重用发送到队列的变量或缓冲区
  • 发送任务和接收任务是完全解耦的-应用程序设计器不需要关心哪个任务‘拥有’数据,或者哪个任务负责释放数据。
  • 按副本排队不会阻止队列也被用于引用队列。 例如,当正在排队的数据的大小使将数据复制到队列中变得不切实际时,则可以将指向数据的指针复制到队列中。
  • RTOS完全负责分配用于存储数据的内存
  • 在内存保护系统中,任务可以访问的RAM将受到限制。 在这种情况下,只有当发送和接收任务都可以访问存储数据的RAM时,才能使用引用队列。 按副本排队不会施加这种限制;内核总是以完全特权运行,允许队列用于通过内存保护边界传递数据。

多任务访问-读(多写单读)

队列是可以被任何任务或知道它们存在的ISR访问的自身对象。 任意数量的任务都可以写入同一个队列,任意数量的任务都可以从同一个队列中读取。 在实践中,队列有多个写者是非常常见的,但队列有多个读者则不那么常见

阻塞任务读取队列数据

当任务试图从队列读取时,它可以选择指定“块”时间如果队列已经为空,则任务将保持在阻塞状态,以等待队列中的数据可用。 处于阻塞状态的任务,等待数据从队列中可用,当另一个任务或中断将数据放入队列时,将自动移动到就绪状态。 如果指定的块时间在数据可用之前过期,任务也将自动从阻塞状态移动到就绪状态。

队列可以有多个读取器,因此单个队列有可能有多个任务被阻塞,等待数据。 在这种情况下,当数据可用时,只有一个任务将被解除阻塞。 畅通的任务将始终是等待数据的最高优先级任务。 如果阻塞的任务具有同等的优先级,那么等待数据时间最长的任务将被解除阻塞。

总结就是有两个阻塞任务优先级一样,谁时间等的长谁就先从阻塞到就绪状态。

多任务访问-写

就像从队列中读取一样,任务在写入队列时可以选择指定块时间。 在这种情况下,阻塞时间是任务应该保持在阻塞状态以等待队列上可用的空间的最大时间,如果队列已经满了。

队列可以有多个编写器,因此一个完整的队列有可能有多个任务被阻塞,等待完成发送操作。 在这种情况下,当队列上的空间可用时,只会解除一个任务。 畅通的任务将永远是等待空间的最高优先级任务。 如果阻塞的任务具有同等的优先级,那么等待空间最长的任务将被解除阻塞

单队列使用

创建一个队列-xQueueCreate()

QueueHandle_t类型的变量 这边是void *空指针类型

  • uxQueueLength
    正在创建的队列可以在任何时候保存的最大项数。
  • uxItemSize
    可以存储在队列中的每个数据项的字节大小。

复位一个队列-xQueueReset()

发送尾部数据xQueueSendToBack()

BaseType_t xQueueSendToBack( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );

xQueueSend() == xQueueSendToBack()

发送头部数据xQueueSendToFront()

BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );

参数说明

  • xQueue
    数据被发送到的队列的句柄(写入)。 队列句柄将从调用返回到用于创建队列的xQueue Create()。
  • pvItemToQueue
    指向要复制到队列中的数据的指针。 当创建队列时,将设置队列可以容纳的每个项的大小,因此将从pv Item到队列的许多字节复制到队列存储区域。
  • xTicksToWait
    如果队列已经满了,任务应该保持在阻塞状态以等待空间在队列上可用的最大时间。
    xQueueSendToFront() and xQueueSendToBack()将会马上返回,如果 xTicksToWait = 0,说明队列已经满了。
  • Returned value 1. pdPASS ,2. errQUEUE_FUL

注意不要再中断的时候使用以上函数

队列数据接收 -xQueueReceive()

队列接收()用于从队列接收(读取)项。 接收到的项从队列中删除


参数如下

  • xQueue
    接收数据的队列的句柄(读取)。 队列句柄将从调用返回到用于创建队列的xQueue Create()。
  • pvBuffer
    指向将复制接收到的数据的内存的指针。 在创建队列时设置队列保存的每个数据项的大小。 pv Buffer指向的内存必须至少足够大,足以容纳那么多字节

如果队列已为空,则任务应保持在阻塞状态以等待数据在队列上可用的最大时间

uxQueueMessagesWaiting()

查询当前队列中的项数。

注意:不要从中断服务例程调用等待()的uxQueueMessagesWaiting()队列消息。 从ISR()等待的中断安全UX队列消息应该在其位置使用。

UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue )
{UBaseType_t uxReturn;configASSERT( xQueue );taskENTER_CRITICAL();{uxReturn = ( ( Queue_t * ) xQueue )->uxMessagesWaiting;}taskEXIT_CRITICAL();return uxReturn;
} /*lint !e818 Pointer cannot be declared const as xQueue is a typedef not pointer. */
/*-----------------------------------------------------------*/

参数说明

  • xQueue
    正在查询的队列的句柄。 队列句柄将从调用返回到用于创建队列的xQueue Create()。
  • 返回值uxReturn:正在查询的队列当前持有的项数。 如果返回零,则队列为空

案例测试 - 单任务发松数据(接收比发送优先级高)

此示例演示正在创建队列、从多个任务发送数据到队列以及从队列接收数据。 创建队列是为了保存int32_t类型的数据项。 发送到队列的任务不指定块时间,而从队列接收的任务则指定块时间

发送到队列的任务的优先级低于从队列接收的任务的优先级。 这意味着队列不应该包含多个项,因为一旦数据被发送到队列,接收任务就会解除阻塞,抢占发送任务,并删除数据-再次将队列清空。

显示写入队列的任务的实现。 创建此任务的两个实例,一个连续地将值100写入队列,另一个连续地将值200写入同一队列。 任务参数用于将这些值传递到每个任务实例中。

  osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128);Task_LED1Handle = osThreadCreate(osThread(Task_LED1), NULL);/* definition and creation of Task_LED2 */osThreadDef(Task_LED2, Func_LED2, osPriorityIdle, 0, 128);Task_LED2Handle = osThreadCreate(osThread(Task_LED2), NULL);/* definition and creation of Task_Queuesend */osThreadDef(Task_Queuesend, vSenderTask, osPriorityIdle, 0, 128);Task_QueuesendHandle = osThreadCreate(osThread(Task_Queuesend), (void *)100);osThreadDef(Task_Queuesend1, vSenderTask, osPriorityIdle, 0, 128);Task_QueuesendHandle = osThreadCreate(osThread(Task_Queuesend1), (void *)200);/* definition and creation of Task_QueueRecei */osThreadDef(Task_QueueRecei, vReceiverTask, osPriorityNormal, 0, 128);Task_QueueReceiHandle = osThreadCreate(osThread(Task_QueueRecei), NULL);
void vSenderTask(void const * argument)
{/* USER CODE BEGIN vSenderTask */int32_t lValueToSend;static  char *string = "Could not send to the queue.\r\n";BaseType_t xStatus;lValueToSend = ( int32_t )argument;for(;;){xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );if(xStatus !=pdPASS)printf("Could not send to the queue\r\n");//osDelay(100);}/* USER CODE END vSenderTask */
}
void vReceiverTask(void const * argument)
{/* USER CODE BEGIN vReceiverTask */int32_t lReceivedValue;BaseType_t xStatus;const TickType_t xTicksToWait = pdMS_TO_TICKS( 10 );/* Infinite loop */for(;;){if( uxQueueMessagesWaiting( xQueue ) != 0 ){printf("Queue should have been empty!\r\n");}xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );if( xStatus == pdPASS ){printf("%d\r\n",lReceivedValue);}else{printf("Could not receive from the queue\r\n");}}/* USER CODE END vReceiverTask */
}

实验现象为

案例解析:

  • 接收器任务首先运行,因为它具有最高优先级。 它试图从队列中读取。 队列为空,因此接收器进入阻塞状态以等待数据可用。 发送器2在接收器阻塞后运行。
  • 发送器2写入队列,导致接收器退出阻塞状态。 接收器有最高的优先级,所以预选发送者2。
  • 接收器任务清空队列,然后再次进入阻塞状态。 这一次发送器1运行后,接收器已经阻塞。
  • -发送器1写入队列,导致接收方退出阻塞状态,并预先阻止发送器1-然后继续执行
  • 循环。。。。

案例测试 - 多任务发送数据(接收比发送优先级低)

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

  • 创建一个队列,它保存Data_t类型的结构。 结构成员既允许数据值,也允许枚举类型,指示在一条消息中向队列发送数据意味着什么。
  • 中央控制器任务用于执行主系统功能。 这必须对队列上传递给它的系统状态的输入和更改作出反应
  • CAN总线任务用于封装CAN总线接口功能。 当CAN总线任务接收并解码消息时,它以Data_t结构向Controller任务发送已经解码的消息。 传输结构的eDataID成员用于让Controller任务知道数据是什么-在所描述的情况下,它是一个电机速度值。 传输结构的eDataID成员用于让控制器任务知道实际的电机速度值
  • 人机界面(HMI)任务用于封装所有HMI功能。 机器操作员可能可以输入许多命令和查询值必须在人机界面任务中检测和解释的方式。 当输入新命令时,HMI任务以Data_t结构向Controller任务发送命令。 传输结构的eDataID成员用于让Controller任务知道数据是什么-在所描述的情况下,它是一个新的设置点值。 传输结构的lDataValue成员用于让Controller任务知道实际的设置点值

设置接收任务优先级低于发送任务优先级

上面的案例接收任务具有最高优先级,因此队列从不包含多个项。 这来自接收任务在数据被放入队列时预先阻止发送任务。 在示例11中,发送任务具有更高的优先级,因此队列通常会满。 这是因为,一旦接收任务从队列中删除一个项,它就会被一个发送任务预先加密,然后立即重新填充队列。 然后,发送任务重新进入阻塞状态,等待空间再次在队列中可用。

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

案例如下:

QueueHandle_t xQueue;
typedef enum
{eSender1,eSender2
} DataSource_t;
/* Define the structure type that will be passed on the queue. */
typedef struct
{uint8_t ucValue;DataSource_t eDataSource;
} Data_t;
/* Declare two variables of type Data_t that will be passed on the queue. */
static const Data_t xStructsToSend[ 2 ] =
{{ 100, eSender1 }, /* Used by Sender1. */{ 200, eSender2 } /* Used by Sender2. */
};xQueue = xQueueCreate( 3, sizeof( Data_t) );/* add queues, ... *//* USER CODE END RTOS_QUEUES *//* Create the thread(s) *//* definition and creation of Task_LED1 */osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128);Task_LED1Handle = osThreadCreate(osThread(Task_LED1), NULL);/* definition and creation of Task_LED2 */osThreadDef(Task_LED2, Func_LED2, osPriorityIdle, 0, 128);Task_LED2Handle = osThreadCreate(osThread(Task_LED2), NULL);/* definition and creation of Task_Queuesend */osThreadDef(Task_Queuesend, vSenderTask, osPriorityNormal, 0, 128);Task_QueuesendHandle = osThreadCreate(osThread(Task_Queuesend), (void *)&xStructsToSend[0]);osThreadDef(Task_Queuesend1, vSenderTask, osPriorityNormal, 0, 128);Task_QueuesendHandle = osThreadCreate(osThread(Task_Queuesend1), (void *)&xStructsToSend[1]);/* definition and creation of Task_QueueRecei */osThreadDef(Task_QueueRecei, vReceiverTask, osPriorityIdle, 0, 128);Task_QueueReceiHandle = osThreadCreate(osThread(Task_QueueRecei), NULL);
/* USER CODE END Header_vSenderTask */
void vSenderTask(void const * argument)
{/* USER CODE BEGIN vSenderTask */BaseType_t xStatus;const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );for(;;){xStatus = xQueueSendToBack( xQueue, argument, xTicksToWait );if(xStatus !=pdPASS)printf("Could not send to the queue\r\n");}/* USER CODE END vSenderTask */
}/* USER CODE BEGIN Header_vReceiverTask */
/**
* @brief Function implementing the Task_QueueRecei thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_vReceiverTask */
void vReceiverTask(void const * argument)
{/* USER CODE BEGIN vReceiverTask */Data_t xReceivedStructure;BaseType_t xStatus;const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );/* Infinite loop */for(;;){if( uxQueueMessagesWaiting( xQueue) != 3 ){printf( "Queue should have been full!\r\n" );}xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );if( xStatus == pdPASS ){/* Data was successfully received from the queue, print out the receivedvalue and the source of the value. */if( xReceivedStructure.eDataSource == eSender1 ){printf( "From Sender 1 = %d \r\n", xReceivedStructure.ucValue );}else{printf( "From Sender 2 = %d \r\n", xReceivedStructure.ucValue );}}else{printf( "Could not receive from the queue.\r\n" );}}/* USER CODE END vReceiverTask */
}

演示由于发送任务的优先级高于接收任务的优先级而导致的执行顺序。 表22提供了图36的进一步解释,并描述了为什么前四条消息来自同一任务。


发送任务一 = 发送任务二 优先级一致

接收任务优先级 < 发送优先级

详细解释如下

  • 任务发件人1执行并向队列发送3个数据项。
  • 队列已满,所以Sender1进入阻塞状态,等待下一次发送完成。 任务发送器2现在是能够运行的最高优先级任务,因此进入运行状态。
  • 队列已满,所以Sender1进入阻塞状态,等待下一次发送完成。 任务发送器2现在是能够运行的最高优先级任务,因此进入运行状态。
  • 任务发件人2发现队列已经满了,所以进入阻塞状态等待其第一次发送完成。 任务接收器现在是能够运行的最高优先级任务,因此进入运行状态。
  • 两个优先级高于接收任务优先级的任务正在等待队列上的空间可用,导致任务接收器在从队列中删除一个项后立即被预先加密。 任务发送器1和发送器2具有相同的优先级,因此调度器选择等待时间最长的任务作为将进入运行状态的任务-在本例中是任务发送器1。
  • 任务发送器1将另一个数据项发送到队列。 队列中只有一个空间,因此任务发送器1进入阻塞状态,等待下一次发送完成。 任务接收器再次是能够运行的最高优先级任务,因此进入运行状态。
    任务发件人1现在已经向队列发送了四个项目,而任务发件人2仍在等待将其第一个项目发送到队列
  • 有两个优先级高于接收任务优先级的任务正在等待队列上的空间可用,因此任务接收器一旦从队列中删除了一个项,就会被加密。 这一次Sender2已经比Sender1等待更长时间,因此Sender2进入运行状态
  • 任务发送器2向队列发送数据项。 队列中只有一个空格,所以Sender2进入阻塞状态,等待下一次发送完成。 两个任务Sender1和Sender2都在等待队列上的空间可用,因此任务接收器是唯一可以进入运行的任务








队列数据较大时使用原则

  • 在处理时间和创建队列所需的RAM量方面,传递指针更有效。 然而,在排队指针时,必须极其小心地确保:
  • 确保两个任务不同时修改内存内容,或者采取任何其他可能导致内存内容无效或不一致的操作。 理想情况下,应该只允许发送任务访问内存,直到一个指向内存的指针已经排队,并且应该只允许接收任务在从队列接收到指针之后访问内存。
  • 如果指向的内存是动态分配的,或者从预先分配的缓冲区池中获得的,那么确切地说,一个任务应该负责释放内存。 任何任务都不应试图在内存被释放后访问它。 指针不应用于访问在任务栈上分配的数据。 堆栈帧更改后,数据将无效。

队列为指向数组指针案例

  • 创建一个最多可容纳5个指针的队列
  • 分配缓冲区,将字符串写入缓冲区,然后将指针发送到缓冲区到队列。
  • 从队列接收到指向缓冲区的指针,然后打印缓冲区中包含的字符串。

案例如下

QueueHandle_t xPointerQueue;xPointerQueue = xQueueCreate( 5, sizeof( char * ) );/* add queues, ... *//* USER CODE END RTOS_QUEUES *//* Create the thread(s) *//* definition and creation of Task_LED1 */osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128);Task_LED1Handle = osThreadCreate(osThread(Task_LED1), NULL);/* definition and creation of Task_LED2 */osThreadDef(Task_LED2, Func_LED2, osPriorityIdle, 0, 128);Task_LED2Handle = osThreadCreate(osThread(Task_LED2), NULL);/* definition and creation of Task_Queuesend */osThreadDef(Task_Queuesend, vSenderTask, osPriorityNormal, 0, 128);Task_QueuesendHandle = osThreadCreate(osThread(Task_Queuesend), NULL);/* definition and creation of Task_QueueRecei */osThreadDef(Task_QueueRecei, vReceiverTask, osPriorityIdle, 0, 128);Task_QueueReceiHandle = osThreadCreate(osThread(Task_QueueRecei), NULL);
void Func_LED2(void const * argument)
{/* USER CODE BEGIN Func_LED2 *//* Infinite loop */for(;;){osDelay(200);HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_6);}/* USER CODE END Func_LED2 */
}/* USER CODE BEGIN Header_vSenderTask */
/**
* @brief Function implementing the Task_Queuesend thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_vSenderTask */
void vSenderTask(void const * argument)
{/* USER CODE BEGIN vSenderTask */BaseType_t xStatus;char *pcStringToSend = "1111111";BaseType_t xStringNumber = 0;for(;;){xStatus = xQueueSend( xPointerQueue, &pcStringToSend, portMAX_DELAY );if(xStatus !=pdPASS)printf("Could not send to the queue\r\n");}/* USER CODE END vSenderTask */
}

TCP/IP数据接收和发送(stm32不支持跳过)

运行在自己任务中的TCP/IP堆栈必须处理来自许多不同来源的事件。 不同的事件类型与不同类型和长度的数据相关联。 在TCP/IP任务之外发生的所有事件都由IPStackEvent_t类型的结构描述,并在队列中发送到TCP/IP任务。 IPStackEvent_t结构如清单55所示。 IPStackEvent_t结构的pv Data成员是一个指针,可以用来直接保存一个值,或者指向缓冲区。

多队列使用说明

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

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

使用队列集接收来自多个源的数据的设计比使用接收结构的单个队列实现相同功能的设计更不整洁、效率更低。 因此,建议只在设计约束使其使用绝对必要时才使用队列集

原则如下:

  • 创建队列集
  • 向集合添加队列。 信号量也可以添加到队列集中。 这本书后面将介绍信号量。
  • 从队列集中读取,以确定集合中的哪些队列包含数据。 当作为集合成员的队列接收数据时,接收队列的句柄被发送到队列集合,当任务调用从队列集合读取的函数时返回。 因此,如果从队列集返回队列句柄,则已知句柄引用的队列包含数据,然后任务可以直接从队列读取。
  • 注意:如果队列是队列集的成员,则不要从队列中读取数据,除非队列的句柄已首先从队列集读取。
  • 在Free RTOSConfig.h中,通过将configUSE_QUEUE_SETS编译时间配置常量设置为1来启用队列集功能。

xQueueCreateSet()创建队列集合

configUSE_QUEUE_SETS == 1 ) &&
( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
需要这个两个宏都等于1

参数说明

  • uxEventQueueLength
  • 当队列集的成员队列接收数据时,接收队列的句柄将被发送到队列集uxEventQueueLength 队列长度定义了正在创建的队列集可以在任何一次保持的队列处理的最大数量。
  • 队列句柄只在集合中的队列接收数据时发送到队列集合。 队列如果已满,则无法接收数据,因此,如果集合中的所有队列都已满,则没有队列句柄可以发送到队列集合。 因此,队列集一次必须持有的最大项数是集合中每个队列长度的总和
  • 例如,如果集合中有三个空队列,并且每个队列的长度为5,那么在集合中的队列总共可以接收十五个项(三个队列乘以五个项),然后集合中的所有队列都满了。 在该示例中,uxEventQueueLength 队列长度必须设置为15,以保证队列集可以接收发送给它的每个项。
  • 信号量也可以添加到队列集中。 二进制和计数信号量将在本书后面介绍。 为了计算必要的UX事件队列长度,二进制信号量的长度为1,计数信号量的长度由信号量的最大计数值给出
  • 作为另一个例子,如果队列集包含长度为3的队列和二进制信号量(长度为1),则UX事件队列长度必须设置为4(3加1)。

xQueueAddToSet 队列或信号量添加到队列集中

宏configUSE_QUEUE_SETS == 1

参数说明

  • xQueueOrSemaphore
    队列或信号量的句柄,该句柄被添加到队列集中。 队列句柄和信号量句柄都可以转换为QueueSetMemberHandle_t类型。
  • xQueueSet
    队列或信号量被添加到的队列集合的句柄

xQueueSelectFromSet()选择读取队列

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

注意:不要从队列或信号量中读取数据,因为队列或信号量是集合的成员,除非队列或信号量的句柄首先从调用返回到xQueueue Select From Set()。 每次队列句柄或信号量句柄从调用返回到xQueueue Select From Set()时,只从队列或信号量读取一个项

configUSE_QUEUE_SETS == 1


参数说明

  • xQueueSet
    接收队列句柄或信号量句柄的队列集合的句柄(读)。 队列集句柄将从调用返回到xQueue Create Set()用于创建队列集。
  • xTicksToWait 等待时间

使用队列集(多队列使用案例)

此示例创建两个发送任务和一个接收任务。 发送任务在两个单独的队列上向接收任务发送数据,每个任务一个队列。 将两个队列添加到队列集中,接收任务从队列集中读取,以确定两个队列中哪一个包含数据。

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

基础案例如下

下面展示一些 内联代码片

 static QueueHandle_t xQueue1 = NULL, xQueue2 = NULL;static QueueSetHandle_t xQueueSet = NULL;xQueue1 = xQueueCreate( 1, sizeof( char * ) );xQueue2 = xQueueCreate( 1, sizeof( char * ) );xQueueSet = xQueueCreateSet( 1 * 2 );xQueueAddToSet( xQueue1, xQueueSet );xQueueAddToSet( xQueue2, xQueueSet );/* add queues, ... *//* USER CODE END RTOS_QUEUES *//* Create the thread(s) *//* definition and creation of Task_LED1 */osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128);Task_LED1Handle = osThreadCreate(osThread(Task_LED1), NULL);/* definition and creation of Task_LED2 */osThreadDef(Task_LED2, Func_LED2, osPriorityIdle, 0, 128);Task_LED2Handle = osThreadCreate(osThread(Task_LED2), NULL);/* definition and creation of Task_Queuesend1 */osThreadDef(Task_Queuesend1, vSenderTask1, osPriorityIdle, 0, 128);Task_Queuesend1Handle = osThreadCreate(osThread(Task_Queuesend1), NULL);/* definition and creation of Task_Queuesend2 */osThreadDef(Task_Queuesend2, vSenderTask2, osPriorityLow, 0, 128);Task_Queuesend2Handle = osThreadCreate(osThread(Task_Queuesend2), NULL);/* definition and creation of Task_QueueRecei */osThreadDef(Task_QueueRecei, vReceiverTask, osPriorityIdle, 0, 128);Task_QueueReceiHandle = osThreadCreate(osThread(Task_QueueRecei), NULL);
void vSenderTask1(void const * argument)
{/* USER CODE BEGIN vSenderTask1 */const TickType_t xBlockTime = pdMS_TO_TICKS( 100 );const char * const pcMessage = "Message from vSenderTask1\r\n";/* Infinite loop */for(;;){vTaskDelay( xBlockTime );xQueueSend( xQueue1, &pcMessage, 0 );}/* USER CODE END vSenderTask1 */
}/* USER CODE BEGIN Header_vSenderTask2 */
/**
* @brief Function implementing the Task_Queuesend2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_vSenderTask2 */
void vSenderTask2(void const * argument)
{/* USER CODE BEGIN vSenderTask2 */const TickType_t xBlockTime = pdMS_TO_TICKS( 200 );const char * const pcMessage = "Message from vSenderTask2\r\n";/* Infinite loop */for(;;){vTaskDelay( xBlockTime );xQueueSend( xQueue2, &pcMessage, 0 );}/* USER CODE END vSenderTask2 */
}/* USER CODE BEGIN Header_vReceiverTask */
/**
* @brief Function implementing the Task_QueueRecei thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_vReceiverTask */
void vReceiverTask(void const * argument)
{/* USER CODE BEGIN vReceiverTask */QueueHandle_t xQueueThatContainsData;char *pcReceivedString;BaseType_t xStatus;/* Infinite loop */for(;;){xQueueThatContainsData = ( QueueHandle_t ) xQueueSelectFromSet( xQueueSet,portMAX_DELAY );xStatus = xQueueReceive(xQueueThatContainsData, &pcReceivedString, 0 );printf("%s",pcReceivedString);if(xStatus !=pdPASS)printf("Could not Receive to the data\r\n");}/* USER CODE END vReceiverTask */
}


演示了一个非常简单的情况;队列集只包含队列,它包含的两个队列都用于发送字符指针。 在实际应用程序中,队列集可能同时包含队列和信号量,队列可能不都持有相同的数据类型。 在此情况下,在使用返回的值之前,需要测试xQueue Select From Set()返回的值。 清单66演示了如何使用从xQueue Select From Set返回的值()当集合有以下成员时:

  1. 二进制信号量。
  2. 读取字符指针的队列。
  3. 读取uint32_t值的队列

进阶案例如下(仅仅代码)

/* 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 performedhere... */}} }

通过判断是哪种类型 进行分别处理数据

使用邮箱

在嵌入式社区中没有关于术语的共识,而“邮箱”在不同的RTOS中意味着不同的东西。 在这本书中,邮箱一词是用来指长度为1的队列。 队列可能会被描述为邮箱,因为它在应用程序中的使用方式,而不是因为它与队列具有功能差异:

  • 队列用于将数据从一个任务发送到另一个任务,或从中断服务例程发送到任务。 发送方在队列中放置一个项,接收方从队列中删除该项。 数据通过队列从发送方传递给接收方。
  • 邮箱用于保存可以被任何任务或任何中断服务例程读取的数据。 数据不会通过邮箱,而是保留在邮箱中,直到它被覆盖。 发件人覆盖邮箱中的值。 接收方从邮箱中读取值,但不会从邮箱中删除值。

xQueueOverwrite()

与xQueue Send to Back()API函数一样,xQueue Overwrite()API函数将数据发送到队列。 与xQueue Send to Back()不同,如果队列已经满了,那么xQueue Overwrite()将覆盖已经在队列中的数据。
队列覆盖()只应与长度为1的队列一起使用。 这种限制避免了函数的实现需要对队列中的哪个项进行任意的决定,如果队列已经满了。

注意:不要从中断服务例程调用xQueue Overwrite()。 中断安全版本x Queue Overwrite From IS位置。

参数说明

  • xQueue
    数据被发送到的队列的句柄(写入)。 队列句柄将从调用返回到用于创建队列的xQueue Create()。
  • pvItemToQueue
    指向要复制到队列中的数据的指针。 当创建队列时,将设置队列可以容纳的每个项的大小,因此将从pv Item到队列的许多字节复制到队列存储区域。

xQueuePeek()

队列Peek()用于从队列中接收(读取)项,而不从队列中删除该项。 队列Peek()从队列的头部接收数据,而不修改存储在队列中的数据,也不修改数据存储在队列中的顺序。
注意:不要从中断服务例程调用xQueue Peek()。 中断安全版本x队列Peek从ISR()应该使用在它的位置。

xQueuePeek() has the same function parameters and return value as xQueueReceive()

FreeRTOS笔记篇:第四章 -- 队列管理相关推荐

  1. Linux(b站视频兄弟连)自学笔记第十四章——日志管理

    Linux(b站视频兄弟连)自学笔记第十四章--日志管理 简介 rsyslogd 日志轮替 简介 rsyslogd 日志轮替

  2. 系统集成项目管理工程师(软考中级)—— 第二十四章 收尾管理、知识产权、法规标准规范 笔记分享

    前言 现在分享一些笔记给大家,希望能够帮助大家并顺利通过软考. 幕布地址:第二十四章 收尾管理.知识产权.法规标准规范 - 幕布 概述 大数据 收尾 收尾管理工作 ①项目验收工作 是项目收尾管理中的首 ...

  3. PMBOK(第六版) PMP笔记——《第四章 项目整合管理》

    第 4 章 项目整合管理 从第四章开始,进入49个过程的学习.49个过程被划分为十大知识领域,分为十个章节,本章节是项目整合管理知识领域,主要讲述项目整合管理的7个过程. 1.需要对什么进行整合管理? ...

  4. 2021-11-04 《计算机操作系统》(第四版)学习笔记:第四章

    文章目录 第四章 存储器管理 4.1 存储器的层次结构 4.1.1 多层结构的存储器系统 4.1.2 主存储器与寄存器 4.1.3 高速缓存和磁盘缓存 4.2 程序的装入和链接 4.2.1 地址以及映 ...

  5. PMBOK(第六版) 学习笔记 ——《第六章 项目进度管理》

    系列文章目录 PMBOK(第六版) 学习笔记 --<第一章 引论> PMBOK(第六版) 学习笔记 --<第二章 项目运行环境> PMBOK(第六版) 学习笔记 --<第 ...

  6. R语言实战笔记--第十四章 主成分和因子分析

    R语言实战笔记–第十四章 主成分和因子分析 标签(空格分隔): R语言 主成分分析 因子分析 原理及区别 主成分分析与因子分析很接近,其目的均是为了降维,以更简洁的数据去解释结果,但这两种方法其实是相 ...

  7. 【信息系统项目管理师】第四章 整体管理思维导图

    [信息系统项目管理师]第四章 整体管理思维导图 整体管理是对输入输出记忆要求最高的,也是论文最久没有考察的内容.还有每一个过程的定义也该清楚明白. 项目整体管理计划包括了其他各个子计划,它们相互影响和 ...

  8. C++ Primer 学习笔记(第四章:表达式)

    2019独角兽企业重金招聘Python工程师标准>>> ##C++ Primer 学习笔记(第四章:表达式) [TOC] ###4.1 基础 左值和右值: 当一个对象被用作右值的时候 ...

  9. 机器学习理论《统计学习方法》学习笔记:第四章 朴素贝叶斯法

    机器学习理论<统计学习方法>学习笔记:第四章 朴素贝叶斯法 4 朴素贝叶斯法 4.1 朴素贝叶斯法的学习与分类 4.1.1 基本方法 4.1.2 后验概率最大化的含义 4.2 朴素贝叶斯法 ...

最新文章

  1. 基于web的可定制数据填报平台
  2. 是不是一个东西_小说:他伸手一摸,摸到一块凉凉的东西,拿起来一看是个黑色牌子...
  3. tcpdump抓包命令_tcpdump实战
  4. 截取两个标签之间的文本
  5. DisplayPowerState
  6. java工商银行项目_ChaosBlade 在工商银行混沌工程体系中的应用实践
  7. 【LeetCode】【HOT】142. 环形链表 II(快慢指针)
  8. Windows下配置Squid反向代理服务器
  9. 一张图学会python-一张图 python
  10. X509Certificate类解析证书的差异,算是.Net的BUG吗?
  11. 4800包括了路线坐标正反算、竖曲线、超高加宽、边坡放样及断面计算等程序。
  12. 计量论文stata代码大全
  13. 微信公众号迁移办理流程及公证书办理方法
  14. 风险预测模型_慢乙肝患者的HCC风险预测模型——精准医学的希望|高分综述
  15. 48 款数据可视化分析工具大集合
  16. 运营之光:我的互联网运营方法论与自白学习总结(思维导图)
  17. 阿里云牵手行业龙头香港快运航空,支持特区数字化升级
  18. make: *** [Makefile:44:obj/start.o] 错误 127
  19. Linux下C++使用Protobuf的安装步骤(vscode)
  20. 银行业务系统设计特点概述

热门文章

  1. 分治策略------棋盘覆盖(ChessBoard)
  2. DotProject的安装(1)
  3. xbox手柄_请不要通过Xbox Live判断白人
  4. Week 8: Face Detection
  5. 2021消防设施操作员(初级)岗位考试模拟题库应急疏散逃生知识部分
  6. Citrix_XenServer-6.1安装过程详解
  7. Android Things 开发入门
  8. matlab土方计算,土方量计算的MATLAB工具箱研制
  9. MapX 简介 (转)
  10. 【Graphite】Graphite常用函数使用