01??
一、概述
隊列又稱消息隊列,是一種常用于任務間通信的數據結構,隊列可以在任務與任務間、中斷和任務間傳遞信息,實現了任務接收來自其他任務或中斷的不固定長度的消息,任務能夠從隊列里面讀取消息,當隊列中的消息是空時,讀取消息的任務將被阻塞,用戶還可以指定阻塞的任務時間?xTicksToWait,在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。
當隊列中有新消息時,被阻塞的任務會被喚醒并處理新消息;當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉為就緒態。
消息隊列是一種異步的通信方式。通過消息隊列服務,任務或中斷服務例程可以將一條或多條消息放入消息隊列中。同樣,一個或多個任務可以從消息隊列中獲得消息。當有多個消息發送到消息隊列時,通常是將先進入消息隊列的消息先傳給任務,也就是說,任務先得到的是最先進入消息隊列的消息,即先進先出原則(FIFO),但是也支持后進先出原則(LIFO)。
特性
FreeRTOS 中使用隊列數據結構實現任務異步通信工作,具有如下特性:?
- LIFO)。?
應用場景
消息隊列可以應用于發送不定長消息的場合,包括任務與任務間的消息交換,隊列是?FreeRTOS 主要的任務間通訊方式,可以在任務與任務間、中斷和任務間傳送信息,發送到隊列的消息是通過拷貝方式實現的,這意味著隊列存儲的數據是原數據,而不是原數據的引用。
02??
二、消息隊列的運作機制
? ? 創建消息隊列時?FreeRTOS 會先給消息隊列分配一塊內存空間,這塊內存的大小等于消息隊列控制塊大小加上(單個消息空間大小與消息隊列長度的乘積),接著再初始化消息隊列,此時消息隊列為空。FreeRTOS的消息隊列控制塊由多個元素組成,當消息隊列被 創建時,系統會為控制塊分配對應的內存空間,用于保存消息隊列的一些信息如消息的存 儲位置,頭指針 pcHead、尾指針 pcTail、消息大小uxItemSize以及隊列長度uxLength等。同時每個消息隊列都與消息空間在同一段連續的內存空間中,在創建成功的時候,這些內存就被占用了,只有刪除了消息隊列的時候,這段內存才會被釋放掉,創建成功的時候就已經分配好每個消息空間與消息隊列的容量,無法更改,每個消息空間可以存放不大于消息大小uxItemSize的任意類型的數據,所有消息隊列中的消息空間總數即是消息隊列的長度,這個長度可在消息隊列創建時指定。
? ? 任務或者中斷服務程序都可以給消息隊列發送消息,當發送消息時,如果隊列未滿或者允許覆蓋入隊,FreeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據用戶指定的阻塞 超時時間進行阻塞,在這段時間中,如果隊列一直不允許入隊,該任務將保持阻塞狀態以等待隊列允許入隊。當其它任務從其等待的隊列中讀取入了數據(隊列未滿),該任務將 自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中還不允許 入隊,任務也會自動從阻塞態轉移為就緒態,此時發送消息的任務或者中斷程序會收到一個錯誤碼 errQUEUE_FULL。
發送緊急消息的過程與發送消息幾乎一樣,唯一的不同是,當發送緊急消息時,發送的位置是消息隊列隊頭而非隊尾,這樣,接收者就能夠優先接收到緊急消息,從而及時進行消息處理。
? ? 當某個任務試圖讀一個隊列時,其可以指定一個阻塞超時時間。在這段時間中,如果隊列為空,該任務將保持阻塞狀態以等待隊列數據有效。當其它任務或中斷服務程序往其等待的隊列中寫入了數據,該任務將自動由阻塞態轉移為就緒態。當等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態轉移為就緒態。
? ? 當消息隊列不再被使用時,應該刪除它以釋放系統資源,一旦操作完成,消息隊列將被永久性的刪除。
03??
1.消息隊列創建函數?
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize);
功能描述:用于創建一個新的隊列。
參數:
返回值:
成功-如果創建成功則返回一個隊列句柄,用于訪問創建的隊列;
失敗-如果創建不成功則返回NULL,可能原因是創建隊列需要的 RAM 無法分配成功。
04??
2.消息隊列靜態創建函數
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t *pucQueueStorageBuffer,StaticQueue_t *pxQueueBuffer );
功能描述:用于創建一個新的隊列。
參數:
- uint8_t 類型的數組,數組的大小至少有uxQueueLength* uxItemSize 個字節。當 uxItemSize 為 0 時,pucQueueStorageBuffer 可以為 NULL。
- StaticQueue_t 類型的變量,該變量用于存儲隊列的數據結構。
返回值:
成功-如果創建成功則返回一個隊列句柄,用于訪問創建的隊列;
失敗-如果創建不成功則返回NULL,可能原因是創建隊列需要的 RAM 無法分配成功。
05??
3.用于向隊列尾部發送一個隊列消息
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
參數說明:
- xTicksToWait?被設置成0,函數立刻返回。超時時間的單位為系統節拍周期,常量portTICK_PERIOD_MS?用于輔助計算真實的時間,單位為ms。如果INCLUDE_vTaskSuspend?設置成1,并且指定延時為portMAX_DELAY?將導致任務掛起(沒有超時)。
返回值:
消息發送成功成功返回pdTRUE,否則返回errQUEUE_FULL。
06??
4.在中斷服務程序中用于向隊列尾部發送一個消息
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
參數說明:
- 隊列句柄。
- 指針,指向要發送到隊列尾部的消息。
- 如果入隊導致一個任務解鎖,并且解鎖的任務優先級高于當前被中斷的任務,則將*pxHigherPriorityTaskWoken設置成pdTRUE,然后在中斷退出前需要進行一次上下文切換,去執行被喚醒的優先級更高的任務。從FreeRTOS V7.3.0?起,pxHigherPriorityTaskWoken?作為一個可選參數,可以設置為NULL。
返回值:
消息發送成功成功返回pdTRUE,否則返回errQUEUE_FULL。
5.向隊列隊首發送一個消息
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait );
參數說明:
- 被設置成 0,函數立刻返回。超時時間的單位為系統節拍周期,常量 portTICK_PERIOD_MS 用于輔助計算真實的時間,單位為 ms。如果 INCLUDE_vTaskSuspend 設置成 1,并且指定延時為 portMAX_DELAY 將導致任務無限阻塞(沒有超時)。
返回值:
消息發送成功成功返回pdTRUE,否則返回errQUEUE_FULL。
6.在中斷服務程序中向消息隊列隊首發送一個消息
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
參數說明:
- 隊列句柄。
- 指針,指向要發送到隊首的消息。
- 如果入隊導致一個任務解鎖,并且解鎖的任務優先級高于當前被中斷的任務,則將*pxHigherPriorityTaskWoken設置成pdTRUE,然后在中斷退出前需要進行一次上下文切換,去執行被喚醒的優先級更高的任務。從FreeRTOS V7.3.0?起,pxHigherPriorityTaskWoken?作為一個可選參數,可以設置為NULL。
返回值:
消息發送成功成功返回pdTRUE,否則返回errQUEUE_FULL。
7.從一個隊列中接收消息,并把接收的消息從隊列中刪除
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
參數說明:
- 地址。
- 0,函數立刻返 回。超時時間的單位為系統節拍周期,常量 portTICK_PERIOD_MS 用 于輔助計算真實的時間,單位為 ms。如果 INCLUDE_vTaskSuspend 設 置成 1,并且指定延時為 portMAX_DELAY 將導致任務無限阻塞(沒有超時)。
返回值:
隊列項接收成功返回?pdTRUE,否則返回 pdFALSE。
若接收完消息,不想刪除,可以使用xQueuePeek函數。
8.在中斷中從一個隊列中接收消息,并從隊列中刪除該消息
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxHigherPriorityTaskWoken);
參數說明:
- 在該隊列上。如果 xQueueReceiveFromISR()到賬了一個任務解鎖了則將 *pxHigherPriorityTaskWoken設置為pdTRUE ,否則 *pxHigherPriorityTaskWoken的值將不變。從 FreeRTOS V7.3.0 起,pxHigherPriorityTaskWoken 作為一個可選參數,可以設置為NULL。
返回值:
隊列項接收成功返回?pdTRUE,否則返回 pdFALSE。
若接收完消息,不想刪除,可以使用xQueuePeekFromISR函數。
07??
四、消息隊列使用注意事項
在使用?FreeRTOS 提供的消息隊列函數的時候,需要了解以下幾點:?
1. 使用 xQueueSend()、xQueueSendFromISR()、xQueueReceive()等這些函數之前應先 創建需消息隊列,并根據隊列句柄進行操作。?
2. 隊列讀取采用的是先進先出(FIFO)模式,會先讀取先存儲在隊列中的數據。當 然也 FreeRTOS 也支持后進先出(LIFO)模式,那么讀取的時候就會讀取到后進 隊列的數據。?
3. 在獲取隊列中的消息時候,我們必須要定義一個存儲讀取數據的地方,并且該數 據區域大小不小于消息大小,否則,很可能引發地址非法的錯誤。?
4. 無論是發送或者是接收消息都是以拷貝的方式進行,如果消息過于龐大,可以將消息的地址作為消息進行發送、接收。
5. 隊列是具有自己獨立權限的內核對象,并不屬于任何任務。所有任務都可以向同一隊列寫入和讀出。一個隊列由多任務或中斷寫入是經常的事,但由多個任務讀出倒是用的比較少。
6、在中斷使用消息隊列發送與接收時注意將搶占優先級設置大于等于5,官方庫存函數說明如下
/* The highest interrupt priority that can be used by any interrupt serviceroutine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALLINTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHERPRIORITY THAN THIS! (higher priorities are lower numeric values. */#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY?5
08??
五、示例代碼
5.1 任務之間消息傳遞
1.freertos.cvoid MX_FREERTOS_Init(void) {/* USER CODE BEGIN RTOS_QUEUES *//* add queues, ... *//* 創建Test_Queue */Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */if(NULL == Test_Queue)printf("創建Test_Queue消息隊列失敗!\r\n");/* USER CODE END RTOS_QUEUES *//* Create the thread(s) *//* definition and creation of defaultTask */// osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);//defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);/* USER CODE BEGIN RTOS_THREADS *//* 創建app_task2任務 */app_task1_handle=xTaskCreateStatic((TaskFunction_t )app_task1, ?/* 任務入口函數 */(const char* ? ?)"app_task1",/* 任務名字 */(uint16_t ? ? ? )512, ?/* 任務棧大小 */(void* ? ? ? ? ?)NULL,/* 任務入口函數參數 */(UBaseType_t ? ?)4,?/* 任務的優先級 */(StackType_t* ? )app_task1_stack,//任務堆棧(StaticTask_t* ?)&app_task1_buffer);/* 任務控制塊指針 */?app_task2_handle=xTaskCreateStatic((TaskFunction_t )app_task2, ?/* 任務入口函數 */(const char* ? ?)"app_task2",/* 任務名字 */(uint16_t ? ? ? )512, ?/* 任務棧大小 */(void* ? ? ? ? ?)NULL,/* 任務入口函數參數 */(UBaseType_t ? ?)5,?/* 任務的優先級 */(StackType_t* ? )app_task2_stack,//任務堆棧(StaticTask_t* ?)&app_task2_buffer);/* 任務控制塊指針 */???/* add threads, ... *//* USER CODE END RTOS_THREADS */}/* USER CODE BEGIN Header_StartDefaultTask *//*** @brief Function implementing the defaultTask thread.* @param argument: Not used?* @retval None*//* USER CODE END Header_StartDefaultTask */void StartDefaultTask(void const * argument){/* USER CODE BEGIN StartDefaultTask *//* Infinite loop */for(;;){osDelay(1000);}/* USER CODE END StartDefaultTask */}/* Private application code --------------------------------------------------*//* USER CODE BEGIN Application */static void app_task1(void* pvParameters){BaseType_t xReturn=pdFALSE;uint32_t txd = 1;for(;;){xReturn = xQueueSend(?Test_Queue,?/* 消息隊列的句柄 */&txd,/* 發送的消息內容 */0 ); ? ? ? ?/* 等待時間 0 */if(pdPASS == xReturn)printf("[app_task1]消息txd發送成功!\r\n");elseprintf("[app_task2]消息txd發送失敗!\r\n");txd++;vTaskDelay(1000);}}?static void app_task2(void* pvParameters){BaseType_t xReturn=pdFALSE;uint32_t rxd;/* 定義一個接收消息的變量 */for(;;){xReturn = xQueueReceive( Test_Queue, ? ? /* 消息隊列的句柄 */&rxd, ? ? ?/* 發送的消息內容 */portMAX_DELAY); /* 等待時間一直等 */if(pdTRUE == xReturn)printf("[app_task2]本次接收到的數據是%x\r\n",rxd);elseprintf("[app_task2]數據接收出錯,錯誤代碼0x%lx\n",xReturn); ?}}?
2. 演示
09??
5.2 中斷向任務發送消息
1.freertos.cstatic void app_task1(void* pvParameters){BaseType_t xReturn=pdFALSE;char buf[QUEUE_SIZE]={0};uint32_t task_cnt=0;for(;;){task_cnt++;sprintf(buf,"app_task1 have run %d times",task_cnt);xReturn = xQueueSend(?Test_Queue, /*?消息隊列的句柄?*/buf,/*?發送的消息內容?*/0 ); ? ? ? ?/*?等待時間?0 */if(pdPASS == xReturn)printf("[app_task1]消息txd發送成功!\r\n");elseprintf("[app_task2]消息txd發送失敗!\r\n");vTaskDelay(3000);}}?static void app_task2(void* pvParameters){BaseType_t xReturn=pdFALSE;uint8_t buf[QUEUE_SIZE]={0};/*?定義一個接收消息的變量?*/for(;;){xReturn = xQueueReceive(?Test_Queue, ? ? /*?消息隊列的句柄?*/buf, ? ? ?/*?發送的消息內容?*/portMAX_DELAY); /*?等待時間一直等?*/if(pdTRUE == xReturn)printf("[app_task2]本次接收到的數據是%s\r\n",buf);elseprintf("[app_task2]數據接收出錯,錯誤代碼0x%lx\n",xReturn); ?memset(buf,0,sizeof buf);}}?2.usart.cstatic volatile uint8_t?g_usart1_recv_buf[64]={0};static volatile uint32_t?g_usart1_recv_cnt = 0;extern QueueHandle_t Test_Queue;?void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){uint32_t ulReturn;/*?進入臨界段,臨界段可以嵌套?*/ulReturn = taskENTER_CRITICAL_FROM_ISR();if(huart->Instance == USART1)//?判斷是由哪個串口觸發的中斷{g_usart1_recv_buf[g_usart1_recv_cnt]=hal_uart_rx_data;//記錄多少個數據g_usart1_recv_cnt++;//檢測到'#'符或接收的數據滿的時候則發送數據if(hal_uart_rx_data=='#' || g_usart1_recv_cnt>=(sizeof g_usart1_recv_buf)){xQueueSendFromISR(Test_Queue,(void *)g_usart1_recv_buf,NULL);g_usart1_recv_cnt=0;}//?重新使能串口1接收中斷HAL_UART_Receive_IT(huart,&hal_uart_rx_data,1);}/*?退出臨界段?*/taskEXIT_CRITICAL_FROM_ISR( ulReturn );}
3. 演示