FreeRTOS队列

本文档主要介绍FreeRTOS操作系统的队列机制,基于正点原子的FreeRTOS源码详解与应用开发一书,详细探讨了如何在嵌入式系统中使用FreeRTOS队列进行任务间通信。
摘要由CSDN通过智能技术生成
——————(正点原子FreeRTOS学习笔记)

开始学习FreeRTOS,学习参考书籍和视频来自正点原子FreeRTOS源码详解与应用开发,北京航空航天大学出版社

1  队列简介
        队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中 断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之 间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的 长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以 也称为消息队列。FreeRTOS 中的信号量的也是依据队列实现的!所以有必要深入的了解 FreeRTOS 的队列。
1 、数据存储
        通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫入队)永 远都是发送到队列的尾部,而从队列提取数据的时候(也叫出队)是从队列的头部提取的。但是 也可以使用 LIFO 的存储缓冲,也就是后进先出,FreeRTOS 中的队列也提供了 LIFO 的存储缓 冲机制。 数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在 队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传 递。学过 uC/OS 的同学应该知道,uC/OS 的消息队列采用的是引用传递,传递的是消息指针。 采用引用传递的话消息内容就必须一直保持可见性,也就是消息内容必须有效,那么局部变量 这种可能会随时被删掉的东西就不能用来传递消息,但是采用引用传递会节省时间啊!因为不
用进行数据拷贝。 采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原 始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可以被重复的使用。FreeRTOS 中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来传递消息啊,我直接 往队列中发送指向这个消息的地址指针不就可以了!这样当我要发送的消息数据太大的时候就 可以直接发送消息缓冲区的地址指针,比如在网络应用环境中,网络的数据量往往都很大的, 采用数据拷贝的话就不现实。
2 、多任务访问
        队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中 提取消息。
3 、出队阻塞
        当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任 务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是 针对从队列中读取消息的任务而言的。比如任务 A 用于处理串口接收到的数据,串口接收到数 据以后就会放到队列 Q 中,任务 A 从队列 Q 中读取数据。但是如果此时队列 Q 是空的,说明 还没有数据,任务 A 这时候来读取的话肯定是获取不到任何东西,那该怎么办呢?任务 A 现在 有三种选择,一:二话不说扭头就走,二:要不我在等等吧,等一会看看,说不定一会就有数 据了,三:死等,死也要等到你有数据!选哪一个就是由这个阻塞时间决定的,这个阻塞时间 单位是时钟节拍数。阻塞时间为 0 的话就是不阻塞,没有数据的话就马上返回任务继续执行接 下来的代码,对应第一种选择。如果阻塞时间为 0~ portMAX_DELAY,当任务没有从队列中获 取到消息的话就进入阻塞态,阻塞时间指定了任务进入阻塞态的时间,当阻塞时间到了以后还 没有接收到数据的话就退出阻塞态,返回任务接着运行下面的代码,如果在阻塞时间内接收到 了数据就立即返回,执行任务中下面的代码,这种情况对应第二种选择。当阻塞时间设置为 portMAX_DELAY 的话,任务就会一直进入阻塞态等待,直到接收到数据为止!这个就是第三种选择。
4 、入队阻塞
        入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队 列发送消息的话也可以设置阻塞时间。比如任务 B 向消息队列 Q 发送消息,但是此时队列 Q 是 满的,那肯定是发送失败的。此时任务 B 就会遇到和上面任务 A 一样的问题,这两种情况的处 理过程是类似的,只不过一个是向队列 Q 发送消息,一个是从队列 Q 读取消息而已。

5 、队列操作过程图示
       下面几幅图简单的演示了一下队列的入队和出队过程。  

   图 13.1.1 中任务 A 要向任务 B 发送消息,这个消息是 x 变量的值。首先创建一个队列,并 且指定队列的长度和每条消息的长度。这里我们创建了一个长度为 4 的队列,因为要传递的是
x 值,而 x 是个 int 类型的变量,所以每条消息的长度就是 int 类型的长度,在 STM32 中就是 4 字节,即每条消息是 4 个字节的。

       图 13.1.2 中任务 A 的变量 x 值为 10,将这个值发送到消息队列中。此时队列剩余长度就是 3 了。前面说了向队列中发送消息是采用拷贝的方式,所以一旦消息发送完成变量 x 就可以再
次被使用,赋其他的值。

图 13.1.3 中任务 A 又向队列发送了一个消息,即新的 x 的值,这里是 20。此时队列剩余长 度为 2。

