freertos学习笔记6--个人自用-第11章 队列(queue) 1

关联文章

 FreeRTOS第11章 队列(Queue)学习大纲https://blog.youkuaiyun.com/SeventeenChen/article/details/155699651?sharetype=blogdetail&sharerId=155699651&sharerefer=PC&sharesource=SeventeenChen&spm=1011.2480.3001.8118


11.1 队列的特性

FreeRTOS 队列的核心特性设计非常巧妙,它是为了解决嵌入式系统中两个最头疼的问题:“数据怎么传才安全”“任务怎么等才省电”

11.1.2FIFO (先进先出)

  • 队列通常遵循“先入队的数据先被读出”的原则。

  • 也可以强制写队列头部:覆盖头部数据

总结对比

11.1.3值拷贝 VS存地址 

内存管理机制:值拷贝 (Copy by Value)   VS存地址 (Copy by Reference/Pointer)

1. 通俗比喻:传真 vs. 保险柜钥匙

2. 内存层面的深度解析

3. 如何在你的 STM32 项目中选择?

问题注意

11.1.3 队列的阻塞访问

阻塞机制简单一句话总结:阻塞就是“让出 CPU,去睡觉,直到闹钟响或事情发生”。

所谓“阻塞访问”,核心就是:“如果条件不满足,我就睡觉(进入 Blocked 状态),等条件满足了或者超时了再叫醒我。”

这分为两种情况:出队阻塞(读)和 入队阻塞(写)。

1. 出队阻塞 (读取时队列空了)

2. 入队阻塞 (写入时队列满了)

3. 阻塞时间的设置 (关键参数)

11.2 队列函数

使用队列的流程:创建队列、写队列、读队列、删除队列

知识点补充:

简单来说,UBaseType_t 是 FreeRTOS 定义的一个**“变色龙”数据类型**。

它的全称是 Unsigned Base Type(无符号基础类型)。

它的核心含义是:“当前 CPU 处理起来最快、最顺手的那种无符号整数。”

1. 创建队列 (Create)

函数原型:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

使用示例:

// 定义句柄变量
QueueHandle_t xMyQueue;

// 创建一个能存 10 个整数的队列
xMyQueue = xQueueCreate(10, sizeof(int));

// 必须判断是否创建成功!
if (xMyQueue == NULL) {
    // 内存不足,错误处理
}

2. 发送数据 写队列 (Write / Send)

xQueueSend:向队列发送数据

函数原型:

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

使用示例:

int send_data = 100;
// 注意取地址符 &
BaseType_t result = xQueueSend(xMyQueue, &send_data, 10);
if(result != pdPASS) {
    printf("发送失败,队列满了");
}

3. 接收数据 读队列 (Read / Receive)

xQueueReceive:从队列接收数据

函数原型:

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

使用示例:

int rev_data;
// 死等数据
if (xQueueReceive(xMyQueue, &rev_data, portMAX_DELAY) == pdPASS) {
    printf("收到了数据: %d", rev_data);
}

4. 删除队列 (Delete)

函数原型:

void vQueueDelete( QueueHandle_t xQueue );

5.总结对照表

6. 复位 清空队列(Reset)

BaseType_t xQueueReset( QueueHandle_t xQueue );

7.查询状态

可以查询队列中有多少个数据、有多少空余空间。

有时候我们不想阻塞(不想睡觉),只想“看一眼”队列的情况再做决定。FreeRTOS 提供了两套查询函数:

A. 查“有多少数据” (Used)

UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );

B. 查“还有多少空位” (Free)

UBaseType_t uxQueueSpacesAvailable( QueueHandle_t xQueue );

8.覆盖写入:xQueueOverwrite()

9.问题

第一个

第二个

示范

第三个(有更优解,仅了解,可不看)

问题

对于“超级大”数据:

  1. 创建队列:大小设为指针大小 (sizeof(char *))。

  2. 发送方Malloc 内存 -> 填数据 -> 发送内存地址。

  3. 接收方:接收内存地址 -> 读数据 -> Free 内存。

(仅了解,不用看)

#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"
#include <string.h>
#include <stdio.h>

// 假设这是一个很大的数据包大小
#define BIG_DATA_SIZE 1000

// 声明队列句柄
QueueHandle_t xPointerQueue;

