FreeRTOS消息队列

队列简介 

 更详细的操作入下图所示:

传输数据的方法

FreeRTOS中的队列传输使用的是拷贝:把数据、把变量的值复制进队列里

FreeRTOS 使用拷贝值的方法,这更简单:
(1) 局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据。
(2)无需分配 buffer 来保存数据,队列中有 buffer
(3)局部变量可以马上再次使用
(4)发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发送任务来释放数据
(5)如果数据实在太大,你还是可以使用队列传输它的地址
(6)队列的空间有 FreeRTOS 内核分配,无需任务操心
(7)对于有内存保护功能的系统,如果队列使用引用方法,也就是使用地址,必须确保双方任务对这个地址都有访问权限。使用拷贝方法时,则无此限制:内核有足够的权限,把数据复制进队列、再把数据复制出队列。

队列函数

使用队列的流程: 创建队列 写队列 读队列 删除队列

创建

队列的创建有两种方法:动态分配内存、静态分配内存。

函数原型如下:
动态分配:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

静态分配:

QueueHandle_t xQueueCreateStatic ( UBaseType_t uxQueueLength , UBaseType_t uxItemSize , uint8_t * pucQueueStorageBuffer , StaticQueue_t * pxQueueBuffer );

复位

队列刚被创建时,里面没有数据;使用过程中可以调用 xQueueReset() 把队列恢复为初始状态,此函数原型为:

