FreeRTOS教程4 消息隊列

来源:https://www.cnblogs.com/lc-guo/p/18068327
-Advertisement-
Play Games

本文主要學習 FreeRTOS 消息隊列的相關知識,包括消息隊列概述、創建刪除複位隊列、寫入/讀取數據到隊列等關於隊列的基礎知識 ...


1、準備材料

正點原子stm32f407探索者開發板V2.4

STM32CubeMX軟體(Version 6.10.0

Keil µVision5 IDE(MDK-Arm

野火DAP模擬器

XCOM V2.6串口助手

2、學習目標

本文主要學習 FreeRTOS 消息隊列的相關知識,包括消息隊列概述、創建刪除複位隊列、寫入/讀取數據到隊列等關於隊列的基礎知識

3、前提知識

3.1、什麼是消息隊列?

在一個實時操作系統構成的完整項目中一般會存在多個任務和中斷,多個任務之間、任務與中斷之間往往需要進行通信, FreeRTOS 中所有的通信與同步機制都是基於隊列來實現的,我們可以把隊列結構想象成如下圖所示樣子

在實際使用中,隊列深度以及隊列中數據類型都可以由用戶自定義,消息隊列是一個共用的存儲區域,其可以被多個進程寫入數據,同時也可以被多個進程讀取數據,為了讓接收任務知道數據的來源,以確定數據應該如何處理,通常可以使用單個隊列來傳輸具有兩者的結構數據的值和結構欄位中包含的數據源,如下圖所示

3.2、創建隊列

隊列在使用前必須先創建,和創建任務類似, FreeRTOS 也提供了動態或靜態記憶體分配創建隊列兩個 API 函數,具體函數聲明如下所示

/**
  * @brief  動態分配記憶體創建隊列函數
  * @param  uxQueueLength:隊列深度
  * @param  uxItemSize:隊列中數據單元的長度,以位元組為單位
  * @retval 返回創建成功的隊列句柄,如果返回NULL則表示因記憶體不足創建失敗
  */
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

/**
  * @brief  靜態分配記憶體創建隊列函數
  * @param  uxQueueLength:隊列深度
  * @param  uxItemSize:隊列中數據單元的長度,以位元組為單位
  * @param  pucQueueStorageBuffer:隊列棧空間數組
  * @param  pxQueueBuffer:指向StaticQueue_t類型的用於保存隊列數據結構的變數
  * @retval 返回創建成功的隊列句柄,如果返回NULL則表示因記憶體不足創建失敗
  */
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
								 UBaseType_t uxItemSize,
								 uint8_t *pucQueueStorageBuffer,
								 StaticQueue_t *pxQueueBuffer);

/*example:創建一個深度為5,隊列單元占uint16_t大小隊列*/
QueueHandle_t QueueHandleTest;
QueueHandleTest = xQueueCreate(5, sizeof(uint16_t));

3.3、向隊列寫入數據

任務或者中斷向隊列寫入數據稱為發送消息。通常情況下,隊列被作為 FIFO(先入先出)使用,即數據由隊列尾部進入,從隊列首讀出,當然可以通過更改寫入方式將隊列作為 LIFO(後入先出)使用,向隊列中寫入數據主要有三組 FreeRTOS API 函數,具體如下所示

/**
  * @brief  向隊列後方發送數據(FIFO先入先出)
  * @param  xQueue:要寫入數據的隊列句柄
  * @param  pvItemToQueue:要寫入的數據
  * @param  xTicksToWait:阻塞超時時間,單位為節拍數,portMAXDELAY表示無限等待
  * @retval pdPASS:數據發送成功,errQUEUE_FULL:隊列滿無法寫入
  */
BaseType_t xQueueSend(QueueHandle_t xQueue,
					  const void * pvItemToQueue,
					  TickType_t xTicksToWait);

/**
  * @brief  向隊列後方發送數據(FIFO先入先出),與xQueueSend()函數一致
  */
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
							const void * pvItemToQueue,
							TickType_t xTicksToWait);

/**
  * @brief  向隊列前方發送數據(LIFO後入先出)
  */
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,
							 const void * pvItemToQueue,
							 TickType_t xTicksToWait);

/**
  * @brief  以下三個函數為上述三個函數的中斷安全版本
  * @param  pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
  */
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
							 const void *pvItemToQueue,
							 BaseType_t *pxHigherPriorityTaskWoken);

BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
								   const void *pvItemToQueue,
								   BaseType_t *pxHigherPriorityTaskWoken)

BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
									const void *pvItemToQueue,
									BaseType_t *pxHigherPriorityTaskWoken);

另外還有一組稍微特殊的向隊列寫入數據的 FreeRTOS API 函數,這組函數只用於隊列長度為 1 的隊列,在隊列已滿時會覆蓋掉隊列原來的數據,具體如下所述

/**
  * @brief  向長度為1的隊發送數據
  * @param  xQueue:要寫入數據的隊列句柄
  * @param  pvItemToQueue:要寫入的數據
  * @retval pdPASS:數據發送成功,errQUEUE_FULL:隊列滿無法寫入
  */
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);

/**
  * @brief  以下函數為上述函數的中斷安全版本
  * @param  pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
  */
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
								  const void *pvItemToQueue,
								  BaseType_t *pxHigherPriorityTaskWoken);

3.4、從隊列接收數據

任務或者中斷從隊列中讀取數據稱為接收消息。從隊列中讀取數據主要有兩組 FreeRTOS API 函數,具體如下所示

/**
  * @brief  從隊列頭部接收數據單元,接收的數據同時會從隊列中刪除
  * @param  xQueue:被讀隊列句柄
  * @param  pvBuffer:接收緩存指針
  * @param  xTicksToWait:阻塞超時時間,單位為節拍數
  * @retval pdPASS:數據接收成功,errQUEUE_FULL:隊列空無讀取到任何數據
  */
BaseType_t xQueueReceive(QueueHandle_t xQueue,
						 void *pvBuffer,
						 TickType_t xTicksToWait);

/**
  * @brief  從隊列頭部接收數據單元,不從隊列中刪除接收的單元
  */
BaseType_t xQueuePeek(QueueHandle_t xQueue,
					  void *pvBuffer,
					  TickType_t xTicksToWait);

/**
  * @brief  以下兩個函數為上述兩個函數的中斷安全版本
  * @param  pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
  */
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
								void *pvBuffer,
								BaseType_t *pxHigherPriorityTaskWoken);

BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void *pvBuffer);

3.5、查詢隊列

FreeRTOS 還提供了一些用於查詢隊列當前有效數組單元個數和剩餘可用空間數的 API 函數,具體如下所述

/**
  * @brief  查詢隊列剩餘可用空間數
  * @param  xQueue:被查詢的隊列句柄
  * @retval 返回隊列中可用的空間數
  */
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue);

/**
  * @brief  查詢隊列有效數據單元個數
  * @param  xQueue:被查詢的隊列句柄
  * @retval 當前隊列中保存的數據單元個數
  */
UBaseType_t uxQueueMessagesWaiting(const QueueHandle_t xQueue);

/**
  * @brief  查詢隊列有效數據單元個數函數的中斷安全版本
  */
UBaseType_t uxQueueMessagesWaitingFromISR(const QueueHandle_t xQueue);

3.6、阻塞狀態

當出現下麵幾種情況時,任務會進入阻塞狀態

  1. 當某個任務向隊列寫入數據,但是被寫的隊列已滿時,任務將進入阻塞狀態等待隊列出現新的位置
  2. 當某個任務從隊列讀取數據,但是被讀的隊列是空時,任務將進入阻塞狀態等待隊列出現新的數據

當出現下麵幾種情況時,任務會退出阻塞狀態

  1. 進入阻塞狀態的任務達到設置的阻塞超時時間之後會退出阻塞狀態
  2. 向滿隊列中寫數據的任務等到隊列中出現新的位置
  3. 從空隊列中讀數據的任務等到隊列.中出現新的數據

當存在多個任務處於阻塞狀態時,如果同時滿足解除阻塞的條件,則所有等待任務中 優先順序最高的任務 或者 優先順序均相同但等待最久的任務 將被解除阻塞狀態

3.7、刪除隊列

/**
  * @brief  刪除隊列
  * @param  pxQueueToDelete:要刪除的隊列句柄
  * @retval None
  */
void vQueueDelete(QueueHandle_t pxQueueToDelete);

3.8、複位隊列

/**
  * @brief  將隊列重置為其原始空狀態
  * @param  xQueue:要複位的隊列句柄
  * @retval pdPASS(從FreeRTOS V7.2.0之後)
  */
BaseType_t xQueueReset(QueueHandle_t xQueue);

3.9、隊列讀寫過程

如下圖展示了用作 FIFO 的隊列寫入和讀取數據的情況的具體過程 (註釋1)

