FreeRTOS队列(queue)

一、队列概述

1.什么是队列

队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任 务间传递信息。

为什么不使用全局变量?

如果使用全局变量,兔子(任务1)修改了变量 a ,等待树獭(任务3)处理,但树獭处理速度很 慢,在处理数据的过程中,狐狸(任务2)有可能又修改了变量 a ,导致树獭有可能得到的不是正确的数据。
在这种情况下,就可以使用队列。兔子和狐狸产生的数据放在流水线上,树獭可以慢慢一个个依次处理。

关于队列的几个名词:
队列项目:队列中的每一个数据;
队列长度:队列能够存储队列项目的最大数量;
创建队列时,需要指定队列长度及队列项目大小。

2.队列的操作

队列的简化操如入下图所示,从此图可知:
·队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)
·每个数据大小固定
·创建队列时就要指定长度、数据大小
·数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部   读
·也可以强制写队列头部:覆盖头部数据

更详细的操作入下图所示:

3.队列的特点

数据传递方式:
        采用实际值传递,即将数据拷贝到队列中进行传递,也可以传递指针,在传递较大的数据的时候采用指针传递。

多任务访问:
        队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息。

出队、入队阻塞:
        当任务向一个队列发送消息时,可以指定一个阻塞时间。
        
假设此时队列已满无法入队,阻塞时间如果设置为:
        ·0:直接返回不会等待;
        ·0~port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回             不再等待;
        ·port_MAX_DELAY:死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;


二、队列函数

1.创建

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
                            UBaseType_t uxItemSize );

2.写队列

写队列总共有以下几个函数:

一般使用xQueueSend函数,原型如下:

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

 3.读队列

读队列总共有以下几个函数:

一般也只用第一个,原型如下:

BaseType_t xQueueReceive(
    QueueHandle_t xQueue,
    void *pvBuffer,
    TickType_t xTicksToWait
);


三、队列的基本使用代码示例

光看函数是没用的,唯有实操才知道怎么用,由于我之前学了Linux进程间通信的相关API,对于freeRTOS同步互斥的操作还是比较好上手和理解的,这里的句柄相当于Linux的各种标识符,通过句柄来标识不同的对象

本程序会创建一个队列,然后创建2个发送任务、1个接收任务:
        ·发送任务优先级为1,分别往队列中写入100、200
        ·接收任务优先级为2,读队列、打印数值

main函数中创建的队列、创建了发送任务、接收任务,代码如下:

/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;

int main( void )
{
    prvSetupHardware();
    /* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );
    if( xQueue != NULL )
    {
        /* 创建2个任务用于写队列, 传入的参数分别是100、200
        * 任务函数会连续执行,向队列发送数值100、200
        * 优先级为1
        */
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );
        /* 创建1个任务用于读队列
        * 优先级为2, 高于上面的两个任务
        * 这意味着队列一有数据就会被读走
        */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
        /* 启动调度器 */
        vTaskStartScheduler();
    }
    else
    {
    /* 无法创建队列 */
    }
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    return 0;
}

发送任务的函数中,不断往队列中写入数值,代码如下:

