一.概述
1.介紹
??隊列(queue)可以用于"任務到任務"、“任務到中斷”、"中斷到任務"直接傳輸信息。
2.核心功能
線程安全:自動處理多任務訪問時的互斥問題。
數據復制:入隊時復制數據(而非引用),避免內存共享風險。
阻塞機制:當隊列滿 / 空時,任務可選擇阻塞等待。
優先級支持:高優先級任務優先獲取隊列資源。
3.關鍵屬性
固定長度:創建時需指定隊列長度(元素個數)。
固定大小:每個元素的字節數固定(如sizeof(int))。
先進先出(FIFO):默認按入隊順序出隊(也支持 LIFO)。
4.原理圖
二.隊列的基本操作
1.創建
隊列的創建有兩種方法:動態分配內存、靜態分配內存,
(1)動態分配內存:xQueueCreate,隊列的內存在函數內部動態分配
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
(2)靜態分配內存:xQueueCreateStatic,隊列的內存要事先分配好
QueueHandle_t xQueueCreateStatic(
???????????????????????????UBaseType_t uxQueueLength,
???????????????????????????UBaseType_t uxItemSize,
???????????????????????????uint8_t *pucQueueStorageBuffer,
???????????????????????????StaticQueue_t *pxQueueBuffer
???????????????????????);
(3)示例代碼:
// 示例代碼
?#define QUEUE_LENGTH 10
?#define ITEM_SIZE sizeof( uint32_t )
?// xQueueBuffer用來保存隊列結構體
?StaticQueue_t xQueueBuffer;
?// ucQueueStorage 用來保存隊列的數據
?// 大小為:隊列長度 * 數據大小
?uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];
?void vATask( void *pvParameters )
?{
QueueHandle_t xQueue1;
// 創建隊列: 可以容納QUEUE_LENGTH個數據,每個數據大小是ITEM_SIZE
xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
??ITEM_SIZE,
??ucQueueStorage,
??&xQueueBuffer );
?}
2.復位
隊列剛被創建時,里面沒有數據;使用過程中可以調用xQueueReset()把隊列恢復為初始狀態,此函數原型為:
/* pxQueue : 復位哪個隊列;
?* 返回值: pdPASS(必定成功)
?*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);
3.刪除
刪除隊列的函數為vQueueDelete(),只能刪除使用動態方法創建的隊列,它會釋放內存。原型如下:
void vQueueDelete( QueueHandle_t xQueue );
4.寫隊列
可以把數據寫到隊列頭部,也可以寫到尾部,這些函數有兩個版本:在任務中使用、在ISR中使用。函數原型如下:
/* 等同于xQueueSendToBack
?* 往隊列尾部寫入數據,如果沒有空間,阻塞時間為xTicksToWait
?*/
BaseType_t xQueueSend(
????????????????????????????????QueueHandle_t ???xQueue,
????????????????????????????????const void ??????*pvItemToQueue,
????????????????????????????????TickType_t ??????xTicksToWait
????????????????????????????);
/*
?* 往隊列尾部寫入數據,如果沒有空間,阻塞時間為xTicksToWait
?*/
BaseType_t xQueueSendToBack(
????????????????????????????????QueueHandle_t ???xQueue,
????????????????????????????????const void ??????*pvItemToQueue,
????????????????????????????????TickType_t ??????xTicksToWait
????????????????????????????);
/*
?* 往隊列尾部寫入數據,此函數可以在中斷函數中使用,不可阻塞
?*/
BaseType_t xQueueSendToBackFromISR(
??????????????????????????????????????QueueHandle_t xQueue,
??????????????????????????????????????const void *pvItemToQueue,
??????????????????????????????????????BaseType_t *pxHigherPriorityTaskWoken
???????????????????????????????????);
/*
?* 往隊列頭部寫入數據,如果沒有空間,阻塞時間為xTicksToWait
?*/
BaseType_t xQueueSendToFront(
????????????????????????????????QueueHandle_t ???xQueue,
????????????????????????????????const void ??????*pvItemToQueue,
????????????????????????????????TickType_t ??????xTicksToWait
????????????????????????????);
/*
?* 往隊列頭部寫入數據,此函數可以在中斷函數中使用,不可阻塞
?*/
BaseType_t xQueueSendToFrontFromISR(
??????????????????????????????????????QueueHandle_t xQueue,
??????????????????????????????????????const void *pvItemToQueue,
??????????????????????????????????????BaseType_t *pxHigherPriorityTaskWoken
???????????????????????????????????);
5 讀隊列
使用xQueueReceive()函數讀隊列,讀到一個數據后,隊列中該數據會被移除。這個函數有兩個版本:在任務中使用、在ISR中使用。函數原型如下:
BaseType_t xQueueReceive( QueueHandle_t xQueue,
??????????????????????????void * const pvBuffer,
??????????????????????????TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(
????????????????????????????????????QueueHandle_t ???xQueue,
????????????????????????????????????void ????????????*pvBuffer,
????????????????????????????????????BaseType_t ??????*pxTaskWoken
????????????????????????????????);
6.查詢
可以查詢隊列中有多少個數據、有多少空余空間。函數原型如下:
/*
?* 返回隊列中可用數據的個數
?*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
?* 返回隊列中可用空間的個數
?*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
7.覆蓋/偷看
當隊列長度為1時,可以使用xQueueOverwrite()或xQueueOverwriteFromISR()來覆蓋數據。
注意,隊列長度必須為1。當隊列滿時,這些函數會覆蓋里面的數據,這也以為著這些函數不會被阻塞。
函數原型如下:
/* 覆蓋隊列
?* xQueue: 寫哪個隊列
?* pvItemToQueue: 數據地址
?* 返回值: pdTRUE表示成功, pdFALSE表示失敗
?*/
BaseType_t xQueueOverwrite(
???????????????????????????QueueHandle_t xQueue,
???????????????????????????const void * pvItemToQueue
??????????????????????);
BaseType_t xQueueOverwriteFromISR(
???????????????????????????QueueHandle_t xQueue,
???????????????????????????const void * pvItemToQueue,
???????????????????????????BaseType_t *pxHigherPriorityTaskWoken
??????????????????????);
8.隊列的基本使用
本程序會創建一個隊列,然后創建2個發送任務、1個接收任務:
發送任務優先級為1,分別往隊列中寫入100、200
接收任務優先級為2,讀隊列、打印數值
(1)main函數中創建的隊列、創建了發送任務、接收任務,代碼如下:
/* 隊列句柄, 創建隊列時會設置這個變量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
????/* 創建隊列: 長度為5,數據大小為4字節(存放一個整數) */
????xQueue = xQueueCreate( 5, sizeof( int32_t ) );
if( xQueue != NULL )
{
/* 創建2個任務用于寫隊列, 傳入的參數分別是100、200
?* 任務函數會連續執行,向隊列發送數值100、200
?* 優先級為1
?*/
xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
/* 創建1個任務用于讀隊列
?* 優先級為2, 高于上面的兩個任務
?* 這意味著隊列一有數據就會被讀走
?*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
/* 啟動調度器 */
vTaskStartScheduler();
}
else
{
/* 無法創建隊列 */
}
/* 如果程序運行到了這里就表示出錯了, 一般是內存不足 */
return 0;
}
(2)發送任務的函數中,不斷往隊列中寫入數值,代碼如下:
static void vSenderTask( void *pvParameters )
{
int32_t lValueToSend;
BaseType_t xStatus;
/* 我們會使用這個函數創建2個任務
?* 這些任務的pvParameters不一樣
? ?*/
lValueToSend = ( int32_t ) pvParameters;
/* 無限循環 */
for( ;; )
{
/* 寫隊列
?* xQueue: 寫哪個隊列
?* &lValueToSend: 寫什么數據? 傳入數據的地址, 會從這個地址把數據復制進隊列
?* 0: 不阻塞, 如果隊列滿的話, 寫入失敗, 立刻返回
?*/
xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
if( xStatus != pdPASS )
{
printf( "Could not send to the queue.\r\n" );
}
}
}
(3)接收任務的函數中,讀取隊列、判斷返回值、打印,代碼如下:
static void vReceiverTask( void *pvParameters )
{
/* 讀取隊列時, 用這個變量來存放數據 */
int32_t lReceivedValue;
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
/* 無限循環 */
for( ;; )
{
/* 讀隊列
?* xQueue: 讀哪個隊列
?* &lReceivedValue: 讀到的數據復制到這個地址
?* xTicksToWait: 如果隊列為空, 阻塞一會
?*/
xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
if( xStatus == pdPASS )
{
/* 讀到了數據 */
printf( "Received = %d\r\n", lReceivedValue );
}
else
{
/* 沒讀到數據 */
printf( "Could not receive from the queue.\r\n" );
}
}
}
三.郵箱
1.FreeRTOS的郵箱概念跟別的RTOS不一樣,這里的郵箱稱為"櫥窗"也許更恰當:
它是一個隊列,隊列長度只有1;
寫郵箱:新數據覆蓋舊數據,在任務中使用xQueueOverwrite(),在中斷中使用xQueueOverwriteFromISR()。
既然是覆蓋,那么無論郵箱中是否有數據,這些函數總能成功寫入數據。
讀郵箱:讀數據時,數據不會被移除;在任務中使用xQueuePeek(),在中斷中使用xQueuePeekFromISR()。
這意味著,第一次調用時會因為無數據而阻塞,一旦曾經寫入數據,以后讀郵箱時總能成功。
2.代碼示例
main函數中創建了隊列(隊列長度為1)、創建了發送任務、接收任務:
發送任務的優先級為2,它先執行
接收任務的優先級為1
代碼如下:
/* 隊列句柄, 創建隊列時會設置這個變量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
????/* 創建隊列: 長度為1,數據大小為4字節(存放一個char指針) */
????xQueue = xQueueCreate( 1, sizeof(uint32_t) );
if( xQueue != NULL )
{
/* 創建1個任務用于寫隊列
?* 任務函數會連續執行,構造buffer數據,把buffer地址寫入隊列
?* 優先級為2
?*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 創建1個任務用于讀隊列
?* 優先級為1
?*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 啟動調度器 */
vTaskStartScheduler();
}
else
{
/* 無法創建隊列 */
}
/* 如果程序運行到了這里就表示出錯了, 一般是內存不足 */
return 0;
}
發送任務、接收任務的代碼和執行流程如下:
運行結果如下圖所示:
四.隊列與其他 FreeRTOS 對象的對比
五.隊列的性能考慮
1.內存開銷:
隊列本身的控制結構(約 40 字節)。
數據緩沖區(長度 × 元素大小)。
3.復制開銷:
入隊 / 出隊時復制數據,大元素(如結構體)會影響性能。
優化方案:傳遞指針而非完整數據(需確保內存安全)。
3.阻塞喚醒開銷:
任務從阻塞到就緒的上下文切換成本。