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