队列
简介
一、队列简介
队列就是任务到任务,任务到中断、中断到任务数据交流的一种机制 (消息传递)
为什么不使用全局变量呢?原因就是当多个任务对变量进行操作时,容易造成数据损坏。没有数据保护
当使用队列是,也会有多个任务同时写一个队列的时候,但是在写、读队列的函数中有进去临界区**(关闭中断)**的机制,就避免了这种情况。
二、队列特点
1、FreeRTOS入出队方式:采用先进先出FIFO的数据缓存机制,也可配置成后进先出LIFO方式。
2、数据传递方式: 采用实际值传递,也可以传递指针,所以在传递大数据时采用指针传递。
3、多任务访问 : 队列不属于某个任务,任何任务都可以向队列发送/读取消息
4、出队/入队阻塞 : 当任务向一个队列发送消息时,可以指定一个阻塞时间。(当队列满的时候,等待时间)
阻塞时间 | 代表含义 |
---|---|
1、若阻塞时间为0 | 直接返回不等待 |
2、0~port_MAX_DELAY | 等待设定的阻塞时间,超时直接返回不等待 |
3、port_MAX_DELAY | 死等,一直到可以入队为止 |
队列结构体
有一个结构体用于描述队列,叫做 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;
函数
队列创建
在使用队列前,要先创建队列
一、动态创建队列函数
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize);//动态创建
此函数是一个宏,此宏最终调用的是函数 xQueueGenericCreate(),原型为: 下面有具体讲这个函数
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
xQueueCreate();函数及参数说明
- 参数
1、UBaseType_t uxQueueLength:队列长度
2、UBaseType_t uxItemSize :队列项大小(即要放的数据大小单位字节)
- 返回值
创建成功返回队列句柄,失败返回NULL
二、静态创建队列函数
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,
uint8_t * pucQueueStorageBuffer,StaticQueue_t * pxQueueBuffer);//静态创建队列
此函数本质上也是一个宏,此宏最终调用的是函数 xQueueGenericCreateStatic();
- 参数
1、UBaseType_t uxQueueLength :队列长度
2、UBaseType_t uxItemSize :队列项大小(即要放的数据大小单位字节)
**3、pucQueueStorage :**指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自行分配。此参数必须指向一个 uint8_t 类型的数组。这个存储区要大于等于(uxQueueLength * uxItemsSize长度x大小)字节。
4、pxQueueBuffer : 此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。
- 返回值
创建成功返回队列句柄,失败返回NULL
三、队列创建函数原型
函数 xQueueGenericCreate()用于动态创建队列,创建队列过程中需要的内存均通过FreeRTOS 中的动态内存管理函数 pvPortMalloc()分配,函数原型如下:
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
- 参数
1、uxQueueLength:要创建的队列的队列长度,这里是队列的项目数。
2、uxItemSize: 队列中每个项目(消息)的长度,单位为字节。
3、ucQueueType: 队列类型,由于 FreeRTOS 中的信号量等也是通过队列来实现的,创建信号
量的函数最终也是使用此函数的,因此在创建的时候需要指定此队列的用途,也就是队列类型,一共有 六种类型:
参数宏 | 类型 | 解释说明 |
---|---|---|
queueQUEUE_TYPE_BASE | 普通的消息队列 | 一般使用队列 |
queueQUEUE_TYPE_SET | 队列集 | |
queueQUEUE_TYPE_MUTEX | 互斥量 | 在创建互斥信号量时,函数选择的这个 |
queueQUEUE_TYPE_COUNTING_SEMAPHORE | 计数量 | |
queueQUEUE_TYPE_BINARY_SEMAPHORE | 二值信号量 | |
queueQUEUE_TYPE_RECURSIVE_MUTEX | 递归互斥信号量 |
函 数 xQueueCreate() 创 建 队 列 的 时 候 此 参 数 默 认 选 择 的 就 是queueQUEUE_TYPE_BASE。
- 返回值
创建成功返回队列句柄,失败返回NULL
四、静态创建队列函数原型
QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength, //队列长度
const UBaseType_t uxItemSize, //队列大小
uint8_t * pucQueueStorage,//队列项存储区
StaticQueue_t * pxStaticQueue, //队列结构体存储
const uint8_t ucQueueType ) //队列类型
- 参数
相较于动态创建,多了两个参数 uint8_t * pucQueueStorage,
StaticQueue_t * pxStaticQueue,
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
1、uxQueueLength:要创建的队列的队列长度,这里是队列的项目数。
2、uxItemSize: 队列中每个项目(消息)的长度,单位为字节。
3、pucQueueStorage:指向队列项目的存储区,也就是消息的存储区,这个存储区需要用户自行分配。此参数 必须指向一个 uint8_t 类型的数组。这个存储区要大于等于(uxQueueLength * uxItemsSize)字节。
**4、pxStaticQueue:**此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体。
5、ucQueueType: 队列类型,由于 FreeRTOS 中的信号量等也是通过队列来实现的,创建信号量的函数最终也是使用此函数的,因此在创建的时候需要指定此队列的用途,也就是队列类型,一共有六种类型:
- 返回值
创建成功返回队列句柄,失败返回NULL
五、队列相关函数详解
最终完成队列创建的函数有两个,一个是静态方法的 xQueueGenericCreateStatic(),
另外一个就是动态方法的 xQueueGenericCreate() 。
创建队列的函数本质就是申请所需空间
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
{
Queue_t *pxNewQueue; //定义结构体指针
size_t xQueueSizeInBytes; //需要的存储大小
uint8_t *pucQueueStorage; //存放*队列项*的首地址
configASSERT( uxQueueLength > ( UBaseType_t ) 0 );//队列长度是否大于0的断言
if( uxItemSize == ( UBaseType_t ) 0 )
{
//队列项大小为 0,那么就不需要存储区。
xQueueSizeInBytes = ( size_t ) 0;
}
else //否则分配空间
{
//分配足够的存储区,确保随时随地都可以保存所有的项目(消息),
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
}//计算需要的存储空间, 即 队列长度 * 队列项的大小
//下面 结构体大小 + 队列项大小
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
//内存申请成功
if( pxNewQueue != NULL ) //如果申请成功
{
pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t ); //设置队列项的首地址
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
//队列是使用动态方法创建的,所以队列字段 ucStaticallyAllocated 标
//记为 pdFALSE。
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif
prvInitialiseNewQueue( uxQueueLength,
\ uxItemSize,
\ pucQueueStorage,
\ ucQueueType,
\ pxNewQueue ); //队列初始化函数
}
return pxNewQueue; //返回结构体地址,也就是使用中的句柄
}
上面函数在最后使用了**队列初始化函数 prvInitialiseNewQueue()**详细内容:
对列初始化的本质就是把申请结构体赋值
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength,//队列长度
const UBaseType_t uxItemSize, //队列项目长度
uint8_t * pucQueueStorage, //队列项目存储区
const uint8_t ucQueueType, //队列类型
Queue_t * pxNewQueue ) //队列结构体
{
//防止编译器报错
( void ) ucQueueType;
if( uxItemSize == ( UBaseType_t ) 0 )
{
//队列项(消息)长度为 0,说明没有队列存储区,这里将 pcHead 指向队列开始地址
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else
{
//设置 pcHead 指向队列项存储区首地址
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage; //在创建时有定义和赋值,作用是指向队列项。
//一定要注意,这是队列项的首地址
}
//初始化队列结构体相关成员变量
pxNewQueue->uxLength = uxQueueLength; //正常赋值队列长度
pxNewQueue->uxItemSize = uxItemSize; //正常赋值队列 项 大小
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );//复位队列//下面详解
#if ( configUSE_TRACE_FACILITY == 1 ) //跟踪调试相关字段初始化
{
pxNewQueue->ucQueueType = ucQueueType;
}
#endif /* configUSE_TRACE_FACILITY */
#if( configUSE_QUEUE_SETS == 1 ) //队列集相关字段初始化
{
pxNewQueue->pxQueueSetContainer = NULL;
}
#endif /* configUSE_QUEUE_SETS */
traceQUEUE_CREATE( pxNewQueue );
}
上面函数在最后使用了**队列复位函数traceQUEUE_CREATE( pxNewQueue );**详细内容:
队列复位函数本质就是又初始化了一遍
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
taskENTER_CRITICAL();
{
//初始化队列相关成员变量
pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->\ (1)
uxItemSize );
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
pxQueue->pcWriteTo = pxQueue->pcHead;
pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - \
( UBaseType_t ) 1U ) * pxQueue->uxItemSize );
pxQueue->cRxLock = queueUNLOCKED;
pxQueue->cTxLock = queueUNLOCKED;
if( xNewQueue == pdFALSE )
{
//由于复位队列以后队列依旧是空的,所以对于那些由于出队(从队列中读取消
//息)而阻塞的任务就依旧保持阻塞壮态。但是对于那些由于入队(向队列中发送
//消息)而阻塞的任务就不同了,这些任务要解除阻塞壮态,从队列的相应列表中
//移除。
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->\
xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
//初始化队列中的列表
vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
六、队列发送函数
分类 | 函数 | 描述 |
---|---|---|
任务级 | xQueueSend() | 发送消息到队列尾部 |
任务级 | xQueueSendToBack() | 发送消息到队列尾部 |
任务级 | xQueueSendToFront() | 发送消息到队列头部 |
任务级 | xQueueOverwrite() | 发送消息到队列,带覆写功能,当队列满自动覆盖 |
中断级 | xQueueSendFromISR() | 发送消息到队列尾部(用于中断服务函数) |
中断级 | xQueueSendToBackFromISR() | 发送消息到队列尾部(用于中断服务函数) |
中断级 | xQueueSendToFrontFromISR() | 发送消息到队列头部(用于中断服务函数) |
中断级 | xQueueOverwriteFromISR() | 发送消息到队列,带覆写功能,当队列满自动覆盖(用于中断服务函数) |
1、函数 xQueueSend()、xQueueSendToBack()和 xQueueSendToFront()
这几个函数本质还是宏定义,原型:xQueueGenericSend()
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, //要发送到哪个队列
const void * const pvItemToQueue, //要发送的消息
TickType_t xTicksToWait, //阻塞时间
const BaseType_t xCopyPosition //发送方式类型
)
{
//...内容较长
}
- 参数:
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue:指向要发送的消息,发送时候会将这个消息拷贝到队列中。
xTicksToWait: 阻塞时间,此参数指示当队列满的时候任务进入阻塞态等待队列空闲的最大时间。如果为 0
的话当队列满的时候就立即返回;当为 **portMAX_DELAY 的话就会一直等待,直到队列有空闲的队 列项,也就是死等,但是宏INCLUDE_vTaskSuspend **必须为 1。
**xCopyPosition:**在上述三个函数中,只是改变了最后一个参数,其参数可以为一下几种:
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 )
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 )
#define queueOVERWRITE ( ( BaseType_t ) 2 )
可以在queue.h中看到,函数1&2宏定义是一样的第四个参数都是queueSEND_TO_BACK。
- 返回值
pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
三个函数原型:
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
const void* pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait);
2、xQueueOverwrite()函数
此函数作用是覆盖写入队列,函数同样是个宏,最终执行的还是xQueueGenericSend()函数,对比xQueueSend()函数的宏定义,会发现,等待时间为0.最后的参数为queueOVERWRITE。
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
前三个函数的参数都是三个参数的,而这个函数是两个参数,因为他是覆盖写入,所以不需要给阻塞时间。
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,const void * pvItemToQueue);
- 参数
同上
- 返回值
pdPASS: 向队列发送消息成功,此函数也只会返回 pdPASS!因为此函数执行过程中不
在乎队列满不满,满了的话我就覆写掉旧的数据,总之肯定能成功。
3、中断级队列发送函数…
xQueueSendFromISR() xQueueSendToBackFromISR() xQueueSendToFrontFromISR()
上三个函数都是在中断中来使用 用来实现队列发送功能的。这三个函数本质还是宏,最终调用
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, //要发送的队列
const void * const pvItemToQueue, //要发送的消息
BaseType_t * const pxHigherPriorityTaskWoken, //是否切换任务
const BaseType_t xCopyPosition //发送类型
)
{
//...
}
- 参数和返回值和上面非中断级的发送函数一样,只是多了一个pxHigherPriorityTaskWoken
用来标记退出此函数以后是否进行任务切换,当此值为pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
BaseType_t xQueueSendFromISR (QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t * pxHigherPriorityTaskWoken)
- 参数
xQueue: 队列句柄,指明要向哪个队列发送数据,创建队列成功以后会返回此队列的队列句柄。
pvItemToQueue:指向要发送的消息,发送的时候会将这个消息拷贝到队列中。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置 的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
- 返回值
pdTRUE: 向队列中发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
4、队列入队函数xQueueOverwriteFromISR()
此函数还是一个宏,最终依旧调用了数 xQueueGenericSendFromISR()
七、队列上锁和解锁(NULL)
八、队列出队函数
分类 | 函数 | 描述 |
---|---|---|
任务级 | xQueueReceive() | 从队列中读取队列项(消息),并且读取完后*删除掉队列项(消息) |
任务级 | xQueuePeek() | 从队列中读取队列项(消息),并且读取完以后*不删除队列项(消息) |
中断级 | xQueueReceiveFromISR() | 从队列中读取队列项(消息),并且读取完后*删除掉队列项(消息) |
中断级 | xQueuePeekFromISR () | 从队列中读取队列项(消息),并且读取完以后*不删除队列项(消息) |
1、xQueueReceive()函数
函数本质还是一个宏,最终调用的是xQueueGenericReceive()
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait )
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )
函数原型
BaseType_t xQueueReceive(QueueHandle_t xQueue, //要出队的队列
void * pvBuffer, //存放地址
TickType_t xTicksToWait //阻塞时间
);
- 参数
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。如果为 0 的 话当队列空的时候就立即返回;当为 portMAX_DELAY的 话 就 会 一 直 等 待 , 直 到 队 列 有 数 据 , 也 就 是 死 等 , 但 是 宏INCLUDE_vTaskSuspend 必须为 1。
- 返回值
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。
2、xQueuePeek()函数
同样是宏
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait )
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdTRUE )
函数原型
BaseType_t xQueuePeek(QueueHandle_t xQueue, //要读出的队列
void * pvBuffer, //存放地址
TickType_t xTicksToWait); //阻塞时间
- 参数
同上
- 返回值
同上
3、xQueueGenericReceive()出队函数原函数
函数原型:
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, //要读哪个队列
void * const pvBuffer, //出队存在哪里
TickType_t xTicksToWait, //阻塞时间
const BaseType_t xJustPeeking //出队是否删除
)
- 参数
xQueue: 队列句柄,指明要读取哪个队列的数据,创建队列成功以后会返回此队列的队列句柄。
pvBuffer: 保存数据的缓冲区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区中。
xTicksToWait: 阻塞时间,此参数指示当队列空的时候任务进入阻塞态等待队列有数据的最大时间。如果为 0 的 话当队列空的时候就立即返回;当为 portMAX_DELAY的 话 就 会 一 直 等 待 , 直到 队 列 有 数 据 , 也 就 是 死 等 , 但是宏INCLUDE_vTaskSuspend 必须为 1。
xJustPeek:标记当读取成功以后是否删除掉队列项,当为 pdTRUE 的时候就不用删除,也就是说你后面再调用 函数 xQueueReceive()获取到的队列项是一样的。当为pdFALSE 的时候就会删除掉这个队列项。
- 返回值
pdTRUE: 从队列中读取数据成功。
pdFALSE: 从队列中读取数据失败。