文章主要讲解如何创建和使用队列。

消息队列浅析:

一个或者多个任务可以通过 RTOS 内核服务从队列中得到消息,通常先进入消息队列的消息先传
给任务,即先进先出的原则(FIFO),FreeRTOS 的消息队列支持 FIFO 和 LIFO 两种数据存取方式。

相比消息队列,使用全局数组主要有如下四个问题: 

a、使用消息队列可以让 RTOS 内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现
b、使用全局数组需要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心
c、使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题
d、FIFO 机制更有利于数据的处理

任务间消息队列

该消息队列可以存放 10 个消息:

创建 2 个任务 Task1 和 Task2,任务 Task1 向消息队列放数据,任务 Task2 从消息队列取数据。

FreeRTOS 的消息存取采用 FIFO 方式,运行过程主要有以下两种情况:

a、放数据的速度快于取数据的速度

会出现消息队列存放满的情况,FreeRTOS 的消息存放函数 xQueueSend 支持超时等待,用户可以设置超时等待,直到有空间可以存放消息或者设置的超时时间溢出。

b、放数据的速度慢于取数据的速度

会出现消息队列为空的情况,FreeRTOS 的消息获取函数 xQueueReceive 支持超时等待,用户可以设置超时等待,直到消息队列中有消息或者设置的超时时间溢出。 

中断方式消息队列

该消息队列可以存放 10 个消息:

创建任务 Task1 和一个串口接收中断,运行过程主要有以下两种情况:

a、中断服务程序向消息队列放数据,任务 Task1 从消息队列取数据,如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况。

由于中断服务程序里面的消息队列发送函数 xQueueSendFromISR 不支持超时设置,所以发送前要通过函数 xQueueIsQueueFullFromISR 检测消息队列是否满。

b、中断服务程序向消息队列放数据,任务 Task1 从消息队列取数据,如果放数据的速度慢于取数据的速度,那么会出现消息队列存为空的情况。

在 FreeRTOS 的任务中可以通过函数 xQueueReceive 获取消息,因为此函数可以设置超时等待,直到消息队列中有消息存放或者设置的超时时间溢出。 

实际应用中,中断方式的消息机制要注意以下几个问题:

a、中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应;

b、实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优
先级,以便退出中断函数后任务可以得到及时执行;

c、中断服务程序中一定要调用专用于中断的消息队列函数,即以 FromISR 结尾的函数。

1、头文件定义和任务启动

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "supporting_functions.h"

// 定义队列数据收发任务函数
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );

// 定义队列变量
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( ;; );
	return 0;
}

2、队列数据发送任务

static void vSenderTask( void *pvParameters )
{
    int32_t lValueToSend;
    BaseType_t xStatus;

	lValueToSend = ( int32_t ) pvParameters;

	for( ;; )
	{	
		// 参数1:队列
		// 参数2:发送的数据地址
		// 参数3:block 时间
		xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );

		if( xStatus != pdPASS )
		{
			vPrintString( "Could not send to the queue.\r\n" );
		}
	}
}

3、队列数据接收任务

static void vReceiverTask( void *pvParameters )
{
    int32_t lReceivedValue;
    BaseType_t xStatus;
    const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );

	for( ;; )
	{
		if( uxQueueMessagesWaiting( xQueue ) != 0 )
		{
			vPrintString( "Queue should have been empty!\r\n" );
		}
		
		// 参数1:队列
		// 参数2:接收数据地址
		// 参数3:block 时间
		xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );

		if( xStatus == pdPASS )
		{
			vPrintStringAndNumber( "Received = ", lReceivedValue );
		}
		else
		{
			vPrintString( "Could not receive from the queue.\r\n" );
		}
	}
}