目錄
概述
1 認識Queue
1.1 Queue定義
1.2?FreeRTOS中的Queue
1.3 Queue狀態
1.4?Queue內容
1.5 發送和接收Message
1.5.1 發送message
1.5.2 接收Message
2?Queue的特性
2.1 數據存儲
2.2?可被多任務存取
2.3?讀Queue時阻塞
2.4 寫Queue時阻塞
3?使用Queue
3.1?xQueueCreate() 函數
3.2?xQueueSendToBack() 與 xQueueSendToFront()函數
3.3 xQueueReceive()與 xQueuePeek() 函數
3.4?uxQueueMessagesWaiting() API 函數
4 一個案例
4.1 功能描述
4.2 定義Queue的變量
?4.3 創建Queue
4.4 應用Queue發送或者接收message
4.4.1 發送Message
4.4.2 接收Message
4.5 測試
5 結論
源代碼下載地址:
stm32-freeRTOS-queue資源-CSDN文庫
概述
本文主要介紹Queue的相關知識,包括Queue的定義,發送和接收消息的方式等內容。重點使用Free RTOS中Queue的接口,實現數據在不同task之間的發送和接收的案例,并在板卡上驗證該功能。
1 認識Queue
1.1 Queue定義
消息隊列是一個類似于緩沖區的對象。通過它,任務和ISR發送和接收消息,實現到數據的同學和同步。消息隊列小一個管道。它暫時保存來自發送者的消息,直到有意的接受者準備讀這些消息。這個臨時緩沖區把發送任務和接收任務隔開,即它必須同時釋放發送和接收消息的任務。
創建一個隊列,其應該具備這些要素:
1)分配一個相關的隊列控制塊(QCB)
2)? 一個消息隊列名
3)一個唯一的ID
4) 存儲器緩沖區
5)隊列長度
6)最大消息長度
7)一個或者多個任務等待列表
1.2?FreeRTOS中的Queue
FreeRTOS 的應用程序由一組獨立的任務構成——每個任務都是具有獨立權限的小程序。這些獨立的任務之間很可能會通過相互通信以提供有用的系統功能。FreeRTOS 中所有的通信與同步機制都是基于隊列實現的。
1.3 Queue狀態
發送消息狀態:
step -1: 當一個任務發送消息給一個消息隊列,消息會直接發送給阻塞的任務
step -2: 阻塞任務進入就緒態或者運行態,此時消息隊列為空,發送消息成功
step -3: 如果另外的消息送到相同的隊列,而且沒有任務在消息隊列的任務等待列表中等候,此時消息隊列的狀態為非空
step -4: 當消息數據達到隊列總數是,隊列為滿,此時隊列無法接收任何消息。
1.4?Queue內容
消息隊列可以用來接收和發送多種數據,有些消息的數據可能相當長,這種情況下,可使用發送數據指針的方式。
1.5 發送和接收Message
1.5.1 發送message
方式一: 先進先出FIFO次序Queue
方式一: 先進后出LIFO次序Queue
1.5.2 接收Message
方式一: 任務等待類表-先進先出FIFO次序Queue
方式二: 任務等待類表-先進后出LIFO次序Queue
2?Queue的特性
2.1 數據存儲
隊列可以保存有限個具有確定長度的數據單元。隊列可以保存的最大單元數目被稱為隊列的“深度”。在隊列創建時需要設定其深度和每個單元的大小。通常情況下,隊列被作為 FIFO(先進先出)使用,即數據由隊列尾寫入,從隊列首讀出。當然,由隊列首寫入也是可能的。往隊列寫入數據是通過字節拷貝把數據復制存儲到隊列中;從隊列讀出數據使得把隊列中的數據拷貝刪除。
2.2?可被多任務存取
隊列是具有自己獨立權限的內核對象,并不屬于或賦予任何任務。所有任務都可以向同一隊列寫入和讀出。一個隊列由多方寫入是經常的事,但由多方讀出倒是很少遇到。
2.3?讀Queue時阻塞
當某個任務試圖讀一個隊列時,其可以指定一個阻塞超時時間。在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當其它任務或中斷服務例程往其等待的隊列中寫入了數據,該任務將自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉移為就緒態。
由于隊列可以被多個任務讀取,所以對單個隊列而言,也可能有多個任務處于阻塞狀態以等待隊列數據有效。這種情況下,一旦隊列數據有效,只會有一個任務會被解除阻塞,這個任務就是所有等待任務中優先級最高的任務。而如果所有等待任務的優先級相同,那么被解除阻塞的任務將是等待最久的任務。
2.4 寫Queue時阻塞
同讀隊列一樣,任務也可以在寫隊列時指定一個阻塞超時時間。這個時間是當被寫隊列已滿時,任務進入阻塞態以等待隊列空間有效的最長時間。
由于隊列可以被多個任務寫入,所以對單個隊列而言,也可能有多個任務處于阻塞狀態以等待隊列空間有效。這種情況下,一旦隊列空間有效,只會有一個任務會被解除阻塞,這個任務就是所有等待任務中優先級最高的任務。而如果所有等待任務的優先級相同,那么被解除阻塞的任務將是等待最久的任務。
3?使用Queue
3.1?xQueueCreate() 函數
隊列在使用前必須先被創建。隊列由聲明為 xQueueHandle 的變量進行引用。 xQueueCreate()用于創建一個隊列,并返回一個 xQueueHandle 句柄以便于對其創建的隊列進行引用。當創建隊列時, FreeRTOS 從堆空間中分配內存空間。分配的空間用于存儲隊列數據結構本身以及隊列中包含的數據單元。如果內存堆中沒有足夠的空間來創建隊列,xQueueCreate()將返回 NULL。第五章會有關于內存堆管理的更多信息。
xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength,
unsigned portBASE_TYPE uxItemSize );
參數名稱 | 描述 |
---|---|
uxQueueLength | 隊列能夠存儲的最大單元數目,即隊列深度 |
uxItemSize | 隊列中數據單元的長度,以字節為單位 |
返回值 | 描述 |
---|---|
NULL | 表示沒有足夠的堆空間分配給隊列而導致創建失敗 |
非 NULL | 表示隊列創建成功。此返回值應當保存下來,以作為 操作此隊列的句柄 |
3.2?xQueueSendToBack() 與 xQueueSendToFront()函數
1) xQueueSendToBack()用于將數據發送到隊列尾;
2) xQueueSendToFront()用于將數據發送到隊列首。
3) xQueueSend()完全等同于 xQueueSendToBack()。
但 切 記 不 要 在 中 斷 服 務 例 程 中 調 用 xQueueSendToFront() 或xQueueSendToBack()。
中斷模式使用的發送消息函數:
1)xQueueSendToFrontFromISR()
2)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,那么阻塞等待將沒有超時限制。 |
返回值介紹
返回值 | 描述 |
---|---|
pdPASS | 返回 pdPASS 只會有一種情況,那就是數據被成功發送到隊列<中。如果設定了阻塞超時時間(xTicksToWait 非 0),在函數返回之前任務將被轉移到阻塞態以等待隊列空間有效—在超時到來前能夠將數據成功寫入到隊列,函數則會返回 pdPASS。 |
errQUEUE_FULL | 如 果 由 于 隊 列 已 滿 而 無 法 將 數 據 寫 入 , 則 將 返 errQUEUE_FULL。如果設定了阻塞超時時間( xTicksToWait 非 0),在函數返回之前任務將被轉移到阻塞態以等待隊列空間有效。但直到超時也沒有其它任務或是中斷服務例程讀取隊列而騰出空間,函數則會返回 errQUEUE_FULL。 |
3.3 xQueueReceive()與 xQueuePeek() 函數
xQueueReceive():?用于從隊列中接收(讀取)數據單元。接收到的單元同時會從隊列中刪除xQueuePeek():也是從從隊列中接收數據單元,不同的是并不從隊列中刪出接收到的單元。?其從隊列首接收到數據后,不會修改隊列中的數據,也不會改變數據在隊列中的存儲序順。
注意:
切記不要在中斷服務例程中調用 xQueueRceive()和 xQueuePeek()。
中斷模式下使用的接收函數:?xQueueReceiveFromISR()
參數介紹
參數值 | 描述 |
---|---|
xQueue | 目標隊列的句柄。這個句柄即是調用 xQueueCreate()創建該隊 列時的返回值。 |
pvBuffer | 接收緩存指針。其指向一段內存區域,用于接收從隊列中拷貝來 的數據。數據單元的長度在創建隊列時就已經被設定,所以該指針指向的 內存區域大小應當足夠保存一個數據單元。 |
xTicksToWait | 阻塞超時時間。如果在發送時隊列已滿,這個時間即是任務處于 阻塞態等待隊列空間有效的最長等待時間。如 果 xTicksToWait 設 為 0 , 并 且 隊 列 已 滿 , 則xQueueSendToFront()與 xQueueSendToBack()均會立即返回。阻塞時間是以系統心跳周期為單位的,所以絕對時間取決于系統心跳頻率。常量 portTICK_RATE_MS 可以用來把心跳時間單位轉換為毫秒時間單位。如 果 把 xTicksToWait 設 置 為 portMAX_DELAY , 并 且 在FreeRTOSConig.h 中設定 INCLUDE_vTaskSuspend 為 1,那么阻塞等待將沒有超時限制。 |
?返回值
返回值 | 描述 |
---|---|
pdPASS | 返回 pdPASS 只會有一種情況,那就是數據被成功發送到隊列<中。如果設定了阻塞超時時間(xTicksToWait 非 0),在函數返回之前任務將被轉移到阻塞態以等待隊列空間有效—在超時到來前能夠將數據成功寫入到隊列,函數則會返回 pdPASS。 |
errQUEUE_FULL | 如 果 由 于 隊 列 已 滿 而 無 法 將 數 據 寫 入 , 則 將 返 errQUEUE_FULL。如果設定了阻塞超時時間( xTicksToWait 非 0),在函數返回之前任務將被轉移到阻塞態以等待隊列空間有效。但直到超時也沒有其它任務或是中斷服務例程讀取隊列而騰出空間,函數則會返回 errQUEUE_FULL。 |
3.4?uxQueueMessagesWaiting() API 函數
uxQueueMessagesWaiting(): 用于查詢隊列中當前有效數據單元個數。
注意:不要在中斷服務例程中調用 uxQueueMessagesWaiting()。
中斷模式下使用的函數: uxQueueMessagesWaitingFromISR()。
unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue );
參數值 | 描述 |
---|---|
xQueue | 被查詢隊列的句柄。這個句柄即是調用 xQueueCreate()創建該隊列時 的返回值。 |
返回值:
返回值 | 描述 |
---|---|
非0 | 當前隊列中保存的數據單元個數 |
0 | 表明隊列為空 |
4 一個案例
4.1 功能描述
使用STM32H7平臺,基于Free RTOS平臺,創建3個Task, 2個Task發送Message, 一個Task接收Message。
4.2 定義Queue的變量
代碼第6行: 定義Queue的屬性
代碼第7行: 創建Queue ID 變量
代碼第9~12行: 定義消息體數據結構
代碼第14~17行: 消息體內容
詳細代碼:
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"const osMessageQueueAttr_t QueueInputSignalAttribute = {.name = "QueueTest"};
osMessageQueueId_t QueueTest;typedef struct {int id;int32_t value;
}Qdata_stru;Qdata_stru queList[2] = {{1, 100},{2, 50},
};
?4.3 創建Queue
使用osMessageQueueNew創建一個Queue,?osMessageQueueNew函數在cmsis_os2.c中定義。其函數原型為:
osMessageQueueId_t osMessageQueueNew (uint32_t msg_count, uint32_t msg_size, const osMessageQueueAttr_t *attr)
參數介紹:
參數名稱 | 描述 |
---|---|
msg_count | 整個消息隊列的長度 |
msg_size | 消息的字節大小 |
attr | 屬性 |
創建方法如下:
?詳細代碼:
void initTask( void )
{int ucQuequeLength= sizeof(Qdata_stru);QueueTest = osMessageQueueNew (10, ucQuequeLength, &QueueInputSignalAttribute);
}
注意:
創建Queue才能實現發送或者接收message
4.4 應用Queue發送或者接收message
4.4.1 發送Message
定義兩個Task,在該Task中實現消息發送功能
代碼第32行:發送message
代碼第33行:? 判斷message 是否發送成功
代碼第47行:發送message
代碼第48行:? 判斷message 是否發送成功
詳細代碼:
void mainTask(void *argument)
{ osStatus_t status; for(;;){status = osMessageQueuePut(QueueTest, &queList[0], NULL,100);if( status != osOK){printf("mainTask osStatus_t = %d \r\n",status);}osDelay(300);}
}void monitorTask(void *argument)
{osStatus_t status; for(;;){status = osMessageQueuePut(QueueTest, &queList[1], NULL,100);if( status != osOK){printf(" monitorTask osStatus_t = %d \r\n",status);}osDelay(200);}
}
4.4.2 接收Message
定義一個Task,在該Task中僅僅實現接收其他Task發送的消息。
代碼第62行: 接收queue消息
代碼第64~69行: 根據 message ID解析消息
?詳細代碼:
void stateTask(void *argument)
{Qdata_stru recv_que;static int cnt = 0;for(;;){if( osOK == osMessageQueueGet(QueueTest, &recv_que, NULL, 100)){if( recv_que.id == 1){printf(" que 1: %d \r\n",recv_que.value);}else if( recv_que.id == 2) {printf(" que 2: %d \r\n",recv_que.value);}}cnt++;if( (cnt %10) == 0){HAL_GPIO_TogglePin(R_STATUS_GPIO_Port, R_STATUS_Pin);}osDelay(1);}
}
4.5 測試
編譯代碼,下載到板卡中,通過終端查看發送和接收消息的情況。打開串口終端,代碼運行后,log信息如下:
5 結論
消息隊列可以不同Task之間的通信,一個消息隊列可接收多個Task發送的消息,對于數據長度大于消息隊列數據長度的情況,可采用傳送指針的方式實現。