FreeRTOS队列(queue)

队列(queue)可以用于"任务到任务"、 "任务到中断"、 "中断到任务"直接传输信息。

1、队列的特性

1、1常规操作

队列的简化操如下图所示,从此图可知:

  • 队列中可以包含若干数据:队列中有若干项,这被称为“长度”;
  • 每个数据大小固定;
  • 创建队列时就要指定长度、数据大小;
  • 数据的操作采用先进先出的方法:写数据时放入尾部,读数据时从头部读;
  • 也可以强制写队列头部:覆盖头部数据。

1、2 数据传输的两种方式

使用队列传输数据时有两种方法:

拷贝:把数据、变量的值复制进队列里;

引用:把数据、变量的地址复制进队列里。

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

1、3队列的阻塞访问

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


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

        跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有空间,则时间到之后它也会进入就绪态。既然写队列的任务个数没有限制,那么当多个任务写"满队列"时,这些任务都会进入阻塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?


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

2、队列函数

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

2.1创建

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

1、动态分配内存: xQueueCreate,队列的内存在函数内部动态分配
 

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

2、静态分配内存: xQueueCreateStatic,队列的内存要事先分配好
函数原型如下:
 

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

示例代码:

// 示例代码
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof( uint32_t )

// xQueueBuffer 用来保存队列结构体
StaticQueue_t xQueueBuffer;

// ucQueueStorage 用来保存队列的数据
// 大小为: 队列长度 * 数据大小
uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];

void vATask( void *pvParameters )
{
    QueueHandle_t xQueue1;
    // 创建队列: 可以容纳 QUEUE_LENGTH 个数据,每个数据大小是 ITEM_SIZE
    xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
        ITEM_SIZE,
        ucQueueStorage,
        &xQueueBuffer );
}

2.2 复位

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

