FreeRTOS 消息队列

1. 队列简介

1.1 队列的概念

队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)

类似全局变量?假设有一个全局变量a = 0,现有两个任务都在写这个变量 a:

在这里插入图片描述
大家想象一下如果任务 1 运行一次,任务 2 运行一次,理论上 a 等于什么?等于 2。但是大家想象一下,这个 a++ 在 C 语言这里其实它内部是分成很多个操作的,我们来看一下,内部首先就把 a 给读到 cpu 的寄存器 r0 里面,然后再把 r0 进行加一,然后加完之后,再把 r0 这个值给回这个 a 这个地址,这样的话 a 就变成 1 了,如果裸机的话,肯定是没有这种任务1、任务2 去干扰你。
但是 os 的话就存在这种可能了,比如任务 1 执行到第二步这里的时候被更高优先级的任务 2 给打断了,这时候有可能出现什么情况?那么假设一下,首先 a 本来是 0,读到 r0 里面,此时 r0 = r0 + 1 = 1,此时被任务 2 打断,那打断之后代表 r0 还没给到 a;那这时候任务 2 它同样的也是这么一个操作,因为任务 2 的优先级是最高的,所以它完整的执行了这 3 步,这时候 a 它还是 0,因为任务 1 还没有执行到第三步,还没给回 a,所以执行完这 3 步以后,a = 1,然后任务 2 执行完之后又回到任务 1 的第三步,那这时候 r0 它也是 1,1 它又赋值给 a,这时候它们运行的结果 a 就等于 1。
那这样的话执行了两个任务,都执行了一次,但是它实际的效果竟然 a = 1,这样就造成一个数据的受损。

全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损。

使用队列的情况如下:
在这里插入图片描述

本质:临界区->关中断->关闭优先级最低的 PendSV 中断->关闭任务调度。

队列的优点:读写队列做好了保护,防止多任务同时访问冲突;我们只需要直接调用API函数即可,简单易用!

FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、 递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列 。

1.2 队列的特征

在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。

类似于数组。

在这里插入图片描述

注意:在创建队列时,就要指定队列长度以及队列项目的大小!

FreeRTOS 队列特点

  1. 数据入队出队方式:队列通常采用 “先进先出”(FIFO) 的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为 “后进先出”(LIFO) 方式;
  2. 数据传递方式。FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递。FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递。(像 ucos 是指针传递,把地址给写进去)

优点:互不干扰。
缺点:耗时比较长。

  1. 多任务访问。队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息。
  2. 出队、入队阻塞。当任务向一个队列发送消息时,假设此时当队列已满无法入队,这时候就可以指定一个阻塞时间。
  1. 若阻塞时间为 0 :直接返回不会等待;
  2. 若阻塞时间为 0~port_MAX_DELAY :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
  3. 若阻塞时间为 port_MAX_DELAY :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
  • 入队阻塞:
    在这里插入图片描述

队列满了,此时写不进去数据;
① 将该任务的状态列表项挂载在 pxDelayedTaskList(阻塞列表);
② 将该任务的事件列表项挂载在 xTasksWaitingToSend(等待发送列表);

  • 出队阻塞:
    在这里插入图片描述
    队列为空,此时读取不了数据;
    ①将该任务的状态列表项挂载在 pxDelayedTaskList(阻塞列表);
    ②将该任务的事件列表项挂载在 xTasksWaitingToReceive(等待接收列表);

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

答:

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

1.3 队列基本操作过程

在这里插入图片描述
在这里插入图片描述
这时候这个 20 它会移到队列头部。

2. 队列结构体介绍

队列实际它的内存是分为两部分,第一个就是存放结构体的内存,接着是队列里面的队列项,它用来存放数据的,我们写入数据给队列就写入到队列项里面了。

