2、FreeRTOS之队列管理

xQueueReceive() 用于从队列中接收 ( 读取)数据单元。接收到的单元同时会从队列
中删除。
xQueuePeek() 也是从从队列中接收数据单元,不同的是并不从队列中删出接收到
的单元。

 uxQueueMessagesWaiting()用于查询队列中当前有效数据单元个数。

写队列任务的代码实现:
这个任务被创建了两个实例,一个不
停地往队列中写数值 100 ,而另一个实例不停地往队列中写入数值 200 。任务的入口参
数被用来为每个实例传递各自的写入值。
static void vSenderTask( void *pvParameters ) 
{ long lValueToSend; portBASE_TYPE xStatus; /* 该任务会被创建两个实例,所以写入队列的值通过任务入口参数传递 – 这种方式使得每个实例使用不同的值。队列创建时指定其数据单元为long型,所以把入口参数强制转换为数据单元要求的类型 */ lValueToSend = ( long ) pvParameters; /* 和大多数任务一样,本任务也处于一个死循环中 */ for( ;; ) { /* 往队列发送数据第一个参数是要写入的队列。队列在调度器启动之前就被创建了,所以先于此任务执行。第二个参数是被发送数据的地址,本例中即变量lValueToSend的地址。第三个参数是阻塞超时时间 – 当队列满时,任务转入阻塞状态以等待队列空间有效。本例中没有设定超时时间,因为此队列决不会保持有超过一个数据单元的机会,所以也决不会满。*/ xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 ); if( xStatus != pdPASS ) { /* 发送操作由于队列满而无法完成 – 这必然存在错误,因为本例中的队列不可能满。 */ vPrintString( "Could not send to the queue.\r\n" ); } /* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务,而不必等到本任务的时间片耗尽 */ taskYIELD(); } 
}
读队列任务的代码实现:
读队列任务设定了 100 毫秒的阻塞超时时
间,所以会进入阻塞态以等待队列数据有效。一旦队列中数据单元有效,或者即使队列
数据无效但等待时间超过 100 毫秒,此任务将会解除阻塞。在本例中,将永远不会出
100 毫秒超时,因为有两个任务在不停地往队列中写数据。
static void vReceiverTask( void *pvParameters ) 
{ /* 声明变量,用于保存从队列中接收到的数据。 */ long lReceivedValue; portBASE_TYPE xStatus; const portTickType xTicksToWait = 100 / portTICK_RATE_MS; /* 本任务依然处于死循环中。 */ for( ;; ) { /* 此调用会发现队列一直为空,因为本任务将立即删除刚写入队列的数据单元。 */ if( uxQueueMessagesWaiting( xQueue ) != 0 ) { vPrintString( "Queue should have been empty!\r\n" ); } /* 从队列中接收数据第一个参数是被读取的队列。队列在调度器启动之前就被创建了,所以先于此任务执行。第二个参数是保存接收到的数据的缓冲区地址,本例中即变量lReceivedValue的地址。此变量类型与队列数据单元类型相同,所以有足够的大小来存储接收到的数据。第三个参数是阻塞超时时间 – 当队列空时,任务转入阻塞状态以等待队列数据有效。本例中常量portTICK_RATE_MS用来将100毫秒绝对时间转换为以系统心跳为单位的时间值。*/ xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait ); if( xStatus == pdPASS ) { /* 成功读出数据,打印出来。 */ vPrintStringAndNumber( "Received = ", lReceivedValue ); } else { /* 等待100ms也没有收到任何数据。必然存在错误,因为发送任务在不停地往队列中写入数据 */ vPrintString( "Could not receive from the queue.\r\n" ); } } 
}
main()函数的实现:
其在启动调度器之前创建了一个队列和三
个任务。尽管对任务的优先级的设计使得队列实际上在任何时候都不可能多于一个数据
单元,本例代码还是创建了一个可以保存最多 5 long 型值的队列
/* 声明一个类型为 xQueueHandle 的变量. 其用于保存队列句柄,以便三个任务都可以引用此队列 */ 
xQueueHandle xQueue; 
int main( void ) 
{ /* 创建的队列用于保存最多5个值,每个数据单元都有足够的空间来存储一个long型变量 */ xQueue = xQueueCreate( 5, sizeof( long ) ); if( xQueue != NULL ) { /* 创建两个写队列任务实例,任务入口参数用于传递发送到队列的值。所以一个实例不停地往队列发送100,而另一个任务实例不停地往队列发送200。两个任务的优先级都设为1。 */ xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL ); xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL ); /* 创建一个读队列任务实例。其优先级设为2,高于写任务优先级 */ xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL ); /* 启动调度器,任务开始执行 */ vTaskStartScheduler(); } else { /* 队列创建失败*/ } /* 如果一切正常,main()函数不应该会执行到这里。但如果执行到这里,很可能是内存堆空间不足导致空闲任务无法创建。第五章有讲述更多关于内存管理方面的信息 */ for( ;; ); 
}
写队列任务在每次循环中都调用 taskYIELD() taskYIELD() 通知调度器立即进行任 务切换,而不必等到当前任务的时间片耗尽。某个任务调用 taskYIELD() 等效于其自愿 放弃运行态。由于本例中两个写队列任务具有相同的任务优先级,所以一旦其中一个任 务调用了 taskYIELD() ,另一个任务将会得到执行 调用 taskYIELD()的任务转移到 就绪态,同时另一个任务进入运行态。这样就可以使得这两个任务轮翻地往队列发送数据。