4、實驗一:嘗試隊列基本操作

4.1、實驗目標

  1. 創建一個用於任務間、任務與中斷間信息傳輸的深度為 10 的隊列 TEST_QUEUE
  2. 創建一個任務 TASK_SEND 實現按鍵掃描響應,當 KEY2、KEY1、KEY0 按鍵按下時分別向隊列 TEST_QUEUE 中發送不同消息
  3. 創建一個任務 TASK_RECEIVE 實現從隊列 TEST_QUEUE 中接收信息,根據接收到的不同信息通過串口輸出不同內容
  4. 啟動一個 RTC 周期喚醒中斷,每隔 1s 向隊列 TEST_QUEUE 中發送一條消息

4.2、CubeMX相關配置

首先讀者應按照 “FreeRTOS教程1 基礎知識” 章節配置一個可以正常編譯通過的 FreeRTOS 空工程,然後在此空工程的基礎上增加本實驗所提出的要求

本實驗需要初始化開發板上 KEY2、KEY1和KEY0 用戶按鍵做普通輸入,具體配置步驟請閱讀“STM32CubeMX教程3 GPIO輸入 - 按鍵響應”,註意雖開發板不同但配置原理一致,如下圖所示

本實驗需要初始化 USART1 作為輸出信息渠道,具體配置步驟請閱讀“STM32CubeMX教程9 USART/UART 非同步通信”,如下圖所示

本實驗需要配置 RTC 周期喚醒中斷,具體配置步驟和參數介紹讀者可閱讀”STM32CubeMX教程10 RTC 實時時鐘 - 周期喚醒、鬧鐘A/B事件和備份寄存器“實驗,此處不再贅述,這裡參數、時鐘配置如下圖所示

由於需要在 RTC 周期喚醒中斷中使用 FreeRTOS 的 API 函數,因此 RTC 周期喚醒中斷的優先順序應該設置在 15~5 之間,此處設置為 7 ,具體如下圖所示

單擊 Middleware and Software Packs/FREERTOS,在 Configuration 中單擊 Tasks and Queues 選項卡,首先雙擊預設任務修改其參數,然後單擊 Add 按鈕按要求增加另外一個任務,具體如下圖所示

然後在下方單擊 Add 按鈕增加一個深度為 10 的隊列,具體如下圖所示

配置 Clock Configuration 和 Project Manager 兩個頁面,接下來直接單擊 GENERATE CODE 按鈕生成工程代碼即可

4.3、添加其他必要代碼

按照 “STM32CubeMX教程9 USART/UART 非同步通信” 實驗 “6、串口printf重定向” 小節增加串口 printf 重定向代碼,具體不再贅述

首先應該在 freertos.c 中添加使用到的頭文件,如下所述

#include "stdio.h"
#include "queue.h"

然後在 rtc.c 文件下方重新實現 RTC 的周期喚醒回調函數,在該函數體內發送數據 ”9“ 到隊列 TEST_QUEUE 中,具體如下所述

/*周期喚醒回調函數*/
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
	uint16_t key_value = 9;
	BaseType_t pxHigherPriorityTaskWoken;
	//向隊列中發送數據,中斷安全版本
	xQueueSendToBackFromISR(TEST_QUEUEHandle, &key_value, &pxHigherPriorityTaskWoken);
	//進行上下文切換
	portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
}

最後在 freertos.c 中添加任務函數體內代碼即可,任務 TASK_SEND 負責當有按鍵按下時發送不同的數據到隊列 TEST_QUEUE 中,任務 TASK_RECEIVE 則負責當隊列中有數據時從隊列中讀取數據並通過串口輸出給用戶 ,具體如下所述

/*發送任務*/
void TASK_SEND(void *argument)
{
  /* USER CODE BEGIN TASK_SEND */
	uint16_t key_value = 0;
  /* Infinite loop */
  for(;;)
  {
	key_value = 0;
	//按鍵KEY2按下
	if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
		key_value = 3;
	//按鍵KEY1按下
	if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
		key_value = 2;
	//按鍵KEY0按下
	if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
		key_value = 1;
	//如果有按鍵按下
	if(key_value != 0)
	{
		BaseType_t err = xQueueSendToBack(TEST_QUEUEHandle, &key_value, pdMS_TO_TICKS(50));
		
		if(err == errQUEUE_FULL)
		{
			xQueueReset(TEST_QUEUEHandle);
		}
		//按鍵消抖
		osDelay(300);
	}
	else
		osDelay(10);
  }
  /* USER CODE END TASK_SEND */
}

