当前位置: 首页 > news >正文

细说STM32单片机FreeRTOS消息缓冲区及其应用实例

目录

一、消息缓冲区功能概述

二、消息缓冲区操作相关函数

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、运行调试

        构建项目后,下载到开发板上并运行测试,会发现显示的写入消息长度和读出消息长度是一致 的,串口助手上显示的消息字符串也是正确的,说明可以写入和读出不同长度的消息。在实际使用消息缓冲区时,写入者和读取者之间应该定义好消息的格式,如同串口通信一样定义通信协议。

相关文章:

  • 精益数据分析(84/126):打造商业造钱机器——从融资思维到盈利模型的落地实践
  • 【DAY28】类的定义和方法
  • 2025家政预约小程序开发:功能模块解析与行业解决方案
  • 【MySQL】CRUD
  • OpenSSH 9.9p2 编译安装全流程指南
  • Linux概述
  • go多线程压测监控
  • [Linux] 再谈 Linux Socket 编程技术(代码示例)
  • 【AI论文】工具之星(Tool-Star):通过强化学习赋能具备大型语言模型(LLM)思维的多工具推理器
  • 一体化雷达波明渠流量计简介
  • 【数据集】中国大陆城市建筑楼面面积高分辨率数据集(2017年)
  • Vue 3 路由传参使用指南
  • JavaSE核心知识点03高级特性03-04(Lambda表达式)
  • 【RocketMQ 生产者和消费者】- 生产者启动源码 - MQClientInstance 定时任务(4)
  • 开盘啦 APP 抓包 逆向分析
  • 真实案例拆解:智能AI客服系统中的两类缓存协同
  • 高分辨率北半球多年冻土数据集(2000-2016)
  • 7.1查找的基本概念
  • 第307个VulnHub靶场演练攻略Corrosion: 2
  • 可编程运动控制器行业2025数据分析报告
  • 潍坊营销型网站制作/深圳排名seo
  • 网站开发培训学院/cms自助建站系统
  • 网页制作三剑客软件/苏州网站优化公司
  • ftp上传网站全教程/营销活动怎么做吸引人
  • 盐城网站定制/电商大数据查询平台免费
  • flash 如何做游戏下载网站/创意设计