typedef struct QueueDefinition 
{int8_t * pcHead							/* 存储区域的起始地址(队列项的起始地址) */int8_t * pcWriteTo;        				/* 下一个写入的位置 */union									/* 当把这个结构体用作不同的功能的时候,联合体的作用是不一样的 */{QueuePointers_t     xQueue; 		/* 当用作队列的时候,就使用的上面这个 */SemaphoreData_t  xSemaphore; 		/* 当用作互斥信号量的时候,就使用的下面这个 */} u ;List_t xTasksWaitingToSend; 			/* 等待发送列表 */List_t xTasksWaitingToReceive;			/* 等待接收列表 */volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */UBaseType_t uxLength;					/* 队列长度 */UBaseType_t uxItemSize;                 /* 队列项目的大小 */volatile int8_t cRxLock; 				/* 读取上锁计数器 */volatile int8_t cTxLock;				/* 写入上锁计数器 *//* 其他的一些条件编译 */
} xQUEUE;

上锁计数器:当锁住队列的时候,此时还是可以正常读写队列的,只不过操作不了它的等待发送和等待接收这两个列表;当解锁的时候,才会去操作这两个列表。那这个就叫队列锁。

当用于队列使用时:

typedef struct QueuePointers
{int8_t * pcTail; 					/* 存储区的结束地址 */int8_t * pcReadFrom;				/* 最后一个读取队列的地址 */
} QueuePointers_t;

当用于互斥信号量和递归互斥信号量时 :

