FreeRTOS基础(十一):消息队列

news2024/10/5 17:15:43

     本文将详细全方位的讲解FreeRTOS的消息队列,其实在FreeRTOS中消息队列的重要性也不言而喻,与FreeRTOS任务调度同等重要,因为后面的各种信号量基本都是基于消息队列的。

目录

一、消息队列的简介

1.1 产生的原因

1.2 消息队列的解决办法

1.3 消息队列的基本概念

1.4 队列的特点

1.5 队列的入队和出队

1.6 队列操作基本过程

二、队列结构体介绍

三、队列相关API函数介绍

3.1 消息队列的使用流程

3.2 创建消息队列API函数

3.3 往队列写入消息API函数

3.4 从队列读取消息API函数

四、队列操作实验

五、队列相关API函数解析(了解)


一、消息队列的简介

1.1 产生的原因

       假设有一个全局变量a = 0,现有两个任务都在对变量a进行自增操作(写操作),如下图所示:

         对于自增操作它不是原子操作,会经过一系列的步骤,最后才将自增后的结果写入到寄存器,如果任务2的优先级高于任务1,在任务1将自增后的2即将写入到a之前,任务2打断任务1进行自增操作,任务2拿到的是a为1,然后再进行的自增,两个任务执行完后自增其实只进行了一次数据无保护,导致数据不安全,当多个任务同时对该全局变量操作时,数据易受损!!其实这就像是Linux中的由于线程的并发运行,导致线程对进程资源的竞争问题,不过在这里是由于任务的优先级影响的。

       因此,使用全局变量并不是安全的,于是产生了消息队列。消息队列可以理解成带中断保护的全局数组,它是用来存放数据的,我们是将他设计成一个队列结构体。

1.2 消息队列的解决办法

      使用队列的情况如下:

队列依旧是来存放数据的,可以看成是全局数组,

       我们对于队列的读写封装好了函数,并且在这个函数里面进行了保护(关闭中断带来的)关闭中断后,管理范围内的中断不会再响应,并且也不会进行任务的切换(PendSV任务切换中断也被关闭),也就是中断和其他任务都不能打断当前任务向队列中写入数据!,同理从队列读取数据也一样,这样保证同一时刻只有一个任务向队列写入数据或者从队列读取数据!因此,他可以做到:防止多任务同时访问冲突;我们只需要直接调用API函数(可以理解Linux中的可重入函数/线程安全函数)即可,简单易用!