/* pxQueue : 复位哪个队列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);

2.3删除

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

void vQueueDelete( QueueHandle_t xQueue );

2.4写队列

        可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR 中使用。函数原型如下:
      

/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);



/*
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToBack(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);



/*
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);



/*
* 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);


/*
* 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToFrontFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);

  

2.5读队列

        使用 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
);

2.6查询

可以查询队列中有多少个数据、有多少空余空间。函数原型如下:
 

/*
* 返回队列中可用数据的个数
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回队列中可用空间的个数
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

2.7覆盖 or偷看

当队列长度为 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,
);

3、传输大数据

FreeRTOS的队列使用拷贝传输,也就是要传输uint32_t时,把4字节的数据拷贝进队列;
要传输一个8字节的结构体时,把8字节的数据拷贝进队列。
如果要传输1000字节的结构体呢?写队列时拷贝1000字节,读队列时再拷贝1000字节?
不建议这么做,影响效率!
这时候,我们要传输的是这个巨大结构体的地址:把它的地址写入队列,对方从队列得
到这个地址,使用地址去访问那1000字节的数据。
使用地址来间接传输数据时,这些数据放在RAM里,对于这块RAM,要保证这几点:
⚫ RAM 的所有者、操作者,必须清晰明了
这块内存,就被称为"共享内存"。要确保不能同时修改 RAM。比如,在写队列之
前只有由发送者修改这块 RAM,在读队列之后只能由接收者访问这块 RAM。
⚫ RAM 要保持可用
这块 RAM 应该是全局变量,或者是动态分配的内存。对于动然分配的内存,要
确保它不能提前释放:要等到接收者用完后再释放。 另外,不能是局部变量。
 

实例

程序会创建一个队列,然后创建1个发送任务、 1个接收任务:
⚫ 创建的队列:长度为 1,用来传输"char *"指针
⚫ 发送任务优先级为 1,在字符数组中写好数据后,把它的地址写入队列
⚫ 接收任务优先级为 2,读队列得到"char *"值,把它打印出来


        这个程序故意设置接收任务的优先级更高,在它访问数组的过程中,接收任务无法执行、无法写这个数组。

main函数中创建了队列、创建了发送任务、接收任务,代码如下:
 

/* 定义一个字符数组 */
static char pcBuffer[100];
/* vSenderTask被用来创建2个任务,用于写队列
* vReceiverTask被用来创建1个任务,用于读队列
*/
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );
/*-----------------------------------------------------------*/
/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
int main( void )
{
prvSetupHardware();
/* 创建队列: 长度为1,数据大小为4字节(存放一个char指针) */
xQueue = xQueueCreate( 1, sizeof(char *) );
if( xQueue != NULL )
{
/* 创建1个任务用于写队列
* 任务函数会连续执行,构造buffer数据,把buffer地址写入队列
* 优先级为1
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 1, NULL );
/* 创建1个任务用于读队列
* 优先级为2, 高于上面的两个任务
* 这意味着读队列得到buffer地址后,本任务使用buffer时不会被打断
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建队列 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}

发送任务的函数中,现在全局大数组pcBuffer中构造数据,然后把它的地址写入队列,
代码如下
 

static void vSenderTask( void *pvParameters )
{
    BaseType_t xStatus;
    static int cnt = 0;
    char *buffer;
    /* 无限循环 */
    for( ;; )
    {
        sprintf(pcBuffer, "www.100ask.net Msg %d\r\n", cnt++);
        buffer = pcBuffer; // buffer变量等于数组的地址, 下面要把这个地址写入队列

            /* 写队列
        * xQueue: 写哪个队列
        * pvParameters: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
        * 0: 如果队列满的话, 即刻返回
        */
        xStatus = xQueueSendToBack( xQueue, &buffer, 0 ); /* 只需要写入4字节, 无需写入
        整个buffer */

        if( xStatus != pdPASS )
        {
            printf( "Could not send to the queue.\r\n" );
        }
    }
}

接收任务的函数中,读取队列、得到buffer的地址、打印,代码如下
 

static void vReceiverTask( void *pvParameters )
{
    /* 读取队列时, 用这个变量来存放数据 */
    char *buffer;
    const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
    BaseType_t xStatus;
    /* 无限循环 */
    for( ;; )
    {
        /* 读队列
        * xQueue: 读哪个队列
        * &xReceivedStructure: 读到的数据复制到这个地址
        * xTicksToWait: 没有数据就阻塞一会
        */
        xStatus = xQueueReceive( xQueue, &buffer, xTicksToWait); /* 得到buffer地址,只
        是4字节 */
        if( xStatus == pdPASS )
        {
            /* 读到了数据 */
            printf("Get: %s", buffer);
        }
        else
        {
            /* 没读到数据 */
            printf( "Could not receive from the queue.\r\n" );
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSql中使用to_char函数、date()函数可能会导致索引无法充分利用,导致查询速度无法提升

今天在处理接口请求速度慢的问题,惊奇的发现加了索引,但还是请求很忙。由于card_stop_info表有300w条数据,这时候关联查询非常慢,于是我加上匹配项索引,但是发现依然没有改变速度。。这时候去搜了一下才知道pgsql的to_…

javaweb 期末复习

1. JDBC数据库连接的实现逻辑与步骤以及JDBC连接配置(单列模式) public class JDBCUtil {// 这些换成自己的数据库 private static final String DB_URL "jdbc:mysql://localhost:3306/你的数据库名称";private static final String USER &q…

辛弃疾,笔墨剑影的一生

辛弃疾,字幼安,号稼轩,生于南宋高宗赵构绍兴十年(公元1140年),卒于南宋宁宗赵扩嘉泰元年(公元1207年),享年67岁。他是中国南宋时期著名的爱国词人,与苏轼并称…

Unity贪吃蛇改编【详细版】

Big and small greedy snakes 游戏概述 游戏亮点 通过对称的美感,设置两条贪吃蛇吧,其中一条加倍成长以及加倍减少,另一条正常成长以及减少,最终实现两条蛇对整个界面的霸占效果。 过程中不断记录两条蛇的得分情况&#xff0c…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 部门项目任务分配(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 部门项目任务分配(100分) 🌍 评测功能需要订阅专栏后私信联…

【eMTC】eMTC PBCH与LTE PBCH有什么不同

1 概述 eMTC是基于LTE演进的物联网技术,在R12中叫Low-Cost MTC,在R13中被称为LTE enhanced MTC ,即eMTC,旨在基于现有的LTE载波满足物联网设备需求。eMTC基于蜂窝网络进行部署,支持上下行最大1Mbps的峰值速率&#xff…

lxml库在爬虫领域的贡献及应用

重头戏lxml库里面的xpath 一段代码给各位开开胃 这段代码首先导入了lxml库中的etree模块,然后定义了一个包含HTML内容的字符串html。接着,我们使用etree.HTML()函数解析这个HTML字符串,得到一个表示整个HTML文档的树形结构。最后,…

《大数据分析》期末考试整理

一、单项选择题(1*9) 1.大数据发展历程:出现阶段、热门阶段和应用阶段 P2 2.大数据影响 P3 1)大数据对科学活动的影响 2)大数据对思维方式的影响 3)大数据对社会发展的影响 4)大数…

C语言---------深入理解指针

目录 一、字符指针 二、指针数组: 三、数组指针: 1、定义: 2、&数组名和数组名区别: 3、数组指针的使用: 四、数组参数,指针参数: 1、一维数组传参: 2、二维数组传参&am…

单列集合顶层接口Collection及五类遍历方式(迭代器)

collection add方法细节: remove方法细节: contains方法细节: 如果集合中存储的是自定义对象, student之类的, 也想通过contains进行判断, 就必须在javaBean中重写equals方法 contains在arrayList中源代码:在底层调用了equals方…

对候选人得票的统计程序

一个结构体变量中可以存放一组数据(如一个学生的学号、姓名、成绩等数据)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组与以前介绍过的数值型数组不同之处在于:每个数组元素都是一…

认识Redis 主从同步、事务和Memcached的区别

08- 什么是 Redis 主从同步? Redis 的主从同步(replication)机制,允许 Slave 从 Master 那里,通过网络传输拷贝到完整的数据备份,从而达到主从机制。 主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从…

React+TS前台项目实战(十)-- 全局常用组件CopyText封装

文章目录 前言CopyText组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲项目常用复制文本组件封装,这个组件是一个用于拷贝文本的 React 组件,它提供了拷贝,国际化和消息提示的功能 CopyText组件 1. 功能分…

HTML表格的跨行与跨列:《红楼梦》人物与小学课表示例

在HTML中,表格不仅可以按常规行和列排列数据,还可以通过跨行(rowspan)和跨列(colspan)属性来合并单元格,以适应更复杂的数据展示需求。以下是跨行与跨列属性的介绍,以及两个示例&…

全网爆火《pvz植物大战僵尸杂交版》最新安装包,Android、Windows、ios安装包+教程!

今天阿星想和大家分享一个最近在B站上引起轰动的老游戏——《植物大战僵尸》! 是的,你没听错,就是那个曾经让我们熬夜到天亮,一关接一关挑战的游戏。 让我们来聊聊,这款游戏怎么就突然又火了起来呢? 原来…

4款好用的文本扩展器!!提高工作效率!【送源码】

今天的文章中为大家带来几款好用的文本扩展器,帮助大家提供工作效率,减少重复劳动~ Beeftext Beeftext 是一个文本扩展工具,可以帮助用户快速输入短语、段落或者常用的文本片段。它允许你创建自定义的缩写和对应的文本替换&…

HTTP-代理

HTTP-代理 web代理服务器是网络的中间实体,代理位于客户端和服务器之间,扮演者中间人的角色,在各端点之间来回传递http报文 web的中间实体 web上的代理服务器是代表客户端完成事务处理的中间人,如果没有web代理,htt…

【猫狗分类】Pytorch VGG16 实现猫狗分类4-开始训练

背景 现在,我们已经完成了,数据集的清洗,标签的制作,也把VGG16的模型建立好了。那接下来,我们应该把数据,放到我们搭建的vgg16的模型里面,让模型针对这些猫和狗的图片,去进行训练&a…

MyBatis操作数据库(一)

什么是MyBatis? MyBatis是一个优秀的持久层框架,⽤于简化JDBC的开发。 MyBatis本是Apache的⼀个开源项⽬iBatis,2010年这个项目由apache迁移到了googlecode,并且改名为MyBatis。 简单来说MyBatis是更加简单完成数据和数据库交互的框架 什么…

内存泄漏 内存溢出

概念 内存泄漏:是程序没有正确的释放已分配的内存,造成系统内存的浪费。内存泄漏很难发现,因为他不会直接导致程序崩溃,而是会慢慢降低程序的性能。 内存溢出:系统中存在无法回收的内存或使用的内存过多,…