13. 消息队列

消息队列是一种常用于任务键通信的数据结构,队列可以在任务与任务间、中断与任务间传递信息,实现了任务接收来自其他任务或者中断的不定长数据。

任务能从队列中读取信息,当队列中的消息为空时,读取消息的任务将被阻塞。用户还可以指定阻塞的任务时间xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效,当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息。当等待的时间超过指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。

13.1 消息队列运作机制

FreeRTOS的消息队列控制块由多个元素组成,当消息队列被创建时系统会为控制块分配对应的内存空间,用于保存消息队列的一些信息,如信息的存储位置、头指针pcHead、尾指针pcTail、消息大小uxItemSize以及队列长度uxLength等

任务或者中断服务程序都可以给消息队列发送消息,发送消息时如果队列未满或者允许覆盖入队,FreeRTOS会将消息复制到消息队列队尾,否则会根据用户指定的阻塞超时时间进行阻塞,如果时间超时到了,那么任务会从阻塞态转换为就绪态,并且此时发送信息的任务或者中断会收到一个错误代码“errQUEUE_FULL”。如果因为队列满了被阻塞,那么在等待的时候如果有别的任务读取了队列中的信息,那么被阻塞的任务将会有空间来发送信息,同时解除阻塞态进入就绪态。

任务同样也可以读取信息,当队列中没有信息的时候进行信息的读取,那么任务会被阻塞,我们同样可以指定超时时间。如果在阻塞的时候有任务向队列中发送了消息,那么阻塞会被解除。

队列还可以发送紧急消息,发送紧急消息时发送的位置是队列的队头而非队尾,这样接受者就能够优先接收紧急消息,从而及时进行消息处理

当消息队列不再被使用时,应该将其删除以释放系统资源,一旦操作完成,队列将被永久删除。

假如有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权。

只有在任务中发送消息才允许进入阻塞态,而在中断中发送消息不允许带有阻塞机制,需要调用在中断中发送消息的API函数接口。

13.2 消息队列控制块