1.3 消息队列的基本概念

       消息队列是任务到任务任务到中断中断到任务数据交流的一种机制(进行消息传递FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、 递归互斥信号量因此很有必要深入了解 FreeRTOS 的队列 。在消息队列中可以存储数量有限、大小固定的数据,可以将队列理解为一个全局数组。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。如下图所示:

我们在进行创建队列时,就必须要指定队列长度以及队列项目的大小!,然后操作系统就会为我们分配相应的内存空间!

1.4 队列的特点

     当任务从队列中读取消息(出队)时,如果此时队列为空,则此任务将进入阻塞状态,等待消息队列不为空。 用户可以指定阻塞等待的时间,当等待的时间超过阻塞等待时间,任务将结束阻塞状态,转为就绪态。同理,入队也是如此。我们可以指定的阻塞时间如下:

①若阻塞时间为:   0 :代表直接返回不会等待;(不进行阻塞)
②若阻塞时间为:0~port_MAX_DELAY (最大值0xffffffff) :代表等待设定的阻塞时间(一段时间内阻塞),若在该时间内还无法入队,超时后直接返回不再等待;
③若阻塞时间为:   port_MAX_DELAY(最大值0xffffffff) :代表死等,(一直阻塞)一直等到可以入队为止。

1.5 队列的入队和出队

      注意:我们通过前面学习知道:每个任务对应一个任务控制块,它就是一个结构体,里面有状态列表项和事件列表项两个成员。

队列满了,此时写不进去数据:

 ①将该任务的状态列表项挂载在pxDelayedTaskList(阻塞状态)

 ②将该任务的事件列表项挂载在xTasksWaitingToSend(等待发送)

队列为空,此时读取不了数据:

 ①将该任务的状态列表项挂载在pxDelayedTaskList (阻塞状态)

 ②将该任务的事件列表项挂载在xTasksWaitingToReceive(等待接收);

思考一个问题:

         当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务    在等待同一个队列的空间。那当队列中有可以写入的空间时,哪个任务会进入就绪态? 

答:

  1、优先级最高的任务

  2、如果大家的优先级相同,那等待时间最久的任务会进入就绪态

1.6 队列操作基本过程

二、队列结构体介绍

     队列结构体如下:

可以看到:里面有一个联合体,我们知道后面学的各种信号量都是基于队列实现的,那这个联合体就根据不同的结构,使用不同的成员。

队列结构体整体示意图:主要有两部分组成:队列结构体/队列控制块和队列项(存储数据的)构成。

三、队列相关API函数介绍

3.1 消息队列的使用流程

         在使用队列之前,我们一定要明确队列的使用流程,这样就会形成编码规范,不容易出错,使用队列的主要流程:创建队列 ——》写队列 ——》 读队列。

3.2 创建消息队列API函数

       创建消息队列函数主要有以下两个:

函数

描述

xQueueCreate()

动态方式创建队列

xQueueCreateStatic()

静态方式创建队列

动态和静态创建队列之间的区别:动态创建队列所需的内存空间由 FreeRTOS FreeRTOS 管理的堆中分配,而静态创建需要用户自行分配内存。我们通常用的是动态创建队列。

此函数用于使用动态方式创建队列,队列所需的内存空间由 FreeRTOS FreeRTOS 管理的堆中分配 ,可以看到,真正起作用的函数是:xQueueGenericCreate()

形参

描述

uxQueueLength

队列长度

uxItemSize

队列项目的大小

返回值

描述

NULL

队列创建失败

其他值

队列创建成功,返回队列句柄

xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE )) 

底层调用的是上述函数,前面两个参数很容易理解,但是最后这个呢?又该如何理解?前面说 FreeRTOS 基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的 queue.h 文件中有定义:

 可以看到,使用哪个功能,第三个参数底层就会使用相应的宏定义。

       从上面我们可以知道,引入队列的头文件,创建队列,我们需要提前定义好消息队列句柄,接收其返回值用于后续访问该消息队列,同时应该传入队列长度参数和队列项目大小参数。

3.3 往队列写入消息API函数

     向队列中写入消息一共有以下函数:

往队列写入消息函数入口参数解析:

形参

描述

xQueue

待写入的队列(它的类型是一个队列句柄)

pvItemToQueue

待写入消息 (传的是一个地址)

xTicksToWait

阻塞超时时间

xCopyPosition

写入的位置

返回值

描述

pdTRUE

队列写入成功

errQUEUE_FULL

队列写入失败

 从上面我们可以知道,往队列中写入消息,传入提前定义好消息队列句柄,以及写入消息的地址,和传入如果队列已经满时,该任务的阻塞时间即可!

3.4 从队列读取消息API函数

      从队列读取消息API函数:

 从上面我们可以知道,读取队列中消息,传入提前定义好消息队列句柄,以及读取消息存放的地址,和传入如果队列当前为空时,该任务的阻塞时间即可!

四、队列操作实验

创建任务文件

#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"
#include "mydelay.h"
#include "mykey.h"
#include "queue.h"


/**********************START_TASK任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/

#define        START_TASK_STACK_SIZE  128   //定义堆栈大小为128字(1字等于4字节)
#define        START_TASK_PRIO         1    //定义任务优先级,0-31根据任务需求
TaskHandle_t   start_task_handler;    //定义任务句柄(结构体指针)
void start_task(void* args);




/**********************TASK1任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK1_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK1_PRIO         2             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task1_handler;           //定义任务句柄(结构体指针)
void task1(void* args);




/**********************TASK2任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK2_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK2_PRIO         3             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task2_handler;           //定义任务句柄(结构体指针)
void task2(void* args);



/**********************TASK3任务配置******************************/
/***********包括任务堆栈大小、任务优先级、任务句柄、创建任务***********/
#define  TASK3_STACK_SIZE  128            //定义堆栈大小为128字(1字等于4字节)
#define  TASK3_PRIO         4             //定义任务优先级,0-31根据任务需求
TaskHandle_t   task3_handler;           //定义任务句柄(结构体指针)
void task3(void* args);


