FreeRTOS-队列管理

一、简介

        “队列”提供了任务到任务、任务到中断和中断到任务的通信机制

二、队列的特征

        队列可以保存有限数量的固定大小的数据项。一个队列可以容纳的最大条目数称为它的“长度”。在创建队列时设置每个数据项的长度和大小。

        队列通常用作先进先出(FIFO)缓冲区,其中数据被写入队列的末端(尾部)并从队列的前端(头部)删除。图1演示了将数据写入队列和从队列中读取数据,该队列被用作FIFO。也可以写到队列的前面,并覆盖已经在队列前面的数据。

图1 向队列写入和从队列读取的示例序列 

三、使用队列的常用API函数

1.xQueueCreate() API函数

        在使用队列之前,必须显式地创建队列。队列由句柄引用,句柄是QueueHandle_t类型的变量。xQueueCreate() API函数创建一个队列并返回一个QueueHandle_t,该QueueHandle_t引用了它创建的队列。

        当创建队列时,FreeRTOS从FreeRTOS堆中分配RAM。RAM用于保存队列数据结构和包含在队列中的项。如果要创建的队列没有足够的堆内存可用,xQueueCreate()将返回NULL。函数原型如下:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize )
参数名称描述
uxQueueLength 正在创建的队列在任何时候可以容纳的最大项数。
uxItemSize 可以存储在队列中的每个数据项的字节大小。
Return Value 

如果返回NULL,则不能创建队列,因为FreeRTOS没有足够的堆内存来分配队列数据结构和存储区域。

返回非null值表示队列已成功创建。返回的值应该作为创建队列的句柄存储。

        创建队列后,可以使用xQueueReset() API函数将队列返回到其原始空状态

2.xQueueSendToBack()和xQueueSendToFront() API函数

        正如函数名字的意思,xQueueSendToBack()用于将数据发送到队列的后端(尾部),xQueueSendToFront()用于将数据发送到队列的前端(头部)。xQueueSend()等价于xQueueSendToBack(),并且与xQueueSendToBack()完全相同。两个函数的原型如下:

注意:永远不要从中断服务例程调用xQueueSendToFront()或xQueueSendToBack()。中断安全版本xQueueSendToFrontFromISR()和xQueueSendToBackFromISR()。

BaseType_t xQueueSendToFront( QueueHandle_t xQueue, 
                              const void * pvItemToQueue, 
                              TickType_t xTicksToWait ); 

 xQueueSendToFront() API函数原型

BaseType_t xQueueSendToBack( QueueHandle_t xQueue, 
                             const void * pvItemToQueue, 
                             TickType_t xTicksToWait ); 

 xQueueSendToBack() API函数原型

参数名称/返回值描述
xQueue正在向其发送(写入)数据的队列句柄。队列句柄将从用于创建队列的xQueueCreate()调用中返回。
pvItemToQueue 

指向要复制到队列中的数据的指针

在创建队列时设置队列可以容纳的每个项目的大小,因此这些字节将从pvItemToQueue复制到队列存储区域。

xTicksToWait 

如果队列已经满了,任务保持在阻塞状态以等待队列上可用空间的最大时间。

如果xTicksToWait为零并且队列已经满了,xQueueSendToFront()和xQueueSendToBack()都将立即返回。

块时间以滴答周期指定,因此它所代表的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以ticks为单位指定的时间。

将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(没有超时),前提是在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1。

Returned value

有两个可能的返回值:

1。只有当数据成功发送到队列时,才会返回pdPASS。如果指定了块时间(xTicksToWait不为零),那么在函数返回之前,调用任务可能被置于Blocked状态,等待队列中有可用空间,但在块时间到期之前,数据已成功写入队列。

2. 如果由于队列已满而无法将数据写入队列,则返回errQUEUE_FULL。如果指定了阻塞时间(xTicksToWait不为零),那么调用任务将被置于阻塞状态,以等待另一个任务或中断在队列中腾出空间,但是指定的阻塞时间在发生之前已经过期。