typedef struct QueueDefinition
{
	int8_t *pcHead;					/* 指向队列消息存储区起始位置,即第一个消息空间 */
	int8_t *pcTail;					/* 指向队列消息存储区结束位置地址 */
	int8_t *pcWriteTo;				/* 指向队列消息存储区下一个可用消息空间 */

	union							/* pcReadFrom与uxRecursiveCallCount是一对互斥变量,使用联合体来确保两个互斥的结构体成员不会同时出现。当结构体用于队列式,pcReadFrom指向出队消息空间的最后一个,也就是读取消息时是从pcReadFrom指向的空间读取消息内容 */
	{
		int8_t *pcReadFrom;			/*< Points to the last place that a queued item was read from when the structure is used as a queue. */
		UBaseType_t uxRecursiveCallCount;	/* 当结构体用于互斥量时,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;	/*< 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;

/* The old xQUEUE name is maintained above then typedefed to the new Queue_t
name below to enable the use of older kernel aware debuggers. */
typedef xQUEUE Queue_t;

13.3 相关函数

13.3.1 队列创建与删除
13.3.1.1 xQueueCreate() 动态创建消息队列

创建队列时真正使用的函数是xQueueGenericCreate()

QueueHandle_t xQueueCreate( uxQueueLength, uxItemSize ) 

参数:	uxQueueLength:队列中能够存储的最大消息单元数目,即队列长度
	  uxItemSize:队列中消息单元的大小,以字节为单位
返回值:如果创建成功,则返回一个队列句柄,用于访问创建的队列;如果创建不成功,则返回NULL,可能的原因是创建队列需要的RAM无法分配成功。

注意事项:

  1. 要想用该函数,必须在FreeRTOSConfig.h中把configSUPPORT_DYNAMIC_ALLOCATION定义为1来启用,这是一个用于启用动态内存分配的宏,通常情况下,在FreeRTOS中凡是创建任务、队列、信号量和互斥量等内核对象都需要用动态内存分配,所以这个宏默认在FreeRTOS.h头文件中已经启用。
13.3.1.2 xQueueCreaetStatic() 静态创建消息队列
QueueHandle xQueueCreateStatic( UBaseType_t uxQueueLength, 		/* 队列能够存储的最大单元数目,即队列函数 */
								UBaseType_t uxItemSize, 		/* 队列中数据单元的长度,以字节为单位 */
								uint8_t *pucQueueStorageBuffer, /* 指针,指向一个uint8_t类型的数组,数组的大小至少有uxQueueLength* uxItemSize个字节,当uxItemSize为0时,pucQueueStorageBuffer可以为NULL */
								StaticQueue_t *pxQueueBuffer 	/* 指向StaticQueue_t类型的变量,该变量用于存储队列的数据结构 */
								) 	
								返回值:如果创建成功,则返回一个队列句柄,用于访问创建的队列;如果创建不成功,则返回NULL,可能的原因是创建队列需要的RAM无法分配成功。

注意事项:

  1. 要想用该函数,必须在FreeRTOSConfig.h中把configSUPPORT_STATIC_ALLOCATION定义为1
13.3.1.3 vQueueDelete() 删除消息队列
void vQueueDelete(QueueHandle_t	xQueue)
13.3.2 消息的发送
13.3.2.1 xQueueSend() 发送队列消息
BaseType_t xQueueSend(QueueHandle_t	xQueue,				/* 队列句柄 */
					  const void * pvItemToQueue,		/* 指针,指向要发送到的队列尾部的队列消息 */
					  TickType_t xTicksToWait			/* 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait被设置成0,函数立刻返回。如果INCLUDE_vTaskSuspend设置成1,并且指定延时为portMAX_DELAY,将一直等待等到空闲 */
)

返回值:发送成功:pdTURE
	   发送失败:errQUEUE_FULL

注意事项:

  1. 该函数消息以复制的形式入队,而不是以引用的形式。该函数决不能在中断服务程序中被调用,中断中必须使用带有中断保护功能的xQueueSendFromISR来代替。
13.3.2.2 xQueueSendFromISR() 中断中发送消息队列
BaseType_t xQueueSendFromISR(QueueHandle_t	xQueue,				/* 队列句柄*/
							 const void *pvItemToQueue,			/* 指针,指向要发送到队列尾部的消息 */
							 BaseType_t	*pxHigherPriorityTaskWoken	/* 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成pdTRUE,然后在中断退出前进行一次上下文切换,去执行比唤醒任务的优先级更高的任务,从FreeRTOS V7.3.0起,pxHigherPriorityTaskWorken作为一个可选参数,可以设置为NULL */
							 )
返回值: 发送成功:pdTRUE
	    发送失败:errQUEUE_FULL

注意事项:

  1. 在中断服务函数中pxHigherPriorityTaskWoken是要我们自己定义然后传入,然后自己判断是否需要上下文转换,我们要手动转换。
13.3.2.2.1 示例
void vBufferISR(void)
{
	char	cIn;
	BaseType_t	xHigherPriorityTaskWoken;
	
	/*在ISR开始时,我们并没有唤醒任务*/
	xHigherPriorityTaskWoken = pdFALSE;
	
	/* 直到缓冲区为空 */
	do {
		/* 从缓冲区获取一个字节的数据 */
		cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );
		/* 发送这个数据 */
		xQueueSendFromISR(xRxQueue,&cIn,&xHigherPriorityTaskWoken);
		
	} while(portINPUT_BYTE(BUFFER_COUNT));
	
	/* 这时buffer已经为空,如果需要,则进行上下文切换 */
	if(xHigherPriorityTaskWoken)
	{
		/* 上下文切换,这是一个宏,不同的处理器,具体的方法不一样 */
		taskYIELD_FROM_ISR();
	}
}
13.3.2.3 xQueueSendToFront() 向队列队首发送消息
BaseType_t xQueueSendToFront(	QueueHandle_t	xQueue,			/* 队列句柄 */
								const void * pvItemToQueue,		/* 指针,指向要发送到队首的消息 */
								TickType_t	xTicksToWait		/* 队列满时,等待队列空闲的最大超时时间。如果队列满并且xTicksToWait被设置成0,函数立刻返回。如果INCLUDE_vTaskSuspend设置成1,并且指定延时为portMAX_DELAY,将一直等待等到空闲  */
)
返回值:	发送成功:pdTRUE
		   发送失败:errQUEUE_FULL
13.3.2.4 xQueueSendToFrontFromISR() 中断中向队列队首发送消息
BaseType_t xQueueSendFromISR(QueueHandle_t	xQueue,				/* 队列句柄*/
							 const void *pvItemToQueue,			/* 指针,指向要发送到队列尾部的消息 */
							 BaseType_t	*pxHigherPriorityTaskWoken	/* 如果入队导致一个任务解锁,并且解锁的任务优先级高于当前被中断的任务,则将*pxHigherPriorityTaskWoken设置成pdTRUE,然后在中断退出前进行一次上下文切换,去执行比唤醒任务的优先级更高的任务,从FreeRTOS V7.3.0起,pxHigherPriorityTaskWorken作为一个可选参数,可以设置为NULL */
							 )
返回值: 发送成功:pdTRUE
	    发送失败:errQUEUE_FULL
13.3.3 消息的接收
13.3.3.1 xQueueReceive() 任务接收消息并删除消息
BaseType_t xQueueReceive( QueueHandle_t xQueue,			/* 队列句柄 */
						  void *pvBuffer, 		/* 指针,指向收到的要保存的数据 */
						  TickType_t xTicksToWait )	/* 队列为空时,阻塞超时的最大时间 */
						  
返回值: 接收成功:pdTRUE
		接收失败:pdFALSE

注意事项:

关于阻塞时间:如果该参数设置为0,函数立即返回,超时时间的单位为系统单位周期,常量portTICK_PERIOD_MS()用于辅助计算真是的时间,单位为ms。如果INCLUDE_vTaskSuspend设置为1,并且指定延时为portMAX_DELAY,将导致任务无限期阻塞

起始xQueueReceive是一个宏定义函数

#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )
13.3.3.2 xQueuePeek() 任务接收消息但不删除消息
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait )

用法和xQueueReceive是一样的,只是不会删除消息。

13.3.3.3 xQueueReceiveFromISR() 中断接收消息并删除消息
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,				/* 队列句柄 */
								 void * const pvBuffer, 			/* 指针,指向收到的要保存的数据 */
								 BaseType_t * const pxHigherPriorityTaskWoken )/* 如果xQueueReceiveFromISR函数导致一个任务解除阻塞,那么 *pxHigherPriorityTaskWoken将被设置成pdTRUR,否则*pxHigherPriorityTaskWoken的值将不变。从FreeRTOS V7.3.0起,pxHigherPriorityTaskWorken作为一个可选参数,可以设置为NULL */

返回值:	接收成功:pdTRUE
		   接收失败:pdFALSE
13.3.3.4 xQueuePeekFromISR() 中断接收消息但不删除消息
BaseType_t	xQueuePeekFromISR(QueueHandle_t xQueue,     /* 队列句柄 */
							  void * pvBuffer)			/* 指针,指向接收到要保存的数据 */

13.4 示例

13.4.1 消息队列发送消息示例
13.4.1.1 从任务中发送消息
static void Send_Task(void* parameter)
{
	BaseType_t	xReturn = pdPASS;
	uint32_t	send_data = 1;
	while(1)
	{
		if(判断条件)
		{
			xReturn = xQueueSend(	Test_Queue,		/*消息队列句柄*/
									&send_data,		 /* 发送的消息内容 */
									0				 /* 等待时间0 */
								)
		}
		if(xReturn == pdPASS)
		{
			printf("消息发送成功");
		}
	}
}


13.4.1.2 从中断中发送消息
void vBufferISR(void)

{

	char cIn;
	BaseType_t	xHigherPriorityTaskWoken;		//这里是一个重点要注意的
	
	xHigherPriorityTaskWoken = pdFALSE;
	
	cIn = 获取的值;
	
	/* 发送这个数据 */
	xQueueSendFromISR(xRxQueue,&cIn,&xHigherPriorityTaskWoken);
	
	/* 手动判断是否需要上下文切换*/
	if(xHigherPriorityTaskWoken)
	{
		taskYIELD();			//任务调度是调用这个函数来进行的。
	}


}
13.4.2 消息队列接收消息示例
13.4.2.1 从任务中接收消息
static void Receive_Task(void* parameter)
{
	BaseType_t	xReturn = pdTRUE;
	uint32_t	r_queue;
	while(1)
	{
		xReturn = xQueueReceive( Test_Queue,
								 &r_queue,
								 portMAX_DELAY
								);
		if(xReturn == pdTRUE)
        {
        	printf("成功");
        }
        else
        {
        	printf("出错");
        }
	}
}

如果不想删除数据 就将xQueueReceive函数改用xQueuePeek即可

13.4.2.2 从中断中接收消息
QueueHandle_t	xQueue;
char cValueToPost = 'a';
const	TickType_t xTicksToWait = (TickType_T)0xff;
xQueue = xQueueCreate(10,sizeof(char));
xQueueSend(xQueue,(void*)&cValueToPost,xTicksToWait);

void vISR_Routine(void)
{
	BaseType_t	xHigherPriorityTaskWoken = pdFALSE;
	char cRxedChar;
	xQueueReceiveFromISR(xQueue,
						(void*) &cRxedChar,
						&xHigherPriorityTaskWoken
						)
	输出这个值...
    
	if(xHigherPriorityTaskWoken!=pdFAALSE)
    {
    	taskYIELD();
    }
	
}