QueueHandle_t  key_queue;                 //小数据队列句柄
QueueHandle_t  big_date_queue;            //大数据队列句柄
char buff[100]="我是大数组12345678911111111111";


/*********开始任务用来创建一个任务,只创建一次,不能是死循环,创建完这个任务后删除开始任务本身***********/
void start_task(void* args)
{
	taskENTER_CRITICAL();        /*进入临界区*/
	
	xTaskCreate( (TaskFunction_t)         task1,
                             (char *)     "task1",  
              ( configSTACK_DEPTH_TYPE)   TASK1_STACK_SIZE,
                            (void *)      NULL,
                            (UBaseType_t) TASK1_PRIO ,
                        (TaskHandle_t *)  &task1_handler );
	

     xTaskCreate( (TaskFunction_t)         task2,
                             (char *)     "task2",  
              ( configSTACK_DEPTH_TYPE)   TASK2_STACK_SIZE,
                            (void *)      NULL,
                            (UBaseType_t) TASK2_PRIO ,
                        (TaskHandle_t *)  &task2_handler );							

							
	 xTaskCreate( (TaskFunction_t)         task3,
                             (char *)     "task3",  
              ( configSTACK_DEPTH_TYPE)   TASK3_STACK_SIZE,
                            (void *)      NULL,
                            (UBaseType_t) TASK3_PRIO ,
                        (TaskHandle_t *)  &task3_handler );	
							
							
	vTaskDelete(NULL);    //删除开始任务自身,传参NULL
							
	taskEXIT_CRITICAL();   /*退出临界区*/
		

    //临界区内不会进行任务的调度切换,出了临界区才会进行任务调度,抢占式						
}




/********任务1的任务函数,无返回值且是死循环***********/

/*****任务1:实现入队*******/
void task1(void* args)
{
	uint8_t  key=0;
	char *buf=buff;
	BaseType_t   xReturn;
	
	while(1)
	{
		 key=KEY_Scan(0);            //按键扫描得到键值
		 if(key==KEY0_PRES ||key==KEY1_PRES )
		 {
			xReturn = xQueueSend( key_queue, &key, portMAX_DELAY );  //将键值写入队列
            if(xReturn !=pdTRUE )
			{
				printf("key_queue队列发送失败!\n");
			}
		 }
		 else if(key==WKUP_PRES)
		 {
			xReturn = xQueueSend( big_date_queue, &buf, portMAX_DELAY );//将键值写入队列
            if(xReturn !=pdTRUE )
			 if(xReturn !=pdTRUE )
			{
				printf("big_date_queue队列发送失败!\n");
			}
		 }
         vTaskDelay(10);       //FreeRTOS自带的延时函数,延时10毫秒
	
	}	
}




/********任务2的任务函数,无返回值且是死循环***********/

/***任务2:实现小数据出队*******/
void task2(void* args)
{
	uint8_t  key = 0;
	BaseType_t   xReturn;
	while(1)
	{
		 xReturn = xQueueReceive( key_queue,&key,portMAX_DELAY);
         if(xReturn !=pdTRUE )
	     {
			 printf("key_queue队列读取失败!\n");
		  } 
          else
		  {
			  printf("key_queue队列读取成功,数据:%d\n",key);
		  }			  
	
        //不需要延时,因为队列为空,该任务会直接会被阻塞,挂载到阻塞列表下面
	
	}	
}




/********任务3的任务函数,无返回值且是死循环***********/

/***任务3:实现大数据出队*******/
void task3(void* args)
{
	BaseType_t   xReturn;
	char * buf;
	while(1)
	{
	    xReturn = xQueueReceive( big_date_queue,&buf,portMAX_DELAY);
		if(xReturn !=pdTRUE )
	    {
			 printf("big_date_queue队列读取失败!\n");
		} 
        else
		{
			  printf("big_date_queue队列读取成功,数据:%s\n",buf);
		}	
		
	
	}	
}