图 13.1.4 中任务 B 从队列中读取消息,并将读取到的消息值赋值给 y,这样 y 就等于 10 了。任务 B 从队列中读取消息完成以后可以选择清除掉这个消息或者不清除。当选择清除这个
消息的话其他任务或中断就不能获取这个消息了,而且队列剩余大小就会加一,变成 3。如果 不清除的话其他任务或中断也可以获取这个消息,而队列剩余大小依旧是 2。

1  队列结构体
        有一个结构体用于描述队列,叫做 Queue_t,这个结构体在文件 queue.c 中定义如下:
typedef struct QueueDefinition
{
    int8_t *pcHead;   //指向队列存储区开始地址。
    int8_t *pcTail; //指向队列存储区最后一个字节。
    int8_t *pcWriteTo; //指向存储区中下一个空闲区域。
    union
    {
        int8_t *pcReadFrom;   //当用作队列的时候指向最后一个出队的队列项首地址
        UBaseType_t uxRecursiveCallCount; //当用作递归互斥量的时候用来记录递归互斥量被 调用的次数。
    } u;
    List_t xTasksWaitingToSend; //等待发送任务列表,那些因为队列满导致入队失败而进 入阻塞态的任务就会挂到此列表上。
    List_t xTasksWaitingToReceive; //等待接收任务列表,那些因为队列空导致出队失败而进 入阻塞态的任务就会挂到此列表上。
    volatile UBaseType_t uxMessagesWaiting; //队列中当前队列项数量,也就是消息数
    UBaseType_t uxLength; //创建队列时指定的队列长度,也就是队列中最大允许的 队列项(消息)数量
    UBaseType_t uxItemSize;   //创建队列时指定的每个队列项(消息)最大长度,单位字节
    volatile int8_t cRxLock; //当队列上锁以后用来统计从队列中接收到的队列项数 量,也就是出队的队列项数量,当队列没有上锁的话此字 段为 queueUNLOCKED
    volatile int8_t cTxLock; //当队列上锁以后用来统计发送到队列中的队列项数量, 也就是入队的队列项数量,当队列没有上锁的话此字 段为 queueUNLOCKED
    #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) &&\
        ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated; //如果使用静态存储的话此字段设置为 pdTURE。
    #endif
    #if ( configUSE_QUEUE_SETS == 1 ) //队列集相关宏
        struct QueueDefinition *pxQueueSetContainer;
    #endif
    #if ( configUSE_TRACE_FACILITY == 1 ) //跟踪调试相关宏
        UBaseType_t uxQueueNumber;
        uint8_t ucQueueType;
    #endif
} xQUEUE;
typedef xQUEUE Queue_t;

        老版本的 FreeRTOS 中队列可能会使用 xQUEUE 这个名字,新版本 FreeRTOS 中队列的名 字都使用 Queue_t。
2  队列创建
2.1 、函数原型
        在使用队列之前必须先创建队列,有两种创建队列的方法,一种是静态的,使用函数 xQueueCreateStatic();另一个是动态的,使用函数 xQueueCreate()。这两个函数本质上都是宏, 真正完成队列创建的函数是 xQueueGenericCreate()和 xQueueGenericCreateStatic(),这两个函数 在文件 queue.c 中有定义,这四个函数的原型如下。

1 、函数 xQueueCreate ()
        此函数本质上是一个宏,用来动态创建队列,此宏最终调用的是函数 xQueueGenericCreate(), 函数原型如下:
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
                                                        UBaseType_t uxItemSize)
参数:
         uxQueueLength :   要创建的队列的队列长度,这里是队列的项目数。
         uxItemSize :            队列中每个项目(消息)的长度,单位为字节
返回值:
        其他值:    队列创捷成功以后返回的队列句柄!
NULL:                 队列创建失败。

2 、函数 xQueueCreateStatic ()
        此函数也是用于创建队列的,但是使用的静态方法创建队列,队列所需要的内存由用户自 行分配,此函数本质上也是一个宏,此宏最终调用的是函数 xQueueGenericCreateStatic(),函数 原型如下:
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
                                                            UBaseType_t uxItemSize,
                                                            uint8_t *  pucQueueStorageBuffer,
                                                            StaticQueue_t *  pxQueueBuffer)
参数:
    uxQueueLength:     要创建的队列的队列长度,这里是队列的项目数。
    uxItemSize:             队列中每个项目(消息)的长度,单位为字节
    pucQueueStorage:    指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自 行分配。此参数必须指向一个 uint8_t 类型的数组。这个存储区要大于等 于(uxQueueLength * uxItemsSize)字节。
    pxQueueBuffer:        此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。
    返回值:
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值