freertos---队列管理

一、queue在freertos中有什么作用?

Queues’ provide a task-to-task, task-to-interrupt, and interrupt-to-task communication  mechanism.

队列提供了一种任务间或者任务和中断间的通讯机制。

二、什么是队列?

队列是一种数据结构,可以保存固定大小的数据。在创建队列时,队列的长度和大小就确认下来了。通常情况下队列是先进先出(First In First Out),即新数据被发送到队列的尾部,从头部取数据。不过数据也可以发送到队列头部,取数据也是从头部。

如下示意图,一个队列用于TaskA和TaskB之间的通讯,该队列能够保存五个整数。TaskA先后向队列写入了“1”、“2”、“3”三个消息。TaskB依次从quene中取了三条消息。

freertos---队列管理_第1张图片

多个任务可以对同一个队列操作(读、写)

运行过程主要有以下两种情况:

如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况,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 从堆空间中分配内存空间(用于存储队列数据结构本身以及队列中包含的数据单元),并进行相关初始化
 
  • uxQueueLength:队列能够存储的最大单元数目,即队列深度。
  • uxItemSize:队列中数据单元的长度,以字节为单位
NULL 表示没有足够的堆空间分配给队列而导致创建失败。
非 NULL 值表示队列创建成功。此返回值应当保存下来,以作为
操作此队列的句柄
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
xQueueSendToBack()用于将数据发送到队列尾。一般都是用的这种方式。中断中使用xQueueSendToBackFromISR
  • xQueue:目标队列的句柄
  • pvItemToQueue:发送数据的指针
  • xTicksToWait:阻塞超时时间,设置为0表示不等待

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不同的是,当队列满的时候,直接覆盖写
  • xQueue:目标队列的句柄
  • pvItemToQueue:发送数据的指针
只有pdPASS
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );

用于从队列头部接收(读取)数据元素。接收到的元素同时会从队列中删除。中断中使用xQueueReceiveFromISR函数

  • xQueue:目标队列的句柄
  • pvBuffer:接受数据的指针
  • xTicksToWait:阻塞超时时间,设置为0表示不等待,立即返回

pdPASS:成功从队列中读取到数据

errQUEUE_EMPTY
:由 于 队 列 为空 导致读取数据失败

BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
从队列头部读取数据元素。和xQueueReceive不同的是,读取之后,不会将队列中的内容删除掉。在中断中使用
xQueuePeekFromISR()
同上 同上
uxQueueMessagesWaiting

 用于查询队列中当前有效数据单元个数。中断中使用uxQueueMessagesWaitingFromISR

四、使用示例

示例1、

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" );
		}
	} 
}

执行结果:

freertos---队列管理_第2张图片

分析:

  • 两个发送任务的优先级相等,因此,轮流被调度
  • 接受任务优先级高于发送队列优先级,这就导致队列中元素个数永远小于等于1,因为一旦有数据放入队列,接受任务就会立马抢占,将数据取走

freertos---队列管理_第3张图片

示例2、接受处理多个源发送的数据

一项任务从多个源接收数据是很常见的。 接收任务需要知道数据来自哪里,以确定应该如何处理数据。 一个简单的设计解决方案是使用单个队列来传输结构体,结构体中包含数据值和数据源信息。 

freertos---队列管理_第4张图片

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" );
		}
	} 
}

执行结果:

freertos---队列管理_第5张图片

分析:

  • t1:sender1 开始发送数据到queue
  • t2:由于sender1发送了三次数据导致queue满,从而sender1进行Blocked state;此时sender2具有最高优先级,sender2进入Running state
  • t3:sender2发现queue满,也进入Blocked state。此时,receiver进入Running state
  • t4:receiver从队列中取出一个元素后,该任务立马sender1被抢占(sender1和sender2优先级相等,sender1等待的时间比sender2长)。
  • t5:sender1向队列中写入一个元素后,队列满,又进入阻塞状态。此时sender2也处于阻塞状态,因此不会被调度器调度。从而receiver进入Running state,从队列中取出一个元素后,立马被sender2抢占(sender2等待的时间比sender1长)。
  • t6:重复以上过程

和示例1不同的是,示例2的队列几乎一直是满的!

freertos---队列管理_第6张图片

 示例3、如何使用队列传输大数据?

 队列中保存的数据很大,更好的办法是不直接传输数据本身,传输数据的指针。在使用的时候需要注意两点:

  • 确保发送任务和接受任务不会同时修改指针指向的内存
  • 如果指针指向的内存是动态分配的,则要确保使用这块内存之前没有被释放
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 - 博客园

你可能感兴趣的:(freertos,freertos)