队列读取消息
FreeRTOS 中用于从队列中读取消息的 API 函数如下表所示:
xQueueReceive
BaseType_t xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait )
{BaseType_t xEntryTimeSet = pdFALSE;TimeOut_t xTimeOut;Queue_t * const pxQueue = xQueue;/* 检查指针是否不为空。 */configASSERT( ( pxQueue ) );/* 如果数据大小不为零,则接收数据的缓冲区不能为 NULL。 */configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );/* 如果调度器被挂起,则不能阻塞。 */#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif/* lint -save -e904 此函数放宽了编码标准,允许在函数内部使用 return 语句。这是为了提高执行效率。 */for( ; ; ){taskENTER_CRITICAL();{const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;/* 队列中是否有数据?调用任务必须是希望访问队列的最高优先级任务。 */if( uxMessagesWaiting > ( UBaseType_t ) 0 ){/* 有数据可用,移除一个项目。 */prvCopyDataFromQueue( pxQueue, pvBuffer );traceQUEUE_RECEIVE( pxQueue );pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;/* 现在队列中有空闲空间,是否有任务在等待向队列发送数据?如果有,解除最高优先级等待任务的阻塞。 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}taskEXIT_CRITICAL();return pdPASS;}else{if( xTicksToWait == ( TickType_t ) 0 ){/* 队列为空且未指定阻塞时间(或阻塞时间已过期),因此离开。 */taskEXIT_CRITICAL();traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else if( xEntryTimeSet == pdFALSE ){/* 队列为空且指定了阻塞时间,因此配置超时结构。 */vTaskInternalSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* 入口时间已设置。 */mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();/* 退出临界区后,中断和其他任务可以向队列发送和接收数据。 */vTaskSuspendAll();prvLockQueue( pxQueue );/* 更新超时状态以查看是否已过期。 */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){/* 超时未过期。如果队列仍然为空,将任务放入等待接收队列的任务列表中。 */if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );prvUnlockQueue( pxQueue );if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}else{/* 队列中再次有数据。循环回去尝试读取数据。 */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{/* 超时。如果队列中没有数据则退出,否则循环回去尝试读取数据。 */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else{mtCOVERAGE_TEST_MARKER();}}} /* lint -restore */
}
xQueueReceive
函数用于从队列中接收数据。
-
参数检查:
-
检查队列指针
xQueue
是否为空。 -
检查缓冲区指针
pvBuffer
是否为空,且队列项大小不为零。 -
检查调度器是否被挂起,如果是且阻塞时间不为零,则断言失败。
-
-
临界区操作:
-
进入临界区,检查队列中是否有数据。
-
-
数据接收:
-
如果队列中有数据,调用
prvCopyDataFromQueue
复制数据到缓冲区。 -
更新队列中等待的消息数量。
-
检查是否有任务在等待向队列发送数据,如果有,解除最高优先级任务的阻塞。
-
退出临界区,返回成功。
-
-
超时处理:
-
如果队列为空且阻塞时间为0,退出临界区,记录失败,返回队列为空。
-
如果队列为空且阻塞时间不为0,配置超时结构,设置入口时间。
-
-
任务调度:
-
挂起所有任务,锁定队列。
-
检查超时状态,如果超时未过期且队列为空,将任务放入等待接收队列的任务列表中。
-
如果超时未过期且队列中有数据,解锁队列,循环回去尝试读取数据。
-
如果超时已过期且队列为空,解锁队列,恢复所有任务,记录失败,返回队列为空。
-
如果超时已过期且队列中有数据,解锁队列,恢复所有任务,循环回去尝试读取数据。
-
xQueuePeek
BaseType_t xQueuePeek( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait )
{BaseType_t xEntryTimeSet = pdFALSE;TimeOut_t xTimeOut;int8_t * pcOriginalReadPosition;Queue_t * const pxQueue = xQueue;/* 检查指针是否不为空。 */configASSERT( ( pxQueue ) );/* 接收数据的缓冲区只有在数据大小为零时才可以为空(即没有数据复制到缓冲区)。 */configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );/* 如果调度器被挂起,则不能阻塞。 */#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif/*lint -save -e904 此函数放宽了编码标准,允许在函数内部使用return语句。这是为了提高执行效率。 */for( ; ; ){taskENTER_CRITICAL();{const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;/* 队列中现在有数据吗?调用任务必须是最高优先级的任务才能访问队列。 */if( uxMessagesWaiting > ( UBaseType_t ) 0 ){/* 记录读取位置,以便在从队列中读取数据后重置,因为此函数只是预览数据,而不是移除它。 */pcOriginalReadPosition = pxQueue->u.xQueue.pcReadFrom;prvCopyDataFromQueue( pxQueue, pvBuffer );traceQUEUE_PEEK( pxQueue );/* 数据不会被移除,因此重置读取指针。 */pxQueue->u.xQueue.pcReadFrom = pcOriginalReadPosition;/* 数据将留在队列中,因此检查是否有其他任务在等待这些数据。 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){/* 等待的任务优先级高于当前任务。 */queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}taskEXIT_CRITICAL();return pdPASS;}else{if( xTicksToWait == ( TickType_t ) 0 ){/* 队列为空且未指定阻塞时间(或阻塞时间已过期),因此现在离开。 */taskEXIT_CRITICAL();traceQUEUE_PEEK_FAILED( pxQueue );return errQUEUE_EMPTY;}else if( xEntryTimeSet == pdFALSE ){/* 队列为空且指定了阻塞时间,因此配置超时结构以准备进入阻塞状态。 */vTaskInternalSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* 入口时间已设置。 */mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();/* 退出临界区后,中断和其他任务可以发送和接收队列中的数据。 */vTaskSuspendAll();prvLockQueue( pxQueue );/* 更新超时状态以查看是否已过期。 */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){/* 超时未过期,检查队列中现在是否有数据,如果没有则进入阻塞状态等待数据。 */if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_PEEK( pxQueue );vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );prvUnlockQueue( pxQueue );if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}else{/* 队列中现在有数据,因此不要进入阻塞状态,而是返回以尝试获取数据。 */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{/* 超时已过期。如果队列中仍然没有数据,则退出,否则返回并再次尝试读取数据。 */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceQUEUE_PEEK_FAILED( pxQueue );return errQUEUE_EMPTY;}else{mtCOVERAGE_TEST_MARKER();}}} /*lint -restore */
}
xQueuePeek
函数用于从队列中预览数据而不移除数据。
-
参数检查:确保队列指针和缓冲区指针有效。
-
调度器状态检查:确保在调度器挂起时不会阻塞。
-
临界区操作:进入临界区,检查队列中是否有数据。
-
数据预览:如果有数据,记录当前读取位置,复制数据到缓冲区,然后重置读取指针。
-
任务唤醒:如果有任务在等待队列中的数据,唤醒这些任务。
-
阻塞等待:如果队列为空且设置了阻塞时间,进入阻塞状态等待数据。
-
超时处理:如果超时,返回错误码
errQUEUE_EMPTY
。如果队列中有数据,返回成功码pdPASS
。
xQueueReceiveFromISR
/*** queue.h* <pre>* BaseType_t xQueueReceiveFromISR(* QueueHandle_t xQueue,* void *pvBuffer,* BaseType_t *pxTaskWoken* );* </pre>** 从队列接收一个项目。从中断服务例程 (ISR) 中调用此函数是安全的。** @param xQueue 要从中接收项目的队列句柄。** @param pvBuffer 指向缓冲区的指针,接收到的项目将被复制到该缓冲区。** @param pxTaskWoken 可能有任务因队列空间不足而被阻塞。如果 xQueueReceiveFromISR 导致这样的任务解除阻塞,* *pxTaskWoken 将被设置为 pdTRUE,否则 *pxTaskWoken 将保持不变。** @return 如果成功从队列接收项目,则返回 pdTRUE,否则返回 pdFALSE。** 使用示例:* <pre>** QueueHandle_t xQueue;** // 创建队列并发布一些值的函数。* void vAFunction( void *pvParameters )* {* char cValueToPost;* const TickType_t xTicksToWait = ( TickType_t )0xff;** // 创建一个能够容纳 10 个字符的队列。* xQueue = xQueueCreate( 10, sizeof( char ) );* if( xQueue == 0 )* {* // 创建队列失败。* }** // ...** // 在 ISR 中使用的字符。如果队列已满,此任务将等待 xTicksToWait 个滴答。* cValueToPost = 'a';* xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );* cValueToPost = 'b';* xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );** // ... 继续发布字符 ... 当队列满时,此任务可能会阻塞。** cValueToPost = 'c';* xQueueSend( xQueue, ( void * ) &cValueToPost, xTicksToWait );* }** // 输出在队列中接收到的所有字符的 ISR。* void vISR_Routine( void )* {* BaseType_t xTaskWokenByReceive = pdFALSE;* char cRxedChar;** while( xQueueReceiveFromISR( xQueue, ( void * ) &cRxedChar, &xTaskWokenByReceive) )* {* // 接收到一个字符。立即输出该字符。* vOutputCharacter( cRxedChar );** // 如果从队列中移除字符导致发布到队列的任务解除阻塞,cTaskWokenByReceive 将被设置为 pdTRUE。* // 无论此循环迭代多少次,只有一个任务会被唤醒。* }** if( xTaskWokenByReceive != pdFALSE )* {* taskYIELD ();* }* }* </pre>* \defgroup xQueueReceiveFromISR xQueueReceiveFromISR* \ingroup QueueManagement*/
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,void * const pvBuffer,BaseType_t * const pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,void * const pvBuffer,BaseType_t * const pxHigherPriorityTaskWoken )
{BaseType_t xReturn;UBaseType_t uxSavedInterruptStatus;Queue_t * const pxQueue = xQueue;// 检查队列指针是否有效configASSERT( pxQueue );// 检查缓冲区指针是否有效,除非队列中的数据大小为0configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );// 检查中断优先级是否有效portASSERT_IF_INTERRUPT_PRIORITY_INVALID();// 保存当前中断状态并屏蔽中断uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;// 检查队列中是否有数据if( uxMessagesWaiting > ( UBaseType_t ) 0 ){const int8_t cRxLock = pxQueue->cRxLock;// 记录从中断服务例程接收数据的事件traceQUEUE_RECEIVE_FROM_ISR( pxQueue );// 从队列中复制数据到缓冲区prvCopyDataFromQueue( pxQueue, pvBuffer );// 更新队列中剩余的消息数量pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;// 如果队列未被锁定if( cRxLock == queueUNLOCKED ){// 检查是否有任务在等待发送数据if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){// 尝试从事件列表中移除等待任务if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){// 如果等待的任务优先级高于当前任务,强制上下文切换if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{// 如果队列被锁定,更新锁计数configASSERT( cRxLock != queueINT8_MAX );pxQueue->cRxLock = ( int8_t ) ( cRxLock + 1 );}// 返回成功xReturn = pdPASS;}else{// 队列为空,返回失败xReturn = pdFAIL;traceQUEUE_RECEIVE_FROM_ISR_FAILED( pxQueue );}}// 恢复中断状态portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;
}
xQueueReceiveFromISR
函数用于从中断服务例程(ISR)中接收队列中的数据。具体功能如下:
-
参数检查:
-
检查队列指针
xQueue
是否有效。 -
检查缓冲区指针
pvBuffer
是否有效,除非队列中的数据大小为0。 -
检查队列中的数据大小是否不为0,因为不能窥探信号量。
-
-
中断优先级检查:
-
确保调用该函数的中断优先级不超过最大系统调用优先级。
-
-
中断屏蔽:
-
保存当前中断状态并屏蔽中断,防止其他中断干扰。
-
-
检查队列状态:
-
检查队列中是否有数据。
-
-
数据窥探:
-
如果队列中有数据,记录当前读取位置,以便稍后恢复。
-
从队列中复制数据到缓冲区。
-
恢复读取位置,因为没有实际从队列中移除数据。
-
-
返回结果:
-
如果成功,返回
pdPASS
。 -
如果队列为空,返回
pdFAIL
,并记录失败事件。
-
-
恢复中断状态:
-
恢复中断状态。
-
xQueuePeekFromISR
BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue,void * const pvBuffer )
{BaseType_t xReturn;UBaseType_t uxSavedInterruptStatus;int8_t * pcOriginalReadPosition;Queue_t * const pxQueue = xQueue;// 检查队列指针是否有效configASSERT( pxQueue );// 检查缓冲区指针是否有效,除非队列中的数据大小为0configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );// 检查队列中的数据大小是否不为0,因为不能窥探信号量configASSERT( pxQueue->uxItemSize != 0 );// 检查中断优先级是否有效portASSERT_IF_INTERRUPT_PRIORITY_INVALID();// 保存当前中断状态并屏蔽中断uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{// 检查队列中是否有数据if( pxQueue->uxMessagesWaiting > ( UBaseType_t ) 0 ){// 记录从中断服务例程窥探数据的事件traceQUEUE_PEEK_FROM_ISR( pxQueue );// 记录当前读取位置,以便稍后恢复pcOriginalReadPosition = pxQueue->u.xQueue.pcReadFrom;// 从队列中复制数据到缓冲区prvCopyDataFromQueue( pxQueue, pvBuffer );// 恢复读取位置,因为没有实际从队列中移除数据pxQueue->u.xQueue.pcReadFrom = pcOriginalReadPosition;// 返回成功xReturn = pdPASS;}else{// 队列为空,返回失败xReturn = pdFAIL;// 记录从中断服务例程窥探数据失败的事件traceQUEUE_PEEK_FROM_ISR_FAILED( pxQueue );}}// 恢复中断状态portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn;
}
从中断服务例程(ISR)中窥探队列的功能
-
开始:函数开始执行。
-
队列指针有效?:检查队列指针是否有效。
-
缓冲区指针有效?:检查缓冲区指针是否有效。
-
队列数据大小不为0?:检查队列中的数据大小是否不为0。
-
保存中断状态并屏蔽中断:保存当前中断状态并屏蔽中断。
-
队列中有数据?:检查队列中是否有数据。
-
记录窥探失败事件:记录从中断服务例程窥探数据失败的事件。
-
恢复中断状态:恢复中断状态。
-
返回失败:返回失败状态。
-
记录窥探事件:记录从中断服务例程窥探数据的事件。
-
复制数据到缓冲区:从队列中复制数据到缓冲区。
-
恢复读取位置:恢复读取位置。
-
返回成功:返回成功状态。