使用队列传递复合数据类型

通常接收方收到数 据后,需要知道数据的来源,并根据数据的来源决定下一步如何处理。一个简单的方式 就是利用队列传递结构体,结构体成员中就包含了数据信息和来源信息。

写队列任务具有最高优先级,所以队列正常情况下一直
是处于满状态。这是因为一旦读队列任务从队列中读走一个数据单元,某个写队列任务
就会立即抢占读队列任务,把刚刚读走的位置重新写入,之后便又转入阻塞态以等待队
列空间有效。
/* 定义队列传递的结构类型。 */ 
typedef struct 
{ unsigned char ucValue; unsigned char ucSource; 
} xData; 
/* 声明两个xData类型的变量,通过队列进行传递。 */ 
static const xData xStructsToSend[ 2 ] = 
{ { 100, mainSENDER_1 }, /* Used by Sender1. */ { 200, mainSENDER_2 } /* Used by Sender2. */ 
};static void vReceiverTask( void *pvParameters ) 
{ /* 声明结构体变量以保存从队列中读出的数据单元 */ xData xReceivedStructure; portBASE_TYPE xStatus; /* This task is also defined within an infinite loop. */ for( ;; ) { /* 读队列任务的优先级最低,所以其只可能在写队列任务阻塞时得到执行。而写队列任务只会在队列写满时才会进入阻塞态,所以读队列任务执行时队列肯定已满。所以队列中数据单元的个数应当等于队列的深度 – 本例中队列深度为3 */ if( uxQueueMessagesWaiting( xQueue ) != 3 ) { vPrintString( "Queue should have been full!\r\n" ); } /* Receive from the queue. 第二个参数是存放接收数据的缓存空间。本例简单地采用一个具有足够空间大小的变量的地址。第三个参数是阻塞超时时间 – 本例不需要指定超时时间,因为读队列任会只会在队列满时才会得到执行,故而不会因队列空而阻塞 */ xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 ); if( xStatus == pdPASS ) { /* 数据成功读出,打印输出数值及数据来源。 */ if( xReceivedStructure.ucSource == mainSENDER_1 ) { vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue ); } else { vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue ); } } else { /* 没有读到任何数据。这一定是发生了错误,因为此任务只支在队列满时才会得到执行 */ vPrintString( "Could not receive from the queue.\r\n" ); } } 
}int main( void ) 
{ /* 创建队列用于保存最多3个xData类型的数据单元。 */ xQueue = xQueueCreate( 3, sizeof( xData ) ); if( xQueue != NULL ) { /* 为写队列任务创建2个实例。 The 任务入口参数用于传递发送到队列中的数据。因此其中一个任务往队列中一直写入xStructsToSend[0],而另一个则往队列中一直写入xStructsToSend[1]。这两个任务的优先级都设为2,高于读队列任务的优先级 */ xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL ); xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL ); /* 创建读队列任务。读队列任务优先级设为1,低于写队列任务的优先级。 */ xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL ); /* 启动调度器,创建的任务得到执行。 */ vTaskStartScheduler(); } else { /* 创建队列失败。 */ } /* 如果一切正常,main()函数不应该会执行到这里。但如果执行到这里,很可能是内存堆空间不足导致空闲任务无法创建。第五章将提供更多关于内存管理方面的信息 */ for( ;; ); 
}

如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针而不是对
数据本身在队列上一字节一字节地拷贝进或拷贝出。
但是,当你利用队列传递指针时,一定要十分小心地做到以
下两点:
1、指针指向的内存空间的所有权必须明确
原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;
共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。
2.、指针指向的内存空间必须有效
切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据将不再
有效

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

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

