FreeRTOS 队列(Queue)学习大纲:从原理到实战
关联文章
freertos学习笔记6--个人自用-第11章 队列(queue) 1
freertos学习笔记7--个人自用-第11章 队列(queue) 2 中断队列
第一部分:核心概念(为什么要用它?)
目标:理解队列与全局变量的区别,明白“阻塞”的意义。
-
什么是队列?
-
形象比喻:传送带、管道、排队做核酸。
-
FIFO 机制:先进先出(First In First Out),保证数据顺序。
-
环形缓冲区:底层实现原理(首尾相接的数组)。
-
-
核心特性(三大金刚)
-
值拷贝 (Copy by Value):
-
“发传真”原理:发送的是数据的副本,原数据销毁不影响队列。
-
优势:解耦,局部变量也能发。
-
-
阻塞机制 (Blocking):
-
读阻塞:队列空了就“睡觉”(让出 CPU),不空转。
-
写阻塞:队列满了就“等待”,直到有空位。
-
-
线程安全:自带临界区保护,不用担心多任务同时访问冲突。
-
第二部分:基础 API 使用(三板斧)
目标:掌握最基本的“创建、发、收”三步走。
-
创建队列
-
函数:
xQueueCreate(长度, 大小) -
参数详解:
-
uxQueueLength:能存几个数据? -
uxItemSize:每个数据多大?(sizeof(type))
-
-
实战注意:必须判断返回值是否为
NULL。
-
-
发送数据(入队)
-
函数:
xQueueSend(句柄, &数据, 超时时间) -
关键点:第二个参数必须传数据的地址。
-
超时设置:
0(不等待)、portMAX_DELAY(死等)。
-
-
接收数据(出队)
-
函数:
xQueueReceive(句柄, &缓存, 超时时间) -
关键点:读取后数据会从队列中删除。
-
对比:
xQueuePeek(偷看一眼,不删除数据)。
-
第三部分:进阶数据处理(怎么传数据?)
目标:解决“一次只能传一个 int”的误区,学会传输复杂数据。
-
传输结构体(推荐)
-
场景:平衡小车传感器数据(角度、角速度、时间戳)。
-
方法:定义
struct,创建队列时sizeof(struct)。 -
优势:保证数据包的原子性,一次发送所有相关参数。
-
-
传输指针(大数据/零拷贝)
-
场景:传输长字符串、摄像头图像数据。
-
方法:队列存
char*,发送内存地址。 -
风险提示:注意内存的生命周期(Malloc/Free),严禁传输局部变量地址。
-
-
特殊模式
-
覆盖写入 (
xQueueOverwrite):-
场景:OLED 显示电池电压(只看最新的,旧的丢弃)。
-
限制:仅限长度为 1 的队列。
-
-
插队发送 (
xQueueSendToFront):-
场景:紧急刹车指令、故障报警。
-
-
第四部分:中断安全(千万别死机)
目标:掌握 ISR 中的专用函数,防止 HardFault。
-
黄金法则
-
中断里绝不能调用普通 API(因为不能阻塞)。
-
必须使用带
FromISR后缀的函数。
-
-
标准模板(背诵全文)
-
步骤一:定义
BaseType_t xHigherPriorityTaskWoken = pdFALSE; -
步骤二:调用
xQueueSendFromISR(..., &xHigherPriorityTaskWoken); -
步骤三:调用
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);(强制切换上下文)。
-
-
实战场景
-
串口接收中断:收一个字节 -> 入队 -> 唤醒解析任务。
-
定时器中断:读取 MPU6050 -> 结构体入队 -> 唤醒 PID 任务。
-
第五部分:内存分配(了解即可)
目标:知道动态与静态的区别。
-
动态创建 (
xQueueCreate)-
简单,省事,由 FreeRTOS 自动管理堆内存(Heap)。
-
推荐初学者使用。
-
-
静态创建 (
xQueueCreateStatic)-
复杂,需要用户自己定义数组和结构体。
-
优势:确定性高,适合对安全要求极高的系统。
-
第六部分:实战架构应用(平衡小车为例)
目标:将知识转化为项目能力。
-
解耦与缓冲
-
中断负责极速采集(MPU6050)。
-
队列负责暂存数据。
-
任务负责复杂运算(PID)。
-
效果:中断不卡顿,数据不丢失。
-
-
多路输入汇集
-
输入源:蓝牙 APP、红外遥控、按键。
-
方法:定义统一的
Command_t结构体。 -
处理:一个主控任务统一处理队列中的指令。
-
第七部分:避坑指南(新手必看)
-
指针陷阱:不要在队列里传局部变量的地址!
-
中断陷阱:中断里别用
xQueueSend,一定要用FromISR。 -
接收陷阱:
xQueueReceive的第二个参数是缓存变量的地址 (&var),不是变量本身。 -
初始化陷阱:定义了句柄
QueueHandle_t q之后,一定要先Create再使用,否则死机。
队列函数
| 功能 | CMSIS-RTOS v2 (CubeMX推荐) | FreeRTOS 原生 (通用教程) |
| 队列句柄类型 | osMessageQueueId_t | QueueHandle_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(通常是因为堆内存不够了)。
-
-
参数详解:
-
msg_count: 队列能存多少个项目。例如16表示最多存16个按键值。 -
msg_size: 每个项目占用的字节数。-
如果你存
uint8_t,填sizeof(uint8_t)。 -
如果你存结构体
struct MyData,填sizeof(struct MyData)。
-
-
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: 参数错了(比如句柄是空的)。
-
-
参数详解:
-
mq_id: 你创建时得到的那个 ID。 -
msg_ptr: 注意!这是指针!-
即便你发的是一个字节
uint8_t key = 1;,你也必须传地址&key。 -
函数内部会根据你创建时设定的
msg_size,把这个地址里的数据复制到队列里去。
-
-
msg_prio:-
填
0: 正常排队 (FIFO),排在队尾。 -
填
非0: 插队 (LIFO),直接插到队头。
-
-
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: 超时了。
-
-
参数详解:
-
mq_id: 队列 ID。 -
msg_ptr: 你得准备一个变量来接数据,这里填那个变量的地址。-
比如
uint8_t recv_data;,这里填&recv_data。 -
函数会把队列里的数据复制到这个地址里。
-
-
msg_prio(容易晕):-
这是一个输出参数。如果你想知道取出来的这条消息优先级是多少,就传一个变量地址进去。
-
绝大多数情况我们不关心,所以直接填
NULL即可。
-
-
timeout: 如果队列空了怎么办?-
0: 看一眼没数据就走(适合 UI 刷新)。 -
osWaitForever: 睡觉死等,直到有数据才醒过来(适合低功耗后台任务)。 -
100: 等 100个 Tick。
-
-
总结速查表
| 函数 | 核心作用 | 第2个参数(指针) | 第3个参数 | 第4个参数 |
| New | 办卡 | 单个数据的大小 (Size) | 属性结构体 (或NULL) | - |
| Put | 存钱 | 要存的数据的地址 (&val) | 0 (正常) / !0 (插队) | 满了等多久 (0) |
| Get | 取钱 | 存取款数据的地址 (&val) | NULL (不关心优先级) | 空了等多久 (0/Forever) |
记住这张表,99% 的队列操作都能搞定
869

被折叠的 条评论
为什么被折叠?



