FreeRTOS队列 | FreeRTOS九

目录

说明:

一、队列简介

1.1、什么是队列

1.2、队列的优势

1.3、队列实现功能

1.4、队列使用了解

1.5、队列特点

1.6、队列阻塞处理

1.7、队列出队入队过程

二、队列结构体

2.1、结构体了解

2.2、共同体了解

2.3、队列结构体存储区

三、队列API函数

3.1、创建队列函数

3.2、入队函数

3.3、出队函数

四、队列API函数实现步骤

4.1、队列创建API函数

4.2、队列写入数据API函数

4.3、队列读取数据API函数


说明:

关于内容:

1)以下内容多为概念了解与步骤分析

2)暂无个人示例代码,使用的是FreeRTOS的官方示例代码

3)若想移植代码测试的,请移步其它地方寻找,下文内容暂无个人示例代码供测试

关于其它:

1)操作系统:win 10

2)平台:keil 5 mdk

3)语言:c语言

4)板子:STM32系列移植FreeRTOS
 

一、队列简介

1.1、什么是队列

        队列是任务到任务任务到中断中断到任务数据交互的一种机制(一种消息机制)。

1.2、队列的优势

        1)相比于裸机常用的全局变量,在FreeRTOS中队列保证了数据的安全性

        2)当出现多个任务同时操作一个变量时,该变量的读写数据不安全,如下图1、2

               

                             图1                                                                          图2

1.3、队列实现功能

        1)FreeRTOS基于队列,实现了多种功能,包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量等

        2)读队列与写队列做了保护,防止多任务干扰,在使用时只需调用相关的API函数即可,如下图3、4

                                               

                    图3                                                                                                     图4

1.4、队列使用了解

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

        2)在创建队列时,就要指定队列长度以及队列项目的大小(数值不固定),如下图5

 图5

1.5、队列特点

        1)数据出队方式,通常采用“先进先出”(FIFO)的数据存储缓冲机制,先入队的数据先被读取;当然也可以配置“后进后出”(LIFO)方式

        2)数据传递方式,采用实际值传递,直接将数值放到队列中传递;也可以使用传递指针,一般在传递较大数据是采用指针传递

        3)多任务访问,队列不属于某个任务,任何任务和中断都可以向队列发送(入队)/读取(出队)消息

        4)出队、入队阻塞,当任务向一个队列发送(入队)消息时,可以指定一个阻塞时间,当此队列已满无法入队时,有如下三种情况:

        1、阻塞时间为0,直接返回不会继续等待

        2、阻塞时间为0-port_Max_DELAY,等待设定的阻塞时间,在该时间内未能入队,返回

        3、阻塞时间为port_Max_DELAY,一直等到可以入队为止

        说明:出队与入队一样,不在重复

1.6、队列阻塞处理

        1)入队阻塞,当队列已满,而依然有任务X要入队时,此时无法入队,首先将任务X状态列表项挂载到pxDelayedTaskList,然后将任务X事件列表项挂载到xTaskWaitingToSend

        2)出队阻塞,当队列为空,而依然有任务Y要入队时,此时无法出队(因为没有数据),首先将任务Y状态列表项挂载到pxDelayedTaskList,然后将任务X事件列表项挂载到xTaskWaitingToReceive

当出现多个任务同时入队到一个“满队列”时,这些任务都会进入阻塞状态,也就是说多个任务在等待同一个队列的空间,当出现一个空间,那个任务先进入就绪态?

        1)在多个任务中优先级最高的任务

        2)如果多个任务中优先级相同,等待时间最久的任务会进入就绪态

1.7、队列出队入队过程

        1)创建队列,如下图6

图6

        2)入队(位置填充),如下图7、8

 图7

 图8

        3)出队(位置填充),如下图9、10

 图9

 图10

二、队列结构体

2.1、结构体了解

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 uxltemSize;                                              /*队列项目的大小*/
        volatile int8_ _t cRxLock;                                                /*读取上锁计数器*/
        volatile int8_ _t cTxLock;                                                /*写入上锁计数器*/
/*其他的一些条件编译*/
}xQUEUE;

2.2、共同体了解

用于队列时:

typedef struct QueuePointers

{
        int8_ _t* pcTail;                             /*存储区的结束地址*/
        int8_ _t * pcReadFrom;                /*最后一个读取队列的地址*/
} QueuePointers_ _t;

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

typedef struct SemaphoreData

{
        TaskHandle_ t xMutexHolder;                         /* 互斥信号量持有者*/
        UBaseType_ t uxRecursiveCallCount;           /* 递归互斥信号量的获取计数器*/
} SemaphoreData_ t;

2.3、队列结构体存储区

如下图11:

 图11

