FreeRTOS学习 消息队列
消息隊列
FreeRTOS學習倉庫地址:https://gitee.com/killerp/free-rtos_-study
消息隊列是RTOS的基礎數據結構,用于任務之間、任務與中斷之間進行數據傳遞。
沒有使用消息隊列時,若想要在兩個任務之間進行數據傳遞,那么必須通過全局變量來傳遞,而在多任務系統中,訪問全局變量往往需要用戶對資源進行保護,這樣就使得編程變得麻煩。
消息隊列封裝了對共享數據的訪問保護,同時還加入了阻塞等待機制。使用戶編程時不用去考慮復雜的并發訪問。
一、隊列的結構
消息隊列結構體的定義如下:
-
消息隊列可理解為一個環形的隊列,通過pcHead和pcTail將隊列的首位連接起來。
-
pcWriteTo和pcReadFrom分別是隊列的首部和尾部(若采用默認的FIFO)
-
隊列中每個消息的大小是固定的,一個隊列可容納若干個消息,由消息大小和數量決定隊列所占內存的大小
-
隊列有兩個鏈表,分別用于存放因發送/接收消息而進入阻塞的任務。
-
隊列支持鎖定,當隊列鎖定時,中斷函數不能修改以上鏈表,實現對數據的保護。
消息隊列可簡單的抽象成如下圖片:
在同一時間內,只能有一個任務 or 中斷在修改隊列的鏈表。是的,隊列鎖并不能阻止中斷向隊列復制數據。
在這里插入圖片描述
二、創建隊列
創建隊列的過程與創建任務類似,需要為隊列結構體和隊列存儲區分配內存,并初始化隊列結構體的成員變量。
以動態內存分配為例:首先分配內存
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,const UBaseType_t uxItemSize,const uint8_t ucQueueType ){Queue_t * pxNewQueue; //指向新的隊列結構體size_t xQueueSizeInBytes; //隊列總大小(字節)uint8_t * pucQueueStorage; //指向隊列存儲區域起始地址//隊列的長度至少為1configASSERT( uxQueueLength > ( UBaseType_t ) 0 );//計算隊列使用的內存大小xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );/* 檢查乘法溢出 */configASSERT( ( uxItemSize == 0 ) || ( uxQueueLength == ( xQueueSizeInBytes / uxItemSize ) ) );/* 檢查溢出. */configASSERT( ( sizeof( Queue_t ) + xQueueSizeInBytes ) > xQueueSizeInBytes );//todo 申請內存, pvPortMalloc字節對齊問題pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); if( pxNewQueue != NULL ){//隊列結構體與隊列實際內存區域在同一連續的內存中 所以pucQueueStorage跳過Queue_tpucQueueStorage = ( uint8_t * ) pxNewQueue;pucQueueStorage += sizeof( Queue_t ); #if ( configSUPPORT_STATIC_ALLOCATION == 1 ){pxNewQueue->ucStaticallyAllocated = pdFALSE;}#endif /* configSUPPORT_STATIC_ALLOCATION *///初始化隊列結構體prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );}else{traceQUEUE_CREATE_FAILED( ucQueueType );mtCOVERAGE_TEST_MARKER();}return pxNewQueue;}初始化隊列結構體的成員變量,初始化后的內存大概為:
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength,const UBaseType_t uxItemSize,uint8_t * pucQueueStorage,const uint8_t ucQueueType,Queue_t * pxNewQueue ) {//移除編譯器的警告( void ) ucQueueType;//設置隊列結構體的成員//當隊列作為信號量時,uxItemSize為0if( uxItemSize == ( UBaseType_t ) 0 ){//pcHead不能為null,因為pcHead為null表示互斥信號量 所以設為一個確定的值pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;}else{//作為隊列,pcHead指向存儲區pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;}//設置隊列長度pxNewQueue->uxLength = uxQueueLength;pxNewQueue->uxItemSize = uxItemSize;//重置隊列 ( void ) xQueueGenericReset( pxNewQueue, pdTRUE );}BaseType_t xQueueGenericReset( QueueHandle_t xQueue,BaseType_t xNewQueue ) {Queue_t * const pxQueue = xQueue;configASSERT( pxQueue );taskENTER_CRITICAL();{//重新設置隊列的指針pxQueue->u.xQueue.pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;pxQueue->pcWriteTo = pxQueue->pcHead;pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - 1U ) * pxQueue->uxItemSize ); pxQueue->cRxLock = queueUNLOCKED;pxQueue->cTxLock = queueUNLOCKED;if( xNewQueue == pdFALSE ){//不是新的隊列/* xTasksWaitingToSend的任務可以發送消息,而 xTasksWaitingToReceive則繼續等待 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{//初始化隊列使用的鏈表vListInitialise( &( pxQueue->xTasksWaitingToSend ) );vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );}}taskEXIT_CRITICAL();return pdPASS; }三、發送消息到隊列
RTOS中發送消息的函數五花八門,但萬變不離其宗,只要掌握了以下這個函數,其他的發送函數都是這個函數的變體。
任務中發送消息
任務調用發送消息函數、在該函數中任務會進入死循環,若任務能成功發送消息,則退出循環。否則,任務就需要進入阻塞,等待隊列中出現空閑位置。
同時通過進入臨界區來保證當前任務對隊列的獨占性訪問。具體代碼邏輯如下:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,const void * const pvItemToQueue,TickType_t xTicksToWait,const BaseType_t xCopyPosition ) {//xEntryTimeSet標記任務是否已經設置過阻塞時間BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;TimeOut_t xTimeOut; //阻塞時間結構體Queue_t * const pxQueue = xQueue;//檢查一些錯誤的用法configASSERT( pxQueue );configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif//循環for( ; ; ){//進入臨界區 要操作鏈表taskENTER_CRITICAL();{//隊列未滿或是覆蓋寫入時,消息可以寫入隊列if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){traceQUEUE_SEND( pxQueue );{//將隊列項復制到隊列指定位置xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );//如果有任務在等待消息if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){//將該任務從事件鏈表中移出if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){//移出的任務優先級更高,需要任務調度queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else if( xYieldRequired != pdFALSE ) //沒有任務在等消息,但需要任務調度{queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL(); //消息發送完成 退出臨界區return pdPASS;}else //隊列滿不能入隊 需要延時 or 退出{//任務不要求延時 直接退出if( xTicksToWait == ( TickType_t ) 0 ){taskEXIT_CRITICAL();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}else if( xEntryTimeSet == pdFALSE ) //未初始化阻塞{//初始化阻塞結構體,用于輔助計算阻塞時間vTaskInternalSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}}//到此、任務未能成功發送消息 需要阻塞等待//退出臨界區后,其他任務或中斷可能搶占當前任務(然后向隊列獲取消息,那么當前任務就有可能發送消息)taskEXIT_CRITICAL();//掛起調度器、防止其他任務訪問隊列的xTasksWaitingToSend鏈表vTaskSuspendAll();//鎖住隊列 防止中斷修改xTasksWaitingToSend鏈表prvLockQueue( pxQueue );//檢查當前任務的阻塞時間是否到達if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){//阻塞時間未到,且隊列中為滿 則將當前任務插入隊列的WaitingToSendif( prvIsQueueFull( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_SEND( pxQueue );vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );//解鎖隊列(允許中斷修改鏈表)prvUnlockQueue( pxQueue );//恢復調度器,產生任務調度,當前任務進入阻塞,等到阻塞時間到達或有其他任務讀取了消息時被喚醒,然后重新進入當前循環if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}}else{//隊列有位置,重新進入當前循環prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{//超時時間到且未能成功發送消息 退出prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();traceQUEUE_SEND_FAILED( pxQueue );return errQUEUE_FULL;}} /*lint -restore */ }FreeRTOS的消息是通過內存復制實現的。過程如圖:先從write指針寫入消息,再將write指針移動到下一個空閑位置。
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,const void * pvItemToQueue,const BaseType_t xPosition ) {BaseType_t xReturn = pdFALSE;UBaseType_t uxMessagesWaiting;/* 當前函數必須在臨界區內調用 */uxMessagesWaiting = pxQueue->uxMessagesWaiting;//作為信號量的情況if( pxQueue->uxItemSize == ( UBaseType_t ) 0 ){{//若是互斥信號量的話,說明信號量被釋放、需要解除優先級反轉if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );pxQueue->u.xSemaphore.xMutexHolder = NULL;}else{mtCOVERAGE_TEST_MARKER();}}}//作為消息隊列 發送到隊列尾部(writeto)else if( xPosition == queueSEND_TO_BACK ){//內存復制( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); //pcWriteTo增加pxQueue->pcWriteTo += pxQueue->uxItemSize; //若pcWriteTo到達內存末尾,則回到內存首部if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail ) {pxQueue->pcWriteTo = pxQueue->pcHead;}else{mtCOVERAGE_TEST_MARKER();}}else {//復制到隊首(pcReadFrom)( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ) {pxQueue->u.xQueue.pcReadFrom = ( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize );}else{mtCOVERAGE_TEST_MARKER();}//若是覆蓋寫入,則有一個消息被覆蓋,uxMessagesWaiting-1if( xPosition == queueOVERWRITE ){if( uxMessagesWaiting > ( UBaseType_t ) 0 ){--uxMessagesWaiting;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}//入隊成功pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;return xReturn; }中斷中發送消息
由于在中斷中不能進入阻塞,所以需要有一個函數來另外實現在中斷發送消息。與在任務中的區別在于,臨界區保護以及當不滿足發送條件時,直接退出函數,不會進入延時等待。
同時,在中斷中若隊列上鎖,則不能修改鏈表。這是因為中斷觸發時,任務可能正在訪問隊列鏈表。
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue,const void * const pvItemToQueue,BaseType_t * const pxHigherPriorityTaskWoken,const BaseType_t xCopyPosition ) {BaseType_t xReturn; //返回值:表示是否需要任務調度UBaseType_t uxSavedInterruptStatus; //保存中斷狀態Queue_t * const pxQueue = xQueue;//檢查隊列configASSERT( pxQueue );configASSERT( !( ( pvItemToQueue == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );configASSERT( !( ( xCopyPosition == queueOVERWRITE ) && ( pxQueue->uxLength != 1 ) ) );portASSERT_IF_INTERRUPT_PRIORITY_INVALID();//進入臨界區uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();{//隊列未滿或是覆蓋寫入時,消息可以寫入隊列if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) ){const int8_t cTxLock = pxQueue->cTxLock; //獲得發送鎖 const UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting;traceQUEUE_SEND_FROM_ISR( pxQueue );/* Semaphores use xQueueGiveFromISR(), so pxQueue will not be a* semaphore or mutex. That means prvCopyDataToQueue() cannot result* in a task disinheriting a priority and prvCopyDataToQueue() can be* called here even though the disinherit function does not check if* the scheduler is suspended before accessing the ready lists. *///復制數據到隊列( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );//若隊列沒有被鎖if( cTxLock == queueUNLOCKED ){{//如果有任務正在等待數據if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ){//將任務從等待鏈表中移除if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){//標記有更高優先級的任務解鎖 退出中斷后需要任務調度if( pxHigherPriorityTaskWoken != NULL ){*pxHigherPriorityTaskWoken = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}( void ) uxPreviousMessagesWaiting;}}else{//隊列被鎖定,增加cTxLock的值,使解鎖隊列的任務明白有多少消息未能成功入隊configASSERT( cTxLock != queueINT8_MAX );pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );}xReturn = pdPASS;}else //隊列已滿 不能阻塞直接退出{traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );xReturn = errQUEUE_FULL;}}//退出臨界區portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );return xReturn; }四、接收消息
任務中接收消息
接收消息的實現與發送消息類似,同樣需要在循環中,成功獲取隊列的消息則退出,否則進入阻塞,等待消息到來。
代碼注釋如下:
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 ) );configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif//循環for( ; ; ){taskENTER_CRITICAL();{//獲取隊列中的消息數量const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;//若隊列中有數據if( uxMessagesWaiting > ( UBaseType_t ) 0 ){//復制數據到buffer,消息數量-1prvCopyDataFromQueue( 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{/* Entry time was already set. */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 */ }向隊列復制消息。在這里需要注意當隊列的消息大小為0時,這個隊列是信號量,不需要內存的復制,這個部分在下一章中講信號量說明。
讀取消息時,是先將read指針移動,再讀取。
static void prvCopyDataFromQueue( Queue_t * const pxQueue,void * const pvBuffer ) {if( pxQueue->uxItemSize != ( UBaseType_t ) 0 ){//pcReadFrom向前移動一個單元pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize; //是否溢出、需要回到隊列頭if( pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail ) {pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;}else{mtCOVERAGE_TEST_MARKER();}//內存復制( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.xQueue.pcReadFrom, ( size_t ) pxQueue->uxItemSize ); } }中斷中接收消息
同理,在中斷中讀取消息,當隊列中沒有消息時,不能進入等待,必須馬上退出。
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 );configASSERT( !( ( 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 );//數據有效,復制數據到bufferprvCopyDataFromQueue( 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 );//設置cRxLock-1 使隊列知道在其鎖定的時候有消息被讀取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; }總結
以上是生活随笔為你收集整理的FreeRTOS学习 消息队列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 该如何提升自己的编程能力?
- 下一篇: SSH设置秘钥