STM32CubeMX学习笔记24---FreeRTOS(消息队列)

 一. 队列简介     

         队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中 断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之 间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的 长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以 也称为消息队列。FreeRTOS 中的信号量的也是依据队列实现的!所以有必要深入的了解 FreeRTOS 的队列。 

1、数据存储

        通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫入队)永 远都是发送到队列的尾部,而从队列提取数据的时候(也叫出队)是从队列的头部提取的。但是 也可以使用 LIFO 的存储缓冲,也就是后进先出,FreeRTOS 中的队列也提供了 LIFO 的存储缓 冲机制。 数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在 队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传 递。学过 UCOS 的同学应该知道,UCOS 的消息队列采用的是引用传递,传递的是消息指针。 采用引用传递的话消息内容就必须一直保持可见性,也就是消息内容必须有效,那么局部变量 这种可能会随时被删掉的东西就不能用来传递消息,但是采用引用传递会节省时间啊!因为不 用进行数据拷贝。 采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原 始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可以被重复的使用。FreeRTOS 中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来传递消息啊,我直接 往队列中发送指向这个消息的地址指针不就可以了!这样当我要发送的消息数据太大的时候就 可以直接发送消息缓冲区的地址指针,比如在网络应用环境中,网络的数据量往往都很大的, 采用数据拷贝的话就不现实。 

2、多任务访问

        队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中 提取消息。 

3、出队阻塞  

         当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任 务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是 针对从队列中读取消息的任务而言的。比如任务 A 用于处理串口接收到的数据,串口接收到数 据以后就会放到队列 Q 中,任务 A 从队列 Q 中读取数据。但是如果此时队列 Q 是空的,说明 还没有数据,任务 A 这时候来读取的话肯定是获取不到任何东西,那该怎么办呢?任务 A 现在 有三种选择,一:二话不说扭头就走,二:要不我在等等吧,等一会看看,说不定一会就有数 据了,三:死等,死也要等到你有数据!选哪一个就是由这个阻塞时间决定的,这个阻塞时间 单位是时钟节拍数。阻塞时间为 0 的话就是不阻塞,没有数据的话就马上返回任务继续执行接 下来的代码,对应第一种选择。如果阻塞时间为 0~ portMAX_DELAY,当任务没有从队列中获 取到消息的话就进入阻塞态,阻塞时间指定了任务进入阻塞态的时间,当阻塞时间到了以后还 没有接收到数据的话就退出阻塞态,返回任务接着运行下面的代码,如果在阻塞时间内接收到 了数据就立即返回,执行任务中下面的代码,这种情况对应第二种选择。当阻塞时间设置为 portMAX_DELAY 的话,任务就会一直进入阻塞态等待,直到接收到数据为止!这个就是第三种选择。         

4、入队阻塞 

        入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队 列发送消息的话也可以设置阻塞时间。比如任务 B 向消息队列 Q 发送消息,但是此时队列 Q 是 满的,那肯定是发送失败的。此时任务 B 就会遇到和上面任务 A 一样的问题,这两种情况的处 理过程是类似的,只不过一个是向队列 Q 发送消息,一个是从队列 Q 读取消息而已。

5、队列操作过程图示

下面几幅图简单的演示了一下队列的入队和出队过程。 

  向队列发送第一个信息

图中任务 A 的变量 x 值为 10,将这个值发送到消息队列中。此时队列剩余长度就是 3 了。前面说了向队列中发送消息是采用拷贝的方式,所以一旦消息发送完成变量 x 就可以再 次被使用,赋其他的值。

向队列发送第二个信息

图中任务 A 又向队列发送了一个消息,即新的 x 的值,这里是 20。此时队列剩余长 度为 2。

图中任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10 了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个 消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变成 3。如果 不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2。

以上引用于:FreeRTOS消息队列-CSDN博客

二、STM32CubeMX设置

 (1)、根据上一章的步骤创建两个任务:

