日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

FreeRtos--队列

發布時間:2023/12/14 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 FreeRtos--队列 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

基于 FreeRTOS 的應用程序由一組獨立的任務構成——每個任務都是具有獨立權限的小程序。這些獨立的任務之間很可能會通過相互通信以提供有用的系統功能。FreeRTOS 中所有的通信與同步機制都是基于隊列實現的。
通常情況下,隊列被作為 FIFO(先進先出)使用,即數據由隊列尾寫入,從隊列首讀出。當然,由隊列首寫入也是可能的。
往隊列寫入數據是通過字節拷貝把數據復制存儲到隊列中;從隊列讀出數據使得把隊列中的數據拷貝刪除。
隊列的寫入和讀取

使用隊列

隊列在使用前必須先被創建。
隊列由聲明為 xQueueHandle 的變量進行引用。xQueueCreate()用于創建一個隊列,并返回一個 xQueueHandle 句柄以便于對其創建的隊列進行引用。
當創建隊列時,FreeRTOS 從堆空間中分配內存空間。分配的空間用于存儲隊列數據結構本身以及隊列中包含的數據單元。如果內存堆中沒有足夠的空間來創建隊列,xQueueCreate()將返回 NULL。

xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength, unsigned portBASE_TYPE uxItemSize );uxQueueLength 隊列能夠存儲的最大單元數目,即隊列深度。uxItemSize 隊列中數據單元的長度,以字節為單位。返回值 NULL 表示沒有足夠的堆空間分配給隊列而導致創建失敗。非 NULL 值表示隊列創建成功。此返回值應當保存下來,以作為操作此隊列的句柄。

xQueueSendToBack() 與 xQueueSendToFront() API 函數
如同函數名字面意思所期望的一樣,xQueueSendToBack()用于將數據發送到隊列尾;而 xQueueSendToFront()用于將數據發送到隊列首。xQueueSend()完全等同于 xQueueSendToBack()。
但切記不要在中斷服務例程中調用 xQueueSendToFront() 或xQueueSendToBack()。系統提供中斷安全版本的 xQueueSendToFrontFromISR()與xQueueSendToBackFromISR()用于在中斷服務中實現相同的功能。

portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue, const void * pvItemToQueue, portTickType xTicksToWait ); portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue, const void * pvItemToQueue, portTickType xTicksToWait );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。

xQueueReceive()與 xQueuePeek() API 函數
xQueueReceive()用于從隊列中接收(讀取)數據單元。接收到的單元同時會從隊列
中刪除。
xQueuePeek()也是從從隊列中接收數據單元,不同的是并不從隊列中刪出接收到的單元。xQueuePeek()從隊列首接收到數據后,不會修改隊列中的數據,也不會改變數據在隊列中的存儲序順。
切記不要在中斷服務例程中調用 xQueueRceive()和 xQueuePeek()。中斷安全版本的替代 API 函數 xQueueReceiveFromISR()。

portBASE_TYPE xQueueReceive( xQueueHandle xQueue, const void * pvBuffer, portTickType xTicksToWait );portBASE_TYPE xQueuePeek( xQueueHandle xQueue, const void * pvBuffer, portTickType xTicksToWait );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。

uxQueueMessagesWaiting() API 函數
uxQueueMessagesWaiting()用于查詢隊列中當前有效數據單元個數。
切記不要在中斷服務例程中調用 uxQueueMessagesWaiting()。應當在中斷服務中
使用其中斷安全版本 uxQueueMessagesWaitingFromISR()。

unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue ); xQueue 被查詢隊列的句柄。這個句柄即是調用 xQueueCreate()創建該隊列時的返回值。 返回值 當前隊列中保存的數據單元個數。返回 0 表明隊列為空。

程序實例

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

/* 定義隊列傳遞的結構類型。 */ typedef struct { unsigned char ucValue; unsigned char ucSource; } xData; /* 聲明兩個xData類型的變量,通過隊列進行傳遞。 */ static const xData xStructsToSend[ 2 ] = { { 100, mainSENDER_1 }, /* Used by Sender1. */ { 200, mainSENDER_2 } /* Used by Sender2. */ }; xQueueHandle xQueue;int main( void ) { /* 創建隊列用于保存最多3個xData類型的數據單元。 */ xQueue = xQueueCreate( 3, sizeof( xData ) ); if( xQueue != NULL ) { /* 為寫隊列任務創建2個實例。 The 任務入口參數用于傳遞發送到隊列中的數據。因此其中一個任務往隊列中一直寫入xStructsToSend[0],而另一個則往隊列中一直寫入xStructsToSend[1]。這兩個任務的優先級都設為2,高于讀隊列任務的優先級*/ xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL ); xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL ); /* 創建讀隊列任務。讀隊列任務優先級設為1,低于寫隊列任務的優先級。 */ xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL ); /* 啟動調度器,創建的任務得到執行。 */ vTaskStartScheduler(); } else { /* 創建隊列失敗。 */ } /* 如果一切正常,main()函數不應該會執行到這里。但如果執行到這里,很可能是內存堆空間不足導致空閑任務無法創建。第五章將提供更多關于內存管理方面的信息*/ for( ;; ); }static void vSenderTask( void *pvParameters ) { portBASE_TYPE xStatus; const portTickType xTicksToWait = 100 / portTICK_RATE_MS; /* As per most tasks, this task is implemented within an infinite loop. */ for( ;; ) { /* 往隊列發送數據第一個參數是要寫入的隊列。隊列在調度器啟動之前就被創建了,所以先于此任務執行。第二個參數是被發送數據的地址,本例中即變量lValueToSend的地址。第三個參數是阻塞超時時間 – 當隊列滿時,任務轉入阻塞狀態以等待隊列空間有效。本例中沒有設定超時時間,因為此隊列決不會保持有超過一個數據單元的機會,所以也決不會滿。*/xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );if( xStatus != pdPASS ){vPrintString( "Could not send to the queue.\r\n" );}} }static void vReceiverTask( void *pvParameters ) { /* 聲明結構體變量以保存從隊列中讀出的數據單元 */ xData xReceivedStructure; portBASE_TYPE xStatus; /* This task is also defined within an infinite loop. */ for( ;; ) { /* 讀隊列任務的優先級最低,所以其只可能在寫隊列任務阻塞時得到執行。而寫隊列任務只會在隊列寫滿時才會進入阻塞態,所以讀隊列任務執行時隊列肯定已滿。所以隊列中數據單元的個數應當等于隊列的深度 – 本例中隊列深度為3 ,里面的元素會在1-2-1,2-1-2,1-2-1,這樣循環著*/ if( uxQueueMessagesWaiting( xQueue ) != 3 ) { vPrintString( "Queue should have been full!\r\n" ); } /* Receive from the queue. 第二個參數是存放接收數據的緩存空間。本例簡單地采用一個具有足夠空間大小的變量的地址。第三個參數是阻塞超時時間 – 本例不需要指定超時時間,因為讀隊列任會只會在隊列滿時才會得到執行,故而不會因隊列空而阻塞*/ xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 ); if( xStatus == pdPASS ) { /* 數據成功讀出,打印輸出數值及數據來源。 */ if( xReceivedStructure.ucSource == mainSENDER_1 ) { vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue ); } else { vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue ); } } else { /* 沒有讀到任何數據。這一定是發生了錯誤,因為此任務只支在隊列滿時才會得到執行 */ vPrintString( "Could not receive from the queue.\r\n" ); } } }

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--队列的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。