/****如果按键未按下,消息队列未不会有数据,任务2和任务3都会进入阻塞态****/


//FreeRTO入口例程函数,无参数,无返回值
void freertos_demo(void)
{
	    /***队列创建***/
	    key_queue = xQueueCreate( 2, sizeof(uint8_t) );
        if(key_queue !=NULL)
		{
			printf("key_queue队列创建成功!\n");
		}
	    
		big_date_queue = xQueueCreate( 1, sizeof(char *) );
        if(big_date_queue !=NULL)
		{
			printf("big_date_queue队列创建成功!\n");
		}
		
		
		
		/***开始任务的创建***/
	    xTaskCreate( (TaskFunction_t)     start_task,
                             (char *)     "start_task",  
              ( configSTACK_DEPTH_TYPE)   START_TASK_STACK_SIZE,
                            (void *)      NULL,
                            (UBaseType_t) START_TASK_PRIO ,
                        (TaskHandle_t *)  &start_task_handler );
							
							
	vTaskStartScheduler();  //开启任务调度器
	
}




主函数任务调度文件

#include "stm32f4xx.h"                  // Device header
#include "stdio.h"
#include "myled.h"
#include "myusart.h"


#include "FreeRTOS.h"
#include "task.h"
#include "dynamic.h"

extern TaskHandle_t Start_Handle;

int main(void)
{
    //硬件初始化
     My_UsartInit();
    
	
	
	
	 //调用入口函数
     freertos_demo();
	 
}

五、队列相关API函数解析(了解)

至此,已经讲解完毕!初次学习,循序渐进,一步步掌握即可!以上就是全部内容!请务必掌握,创作不易,欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见! 

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

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

相关文章

防病毒克星——白名单可信系统

白名单作为一种网络安全措施,其核心概念在于限制用户只能访问网络所有者定义的受信任内容。这种机制在保护系统免受恶意软件、病毒等攻击方面发挥着重要作用。然而,关于白名单是否可以防病毒的问题,实际上涉及了多个方面的考虑。 首先&#x…

B=2W,奈奎斯特极限定理详解

一直没搞明白奈奎斯特极限定理的含义,网上搜了很久也没得到答案。最近深思几天后,终于有了点心得。顺便吐槽一下,csdn的提问栏目,有很多人用chatgpt秒回这个事,实在是解决不了问题,有时候人的问题大多数都是…

台式机安装Windows 11和Ubuntu 22双系统引导问题

一、基本情况 1.1、硬件情况 电脑有2个NVMe固态硬盘,1个SATA固态硬盘,1个机械硬盘。其中一个NVMe固态硬盘是Windows系统盘,另一个NVMe固态为Windows软件和文件盘,SATA固态硬盘为Ubuntu专用,机械硬盘为数据备份盘。 …

Java | Leetcode Java题解之第136题只出现一次的数字

题目: 题解: class Solution {public int singleNumber(int[] nums) {int single 0;for (int num : nums) {single ^ num;}return single;} }

Ubuntu下安装和配置Redis

目录 1、更新软件包 2、安装Redis 3、启动 Redis临时服务 4、测试Redis服务 5、配置redis服务 6、Redis服务控制命令 1、更新软件包 执行sudo apt-get update更新软件包 sudo apt-get update2、安装Redis 执行sudo apt-get install redis-server 安装命令 sudo apt i…

CLion配置

下载环境:MinGW-w64 - for 32 and 64 bit Windows - Browse Files at SourceForge.net 解压后找一个位置存放,一般放在和ide同一目录,方便查找 个人习惯配置调整: 项目创建 修改ide解码形式 项目右下角一般默认是utf8 文件编码改…

【C++题解】1074 - 小青蛙回来了

问题:1074 - 小青蛙回来了 类型:需要找规律的循环 题目描述: 关于小青蛙爬井的故事,你应该早就听过了:井深10 尺,小青蛙从井底向上爬,每个白天向上爬 3 尺,每个晚上又滑下来 2 尺&…

kafka安装流程