STM32CubeMX学习笔记22---FreeRTOS(任务创建和删除)-CSDN博客

任务LED1用作发送,LED2用作接收。

 (2)、创建消息队列

  • Queue Name: 队列名称
  • Queue Size: 队列能够存储的最大单元数目,即队列深度
  • Queue Size: 队列中数据单元的长度,以字节为单位
  • Allocation: 分配方式:Dynamic 动态内存创建
  • Buffer Name: 缓冲区名称
  • Buffer Size: 缓冲区大小
  • Conrol Block Name: 控制块名称
(3)、生成代码

三、程序编程

相关函数

1、队列ID:osMessageQId

在freertos.c文件下定义了队列的ID,对队列的操作都需要用到这个ID,例如,对osMessageCreate的调用返回。可用作参数到osMessageDelete以删除队列。

osMessageQId myQueue01Handle;
2、使用动态内存的方式创建一个新的队列:osMessageCreate
函数osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id)
参数queue_def: 引用由osMessageQDef定义的队列

thread_id: 线程ID或NULL
返回值成功返回任务ID,失败返回0

 例:

  /* Create the queue(s) */
  /* definition and creation of myQueue01 */
  osMessageQDef(myQueue01, 16, uint32_t);//定义myQueue01队列,第2参数:消息队列的长度,第3参数:消息的大小
  myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);//创建队列
3、队列删除:osMessageDelete

        队列删除函数是根据消息队列ID直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了。

函数osStatus osMessageDelete (osMessageQId queue_id)
参数queue_id: 消息队列ID,表示的是要删除哪个想队列
返回值错误码

例:

osMessageDelete(TestQueueHandle);
4、向队列尾部发送一个队列消息:osMessagePut

用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以引用的形式。可用在中断服务程序中。

函数osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)
参数

        queue_id: 目标队列ID。这个句柄即是调用 osMessageCreate() 创建该队列时的返回值

        info: 发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域。

        millisec: 队列空时,阻塞超时的最大时间。如果该参数设置为 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)。

返回值错误码

 例:

osMessagePut(TestQueueHandle,send_data1,0);
//三个参数分别为:消息队列的句柄,发送的消息内容,等待时间
5、从一个队列中接收消息并把消息从队列中删除:osMessageGet 

        用于从一个队列中接收消息并把消息从队列中删除。接收的消息是以拷贝的形式进行的,所以我们必须提供一个足够大空间的缓冲区。具体能够拷贝多少数据到缓冲区,这个在队列创建的时候已经设定。可用在中断服务程序中。

例: 

osMessageGet(TestQueueHandle,osWaitForever);
//两个参数分别为:消息队列的句柄,等待时间(此时为一直等待)

//使用范例
osEvent event;
event=osMessageGet(TestQueueHandle,osWaitForever);//一直等待
 6、从队列中接收数据单元,但是并不删除接收到的单元:osMessagePeek

        osMessagePeek() 也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。osMessagePeek() 从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。可用在中断服务程序中。

 7、查询队列中当前有校的数据单元个数:osMessageWaiting

例:

uint32_t a=osMessageWaiting(TestQueueHandle);

编写代码

1、传输数字

(1)、发送

void LED1_Task1(void const * argument)
{
  /* USER CODE BEGIN LED1_Task1 */
  /* Infinite loop */
	osEvent xReturn;
  uint32_t send_data1;
  for(;;)
  {
		
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);   //LED1状态每500s翻转一次
		
		printf("this LED1 run %d\r\n",++send_data1);
		xReturn.status=osMessagePut(myQueue01Handle,send_data1,0);
			
		if(osOK!=xReturn.status)
		{
			printf("send fail\n");
		}

		
    osDelay(500);
  }
  /* USER CODE END LED1_Task1 */
}

(2)、发送 