/*接收任務*/
void TASK_RECEIVE(void *argument)
{
  /* USER CODE BEGIN TASK_RECEIVE */
	UBaseType_t msgCount=0,freeSpace=0;
	uint16_t key_value=0;
  /* Infinite loop */
  for(;;)
  {
	msgCount = uxQueueMessagesWaiting(TEST_QUEUEHandle);
	freeSpace = uxQueueSpacesAvailable(TEST_QUEUEHandle);
	BaseType_t result = xQueueReceive(TEST_QUEUEHandle, &key_value, pdMS_TO_TICKS(50));
	
	if(result != pdTRUE)
		continue;
	
	printf("msgCount: %d, freeSpace: %d, key_value: %d\r\n", (uint16_t)msgCount, (uint16_t)freeSpace, key_value);
	
    osDelay(100);
  }
  /* USER CODE END TASK_RECEIVE */
}

4.4、燒錄驗證

燒錄程式,打開串口助手,可以發現每隔一定時間 TASK_RECEIVE 任務會從隊列中接收到 ”9“,當按鍵 KEY2 按下時 TASK_SEND 任務向隊列中發送 ”3“,同時 TASK_RECEIVE 任務會從隊列中接收到 ”3“ 表示任務 TASK_SEND 發送成功,同理按鍵 KEY1 按下時發送接收 ”2“ ,按鍵 KEY0 按下時發送接收 ”1“ ,整個過程串口輸出信息如下圖所示

5、註釋詳解

註釋1:圖片來源於 Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

參考資料

STM32Cube高效開發教程(基礎篇)

Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • System.Diagnostics.DiagnosticSource 可以豐富地記錄程式中地日誌,包括不可序列化的類型(例如 HttpResponseMessage 或 HttpContext)。 System.Diagnostics.DiagnosticSource 通過訂閱發佈模式運行,我們可 ...
  • 開源項目地址:https://gitee.com/lowcodexaf/rules-engine-editor 前言 本項目是基於XAFBlazor的規則引擎編輯器,規則引擎採用的是微軟開源的RulesEngine RulesEngine項目地址:https://github.com/microso ...
  • 開閉原則(Open-Closed Principle, OCP)是面向對象設計的五大SOLID原則之一。這個原則主張“軟體實體(類、模塊、函數等)應該對擴展開放,對修改關閉”。也就是說,軟體的設計應該允許在不修改原有代碼的情況下增加新的功能。這樣的設計有助於降低代碼的複雜性和維護成本,同時提高系統的 ...
  • 上傳附件判斷word、excel、txt等文檔中是否含有敏感詞如身份證號,手機號等,其它檢測如PDF,圖片(OCR)等可以自行擴展。 互聯網項目中,展示的數據中不能包含個人信息等敏感信息。判斷word中是否包含手機號,word正文中是否包含身份證號等敏感信息,通過正則表達式判斷匹配手機號,身份證號, ...
  • 單一職責原則(Single Responsibility Principle, SRP)是面向對象編程和設計的五大SOLID原則之一。它強調一個類、方法或模塊應該只有一個職責,即只做一件事情。如果一個類承擔的職責過多,那麼它的可維護性、可讀性和可擴展性都會受到影響。當需要修改類的某個職責時,可能會影 ...
  • 由於同一臺電腦可以安裝多個版本的.NET Core SDK。 當安裝了許多不同版本的.NET Core SDK 之後,要如何才能使用舊版dotnet 命令,執行dotnet new 或dotnet build 之類的命令? 這部分其實並不困難,只要設定global.json 即可。 首先要查詢目前電 ...
  • 1、準備材料 正點原子stm32f407探索者開發板V2.4 STM32CubeMX軟體(Version 6.10.0) Keil µVision5 IDE(MDK-Arm) 野火DAP模擬器 XCOM V2.6串口助手 一個滑動變阻器 2、學習目標 本文主要學習 FreeRTOS 信號量的相關知識 ...
  • Linux的shell腳本,有時候我們在運行shell腳本時會給腳本傳入參數,出於邏輯上的嚴謹,在腳本中可能會做一些邏輯判斷或處理,例如判斷腳本傳入參數的個數。一般我們會用$#獲取傳入參數的個數,假如,我們在shell腳本的main函數中去判斷腳本傳入參數的個數,類似如下所示: .........f ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...