/* pxQueue : 复位哪个队列;

* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);

删除 

删除队列的函数为 vQueueDelete() ,只能删除使用动态方法创建的队列,它会释放内存。原型如下:
void vQueueDelete( QueueHandle_t xQueue );

写队列 

可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:
任务中往尾部写入:
/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);

在ISR中往尾部写入:

/* 
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
这些函数用到的参数是类似的,统一说明如下:

+

读队列

使用 xQueueReceive() 函数读队列,读到一个数据后,队列中该数据会被移除。这个函数有两个版本:在任务中使用、在 ISR 中使用。函数原型如下:
BaseType_t xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxTaskWoken);
参数说明如下:
           

查询

可以查询队列中有多少个数据、有多少空余空间。函数原型如下:
/*
* 返回队列中可用数据的个数
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回队列中可用空间的个数
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

覆盖/偷看

当队列长度为 1 时,可以使用 xQueueOverwrite() xQueueOverwriteFromISR()来覆盖数据。
注意,队列长度必须为 1。当队列满时,这些函数会覆盖里面的数据,这也以为着这些函数不会被阻塞。
函数原型如下:
/* 覆盖队列
* xQueue: 写哪个队列
* pvItemToQueue: 数据地址
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,const void * pvItemToQueue);
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。那么可以使用" 窥视 " ,也就是 xQueuePeek() xQueuePeekFromISR() 。这些函数会从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么" 偷看 " 时会导致阻塞;一旦队列中有数据,以后每次" 偷看 "都会成功。 函数原型如下:
/* 偷看队列
* xQueue: 偷看哪个队列
* pvItemToQueue: 数据地址, 用来保存复制出来的数据
* xTicksToWait: 没有数据的话阻塞一会
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueuePeek(QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait);
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,void *pvBuffer,);

队列的阻塞访问(Key!!!)

只要知道队列的句柄,谁都可以读、写该队列。任务、ISR 都可读、写队列。可以多个任务读写队列。
任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。口语化地说,就是可以定个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。 某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间到之后它也会进入就绪态。既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪态?
优先级最高的任务
如果大家的优先级相同,那等待时间最久的任务会进入就绪态
跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有空间,则时间到之后它也会进入就绪态。既然写队列的任务个数没有限制,那么当多个任务写" 满队列 " 时,这些任务都会进入阻塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?
优先级最高的任务
如果大家的优先级相同,那等待时间最久的任务会进入就绪态
这里要补充一点,进入阻塞态就是把任务写到阻塞链表里面,就绪态就是把任务写入到就绪链表里面。

队列进阶使用(队列集)

假设有 2 个输入设备:红外遥控器、旋转编码器,它们的驱动程序应该专注于“产生硬件数据”,不应该跟“业务有任何联系”。比如:红外遥控器驱动程序里,它只应该把键值记录下来、写入某个队列,它不应该把键值转换为游戏的控制键。在红外遥控器的驱动程序里,不应该有游戏相关的代码,这样,切换使用场景时,这个驱动程序还可以继续使用。 相当于多一个队列进行中转,一个队列对硬件数据进行处理,并把自己写进另一个队列,另一个队列会根据自己队列里面的”数据“,来对硬件数据转换成自己需要的数据。
队列集的本质也是队列,只不过里面存放的是“队列句柄”。 使用过程如下:
a. 创建队列 A,它的长度是 n1
b. 创建队列 B,它的长度是 n2
c. 创建队列集 S,它的长度是“n1+n2”
d. 把队列 A、B 加入队列集 S
e. 这样,写队列 A 的时候,会顺便把队列 A 的句柄写入队列集 S
f. 这样,写队列 B 的时候,会顺便把队列 B 的句柄写入队列集 S
g. InputTask 先读取队列集 S,它的返回值是一个队列句柄,这样就可以知道哪个队列有
有数据了;然后 InputTask 再读取这个队列句柄得到数据。

创建队列集

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )

把队列加入队列集

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,QueueSetHandle_t xQueueSet );

读取队列集

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,TickType_t const xTicksToWait );

队列的使用示例

这里我推荐b站韦东山老师的课程,他也有讲解FreeRTOS的使用,大家要去看队列的使用示例,可以去看韦老师的课程,他确实讲的很好,我这里只是教内部机制和原理,只要会内部机制和使用原理,那么,读者可以随便找个以前写过的代码,加入队列,就可以验证自己是否成功使用到队列,

队列的内部机制核心

核心是:关中断、环形缓冲区、链表!!! 

 怎么互斥访问数据 

简单粗暴:关中断

在queue.c中:

 这里为什么要用 taskENTER_CRITICAL();来关闭中断以避免冲突呢?FreeRTOS中有这么多任务,如果说,有两个任务想要同时进行写队列,而且前一个任务还没有写完队列就被下一个写队列的任务切换,那么这样子的话,整个RTOS会乱套,所以写队列之前要关闭调度。

 

函数 BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition ) 是 FreeRTOS 中用于向队列发送数据的函数。让我们逐个解释其参数和功能:

1.xQueue:
这是一个队列句柄(QueueHandle_t),用于标识将要发送数据的目标队列。队列句柄是FreeRTOS 中管理队列的标识符,用于区分不同的队列实例。
2.pvItemToQueue:

这是一个指向将要发送到队列中的数据项的指针。它是一个 const void * 类型,即指向常量数据的指针,因为该函数并不修改实际的数据内容,只是将数据项发送到队列。

3.xTicksToWait:

这是发送数据时的超时时间,以时钟节拍(TickType_t)为单位。如果队列已满,并且在指定的超时时间内仍未能发送数据到队列,则函数将会阻塞任务或者返回一个错误码(取决于具体的使用方式)。

4.xCopyPosition:
这个参数指示了数据项在发送到队列时的复制策略。它的类型是 BaseType_t,通常用作一个标志或者枚举值,表示数据项的复制方式。具体的取值和含义可以根据实际的 FreeRTOS 配置和使用场景而有所不同。

函数功能概述:

xQueueGenericSend 函数的作用是向指定的队列 xQueue 发送一个数据项 pvItemToQueue。如果队列已满,且指定的 xTicksToWait 时间内无法发送数据项到队列,函数将会根据超时策略进行处理(可能阻塞任务或者返回一个错误码)。发送数据项的方式(复制策略)由 xCopyPosition 参数控制,确保数据项能够安全地发送到队列中,不会被意外修改或损坏。在 FreeRTOS 中,队列的发送操作是一个重要的任务间通信机制,允许一个任务将数据发送给另一个任务,实现任务间的同步和数据传递。

怎么传递数据 

使用环形缓冲区传递数据。

环形缓冲区(ring buffer)也称作循环缓冲区(cyclic buffer)、圆形队列(circular queue)、圆形缓冲区(circular buffer)。环形缓冲区并不是指物理意义上的一个首尾相连成“环”的缓冲区,而是逻辑意义上的一个环,因为内存空间是线性结构,所以实际上环形缓冲区仍是一段有长度的内存空间,是一个先进先出功能的缓冲区,具备实现通信进程对该缓冲区的互斥访问功能。

 

 环形缓冲区实际在内存空间内示意图:

 

环形缓冲区的长度是固定的,在使用该缓冲区时,不需要将所有的数据清除,只需要调整指向该缓冲区的pHead、pValidWrite和pTail指针位置即可。pValidWrite指针最先指向pHead指针位置(环形缓冲区开头位置),数据从pValidWrite指针处开始存储,每存储一个数据,pValidWrite指针位置向后移动一个长度 ,随着数据的添加,pValidWrite指针随移动数据长度大小个位置。当pValidWrite指向pTail尾部指针,pValidWrite重新指向pHead指针位置(折行处理),并且覆盖原先位置数据内容直到数据存储完毕。

实现原理:

一般构建一个环形缓冲区需要一段连续的内存空间以及4个指针:
pHead指针:指向内存空间中的首地址;
pTail指针:指向内存空间的尾地址;
pValidRead:指向内存空间存储数据的起始位置(读指针);
pValidWrite:指向内存空间存储数据的结尾位置(写指针)。

当申请完内存以及指针定义完毕后,环形缓冲区说明及使用如下:

 

1.该段内存空间的长度是Len = pTail-pHead;
2.pValidRead是读数据的起始位置,当读取完N数据之后要移动N个单位长度的偏移,当有addlen长度的数据要存入到环形缓冲区,若addlen + pValidWrite > pTail时,pValidWrite将存入len1 = pTail - pValidWrite个数据长度,然后pValidWrite回到pHead位置,将剩下的len2 = addlen - len1个数据从pHead开始存储并覆盖到原来的数据内容。
3.pValidWrite是写数据的起始位置,当存入N个数据之后要移动N个单位长度的偏移,pValidRead是读数据的起始位置,当读取N个数据之后要移动N个单位长度的偏移。当要addlen长度的数据要从环形缓冲区读取,若addlen + pValidRead > pTail时,pValidRead 将读取len1 = pTail - pValidRead 个数据长度,然后pValidRead 回到pHead位置,将剩下的len2 = addlen - len1个数据从pHead开始读取完毕。

 我们该怎么知道这个buff是空的还是满的呢?还有说还有空位?(我们这里假设指针类型和buff类型是一样的)。

 判断环形buff为空:

pValidRead==pValidWrite。我们在创建环形buff的时候,读指针和写指针所指向的位置是一样的,只要当读指针的值等于写指针的值,那么这个buff为空,所以意味着这个队列读不了,读了会阻塞。

判断环形buff为满:

pValidRead==pValidWrite+1;当下一个写位置要等于读位置的时候,那么这个buff已经满了。所以意味这个队列写不了,写了会阻塞。 

 写入队列就判断buff是不是满的,如果不是满的,直接写buff->pValidWrite+=1;

读出队列就是判断buff是不是空的,如果不是空的,直接读buff-> pValidRead+=1;

写队列 

调用过程:

xQueueSendToBackxQueueGenericSend/* 如果不成功: */vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );// 1. 当前任务记录在队列的链表里:  pxQueue->xTasksWaitingToSendvListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );// 2. 把当前任务从ready list放到delayed listprvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );

 xEventListItem是为了当队列被读之后,有空位,可以通过这个链表来找回这个任务,通知他可以写队列了,来唤醒它。

