目录
说明:
一、队列简介
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 ()初始化新队列pxNewQueue1、初始化队列结构体成员变量
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、不需要:队列解锁,恢复调度器、返回队列为空错误