void LED2_Task03(void const * argument)
{
  /* USER CODE BEGIN LED2_Task03 */
  /* Infinite loop */
	 osEvent event;
	
  for(;;)
  { 
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);   //LED1状态每500s翻转一次
		event=osMessageGet(myQueue01Handle,osWaitForever);
		
	if(osEventMessage==event.status)
	{
		printf("receive data:%d\n",event.value.v);
	}
	else
	{
		printf("error:0x%d\n",event.status);
	}
   // osDelay(600);
  }
  /* USER CODE END LED2_Task03 */
}

(3)、下载验证:

程序编译无误后下载到板子上,可以看到LED1发送的数据,LED2均能接收。

2、传输字符串或者结构体

(1)、发送

typedef struct
{
	uint8_t* name;
	uint8_t id;
	uint8_t age;
}T_data;


/* USER CODE END Header_LED1_Task1 */
void LED1_Task1(void const * argument)
{
  /* USER CODE BEGIN LED1_Task1 */
  /* Infinite loop */
	osEvent xReturn;
  uint32_t send_data1;
  for(;;)
  {
		T_data m_data;
		uint8_t name1[]="H2ZStr";
		
		m_data.age=20;
		m_data.id=2;
		m_data.name=name1;

		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);   //LED1状态每500s翻转一次
		
		printf("LED1 send !!\n");
		xReturn.status=osMessagePut(myQueue01Handle,(uint32_t)&m_data,0);
			
		if(osOK!=xReturn.status)
		{
			printf("send fail\n");
		}
    osDelay(500);
  }
  /* USER CODE END LED1_Task1 */
}

(2)、接收

void LED2_Task03(void const * argument)
{
  /* USER CODE BEGIN LED2_Task03 */
  /* Infinite loop */
	 osEvent event;
	
  for(;;)
  { 
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);   //LED1状态每500s翻转一次
		event=osMessageGet(myQueue01Handle,osWaitForever);
			if(event.status==osEventMessage)
		{
			T_data *pData=(T_data *)event.value.p;
			
			printf("data=%d\n",pData->age);
			printf("id=%d\n",pData->id);
			printf("name=%s\n",pData->name);
		}
   // osDelay(600);
  }
  /* USER CODE END LED2_Task03 */
}

(3)、下载验证:

程序编译无误后下载到板子上,可以看到LED1发送的数据,LED2均能接收。

3、以邮箱的形式传输数据

邮箱不能使用cubemx自动生成,需要手动添加

(1)、邮箱发送

typedef struct
{
	uint8_t* name;
	uint8_t id;
	uint8_t age;
}T_data;

	osMailQId mailQ01Handle; //定义邮箱ID

/* USER CODE END Header_LED1_Task1 */
void LED1_Task1(void const * argument)
{
  /* USER CODE BEGIN LED1_Task1 */
  /* Infinite loop */
	

 
	osMailQDef(mailQ01,15,T_data);
	mailQ01Handle=osMailCreate(osMailQ(mailQ01),NULL); //创建邮箱
	
	osEvent xReturn;
  uint32_t send_data1;
  for(;;)
  {
		T_data m_data;
		uint8_t name1[]="H2ZStr";
		
		m_data.age=20;
		m_data.id=2;
		m_data.name=name1;

		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);   //LED1状态每500s翻转一次
		
		printf("LED1 send !!\n");
		
		osMailPut(mailQ01Handle,&m_data); //邮箱发送
    osDelay(500);
  }
  /* USER CODE END LED1_Task1 */
}

(2)邮箱接收

void LED2_Task03(void const * argument)
{
  /* USER CODE BEGIN LED2_Task03 */
  /* Infinite loop */
	
  for(;;)
  { 
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);   //LED1状态每500s翻转一次
		osEvent event=osMailGet(mailQ01Handle,osWaitForever);
		if(event.status==osEventMail)
		{
			T_data *m_Data=(T_data *)event.value.p;
			printf("data=%d\n",m_Data->age);
			printf("id=%d\n",m_Data->id);
			printf("name=%s\n",m_Data->name);			
		}
   // osDelay(600);
  }
  /* USER CODE END LED2_Task03 */
}