三、队列API函数

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

3.1、创建队列函数

1)函数名,xQueueCreate(),作用:动态方式创建队列

2)函数名,xQueueCreateStatic(),作用:静态方式创建队列

3)二者区别:动态方式创建队列内存由FreeRTOS所管理的内存动态分配,而静态创建需要用户自己分配内存

代码部分:

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
    #define xQueueCreate( uxQueueLength, uxItemSize )    xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif

参数解释:

uxQueueLength,含义:队列长度

uxItemSize ,含义:队列项目大小

queueQUEUE_TYPE_BASE ,含义:实现什么功能的队列

可选参数如下图12:

 图12

返回值解释:

返回:NULL,含义:队列创建失败

返回:其他值,含义:队列创建成功

3.2、入队函数

如下图13:

图13

代码部分:

如下图14:

 图14

入队位置,如下图15:

 图15

入队入口函数:

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、出队函数

如下图16:

 图16

代码部分:

BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

参数解释:

xQueue,含义:待读出队列

pvBuffer,含义:消息读取缓存区

xTicksToWait ,含义:阻塞超时时间

返回值解释:

返回:pdTRUE,含义:队列写入成功

返回:pdFALSE,含义:队列写入失败

说明:

此函数在成功读取消息后,会讲已读取的消息移除,而函数xQueuePeek函数不会移除已读消息

四、队列API函数实现步骤

4.1、队列创建API函数

名称:xQueueCreate

实现过程:

1)实际执行的是xQueueGenericCreate( )
2)xQueueGenericCreate( ( uxQueueLength ). ( uxltemSize ), (
queueQUEUE_ TYPE. BASE))
3)计算队列需要多大内存xQueueSizeInBytes = ( size. t)( uxQueueLength *
uxltemSize )
4)为队列申请内存,申请大小: sizeof( Queue, t) + xQueueSizeInBytes .前面部分存
放结构体成员,后面存放队列项
5)判断内存是否申请成功,成功即计算出队列项存储区的首地址
6)调用prvlnitialiseNewQueue ()初始化新队列pxNewQueue

1、初始化队列结构体成员变量
2、调用xQueueGenericReset ()复位队列

4.2、队列写入数据API函数

名称:xQueueSend

实现过程:

1)实际执行的是:xQueueGenericSend( QueueHandle_t xQueue,
                              const void * const pvItemToQueue,
                              TickType_t xTicksToWait,
                              const BaseType_t xCopyPosition );

2)进入临界区(关中断)

3)判断队列是否已满

4)队列有空闲位置,则

1、只有在队列有空闲位置或为覆写的情况才能写入消息

2、当有空闲位置或覆写时,将代写入消息按指定写入方式复制到队列中

3、判断是否有因为读不到消息而阻塞的任务,有则解除阻塞态,通过:xTaskRemoveFromEventList()函数实现-->判断调度器是否被挂起

        1、没有被挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中

        2、已挂起:将移除事件列表项,将事件列表项添加到等待就绪列表项:xPendingReadyList,当调用恢复任务调度器xTaskResumeALL(),xPendingReadyList中任务就会被处理

4、退出临界区

5)队列已满,则

1、此时不能写入消息,因此要将任务阻塞

2、如果阻塞时间为0,代表不阻塞,直接返回队列已满的错误

3、如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值和溢出次数,用于下面对阻塞时间进行补偿

4.3、队列读取数据API函数

名称:xQueueReceive

实现过程:

1)进入临界区

2)判断队列是否为空

3)有数据,则

1、使用函数prvCopyDataFromQueue()拷贝数据

2、队列项目个数减一

3、因为前面已经减了一个队列项,所以队列存在空位,如果xTaskWaitingToSend等待发送列表中,有任务,解除阻塞态,通过xTaskRemoveFromEventList()函数判断调度器是否被挂起:

        1、没有被挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中

        2、已挂起:将移除事件列表项,将事件列表项添加到等待就绪列表项:xPendingReadyList,当调用恢复任务调度器xTaskResumeALL(),xPendingReadyList中任务就会被处理

        

4、退出临界区(开中断)

4)没有数据

1、此时读取不到消息,将任务阻塞

2、如果阻塞时间为0,代表不阻塞,直接返回队列为空的错误

3、如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值和溢出次数,用于下面对阻塞时间进行补偿

4、判断阻塞时间补偿后,是否还需要继续阻塞,则

        1、需要:将任务的事件列表添加到等待接收列表中,将任务状态列表项添加到阻塞列表进行阻塞,队列解锁,恢复调度器

        2、不需要:队列解锁,恢复调度器、返回队列为空错误

猜你喜欢

转载自blog.csdn.net/qq_57663276/article/details/128918116