章節概述:
介紹 FreeRTOS中的任務間通訊機制:隊列的使用(盡管FreeRTOS中沒有進程的概念,但為了統一,我們還是以進程間通訊(IPC)的說法)
介紹
隊列是主要的任務間通訊方式。可以在任務與任務間、中斷和任務間傳送信息。
大多數情況下,隊列用於具有線程保護的FIFO(先進先出)緩沖區。
特性
FreeRTOS隊列具有以下特性:
- 通過拷貝傳遞數據
當信息的大小到達一個臨界點后,逐字節拷貝整個信息是不實際的,可以定義一個指針隊列,只拷貝指向消息的指針來代替整個信息拷貝。
FreeRTOS+UDP IP棧例程正是使用這種方法向FreeRTOS協議棧傳遞大量網絡數據的。
但我們還可以通過定義結構體,並在其中使用指針:
通過定義保存一個結構體變量的隊列實現:變長消息(一個成員保存緩存數據的大小;另一個成員指向要入隊的緩存)
通過定義保存一個結構體變量的隊列實現:單個隊列可以接收不同類型信息,並且信息可以來自不同的位置。(一個成員保存信息類型;另一個成員保存信息數據或者指向信息數據的指針)。數據如何解讀取決於信息類型。
管理FreeRTOS+UDP IP棧的任務正是使用單個隊列接收ARP定時器時間通知、以太網硬件傳送來的數據包、從應用層傳送來的數據包、網絡關閉事件等等。
- 隊列內存區域分配由內核完成,我們不需要關心數據如何出入隊。
- 天生適用於那些內存保護(MPU)場合。一個具有內存區域保護的任務可以向另一個具有內存區域保護的任務傳遞數據,因為調用隊列發送函數會引起RTOS提升微控制器特權級別。只有RTOS(具有所有特權)才可以訪問隊列存儲區域。
- 在中斷函數中使用獨立的API。將RTOS任務API和中斷服務例程API分來實現意味着可以避免執行時的上下文調用檢查開銷,還意味着在大多數情況下,與其它RTOS產品相比,用戶創建中斷服務例程會更簡單。
基本用法
隊列的基本用法:
- 定義一個隊列句柄變量,用於保存創建的隊列:
xQueueHandle xQueue1;
- 使用API函數
xQueueCreate()
創建一個隊列。 - 如果希望使用先進先出隊列,使用API函數
xQueueSend()
向隊列投遞隊列項。如果希望使用后進先出隊列,使用API函數xQueueSendToFront()
向隊列投遞隊列項。 - 使用API函數xQueueReceive()從隊列讀取隊列項。
如果在中斷服務程序中,切記使用它們的帶中斷保護版本。
基本操作
創建隊列
QueueHandle_t xQueueCreate (UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
描述:創建新隊列。為新隊列分配指定的存儲空間並返回隊列句柄。
參數解析:
-
usQueueLength:隊列項數目
-
uxItemSize:每個隊列項大小,單位是字節。隊列項通過拷貝入隊而不是通過引用入隊,因此需要隊列項的大小。每個隊列項的大小必須相同。
返回值:成功創建隊列返回隊列句柄,否則返回0。
例子:
struct AMessage
{
portCHAR ucMessageID;
portCHAR ucData[ 20 ];
};
void vATask( void*pvParameters )
{
xQueueHandle xQueue1, xQueue2;
// 創建一個隊列,隊列能包含10個unsigned long類型的值。
xQueue1 = xQueueCreate( 10, sizeof( unsigned portLONG ));
if( xQueue1 ==0 )
{
// 隊列創建失敗,不可以使用
}
// 創建一個隊列,隊列能包含10個 Amessage結構體指針類型的值。
// 這樣可以通過傳遞指針變量來包含大量數據。
xQueue2 =xQueueCreate( 10, sizeof( struct AMessage * ) );
if( xQueue2 ==0 )
{
// 隊列創建失敗,不可以使用
}
// ... 任務的其它代碼.
}
注意:創建完成句柄以后,此后對隊列的操作函數都需要使用QueueHandle_t xQueue
,篇幅有限,會對此參數進行省略。
刪除隊列
void vQueueDelete( QueueHandle_t xQueue );
描述:刪除隊列並釋放所有分配給隊列的內存。
復位隊列
BaseType_t xQueueReset( QueueHandle_t xQueue );
描述:將隊列復位到初始狀態。
獲取信息
獲取隊列入隊信息數目
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
// 具有中斷保護的版本:
UBaseType_t uxQueueMessagesWaitingFromISR( const QueueHandle_t xQueue )。
描述:獲取隊列中存儲的信息數目。
獲取隊列的空閑數目
UBaseType_t uxQueueSpacesAvailable( QueueHandle_t xQueue );
描述:獲取隊列的空閑數目。
查詢隊列是否為空
BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t xQueue );
描述:查詢隊列是否為空。這個函數僅用於ISR。
返回值:隊列非空返回pdFALSE,其它值表示隊列為空。
查詢隊列是否滿
描述:查詢隊列是否滿,僅用於ISR。
BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t xQueue )
返回值:沒有滿返回pdFALSE;其它值表示隊列滿。
數據傳輸
向隊列投遞一個值
BaseType_t xQueueSend(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
// 中斷保護的版本
BaseType_t xQueueSendFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken);
描述:向隊列尾部投遞一個隊列項。
參數解析:
- pvItemToQueue:指針,指向要入隊的項目。要保存到隊列中的項目字節數在隊列創建時就已確定。因此要從指針pvItemToQueue指向的區域拷貝到隊列存儲區域的字節數,也已確定。
- xTicksToWait:如果隊列滿,任務等待隊列空閑的最大時間。如果隊列滿並且xTicksToWait被設置成0,函數立刻返回。時間單位為系統節拍時鍾周期,因此宏portTICK_PERIOD_MS可以用來輔助計算真實延時值。如果INCLUDE_vTaskSuspend設置成1,並且指定延時為portMAX_DELAY將引起任務無限阻塞(沒有超時)。
- pxHigherPriorityTaskWoken:如果入隊導致一個任務解鎖,並且解鎖的任務優先級高於當前運行的任務,則該函數將*pxHigherPriorityTaskWoken設置成pdTRUE。如果xQueueSendFromISR()設置這個值為pdTRUE,則中斷退出前需要一次上下文切換。從FreeRTOS V7.3.0起, pxHigherPriorityTaskWoken 作為可選參數,並可以設置為NULL。
返回值:隊列項入隊成功返回pdTRUE,否則返回errQUEUE_FULL。
例子:
struct AMessage
{
portCHAR ucMessageID;
portCHAR ucData[ 20 ];
}xMessage;
unsigned portLONG ulVar = 10UL;
void vATask( void *pvParameters )
{
xQueueHandle xQueue1, xQueue2;
struct AMessage *pxMessage;
/*創建一個隊列,隊列能包含10個unsigned long類型的值。*/
xQueue1 = xQueueCreate( 10, sizeof( unsigned portLONG ) );
/* 創建一個隊列,隊列能包含10個 Amessage結構體指針類型的值。
這樣可以通過傳遞指針變量來包含大量數據。*/
xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );
// ...
if( xQueue1 != 0 )
{
/*1個unsigned long型數據入隊.如果需要等待隊列空間變的有效,
會最多等待10個系統節拍周期*/
if( xQueueSend( xQueue1, ( void * ) &ulVar, ( portTickType ) 10 ) !=pdPASS )
{
/*消息入隊失敗*/
}
}
if( xQueue2 != 0 )
{
/* 發送一個指向結構體Amessage的對象,如果隊列滿也不等待 */
pxMessage = & xMessage;
xQueueSend( xQueue2, ( void * ) &pxMessage, ( portTickType ) 0 );
}
//... 任務其余代碼.
}
// xQueueSendFromISR 例子
void vBufferISR( void )
{
portCHARcIn;
portBASE_TYPE xHigherPriorityTaskWoken;
/* 初始化,沒有喚醒任務*/
xHigherPriorityTaskWoken = pdFALSE;
/* 直到緩沖區為空 */
do
{
/* 從緩沖區獲得一個字節數據 */
cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );
/* 投遞這個數據 */
xQueueSendFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken );
}while( portINPUT_BYTE( BUFFER_COUNT ) );
/* 這里緩沖區已空,如果需要進行一個上下文切換*/
/*根據不同移植平台,這個函數也不同*/
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
向隊尾投遞一個值
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
// 中斷保護的版本
BaseType_t xQueueSendToBackFromISR (QueueHandle_t xQueue,
const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );
描述:向隊列尾投遞一個隊列項。
參數解析:同xQueueSend
或 xQueueSendFromISR
。
返回值:同xQueueSend
。
向隊首投遞一個值
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
// 有中斷保護
BaseType_t xQueueSendToFrontFromISR (QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken);
描述:向隊列首部投遞一個隊列項。
參數解析:
- pvItemToQueue:指針,指向要入隊的項目。要保存到隊列中的項目字節數在隊列創建時就已確定。因此要從指針pvItemToQueue指向的區域拷貝到隊列存儲區域的字節數,也已確定。
- xTicksToWait:如果隊列滿,任務等待隊列空閑的最大時間。如果隊列滿並且xTicksToWait被設置成0,函數立刻返回。時間單位為系統節拍時鍾周期,因此宏portTICK_PERIOD_MS可以用來輔助計算真實延時值。如果INCLUDE_vTaskSuspend設置成1,並且指定延時為portMAX_DELAY將引起任務無限阻塞(沒有超時)。
- pxHigherPriorityTaskWoken:如果入隊導致一個任務解鎖,並且解鎖的任務優先級高於當前運行的任務,則該函數將*pxHigherPriorityTaskWoken設置成pdTRUE。如果xQueueSendFromISR()設置這個值為pdTRUE,則中斷退出前需要一次上下文切換。從FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken稱為一個可選參數,並可以設置為NULL。
返回值:隊列項入隊成功返回pdTRUE,否則返回errQUEUE_FULL。
從隊列中讀取一個值
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait);
// 帶有中斷保護的版本
BaseType_t xQueueReceiveFromISR (QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxHigherPriorityTaskWoken);
描述:從隊列中讀取一個隊列項並把該隊列項從隊列中刪除。
參數解析:
- pvBuffer:指向一個緩沖區,用於拷貝接收到的列表項。必須提供足夠大的緩沖區以便容納隊列項。
- xTicksToWait:要接收的項目隊列為空時,允許任務最大阻塞時間。如果設置該參數為0,則表示即隊列為空也立即返回。阻塞時間的單位是系統節拍周期,宏portTICK_RATE_MS可輔助計算真實阻塞時間。如果INCLUDE_vTaskSuspend設置成1,並且阻塞時間設置成portMAX_DELAY,將會引起任務無限阻塞(不會有超時)。
- pxHigherPriorityTaskWoken:如果入隊導致一個任務解鎖,並且解鎖的任務優先級高於當前運行的任務,則該函數將*pxHigherPriorityTaskWoken設置成pdTRUE。如果xQueueSendFromISR()設置這個值為pdTRUE,則中斷退出前需要一次上下文切換。從FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken稱為一個可選參數,並可以設置為NULL。
返回值:成功接收到列表項返回pdTRUE,否則返回pdFALSE。
例子:
struct AMessage
{
portCHAR ucMessageID;
portCHAR ucData[ 20 ];
} xMessage;
xQueueHandle xQueue;
// 創建一個隊列並投遞一個值
void vATask( void *pvParameters )
{
struct AMessage *pxMessage;
// 創建一個隊列,隊列能包含10個 Amessage結構體指針類型的值。
// 這樣可以通過傳遞指針變量來包含大量數據。
xQueue =xQueueCreate( 10, sizeof( struct AMessage * ) );
if( xQueue == 0)
{
// 創建隊列失敗
}
// ...
// 向隊列發送一個指向結構體對象Amessage的指針,如果隊列滿不等待
pxMessage = & xMessage;
xQueueSend(xQueue, ( void * ) &pxMessage, ( portTickType ) 0 );
// ... 其它代碼
}
// 該任務從隊列中接收一個隊列項
voidvADifferentTask( void *pvParameters )
{
struct AMessage *pxRxedMessage;
if( xQueue != 0)
{
// 從創建的隊列中接收一個消息,如果消息無效,最多阻塞10個系統節拍周期
if(xQueueReceive( xQueue, &( pxRxedMessage ), ( portTickType ) 10 ) )
{
// 現在pcRxedMessage 指向由vATask任務投遞進來的結構體Amessage變量
}
}
// ... 其它代碼
}
// xQueueReceiveFromISR 例程
/* 該函數創建一個隊列並投遞一些值 */
voidvAFunction( void *pvParameters )
{
portCHAR cValueToPost;
const portTickType xBlockTime = (portTickType )0xff;
/*創建一個隊列,可以容納10個portCHAR型變量 */
xQueue = xQueueCreate( 10, sizeof( portCHAR ) );
if( xQueue == 0 )
{
/* 隊列創建失敗 */
}
/*…... */
/* 投遞一些字符,在ISR中使用。如果隊列滿,任務將會阻塞xBlockTime 個系統節拍周期 */
cValueToPost = 'a';
xQueueSend( xQueue, ( void * ) &cValueToPost, xBlockTime );
cValueToPost = 'b';
xQueueSend( xQueue, ( void * ) &cValueToPost, xBlockTime );
/*... 繼續投遞字符 ... 當隊列滿時,這個任務會阻塞*/
cValueToPost = 'c';
xQueueSend( xQueue, ( void * ) &cValueToPost, xBlockTime );
}
/* ISR:輸出從隊列接收到的所有字符 */
voidvISR_Routine( void )
{
portBASE_TYPE xTaskWokenByReceive = pdFALSE;
portCHAR cRxedChar;
while( xQueueReceiveFromISR( xQueue, ( void *) &cRxedChar, &xTaskWokenByReceive) )
{
/* 接收到一個字符串,輸出.*/
vOutputCharacter( cRxedChar );
/* 如果從隊列移除一個字符串后喚醒了向此隊列投遞字符的任務,那么參數xTaskWokenByReceive將會設置成pdTRUE,這個循環無論重復多少次,僅會
有一個任務被喚醒。*/
}
/*這里緩沖區已空,如果需要進行一個上下文切換根據不同移植平台,這個函數也不同 */
portYIELD_FROM_ISR(xTaskWokenByReceive);
}
讀取但不移除隊列項
BaseType_t xQueuePeek(QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait);
// 帶有中斷保護的版本xQueuePeekFromIS()來完成相同功能。
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void *pvBuffer,);
描述:從隊列中讀取一個隊列項,但不會把該隊列項從隊列中移除。
參數解析:同xQueueReceive()。
返回值:成功接收到列表項返回pdTRUE,否則返回pdFALSE。
向隊尾覆蓋投遞隊列項
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
const void * pvItemToQueue);
//帶有中斷保護的版本
BaseType_t xQueueOverwriteFromISR (QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken);
描述:向隊列尾投遞一個隊列項,如果隊列已滿,則覆蓋之前的隊列項。
一般用於只有一個隊列項的隊列中,如果隊列的隊列項超過1個,使用這個宏會觸發一個斷言(已經正確定義configASSERT()的情況下)。
參數解析:
- pvItemToQueue:指針,指向要入隊的項目。要保存到隊列中的項目字節數在隊列創建時就已確定。因此要從指針pvItemToQueue指向的區域拷貝到隊列存儲區域的字節數,也已確定。
- pxHigherPriorityTaskWoken:如果入隊導致一個任務解鎖,並且解鎖的任務優先級高於當前運行的任務,則該函數將*pxHigherPriorityTaskWoken設置成pdTRUE。如果xQueueSendFromISR()設置這個值為pdTRUE,則中斷退出前需要一次上下文切換。從FreeRTOS V7.3.0起,pxHigherPriorityTaskWoken稱為一個可選參數,並可以設置為NULL。
例子:
void vFunction( void *pvParameters )
{
QueueHandle_t xQueue;
unsigned long ulVarToSend, ulValReceived;
/*創建隊列,保存一個unsignedlong值。如果一個隊列的隊列項超過1個,強烈建議不要使用xQueueOverwrite(),如果使用xQueueOverwrite()會觸發一個斷言(已經正確定義configASSERT()的情況下)。*/
xQueue = xQueueCreate( 1, sizeof( unsigned long ) );
/*使用 xQueueOverwrite().向隊列寫入10*/
ulVarToSend = 10;
xQueueOverwrite( xQueue, &ulVarToSend );
/*從隊列讀取值,但是不把這個值從隊列中刪除。*/
ulValReceived = 0;
xQueuePeek( xQueue, &ulValReceived, 0 );
if( ulValReceived != 10 )
{
/* 處理錯誤*/
}
/*到這里隊列仍是滿的。使用xQueueOverwrite()覆寫隊列,寫入值100 */
ulVarToSend = 100;
xQueueOverwrite( xQueue, &ulVarToSend );
/* 從隊列中讀取值*/
xQueueReceive( xQueue, &ulValReceived, 0 );
if( ulValReceived != 100 )
{
/*處理錯誤 */
}
/* ... */
}
調試
僅當你想使用可視化調試內核時,才進行隊列和信號量注冊。
宏configQUEUE_REGISTRY_SIZE定義了可以注冊的隊列和信號量的最大數量。
隊列注冊有兩個目的,這兩個目的都是為了調試RTOS內核:
- 它允許隊列具有一個相關的文本名字,在GUI調試中可以容易的標識隊列;
- 結合調試器用於定位每一個已經注冊的隊列和信號量時所需的信息。
隊列注冊
void vQueueAddToRegistry(QueueHandle_t xQueue, char *pcQueueName,);
描述:為隊列分配名字並進行注冊。
例子:
void vAFunction( void )
{
xQueueHandle xQueue;
/*創建一個隊列,可以容納10個char類型數值 */
xQueue = xQueueCreate( 10, sizeof( portCHAR ) );
/* 我們想可視化調試,所以注冊它*/
vQueueAddToRegistry( xQueue, "AMeaningfulName" );
}
解除注冊
void vQueueUnregisterQueue(QueueHandle_t xQueue);
描述:從隊列注冊表中移除指定的隊列。