10-消息队列.md 8.8 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第10章 消息队列" author: "尚硅谷研究院" date: 2026-07-17

created: 2026-07-17

消息队列

用生活理解:消息队列就像快递柜。寄件人(发送任务)把包裹放进柜子,收件人(接收任务)从柜子里取走。柜子有固定数量的格子(队列长度),格子满了寄件人就得排队等空位,格子空了收件人就得等新包裹。每个格子只放固定尺寸的物品(数据拷贝),而不是放一把钥匙让你自己去取(传指针)。

Concept

Basic concept

队列(Queue)是 FreeRTOS 中任务间通信的主要形式,用于在任务之间以及中断和任务之间发送消息。

  • FIFO 顺序:先发送的消息会被先接收
  • 线程安全:队列操作是原子的,确保多任务环境中的数据完整性
  • 阻塞操作:队列满时发送方可阻塞等待,队列空时接收方可阻塞等待
  • 传值而非传指针:数据被拷贝到队列中(对于大数据可传指针优化)

Working principle

队列内部结构包含:

  • 环形缓冲区:存储实际数据
  • 发送等待列表:队列满时,等待发送的任务进入阻塞
  • 接收等待列表:队列空时,等待接收的任务进入阻塞

当队列有空间时,最高优先级的等待发送任务将解除阻塞;当队列有数据时,最高优先级的等待接收任务将解除阻塞。

Core API

Function Params Description
xQueueCreate() uxQueueLength, uxItemSize 动态创建队列
xQueueSend() xQueue, pvItemToQueue, xTicksToWait 尾部写入消息
xQueueSendToBack() 同上 同 xQueueSend()
xQueueSendToFront() 同上 头部写入消息
xQueueReceive() xQueue, pvBuffer, xTicksToWait 头部读取并删除消息
xQueuePeek() 同上 头部读取消息(不删除)
xQueueOverwrite() xQueue, pvItemToQueue 覆写(仅队列长度=1时可用)

Experiment

Requirements

学习使用 FreeRTOS 队列相关函数,包括创建队列、入队和出队操作:

  • task1:按键 KEY1/KEY2 按下时,将键值发送到 queue1(小数据队列);按键 KEY3 按下时,将大数据的地址发送到 big_queue(指针队列)
  • task2:从 queue1 读取消息并打印键值
  • task3:从 big_queue 读取大数据地址,通过地址访问数据

Code Implementation

File: P09/09_消息队列/Core/Src/freertos_demo.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);    /* 通过指针访问大数据 */
        }
    }
}

Key Config

/* FreeRTOSConfig.h */
#define configQUEUE_REGISTRY_SIZE   8   /* 可注册的队列/信号量个数 */

Core API Reference Table

Function Parameters Return Description
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) 队列句柄 当前消息数 查询队列中消息数量

Common Issues & Pitfalls

  1. 队列项大小与发送不匹配xQueueCreateuxItemSize 必须与发送数据大小一致。传结构体时特别注意大小。
  2. 队列满导致死等:发送时若 xTicksToWait = portMAX_DELAY 且队列始终无空位,任务将永久阻塞。建议设计时确保接收方及时取走数据。
  3. 大数据拷贝开销:队列默认传值(拷贝数据)。大数据建议传指针(如 xQueueCreate(1, sizeof(char*))),但需确保指针指向的内存生命周期有效。
  4. 中断中发送:在 ISR 中使用 xQueueSendFromISR() 而非 xQueueSend(),且末尾调用 portYIELD_FROM_ISR() 触发切换。