关联文章
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.问题
第一个

第二个


示范




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

问题


对于“超级大”数据:
-
创建队列:大小设为指针大小 (
sizeof(char *))。 -
发送方:
Malloc内存 -> 填数据 -> 发送内存地址。 -
接收方:接收内存地址 -> 读数据 ->
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 语言的层面,它就是一个数据类型(类似于 int 或 char),但它是专门用来存储信号量句柄的。



3. 等待/选择消息:xQueueSelectFromSet (最核心)
函数原型:
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
const TickType_t xTicksToWait );


4. 移除成员:xQueueRemoveFromSet
函数原型:
BaseType_t xQueueRemoveFromSet( QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet );




总结:完整逻辑流
把这四个函数串起来,一个完整的 setup 和 loop 应该是这样的
/* --- 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总结大纲
在实际工程中,队列主要扮演四大角色:解耦缓冲、串行化处理、资源守护、状态信箱。
但对于初学者


检验

711

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