DelayedList是为了当队列超时之后,通过这个链表来对这个任务进行唤醒。

一个任务写队列时,如果队列已经满了,它会被挂起,何时被唤醒?

超时:

  • 任务写队列不成功时,它会被挂起:从ready list移到delayed list中
  • 在delayed list中,按照"超时时间"排序
  • 系统Tick中断不断发生,在Tick中断里判断delayed list中的任务时间到没?时间到后就唤醒它

别的任务读队列:

         

读队列:

读队列也是一样的,读不到就被阻塞,等待超时或者等待有人写队列来去唤醒它。 

总结:

以上就是FreeRTOS中关于消息队列的知识,我从浅到深讲解了消息队列,不局限于单纯的去调用API函数使用消息队列,要对其内部机制有一定的了解,这才是我们学习者该做到的事情, 

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

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

相关文章

linux最大线程数限制及打开最大文件数

1.root用户下执行 ulimit -a 然后查看 max user processes 这个值通常是系统最大线程数的一半 max user processes:当前用户同时打开的进程(包括线程)的最大个数为 2.普通用户下 ulimit -a 出现的max user processes的值 默认是 /etc/security/limits.d/20-nproc.co…

PHP环境搭建之使用PhpStudy

文章目录 1 PhpStudy1.1 简介1.2 下载&安装1.3 修改配置1.3.1 Apache配置1.3.2 MySQL配置1.3.3 MySQL启动问题 1.4 Composer1.4.1 简介1.4.2 下载安装1.4.3 修改配置1.4.4 使用命令 1 PhpStudy 1.1 简介 phpstudy是一个php运行环境的集成包,用户不需要去配置运…

