FreeRTOS教程 - 任务间通信:使用队列(Queue)

FreeRTOS教程 - 任务间通信:使用队列(Queue)

📚 本文是【FreeRTOS从入门到精通】系列教程的一部分

🔗 教程完整代码已开源:GitHub - Despacito0o/FreeRTOS

⭐ 如果觉得有帮助,欢迎点个Star支持一下!

一、队列简介

队列是FreeRTOS数据传输方式的一种,可以用于任务间数据传输,也可用于任务与中断间数据传输。在之前的教程中,我们在每个任务中都直接使用串口打印信息,这种结构存在一定问题:每个任务既需要产生数据(如处理传感器数据等),又需要关心数据的显示,导致任务逻辑层次不清晰。

使用队列可以将程序分层:

  • 把显示相关程序单独拿出来,组成一个显示任务
  • 其他任务只需要将各自要显示的数据写入队列
  • 显示任务只需要从队列中读取数据进行显示

这样整体结构就变得很清晰:

  • 任务1到3可以专注自己的数据生成,而不需要关心如何显示数据
  • 显示任务只关心如何显示数据,而不需要关心数据如何产生

下面我们就使用队列实现这种结构。

二、准备工作

首先,打开Despacito文件夹,把工程模板005复制,粘贴并重命名为008,然后打开008。

在这里插入图片描述

这是之前多任务的代码,我们先删除多余的程序,每个任务只保留打印程序,然后保存编译一下,没有错误就可以开始本次实验了。

在这里插入图片描述

/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	//开启GPIOC的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;					//定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;				//GPIO引脚,赋值为第13号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//GPIO速度,赋值为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure);					//将赋值后的构体变量传递给GPIO_Init函数
//	GPIO_SetBits(GPIOC, GPIO_Pin_13);					//将PC13引脚设置为高电平
GPIO_ResetBits(GPIOC, GPIO_Pin_13);						//将PC13引脚设置为低电平

在这里插入图片描述

uint8_t i = 0;
i++;
if(i == 1)
{
    vTaskDelete(myTaskHandler3);
}
if(i == 2)
{
    vTaskDelete(NULL);
}

三、创建队列

在这里插入图片描述

我们需要使用队列,所以要先包含队列的头文件:

#include "queue.h"

本次实验实现一下任务一、任务二写队列,任务三读数据并打印显示。首先我们需要创建一个队列,使用xQueueCreate()函数可以动态创建一个队列。

在这里插入图片描述

右键转到定义,看一下它的参数。它使用了一个宏定义,定义了这个函数,方便我们使用:

  • 第一个参数是队列的长度,即队列可以存放多少个数据
  • 第二个参数是每个数据的大小,单位是字节

由于有两个任务需要写数据,我们将队列长度设置为2。每个任务要写的数据是一个字符串,我们设置20个字节的数据大小:

在这里插入图片描述

xQueueCreate(2, 20);

函数的返回值就是我们创建的队列句柄,我们可以看一下函数原型:

在这里插入图片描述

QueueHandle_t

在main.c上方定义一个变量来接收创建好的队列句柄:

QueueHandle_t myPrintfQueueHandler;

然后在主函数中接收返回值:

myPrintfQueueHandler = xQueueCreate(2, 20);

更严谨的写法还需要判断返回值是否为空(队列创建是否成功),这里简化处理。

四、向队列发送数据

队列创建好后就可以向队列写入数据了。我们使用xQueueSend函数:

在这里插入图片描述

右键转到定义看一下参数,也是一个宏定义函数:

  • 第一个参数是队列句柄
  • 第二个参数是数据的地址,这个数据的值会被复制进队列
  • 第三个参数是阻塞时间,队列满无法写入新数据时会阻塞设定的时间

在这里插入图片描述

#define xQueueSend(xQueue, pvItemToQueue, xTicksToWait)  

在任务1中,先定义一个字符数组,数组大小就是创建队列时指定的数据大小20字节:

在这里插入图片描述

char data[20] = "myTask1 runnig";

然后向队列发送数据:

xQueueSend(myPrintfQueueHandler, data, 0);

第三个参数为0,表示无法写入数据时函数会立即返回。

任务2也做同样的修改,将字符串修改为任务2相关内容:

在这里插入图片描述

五、从队列接收数据

我们修改任务3,用于从队列读取数据。读队列使用xQueueReceive函数:

在这里插入图片描述

参数与写队列类似:

  • 第一个参数是队列句柄
  • 第二个参数是读取的数据存放地址
  • 第三个参数是阻塞时间

我们定义一个20字节字符数组用来接收读取的数据,并将阻塞时间设为portMAX_DELAY,表示读不到数据就一直阻塞在这里,直到有数据可读:

在这里插入图片描述

读队列函数会返回状态,我们需要判断读取是否成功。查看返回值类型:

在这里插入图片描述

定义一个BaseType_t类型的变量,用来接收返回的状态,读取成功会返回pdTRUE

在这里插入图片描述

在判断中,将显示代码放到if语句中,打印接收到的数据。由于读队列没有数据时会一直阻塞,所以下方的delay可以不需要了。

六、编译运行

现在编译一下,确保没有错误:

在这里插入图片描述

进入debug,点击运行,可以看到串口正常打印数据,这样就实现了任务间传输字符串:

在这里插入图片描述

七、使用结构体传输多种数据类型

如果需要传输很多不同类型的数据,可以使用结构体。具体实现如下:

首先定义一个结构体类型,包含要传输的数据:

在这里插入图片描述

修改队列创建时的数据大小,使用sizeof计算结构体大小:

myPrintfQueueHandler = xQueueCreate(2, sizeof(struct print));

然后修改任务函数。在任务1中定义结构体变量并初始化:

在这里插入图片描述

任务2也做同样的修改:

void myTask2(void *arg)
{
    while(1)
    {
        struct print data = {
            .data = "myTask2 runnig"
        };
        // taskENTER_CRITICAL();
        // printf("myTask2 runnig\n");
        // taskEXIT_CRITICAL();
        data.cnt++;
        xQueueSend(myPrintfQueueHandler, &data, 0);
        vTaskDelay(500);
    }
}

最后修改显示任务。定义结构体变量接收数据,并打印结构体中的内容:

在这里插入图片描述

void myTask3(void *arg)
{
    struct print data;
    BaseType_t xStatus;
    while(1)
    {
        xStatus = xQueueReceive(myPrintfQueueHandler, &data, portMAX_DELAY);
        if(xStatus == pdPASS)
        {
            taskENTER_CRITICAL();
            printf("%s:%d\n", data.data, data.cnt);
            taskEXIT_CRITICAL();
        }
        // vTaskDelay(500);
    }
}

编译运行,可以看到正确显示结果,这样就实现了任务间传递多种参数。

八、总结

通过本次实验,我们学习了:

  1. FreeRTOS队列的基本使用方法
  2. 如何使用队列分离数据生成和显示逻辑
  3. 如何通过结构体传递多种类型的数据

队列是FreeRTOS中最基础也是最常用的任务通信方式,掌握它对进一步学习更复杂的RTOS应用至关重要。

📢 更多精彩内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值