(3)、下载验证:

程序编译无误后下载到板子上,可以看到LED1发送的数据,LED2均能接收。

 

四、参考文献

韦东山freeRTOS系列教程之【第五章】队列(queue)_freertos queue-CSDN博客

STM32CubeMX学习笔记(29)——FreeRTOS实时操作系统使用(消息队列)_cubemx的sys里的timebase source-CSDN博客 stm32cubemx hal学习记录:FreeRTOS消息队列_osmessagecreate-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/470785.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SLAM 求解IPC算法

基础知识:方差,协方差,协方差矩阵 方差:描述了一组随机变量的离散程度 方差 每个样本值 与 全部样本的平均值 相差的平方和 再求平均数,记作: 例如:计算数字1-5的方差,如下 去中心化…

Vulnhub靶机渗透:DC-7打靶记录

前言 自信自强,来自于不怕苦、不怕难的积淀。宝剑锋从磨砺出,梅花香自苦寒来;任何美好理想,都离不开筚路蓝缕、手胼足胝的艰苦奋斗! 靶场介绍 DC-7是一个初中级的靶场,需要具备以下前置知识:…

【开源】SpringBoot框架开发不良邮件过滤系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统用户模块2.2 收件箱模块2.3 发件箱模块2.4 垃圾箱模块2.5 回收站模块2.6 邮箱过滤设置模块 三、实体类设计3.1 系统用户3.2 邮件3.3 其他实体 四、系统展示五、核心代码5.1 查询收件箱档案5.2 查询回收站档案5.3 新…

[Qt项目实战]Qt实现美松标签打印机标签二维码打印(QR混排模式+页打印模式)

1、硬件信息、环境参数及配套资料 1.1 打印机信息及开发环境 打印机 美松标签打印机串口/USB通讯Qt5.9 64位程序 1.2 打印机配套开发资料 打印机主要配套测试工具、开发SDK及驱动等,均由厂家提供。 开发Demo及动态库:MsPrintSDK-DLL-V2.2.2.5 链接&…

3、java虚拟机-类的生命周期-初始化阶段(与程序员有关)

一 、静态代码块执行顺序和字节码文件中的执行顺序以及什么赋值。 类的生命周期-初始化阶段-被static所修饰的常量才会被赋予值 初始化阶段-代码中静态代码块和静态变量的顺序和字节码中的执行顺序是一致的。 二、4种情况下,类会被初始化。 1、怎样查看类是…

阿里云部署MySQL、Redis、RocketMQ、Nacos集群

文章目录 🔊博主介绍🥤本文内容MySQL集群配置云服务器选购CPU选择内存选择云盘选择ESSD AutoPL云盘块存储性能(ESSD) 镜像选择带宽选择密码配置注意事项 搭建宝塔面板方便管理云服务器云服务器的安全组安装docker和docker-compose…

Go语言学习13-常见软件架构的实现

Go语言学习13-常见软件架构的实现 架构模式 An architectural pattern is a general, reusable solution to a commonly occurring problem in software architectural within a given context. ——wikipedia Pipe-Filter 架构 Pipe-Filter 模式 非常适合于数据处理及数据分…

taro之Picker,PickerView基础用法

