freertos---队列管理
一、queue在freertos中有什么作用?
Queues’ provide a task-to-task, task-to-interrupt, and interrupt-to-task communication? mechanism.隊列提供了一種任務間或者任務和中斷間的通訊機制。
二、什么是隊列?
隊列是一種數據結構,可以保存固定大小的數據。在創建隊列時,隊列的長度和大小就確認下來了。通常情況下隊列是先進先出(First In First Out),即新數據被發送到隊列的尾部,從頭部取數據。不過數據也可以發送到隊列頭部,取數據也是從頭部。
如下示意圖,一個隊列用于TaskA和TaskB之間的通訊,該隊列能夠保存五個整數。TaskA先后向隊列寫入了“1”、“2”、“3”三個消息。TaskB依次從quene中取了三條消息。
多個任務可以對同一個隊列操作(讀、寫)
運行過程主要有以下兩種情況:
如果放數據的速度快于取數據的速度,那么會出現消息隊列存放滿的情況,FreeRTOS的消息存放函數xQueueSend支持超時等待,用戶可以設置超時等待,直到有空間可以存放消息或者設置的超時時間溢出。隊列可以有多個寫入者,因此一個隊列可能會阻塞多個任務以等待完成發送操作。 在這種情況下,當隊列上的空間可用時,優先級最高的任務會被解除阻塞,從而向隊列寫數據;如果被阻塞的任務具有相同的優先級,那么等待空間最長的任務將被解除阻塞。
如果放數據的速度慢于取數據的速度,那么會出現消息隊列為空的情況,FreeRTOS的消息獲取函數xQueueReceive支持超時等待,用戶可以設置超時等待,任務將保持在阻塞狀態以等待隊列中可用數據或者設置的超時時間溢出。如果多個任務都處在阻塞狀態等待數據,那么一旦數據準備完畢,優先級最高的任務可以首先獲得數據。如果優先級相同,等待時間越長的任務先獲得數據。
三、使用隊列
freertos中使用一個結構體來管理隊列,下面的所有API函數都是圍繞這個結構體展開,結構體如下:
typedef struct QueueDefinition * QueueHandle_t; typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */ {int8_t * pcHead; /*< Points to the beginning of the queue storage area. */int8_t * pcWriteTo; /*< Points to the free next place in the storage area. */union{QueuePointers_t xQueue; /*< Data required exclusively when this structure is used as a queue. */SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */} u;List_t xTasksWaitingToSend; /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */List_t xTasksWaitingToReceive; /*< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */volatile UBaseType_t uxMessagesWaiting; /*< The number of items currently in the queue. */UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */#endif#if ( configUSE_QUEUE_SETS == 1 )struct QueueDefinition * pxQueueSetContainer;#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxQueueNumber;uint8_t ucQueueType;#endif } xQUEUE;| API原型 | 函數說明 | 參數說明 | 返回值 |
| QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize ); | 用于創建一個隊列,并返回一個?xQueueHandle?句柄 。當創建隊列時,?FreeRTOS?從堆空間中分配內存空間(用于存儲隊列數據結構本身以及隊列中包含的數據單元),并進行相關初始化 ? |
| NULL?表示沒有足夠的堆空間分配給隊列而導致創建失敗。 非?NULL?值表示隊列創建成功。此返回值應當保存下來,以作為 操作此隊列的句柄 |
| BaseType_t xQueueSendToBack( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait ); | xQueueSendToBack()用于將數據發送到隊列尾。一般都是用的這種方式。中斷中使用xQueueSendToBackFromISR |
| pdPASS:數據被成功發送到隊列 errQUEUE_FULL |
| BaseType_t xQueueSendToFront( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait ); | 用于將數據發送到隊列首,中斷中使用xQueueSendToFrontFromISR | 同上 | 同上 |
| xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue ) | 該API僅僅在隊列長度為1的情況下使用(也就是mailbox)。和xQueueSendToBack不同的是,當隊列滿的時候,直接覆蓋寫。 |
| 只有pdPASS |
| BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ); | 用于從隊列頭部接收(讀取)數據元素。接收到的元素同時會從隊列中刪除。中斷中使用xQueueReceiveFromISR函數 |
| pdPASS:成功從隊列中讀取到數據 errQUEUE_EMPTY |
| BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait ) | 從隊列頭部讀取數據元素。和xQueueReceive不同的是,讀取之后,不會將隊列中的內容刪除掉。在中斷中使用 xQueuePeekFromISR() | 同上 | 同上 |
| uxQueueMessagesWaiting | ?用于查詢隊列中當前有效數據單元個數。中斷中使用uxQueueMessagesWaitingFromISR |
四、使用示例
示例1、
| Task | 發送/接收 | 指定超時時間 | 優先級 |
| Sender1 | 循環發送100 | 0 | 1 |
| Sender2 | 循環發送200 | 0 | 1 |
| Receiver | 接收 | 100ms | 2 |
源碼:
QueueHandle_t xQueue; int main( void ) {xQueue = xQueueCreate( 5, sizeof( int32_t ) );if( xQueue != NULL ){xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );vTaskStartScheduler();}for( ;; ); }static void vSenderTask( void *pvParameters ) {int32_t lValueToSend;BaseType_t xStatus;lValueToSend = ( int32_t ) pvParameters;for( ;; ){xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );if( xStatus != pdPASS ){vPrintString( "Could not send to the queue.\r\n" );}} }static void vReceiverTask( void *pvParameters ) {int32_t lReceivedValue;BaseType_t xStatus;const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );for( ;; ){/* This call should always find the queue empty because this task willimmediately remove any data that is written to the queue. */if( uxQueueMessagesWaiting( xQueue ) != 0 ){vPrintString( "Queue should have been empty!\r\n" );}xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );if( xStatus == pdPASS ){vPrintStringAndNumber( "Received = ", lReceivedValue );}else{vPrintString( "Could not receive from the queue.\r\n" );}} }執行結果:
分析:
- 兩個發送任務的優先級相等,因此,輪流被調度
- 接受任務優先級高于發送隊列優先級,這就導致隊列中元素個數永遠小于等于1,因為一旦有數據放入隊列,接受任務就會立馬搶占,將數據取走
示例2、接受處理多個源發送的數據
一項任務從多個源接收數據是很常見的。 接收任務需要知道數據來自哪里,以確定應該如何處理數據。 一個簡單的設計解決方案是使用單個隊列來傳輸結構體,結構體中包含數據值和數據源信息。?
| Task | 發送/接收 | 指定超時時間 | 優先級 |
| Sender1 | 循環發送xStructsToSend[0] | 100ms | 2 |
| Sender2 | 循環發送xStructsToSend[1] | 100ms | 2 |
| Receiver | 接收 | 0 | 1 |
源碼:
typedef enum {eSender1,eSender2 } DataSource_t;typedef struct {uint8_t ucValue;DataSource_t eDataSource; } Data_t;static const Data_t xStructsToSend[ 2 ] = {{ 100, eSender1 }, /* Used by Sender1. */{ 200, eSender2 } /* Used by Sender2. */ };int main( void ) {xQueue = xQueueCreate( 3, sizeof( Data_t ) );if( xQueue != NULL ){xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL );xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL );xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );vTaskStartScheduler();}for( ;; ); }static void vSenderTask( void *pvParameters ) {BaseType_t xStatus;const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );for( ;; ){xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );if( xStatus != pdPASS ){vPrintString( "Could not send to the queue.\r\n" );}} }static void vReceiverTask( void *pvParameters ) {Data_t xReceivedStructure;BaseType_t xStatus;for( ;; ){if( uxQueueMessagesWaiting( xQueue ) != 3 ){vPrintString( "Queue should have been full!\r\n" );}xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );if( xStatus == pdPASS ){if( xReceivedStructure.eDataSource == eSender1 ){vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );}else{vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );}}else{vPrintString( "Could not receive from the queue.\r\n" );}} }執行結果:
分析:
- t1:sender1 開始發送數據到queue
- t2:由于sender1發送了三次數據導致queue滿,從而sender1進行Blocked state;此時sender2具有最高優先級,sender2進入Running state
- t3:sender2發現queue滿,也進入Blocked state。此時,receiver進入Running state
- t4:receiver從隊列中取出一個元素后,該任務立馬sender1被搶占(sender1和sender2優先級相等,sender1等待的時間比sender2長)。
- t5:sender1向隊列中寫入一個元素后,隊列滿,又進入阻塞狀態。此時sender2也處于阻塞狀態,因此不會被調度器調度。從而receiver進入Running state,從隊列中取出一個元素后,立馬被sender2搶占(sender2等待的時間比sender1長)。
- t6:重復以上過程
和示例1不同的是,示例2的隊列幾乎一直是滿的!
?示例3、如何使用隊列傳輸大數據?
?隊列中保存的數據很大,更好的辦法是不直接傳輸數據本身,傳輸數據的指針。在使用的時候需要注意兩點:
- 確保發送任務和接受任務不會同時修改指針指向的內存
- 如果指針指向的內存是動態分配的,則要確保使用這塊內存之前沒有被釋放
?示例4、如何使用隊列傳輸數據類型和數據大小不確定的數據?
將示例2和示例3的方法結合起來,就可以達到傳輸數據類型與大小不確定的數據!
如freertos中TCP/IP stack的源碼所示:
typedef enum {eNetworkDownEvent = 0, eNetworkRxEvent, eTCPAcceptEvent, } eIPEvent_t;typedef struct IP_TASK_COMMANDS {eIPEvent_t eEventType;/* A generic pointer that can hold a value, or point to a buffer. */void *pvData; } IPStackEvent_t;五、源碼解析
1、創建隊列
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) ) #endif/*@param uxQueueLength:隊列長度,也就是隊列最多能存儲多少個元素@param uxItemSize:元素大小,隊列中每個元素占多少byte@param ucQueueType :隊列類型,內部使用,不必太多關心#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U )#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U )#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U )#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U )@return QueueHandle_t 類型的結構體,也就是隊列管理的句柄 */ QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,const UBaseType_t uxItemSize,const uint8_t ucQueueType ) {Queue_t * pxNewQueue;size_t xQueueSizeInBytes;uint8_t * pucQueueStorage;/*分配隊列結構體和隊列元素存儲空間*/xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); if( pxNewQueue != NULL )//分配空間成功{pucQueueStorage = ( 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; }待補充。。。。。。
ref:
FreeRTOS高級篇5---FreeRTOS隊列分析_朱工的專欄-CSDN博客
FreeRTOS--消息隊列 - M&D - 博客園
freeRTOS中文實用教程2--隊列 - jasonactions - 博客園
總結
以上是生活随笔為你收集整理的freertos---队列管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eclipse hadoop1.2.0配
- 下一篇: Lua ipairs与pairs的区别