队列提供了一种任务间或者任务和中断间的通讯机制。
队列是一种数据结构,可以保存固定大小的数据。在创建队列时,队列的长度和大小就确认下来了。通常情况下队列是先进先出(First In First Out),即新数据被发送到队列的尾部,从头部取数据。不过数据也可以发送到队列头部,取数据也是从头部。
如下示意图,一个队列用于TaskA和TaskB之间的通讯,该队列能够保存五个整数。TaskA先后向队列写入了“1”、“2”、“3”三个消息。TaskB依次从quene中取了三条消息。
多个任务可以对同一个队列操作(读、写)
运行过程主要有以下两种情况:
如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况,FreeRTOS的消息存放函数xQueueSend支持超时等待,用户可以设置超时等待,直到有空间可以存放消息或者设置的超时时间溢出。队列可以有多个写入者,因此一个队列可能会阻塞多个任务以等待完成发送操作。 在这种情况下,当队列上的空间可用时,优先级最高的任务会被解除阻塞,从而向队列写数据;如果被阻塞的任务具有相同的优先级,那么等待空间最长的任务将被解除阻塞。
如果放数据的速度慢于取数据的速度,那么会出现消息队列为空的情况,FreeRTOS的消息获取函数xQueueReceive支持超时等待,用户可以设置超时等待,任务将保持在阻塞状态以等待队列中可用数据或者设置的超时时间溢出。如果多个任务都处在阻塞状态等待数据,那么一旦数据准备完毕,优先级最高的任务可以首先获得数据。如果优先级相同,等待时间越长的任务先获得数据。
freertos中使用一个结构体来管理队列,下面的所有API函数都是围绕这个结构体展开,结构体如下:
typedef struct QueueDefinition * QueueHandle_t;
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
int8_t * pcHead; /*< Points to the beginning of the queue storage area. */
int8_t * pcWriteTo; /*< Points to the free next place in the storage area. */
union
{
QueuePointers_t xQueue; /*< Data required exclusively when this structure is used as a queue. */
SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */
} u;
List_t xTasksWaitingToSend; /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */
List_t xTasksWaitingToReceive; /*< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */
volatile UBaseType_t uxMessagesWaiting; /*< The number of items currently in the queue. */
UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */
UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */
volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
API原型 | 函数说明 | 参数说明 | 返回值 |
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
|
用于创建一个队列,并返回一个 xQueueHandle 句柄 。当创建队列时, FreeRTOS 从堆空间中分配内存空间(用于存储队列数据结构本身以及队列中包含的数据单元),并进行相关初始化 |
|
NULL 表示没有足够的堆空间分配给队列而导致创建失败。 非 NULL 值表示队列创建成功。此返回值应当保存下来,以作为 操作此队列的句柄 |
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
|
xQueueSendToBack()用于将数据发送到队列尾。一般都是用的这种方式。中断中使用xQueueSendToBackFromISR |
|
pdPASS:数据被成功发送到队列 errQUEUE_FULL |
BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
|
用于将数据发送到队列首,中断中使用xQueueSendToFrontFromISR |
同上 | 同上 |
xQueueOverwrite( QueueHandle_t xQueue, const void * pvItemToQueue )
|
该API仅仅在队列长度为1的情况下使用(也就是mailbox)。和xQueueSendToBack不同的是,当队列满的时候,直接覆盖写。 |
|
只有pdPASS |
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
|
用于从队列头部接收(读取)数据元素。接收到的元素同时会从队列中删除。中断中使用xQueueReceiveFromISR函数 |
|
pdPASS:成功从队列中读取到数据 errQUEUE_EMPTY |
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
|
从队列头部读取数据元素。和xQueueReceive不同的是,读取之后,不会将队列中的内容删除掉。在中断中使用
xQueuePeekFromISR()
|
同上 | 同上 |
uxQueueMessagesWaiting | 用于查询队列中当前有效数据单元个数。中断中使用uxQueueMessagesWaitingFromISR |
Task | 发送/接收 | 指定超时时间 | 优先级 |
Sender1
|
循环发送100 | 0 | 1 |
Sender2
|
循环发送200 | 0 | 1 |
Receiver
|
接收 | 100ms | 2 |
源码:
QueueHandle_t xQueue;
int main( void )
{
xQueue = xQueueCreate( 5, sizeof( int32_t ) );
if( xQueue != NULL )
{
xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
vTaskStartScheduler();
}
for( ;; );
}
static void vSenderTask( void *pvParameters )
{
int32_t lValueToSend;
BaseType_t xStatus;
lValueToSend = ( int32_t ) pvParameters;
for( ;; )
{
xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
if( xStatus != pdPASS )
{
vPrintString( "Could not send to the queue.\r\n" );
}
}
}
static void vReceiverTask( void *pvParameters )
{
int32_t lReceivedValue;
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );
for( ;; )
{
/* This call should always find the queue empty because this task will
immediately remove any data that is written to the queue. */
if( uxQueueMessagesWaiting( xQueue ) != 0 )
{
vPrintString( "Queue should have been empty!\r\n" );
}
xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
if( xStatus == pdPASS )
{
vPrintStringAndNumber( "Received = ", lReceivedValue );
}
else
{
vPrintString( "Could not receive from the queue.\r\n" );
}
}
}
执行结果:
分析:
一项任务从多个源接收数据是很常见的。 接收任务需要知道数据来自哪里,以确定应该如何处理数据。 一个简单的设计解决方案是使用单个队列来传输结构体,结构体中包含数据值和数据源信息。
Task | 发送/接收 | 指定超时时间 | 优先级 |
Sender1
|
循环发送xStructsToSend[0] | 100ms | 2 |
Sender2
|
循环发送xStructsToSend[1] | 100ms | 2 |
Receiver
|
接收 | 0 | 1 |
源码:
typedef enum
{
eSender1,
eSender2
} DataSource_t;
typedef struct
{
uint8_t ucValue;
DataSource_t eDataSource;
} Data_t;
static const Data_t xStructsToSend[ 2 ] =
{
{ 100, eSender1 }, /* Used by Sender1. */
{ 200, eSender2 } /* Used by Sender2. */
};
int main( void )
{
xQueue = xQueueCreate( 3, sizeof( Data_t ) );
if( xQueue != NULL )
{
xTaskCreate( vSenderTask, "Sender1", 1000, &( xStructsToSend[ 0 ] ), 2, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, &( xStructsToSend[ 1 ] ), 2, NULL );
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
vTaskStartScheduler();
}
for( ;; );
}
static void vSenderTask( void *pvParameters )
{
BaseType_t xStatus;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100 );
for( ;; )
{
xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );
if( xStatus != pdPASS )
{
vPrintString( "Could not send to the queue.\r\n" );
}
}
}
static void vReceiverTask( void *pvParameters )
{
Data_t xReceivedStructure;
BaseType_t xStatus;
for( ;; )
{
if( uxQueueMessagesWaiting( xQueue ) != 3 )
{
vPrintString( "Queue should have been full!\r\n" );
}
xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
if( xStatus == pdPASS )
{
if( xReceivedStructure.eDataSource == eSender1 )
{
vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue );
}
else
{
vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );
}
}
else
{
vPrintString( "Could not receive from the queue.\r\n" );
}
}
}
执行结果:
分析:
和示例1不同的是,示例2的队列几乎一直是满的!
队列中保存的数据很大,更好的办法是不直接传输数据本身,传输数据的指针。在使用的时候需要注意两点:
QueueHandle_t xPointerQueue;
xPointerQueue = xQueueCreate( 5, sizeof( char * ) );
void vStringSendingTask( void *pvParameters )
{
char *pcStringToSend;
const size_t xMaxStringLength = 50;
BaseType_t xStringNumber = 0;
for( ;; )
{
//申请存储字符串的内存空间,可能是动态申请,也可能是静态申请
pcStringToSend = ( char * ) prvGetBuffer( xMaxStringLength );
/* 向申请到的空间中写数据 */
snprintf( pcStringToSend, xMaxStringLength, "String number %d\r\n", xStringNumber );
xStringNumber++;
/* 向队列中写地址 */
xQueueSend( xPointerQueue, &pcStringToSend, portMAX_DELAY );
}
}
void vStringReceivingTask( void *pvParameters )
{
char *pcReceivedString;
for( ;; )
{
/* Receive the address of a buffer. */
xQueueReceive( xPointerQueue, &pcReceivedString, portMAX_DELAY );
/* The buffer holds a string, print it out. */
vPrintString( pcReceivedString );
/* The buffer is not required any more - release it so it can be freed, or re-used. */
prvReleaseBuffer( pcReceivedString );
}
}
示例4、如何使用队列传输数据类型和数据大小不确定的数据?
将示例2和示例3的方法结合起来,就可以达到传输数据类型与大小不确定的数据!
如freertos中TCP/IP stack的源码所示:
typedef enum
{
eNetworkDownEvent = 0,
eNetworkRxEvent,
eTCPAcceptEvent,
} eIPEvent_t;
typedef struct IP_TASK_COMMANDS
{
eIPEvent_t eEventType;
/* A generic pointer that can hold a value, or point to a buffer. */
void *pvData;
} IPStackEvent_t;
1、创建队列
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif
/*
@param uxQueueLength:队列长度,也就是队列最多能存储多少个元素
@param uxItemSize:元素大小,队列中每个元素占多少byte
@param ucQueueType :队列类型,内部使用,不必太多关心
#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 )
@return QueueHandle_t 类型的结构体,也就是队列管理的句柄
*/
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
{
Queue_t * pxNewQueue;
size_t xQueueSizeInBytes;
uint8_t * pucQueueStorage;
/*分配队列结构体和队列元素存储空间*/
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
if( pxNewQueue != NULL )//分配空间成功
{
pucQueueStorage = ( uint8_t * ) pxNewQueue;
pucQueueStorage += sizeof( Queue_t ); //定位到存储队列元素内容的区域
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
{
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}
else
{
traceQUEUE_CREATE_FAILED( ucQueueType );
mtCOVERAGE_TEST_MARKER();
}
return pxNewQueue;
}
待补充。。。。。。
ref:
FreeRTOS高级篇5---FreeRTOS队列分析_朱工的专栏-CSDN博客
FreeRTOS--消息队列 - M&D - 博客园
freeRTOS中文实用教程2--队列 - jasonactions - 博客园