FreeRTOS第11章 队列(Queue)学习大纲

FreeRTOS 队列(Queue)学习大纲:从原理到实战

关联文章

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

freertos学习笔记7--个人自用-第11章 队列(queue) 2 中断队列

第一部分:核心概念(为什么要用它?)

目标:理解队列与全局变量的区别,明白“阻塞”的意义。

  1. 什么是队列?

    • 形象比喻:传送带、管道、排队做核酸。

    • FIFO 机制:先进先出(First In First Out),保证数据顺序。

    • 环形缓冲区:底层实现原理(首尾相接的数组)。

  2. 核心特性(三大金刚)

    • 值拷贝 (Copy by Value)

      • “发传真”原理:发送的是数据的副本,原数据销毁不影响队列。

      • 优势:解耦,局部变量也能发。

    • 阻塞机制 (Blocking)

      • 读阻塞:队列空了就“睡觉”(让出 CPU),不空转。

      • 写阻塞:队列满了就“等待”,直到有空位。

    • 线程安全:自带临界区保护,不用担心多任务同时访问冲突。


第二部分:基础 API 使用(三板斧)

目标:掌握最基本的“创建、发、收”三步走。

  1. 创建队列

    • 函数:xQueueCreate(长度, 大小)

    • 参数详解:

      • uxQueueLength:能存几个数据?

      • uxItemSize:每个数据多大?(sizeof(type))

    • 实战注意:必须判断返回值是否为 NULL

  2. 发送数据(入队)

    • 函数:xQueueSend(句柄, &数据, 超时时间)

    • 关键点:第二个参数必须传数据的地址

    • 超时设置:0(不等待)、portMAX_DELAY(死等)。

  3. 接收数据(出队)

    • 函数:xQueueReceive(句柄, &缓存, 超时时间)

    • 关键点:读取后数据会从队列中删除。

    • 对比xQueuePeek(偷看一眼,不删除数据)。


第三部分:进阶数据处理(怎么传数据?)

目标:解决“一次只能传一个 int”的误区,学会传输复杂数据。

  1. 传输结构体(推荐)

    • 场景:平衡小车传感器数据(角度、角速度、时间戳)。

    • 方法:定义 struct,创建队列时 sizeof(struct)

    • 优势:保证数据包的原子性,一次发送所有相关参数。

  2. 传输指针(大数据/零拷贝)

    • 场景:传输长字符串、摄像头图像数据。

    • 方法:队列存 char*,发送内存地址。

    • 风险提示:注意内存的生命周期(Malloc/Free),严禁传输局部变量地址。

  3. 特殊模式

    • 覆盖写入 (xQueueOverwrite)

      • 场景:OLED 显示电池电压(只看最新的,旧的丢弃)。

      • 限制:仅限长度为 1 的队列。

    • 插队发送 (xQueueSendToFront)

      • 场景:紧急刹车指令、故障报警。


第四部分:中断安全(千万别死机)

目标:掌握 ISR 中的专用函数,防止 HardFault。

  1. 黄金法则

    • 中断里绝不能调用普通 API(因为不能阻塞)。

    • 必须使用带 FromISR 后缀的函数。

  2. 标准模板(背诵全文)

    • 步骤一:定义 BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    • 步骤二:调用 xQueueSendFromISR(..., &xHigherPriorityTaskWoken);

    • 步骤三:调用 portYIELD_FROM_ISR(xHigherPriorityTaskWoken);(强制切换上下文)。

  3. 实战场景

    • 串口接收中断:收一个字节 -> 入队 -> 唤醒解析任务。

    • 定时器中断:读取 MPU6050 -> 结构体入队 -> 唤醒 PID 任务。


第五部分:内存分配(了解即可)

目标:知道动态与静态的区别。

  1. 动态创建 (xQueueCreate)

    • 简单,省事,由 FreeRTOS 自动管理堆内存(Heap)。

    • 推荐初学者使用

  2. 静态创建 (xQueueCreateStatic)

    • 复杂,需要用户自己定义数组和结构体。

    • 优势:确定性高,适合对安全要求极高的系统。


第六部分:实战架构应用(平衡小车为例)

目标:将知识转化为项目能力。

  1. 解耦与缓冲

    • 中断负责极速采集(MPU6050)。

    • 队列负责暂存数据。

    • 任务负责复杂运算(PID)。

    • 效果:中断不卡顿,数据不丢失。

  2. 多路输入汇集

    • 输入源:蓝牙 APP、红外遥控、按键。

    • 方法:定义统一的 Command_t 结构体。

    • 处理:一个主控任务统一处理队列中的指令。


第七部分:避坑指南(新手必看)

  1. 指针陷阱:不要在队列里传局部变量的地址!

  2. 中断陷阱:中断里别用 xQueueSend,一定要用 FromISR

  3. 接收陷阱xQueueReceive 的第二个参数是缓存变量的地址 (&var),不是变量本身。

  4. 初始化陷阱:定义了句柄 QueueHandle_t q 之后,一定要先 Create 再使用,否则死机。

队列函数

