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

news2025/1/19 17:22:46

 一. 队列简介     

         队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中 断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之 间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的 长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以 也称为消息队列。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.coloradmin.cn/o/1529922.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQLiteC/C++接口详细介绍sqlite3_stmt类(一)

返回目录:SQLite—免费开源数据库系列文章目录 上一篇:SQLiteC/C接口详细介绍sqlite3_stmt类简介 下一篇:SQLiteC/C接口详细介绍sqlite3_stmt类(二) ​ 序言: 本文开始了SQLite的第二个类的详细介绍…

JavaScript实现简单的表单验证

关键代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><s…

基于python企业办公文件分类系统flask-django-nodejs-php

系统内容可以随时更新&#xff0c;这点对于现代企业通知与文件分享管理来说是很重要&#xff0c;但传统的管理方式都无法做到的。企业办公文件分类系统就可以每天更新&#xff0c;随时反映您企业通知与文件分享的最新情况。 网络的广泛应用给生活带来了十分的便利。所以把企业…

React - 实现菜单栏滚动

简介 本文将会基于react实现滚动菜单栏功能。 技术实现 实现效果 点击菜单&#xff0c;内容区域会自动滚动到对应卡片。内容区域滑动&#xff0c;指定菜单栏会被选中。 ScrollMenu.js import {useRef, useState} from "react"; import ./ScrollMenu.css;export co…

Springboot笔记-04

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

【RabbitMQ | 第七篇】RabbitMQ实现JSON、Map格式数据的发送与接收

文章目录 7.RabbitMQ实现JSON、Map格式数据的发送与接收7.1消息发送端7.1.1引入依赖7.1.2yml配置7.1.3RabbitMQConfig配置类——&#xff08;非常重要&#xff09;&#xff08;1&#xff09;创建交换器方法&#xff08;2&#xff09;创建队列方法&#xff08;3&#xff09;绑定…

牛客NC241 计算器(二)【中等 dfs+双端队列 Java】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/a9c170bfaf7349e3acb475d786ab1c7d 核心 DFS双端队列参考答案Java import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定…

L1-027 出租(PTA)

文章目录 L1-027 出租题目描述模拟哈希表二分查找 L1-027 出租 题目描述 下面是新浪微博上曾经很火的一张图&#xff1a; 一时间网上一片求救声&#xff0c;急问这个怎么破。其实这段代码很简单&#xff0c;index数组就是arr数组的下标&#xff0c;index[0]2 对应 arr[2]1&a…

C#,图论与图算法,有向图(Graph)之环(Cycle)判断的颜色算法与源代码

1 检查该图是否包含循环 给定一个有向图,检查该图是否包含循环。如果给定的图形至少包含一个循环,则函数应返回true,否则返回false。 方法:深度优先遍历可用于检测图中的循环。连接图的DFS生成树。只有当图中存在后缘时,图中才存在循环。后边是从节点到自身(自循环)或…

锂电极片生产中机器视觉系统的多元检测能力

随着新能源行业的快速发展&#xff0c;锂电池作为核心组件&#xff0c;其生产质量受到了前所未有的关注。在锂电极片的生产过程中&#xff0c;机器视觉系统以其高精度、高效率的特点&#xff0c;成为了保障产品质量的关键工具。本文将探讨机器视觉系统在锂电极片生产中可以检测…

竞争优势:大型语言模型 (LLM) 如何重新定义业务策略

人工智能在内容创作中的突破 在当今快节奏的商业环境中&#xff0c;像 GPT-4 这样的大型语言模型 (LLM) 不再只是一种技术新颖性&#xff1b; 它们已成为重新定义跨行业业务战略的基石。 从增强客户服务到推动创新&#xff0c;法学硕士提供了企业不容忽视的竞争优势。 1. 加强…

工业相机采图方式、图像格式(BYTE、HObject和Mat)转换

1、概述 机器视觉项目中&#xff0c;如何采集到合适的图像是项目的第一步&#xff0c;也是最重要的一步&#xff0c;直接关系到后面图像处理算法及最终执行的结果。所以采用不同的工业相机成像以及如何转换成图像处理库所需要的格式成为项目开发中首先要考虑的问题。 2、工业…

【Micropython ESP32】RTC时钟

文章目录 前言一、RTC时钟的介绍1.1 RTC时钟的作用1.2 Micropython中时钟于硬件时钟的区别 二、machine.RTC 类2.1 machine.RTC 类的构造方法2.2 初始化 RTC 设备起始时间2.3 关闭 RTC 设备2.4 获取当前时间 三、示例代码总结 前言 在嵌入式设备开发中&#xff0c;实时时钟&am…

001_【基础篇】SpringBoot入门案例创建与实现

要求&#xff1a;使用 Springboot 开发一个 web 程序&#xff0c;浏览器发起请求/hello后&#xff0c;给浏览器返回字符串 hello springboot 使用 springboot 只需要引入一个起步依赖 <dependency><groupId>org.springframework.boot</groupId><artifac…

Python 自然语言处理库之stanza使用详解

概要 在自然语言处理(NLP)领域,Python Stanza 库是一个备受推崇的工具,它提供了强大的功能和易用的接口,帮助开发者处理文本数据、进行语言分析和构建NLP应用。本文将深入探讨 Stanza 库的特性、用法,并通过丰富的示例代码展示其在实际项目中的应用。 Stanza 简介 Stan…

docker小白第十四天之Portainer与CIG

Portainer简介 Portainer是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 Portainer命令安装 # 一个容器可以同时起多个-p端口&#xff0c;restartalways表示随时在线&#xff0c;重启机器后也…

Tomcat 服务器部署和 IDEA 配置 Tomcat

(一) Tomcat 简介 Tomcat是Apache软件基金会一个核心项目&#xff0c;是一个开源免费的轻量级Web服务器&#xff0c;支持Servlet/JSP少量JavaEE规范。 概念中提到了JavaEE规范&#xff0c;那什么又是JavaEE规范呢? JavaEE: Java Enterprise Edition,Java企业版。指Java企业级…

CentOS 7.9 常用环境配置

文章目录 环境准备安装docker安装Java安装maven安装git安装MYSQL安装Redis安装RabbitMq安装minio 环境准备 操作系统版本为centos 7.9&#xff0c;内核版本需要在3.10以上 sudo uname -rsudo cat /etc/redhat-release1.确认环境好后&#xff0c;安装工具包并设置仓库 sudo yum…

解决由于历史原因解析tflite失败的问题

文章目录 0. 背景1. tflite 历史遗留问题2. schema3. flatbuffers 编译器3.1 安装 FlatBuffers 编译器3.2. 编译 FlatBuffers schema 文件3.3 使用生成的 Python 文件 4 问题未解决终极解决方案 写在最前面&#xff1a;解决方法是升级tensorflow版本&#xff0c;重新生成tflite…

算法---二分查找练习-2(寻找旋转排序数组中的最小值)

寻找旋转排序数组中的最小值 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 首先&#xff0c;检查数组的最后一个元素是否大于第一个元素。如果是&#xff0c;说明数组没有进行旋转&#xff0c;直接返回第一个元素作为最小值…