static void vSenderTask( void *pvParameters )
{
    int32_t lValueToSend;
    BaseType_t xStatus;
    /* 我们会使用这个函数创建2个任务
    * 这些任务的pvParameters不一样
    */
    lValueToSend = ( int32_t ) pvParameters;
    /* 无限循环 */
    for( ;; )
    {
        /* 写队列
        * xQueue: 写哪个队列
        * &lValueToSend: 写什么数据? 传入数据的地址, 会从这个地址把数据复制进队列
        * 0: 不阻塞, 如果队列满的话, 写入失败, 立刻返回
        */
        xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
        if( xStatus != pdPASS )
        {
            printf( "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( 100UL );
    /* 无限循环 */
    for( ;; )
    {
        /* 读队列
        * xQueue: 读哪个队列
        * &lReceivedValue: 读到的数据复制到这个地址
        * xTicksToWait: 如果队列为空, 阻塞一会
        */
        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
        if( xStatus == pdPASS )
        {
            /* 读到了数据 */
            printf( "Received = %d\r\n", lReceivedValue );
        }
        else
        {
            /* 没读到数据 */
            printf( "Could not receive from the queue.\r\n" );
        }
    }
}

补充:

关于队列的写入,由于send函数的第二个参数是将传入指针所指向的数据复制到队列里,我们当然可以传入一个指向结构体的指针,或指向字符串的指针。就可以做到分辨数据源(当我们想知道是哪个任务传入数据时,可以让任务传入结构体指针,其中包含每个任务的ID号,用于分辨数据源);当传输大型数据时,我们只需要传入指向该大型数据的地址即可。

四、邮箱

用于补充说明,用的不多,因为过于简单

FreeRTOS的邮箱概念跟别的RTOS不一样,这里的邮箱称为"橱窗"也许更恰当:
       · 它是一个队列,队列长度只有1
       · 写邮箱:新数据覆盖旧数据,在任务中使用 xQueueOverwrite() ,在中断中使                               用xQueueOverwriteFromISR()
         既然是覆盖,那么无论邮箱中是否有数据,这些函数总能成功写入数据。
       · 读邮箱:读数据时,数据不会被移除;在任务中使用 xQueuePeek() ,在中断中使用
          xQueuePeekFromISR()
         这意味着,第一次调用时会因为无数据而阻塞,一旦曾经写入数据,以后读邮箱时总能成               功。

五、队列集

队列集一句话概括就是一个队列中,每个项目都是其他队列的句柄。这个队列叫队列集,它可以包含其他队列,在FreeRTOS中,队列集的作用是将多个队列和计数信号量聚合在一起,允许任务以一种更简洁的方式管理这些同步和通信机制。它使得任务能够一次性接收来自多个队列或信号量的消息,提高了系统的灵活性和效率。(想从多个队列得到数据,使用队列集)

1.创建函数

//创建成功返回句柄
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );

传入的参数为队列集的长度:
要监测3个队列A、B、C
队列集的长度是:队列A的长度+队列B的长度 +队列C的长度,否则在A、B、C都满的情况下,队列集没有空间存放所有的handle。

2.将要监测的队列的handle放入队列集

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,
                               QueueSetHandle_t xQueueSet );

第一个参数是要放入的句柄,第二个参数是队列集的句柄。

3.监测哪个队列有数据

//返回有数据的队列的handle
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
                                                TickType_t const xTicksToWait );

第一个参数传入要监测的队列集,第二个参数传入超时时间。

4.队列集代码示例

main函数:

static QueueHandle_t xQueueHandle1;
static QueueHandle_t xQueueHandle2;
static QueueSetHandle_t xQueueSet;
//句柄

int main( void )
{
	TaskHandle_t xHandleTask1;
		
#ifdef DEBUG
  debug();
#endif

	prvSetupHardware();

	/* 1. 创建2个queue */

	xQueueHandle1 = xQueueCreate(2, sizeof(int));
	if (xQueueHandle1 == NULL)
	{
		printf("can not create queue\r\n");
	}

	xQueueHandle2 = xQueueCreate(2, sizeof(int));
	if (xQueueHandle2 == NULL)
	{
		printf("can not create queue\r\n");
	}

	/* 2. 创建queue set */
	xQueueSet = xQueueCreateSet(4);

	/* 3. 把2个queue添加进queue set */
	xQueueAddToSet(xQueueHandle1, xQueueSet);
	xQueueAddToSet(xQueueHandle2, xQueueSet);

	/* 4. 创建3个任务 */
	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
	xTaskCreate(Task3Function, "Task3", 100, NULL, 1, NULL);

	/* Start the scheduler. */
	vTaskStartScheduler();

	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}

任务1和任务2往自己的队列里写数据,任务3监测队列集,有数据就读出来:

void Task1Function(void * param)
{
	int i = 0;
	while (1)
	{
		xQueueSend(xQueueHandle1, &i, portMAX_DELAY);
		i++;
		vTaskDelay(10);
	}
}

void Task2Function(void * param)
{
	int i = -1;
	while (1)
	{
		xQueueSend(xQueueHandle2, &i, portMAX_DELAY);
		i--;
		vTaskDelay(20);
	}
}

void Task3Function(void * param)
{
	QueueSetMemberHandle_t handle;
	int i;
	while (1)
	{
		/* 1. read queue set: which queue has data */
		handle = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);

		/* 2. read queue */
		xQueueReceive(handle, &i, 0);

		/* 3. print */
		printf("get data : %d\r\n", i);
	}
}

注意:要使用队列集相关函数,我们要自己配置一个宏

需要在FreeRTOSConfig.h自己加上标蓝的那一个宏。 

每当有任务往被监测的队列里写数据时,会同时将该队列的handle写入建立了连接的队列集xQueueSelectFromSet函数会阻塞,直到监测的队列有数据或者自己设置的超时时间到了(我们这里设置的是永不超时),监测到有数据的队列会返回它的handle,我们再根据拿到的handle来进行后续操作,读出数据。

运行结果:

  • 25
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sakabu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值