tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第10、14章 + 配套代码09/14" author: "尚硅谷研究院" date: 2026-07-12 url: ""
用生活理解:队列就像公司内部的"传话器"——task1 把消息放进队列,task2 从队列取出消息。就像两个人用一个信箱通信:一个人往里塞信,一个人取信看。队列集就像你把多个信箱挂在一起,派一个人守着,哪个信箱有信来了他就取。
队列 = 先进先出(FIFO)的消息缓冲区,线程安全。
QueueHandle_t queue1;
// 参数1:队列长度(能存几个消息)
// 参数2:每个消息的大小(单位字节)
queue1 = xQueueCreate(2, sizeof(uint8_t));
// 创建一个能存2个消息的队列,每个消息1字节
// 就像做了一个只能放2封信的信箱,每封信只能写1个字
uint8_t key = KEY1_PRESS;
xQueueSend(queue1, &key, portMAX_DELAY);
// 第3个参数 portMAX_DELAY = "队列满了就死等"
// 也可以设 0 = "满了就放弃" 或者 100 = "等100个tick"
阻塞 / 非阻塞:就像排队吃饭——队列满了你可以等(设超时),也可以直接走人(超时=0 不等待)。接收同理:队列空了可以等数据来,也可以直接放弃。
uint8_t receive;
xQueueReceive(queue1, &receive, portMAX_DELAY);
// 同样 portMAX_DELAY = "队列空了就死等"
Receive vs Peek:
xQueueReceive()= 剪切(从队列中删除数据),xQueuePeek()= 复制(数据保留在队列中,只看不取)。就像看信箱——Receive 是取走信,Peek 是只看看不拿。Send to front:
xQueueSendToFront()= 插队(把消息放到队首),xQueueSendToBack()= 正常排队(默认)。
// ======== 小数据:直接传值(比如按键值) ========
uint8_t key = KEY1_PRESS;
xQueueSend(queue1, &key, portMAX_DELAY);
// 把key的值复制一份放进队列,原始key怎么变都不影响
// ======== 大数据:传地址(比如字符串) ========
char *big_data = "这是一段很长的数据...";
xQueueSend(big_queue, &big_data, portMAX_DELAY);
// 不复制整个字符串,只复制地址(4字节),省内存!
// 接收方通过地址直接访问字符串
为什么要传地址? 如果数据有 1KB,每次复制一份就太浪费内存了。传地址就像把文件路径告诉对方,而不是把整个文件复印一份送过去。
#include "queue.h" // 队列相关API的头文件
QueueHandle_t queue1; // 小数据队列
QueueHandle_t big_queue; // 大数据队列
void freertos_start(void)
{
// 创建小数据队列:长度2,每项1字节
queue1 = xQueueCreate(2, sizeof(uint8_t));
// 创建大数据队列:长度1,每项4字节(指针大小)
big_queue = xQueueCreate(1, sizeof(char *));
xTaskCreate(/* ...start_task... */);
vTaskStartScheduler();
}
/* ========== task1:发送消息 ========== */
void task1(void *pvParameters)
{
char *big_data = "这里是需要传递的大数据内容...";
uint8_t key = 0;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS || key == KEY2_PRESS)
{
// 发送键值到小数据队列
xQueueSend(queue1, &key, portMAX_DELAY);
}
else if (key == KEY3_PRESS)
{
// 发送大数据地址
xQueueSend(big_queue, &big_data, portMAX_DELAY);
}
vTaskDelay(500);
}
}
/* ========== task2:接收小数据 ========== */
void task2(void *pvParameters)
{
uint8_t receive;
while (1)
{
xQueueReceive(queue1, &receive, portMAX_DELAY);
printf("收到键值: %d\r\n", receive);
}
}
/* ========== task3:接收大数据 ========== */
void task3(void *pvParameters)
{
char *receive_buff;
while (1)
{
xQueueReceive(big_queue, &receive_buff, portMAX_DELAY);
printf("收到大数据: %s\r\n", receive_buff);
}
}
假设你管理两个信箱(队列)和一个通知铃(信号量)。不用队列集时,你必须轮询检查每个信箱——"1号有信吗?2号有信吗?铃响了吗?"很麻烦。
痛点导向:当需要从多个队列读取数据时,如果先阻塞在队列 A 的读取上,但队列 B 先到了数据,就会卡住——永远等不到 B 的数据。队列集让任务一次性阻塞在所有队列上,哪个先到就处理哪个。
队列集 = 统一管理的"消息中心"。你把队列和信号量都注册进去,然后只需要等一个函数返回,它就告诉你"是哪个有消息了"。
// ======== 创建队列集并注册 ========
QueueSetHandle_t queue_set_handle;
// 创建一个能容纳2个成员的队列集
queue_set_handle = xQueueCreateSet(2);
// 把队列和信号量注册到队列集
xQueueAddToSet(queue1, queue_set_handle); // 注册队列
xQueueAddToSet(sem_handle, queue_set_handle); // 注册信号量
// ======== 接收方:哪个有消息就处理哪个 ========
void task2(void *pvParameters)
{
QueueSetMemberHandle_t member_handle;
while (1)
{
// 阻塞等待:队列集中任何一个成员有消息
member_handle = xQueueSelectFromSet(
queue_set_handle, portMAX_DELAY);
if (member_handle == queue1)
{
// 是队列有消息了
uint8_t val;
xQueueReceive(queue1, &val, 0);
printf("队列收到: %d\r\n", val);
}
else if (member_handle == sem_handle)
{
// 是信号量被释放了
xSemaphoreTake(sem_handle, 0);
printf("信号量收到!\r\n");
}
}
}
code/09_消息队列/code/14_队列集/后续章节:[[08-信号量与互斥信号量]]