3.xQueueReceive() API函数

        xQueueReceive()用于从队列中接收(读取)项。接收到的项目将从队列中删除。原型如下:

注意:永远不要从中断服务例程中调用xqueuerreceive()。中断安全的是xQueueReceiveFromISR()

BaseType_t xQueueReceive( QueueHandle_t xQueue, 
                          void * const pvBuffer, 
                          TickType_t xTicksToWait ); 
参数名称/返回值描述
xQueue

正在从中接收(读取)数据的队列句柄。队列句柄将从用于创建队列的xQueueCreate()调用中返回。

pvBuffer 

指向存储器的指针,接收到的数据将被复制到其中。

队列保存的每个数据项的大小在创建队列时设置。pvBuffer所指向的内存必须至少大到足以容纳这么多字节。

xTicksToWait 

如果队列已经为空,则任务应保持在Blocked状态以等待队列上的数据可用的最大时间。

如果xTicksToWait为零,那么如果队列已经为空,xQueueReceive()将立即返回。

块时间以滴答周期指定,因此它所代表的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以ticks为单位指定的时间。

将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(没有超时),前提是在FreeRTOSConfig.h中INCLUDE_vTaskSuspend设置为1。

Returned value

有两个可能的返回值:

1。只有当数据从队列中成功读取时,才会返回pdPASS。如果指定了块时间(xTicksToWait不为零),则可能将调用任务置于Blocked状态,以等待队列上的数据可用,但是在块时间到期之前成功地从队列中读取了数据。

2. 如果由于队列已经为空而无法从队列中读取数据,则返回errQUEUE_EMPTY。如果指定了阻塞时间(xTicksToWait不为零),则调用任务将被置于阻塞状态,以等待另一个任务或中断将数据发送到队列,但阻塞时间在此之前就过期了。

4.uxQueueMessagesWaiting() API

        uxQueueMessagesWaiting()用于查询当前在队列中的项数。函数原型如下:

注意:永远不要从中断服务例程调用uxQueueMessagesWaiting()。中断中使用uxQueueMessagesWaitingFromISR()。

UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue ); 
参数名称/返回值描述
xQueue 正在查询的队列的句柄。队列句柄将从用于创建队列的xQueueCreate()调用中返回。
Returned value正在查询的队列当前持有的项数。如果返回0,则队列为空。

四、实例

1.从队列接收时阻塞

        此示例演示了创建队列、从多个任务向队列发送数据以及从队列接收数据的过程。创建队列是为了保存int32_t类型的数据项。发送到队列的任务不指定阻塞时间,而从队列接收的任务则指定阻塞时间。

        发送到队列的任务的优先级低于从队列接收的任务的优先级。这意味着队列不应该包含多个项目,因为一旦数据被发送到队列,接收任务就会解除阻塞,抢占发送任务,并删除数据,使队列再次为空。

        创建两个实例,一个连续地将值100写入队列,另一个连续地将值200写入同一个队列。task参数用于将这些值传递给每个任务实例。

        首先在main函数里创建队列以容纳最多5个值,每个值都足够大,可以容纳int32_t类型的变量。然后再创建2个任务,一个向队列里发送100,另一个向队列发送200。程序如下:

xQueueHandle xqueue;//队列的句柄
int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	USART_Config();//串口配置  通过keil软件仿真

	xqueue=xQueueCreate(5, sizeof(uint32_t));
    //创建队列 内容是5个大小为uint32_t型的数据

	xTaskCreate(vsendertask,"vsendertask",128,(void *)100,1,NULL);
	xTaskCreate(vsendertask,"vsendertask",128,(void *)200,1,NULL);
    //创建2个任务,同样的名字但是参数不一样
	
    xTaskCreate(vreceivertask,"vreceivertask",128,NULL,2,NULL);
    //接收任务,优先级比发送高
	
    vTaskStartScheduler();//开启任务调度器

}

        创建好任务和队列后,编写2个任务的函数,具体程序如下:

void vsendertask(void *pvparameter)//发送任务
{
	uint32_t uvaluetosend;
	BaseType_t xStatus;
	uvaluetosend=(uint32_t)pvparameter;//存的是参数的地址,方便使用
	while(1)
	{
		xStatus = xQueueSendToBack( xqueue, &uvaluetosend, 0 ); 
		if( xStatus != pdPASS ) 
        { 
            printf("无法发送到队列\r\n"); 
        } 
	}
	
}

void vreceivertask(void *pvparameter)//接收任务
{
	uint32_t uvaluetoreceive;
	BaseType_t xStatus;

	while(1)
	{
		xStatus = xQueueReceive( xqueue, &uvaluetoreceive, 10 ); 
		if(xStatus == pdPASS)
		{
			printf( "接收到的数据是:%ld\r\n", uvaluetoreceive);
		}
		else
		{
			printf( "队列是空的\r\n" );
		}
	}
}

        试验的结果如图2所示,和预计的效果一致,首先运行接收任务,但是由于没有数据,所以接收任务阻塞,此时由等待最久的发送100任务向队列发送数据,队列有数据后,接收任务立刻被唤醒进行打印数据,然后阻塞,再由发送200的任务运行,以此类推,从此轮流打印100和200。 

图2 试验效果 

2. 队列上发送结构体数据

        在很多情况下数据是结构体形式的,有ID有数据的格式,同时发送队列的任务也不止一个,可能是多个任务发送,一个任务接收,接收任务通过ID判断类型,再具体分类使用数据,因此在这里创建3个任务,两个发送任务,一个接收任务,且发送任务的优先级比接收任务的优先级高。其中数据类型为结构体形式,结构体数据结构如下:

typedef enum  //通过枚举充当任务的ID
{
	task1enum,
	task2enum
}tasknum;

typedef struct //数据结构体
{
	uint32_t value;
	tasknum tasknumvalue;
}taskstruct;
taskstruct task12struct[2]={{100,task1enum},{200,task2enum}};
//枚举1任务发送数据100  枚举2任务发送数据200

        发送任务程序如下:

void vsendertask(void *pvparameter)
{
	while(1)
	{
		xQueueSendToBack( xqueue, pvparameter, 100 ); //发送数据
	}
	
}

        接收任务程序如下:

void vreceivertask(void *pvparameter)
{
	taskstruct uvaluetoreceive;
	BaseType_t xStatus;

	while(1)
	{
		xStatus = xQueueReceive( xqueue, &uvaluetoreceive, 10 ); 
		if(xStatus == pdPASS)
		{
			if(uvaluetoreceive.tasknumvalue == task1enum)//判断ID号
			{
				printf("数据从任务1来:value=%d\n",uvaluetoreceive.value);
			}
			if(uvaluetoreceive.tasknumvalue == task2enum)
			{
				printf("数据从任务2来:value=%d\n",uvaluetoreceive.value);
			}
		}
		else
		{
			printf( "队列是空的\r\n" );
		}
	}
}

        主函数创建3个任务,主函数程序如下:

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	USART_Config();
	xqueue=xQueueCreate(5, sizeof(taskstruct));
	xTaskCreate(vsendertask,"vsendertask",128,(void *)&task12struct[0],3,NULL);
	xTaskCreate(vsendertask,"vsendertask",128,(void *)&task12struct[1],3,NULL);
//创建2个发送任务
	xTaskCreate(vreceivertask,"vreceivertask",128,NULL,2,NULL);
//创建一个接收任务
	vTaskStartScheduler();

}

        按照程序所示,试验结果应该是任务创建完成后首先运行发送任务1任务,当队列满了后发送任务1和2均阻塞,接收任务接收一个后,队列有空位,此时发送任务2被唤醒运行像队列发送数据,然后阻塞,接收任务接收一个数据后,发送任务1运行,以此类推。通过keil仿真所示,仿真结果和预期一致,仿真结果如图3所示。

图3 仿真结果图 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值