003 FreeRTOS 队列

一、消息队列简介

  基于 FreeRTOS 任务都是具有独立权限的应用。这些独立的任务之间会通过相互通信以提供有用的系统功能。FreeRTOS 中所有的通信与同步机制都是基于队列实现的。

1.队列的特性

数据存储

  队列可以保存有限个具有确定长度的数据单元。通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把队列中的 数据拷贝删除

可被多任务存取

  队列是具有自己 独立权限的内核对象 ,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。

读队列时阻塞

  当某个任务试图读一个队列时,其可以 指定一个阻塞超时时间 。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态
  由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务

写队列时阻塞

  同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。
  由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务
在这里插入图片描述

2.使用队列

2.1xQueueCreate()

在这里插入图片描述
  这个函数原型是一个宏定义,其主要的原型是 xQueueGenericCreate()函数
在这里插入图片描述

函数参数(前两个也是 xQueueCreate 函数的参数)
uxQueueLength:队列能够存储的最大单元数目,即队列深度。
uxItemSize:队列中数据单元的长度,以字节为单位。
ucQueueType:队列的类型(这个参数没有实质用途)
返回值 QueueHandle_t :NULL 表示没有足够的堆空间分配给队列而导致创建失败。非 NULL 值表示队列创建成功。此返回值应当保存下来,以作为操作此队列的句柄。

2.1.1 对于参数 ucQueueType 的具体说明如下:

  在队列中该参数并没有实质上的用途,调用了 prvInitialiseNewQueue 函数,这是一个初始化队列函数。
在这里插入图片描述
prvInitialiseNewQueue 函数中,ucQueueType 并没有具体的使用,并且官方还提示我们要关闭对未使用的参数的警告,configUSE_TRACE_FACILITY 设置为0
在这里插入图片描述
在 FreeRTOSConfig.h 中设置这个宏。
在这里插入图片描述

2.1.2 对于返回值 QueueHandle_t 的具体说明如下:

  QueueHandle_t 类型就是 void* 类型,可以表示能够存储任何类型的地址。意思就是QueueHandle_t 类型变量可以指向任意类型的数据地址。
在这里插入图片描述
  这个类型的变量将存储创建的消息队列,则表明这个变量能被发送消息的任务和获取消息的任务访问,一般来讲是定义为全局变量,这样任何任务都可以访问到,当然有时也会对特定的队列做出保护,不让其他任务访问到。

2.2 xQueueSendToBack() 与 xQueueSendToFront()

  xQueueSend()完全等同于 xQueueSendToBack()。
  但切记不要在中断服务例程中调用 xQueueSendToFront() 或xQueueSendToBack()。系统提供中断安全版本的 xQueueSendToFrontFromISR()与xQueueSendToBackFromISR()用于在中断服务中实现相同的功能

两个函数的参数如下:

xQueue: 目标队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。
pvItemToQueue: 发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域。
xTicksToWait: 阻塞超时时间。如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。
返回值 BaseType_t

2.2.1 xTicksToWait 的具体说明

  如果 xTicksToWait 设为 0 ,并且队列已满,则xQueueSendToFront()与 xQueueSendToBack()均会立即返回。
  阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量 pdMS_TO_TICKS可以用来把心跳时间单位转换为毫秒时间单位。如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制。

2.2.2 返回值 BaseType_t 的具体说明
2.2.2.1. pdPASS

  返回 pdPASS 只会有一种情况,那就是数据被成功发送到队列中。
  如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列空间有效—在超时到来前能够将数据成功写入到队列,函数则会返回 pdPASS。

2.2.2.2. errQUEUE_FULL

  如果由于队列已满而无法将数据写入,则将返回errQUEUE_FULL。
  如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列空间有效。但直到超时也没有其它任务或是中断服务例程读取队列而腾出空间,函数则会返回 errQUEUE_FULL

2.2.3 xQueueSendToBack()

  将数据发送到队列尾
在这里插入图片描述

2.2.4 xQueueSendToFront()

  将数据发送到队列首
在这里插入图片描述

2.3 xQueueReceive()与 xQueuePeek()

  切记不要在中断服务例程中调用 xQueueRceive()和 xQueuePeek()。中断安全版本的替代 API 函数是 xQueueReceiveFromISR()

xQueue: 被读队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。
pvBuffer: 接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。数据单元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一个数据单元。
xTicksToWait: 阻塞超时时间。如果在接收时队列为空,则这个时间是任务处于阻塞状态以等待队列数据有效的最长等待时间。
返回值 BaseType_t

