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

     本文将详细全方位的讲解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);    //删除开始任务自身,传参NULLtaskEXIT_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.mzph.cn/bicheng/23784.shtml

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

相关文章

【数据库】SQL零基础入门学习

人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 目录 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌…

重邮计算机网络803-(2)物理层

一.物理层 1.介绍 物理层的主要任务描述为确定与传输媒体的接口的一些特性,即: ①机械特性 指明接口所用接线器的形状和尺寸、引线数目和排列、固定和锁定装置等等。 ②电气特性 指明在接口电缆的各条线上出现的电压的范围。 ③功能特性 指明某条线上…

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

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

HDFS 之 DataNode 核心知识点

优质博文:IT-BLOG-CN 一、DataNode工作机制 DataNode工作机制,如下所示: 【1】一个数据块在 DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度&#xff0c…

前端 JS 经典:图片裁剪上传原理

前言:图片裁剪一般都是用户选择头像时用到,现在很多插件都可以满足这个功能,但是我们不仅要会用插件,还要自己懂的裁剪原理。 1. 流程 流程分为:1. 预览本地图片 2. 选择裁剪区域 3. 上传裁剪图像 2. 如何预览图片 …

小熊家务帮day10-day12 门户管理(缓存,主页,定时任务)

门户管理 1 门户介绍1.1 介绍1.2 常用技术方案 2 缓存技术方案2.1 需求分析2.1.1 C端用户界面原型2.1.2 缓存需求2.1.3 使用的工具 2.2 项目基础使用2.2.1 项目集成SpringCache2.2.2 测试Cacheable需求Service测试 2.1.3 缓存管理器(设置过期时间)2.1.4 …

计算机毕业设计PySpark+Hadoop地震预测系统 地震数据分析可视化 地震爬虫 大数据毕业设计 Flink Hadoop 深度学习

基于Hadoop的地震预测的 分析与可视化研究 姓 名:____田伟情_________ 系 别:____信息技术学院___ 专 业:数据科学与大数据技术 学 号:__2011103094________ 指导教师:_____王双喜________ 年 月 日 …

sqli-labs 靶场 less-5、6 第五关和第六关:判断注入点、使用错误函数注入爆库名、updatexml()函数

SQLi-Labs是一个用于学习和练习SQL注入漏洞的开源应用程序。通过它,我们可以学习如何识别和利用不同类型的SQL注入漏洞,并了解如何修复和防范这些漏洞。Less 5 SQLI DUMB SERIES-5 判断注入点:1. 首先,尝试正常的回显内容&#x…

Hadoop3:MapReduce源码解读之Map阶段的TextInputFormat切片机制(3)

Job那块的断点代码截图省略,直接进入切片逻辑 参考:Hadoop3:MapReduce源码解读之Map阶段的Job任务提交流程(1) 5、TextInputFormat源码解析 类的继承关系 它的内容比较少 重写了两个父类的方法 这里关心一下泛型参数…

【Python报错】已解决Attributeerror: ‘list‘ object has no attribute ‘join‘( Solved)

解决Python报错:AttributeError: ‘list’ object has no attribute ‘join’ (Solved) 在Python中,字符串(str)对象有一个非常有用的join()方法,它允许你将序列中的元素连接(join)成一个字符串…

机器学习笔记 - 本地windows 11 + PyCharm运行stable diffusion流程简述

一、环境说明 硬件:本地电脑windows11、32.0 GB内存、2060的6G的卡。 软件:本地有一个python环境,主要是torch 2.2.2+cu118 二、准备工作 1、下载模型 https://huggingface.co/CompVishttps://huggingface.co/CompVis 进入上面的网址,我这里下载的是这个里面的 …

最新付会进群多群同时变现社群系统V3.5.3版本 详细教程+源码下载

市面1888最新付费进群多群同时变现系统V3.5.3版本 详细教程源码下载介绍: 续男粉变现,相亲群变现后 演化出来的最新多群同时变现系统 可同时进行40个群同时变现 可设置地域群,相亲,男粉变现等多种群 购买后包括详细的 域名服…

Linux文件系统(操作系统的文件管理)

一.Linux系统的文件接口 我们先介绍下Linux系统俩种的文件接口&#xff0c;引出一些问题。 1.open&#xff08;&#xff09; 我们看下说明&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int open(const char *pathname,…

《大道平渊》· 拾 —— 身心的“肥胖”与我们不知饥渴的病:追求中的丰盈与节制

《平渊》 拾 "水满则溢&#xff0c;月盈则亏。" 《道德经》有言&#xff1a;"水满则溢&#xff0c;月盈则亏"。 意思是&#xff1a;水满了就会溢出&#xff0c;月亮最圆的时候就会走向亏的状态。 这揭示了自然界和人类社会中一切事物的内在规律 —— 任…

Proxmox VE虚拟机与容器管理平台安装指南

上篇文章说了ESXI和Proxmox VE&#xff08;简称pve&#xff09;区别&#xff0c;由于需要从esxi5.迁移到PVE8.2&#xff0c;所以开始简单的在一个测试机上的部署个说明指南&#xff0c;以备无患。 一、引言 Proxmox VE是一款基于Debian Linux的完全开源平台&#xff0c;专为虚…

6-Maven的使用

6-Maven的使用 常用maven命令 //常用maven命令 mvn -v //查看版本 mvn archetype:create //创建 Maven 项目 mvn compile //编译源代码 mvn test-compile //编译测试代码 mvn test //运行应用程序中的单元测试 mvn site //生成项目相关信息的网站 mvn package //依据项目生成 …

【代码随想录】【算法训练营】【第30天】 [322]重新安排行程 [51]N皇后 [37]解数独

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 30&#xff0c;周四&#xff0c;好难&#xff0c;会不了一点~ 题目详情 [322] 重新安排行程 题目描述 322 重新安排行程 解题思路 前提&#xff1a;…… 思路&#xff1a;回溯。 重点&…

RabbitMQ--Hello World(基础详解)

文章目录 先决条件RabbitMQ 初识RabbitMQ--Hello World发送接收 更多相关内容可查看 先决条件 本教程假定 RabbitMQ 已安装并在标准端口 &#xff08;5672&#xff09; 上运行。如果你 使用不同的主机、端口或凭据&#xff0c;连接设置将需要 调整。如未安装可查看Windows下载…

Nvidia/算能 +FPGA+AI大算力边缘计算盒子:桥梁结构安全监测

中国铁路设计集团有限公司&#xff08;简称中国铁设&#xff09;&#xff0c;原铁道第三勘察设计院集团有限公司&#xff08;铁三院&#xff09;&#xff0c;是中国国家铁路集团有限公司所属的唯一设计企业&#xff0c;成立于1953年&#xff0c;总部位于天津市&#xff0c;是以…

f4pga环境搭建教程

f4pga环境搭建教程 背景介绍 FOSS Flows For FPGA (F4PGA) project&#xff0c;是一套开源的FPGA工具链&#xff0c;号称the GCC of FPGAs&#xff0c;作用是将写的硬件描述语言&#xff08;verilog或VHDL&#xff09;转化为可以在FPGA上运行的可执行文件&#xff08;bit文件…