FreeRTOS 队列管理
??基于 FreeRTOS 的應用程序由一組獨立的任務構成——每個任務都是具有獨立權限的小程序。這些獨立的任務之間很可能會通過相互通信以提供有用的系統功能。FreeRTOS 中所有的通信與同步機制都是基于隊列實現的。
一、隊列的特性
1.1、數據存儲
??隊列可以保存有限個具有確定長度的數據單元。隊列可以保存的最大單元數目被稱為隊列的“深度”。在隊列創建時需要設定其深度和每個單元的大小。通常情況下,隊列被作為 FIFO(先進先出)使用,即數據由隊列尾寫入,從隊列首讀出。當然,由隊列首寫入也是可能的。往隊列寫入數據是通過字節拷貝把數據復制存儲到隊列中;從隊列讀出數據使得把隊列中的數據拷貝刪除。
1.2、可被多任務存取
??隊列是具有自己獨立權限的內核對象,并不屬于或賦予任何任務。所有任務都可以向同一隊列寫入和讀出。一個隊列由多方寫入是經常的事,但由多方讀出倒是很少遇到
1.3、讀隊列時阻塞
??當某個任務試圖讀一個隊列時,其可以指定一個阻塞超時時間。在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當其它任務或中斷服務例程往其等待的隊列中寫入了數據,該任務將自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉移為就緒態。
??由于隊列可以被多個任務讀取,所以對單個隊列而言,也可能有多個任務處于阻塞狀態以等待隊列數據有效。這種情況下,一旦隊列數據有效,只會有一個任務會被解除阻塞,這個任務就是所有等待任務中優先級最高的任務。而如果所有等待任務的優先級相同,那么被解除阻塞的任務將是等待最久的任務。
1.4、寫隊列時阻塞
??同讀隊列一樣,任務也可以在寫隊列時指定一個阻塞超時時間。這個時間是當被寫隊列已滿時,任務進入阻塞態以等待隊列空間有效的最長時間。
??由于隊列可以被多個任務寫入,所以對單個隊列而言,也可能有多個任務處于阻塞狀態以等待隊列空間有效。這種情況下,一旦隊列空間有效,只會有一個任務會被解除阻塞,這個任務就是所有等待任務中優先級最高的任務。而如果所有等待任務的優先級相同,那么被解除阻塞的任務將是等待最久的任務。
二、使用隊列
2.1、創建隊列
??隊列在使用前必須先被創建。隊列由聲明為 xQueueHandle 的變量進行引用。 xQueueCreate()用于創建一個隊列,并返回一個 xQueueHandle 句柄以便于對其創建的隊列進行引用。
??當創建隊列時, FreeRTOS 從堆空間中分配內存空間。分配的空間用于存儲隊列數據結構本身以及隊列中包含的數據單元。如果內存堆中沒有足夠的空間來創建隊列,xQueueCreate()將返回 NULL。
??uxQueueLength 隊列能夠存儲的最大單元數目,即隊列深度。
??uxItemSize 隊列中數據單元的長度,以字節為單位。
??返回值 NULL 表示沒有足夠的堆空間分配給隊列而導致創建失敗。非 NULL 值表示隊列創建成功。此返回值應當保存下來,以作為操作此隊列的句柄。
2.2、向隊列發送消息
??創建好隊列以后就可以向隊列發送消息了, FreeRTOS 提供的向隊列發送消息的 API 函數有xQueueSendToBack() 與 xQueueSendToFront()
??xQueueSendToBack()用于將數據發送到隊列尾;而 xQueueSendToFront()用于將數據發送到隊列首。xQueueSend()完全等同于 xQueueSendToBack()。
??但不 要 在 中 斷 服 務 例 程 中 調 用 xQueueSendToFront() 或xQueueSendToBack()。系統提供中斷安全版本的xQueueSendToFrontFromISR()與xQueueSendToBackFromISR()用于在中斷服務中實現相同的功能。
??xQueue 目標隊列的句柄。這個句柄即是調用 xQueueCreate()創建該隊列時的返回值。
??pvItemToQueue 發送數據的指針。其指向將要復制到目標隊列中的數據單元。由于在創建隊列時設置了隊列中數據單元的長度,所以會從該指針指向的空間復制對應長度的數據到隊列的存儲區域。
??xTicksToWait 阻塞超時時間。如果在發送時隊列已滿,這個時間即是任務處于阻塞態等待隊列空間有效的最長等待時間。如 果 xTicksToWait 設 為 0 , 并 且 隊 列 已 滿 , 則xQueueSendToFront()與 xQueueSendToBack()均會立即返回。阻塞時間是以系統心跳周期為單位的,所以絕對時間取決于系統心跳頻率。常量 portTICK_RATE_MS 可以用來把心跳時間單位轉換為毫秒時間單位。如 果 把 xTicksToWait 設 置 為 portMAX_DELAY , 并 且 在FreeRTOSConig.h 中設定 INCLUDE_vTaskSuspend 為 1,那么阻塞等待將沒有超時限制
??返回值 有兩個可能的返回值:
??1、 pdPASS 返回 pdPASS 只會有一種情況,那就是數據被成功發送到隊列中。如果設定了阻塞超時時間(xTicksToWait 非 0),在函數返回之前任務將被轉移到阻塞態以等待隊列空間有效—在超時到來前能夠將數據成功寫入到隊列,函數則會返回 pdPASS。
??2、 errQUEUE_FULL 如 果 由 于 隊 列 已 滿 而 無 法 將 數 據 寫 入 , 則 將 返 回errQUEUE_FULL。如果設定了阻塞超時時間( xTicksToWait 非 0),在函數返回之前任務將被轉移到阻塞態以等待隊列空間有效。但直到超時也沒有其它任務或是中斷服務例程讀取隊列而騰出空間,函數則會返回 errQUEUE_FULL。
2.3、從隊列讀取消息
??出隊就是從隊列中獲取隊列項(消息), FreeRTOS 中出隊函數xQueueReceive()與 xQueuePeek()
??QueueReceive()用于從隊列中接收(讀取)數據單元。接收到的單元同時會從隊列中刪除。xQueuePeek()也是從從隊列中接收數據單元,不同的是并不從隊列中刪出接收到的單元。 xQueuePeek()從隊列首接收到數據后,不會修改隊列中的數據,也不會改變數據在隊列中的存儲序順。
??不要在中斷服務例程中調用 xQueueRceive()和 xQueuePeek()。中斷安全版本的替代 API 函數 xQueueReceiveFromISR()
??xQueue 被讀隊列的句柄。這個句柄即是調用 xQueueCreate()創建該隊列時的返回值。
??pvBuffer 接收緩存指針。其指向一段內存區域,用于接收從隊列中拷貝來的數據。數據單元的長度在創建隊列時就已經被設定,所以該指針指向的內存區域大小應當足夠保存一個數據單元。
??xTicksToWait 阻塞超時時間。如果在接收時隊列為空,則這個時間是任務處于阻塞狀態以等待隊列數據有效的最長等待時間。如果 xTicksToWait 設為 0,并且隊列為空,則 xQueueRecieve()與 xQueuePeek()均會立即返回。阻塞時間是以系統心跳周期為單位的,所以絕對時間取決于系統心跳頻率。常量 portTICK_RATE_MS 可以用來把心跳時間單位轉換為毫秒時間單位。如 果 把 xTicksToWait 設 置 為 portMAX_DELAY , 并 且 在FreeRTOSConig.h 中設定 INCLUDE_vTaskSuspend 為 1,那么阻塞等待將沒有超時限制。
??返回值 有兩個可能的返回值:
??1、pdPASS 只有一種情況會返回 pdPASS,那就是成功地從隊列中讀到數據。如果設定了阻塞超時時間(xTicksToWait 非 0),在函數返回之前任務將被轉移到阻塞態以等待隊列數據有效—在超時到來前能夠從隊列中成功讀取數據,函數則會返回 pdPASS。
??2、errQUEUE_FULL 如果在讀取時由于隊列已空而沒有讀到任何數據,則將返回errQUEUE_FULL。如果設定了阻塞超時時間( xTicksToWait 非 0),在函數返回之前任務將被轉移到阻塞態以等待隊列數據有效。但直到超時也沒有其它任務或是中斷服務例程往隊列中寫入數據,函數則會返回errQUEUE_FULL。
2.4、查詢隊列
??uxQueueMessagesWaiting()用于查詢隊列中當前有效數據單元個數。
??不要在中斷服務例程中調用 uxQueueMessagesWaiting()。應當在中斷服務中使用其中斷安全版本uxQueueMessagesWaitingFromISR()。
??xQueue 被查詢隊列的句柄。這個句柄即是調用 xQueueCreate()創建該隊列時的返回值。
??返回值 當前隊列中保存的數據單元個數。返回 0 表明隊列為空。
三、隊列使用實例
??本示范創建一個隊列,由多個任務往隊列中寫數據,以及從隊列中把數據讀出。這個隊列創建出來保存 long 型數據單元。往隊列中寫數據的任務沒有設定阻塞超時時間,而讀隊列的任務設定了超時時間。往隊列中寫數據的任務的優先級低于讀隊列任務的優先級。這意味著隊列中永遠不會保持超過一個的數據單元。因為一旦有數據被寫入隊列,讀隊列任務立即解除阻塞,搶占寫隊列任務,并從隊列中接收數據,同時數據從隊列中刪除—隊列再一次變為空隊列
3.1、創建隊列
?? main()函數在啟動調度器之前創建了一個隊列和三個任務。盡管對任務的優先級的設計使得隊列實際上在任何時候都不可能多于一個數據單元,本例代碼還是創建了一個可以保存最多 5 個 long 型值的隊列。
/* 聲明一個類型為 xQueueHandle 的變量. 其用于保存隊列句柄,以便三個任務都可以引用此隊列 */ xQueueHandle xQueue; int main( void ) {/* 創建的隊列用于保存最多5個值,每個數據單元都有足夠的空間來存儲一個long型變量 */xQueue = xQueueCreate( 5, sizeof( long ) );if( xQueue != NULL ){/* 創建兩個寫隊列任務實例,任務入口參數用于傳遞發送到隊列的值。所以一個實例不停地往隊列發送100,而另一個任務實例不停地往隊列發送200。兩個任務的優先級都設為1。 */xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );/* 創建一個讀隊列任務實例。其優先級設為2,高于寫任務優先級 */xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );/* 啟動調度器,任務開始執行 */vTaskStartScheduler();}else{/* 隊列創建失敗*/} }3.2、寫隊列
??這個任務被創建了兩個實例,一個不停地往隊列中寫數值 100,而另一個實例不停地往隊列中寫入數值 200。任務的入口參數被用來為每個實例傳遞各自的寫入值。
static void vSenderTask( void *pvParameters ) {long lValueToSend;portBASE_TYPE xStatus;/* 該任務會被創建兩個實例,所以寫入隊列的值通過任務入口參數傳遞 – 這種方式使得每個實例使用不同的值。隊列創建時指定其數據單元為long型,所以把入口參數強制轉換為數據單元要求的類型 */lValueToSend = ( long ) pvParameters;/* 和大多數任務一樣,本任務也處于一個死循環中 */for( ;; ){/* 往隊列發送數據第一個參數是要寫入的隊列。隊列在調度器啟動之前就被創建了,所以先于此任務執行。第二個參數是被發送數據的地址,本例中即變量lValueToSend的地址。第三個參數是阻塞超時時間 – 當隊列滿時,任務轉入阻塞狀態以等待隊列空間有效。本例中沒有設定超時時間,因為此隊列決不會保持有超過一個數據單元的機會,所以也決不會滿。*/xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );if( xStatus != pdPASS ){/* 發送操作由于隊列滿而無法完成 – 這必然存在錯誤,因為本例中的隊列不可能滿。 */vPrintString( "Could not send to the queue.\r\n" );}/* 允許其它發送任務執行。 taskYIELD()通知調度器現在就切換到其它任務,而不必等到本任務的時間片耗盡 */taskYIELD();} }3.3、讀隊列
??讀隊列任務設定了 100 毫秒的阻塞超時時間,所以會進入阻塞態以等待隊列數據有效。一旦隊列中數據單元有效,或者即使隊列數據無效但等待時間超過 100 毫秒,此任務將會解除阻塞。在本例中,將永遠不會出現 100 毫秒超時,因為有兩個任務在不停地往隊列中寫數據。
static void vReceiverTask( void *pvParameters ) {/* 聲明變量,用于保存從隊列中接收到的數據。 */long lReceivedValue;portBASE_TYPE xStatus;const portTickType xTicksToWait = 100 / portTICK_RATE_MS;/* 本任務依然處于死循環中。 */for( ;; ){/* 此調用會發現隊列一直為空,因為本任務將立即刪除剛寫入隊列的數據單元。 */if( uxQueueMessagesWaiting( xQueue ) != 0 ){vPrintString( "Queue should have been empty!\r\n" );}/* 從隊列中接收數據第一個參數是被讀取的隊列。隊列在調度器啟動之前就被創建了,所以先于此任務執行。第二個參數是保存接收到的數據的緩沖區地址,本例中即變量lReceivedValue的地址。此變量類型與隊列數據單元類型相同,所以有足夠的大小來存儲接收到的數據。第三個參數是阻塞超時時間 – 當隊列空時,任務轉入阻塞狀態以等待隊列數據有效。本例中常量portTICK_RATE_MS用來將100毫秒絕對時間轉換為以系統心跳為單位的時間值。*/xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );if( xStatus == pdPASS ){/* 成功讀出數據,打印出來。 */vPrintStringAndNumber( "Received = ", lReceivedValue );}else{/* 等待100ms也沒有收到任何數據。必然存在錯誤,因為發送任務在不停地往隊列中寫入數據 */vPrintString( "Could not receive from the queue.\r\n" );}} }3.4、運行結果顯示
??寫隊列任務在每次循環中都調用 taskYIELD()。 taskYIELD()通知調度器立即進行任務切換,而不必等到當前任務的時間片耗盡。某個任務調用 taskYIELD()等效于其自愿放棄運行態。由于本例中兩個寫隊列任務具有相同的任務優先級,所以一旦其中一個任務調用了 taskYIELD(),另一個任務將會得到執行 — 調用 taskYIELD()的任務轉移到就緒態,同時另一個任務進入運行態。這樣就可以使得這兩個任務輪翻地往隊列發送數據。
下圖 中可以看到輸出結果。
總結
以上是生活随笔為你收集整理的FreeRTOS 队列管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何设置软件开机自启
- 下一篇: 学术会议 Rebuttal 模板资料留存