2.3.1 参数 xTicksToWait 的具体说明

  如果 xTicksToWait 设为 0,并且队列为空,则 xQueueRecieve()与 xQueuePeek()均会立即返回。
  阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量 pdMS_TO_TICKS可以用来把心跳时间单位转换为毫秒时间单位。如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制。

2.3.2 返回值 BaseType_t 的具体说明
2.3.2.1 pdPASS

  只有一种情况会返回 pdPASS,那就是成功地从队列中读到数据。
  如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列数据有效—在超时到来前能够从队列中成功读取数据,函数则会返回 pdPASS。

2.3.2.2. errQUEUE_FULL

  如果在读取时由于队列已空而没有读到任何数据,则将返回 errQUEUE_FULL。
  如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待队列数据有效。但直到超时也没有其它任务或是中断服务例程往队列中写入数据,函数则会返回 errQUEUE_FULL。

2.3.3 xQueueReceive()

  从队列中接收(读取)数据单元。
在这里插入图片描述

2.3.4 xQueuePeek()

  从队列中接收(读取)数据单元,不同的是并不从队列中删除接收到的单元
在这里插入图片描述

2.4 uxQueueMessagesWaiting()

  查询队列中当前有效数据单元个数。
  不要在中断服务例程中调用 uxQueueMessagesWaiting()。应当在中断服务中使用其中断安全版本 uxQueueMessagesWaitingFromISR()。
在这里插入图片描述

xQueue: 被查询队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。
返回值UBaseType_t: 当前队列中保存的数据单元个数。返回 0 表明队列为空。

二、消息队列的具体使用 010

  打开 Example010

首先定义了一个消息队列:QueueHandle_t xQueue;

  接着创建了三个任务 Sender1 、Sender2、Receiver,其中前两个任务 Sender1 、Sender2 他们的优先级相同都是1,而且各自向任务实例中传递了一个数值。最后一个任务 Receiver 作为消息队列的消息接收者,任务优先级高于前两个任务为2。

  在第一个红框中调用了 xQueueCreate() 这个函数的第一个参数是队列的深度(长度)这里深度为5,表示可以在这个队列中存储5个数据消息,第二个参数是队列中数据单元的长度,是以字节为单位,为int32_t字节大小。
在这里插入图片描述
  下面是前两个任务 Sender1 、Sender2 的任务实例 vSenderTask ,在这个任务实例中,将创建任务时传递的参数,存储在了一个 int32_t 类型的变量 lValueToSend 中,接着调用一个消息队列发送消息的函数 xQueueSendToBack 将消息发送到队尾,目标队列是刚刚创建的 xQueue 这个队列,发送的消息是创建函数时传递的参数 lValueToSend ,没有使用阻塞的方式进行消息的发送。如果消息队列满,那么将 “Could not send to the queue” 提示符打印在控制台。
在这里插入图片描述
  在接受消息队列的任务中,首先调用 uxQueueMessagesWaiting 查询队列中当前有效数据单元个数,接着调用消息队列接受消息的函数 xQueueReceive ,将接受到了数据存储在一个 int32_t 类型的变量 lValueToSend 中,并且使用的是阻塞的方式。
在这里插入图片描述

  运行的结果如下:
在这里插入图片描述

三、消息队列传递自定义数据011

  打开 Example011。
  这一次创建了一个深度为3,数据长度是 Data_t 类型的长度的消息队列。

在这里插入图片描述

Data_t :是自定义的一个消息类型

在这里插入图片描述

  在Data_t 结构体中,存储了两个数据,传递的值 ucValue 和发送对象 eDataSource 。

  发送对象是一个枚举类型

typedef enum
{
	eSender1,
	eSender2
} DataSource_t;

接着又创建了两个用于发送消息的任务

static const Data_t xStructsToSend[ 2 ] =
{
	{ 100, eSender1 }, /* Used by Sender1. */
	{ 200, eSender2 }  /* Used by Sender2. */
};

//这两个任务的优先级一样,且高于接收消息的任务
//xStructsToSend 是传入的参数,上面已经给出
xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) &( xStructsToSend[ 0 ] ), 2, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) &( xStructsToSend[ 1 ] ), 2, NULL );
/* Create the task that will read from the queue.  The task is created with
priority 1, so below the priority of the sender tasks. */
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );

发送任务实例:
  这是发送任务的统一实例

static void vSenderTask( void *pvParameters )
{
	//接收 消息发送的状态
	BaseType_t xStatus;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
	for( ;; )
	{
		//以100MS作为阻塞值发送消息
		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" );
		}
	}
}

运行一下:
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值