安装kafka前需要安装zookeeper zookeeper安装教程 1.新建一个logs文件夹 2.修改配置文件 3.修改listeners参数 4.以管理员身份启动kafka服务 .\bin\windows\kafka-server-start.bat .\config\server.properties 如果报 输入行太长。 命令语法不正确。 解决方案如下&#x…

全流程透明双语大语言模型MAP-Neo,4.5T 高质量数据训练

前言 近年来,大语言模型 (LLM) 已经成为人工智能领域最热门的研究方向之一,并在各种任务中展现出前所未有的性能。然而,由于商业利益的驱动,许多最具竞争力的模型,例如 GPT、Gemini 和 Claude,其训练细节和…

codeforces round 949 div2

A Turtle and Piggy Are Playing a Game 题目&#xff1a; 思路&#xff1a;输出2的幂次b使得2^b为最大的不超过x的数 代码&#xff1a; #include <iostream>using namespace std;const int N 2e5 10;void solve() {int l, r;cin >> l >> r;if(r % 2) …

哈希表和二维矩阵的结合-2352. 相等行列对(新思路、新解法)

题目链接及描述 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/equal-row-and-column-pairs/description/?envTypest…

制作AI问答机器人的优势和技巧

AI问答机器人已经成为我们生活和工作中的得力助手&#xff0c;应用在社会各个不同的领域中&#xff0c;它们能帮助我们快速获取信息&#xff0c;以提高做事效率&#xff0c;很多企业为了让企业的运营更加高效&#xff0c;为了从众多竞争对手中脱颖而出&#xff0c;纷纷开始制作…

VirtualBox 虚拟机中的 centos7 系统拉取 docker 镜像常见报错及解决方法

一、拉取镜像时报错&#xff1a;Error response from daemon: Get "https://registry-1.docker.io/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority 原因&#xff1a;&#xff08;文心一言给出的原因&#xff09; 这个错误…

ctfshow web

红包题第二弹 <?phpif(isset($_GET[cmd])){$cmd$_GET[cmd];highlight_file(__FILE__);if(preg_match("/[A-Za-oq-z0-9$]/",$cmd)){die("cerror");}if(preg_match("/\~|\!|\|\#|\%|\^|\&|\*|\(|\)|\&#xff08;|\&#xff09;|\-|\_|\{|\}|\…

JVM运行时数据区 - 程序计数器

运行时数据区 Java虚拟机在执行Java程序的过程中&#xff0c;会把它管理的内存划分成若干个不同的区域&#xff0c;这些区域有各自的用途、创建及销毁时间&#xff0c;有些区域随着虚拟机的启动一直存在&#xff0c;有些区域则随着用户线程的启动和结束而建立和销毁&#xff0…

c#vb代码互转工具

下载地址&#xff1a; https://download.csdn.net/download/wgxds/88979921

【MMU】——MMU 相关的 CP15 寄存器

文章目录 MMU 相关的 CP15 寄存器C1(System Control Register)C2(Translation Table Base Control Register)C3(Domain Access Control Register)C5(Data Fault Status Register)C6(Data Fault Address Register)C9C10MMU 相关的 CP15 寄存器 寄存器作用寄存器 C1 中…

信息系统项目管理师0147:工具与技术(9项目范围管理—9.3规划范围管理—9.3.2工具与技术)

点击查看专栏目录 文章目录 9.3.2 工具与技术 9.3.2 工具与技术 专家判断 规划范围管理过程中&#xff0c;应征求具备如下领域相关专业知识或接受过相关培训的个人或小组 的意见&#xff0c;涉及的领域包括&#xff1a;以往类似项目&#xff1b;特定行业、学科和应用领域的信息…

Linux操作系统:Spark在虚拟环境下的安装及部署

将Spark安装到指定目录 // 通过wget下载Spark安装包 $ wget https://d3kbcqa49mib13.cloudfront.net/spark-2.1.1-bin-hadoop2.7.tgz // 将spark解压到安装目录 $ tar –zxvf spark-2.1.1-bin-hadoop2.7.tgz –C /usr/local/ // 重命名 $ mv /usr/local/spark-2.1.1-bin-hado…

Finance Manager System (FMS)

Finance Manager System &#xff08;FMS&#xff09;财务软件&#xff0c;基本三报表合并报表