FreeRtos--队列
基于 FreeRTOS 的應用程序由一組獨立的任務構成——每個任務都是具有獨立權限的小程序。這些獨立的任務之間很可能會通過相互通信以提供有用的系統功能。FreeRTOS 中所有的通信與同步機制都是基于隊列實現的。
通常情況下,隊列被作為 FIFO(先進先出)使用,即數據由隊列尾寫入,從隊列首讀出。當然,由隊列首寫入也是可能的。
往隊列寫入數據是通過字節拷貝把數據復制存儲到隊列中;從隊列讀出數據使得把隊列中的數據拷貝刪除。
隊列的寫入和讀取
使用隊列
隊列在使用前必須先被創建。
隊列由聲明為 xQueueHandle 的變量進行引用。xQueueCreate()用于創建一個隊列,并返回一個 xQueueHandle 句柄以便于對其創建的隊列進行引用。
當創建隊列時,FreeRTOS 從堆空間中分配內存空間。分配的空間用于存儲隊列數據結構本身以及隊列中包含的數據單元。如果內存堆中沒有足夠的空間來創建隊列,xQueueCreate()將返回 NULL。
xQueueSendToBack() 與 xQueueSendToFront() API 函數
如同函數名字面意思所期望的一樣,xQueueSendToBack()用于將數據發送到隊列尾;而 xQueueSendToFront()用于將數據發送到隊列首。xQueueSend()完全等同于 xQueueSendToBack()。
但切記不要在中斷服務例程中調用 xQueueSendToFront() 或xQueueSendToBack()。系統提供中斷安全版本的 xQueueSendToFrontFromISR()與xQueueSendToBackFromISR()用于在中斷服務中實現相同的功能。
xQueueReceive()與 xQueuePeek() API 函數
xQueueReceive()用于從隊列中接收(讀取)數據單元。接收到的單元同時會從隊列
中刪除。
xQueuePeek()也是從從隊列中接收數據單元,不同的是并不從隊列中刪出接收到的單元。xQueuePeek()從隊列首接收到數據后,不會修改隊列中的數據,也不會改變數據在隊列中的存儲序順。
切記不要在中斷服務例程中調用 xQueueRceive()和 xQueuePeek()。中斷安全版本的替代 API 函數 xQueueReceiveFromISR()。
uxQueueMessagesWaiting() API 函數
uxQueueMessagesWaiting()用于查詢隊列中當前有效數據單元個數。
切記不要在中斷服務例程中調用 uxQueueMessagesWaiting()。應當在中斷服務中
使用其中斷安全版本 uxQueueMessagesWaitingFromISR()。
程序實例
static void vReceiverTask( void *pvParameters ); static void vSenderTask( void *pvParameters ) ;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 { /* 隊列創建失敗*/ } /* 如果一切正常,main()函數不應該會執行到這里。但如果執行到這里,很可能是內存堆空間不足導致空閑 任務無法創建。第五章有講述更多關于內存管理方面的信息 */ for( ;; ); }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(); } }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" ); } } }寫隊列任務在每次循環中都調用 taskYIELD()。taskYIELD()通知調度器立即進行任務切換,而不必等到當前任務的時間片耗盡。某個任務調用 taskYIELD()等效于其自愿放棄運行態。由于本例中兩個寫隊列任務具有相同的任務優先級,所以一旦其中一個任務調用了 taskYIELD(),另一個任務將會得到執行 — 調用 taskYIELD()的任務轉移到就緒態,同時另一個任務進入運行態。這樣就可以使得這兩個任務輪翻地往隊列發送數據。
這個圖詳細寫到了發送過程
結果:
使用隊列傳遞復合數據類型可以通過變量識別傳輸過來的是什么數據
一個任務從單個隊列中接收來自多個發送源的數據是經常的事。通常接收方收到數據后,需要知道數據的來源,并根據數據的來源決定下一步如何處理。一個簡單的方式就是利用隊列傳遞結構體,結構體成員中就包含了數據信息和來源信息。
? 創建一個隊列用于保存類型為 xData 的結構體數據單元。結構體成員包括了一個數
據值和表示數據含義的編碼,兩者合為一個消息可以一次性發送到隊列。
? 中央控制任務用于完成主要的系統功能。其必須對隊列中傳來的輸入和其它系統狀
態的改變作出響應。
? CAN 總線任務用于封裝 CAN 總線的接口功能。當 CAN 總線任務收到并解碼一個消
息后,其將把解碼后的消息放到 xData 結構體中發往控制任務。結構體的 iMeaning
成員用于讓中央控制任務知道這個數據是用來干什么的 — 從圖中的描述可以看
出,這個數據表示電機速度。結構體的 iValue 成員可以讓中央控制任務知道電機的
實際速度值。
? 人機接口(HMI)任務用于對所有的人機接口功能進行封裝。設備操作員可能通過各種
方式進行命令輸入和參數查詢,人機接口任務需要對這些操作進行檢測并解析。當
接收到一個新的命令后,人機接口任務通過 xData 結構將命令發送到中央控制任務。
結構體的 iMeaning 成員用于讓中央控制任務知道這個數據是用來干什么的 — 從
圖中的描述可以看出,這個數據表示一個新的參數設置。結構體的 iValue 成員可以
讓中央控制任務知道具體的設置值。
demo
t1 寫隊列任務 1 得到執行,并往隊列中發送數據.
t2 寫隊列任務 1 切換到寫隊列任務 2。寫隊列任務 2 往隊列中發送數據。
t3 寫隊列任務 2 又切回寫隊列任務 1。寫隊列任務 1 再次將數據寫入隊列,導致
隊列滿。
t4 寫隊列任務 1 切換到寫隊列任務 2。
t5 寫隊列任務 2 試圖往隊列中寫入數據。但由于隊列已滿,所以寫隊列任務 2 轉
入阻塞態以等待隊列空間有效。這使得寫隊列任務 1 再次得到執行。
t6 寫隊列任務 1 試圖往隊列中寫入數據。但由于隊列已滿,所以寫隊列任務 1 也
轉入阻塞態以等待隊列空間有效。此時寫隊列任務均處于阻塞態,這才使得被
賦予最低優先級的讀隊列任務得以執行。
t7 讀隊列任務從隊列讀取數據,并把讀出的數據單元從隊列中移出。一旦隊列空
間有效,寫隊列任務 2 立即解除阻塞,并且因為其具有更高優先級,所以搶占
讀隊列任務。寫隊列任務 2 又往隊列中寫入數據,填充到剛剛被讀隊列任務騰
出的存儲空間,使得隊列再一次變滿。寫隊列發送完數據后便調用 taskYIELD(),
但寫隊列任務 1 尚還處理阻塞態,所以寫隊列任務 2 并未被切換出去,繼續執
行。
t8 寫隊列任務 2 試圖往隊列中寫入數據。但隊列已滿,所以寫隊列任務 2 轉入阻
塞態。兩個寫隊列任務再一次同時處于阻塞態,所以讀隊列任務得以執行。
t9 讀隊列任務從隊列讀取數據,并把讀出的數據單元從隊列中移出。一旦隊列空
間有效,寫隊列任務 1 立即解除阻塞,并且因為其具有更高優先級,所以搶占
讀隊列任務。寫隊列任務 1 又往隊列中寫入數據,填充到剛剛被讀隊列任務騰
出的存儲空間,使得隊列再一次變滿。寫隊列發送完數據后便調用 taskYIELD(),
但寫隊列任務 2 尚還處理阻塞態,所以寫隊列任務 1 并未被切換出去,繼續執
行。寫隊列任務 1 試圖往隊列中寫入數據。但隊列已滿,所以寫隊列任務 1 轉
入阻塞態。
兩個寫隊列任務再一次同時處于阻塞態,所以讀隊列任務得以執行
工作于大型數據單元
如果隊列存儲的數據單元尺寸較大,那最好是利用隊列來傳遞數據的指針而不是對
數據本身在隊列上一字節一字節地拷貝進或拷貝出。傳遞指針無論是在處理速度上還是
內存空間利用上都更有效。但是,當你利用隊列傳遞指針時,一定要十分小心地做到以
下兩點:
當任務間通過指針共享內存時,應該從根本上保證所不會有任意兩個任務同時
修改共享內存中的數據,或是以其它行為方式使得共享內存數據無效或產生一致性
問題。原則上,共享內存在其指針發送到隊列之前,其內容只允許被發送任務訪問;
共享內存指針從隊列中被讀出之后,其內容亦只允許被接收任務訪問。
如果指針指向的內存空間是動態分配的,只應該有一個任務負責對其進行內存
釋放。當這段內存空間被釋放之后,就不應該有任何一個任務再訪問這段空間。
切忌用指針訪問任務棧上分配的空間。因為當棧幀發生改變后,棧上的數據將不再
有效。
總結
以上是生活随笔為你收集整理的FreeRtos--队列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: tensorflow.python.fr
- 下一篇: r语言kmeans聚类(真实案例完整流程