KIVY BLOG Kivy tutorial 007: Introducing kv language

Kivy tutorial 007: Introducing kv language – Kivy Blog DECEMBER 18, 2019 BY ALEXANDER TAYLOR Kivy tutorial 007: Introducing kv language Kivy 导师课007: 介绍kv语言 Central themes: kv language, building a gui, integration with Python 中心主题:…

路由模式--哈希模式下使用a标签跳转会有问题

路由模式分为 history 和 hash 两种模式&#xff0c;在 hash 模式下&#xff0c;使用 a 标签去跳转路由&#xff0c;可能会有问题。 比如&#xff1a; <a href"/home"><img src"/logo.png" class"logo" /></a> 在跳转路由时…

神经网络学习6-线性层

归一化用的较少 正则化用来解决过拟合&#xff0c;处理最优化问题&#xff0c;批量归一化加快速度 正则化&#xff08;Regularization&#xff09;&#xff1a; 作用&#xff1a;正则化是一种用来防止过拟合的技术&#xff0c;通过向模型的损失函数中添加惩罚项&#xff0c;使…

【Mysql】SQL约束、主键约束、非空、唯一、外键约束

SQL约束 什么是约束: 对表中的数据进行进一步的限制&#xff0c;从而保证数据的正确性、有效性、完整性. 违反约束的不正确数据,将无法插入到表中。 常见的约束 约束名 约束关键字 主键 primary key 唯一 unique 非空 not null 外键 foreign key 2.1 主键约束 什么是主键约束&a…

香橙派 5 PLUS 安装QQ(arm架构、Ubuntu系统)

1、下载QQ for Linux&#xff1a; 访问腾讯QQ官网&#xff0c;下载适用于香橙派 5 PLUS的arm架构Linux的QQ安装包。 比如&#xff1a;ARM版下载deb格式QQ安装包 ‘ QQ_3.2.9_240617_arm64_01.deb ’。 2、安装QQ for Linux&#xff1a; sudo dpkg -i [下载的文件名.deb]3、运…

微信小程序反编译 2024 unveilr.exe

ps&#xff1a;一开始用的反编译工具是wxappUnpacker&#xff0c;后面改为 unveilr.exe 1.先找到小程序安装目录“E:\聊天记录\WeChat Files\Applet”&#xff0c;要反编译小程序的包 文件夹下的名字对应的是小程序ID&#xff0c;如果不确定是哪个&#xff0c;可以删除->打…

Linux集群自动化维护-Ansible

1.1Ansible概述 自动化运维&#xff1a;批量管理&#xff0c;批量分发&#xff0c;批量执行&#xff0c;维护。。是python写的 批量管理工具&#xff1a; Ansible&#xff08;无客户端&#xff09;&#xff1a;无客户端&#xff0c;基于ssh进行管理与维护 Saltstack &#…

Python武器库开发-武器库篇之ThinkPHP 2.x 任意代码执行漏洞(六十三)