typedef struct SemaphoreData
{TaskHandle_t xMutexHolder;			/* 互斥信号量持有者 */UBaseType_t uxRecursiveCallCount;	/* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;

队列结构体整体示意图:

在这里插入图片描述

3. 队列相关 API 函数介绍

使用队列的主要流程:创建队列->写队列->读队列。

3.1 创建队列

创建队列相关API函数介绍:

函数描述
xQueueCreate()动态方式创建队列
xQueueCreateStatic()静态方式创建队列

动态和静态创建队列之间的区别:

  • 动态:队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中自动分配。
  • 静态:创建需要用户自行分配内存。

3.1.1 动态创建队列(常用)

#define	xQueueCreate(  uxQueueLength,   uxItemSize  )						xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE ))

此函数用于使用动态方式创建队列,队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中自动分配 。

形参描述
uxQueueLength队列长度
uxItemSize队列项目的大小
返回值描述
NULL队列创建失败
其他值队列创建成功,返回队列句柄

前面说 FreeRTOS 基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的 queue.h 文件中有定义:

#define queueQUEUE_TYPE_BASE                  	( ( uint8_t ) 0U )	/* 队列 */
#define queueQUEUE_TYPE_SET                  	( ( uint8_t ) 0U )	/* 队列集 */
#define queueQUEUE_TYPE_MUTEX                 	( ( uint8_t ) 1U )	/* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE    	( ( uint8_t ) 2U )	/* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE     	( ( uint8_t ) 3U )	/* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX       	( ( uint8_t ) 4U )	/* 递归互斥信号量 */ ```

3.2 写队列

往队列写入消息API函数:

函数(任务级+中断级)描述
xQueueSend()往队列的尾部写入消息
xQueueSendToBack()同 xQueueSend()
xQueueSendToFront()往队列的头部写入消息
xQueueOverwrite()覆写队列消息(只用于队列长度为 1 的情况)
xQueueSendFromISR()在中断中往队列的尾部写入消息
xQueueSendToBackFromISR()同 xQueueSendFromISR()
xQueueSendToFrontFromISR()在中断中往队列的头部写入消息
xQueueOverwriteFromISR()在中断中覆写队列消息(只用于队列长度为 1 的情况)

队列写入消息

#define	xQueueSend(  xQueue,   pvItemToQueue,   xTicksToWait  )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define	xQueueSendToBack(  xQueue,   pvItemToQueue,   xTicksToWait  )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define	xQueueSendToFront(  xQueue,   pvItemToQueue,   xTicksToWait  )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
#define	xQueueOverwrite(  xQueue,   pvItemToQueue  )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

可以看到这几个写入函数调用的是同一个函数 xQueueGenericSend(),只是指定了不同的写入位置!

覆写:队列长度为 1,只有一个队列项目,比如队列里有数据了,那覆写就是不管队列中有没有数据,都会再写进去覆盖,就是永远是写的进去的,所以是不会阻塞的,所以它的默认阻塞时间是 0,就直接跳过,直接继续写。
其他像尾部写入、头部写入,那这些它们都是会阻塞的,它们都是有一个阻塞时间的设置的,如果队列满了之后,它是写不进去的。那这个是覆写方式与其他方式它们之间的一个区别。

队列一共有 3 种写入位置 :

#define queueSEND_TO_BACK               ( ( BaseType_t ) 0 )		/* 写入队列尾部 */
#define queueSEND_TO_FRONT              ( ( BaseType_t ) 1 )		/* 写入队列头部 */
#define queueOVERWRITE                  ( ( BaseType_t ) 2 )		/* 覆写队列 */

注意:覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用

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

BaseType_t	xQueueGenericSend(QueueHandle_t 	xQueue,const void * const 	pvItemToQueue,TickType_t 	xTicksToWait,const BaseType_t 	xCopyPosition); 
形参描述
xQueue待写入的队列
pvItemToQueue待写入消息
xTicksToWait阻塞超时时间
xCopyPosition写入的位置
返回值描述
pdTRUE队列写入成功
errQUEUE_FULL队列写入失败

3.3 读队列

从队列读取消息API函数:

函数(任务级+中断级)描述
xQueueReceive()从队列头部读取消息,并删除消息
xQueuePeek()从队列头部读取消息
xQueueReceiveFromISR()在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR()在中断中从队列头部读取消息
BaseType_t    xQueueReceive( QueueHandle_t   xQueue,  void *   const pvBuffer,  TickType_t   xTicksToWait )

此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。

形参描述
xQueue待读取的队列
pvBuffer信息读取缓冲区
xTicksToWait阻塞超时时间
返回值描述
pdTRUE读取成功
pdFALSE读取失败
BaseType_t   xQueuePeek( QueueHandle_t   xQueue,   void * const   pvBuffer,   TickType_t   xTicksToWait )

此函数用于在任务中,从队列中读取消息, 但与函数 xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息!

形参描述
xQueue待读取的队列
pvBuffer信息读取缓冲区
xTicksToWait阻塞超时时间
返回值描述
pdTRUE读取成功
pdFALSE读取失败

4. 队列操作实验

实验目的:学习 FreeRTOS 的队列相关API函数的使用 ,实现队列的入队和出队操作。
实验设计:将设计四个任务:start_task、task1、task2、task3

四个任务的功能如下:
start_task:用来创建task1和task2以及task3任务
task1:当按键 key0 或 key1 按下,将键值拷贝到队列 key_queue(入队);当按键 key_up 按下,将传输大数据,这里拷贝大数据的地址到队列 big_date_queue 中。
task2:读取队列 key_queue 中的消息(出队),打印出接收到的键值。
task3:从队列 big_date_queue 读取大数据地址,通过地址访问大数据。

4.1 创建队列

#include "queue.h"QueueHandle_t key_queue;        /* 小数据句柄 */
QueueHandle_t big_date_queue;   /* 大数据句柄 */
char buff[100] = {"我是一个大数组,大大的数组 124214 uhsidhaksjhdklsadhsaklj"};
/*** @brief       FreeRTOS例程入口函数* @param       无* @retval      无*/
void freertos_demo(void)
{    /* 队列的创建 */key_queue = xQueueCreate( 2, sizeof(uint8_t) );if(key_queue != NULL){printf("key_queue队列创建成功!!\r\n");}else printf("key_queue队列创建失败!!\r\n");big_date_queue = xQueueCreate( 1, sizeof(char *) );if(big_date_queue != NULL){printf("big_date_queue队列创建成功!!\r\n");}else printf("big_date_queue队列创建失败!!\r\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();
}

4.2 任务函数实现

/* 任务一,实现入队 */
void task1( void * pvParameters )
{uint8_t key = 0;char * buf;buf = buff; 														/* buf = &buff[0] */BaseType_t   err = 0;while(1) {key = key_scan(0);if(key == KEY0_PRES || key == KEY1_PRES){err = xQueueSend( key_queue, &key, portMAX_DELAY );			/* 写入队列、死等 */if(err != pdTRUE){printf("key_queue队列发送失败\r\n");}}else if(key == WKUP_PRES){err = xQueueSend( big_date_queue, &buf, portMAX_DELAY );	/* 写入队列、死等 */if(err != pdTRUE){printf("key_queue队列发送失败\r\n");}}vTaskDelay(10);}
}/* 任务二,小数据出队 */
void task2( void * pvParameters )
{uint8_t key = 0;BaseType_t err = 0;while(1){err = xQueueReceive(key_queue, &key, portMAX_DELAY);if(err != pdTRUE){printf("key_queue队列读取失败\r\n");}else {printf("key_queue读取队列成功,数据:%d\r\n",key);}}
}/* 任务三,大数据出队 */
void task3( void * pvParameters )
{char * buf;BaseType_t err = 0;while(1){err = xQueueReceive(big_date_queue, &buf, portMAX_DELAY);if(err != pdTRUE){printf("big_date_queue队列读取失败\r\n");}else {printf("数据:%s\r\n",buf);}}
}

5. 队列相关API函数解析

  • 队列的创建API函数:xQueueCreate( )
  • 往队列写入数据API函数(入队):xQueueSend( )
  • 从队列读取数据API函数(出队): xQueueReceive( )

更多的队列相关API函数讲解可以查看《FreeRTOS开发指南》第十三章—队列

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

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

相关文章

[NOIP1998 提高组] 拼数

[NOIP1998 提高组] 拼数 题目描述 设有 n n n 个正整数 a 1 … a n a_1 \dots a_n a1​…an​,将它们联接成一排,相邻数字首尾相接,组成一个最大的整数。 输入格式 第一行有一个整数,表示数字个数 n n n。 第二行有 n n …

一些刷题需要用的大数据

无符号版本和有符号版本的区别就是有符号类型需要使用一个bit来表示数字的正负。 如果需声明无符号类型的话就需要在类型前加上unsigned。 整型的每一种都分为:无符号(unsigned)和有符号(signed)两种类型(f…

Day76:WEB攻防-Fuzz模糊测试篇JS算法口令隐藏参数盲Payload未知文件目录

目录 Fuzz技术-用户口令-常规&模块&JS插件 无验证码密码明文 无验证码密码弱加密(MD5) 无验证码密码复杂加密(通过js文件进行加密) Fuzz技术-目录文件-目录探针&文件探针 Fuzz技术-未知参数名-文件参数&隐藏参数 Fuzz技术-构造参数值-漏洞攻击恶意Paylo…

分布式之Skywalking

Skywalking skywalking是一个apm系统,包含监控,追踪,并拥有故障诊断能力的 分布式系统 一、Skywalking介绍 1.什么是SkyWalking Skywalking是由国内开源爱好者吴晟开源并提交到Apache孵化器的产品,它同时吸收了Zipkin /Pinpoint …

Anaconda安装 (windowsLinux)

文章目录 Anaconda简介设置国内源pip || conda 一、Anaconda (Windows系统)1.1 下载及安装1.2 虚拟环境创建1.3 在Pycharm中配置conda的环境 二、Anaconda(Linux系统) Anaconda简介 conda是一个开源的包、环境管理器,可…

2024年【T电梯修理】模拟考试及T电梯修理模拟考试题库

题库来源:安全生产模拟考试一点通公众号小程序 T电梯修理模拟考试是安全生产模拟考试一点通生成的,T电梯修理证模拟考试题库是根据T电梯修理最新版教材汇编出T电梯修理仿真模拟考试。2024年【T电梯修理】模拟考试及T电梯修理模拟考试题库 1、【多选题】…

Huggingface 笔记:大模型(Gemma2B,Gemma 7B)部署+基本使用

1 部署 1.1 申请权限 在huggingface的gemma界面,点击“term”以申请gemma访问权限 https://huggingface.co/google/gemma-7b 然后接受条款 1.2 添加hugging对应的token 如果直接用gemma提供的代码,会出现如下问题: from transformers i…

怎样修改grafana的Loading picture和加载的文本

登录装了grafana的linux机器 command “sudo vi /usr/share/grafana/public/views/index.html”,编辑配置文件。 找到.preloader__logo更改background-image. 这里可以是个url也可以是个路径。 如果想要更改加载的文字.可以更改 的内容 改完:wq保存以后退出&…

鸿蒙一次开发,多端部署(八)典型布局场景

虽然不同应用的页面千变万化,但对其进行拆分和分析,页面中的很多布局场景是相似的。本小节将介绍如何借助自适应布局、响应式布局以及常见的容器类组件,实现应用中的典型布局场景。 说明: 在本文 媒体查询 小节中已经介绍了如何通…

【JS】替换文本为emjio表情

最终效果展示 T1 T2 T3 T4 需求 把评论你好帅啊啊啊[开心][开心],[开心] 替换为图片 思路 正则match提取[开心]到一个数组数组去重创建img标签img标签转文本. 。例:(el.outerHTML),将el元素转文本字符串replaceAll…

js【详解】深拷贝

什么是深拷贝? 对于引用类型的数据,才有深浅拷贝的说法 浅拷贝 :执行拷贝的变量只复制被拷贝变量内存的引用数据的地址。 被拷贝变量内地址指向的数据发生变化时,执行拷贝的变量也会同步改变 深拷贝: 在堆内存中开…

如何在edge上安装拓展weTab

1.点解管理拓展 2.点击获取拓展 3.搜索框输入"wetab"并搜索 4.点击获取按钮 5.点击之后跳出弹窗,点击"添加拓展" 6.回到拓展页面,找到wetab拓展,点击右侧启动拓展 7.打开新的界面,wetab已经启动 8.自定义界面 1. 右键图标可以进行删除操作 2.左下角有个设…

Kubernetes集群搭建 kubernetes集群安装

Kubeadm kubeadm 是 Kubernetes 社区提供的集群构建工具,它能够以最佳实践的方式部署一个最小化的可用 Kubernetes 集群。 但是 kubeadm 在设计上并未安装网络解决方案,所以需要用户自行安装第三方符合 CNI 的网络解决方案,如 flanal&#…

【Python + Django】启动简单的文本页面

前言: 为了应付(bushi)毕业论文,总要自己亲手搞一个像模像样的项目出来吧 ~ ~ 希望自己能在新的连载中学到项目搭建的知识,这也算是为自己的测试经历增添光彩吧!!! 希望、希望大家…

你的电脑打不开摄像头问题

我一直以为我电脑上的摄像头老是打不开是因为硬件不匹配的问题。知道我发现了我的拯救者Y7000的机身盘边的“摄像头开关”按钮。。。 我去,你的摄像头开关按钮怎么设置在机身旁边啊。。。。 —————————————————————— 2024年3月21日更新记录&a…

UE5.3 StateTree使用实践

近期浏览UE的CitySample(黑客帝国Demo),发现有不少逻辑用到了StateTree学习一下,StateTree是多层状态机实现,以组件的形式直接挂载在蓝图中运行。 与平时常见的一些FSM库不同,StateTree并不会返回给外界当…

【Git】第二课:git安装和配置

安装 我们伟大祖国有句古话叫巧妇难为无米之炊,还有句话叫工欲善其事必先利其器。所以,在正式的学习之前,我们需要先把git这把利器安装好。 Windows系统 下载安装包 打开Git - Downloading Package页面,根据系统类型32位还是6…

基于C/C++的easyx图形库教程

文章目录: 一:前言 二:窗口(宽高 背景颜色 窗口标题 弹出对话框) 三:图形绘制(点 线 矩形 圆 椭圆) 四:文字(颜色 大小 背景 位置 打印 文字居中) 五&a…

vue3 reactive丢失响应式

问题 使用 reactive 构造响应式对象时,当对其进行重新赋值后,会导致原有变量失去响应式,页面不会发生联动更新 例如: 1、使用 reactive 定义一个响应式的对象变量 let data1 reactive({name: 小李,date: 2024-03-18,address: xx…

解读“CFMS中国闪存市场峰会”存储技术看点-1

昨天CFMS中国闪存市场峰会在深圳举行,小编本来计划前往现场参加,但由于有事冲突未能如期前往,非常遗憾! 本次峰会的主题是“存储周期激发潜能”。在闪存市场的供需关系逐渐恢复正常的阶段,闪存市场如何发展变化&#x…