欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > FreeRTOS学习 --- 消息队列

FreeRTOS学习 --- 消息队列

2025/2/6 16:12:16 来源:https://blog.csdn.net/gdragen_/article/details/145423667  浏览:    关键词:FreeRTOS学习 --- 消息队列

队列简介

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

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

        使用队列的情况如下:

 

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

        FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、

二值信号量、 递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列 。

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

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

FreeRTOS队列特点:

1、数据入队出队方式

        队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为“后进先出”LIFO方式;

2、数据传递方式

        FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递, FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递

3、多任务访问

        队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息

4、出队、入队阻塞

        当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队

若阻塞时间为0  :直接返回不会等待;
若阻塞时间为0~port_MAX_DELAY  :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
若阻塞时间为port_MAX_DELAY  :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;

入队阻塞:

        

队列满了,此时写不进去数据;

 ①将该任务的状态列表项挂载在pxDelayedTaskList

 ②将该任务的事件列表项挂载在xTasksWaitingToSend

出队阻塞:

队列为空,此时读取不了数据;

 ①将该任务的状态列表项挂载在pxDelayedTaskList

 ②将该任务的事件列表项挂载在xTasksWaitingToReceive

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

答:

          1、优先级最高的任务

          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;

队列结构体整体示意图:

队列相关API函数介绍

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

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

函数

描述

xQueueCreate()

动态方式创建队列

xQueueCreateStatic()

静态方式创建队列

         动态和静态创建队列之间的区别:队列所需的内存空间由 FreeRTOS FreeRTOS 管理的堆中分配,而静态创建需要用户自行分配内存。

创建队列函数入口参数解析:

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

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

        前面说 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 )	/* 递归互斥信号量 */

往队列写入消息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( ),只是指定了不同的写入位置!

        队列一共有 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   );

从队列读取消息API函数:

函数

描述

xQueueReceive()

从队列头部读取消息,并删除消息

xQueuePeek()

从队列头部读取消息

xQueueReceiveFromISR()

在中断中从队列头部读取消息,并删除消息

xQueuePeekFromISR()

在中断中从队列头部读取消息

队列读取消息函数入口参数解析:

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

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

BaseType_t   xQueuePeek( QueueHandle_t   xQueue,   void * const    pvBuffer,   TickType_t      xTicksToWait )

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

队列相关API函数工作流程具体解析:

创建队列:xQueueCreate( )

        实际执行的是xQueueGenericCreate( )

xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

        1、计算队列需要多大内存 xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize )

        2、为队列申请内存,申请大小:sizeof( Queue_t ) + xQueueSizeInBytes ,前面部分存放结构体成员,后面就是队列项大小

        3、判断内存是否申请成功,成功即计算出队列项存储区的首地址

        4、调用prvInitialiseNewQueue()初始化新队列pxNewQueue

                (1)、初始化队列结构体成员变量

                (2)、调用xQueueGenericReset()复位队列

                        1)、初始化其他队列结构体成员变量

                        2)、判断要复位的队列是否为新创建的队列

                                不是新创建队列,那就复位它,将列表xTasksWaitingToSend移除,是新创建的队列,那就初始化这两个列表xTasksWaitingToSend和xTasksWaitingToReceive

往队列写入数据(入队):xQueueSend( )

        实际执行的是:xQueueGenericSend( )

xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

        1、进入临界区(关中断)

        2、判断队列是否已满?

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

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

                (3)、判断是否有因为读不到消息而阻塞的任务,有的话,将解除阻塞态,通过这个函数

                        判断调度器是否被挂起,没有挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中。挂起:将移除事件列表项,将事件列表项添加到等待就绪列表:xPendingReadyList,当调用恢复调度器时xTaskResumeAll( ),xPendingReadyList中多任务就会被处理
                (4)、退出临界区(开中断)

        3、队列已满

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

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

                (3)、如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值和溢出次数,用于下面对阻塞时间进行补偿(补偿就是计算剩余的阻塞时间还有多久)

                (4)、判断阻塞时间补偿后,是否还需要阻塞

                        需要:将任务的事件列表项添加到等待发送列表中,将任务状态列表项添加到阻塞列表中进行阻塞,队列解锁,恢复调度器。不需要:队列解锁,恢复调度器,返回队列满错误。

                        不需要:队列解锁,恢复调度器,返回队列满错误
 

  从队列读取数据(出队):xQueueReceive( )

        1、进入临界区(关中断)

        2、判断队列是否为空

        3、有数据

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

                (2)、队列项目个数减一

                (3)、因为前面已经减了一个队列项,所以队列已经有空位了,如果xTasksWaitingToSend等待发送列表中,有任务,则解除阻塞态,通过这个函数xTaskRemoveFromEventList( )

                        判断调度器是否被挂起,没有挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中。挂起:将移除事件列表项,将事件列表项添加到等待就绪列表:xPendingReadyList,当调用恢复调度器时xTaskResumeAll( ),xPendingReadyList中多任务就会被处理

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

        4、为空

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

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

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

                (4)、判断阻塞时间补偿后,是否还需要阻塞

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

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com