1.Picker 直接上代码 import Taro,{Component} from "tarojs/taro"; import {View,Picker} from tarojs/components import { AtIcon } from taro-ui import { putKey } from /src/utils/storage-utilsclass AgriculturePolicy extends Component{constructor (prop…

su: authentication failure 解决方法

产生问题的原因:使用su和sudo是有区别的,root的密码可能是原始的密码,你现在用户的密码大概率是更改过的 解决办法: 在Linux上切换root时,密码正确。。但提示:su: authentication failure ->sudo pa…

统计学基础概念和在AI中的应用

基本概念 统计学是一门研究数据收集、分析、解释和展示的科学,它提供了一套方法论,用于理解数据并从数据中得出结论。统计学在各个领域都有应用,包括经济学、医学、工程学、社会科学等。以下是统计学的一些基本概念: 描述性统计…

wireshark数据捕获实验简述

Wireshark是一款开源的网络协议分析工具,它可以用于捕获和分析网络数据包。是一款很受欢迎的“网络显微镜”。 实验拓扑图: 实验基础配置: 服务器: ip:172.16.1.88 mask:255.255.255.0 r1: sys sysname r1 undo info enable in…

B站python爬虫课程笔记(Q16-)

下面是学习的网址: ​​​​​​【Python爬虫】 16、捕捉异常try&except语句的一些问题 1)一些常见的异常类型 IndexError索引错误ZeroDivisionError除零错误FileNotFindError找不到文件错误TypeError类型错误KeyError键错误ValueError值错误Ind…

代理IP品质对Tik Tok代理的重要性

随着Tik Tok的迅速崛起,越来越多的人开始关注如何透过Tik Tok进行行销和推广。其中,使用Tik Tok代理程式是常见的方法。 然而,在选择和使用代理时,IP品质是一个不可忽视的因素。本文将探讨IP品质对Tik Tok代理的重要性&#xff0…

【征稿进行时|见刊、检索快速稳定】2024年区块链、物联网与复合材料与国际学术会议 (ICBITC 2024)

【征稿进行时|见刊、检索快速稳定】2024年区块链、物联网与复合材料与国际学术会议 (ICBITC 2024) 大会主题: (主题包括但不限于, 更多主题请咨询会务组苏老师) 区块链: 区块链技术和系统 分布式一致性算法和协议 块链性能 信息储存系统 区块链可扩展性 区块…

Springboot笔记-04

1.PropertySource&ImportResource&Bean PropertySource:加载指定的配置文件,只能用于properties文件,不支持yml文件; 以person为例子: ConfigurationProperties:告诉springboot将本类中所有属性和配制文件相关的配制进行…

docker入门(一)—— docker概述

docker 概述 docker 官网:http://www.docker.com 官网文档: https://docs.docker.com/get-docker/ Docker Hub官网:https://hub.docker.com (仓库) 什么是 docker docker 是一个开源的容器化平台,可以…

ARM Cortex-R82处理器在压缩SSD场景的应用

ScaleFlux公司宣布在其下一代企业级SSD控制器产品线中采用Arm公司的Cortex-R82处理器。这一决策旨在应对企业环境中对高带宽存储解决方案日益增长的需求,并通过提升数据传输速度和效率来满足市场期待。 Arm Cortex-R82处理器是Arm公司迄今为止性能最强的实时处理器…

maven手动上传的第三方包 打包项目报错 Could not find xxx in central 解决办法

背景: 在Maven私服手动上传了第三方的jar包, 只有jar包, 没有pom文件, 项目在ide中可以正常编译启动,但打包报错无法找到jar包 解决办法: 上传jar包的时候, 点击生成pom. 则打包的时候不会报错

2.28线程

注意被抢占时是返回原队列,优先级不变。越往下优先级越小。往下没有优先级时,在最低的优先级队列里循环 到达了不一定会被服务,会进入就绪态进行等待 。核心等式就是周转时间运行时间等待时间,带权就是周转/运行, 随着…

wayland(xdg_wm_base) + egl + opengles 使用 Assimp 加载带光照信息的材质文件Mtl 实现光照贴图的最简实例(十七)

文章目录 前言一、3d 立方体 model 属性相关文件1. cube1.obj2. cube1.Mtl3. 纹理图片 cordeBouee4.jpg二、实现光照贴图的效果1. 依赖库和头文件1.1 assimp1.2 stb_image.h2. egl_wayland_obj_cube1.cpp3. Matrix.h 和 Matrix.cpp4. xdg-shell-client-protocol.h 和 xdg-shell…