--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第10章 消息队列" author: "尚硅谷研究院" date: 2026-07-17 created: 2026-07-17 --- # 消息队列 > **用生活理解**:消息队列就像快递柜。寄件人(发送任务)把包裹放进柜子,收件人(接收任务)从柜子里取走。柜子有固定数量的格子(队列长度),格子满了寄件人就得排队等空位,格子空了收件人就得等新包裹。每个格子只放固定尺寸的物品(数据拷贝),而不是放一把钥匙让你自己去取(传指针)。 ## 概念介绍 ### 基本概念 队列(Queue)是 FreeRTOS 中任务间通信的主要形式,用于在**任务之间**以及**中断和任务之间**发送消息。 - **FIFO 顺序**:先发送的消息会被先接收 - **线程安全**:队列操作是原子的,确保多任务环境中的数据完整性 - **阻塞操作**:队列满时发送方可阻塞等待,队列空时接收方可阻塞等待 - **传值而非传指针**:数据被拷贝到队列中(对于大数据可传指针优化) ### 工作原理 队列内部结构包含: - **环形缓冲区**:存储实际数据 - **发送等待列表**:队列满时,等待发送的任务进入阻塞 - **接收等待列表**:队列空时,等待接收的任务进入阻塞 当队列有空间时,最高优先级的等待发送任务将解除阻塞;当队列有数据时,最高优先级的等待接收任务将解除阻塞。 ### 核心API | 函数 | 参数 | 功能简述 | |----------|--------|-------------| | `xQueueCreate()` | `uxQueueLength`, `uxItemSize` | 动态创建队列 | | `xQueueSend()` | `xQueue`, `pvItemToQueue`, `xTicksToWait` | 尾部写入消息 | | `xQueueSendToBack()` | 同上 | 同 xQueueSend() | | `xQueueSendToFront()` | 同上 | 头部写入消息 | | `xQueueReceive()` | `xQueue`, `pvBuffer`, `xTicksToWait` | 头部读取并删除消息 | | `xQueuePeek()` | 同上 | 头部读取消息(不删除) | | `xQueueOverwrite()` | `xQueue`, `pvItemToQueue` | 覆写(仅队列长度=1时可用) | ## 实验 ### 需求描述 学习使用 FreeRTOS 队列相关函数,包括创建队列、入队和出队操作: - **task1**:按键 KEY1/KEY2 按下时,将键值发送到 `queue1`(小数据队列);按键 KEY3 按下时,将大数据的地址发送到 `big_queue`(指针队列) - **task2**:从 `queue1` 读取消息并打印键值 - **task3**:从 `big_queue` 读取大数据地址,通过地址访问数据 ### 代码实现 **代码来源:**03_代码/09_消息队列/Core/Src/freertos_demo.c ```c #include "queue.h" /* 队列操作头文件 */ #include "FreeRTOS.h" #include "task.h" /* 任务配置 */ #define START_TASK_PRIORITY 1 #define START_TASK_STACK_DEPTH 128 TaskHandle_t start_task_handler; void Start_Task(void *pvParameters); #define TASK1_PRIORITY 2 #define TASK1_STACK_DEPTH 128 TaskHandle_t task1_handler; void Task1(void *pvParameters); #define TASK2_PRIORITY 3 #define TASK2_STACK_DEPTH 128 TaskHandle_t task2_handler; void Task2(void *pvParameters); #define TASK3_PRIORITY 4 #define TASK3_STACK_DEPTH 128 TaskHandle_t task3_handler; void Task3(void *pvParameters); QueueHandle_t queue1; /* 小数据队列句柄 */ QueueHandle_t big_queue; /* 大数据队列句柄 */ char buff[100] = {"大大大fdahjk324hjkhfjksdahjk#$@!@#jfaskdfhjka"}; /* FreeRTOS入口函数 */ void FreeRTOS_Start(void) { /* 创建queue1队列:长度2,每个元素1字节 */ queue1 = xQueueCreate(2, sizeof(uint8_t)); if (queue1 != NULL) { printf("queue1队列创建成功\r\n"); } else { printf("queue1队列创建失败\r\n"); } /* 创建big_queue队列:长度1,每个元素为指针(char*) */ big_queue = xQueueCreate(1, sizeof(char *)); if (big_queue != NULL) { printf("big_queue队列创建成功\r\n"); } else { printf("big_queue队列创建失败\r\n"); } xTaskCreate((TaskFunction_t)Start_Task, (char *)"Start_Task", (configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH, (void *)NULL, (UBaseType_t)START_TASK_PRIORITY, (TaskHandle_t *)&start_task_handler); vTaskStartScheduler(); } /* 启动任务:创建三个工作任务 */ void Start_Task(void *pvParameters) { taskENTER_CRITICAL(); /* 进入临界区 */ xTaskCreate((TaskFunction_t)Task1, (char *)"Task1", (configSTACK_DEPTH_TYPE)TASK1_STACK_DEPTH, (void *)NULL, (UBaseType_t)TASK1_PRIORITY, (TaskHandle_t *)&task1_handler); xTaskCreate((TaskFunction_t)Task2, (char *)"Task2", (configSTACK_DEPTH_TYPE)TASK2_STACK_DEPTH, (void *)NULL, (UBaseType_t)TASK2_PRIORITY, (TaskHandle_t *)&task2_handler); xTaskCreate((TaskFunction_t)Task3, (char *)"Task3", (configSTACK_DEPTH_TYPE)TASK3_STACK_DEPTH, (void *)NULL, (UBaseType_t)TASK3_PRIORITY, (TaskHandle_t *)&task3_handler); vTaskDelete(NULL); /* 删除启动任务自身 */ taskEXIT_CRITICAL(); /* 退出临界区 */ } /* task1:扫描按键,入队 */ void Task1(void *pvParameters) { uint8_t key = 0; char *buf; BaseType_t err = 0; buf = &buff[0]; /* 获取大数据缓冲区地址 */ while (1) { key = Key_Detect(); if (key == KEY1_PRESS || key == KEY2_PRESS) { /* 发送键值到queue1队列,永久等待直到成功 */ err = xQueueSend(queue1, &key, portMAX_DELAY); if (err != pdTRUE) { printf("queue1队列发送失败\r\n"); } } else if (key == KEY3_PRESS) { /* 发送大数据地址到big_queue队列 */ err = xQueueSend(big_queue, &buf, portMAX_DELAY); if (err != pdTRUE) { printf("big_queue队列发送失败\r\n"); } } vTaskDelay(10); } } /* task2:从queue1出队(小数据) */ void Task2(void *pvParameters) { uint8_t key = 0; BaseType_t err = 0; while (1) { /* 从queue1读取键值,永久等待 */ err = xQueueReceive(queue1, &key, portMAX_DELAY); if (err != pdTRUE) { printf("queue1队列读取失败\r\n"); } else { printf("queue1读取队列成功,数据:%d\r\n", key); } } } /* task3:从big_queue出队(大数据) */ void Task3(void *pvParameters) { char *buf; BaseType_t err = 0; while (1) { /* 从big_queue读取指针,永久等待 */ err = xQueueReceive(big_queue, &buf, portMAX_DELAY); if (err != pdTRUE) { printf("big_queue队列读取失败\r\n"); } else { printf("数据:%s\r\n", buf); /* 通过指针访问大数据 */ } } } ``` ### 关键配置 ```c /* FreeRTOSConfig.h */ #define configQUEUE_REGISTRY_SIZE 8 /* 可注册的队列/信号量个数 */ ``` ## 核心函数速查表 | 函数 | 参数 | 返回值 | 功能简述 | |----------|-----------|--------|-------------| | `QueueHandle_t xQueueCreate(uxQueueLength, uxItemSize)` | 队列长度, 每项字节数 | NULL=失败, 非NULL=句柄 | 动态创建队列 | | `BaseType_t xQueueSend(xQueue, pvItemToQueue, xTicksToWait)` | 队列句柄, 数据指针, 超时ticks | pdPASS/errQUEUE_FULL | 尾部写入 | | `BaseType_t xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait)` | 同上 | 同上 | 头部写入 | | `BaseType_t xQueueReceive(xQueue, pvBuffer, xTicksToWait)` | 队列句柄, 接收缓冲区指针, 超时ticks | pdPASS/errQUEUE_EMPTY | 读取并删除 | | `BaseType_t xQueuePeek(xQueue, pvBuffer, xTicksToWait)` | 同上 | 同上 | 读取不删除 | | `BaseType_t xQueueOverwrite(xQueue, pvItemToQueue)` | 队列句柄, 数据指针 | pdPASS | 覆写(长度须为1) | | `UBaseType_t uxQueueMessagesWaiting(xQueue)` | 队列句柄 | 当前消息数 | 查询队列中消息数量 | ## 常见问题与避坑 1. **队列项大小与发送不匹配**:`xQueueCreate` 的 `uxItemSize` 必须与发送数据大小一致。传结构体时特别注意大小。 2. **队列满导致死等**:发送时若 `xTicksToWait = portMAX_DELAY` 且队列始终无空位,任务将永久阻塞。建议设计时确保接收方及时取走数据。 3. **大数据拷贝开销**:队列默认传值(拷贝数据)。大数据建议传指针(如 `xQueueCreate(1, sizeof(char*))`),但需确保指针指向的内存生命周期有效。 4. **中断中发送**:在 ISR 中使用 `xQueueSendFromISR()` 而非 `xQueueSend()`,且末尾调用 `portYIELD_FROM_ISR()` 触发切换。