// --- 任务 A:生产者 (申请内存 -> 填数据 -> 发送指针) ---
void vSenderTask(void *pvParameters) {
    char *pDataBuffer; // 这是一个指针变量
    int counter = 0;

    for (;;) {
        // 1. 【动态申请】在堆上申请 1000 字节的空间
        // 必须使用 pvPortMalloc,不能用局部数组!
        pDataBuffer = (char *)pvPortMalloc(BIG_DATA_SIZE);

        if (pDataBuffer != NULL) {
            // 2. 【填入数据】往这块内存里写东西
            // 模拟写入一个长字符串
            sprintf(pDataBuffer, "这是第 %d 次发送的超级大数据...[省略900字]...", counter++);

            // 3. 【发送指针】
            // 注意:
            // pDataBuffer 存放的是那块大内存的地址 (比如 0x20001000)
            // 我们要把这个 0x20001000 发送到队列里
            // 所以这里填 &pDataBuffer
            xQueueSend(xPointerQueue, &pDataBuffer, portMAX_DELAY);
            
            // 注意:发送完后,Sender 任务就不要再去动这块内存了!
            // 它的所有权已经移交给接收方了。
        }

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// --- 任务 B:消费者 (接收指针 -> 读数据 -> 释放内存) ---
void vReceiverTask(void *pvParameters) {
    char *pReceivedPtr; // 准备一个指针变量来接数据

    for (;;) {
        // 4. 【接收指针】
        // 队列里弹出的只是一个地址 (4字节)
        if (xQueueReceive(xPointerQueue, &pReceivedPtr, portMAX_DELAY) == pdPASS) {
            
            // 5. 【处理数据】直接通过指针访问那块大内存
            printf("接收到的数据内容: %s\r\n", pReceivedPtr);

            // 6. 【释放内存】非常重要!!
            // 处理完了,必须把这块堆内存释放掉,否则内存几秒钟就爆了
            vPortFree(pReceivedPtr);
        }
    }
}

// --- 初始化 ---
int main(void) {
    // 硬件初始化...

    // 7. 【创建队列】
    // 关键点:每个单元的大小是 sizeof(char *),即 4 字节
    // 深度为 5,表示最多能存 5 个指针
    xPointerQueue = xQueueCreate(5, sizeof(char *));

    if (xPointerQueue != NULL) {
        xTaskCreate(vSenderTask, "Sender", 256, NULL, 1, NULL);
        xTaskCreate(vReceiverTask, "Receiver", 256, NULL, 1, NULL);
        vTaskStartScheduler();
    }

    while (1);
}

11.5 队列集

FreeRTOS 队列集合(Queue Sets)是一种机制,用于同时监听多个队列和信号量,以实现任务间的高效通信。

它的核心作用只有一句话:让一个任务能够同时“死等”多个队列(或信号量)。

之前我们讲的“多对一(结构体封装)”虽然好用,但它要求所有数据必须能塞进同一个队列里。但如果你的数据源一个是 Queue A(存结构体),另一个是 Semaphore B(二值信号量),这就没法合并了。

这时候,就需要请出 队列集

11.5.1 创建队列集

1. 为什么要用队列集?(解决什么痛点)

2. 使用步骤 (API 详解)

0.知识点复习

使用队列集通常分为 4 个步骤

1. 创建队列集:xQueueCreateSet()

函数原型:

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );

2. 添加成员:xQueueAddToSet()

函数原型:

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, 
                           QueueSetHandle_t xQueueSet );

知识点补充:

SemaphoreHandle_t 就是 FreeRTOS 中用来代表一个信号量(Semaphore)或互斥量(Mutex)的“身份证”或“遥控器”

在 C 语言的层面,它就是一个数据类型(类似于 intchar),但它是专门用来存储信号量句柄的。

3. 等待/选择消息:xQueueSelectFromSet (最核心)

函数原型:

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, 
                                            const TickType_t xTicksToWait );

4. 移除成员:xQueueRemoveFromSet

函数原型:

BaseType_t xQueueRemoveFromSet( QueueSetMemberHandle_t xQueueOrSemaphore, 
                                QueueSetHandle_t xQueueSet );

总结:完整逻辑流

把这四个函数串起来,一个完整的 setuploop 应该是这样的

/* --- 1. 全局定义 --- */
QueueHandle_t q_handle;
SemaphoreHandle_t s_handle;
QueueSetHandle_t set_handle;

/* --- 2. 初始化阶段 --- */
void System_Init() {
    // A. 创建成员
    q_handle = xQueueCreate(5, sizeof(int));
    s_handle = xSemaphoreCreateBinary();

    // B. 创建集合 (大小 = 5 + 1 = 6)
    set_handle = xQueueCreateSet(6);

    // C. 加入集合 (必须在发数据之前!)
    xQueueAddToSet(q_handle, set_handle);
    xQueueAddToSet(s_handle, set_handle);
}

/* --- 3. 任务循环阶段 --- */
void Task_Process() {
    QueueSetMemberHandle_t active_member;
    int val;

    while(1) {
        // D. 死等
        active_member = xQueueSelectFromSet(set_handle, portMAX_DELAY);

        // E. 区分处理 (读取时 WaitTime = 0)
        if (active_member == q_handle) {
            xQueueReceive(q_handle, &val, 0);
            // 处理 val...
        } 
        else if (active_member == s_handle) {
            xSemaphoreTake(s_handle, 0);
            // 处理信号...
        }
    }
}

11.6总结大纲

在实际工程中,队列主要扮演四大角色:解耦缓冲、串行化处理、资源守护、状态信箱

但对于初学者

检验

11.9坐标

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值