相关文章

perl 用 XML::DOM 解析 Freeplane.mm文件,生成测试用例.csv文件

Perl 官网 www.cpan.org 从 https://strawberryperl.com/ 下载网速太慢了 建议从 https://download.csdn.net/download/qq_36286161/87892419 下载 strawberry-perl-5.32.1.1-64bit.zip 约105MB 解压后安装.msi,装完后有520MB,建议安装在D:盘。 运行 …

【目标检测】原始的 YOLOv1 网络结构(GoogLeNet 作为 backbone 的实现)

现在看网上的很多 YOLOv1 的代码实现,基本都是使用新的 backbone,例如 ResNet 或者 VGG 来实现的,因为这些后面的通用的 backbone 可能比较方便的获得预训练模型,不需要从头开始训练。 但是我就是想看一下,一开始 YOL…

力扣hot100:416.分割等和子集(组合/动态规划/STL问题)

组合数问题 我们思考一下,如果要把数组分割成两个子集,并且两个子集的元素和相等,是否等价于在数组中寻找若干个数使之和等于所有数的一半?是的! 因此我们可以想到,两种方式: ①回溯的方式找到t…

批量查询快递不再难,前缀单号助你轻松搞定!

在快递业务日益繁忙的当下,批量查询快递单号成为了许多人的迫切需求。如何能够快速、准确地找到所需的快递单号呢?其实,利用前缀单号进行批量查询是一个高效且实用的方法。下面,就让我们一起了解如何利用前缀单号轻松查找快递单号…

Delphi7应用教程学习1.3【练习题目】:文本及悬停文字的显示

这个例子主要用到了btn的Hint 属性,Hint是提示的意思。 还有Delphi7还是很好用的,改变了的属性是粗体,默认没有改变的属性为细体。

力扣L10--- 3. 无重复字符的最长子串--2024年3月14日

1.题目 2.知识点 注1:containsKey 是 Java 中 HashMap 类的一个方法,用于检查哈希表中是否包含指定的键。 注2:在哈希表(HashMap)中,每个键对应着唯一的值,因此键不能重复,但值可以重复。 (1)创…

Linux基础命令[19]-id

文章目录 1. id 命令说明2. id 命令语法3. id 命令示例3.1 不加参数3.2 -u/-g/-G(用户、组、所属组)3.3 -gr/-Gr/-ur(有效ID) 4. 总结 1. id 命令说明 id:显示真实有效的用户ID(UID)和组ID(GID),十分方便&…

C/C++火柴棍等式

有n根(n<24)火柴棍&#xff0c;你可以拼出多少个形如“ABC"的等式?等式中的A、B、C是用火柴棍拼出的整数(若该数非零&#xff0c;则最高位不能是0)。用火柴棍拼数字0-9的拼法如图所示: 依次需要用到的火柴棍数目为6 2 5 5 4 5 6 3 7 6 。 如果是初学者可能会这么写。 …

Navicat 面试题及答案整理,最新面试题

Navicat 在数据库管理中的主要用途有哪些&#xff1f; Navicat 是一款数据库管理工具&#xff0c;其主要用途包括&#xff1a; 1、多数据库支持&#xff1a; Navicat 支持多种数据库连接&#xff0c;包括 MySQL、Oracle、PostgreSQL、SQLite、SQL Server 等&#xff0c;方便用…

Android分区存储到底是怎么回事

文章目录 一、Android存储结构二、什么是分区存储&#xff1f;三、私有目录和公有目录三、存储权限和分区存储有什么关系&#xff1f;四、我们应该该怎么做适配&#xff1f;4.1、利用File进行操作4.2、使用MediaStore操作数据库 一、Android存储结构 Android存储分为内部存储和…

系统重构后,对项目定制开发的兼容性问题

公司自实施产品线战略以来&#xff0c;基本推翻了全部旧有业务模块。后续以标准产品二次开发的模式进行项目开发。但在涉及到一些旧有系统二期、三期升级改造过程中。不可避免的需要解决旧有系统的客户定制化开发兼容性问题。也就是旧有系统定制开发的模块不能丢弃。重新开发从…

HTTPS证书很贵吗?

首先&#xff0c;我们需要明确一点&#xff0c;HTTPS证书的价格并不是一成不变的&#xff0c;它受到多种因素的影响。其中最主要的因素包括证书的类型、颁发机构以及所需的验证级别。 从类型上来看&#xff0c;HTTPS证书主要分为单域名证书、多域名证书和通配符证书。单域名证书…