Python武器库开发-武器库篇之ThinkPHP 2.x 任意代码执行漏洞&#xff08;六十三&#xff09; PHP代码审计简介 PHP代码审计是指对PHP程序进行安全审计&#xff0c;以发现潜在的安全漏洞和风险。PHP是一种流行的服务器端脚本语言&#xff0c;广泛用于开发网站和Web应用程序。由…

探索Linux的奇妙世界:第二关---Linux的基本指令1

1. xshell与服务器的连接 想必大家在看过上一期视频时已经搭建好了Linux的环境了并且已经下好了终端---xshell了吧?让我来带大家看一看下好了是什么样子的: 第一次登陆会让你连接你的服务器,就是我们买的云服务器,买完之后需要把公网地址ip复制过来进行链接,需要用户名和密码连…

React hydrateRoot如何实现

React 服务器渲染中&#xff0c;hydrateRoot 是核心&#xff0c;它将服务器段的渲染与客户端的交互绑定在一起&#xff0c;我们知道 React 中 Fiber Tree 是渲染的的核心&#xff0c;那么 React 是怎么实现 hydrateRoot 的呢&#xff1f;首先我们验证一下&#xff0c;hydrateRo…

Python基础教程(三十):math模块

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

基于vue3 + ant-design 使用阿里图标库iconfont.cn

对于使用 iconfont.cn 的用户&#xff0c;通过设置 createFromIconfontCN 方法参数对象中的 scriptUrl 字段&#xff0c; 即可轻松地使用已有项目中的图标。 组件封装 IconFont <template><IconFont :type"iconType" /> </template><script se…

Web应用和Tomcat的集成鉴权1-BasicAuthentication

作者:私语茶馆 1.Web应用与Tomcat的集成式鉴权 Web应用部署在Tomcat时,一般有三层鉴权: (1)操作系统鉴权 (2)Tomcat容器层鉴权 (3)应用层鉴权 操作系统层鉴权包括但不限于:Tomcat可以和Windows的域鉴权集成,这个适合企业级的统一管理。也可以在Tomcat和应用层独立…

湖南(市场调研)源点咨询 新产品上市前市场机会调研与研究分析

湖南源点调研认为&#xff1a;无论是创业公司&#xff0c;还是在公司内部探索新的项目或者新的产品线等&#xff0c;首先都要做“市场机会分析与调研“&#xff0c;要真正思考并解答以下疑问&#xff1a; 我们的目标客户群体是谁&#xff0c;他们如何决策&#xff1f; 我们所…

windows下mysql修改 my.ini的datadir后 `Access denied`

1. 背景 window安装mysql数据库时&#xff0c;不能指定数据文件存放位置&#xff08;默认安装路径 "C:/ProgramData"&#xff09;。 只能通过修改mysql.ini来更改数据文件存放目录。 2. 问题&#xff1a; 修改mysql.ini后&#xff0c;mysql 出现 "Access den…

Python爬虫学习 | Scrapy框架详解

一.Scrapy框架简介 何为框架&#xff0c;就相当于一个封装了很多功能的结构体&#xff0c;它帮我们把主要的结构给搭建好了&#xff0c;我们只需往骨架里添加内容就行。scrapy框架是一个为了爬取网站数据&#xff0c;提取数据的框架&#xff0c;我们熟知爬虫总共有四大部分&am…

【Java】已解决java.lang.NoSuchMethodException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.lang.NoSuchMethodException异常 在Java编程中&#xff0c;java.lang.NoSuchMethodException是一个常见的运行时异常&#xff0c;它通常表示尝试通过反射调用一个不存在…

耳夹式佩戴的舒适体验,拥有AI功能的生活助手,塞那Z50耳夹耳机上手

在数码产品层出不穷的今天&#xff0c;一款能够脱颖而出的耳机&#xff0c;不仅要有出色的音质&#xff0c;更要有人性化的设计和独特的功能。最近我就发现了这么一款很有趣的耳机&#xff0c;它是来自sanag塞那Z50耳夹耳机&#xff0c;这款耳机有着新颖的佩戴方式和动听的音质…