功能CMSIS-RTOS v2 (CubeMX推荐)FreeRTOS 原生 (通用教程)
队列句柄类型osMessageQueueId_tQueueHandle_t
创建队列osMessageQueueNew(...)xQueueCreate(...)
发送数据osMessageQueuePut(...)xQueueSend(...)
接收数据osMessageQueueGet(...)xQueueReceive(...)
延时osDelay(...)vTaskDelay(...)
任务创建osThreadNew(...)xTaskCreate(...)

CMSIS-RTOS v2 标准中关于 消息队列 (Message Queue) 的三个最核心函数的原型。

这些定义都包含在头文件 cmsis_os2.h 中。


1. 创建队列:osMessageQueueNew

这是队列的“出生证明”,通常在初始化阶段调用。

C

osMessageQueueId_t osMessageQueueNew (
    uint32_t msg_count,                // 参数1: 队列深度 (能排多少人)
    uint32_t msg_size,                 // 参数2: 单个数据大小 (每个人占多大地)
    const osMessageQueueAttr_t *attr   // 参数3: 属性配置 (名字/内存地址)
);
  • 返回值 (osMessageQueueId_t):

    • 成功:返回一个非空的 ID(句柄),你是靠这个 ID 来操作这个队列的。

    • 失败:返回 NULL(通常是因为堆内存不够了)。

  • 参数详解:

    1. msg_count: 队列能存多少个项目。例如 16 表示最多存16个按键值。

    2. msg_size: 每个项目占用的字节数。

      • 如果你存 uint8_t,填 sizeof(uint8_t)

      • 如果你存结构体 struct MyData,填 sizeof(struct MyData)

    3. attr: 也就是我们刚才讨论的“名片”。

      • 如果不需要起名字,也不需要指定静态内存,直接填 NULL 即可。


2. 发送数据:osMessageQueuePut

这是“生产者”(如 Key_Task)使用的函数。

C

osStatus_t osMessageQueuePut (
    osMessageQueueId_t mq_id,  // 参数1: 队列句柄
    const void *msg_ptr,       // 参数2: 要发送的数据的指针
    uint8_t msg_prio,          // 参数3: 消息优先级 (0=正常排队)
    uint32_t timeout           // 参数4: 超时等待时间
);
  • 返回值 (osStatus_t):

    • osOK: 发送成功。

    • osErrorResource: 失败,队列满了(且超时时间到了也没空出位置)。

    • osErrorParameter: 参数错了(比如句柄是空的)。

  • 参数详解:

    1. mq_id: 你创建时得到的那个 ID。

    2. msg_ptr: 注意!这是指针!

      • 即便你发的是一个字节 uint8_t key = 1;,你也必须传地址 &key

      • 函数内部会根据你创建时设定的 msg_size,把这个地址里的数据复制到队列里去。

    3. msg_prio:

      • 0: 正常排队 (FIFO),排在队尾。

      • 非0: 插队 (LIFO),直接插到队头。

    4. timeout: 如果队列满了怎么办?

      • 0: 立刻放弃,返回错误(常用)。

      • osWaitForever: 死等,直到队列有空位才返回。

      • 100: 等 100个 Tick,还没有空位就放弃。


3. 接收数据:osMessageQueueGet

这是“消费者”(如 GUI_Task)使用的函数。

C

osStatus_t osMessageQueueGet (
    osMessageQueueId_t mq_id,  // 参数1: 队列句柄
    void *msg_ptr,             // 参数2: 接收缓存区的指针
    uint8_t *msg_prio,         // 参数3: [输出] 获取到的消息优先级
    uint32_t timeout           // 参数4: 超时等待时间
);
  • 返回值 (osStatus_t):

    • osOK: 成功拿到了数据。

    • osErrorResource: 失败,队列是空的(且超时时间到了也没数据)。

    • osErrorTimeout: 超时了。

  • 参数详解:

    1. mq_id: 队列 ID。

    2. msg_ptr: 你得准备一个变量来接数据,这里填那个变量的地址。

      • 比如 uint8_t recv_data;,这里填 &recv_data

      • 函数会把队列里的数据复制到这个地址里。

    3. msg_prio (容易晕):

      • 这是一个输出参数。如果你想知道取出来的这条消息优先级是多少,就传一个变量地址进去。

      • 绝大多数情况我们不关心,所以直接填 NULL 即可。

    4. timeout: 如果队列空了怎么办?

      • 0: 看一眼没数据就走(适合 UI 刷新)。

      • osWaitForever: 睡觉死等,直到有数据才醒过来(适合低功耗后台任务)。

      • 100: 等 100个 Tick。


总结速查表

函数核心作用第2个参数(指针)第3个参数第4个参数
New办卡单个数据的大小 (Size)属性结构体 (或NULL)-
Put存钱要存的数据的地址 (&val)0 (正常) / !0 (插队)满了等多久 (0)
Get取钱存取款数据的地址 (&val)NULL (不关心优先级)空了等多久 (0/Forever)

记住这张表,99% 的队列操作都能搞定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值