FreeRTOS教程----队列详解

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏

专栏传送门《产品测评专栏》《Ai智能体专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

一、队列概述

二、队列函数

2.1、创建队列

2.1.1、动态分配内存

2.1.2、静态分配

2.2、队列的写入函数

2.3、队列的读取(接收)函数

三、队列集

3.1、队列集使用步骤

3.2、队列集核心API函数

[3.2.1、创建队列集 xQueueCreateSet](#3.2.1、创建队列集 xQueueCreateSet)

[3.2.2、把队列加入队列集 xQueueAddToSet](#3.2.2、把队列加入队列集 xQueueAddToSet)

[3.2.3、读取队列集 xQueueSelectFromSet](#3.2.3、读取队列集 xQueueSelectFromSet)

3.3、示例

总结


前言

在 FreeRTOS 多任务系统中,任务之间不能直接互相访问变量 ,否则会出现数据竞争、死机、打印乱码等问题。队列就是 FreeRTOS 提供的最基础、最核心、最通用的任务间通信工具,掌握队列,信号量、互斥锁、消息邮箱都能一通百通。


一、队列概述

队列(Queue) 是一种用于任务间通信的数据结构。本质上,它像一个 "带锁的管道" 或 "安全的消息邮箱",允许一个任务发送数据、另一个任务接收数据,且不会发生数据冲突。队列是线程安全的。

队列可以包含若干个数据:队列中有若干项,这被称为长度。每个数据大小固定。

创建队列时就要指定长度、数据大小。

数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读。也可以强制写队列头部:覆盖头部数据。

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

队列中,数据的读写本质就是环形缓冲区,在此基础上增加了互斥措施、阻塞 - 唤醒机制。

如果队列不传输数据,仅调整 "数据个数"(只传递状态) ,它就是信号量(semaphore)。

如果信号量中限定 "数据个数" 的最大值为 1,它就是互斥量(mutex)。

队列通常用作环形****FIFO(先进先出) 缓冲区,其中数据被写入队列的末尾(尾部) ,此时尾部就变成头部了,并从队列的前端(头部)删除。

二、队列函数

2.1、创建队列

队列的创建有两种方法:动态分配内存、静态分配内存。

2.1.1、动态分配内存

这是最常用的方式,内存由 FreeRTOS 内核自动分配,用完自动回收,省心省力。

函数原型:

cpp 复制代码
QueueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength,    // 队列长度:最多存多少个数据
    UBaseType_t uxItemSize         // 每个数据的大小(字节)
);

参数说明:

参数 说明
uxQueueLength 队列的最大容量,比如填 10,就表示最多能存 10 条消息
uxItemSize 单条消息的大小,比如存 uint32_t 就是 4,存自定义结构体就是 sizeof(MyStruct)

返回值

非 NULL:创建成功,返回队列句柄,后续操作都用这个句柄。

NULL:创建失败,通常是堆内存不足。

2.1.2、静态分配

如果你做的是高可靠性项目,不想依赖堆内存,就可以用静态方式,自己提前定义好内存缓冲区。

cpp 复制代码
QueueHandle_t xQueueCreateStatic(
    UBaseType_t uxQueueLength,
    UBaseType_t uxItemSize,
    uint8_t *pucQueueStorageBuffer,  // 数据存储缓冲区
    StaticQueue_t *pxQueueBuffer     // 队列控制块
);

参数说明

参数 说明
uxQueueLength / uxItemSize 和动态创建的参数含义完全一样
pucQueueStorageBuffer 你自己定义的 uint8_t 数组,大小必须是 uxQueueLength * uxItemSize
pxQueueBuffer 必须是一个 StaticQueue_t 类型的结构体变量,用来保存队列的元数据

2.2、队列的写入函数

创建好队列后,我们就可以往里面发数据了。FreeRTOS 提供了一套 xQueueSend 系列函数,功能非常丰富。

函数 描述
xQueueSend() / xQueueSendToBack() 往队列的尾部写入消息(标准 FIFO)
xQueueSendToFront() 往队列的头部写入消息(插队)
xQueueOverwrite() 覆写队列消息(仅用于队列长度为 1 的情况)
xQueueSendFromISR() / xQueueSendToBackFromISR() 中断服务函数中往队列尾部写入消息
xQueueSendToFrontFromISR() 在中断中往队列头部写入消息
xQueueOverwriteFromISR() 在中断中覆写队列消息(仅用于队列长度为 1 的情况)

2.3、队列的读取(接收)函数

发送了数据,自然就要读取。FreeRTOS 也提供了一套读取 API,核心是 xQueueReceive

函数 描述
xQueueReceive() 从队列头部读取消息,并删除消息
xQueuePeek() 从队列头部读取消息,但不删除消息(数据还在队列里)
xQueueReceiveFromISR() 在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR() 在中断中从队列头部读取消息,不删除
cpp 复制代码
BaseType_t xQueueReceive(
    QueueHandle_t xQueue,
    void * const pvBuffer,
    TickType_t xTicksToWait
);
参数 说明
xQueue 队列句柄,要读哪个队列
pvBuffer 数据接收缓冲区,队列的数据会被复制到这里(大小在创建队列时已指定)
xTicksToWait 阻塞超时时间:队列为空时,任务最多等待多久
返回值 pdPASS:成功读取;pdFALSE:超时失败

三、队列集

普通队列只能传递同一种数据类型,而且一个任务只能阻塞等待一个队列。如果你的任务需要同时监听多个队列(比如同时接收按键、串口、传感器数据),就需要用到队列集(Queue Set)

3.1、队列集使用步骤

启用队列集:在 FreeRTOSConfig.h 中,将 configUSE_QUEUE_SETS 配置为 1。

创建队列集:xQueueCreateSet()

创建队列 / 信号量

将队列 / 信号量加入队列集:xQueueAddToSet()

发送数据 / 释放信号量

从队列集中读取句柄:xQueueSelectFromSet()

3.2、队列集核心API函数

3.2.1、创建队列集 xQueueCreateSet

cpp 复制代码
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength);

uxEventQueueLength:队列集的长度,等于要加入的队列 / 信号量的总数。

返回值:队列集句柄,失败返回 NULL。

3.2.2、把队列加入队列集 xQueueAddToSet

cpp 复制代码
BaseType_t xQueueAddToSet(
    QueueSetMemberHandle_t xQueueOrSemaphore,
    QueueSetHandle_t xQueueSet
);

xQueueOrSemaphore:要加入的队列或信号量句柄

xQueueSet:队列集句柄

返回值:pdTRUE 成功,pdFALSE 失败

3.2.3、读取队列集 xQueueSelectFromSet

cpp 复制代码
QueueSetMemberHandle_t xQueueSelectFromSet(
    QueueSetHandle_t xQueueSet,
    TickType_t const xTicksToWait
);

xQueueSet:队列集句柄

xTicksToWait:阻塞超时时间

返回值:有数据的队列 / 信号量句柄,失败返回 NULL

3.3、示例

cpp 复制代码
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#define QUEUE1_LENGTH 5
#define QUEUE2_LENGTH 5
#define ITEM_SIZE sizeof(uint32_t)

QueueHandle_t g_Queue1, g_Queue2;
QueueSetHandle_t g_QueueSet;

// 生产者任务1:往Queue1发数据
void vProducer1(void *pvParameters)
{
    uint32_t count = 0;
    while(1)
    {
        count++;
        xQueueSend(g_Queue1, &count, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

// 生产者任务2:往Queue2发数据
void vProducer2(void *pvParameters)
{
    uint32_t count = 100;
    while(1)
    {
        count++;
        xQueueSend(g_Queue2, &count, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(700));
    }
}

// 消费者任务:监听队列集,处理两个队列的数据
void vConsumer(void *pvParameters)
{
    QueueSetMemberHandle_t xReceivedQueue;
    uint32_t data;

    while(1)
    {
        // 阻塞等待队列集里有事件
        xReceivedQueue = xQueueSelectFromSet(g_QueueSet, portMAX_DELAY);

        if (xReceivedQueue == g_Queue1)
        {
            xQueueReceive(g_Queue1, &data, 0);
            // 处理Queue1的数据
        }
        else if (xReceivedQueue == g_Queue2)
        {
            xQueueReceive(g_Queue2, &data, 0);
            // 处理Queue2的数据
        }
    }
}

void app_main()
{
    // 1. 创建两个队列
    g_Queue1 = xQueueCreate(QUEUE1_LENGTH, ITEM_SIZE);
    g_Queue2 = xQueueCreate(QUEUE2_LENGTH, ITEM_SIZE);

    // 2. 创建队列集,长度为2(要监听2个队列)
    g_QueueSet = xQueueCreateSet(2);

    // 3. 将队列加入队列集
    xQueueAddToSet(g_Queue1, g_QueueSet);
    xQueueAddToSet(g_Queue2, g_QueueSet);

    // 4. 创建任务
    xTaskCreate(vProducer1, "Producer1", 1024, NULL, 2, NULL);
    xTaskCreate(vProducer2, "Producer2", 1024, NULL, 2, NULL);
    xTaskCreate(vConsumer, "Consumer", 1024, NULL, 3, NULL);

    vTaskStartScheduler();
}

总结

本期博客主要对FreeRTOS中的队列进行了详解。

相关推荐
2023自学中2 小时前
Linux 多线程 + 信号,统一屏蔽哪些信号?什么是异步信号,同步信号?
linux·嵌入式
Hello_Embed2 小时前
libmodbus 源码分析
笔记·stm32·单片机·嵌入式·ai编程
FreakStudio18 小时前
WIZnet-EVB-Pico2开始,用MicroPython玩转以太网开发
python·单片机·嵌入式·大学生·面向对象·技术栈·并行计算·电子diy·电子计算机
2601_958352901 天前
手撕环境噪音:双麦降噪模块AN-93上板实测,降噪36dB是真是假?
人工智能·音视频·嵌入式·降噪
行走的bug...1 天前
mathcad安装
嵌入式
iCxhust1 天前
8086/8088单板机VSCode集中环境开发编译(第二版整理)
ide·vscode·嵌入式硬件·编辑器·嵌入式·微机原理·8086最小系统
飞凌嵌入式1 天前
飞凌嵌入式率先推出RK3572核心板 | 新一代八核AIoT平台,新品强势来袭!
科技·嵌入式硬件·嵌入式
温中志2 天前
esp_event_loop_create_default详细解释
esp32·freertos
DashVector2 天前
Zvec v0.4.0 正式发布
数据库·嵌入式·ai编程