目錄
一、消息緩沖區功能概述
二、消息緩沖區操作相關函數
1、相關函數概述
2、部分函數詳解
(1)創建消息緩沖區
(2)寫入消息
(3)讀取消息
(4)消息緩沖區狀態查詢
三、消息緩沖區使用示例
1、示例功能與CubeMX項目設置
(1)RCC、SYS、Code Generator、USART3、TIM6
(2)RTC的設置
(3)FreeRTOS的設置
(4)NVIC
2、程序功能實現
(1)主程序
(2)FreeRTOS對象初始化
(3)RTC的喚醒中斷
(4)任務Task_Show的功能
3、運行調試
一、消息緩沖區功能概述
????????消息緩沖區(message buffer)是基于流緩沖區實現的,也就是它的實現使用了流緩沖區的技術,如同信號量是基于隊列實現的。與流緩沖區的差異在于:消息緩沖區傳輸的是可變長度的消息,如10字節、20字節或35字節的消息。寫入者向消息緩沖區寫入一個10字節的消息,讀取者也必須以10字節的消息讀出,而不是像流緩沖區那樣,按字節流讀出。
????????每個消息都有一個消息頭,就是消息數據的字節數。在STM32 MCU上,消息頭就是一個uint32_t類型的整數。消息頭的寫入和讀取是由FreeRTOS的API函數自動處理的,例如,向消息緩沖區寫入一個長度為20字節的消息,實際占用空間是24字節。
????????消息緩沖區沒有觸發水平,寫入和讀取都是以一條消息為單位的,操作要么成功,要么失敗。
????????消息緩沖區的其他特性與流緩沖區一樣。例如:在只有一個寫入者和一個讀取者的情況下,可以安全操作消息緩沖區;如果有多個寫入者或多個讀取者,讀寫消息緩沖區的代碼必須在臨界代碼段內,且等待時間必須設置為0。
二、消息緩沖區操作相關函數
1、相關函數概述
????????消息緩沖區相關函數的頭文件message_buffer.h,源程序都在文件stream_buffer.c里,因為消息緩沖區是基于流緩沖區實現的,要在程序中使用消息緩沖區,需包含頭文件message_buffer.h。
分組 | 函數 | 功能 |
創建 和 | xMessageBufferCreate() | 創建一個消息緩沖區,只需設置緩沖區大小 |
xMessageBufferCreateStatic() | 創建一個消息緩沖區,靜態分配內存 | |
vMessageBufferDelete() | 刪除一個消息緩沖區 | |
xMessageBufferReset() | 復位一個消息緩沖區,清空數據。只有沒有任務在阻塞狀態下讀或寫消息緩沖區時,才可以復位消息緩沖區 | |
寫入 | xMessageBufferSend() | 向消息緩沖區發送一個消息 |
xMessageBufferSendFromISR() | xMessageBufferSend()的ISR版本 | |
讀取 | xMessageBufferReceive() | 從消息緩沖區接收一條消息 |
xMessageBufferReceiveFromISR() | xMessageBufferReceive()的ISR版本 | |
狀態 查詢 | xMessageBufferIsEmpty() | 查詢消息緩沖區是否為空,返回值pdTRUE表示無任何消息 |
xMessageBufferIsFull() | 查詢消息緩沖區是否滿了,返回值pdTRUE表示不能 | |
xMessageBufferSpacesAvailable() | 查詢消息緩沖區的剩余存儲空間 |
????????與流緩沖區不同的是:消息緩沖區無須設置觸發水平,在寫入或讀取消息超時的時候,實際寫入或讀取的數據字節數為0,不會只寫入或讀取部分數據。
2、部分函數詳解
(1)創建消息緩沖區
????????用于創建消息緩沖區的函數是xMessageBufferCreate(),這是個宏函數,其原型定義如下:
/**
* \defgroup xMessageBufferCreate xMessageBufferCreate
* \ingroup MessageBufferManagement
*/
#define xMessageBufferCreate( xBufferSizeBytes ) ( MessageBufferHandle_t ) xStreamBufferGenericCreate( xBufferSizeBytes, ( size_t ) 0, pdTRUE )
????????調用函數xMessageBufferCreate()時,只需傳遞緩沖區大小xBufferSizeBytes。這個函數實際上調用了函數xStreamBufferGenericCreate(),傳遞的觸發水平參數為0,因為消息緩沖區沒有觸發水平,最后的參數pdTRUE表示要創建的是消息緩沖區。
????????函數xMessageBufferCreate()的返回值是MessageBufferHandle_t類型的,就是所創建的消息緩沖區對象指針。
(2)寫入消息
????????用于向消息緩沖區寫入消息的函數是xMessageBufferSend(),這是個宏函數,其原型定義如下:
/**
* \defgroup xMessageBufferSend xMessageBufferSend
* \ingroup MessageBufferManagement
*/
#define xMessageBufferSend( xMessageBuffer, pvTxData, xDataLengthBytes, xTicksToWait ) xStreamBufferSend( ( StreamBufferHandle_t ) xMessageBuffer, pvTxData, xDataLengthBytes, xTicksToWait )
????????實際上,它是執行了流緩沖區寫入數據的函數xStreamBufferSend()。函數中各參數的意義如下。
- xMessageBuffer,所操作的消息緩沖區的句柄。
- pvTxData,準備寫入的數據緩沖區指針。
- xDataLengthBytes,消息數據的字節數,不包括消息頭的4字節。
- xTicksToWait,等待的節拍數,如果消息緩沖區沒有足夠的空間用于寫入這條消息,任務可以進入阻塞狀態等待。若設置為0,則表示不等待;若設置為portMAX_DELAY,則表示一直等待。
????????函數xStreamBufferSend()內部會判斷傳遞來的緩沖區對象的類型。如果是消息緩沖區,就在實際寫入數據前面加上一個uint32_t類型的整數,表示消息的字節數;如果是流緩沖區,就直接寫入數據。
????????函數xMessageBufferSend()的返回值是實際寫入消息的字節數,不包括消息頭的4字節。如果函數是因為等待超時而退出的,則返回值為0;如果寫入成功,返回值就是寫入的消息數據的字節數。這是與流緩沖區不同的一個地方,使用函數xStreamBufferSend()向流緩沖區寫入數據時,如果因等待超時而退出,仍然可能向流緩沖區寫入了一些數據。
????????在ISR中,向消息緩沖區寫入消息的函數是xMessageBufferSendFromISR(),它是個宏函數,實際就是執行了函數xStreamBufferSendFromISR(),其原型定義如下:
/**
* \defgroup xMessageBufferSendFromISR xMessageBufferSendFromISR
* \ingroup MessageBufferManagement
*/
#define xMessageBufferSendFromISR( xMessageBuffer, pvTxData, xDataLengthBytes, pxHigherPriorityTaskWoken ) xStreamBufferSendFromISR( ( StreamBufferHandle_t ) xMessageBuffer, pvTxData, xDataLengthBytes, pxHigherPriorityTaskWoken )
(3)讀取消息
????????用于從消息緩沖區讀取消息的函數是xMessageBufferReceive(),其原型定義如下:
/**
* \defgroup xMessageBufferReceive xMessageBufferReceive
* \ingroup MessageBufferManagement
*/
#define xMessageBufferReceive( xMessageBuffer, pvRxData, xBufferLengthBytes, xTicksToWait ) xStreamBufferReceive( ( StreamBufferHandle_t ) xMessageBuffer, pvRxData, xBufferLengthBytes, xTicksToWait )
????????它就是執行了函數xStreamBufferReceive()。函數中各參數的意義如下。
- xMessageBuffer,所操作的消息緩沖區的句柄。
- pvRxData,保存讀出數據的緩沖區指針。
- xBufferLengthBytes,緩沖區pvRxData的長度,也就是最大能讀取的字節數。
- xTicksToWait,等待的節拍數。如果消息緩沖區里沒有消息,任務可以進入阻塞狀態等待。若設置為0,則表示不等待;若設置為portMAX_DELAY,則表示一直等待。
????????函數xStreamBufferReceive()會自動區分參數xMessageBuffer是流緩沖區,還是消息緩沖區。如果是消息緩沖區,它會先讀取表示消息長度的4字節消息頭,然后按照長度讀取后面的消息數據。
????????函數xMessageBufferReceive()返回的是實際讀取的消息的字節數,不包括消息頭的4字節。如果函數是因為等待超時而退出的,則返回值為0。
????????在ISR中從消息緩沖區讀取消息的函數是xMessageBufferReceiveFromISR(),它是個宏函數,實際就是執行了函數xStreamBufferReceiveFromISR(),其原型定義如下:
/**
* \defgroup xMessageBufferReceiveFromISR xMessageBufferReceiveFromISR
* \ingroup MessageBufferManagement
*/
#define xMessageBufferReceiveFromISR( xMessageBuffer, pvRxData, xBufferLengthBytes, pxHigherPriorityTaskWoken ) xStreamBufferReceiveFromISR( ( StreamBufferHandle_t ) xMessageBuffer, pvRxData, xBufferLengthBytes, pxHigherPriorityTaskWoken )
(4)消息緩沖區狀態查詢
????????以下幾個查詢消息緩沖區狀態的函數,只需使用消息緩沖區的句柄作為函數的輸入參數。
- xMessageBufferIsEmpty()查詢一個消息緩沖區是否為空,若返回pdTRUE,則表示緩沖區不包含任何消息。
- xMessageBufferIsFull()查詢一個消息緩沖區是否已滿,若返回pdTRUE,則表示不能再寫入任何消息。
- xMessageBufferSpacesAvailable()查詢一個消息緩沖區剩余的存儲空間字節數,返回值類型為uint32_t。
三、消息緩沖區使用示例
1、示例功能與CubeMX項目設置
????????本示例演示消息緩沖區的使用,實例的功能和使用流程如下。
- 創建一個消息緩沖區和一個任務Task_Show。
- 使用RTC的喚醒中斷,喚醒周期為1s。在RTC的喚醒中斷里讀取當前時間,轉化為字符串后,作為消息寫入消息緩沖區,每次寫入的消息長度不一樣。
- 在任務Task_Show里讀取消息緩沖區的消息,并在串口助手上顯示。
-
繼續使用旺寶紅龍開發板STM32F407ZGT6 KIT V1.0。
-
一些設置可以參考本文作者寫的其他文章:
????????細說STM32單片機FreeRTOS流緩沖區及其應用實例-CSDN博客 ?https://wenchm.blog.csdn.net/article/details/148168854?spm=1011.2415.3001.5331
(1)RCC、SYS、Code Generator、USART3、TIM6
????????配置時鐘樹,將APB1定時器時鐘頻率設置為84MHz,APB2定時器時鐘頻率設置為168MHz ;設置TIM6作為基礎時鐘源;其它設置可見參考文章。
(2)RTC的設置
????????啟用LSE,啟用RTC,在時鐘樹上將LSE作為RTC的時鐘源。啟用周期喚醒功能,設置喚醒周期為1s,其他參數用默認值即可。
?
(3)FreeRTOS的設置
????????設置FreeRTOS接口為CMSIS_V2,所有“Config”和“Include”參數保持默認值。在FreeRTOS里創建一個任務Task_Show,其主要參數如圖所示。
(4)NVIC
????????在NVIC里開啟RTC喚醒中斷,設置其中斷優先級為5,因為要在其ISR里使用FreeRTOS API函數。
2、程序功能實現
(1)主程序
????????完成設置后,CubeMX自動生成代碼。在CubeIDE中打開項目,添加用戶功能代碼后,主程序代碼如下:
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "rtc.h"
#include "usart.h"
#include "gpio.h"/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);/*** @brief The application entry point.* @retval int*/
int main(void)
{/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_RTC_Init();MX_USART3_UART_Init();/* USER CODE BEGIN 2 *///Start Menuuint8_t startstr[] = "Demo9_2:Using Message Buffer.\r\n\r\n";HAL_UART_Transmit(&huart3,startstr,sizeof(startstr),0xFFFF);/* USER CODE END 2 *//* Init scheduler */osKernelInitialize();/* Call init function for freertos objects (in cmsis_os2.c) */MX_FREERTOS_Init();/* Start scheduler */osKernelStart();/* We should never get here as control is now taken by the scheduler *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}
(2)FreeRTOS對象初始化
????????自動生成的函數MX_FREERTOS_Init()只創建了任務,在CubeMX里不能可視化地創建消息緩沖區,需要在CubeMX生成的CubeIDE初始代碼的基礎上,編程創建消息緩沖區。在文件freertos.c中定義兩個常量和消息緩沖區對象,在函數MX_FREERTOS_Init()中增加創建消息緩沖區對象的代碼。完成后的代碼如下:
? ? ? ? 自動生成includes,并手動添加私有includes:?
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "message_buffer.h"
#include "usart.h"
#include <stdio.h> //用到函數sprintf()
#include <string.h> //用到函數strlen()
/* USER CODE END Includes */
????????手動添加私有宏定義:
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define MSG_BUFFER_LEN 50 //消息緩存區長度,單位:字節
#define MSG_MAX_LEN 20 //消息最大長度,單位:字節
/* USER CODE END PD */
????????手動添加創建消息緩沖區句柄變量代碼;
? ? ? ? 自動生成任務函數句柄變量代碼:
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
MessageBufferHandle_t msgBuffer; //消息緩存區句柄變量
/* USER CODE END Variables */
/* Definitions for Task_Show */
osThreadId_t Task_ShowHandle;
const osThreadAttr_t Task_Show_attributes = {.name = "Task_Show",.stack_size = 256 * 4,.priority = (osPriority_t) osPriorityNormal,
};/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes *//* USER CODE END FunctionPrototypes */void AppTask_Show(void *argument);void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) *//*** @brief FreeRTOS initialization* @param None* @retval None*/
void MX_FREERTOS_Init(void)
{/* Create the thread(s) *//* creation of Task_Show */Task_ShowHandle = osThreadNew(AppTask_Show, NULL, &Task_Show_attributes);/* USER CODE BEGIN RTOS_THREADS *//* add threads, ... */msgBuffer=xMessageBufferCreate(MSG_BUFFER_LEN); //創建消息緩存區/* USER CODE END RTOS_THREADS */
}
(3)RTC的喚醒中斷
????????在RTC的喚醒中斷里讀取當前時間,將其轉換為字符串后寫入消息緩沖區。RTC喚醒中斷的回調函數是HAL_RTCEx_WakeUpTimerEventCallback()。為便于使用消息緩沖區句柄變量msgBuffer,在文件freertos.c中重新實現這個回調函數:
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{RTC_TimeTypeDef sTime;RTC_DateTypeDef sDate;if (HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)return;if (HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN) !=HAL_OK)return;char dtArray[MSG_MAX_LEN]; //存儲消息的數組, MSG_MAX_LEN=20if ((sTime.Seconds % 2)==0) //分奇偶秒,發送不同長度的消息字符串siprintf(dtArray,"Seconds = %u",sTime.Seconds); //轉換為字符串,自動加'\0'elsesiprintf(dtArray,"Minute= %u",sTime.Minutes); //轉換為字符串,自動加'\0'uint8_t bytesCount=strlen(dtArray); //字符串長度,不帶最后的結束符BaseType_t highTaskWoken=pdFALSE;if (msgBuffer != NULL){uint16_t realCnt=xMessageBufferSendFromISR(msgBuffer,dtArray, bytesCount+1, &highTaskWoken); // bytesCount+1,帶結束符'\0'printf("Write bytes= %d\r\n", realCnt); //實際寫入消息長度portYIELD_FROM_ISR(highTaskWoken); //申請進行一次任務調度}
}int __io_putchar(int ch)
{HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);return ch;
}
/* USER CODE END Application */
????????上述程序首先讀取RTC的時間和日期,根據當前時間的秒數是奇數還是偶數,生成不同長度的字符串數據并保存到數組dtArray里。這里用到了C語言標準庫中的兩個函數siprintf()和strlen()。siprintf()與printf()類似,只是把字符串寫入一個數組,并且在字符串最后自動添加結束符\0。strlen()用于得到字符串的長度,但是不包括最后的結束符。
????????在使用函數xMessageBufferSendFromISR()向消息緩沖區寫入消息時,執行的代碼如下:
uint16_t realCnt = xMessageBufferSendFromISR(msgBuffer,dtArray,bytesCount+1,&highTaskwoken);
????????這里傳遞的第3個參數值是bytesCount+1,也就是加上了字符串的結束符,否則,讀取者讀出的消息字符串將不帶結束符,串口助手將無法正常顯示字符串。bytesCount+1的值必須小于或等于MSG_MAX_LEN。
????????函數的返回值realCnt是實際寫入的消息長度,不帶消息頭的4個字節。如果消息寫入成功,那么realCnt等于bytesCount+1。
????????這里寫入消息的數據是字符串,這只是為了演示方便,實際寫入消息的數據可以是任意類型的數據,而不一定是字符串。
(4)任務Task_Show的功能
????????在任務Task_Show里讀取消息緩沖區里的消息,并在串口助手上顯示,其任務函數代碼如下:
/* USER CODE BEGIN Header_AppTask_Show */
/*** @brief Function implementing the Task_Show thread.* @param argument: Not used* @retval None*/
/* USER CODE END Header_AppTask_Show */
void AppTask_Show(void *argument)
{/* USER CODE BEGIN AppTask_Show *//* Infinite loop */uint8_t dtArray[MSG_MAX_LEN]; //讀出的數據臨時保存數組for(;;){uint16_t realCnt=xMessageBufferReceive(msgBuffer, dtArray,MSG_MAX_LEN, portMAX_DELAY); //讀取消息printf("Read message bytes = %d\r\n", realCnt); //實際讀出字節數printf("message string Read = %s\r\n", dtArray); //顯示讀出的消息字符串}/* USER CODE END AppTask_Show */
}
????????上述程序用函數xMessageBufferReceive()讀取消息緩沖區里的消息,然后在串口助手上顯示實際讀取的消息長度和消息字符串。調用函數xMessageBufferReceive()的代碼如下:
uint16_t realCnt = xMessageBufferReceive(msgBuffer,dtArray,MSG_MAX_LEN,portMAX_DELAY);
????????其中,dtArray是用于存儲讀出數據的uint8_t類型數組,傳遞的第3個參數是MSG_MAX_LEN,也就是最大可以讀取的消息的長度。函數返回值realCnt是實際讀取的消息的長度,不包括消息頭的4個字節。MSG_MAX_LEN應該大于或等于realCnt,否則,會導致無法讀出一條完整的消息。
3、運行調試
????????構建項目后,下載到開發板上并運行測試,會發現顯示的寫入消息長度和讀出消息長度是一致?的,串口助手上顯示的消息字符串也是正確的,說明可以寫入和讀出不同長度的消息。在實際使用消息緩沖區時,寫入者和讀取者之間應該定義好消息的格式,如同串口通信一樣定義通信協議。