跳转至

FreeRTOS队列通信实战:掌握任务间数据传递的核心技术

学习目标

完成本教程后,你将能够:

  • 理解FreeRTOS队列的工作原理和应用场景
  • 掌握队列创建和配置的方法
  • 熟练使用队列发送和接收API
  • 理解队列的阻塞机制和超时处理
  • 掌握队列集的使用方法
  • 学会使用邮箱进行数据传递
  • 能够在实际项目中应用队列通信

前置要求

在开始本教程之前,你需要:

知识要求: - 完成FreeRTOS快速入门和任务管理教程 - 理解RTOS任务调度和状态转换 - 了解消息队列的基本概念 - 熟悉C语言指针和结构体

技能要求: - 能够创建和管理FreeRTOS任务 - 会使用STM32CubeIDE或Keil MDK - 了解如何编译和调试程序

硬件准备: - STM32开发板(如STM32F4 Discovery) - ST-Link调试器 - USB数据线 - LED灯(用于演示,可选)

准备工作

环境配置

本教程基于以下环境:

软件环境: - STM32CubeIDE v1.10+ - FreeRTOS V10.3.1+ - HAL库 v1.27+

硬件环境: - STM32F407VGT6开发板 - 板载LED(用于演示) - 串口调试工具

FreeRTOS配置

确保在FreeRTOSConfig.h中启用队列相关配置:

// 启用队列功能
#define configUSE_QUEUE_SETS                1
#define configQUEUE_REGISTRY_SIZE           10

// 基本配置
#define configUSE_PREEMPTION                1
#define configTICK_RATE_HZ                  1000
#define configMAX_PRIORITIES                5
#define configTOTAL_HEAP_SIZE               15360

创建基础项目

如果你已经完成了前面的教程,可以直接使用之前的项目。否则:

  1. 打开STM32CubeIDE,创建新的STM32项目
  2. 在CubeMX中启用FreeRTOS(CMSIS_V1接口)
  3. 配置系统时钟和串口(用于调试输出)
  4. 生成代码并打开项目

核心内容

队列的基本概念

什么是队列?

队列(Queue)是FreeRTOS中用于任务间数据传递的核心机制。它是一个先进先出(FIFO)的数据结构,允许任务之间安全地传递数据。

队列的特点: - FIFO顺序: 先发送的消息先被接收 - 数据复制: 队列复制数据,不是传递指针 - 阻塞机制: 支持发送和接收阻塞 - 多任务安全: 自动处理并发访问 - 固定大小: 创建时确定队列长度和消息大小

队列结构示意:

队列头 → [消息1] [消息2] [消息3] [空] [空] ← 队列尾
         ↑                              ↑
       读取位置                      写入位置

队列长度: 5
消息大小: 4字节
已用: 3个消息
可用: 2个空位

生活中的类比:

想象一个邮局的邮箱: - 发件人投递信件(发送消息) - 信件按顺序排列(FIFO队列) - 收件人取出信件(接收消息) - 邮箱有容量限制(队列长度) - 邮箱满时需要等待(阻塞发送) - 邮箱空时需要等待(阻塞接收)

步骤1: 创建和使用队列

1.1 队列创建API

FreeRTOS提供了两种队列创建方法:动态创建和静态创建。

动态创建队列 - xQueueCreate()

QueueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength,    // 队列长度(消息数量)
    UBaseType_t uxItemSize        // 每个消息的大小(字节)
);

返回值: - 成功: 返回队列句柄 - 失败: 返回NULL(内存不足)

完整示例:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

// 声明队列句柄
QueueHandle_t xDataQueue;

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();

    // 创建队列
    // 参数1: 队列长度 - 可以存储10个消息
    // 参数2: 消息大小 - 每个消息4字节(uint32_t)
    xDataQueue = xQueueCreate(10, sizeof(uint32_t));

    if(xDataQueue != NULL)
    {
        printf("Queue created successfully\n");
        printf("Queue length: 10\n");
        printf("Item size: %d bytes\n", sizeof(uint32_t));

        // 创建任务...
        xTaskCreate(SenderTask, "Sender", 256, NULL, 2, NULL);
        xTaskCreate(ReceiverTask, "Receiver", 256, NULL, 2, NULL);

        // 启动调度器
        vTaskStartScheduler();
    }
    else
    {
        printf("Failed to create queue - insufficient memory\n");
    }

    while(1);
}

代码说明: - xQueueCreate()从堆中分配内存 - 队列长度决定可以缓存多少个消息 - 消息大小必须是实际数据类型的大小 - 创建失败通常是因为堆内存不足

内存计算:

// 队列所需内存 ≈ 队列长度 × 消息大小 + 队列控制块大小
// 示例: 10个uint32_t消息
// 内存需求 ≈ 10 × 4 + sizeof(Queue_t) ≈ 40 + 80 = 120字节

1.2 发送消息到队列

FreeRTOS提供了多个发送API,适用于不同场景。

发送到队列尾部 - xQueueSend()

BaseType_t xQueueSend(
    QueueHandle_t xQueue,           // 队列句柄
    const void *pvItemToQueue,      // 要发送的数据指针
    TickType_t xTicksToWait         // 等待时间(时钟节拍)
);

返回值: - pdTRUE: 发送成功 - pdFALSE: 发送失败(队列满且超时)

基本示例:

void SenderTask(void *pvParameters)
{
    uint32_t ulValueToSend = 100;
    BaseType_t xStatus;

    while(1)
    {
        // 发送数据到队列
        xStatus = xQueueSend(xDataQueue, &ulValueToSend, pdMS_TO_TICKS(1000));

        if(xStatus == pdTRUE)
        {
            printf("Data sent: %lu\n", ulValueToSend);
            ulValueToSend++;
        }
        else
        {
            printf("Failed to send data - queue full\n");
        }

        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

等待时间参数:

// 不等待,立即返回
xQueueSend(xQueue, &data, 0);

// 等待1秒
xQueueSend(xQueue, &data, pdMS_TO_TICKS(1000));

// 永久等待
xQueueSend(xQueue, &data, portMAX_DELAY);

发送到队列头部 - xQueueSendToFront()

// 发送消息到队列头部(插队)
BaseType_t xQueueSendToFront(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    TickType_t xTicksToWait
);

// 用于紧急消息
uint32_t ulUrgentData = 999;
xQueueSendToFront(xDataQueue, &ulUrgentData, 0);
printf("Urgent message sent to front\n");

在中断中发送 - xQueueSendFromISR()

// 中断安全的发送函数
BaseType_t xQueueSendFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
);

// 中断服务函数示例
void EXTI0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
    {
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);

        uint32_t ulData = 888;

        // 在中断中发送到队列
        xQueueSendFromISR(xDataQueue, &ulData, &xHigherPriorityTaskWoken);

        // 如果有更高优先级任务被唤醒,触发任务切换
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

重要提示: - 在中断中必须使用FromISR版本的API - 不能在中断中使用阻塞等待(xTicksToWait必须为0) - 需要处理pxHigherPriorityTaskWoken参数

1.3 从队列接收消息

接收消息 - xQueueReceive()

BaseType_t xQueueReceive(
    QueueHandle_t xQueue,           // 队列句柄
    void *pvBuffer,                 // 接收缓冲区指针
    TickType_t xTicksToWait         // 等待时间
);

返回值: - pdTRUE: 接收成功 - pdFALSE: 接收失败(队列空且超时)

基本示例:

void ReceiverTask(void *pvParameters)
{
    uint32_t ulReceivedValue;
    BaseType_t xStatus;

    while(1)
    {
        // 从队列接收数据
        xStatus = xQueueReceive(xDataQueue, &ulReceivedValue, portMAX_DELAY);

        if(xStatus == pdTRUE)
        {
            printf("Received data: %lu\n", ulReceivedValue);

            // 处理接收到的数据
            ProcessData(ulReceivedValue);
        }
        else
        {
            printf("Failed to receive data\n");
        }
    }
}

窥视队列 - xQueuePeek()

// 查看队列头部的消息,但不移除
BaseType_t xQueuePeek(
    QueueHandle_t xQueue,
    void *pvBuffer,
    TickType_t xTicksToWait
);

// 示例:查看但不取出消息
uint32_t ulPeekedValue;
if(xQueuePeek(xDataQueue, &ulPeekedValue, 0) == pdTRUE)
{
    printf("Next message: %lu (not removed)\n", ulPeekedValue);
}

在中断中接收 - xQueueReceiveFromISR()

void UART_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint8_t ucReceivedByte;

    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))
    {
        // 在中断中从队列接收
        if(xQueueReceiveFromISR(xRxQueue, &ucReceivedByte, &xHigherPriorityTaskWoken) == pdTRUE)
        {
            // 处理接收到的数据
        }

        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

1.4 完整示例:基本队列通信

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>

// 队列句柄
QueueHandle_t xDataQueue;

// 发送任务
void vSenderTask(void *pvParameters)
{
    uint32_t ulCounter = 0;
    BaseType_t xStatus;

    printf("[Sender] Task started\n");

    while(1)
    {
        ulCounter++;

        printf("[Sender] Sending: %lu\n", ulCounter);

        // 发送数据到队列,等待最多1秒
        xStatus = xQueueSend(xDataQueue, &ulCounter, pdMS_TO_TICKS(1000));

        if(xStatus == pdTRUE)
        {
            printf("[Sender] Sent successfully\n");
        }
        else
        {
            printf("[Sender] Queue full, send failed\n");
        }

        // 每500ms发送一次
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

// 接收任务
void vReceiverTask(void *pvParameters)
{
    uint32_t ulReceivedValue;
    BaseType_t xStatus;

    printf("[Receiver] Task started\n");

    while(1)
    {
        // 等待接收数据
        xStatus = xQueueReceive(xDataQueue, &ulReceivedValue, portMAX_DELAY);

        if(xStatus == pdTRUE)
        {
            printf("[Receiver] Received: %lu\n", ulReceivedValue);

            // 模拟数据处理(1秒)
            vTaskDelay(pdMS_TO_TICKS(1000));

            printf("[Receiver] Processing complete\n\n");
        }
    }
}

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();
    MX_USART2_UART_Init();

    printf("\n=== FreeRTOS Queue Example ===\n\n");

    // 创建队列(长度10,每个消息4字节)
    xDataQueue = xQueueCreate(10, sizeof(uint32_t));

    if(xDataQueue != NULL)
    {
        printf("Queue created: length=10, item_size=4 bytes\n\n");

        // 创建任务
        xTaskCreate(vSenderTask, "Sender", 256, NULL, 2, NULL);
        xTaskCreate(vReceiverTask, "Receiver", 256, NULL, 2, NULL);

        // 启动调度器
        printf("Starting scheduler...\n\n");
        vTaskStartScheduler();
    }
    else
    {
        printf("Failed to create queue\n");
    }

    while(1);
}

运行结果:

=== FreeRTOS Queue Example ===

Queue created: length=10, item_size=4 bytes

Starting scheduler...

[Sender] Task started
[Receiver] Task started
[Sender] Sending: 1
[Sender] Sent successfully
[Receiver] Received: 1
[Sender] Sending: 2
[Sender] Sent successfully
[Sender] Sending: 3
[Sender] Sent successfully
[Receiver] Processing complete

[Receiver] Received: 2
[Sender] Sending: 4
[Sender] Sent successfully

代码说明: - 发送任务每500ms发送一个数据 - 接收任务每1000ms处理一个数据 - 队列自动缓存未处理的数据 - 当队列满时,发送任务会阻塞等待

步骤2: 传递复杂数据结构

2.1 传递结构体

队列可以传递任意类型的数据,包括结构体:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

// 定义传感器数据结构
typedef struct
{
    float fTemperature;
    float fHumidity;
    uint32_t ulTimestamp;
    uint8_t ucSensorID;
} SensorData_t;

// 队列句柄
QueueHandle_t xSensorQueue;

// 传感器任务
void vSensorTask(void *pvParameters)
{
    uint8_t ucSensorID = (uint8_t)(uint32_t)pvParameters;
    SensorData_t xData;

    while(1)
    {
        // 读取传感器数据
        xData.fTemperature = 25.5f + (ucSensorID * 0.5f);
        xData.fHumidity = 60.0f + (ucSensorID * 2.0f);
        xData.ulTimestamp = xTaskGetTickCount();
        xData.ucSensorID = ucSensorID;

        printf("[Sensor %d] Temp=%.1f°C, Humidity=%.1f%%\n",
               ucSensorID, xData.fTemperature, xData.fHumidity);

        // 发送结构体到队列
        if(xQueueSend(xSensorQueue, &xData, pdMS_TO_TICKS(100)) == pdTRUE)
        {
            printf("[Sensor %d] Data sent to queue\n", ucSensorID);
        }
        else
        {
            printf("[Sensor %d] Queue full!\n", ucSensorID);
        }

        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

// 数据处理任务
void vProcessTask(void *pvParameters)
{
    SensorData_t xReceivedData;

    while(1)
    {
        // 接收数据
        if(xQueueReceive(xSensorQueue, &xReceivedData, portMAX_DELAY) == pdTRUE)
        {
            printf("\n[Process] Received from Sensor %d:\n", xReceivedData.ucSensorID);
            printf("          Temperature: %.1f°C\n", xReceivedData.fTemperature);
            printf("          Humidity: %.1f%%\n", xReceivedData.fHumidity);
            printf("          Timestamp: %lu ms\n\n", xReceivedData.ulTimestamp);

            // 处理数据
            vTaskDelay(pdMS_TO_TICKS(500));
        }
    }
}

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();

    // 创建队列(传递结构体)
    xSensorQueue = xQueueCreate(5, sizeof(SensorData_t));

    if(xSensorQueue != NULL)
    {
        // 创建多个传感器任务
        xTaskCreate(vSensorTask, "Sensor1", 256, (void *)1, 2, NULL);
        xTaskCreate(vSensorTask, "Sensor2", 256, (void *)2, 2, NULL);
        xTaskCreate(vSensorTask, "Sensor3", 256, (void *)3, 2, NULL);

        // 创建处理任务
        xTaskCreate(vProcessTask, "Process", 256, NULL, 2, NULL);

        // 启动调度器
        vTaskStartScheduler();
    }

    while(1);
}

运行结果:

[Sensor 1] Temp=26.0°C, Humidity=62.0%
[Sensor 1] Data sent to queue

[Process] Received from Sensor 1:
          Temperature: 26.0°C
          Humidity: 62.0%
          Timestamp: 1000 ms

[Sensor 2] Temp=26.5°C, Humidity=64.0%
[Sensor 2] Data sent to queue
[Sensor 3] Temp=27.0°C, Humidity=66.0%
[Sensor 3] Data sent to queue

[Process] Received from Sensor 2:
          Temperature: 26.5°C
          Humidity: 64.0%
          Timestamp: 1000 ms

代码说明: - 定义传感器数据结构体 - 多个传感器任务发送数据到同一个队列 - 处理任务按顺序接收和处理数据 - 队列自动管理数据的存储和顺序

2.2 传递指针的注意事项

⚠️ 重要提示: 传递指针时要特别小心!

错误示例:

// ❌ 错误:传递局部变量的指针
void vBadSenderTask(void *pvParameters)
{
    while(1)
    {
        uint32_t ulData = 100;  // 局部变量
        uint32_t *pxData = &ulData;

        // 危险!ulData在函数返回后失效
        xQueueSend(xPtrQueue, &pxData, 0);

        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

正确示例1: 传递动态分配的内存

// ✅ 正确:传递动态分配的内存指针
void vGoodSenderTask(void *pvParameters)
{
    while(1)
    {
        // 动态分配内存
        uint32_t *pulData = (uint32_t *)pvPortMalloc(sizeof(uint32_t));

        if(pulData != NULL)
        {
            *pulData = 100;

            // 发送指针
            xQueueSend(xPtrQueue, &pulData, 0);
        }

        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vReceiverTask(void *pvParameters)
{
    uint32_t *pulReceivedPtr;

    while(1)
    {
        if(xQueueReceive(xPtrQueue, &pulReceivedPtr, portMAX_DELAY) == pdTRUE)
        {
            // 使用数据
            printf("Received: %lu\n", *pulReceivedPtr);

            // 释放内存
            vPortFree(pulReceivedPtr);
        }
    }
}

正确示例2: 传递静态缓冲区的指针

// ✅ 正确:传递静态缓冲区的指针
#define BUFFER_COUNT 5
static uint8_t ucBuffers[BUFFER_COUNT][256];

void vSenderTask(void *pvParameters)
{
    static uint8_t ucBufferIndex = 0;

    while(1)
    {
        // 使用静态缓冲区
        uint8_t *pucBuffer = ucBuffers[ucBufferIndex];
        ucBufferIndex = (ucBufferIndex + 1) % BUFFER_COUNT;

        // 填充数据
        sprintf((char *)pucBuffer, "Message %d", ucBufferIndex);

        // 发送指针
        xQueueSend(xPtrQueue, &pucBuffer, 0);

        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

步骤3: 队列管理和查询

3.1 查询队列状态

// 获取队列中等待的消息数量
UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue);

// 获取队列中可用空间数量
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue);

// 检查队列是否为空(中断安全)
BaseType_t xQueueIsQueueEmptyFromISR(QueueHandle_t xQueue);

// 检查队列是否已满(中断安全)
BaseType_t xQueueIsQueueFullFromISR(QueueHandle_t xQueue);

监控任务示例:

void vMonitorTask(void *pvParameters)
{
    UBaseType_t uxWaiting, uxAvailable;

    while(1)
    {
        uxWaiting = uxQueueMessagesWaiting(xDataQueue);
        uxAvailable = uxQueueSpacesAvailable(xDataQueue);

        printf("\n[Monitor] Queue status:\n");
        printf("          Messages waiting: %u\n", uxWaiting);
        printf("          Spaces available: %u\n", uxAvailable);
        printf("          Usage: %u%%\n\n", (uxWaiting * 100) / (uxWaiting + uxAvailable));

        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

3.2 重置和删除队列

// 清空队列中的所有消息
BaseType_t xQueueReset(QueueHandle_t xQueue);

// 删除队列,释放内存
void vQueueDelete(QueueHandle_t xQueue);

// 示例
void vCleanupTask(void *pvParameters)
{
    // 使用队列...

    // 清空队列
    xQueueReset(xDataQueue);
    printf("Queue cleared\n");

    // 不再需要时删除
    vQueueDelete(xDataQueue);
    printf("Queue deleted\n");

    vTaskDelete(NULL);
}

步骤4: 队列集(Queue Sets)

队列集允许任务同时等待多个队列,当任何一个队列有数据时被唤醒。

4.1 创建和使用队列集

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

// 队列句柄
QueueHandle_t xQueue1, xQueue2, xQueue3;
QueueSetHandle_t xQueueSet;

void vSetupQueues(void)
{
    // 创建3个队列
    xQueue1 = xQueueCreate(5, sizeof(uint32_t));
    xQueue2 = xQueueCreate(5, sizeof(uint32_t));
    xQueue3 = xQueueCreate(5, sizeof(uint32_t));

    // 创建队列集(总容量 = 所有队列长度之和)
    xQueueSet = xQueueCreateSet(5 + 5 + 5);

    // 将队列添加到队列集
    xQueueAddToSet(xQueue1, xQueueSet);
    xQueueAddToSet(xQueue2, xQueueSet);
    xQueueAddToSet(xQueue3, xQueueSet);
}

// 发送任务1
void vSender1Task(void *pvParameters)
{
    uint32_t ulValue = 100;

    while(1)
    {
        xQueueSend(xQueue1, &ulValue, portMAX_DELAY);
        printf("[Sender1] Sent: %lu\n", ulValue);
        ulValue++;
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 发送任务2
void vSender2Task(void *pvParameters)
{
    uint32_t ulValue = 200;

    while(1)
    {
        xQueueSend(xQueue2, &ulValue, portMAX_DELAY);
        printf("[Sender2] Sent: %lu\n", ulValue);
        ulValue++;
        vTaskDelay(pdMS_TO_TICKS(1500));
    }
}

// 发送任务3
void vSender3Task(void *pvParameters)
{
    uint32_t ulValue = 300;

    while(1)
    {
        xQueueSend(xQueue3, &ulValue, portMAX_DELAY);
        printf("[Sender3] Sent: %lu\n", ulValue);
        ulValue++;
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

// 接收任务(使用队列集)
void vReceiverTask(void *pvParameters)
{
    QueueSetMemberHandle_t xActivatedMember;
    uint32_t ulReceivedValue;

    while(1)
    {
        // 等待队列集中的任何队列有数据
        xActivatedMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);

        // 确定是哪个队列有数据
        if(xActivatedMember == xQueue1)
        {
            xQueueReceive(xQueue1, &ulReceivedValue, 0);
            printf("[Receiver] From Queue1: %lu\n\n", ulReceivedValue);
        }
        else if(xActivatedMember == xQueue2)
        {
            xQueueReceive(xQueue2, &ulReceivedValue, 0);
            printf("[Receiver] From Queue2: %lu\n\n", ulReceivedValue);
        }
        else if(xActivatedMember == xQueue3)
        {
            xQueueReceive(xQueue3, &ulReceivedValue, 0);
            printf("[Receiver] From Queue3: %lu\n\n", ulReceivedValue);
        }
    }
}

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();

    // 设置队列和队列集
    vSetupQueues();

    // 创建任务
    xTaskCreate(vSender1Task, "Sender1", 256, NULL, 2, NULL);
    xTaskCreate(vSender2Task, "Sender2", 256, NULL, 2, NULL);
    xTaskCreate(vSender3Task, "Sender3", 256, NULL, 2, NULL);
    xTaskCreate(vReceiverTask, "Receiver", 256, NULL, 3, NULL);

    // 启动调度器
    vTaskStartScheduler();

    while(1);
}

运行结果:

[Sender1] Sent: 100
[Receiver] From Queue1: 100

[Sender2] Sent: 200
[Receiver] From Queue2: 200

[Sender1] Sent: 101
[Receiver] From Queue1: 101

[Sender3] Sent: 300
[Receiver] From Queue3: 300

[Sender2] Sent: 201
[Receiver] From Queue2: 201

代码说明: - 创建3个独立的队列 - 创建队列集并添加所有队列 - 接收任务使用xQueueSelectFromSet()等待任何队列 - 根据返回的句柄确定是哪个队列有数据

4.2 队列集的应用场景

场景1: 多源数据采集

// 同时监听多个传感器的数据
QueueHandle_t xTempQueue, xHumidityQueue, xPressureQueue;
QueueSetHandle_t xSensorSet;

// 统一的数据处理任务
void vDataProcessTask(void *pvParameters)
{
    QueueSetMemberHandle_t xActivatedQueue;

    while(1)
    {
        xActivatedQueue = xQueueSelectFromSet(xSensorSet, portMAX_DELAY);

        if(xActivatedQueue == xTempQueue)
        {
            // 处理温度数据
        }
        else if(xActivatedQueue == xHumidityQueue)
        {
            // 处理湿度数据
        }
        else if(xActivatedQueue == xPressureQueue)
        {
            // 处理压力数据
        }
    }
}

场景2: 多通道通信

// 同时监听多个通信通道
QueueHandle_t xUartQueue, xSpiQueue, xI2cQueue;
QueueSetHandle_t xCommSet;

// 统一的通信处理任务
void vCommHandlerTask(void *pvParameters)
{
    QueueSetMemberHandle_t xActivatedQueue;

    while(1)
    {
        xActivatedQueue = xQueueSelectFromSet(xCommSet, portMAX_DELAY);

        // 处理来自不同通道的数据
    }
}

步骤5: 邮箱(Mailbox)

邮箱是一种特殊的队列,长度为1,用于传递单个数据项。

5.1 邮箱的概念

邮箱 vs 队列:

特性 邮箱 队列
长度 1 可配置
覆盖 可以覆盖旧数据 不能覆盖
用途 保存最新状态 缓存多个消息
典型场景 状态更新 事件序列

5.2 使用队列实现邮箱

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

// 系统状态结构
typedef struct
{
    uint32_t ulCpuUsage;
    uint32_t ulMemoryUsage;
    uint32_t ulTemperature;
    uint32_t ulTimestamp;
} SystemStatus_t;

// 邮箱(长度为1的队列)
QueueHandle_t xStatusMailbox;

// 状态更新任务
void vStatusUpdateTask(void *pvParameters)
{
    SystemStatus_t xStatus;

    while(1)
    {
        // 读取系统状态
        xStatus.ulCpuUsage = GetCpuUsage();
        xStatus.ulMemoryUsage = GetMemoryUsage();
        xStatus.ulTemperature = GetTemperature();
        xStatus.ulTimestamp = xTaskGetTickCount();

        // 覆盖式发送(如果邮箱满,覆盖旧数据)
        xQueueOverwrite(xStatusMailbox, &xStatus);

        printf("[Update] Status updated: CPU=%lu%%, Mem=%lu%%, Temp=%lu°C\n",
               xStatus.ulCpuUsage, xStatus.ulMemoryUsage, xStatus.ulTemperature);

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 状态显示任务
void vStatusDisplayTask(void *pvParameters)
{
    SystemStatus_t xStatus;

    while(1)
    {
        // 读取最新状态(不移除)
        if(xQueuePeek(xStatusMailbox, &xStatus, portMAX_DELAY) == pdTRUE)
        {
            printf("\n[Display] Current Status:\n");
            printf("          CPU Usage: %lu%%\n", xStatus.ulCpuUsage);
            printf("          Memory Usage: %lu%%\n", xStatus.ulMemoryUsage);
            printf("          Temperature: %lu°C\n", xStatus.ulTemperature);
            printf("          Timestamp: %lu ms\n\n", xStatus.ulTimestamp);
        }

        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();

    // 创建邮箱(长度为1的队列)
    xStatusMailbox = xQueueCreate(1, sizeof(SystemStatus_t));

    if(xStatusMailbox != NULL)
    {
        // 创建任务
        xTaskCreate(vStatusUpdateTask, "Update", 256, NULL, 2, NULL);
        xTaskCreate(vStatusDisplayTask, "Display", 256, NULL, 1, NULL);

        // 启动调度器
        vTaskStartScheduler();
    }

    while(1);
}

邮箱专用API:

// 覆盖式发送(如果邮箱满,覆盖旧数据)
BaseType_t xQueueOverwrite(
    QueueHandle_t xQueue,
    const void *pvItemToQueue
);

// 在中断中覆盖式发送
BaseType_t xQueueOverwriteFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
);

代码说明: - 邮箱始终保存最新的状态 - 使用xQueueOverwrite()覆盖旧数据 - 使用xQueuePeek()读取但不移除数据 - 适合状态监控和数据共享场景

实践示例

示例1: 中断与任务通信

使用队列实现UART中断与任务之间的数据传递:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

// 队列句柄
QueueHandle_t xUartRxQueue;

// UART中断服务函数
void USART2_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    // 检查接收中断
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))
    {
        // 读取数据
        uint8_t ucData = (uint8_t)(huart2.Instance->DR & 0xFF);

        // 发送到队列
        xQueueSendFromISR(xUartRxQueue, &ucData, &xHigherPriorityTaskWoken);

        // 触发任务切换
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

// UART处理任务
void vUartProcessTask(void *pvParameters)
{
    uint8_t ucReceivedByte;
    uint8_t ucBuffer[256];
    uint8_t ucIndex = 0;

    while(1)
    {
        // 接收数据
        if(xQueueReceive(xUartRxQueue, &ucReceivedByte, portMAX_DELAY) == pdTRUE)
        {
            // 处理接收到的字节
            if(ucReceivedByte == '\n' || ucReceivedByte == '\r')
            {
                // 收到换行符,处理完整命令
                ucBuffer[ucIndex] = '\0';
                printf("[UART] Received command: %s\n", ucBuffer);

                // 处理命令
                ProcessCommand((char *)ucBuffer);

                // 重置缓冲区
                ucIndex = 0;
            }
            else if(ucIndex < sizeof(ucBuffer) - 1)
            {
                // 添加到缓冲区
                ucBuffer[ucIndex++] = ucReceivedByte;
            }
        }
    }
}

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();
    MX_USART2_UART_Init();

    // 创建队列(缓存64个字节)
    xUartRxQueue = xQueueCreate(64, sizeof(uint8_t));

    // 创建处理任务
    xTaskCreate(vUartProcessTask, "UART", 512, NULL, 3, NULL);

    // 启动调度器
    vTaskStartScheduler();

    while(1);
}

代码说明: - 中断服务函数快速将数据放入队列 - 任务从队列中取出数据进行处理 - 避免在中断中进行复杂处理 - 提高系统响应性和可靠性

示例2: 生产者-消费者模式

实现经典的生产者-消费者模式:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

// 数据结构
typedef struct
{
    uint32_t ulID;
    uint32_t ulValue;
    uint32_t ulTimestamp;
} DataItem_t;

// 队列句柄
QueueHandle_t xProductionQueue;

// 生产者任务
void vProducerTask(void *pvParameters)
{
    uint32_t ulProducerID = (uint32_t)pvParameters;
    uint32_t ulItemCount = 0;
    DataItem_t xItem;

    while(1)
    {
        // 生产数据
        xItem.ulID = ulProducerID;
        xItem.ulValue = ulItemCount++;
        xItem.ulTimestamp = xTaskGetTickCount();

        printf("[Producer %lu] Producing item %lu\n", ulProducerID, xItem.ulValue);

        // 发送到队列
        if(xQueueSend(xProductionQueue, &xItem, pdMS_TO_TICKS(1000)) == pdTRUE)
        {
            printf("[Producer %lu] Item %lu sent\n", ulProducerID, xItem.ulValue);
        }
        else
        {
            printf("[Producer %lu] Queue full, item %lu discarded\n", 
                   ulProducerID, xItem.ulValue);
        }

        // 生产速度
        vTaskDelay(pdMS_TO_TICKS(300 + (ulProducerID * 100)));
    }
}

// 消费者任务
void vConsumerTask(void *pvParameters)
{
    uint32_t ulConsumerID = (uint32_t)pvParameters;
    DataItem_t xItem;

    while(1)
    {
        // 从队列获取数据
        if(xQueueReceive(xProductionQueue, &xItem, portMAX_DELAY) == pdTRUE)
        {
            printf("[Consumer %lu] Consuming from Producer %lu: value=%lu, time=%lu\n",
                   ulConsumerID, xItem.ulID, xItem.ulValue, xItem.ulTimestamp);

            // 模拟消费处理
            vTaskDelay(pdMS_TO_TICKS(500));

            printf("[Consumer %lu] Item processed\n\n", ulConsumerID);
        }
    }
}

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();

    // 创建队列
    xProductionQueue = xQueueCreate(10, sizeof(DataItem_t));

    // 创建3个生产者任务
    xTaskCreate(vProducerTask, "Producer1", 256, (void *)1, 2, NULL);
    xTaskCreate(vProducerTask, "Producer2", 256, (void *)2, 2, NULL);
    xTaskCreate(vProducerTask, "Producer3", 256, (void *)3, 2, NULL);

    // 创建2个消费者任务
    xTaskCreate(vConsumerTask, "Consumer1", 256, (void *)1, 2, NULL);
    xTaskCreate(vConsumerTask, "Consumer2", 256, (void *)2, 2, NULL);

    // 启动调度器
    vTaskStartScheduler();

    while(1);
}

代码说明: - 多个生产者任务生产数据 - 多个消费者任务消费数据 - 队列自动管理数据的缓冲和同步 - 实现了解耦和负载均衡

验证

验证方法

  1. 编译项目

    # 在STM32CubeIDE中
    Project  Build Project
    
    # 检查编译输出
    Build Finished. 0 errors, 0 warnings.
    

  2. 下载到开发板

  3. 连接开发板到电脑
  4. 点击"Debug"或"Run"按钮
  5. 程序自动下载并运行

  6. 查看串口输出

  7. 打开串口调试工具
  8. 配置: 115200, 8N1
  9. 观察任务执行和队列操作的输出

预期结果

基本队列通信: - ✅ 发送任务成功发送数据 - ✅ 接收任务按顺序接收数据 - ✅ 队列自动缓存数据 - ✅ 队列满时发送任务阻塞

结构体传递: - ✅ 多个传感器任务发送数据 - ✅ 处理任务正确接收完整结构体 - ✅ 数据内容完整无误 - ✅ 按FIFO顺序处理

队列集: - ✅ 接收任务能够监听多个队列 - ✅ 正确识别数据来源 - ✅ 按到达顺序处理数据

中断通信: - ✅ 中断快速将数据放入队列 - ✅ 任务从队列取出数据处理 - ✅ 系统响应及时 - ✅ 无数据丢失

测试要点

  1. 功能测试
  2. 队列创建成功
  3. 发送和接收工作正常
  4. 数据传递正确
  5. 阻塞机制工作正常
  6. 队列集功能正常

  7. 边界测试

  8. 队列满时发送阻塞
  9. 队列空时接收阻塞
  10. 超时机制工作正常
  11. 队列重置正确

  12. 性能测试

  13. 数据传递及时
  14. 无数据丢失
  15. 系统响应流畅
  16. 内存使用合理

故障排除

问题1: 队列创建失败

现象:

xDataQueue = xQueueCreate(10, sizeof(uint32_t));
if(xDataQueue == NULL) {
    printf("Failed to create queue\n");
}

可能原因: 1. 堆内存不足 2. 队列长度或消息大小过大 3. FreeRTOS配置错误

解决方法:

// 1. 增加堆大小(FreeRTOSConfig.h)
#define configTOTAL_HEAP_SIZE  ((size_t)(20 * 1024))  // 增加到20KB

// 2. 减小队列大小
xDataQueue = xQueueCreate(5, sizeof(uint32_t));  // 减小长度

// 3. 检查剩余堆空间
size_t xFreeHeap = xPortGetFreeHeapSize();
printf("Free heap: %u bytes\n", xFreeHeap);

// 4. 计算队列所需内存
size_t xQueueSize = 10 * sizeof(uint32_t) + sizeof(StaticQueue_t);
printf("Queue needs: %u bytes\n", xQueueSize);

问题2: 数据接收不正确

现象:

// 发送的数据和接收的数据不一致
uint32_t ulSent = 100;
xQueueSend(xQueue, &ulSent, 0);

uint32_t ulReceived;
xQueueReceive(xQueue, &ulReceived, 0);
printf("Received: %lu\n", ulReceived);  // 输出错误的值

可能原因: 1. 队列消息大小设置错误 2. 数据类型不匹配 3. 指针使用错误

解决方法:

// 1. 确保消息大小正确
// ❌ 错误
xQueue = xQueueCreate(10, sizeof(uint32_t *));  // 指针大小
// ✅ 正确
xQueue = xQueueCreate(10, sizeof(uint32_t));    // 数据大小

// 2. 确保数据类型匹配
typedef struct {
    uint32_t ulValue;
    float fData;
} MyData_t;

MyData_t xSendData = {100, 25.5f};
MyData_t xRecvData;

xQueueSend(xQueue, &xSendData, 0);
xQueueReceive(xQueue, &xRecvData, 0);

// 3. 正确使用指针
// ❌ 错误: 传递局部变量指针
void BadFunction(void) {
    uint32_t ulData = 100;
    xQueueSend(xQueue, &ulData, 0);  // ulData在函数返回后失效
}

// ✅ 正确: 传递数据本身
void GoodFunction(void) {
    uint32_t ulData = 100;
    xQueueSend(xQueue, &ulData, 0);  // 队列复制数据
}

问题3: 任务永久阻塞

现象:

// 任务一直等待,永不返回
xQueueReceive(xQueue, &xData, portMAX_DELAY);
printf("This never prints\n");

可能原因: 1. 没有任务发送数据 2. 队列句柄错误 3. 死锁情况

解决方法:

// 1. 使用超时等待
if(xQueueReceive(xQueue, &xData, pdMS_TO_TICKS(5000)) == pdTRUE) {
    printf("Received: %lu\n", xData);
} else {
    printf("Timeout waiting for data\n");
    // 错误处理
}

// 2. 检查队列句柄
if(xQueue == NULL) {
    printf("Queue handle is NULL!\n");
    return;
}

// 3. 添加调试输出
printf("Waiting for data...\n");
xQueueReceive(xQueue, &xData, portMAX_DELAY);
printf("Data received\n");

// 4. 检查队列状态
UBaseType_t uxWaiting = uxQueueMessagesWaiting(xQueue);
printf("Messages in queue: %u\n", uxWaiting);

问题4: 数据丢失

现象:

// 发送了10个数据,但只接收到5个
for(int i = 0; i < 10; i++) {
    xQueueSend(xQueue, &i, 0);  // 部分发送失败
}

可能原因: 1. 队列容量不足 2. 发送超时时间为0 3. 接收速度慢于发送速度

解决方法:

// 1. 增加队列容量
xQueue = xQueueCreate(20, sizeof(uint32_t));  // 增加容量

// 2. 使用超时等待
for(int i = 0; i < 10; i++) {
    if(xQueueSend(xQueue, &i, pdMS_TO_TICKS(1000)) != pdTRUE) {
        printf("Failed to send data %d\n", i);
    }
}

// 3. 检查发送结果
BaseType_t xResult = xQueueSend(xQueue, &xData, 0);
if(xResult != pdTRUE) {
    printf("Queue full, data lost\n");
    // 记录丢失的数据
}

// 4. 监控队列状态
UBaseType_t uxAvailable = uxQueueSpacesAvailable(xQueue);
if(uxAvailable == 0) {
    printf("Warning: Queue is full!\n");
}

问题5: 中断中使用错误的API

现象:

// 在中断中使用普通API(错误)
void USART2_IRQHandler(void) {
    uint8_t ucData = UART_ReceiveData();
    xQueueSend(xQueue, &ucData, 0);  // ❌ 错误!
}

解决方法:

// 在中断中必须使用FromISR版本的API
void USART2_IRQHandler(void) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint8_t ucData = UART_ReceiveData();

    // ✅ 正确: 使用FromISR版本
    xQueueSendFromISR(xQueue, &ucData, &xHigherPriorityTaskWoken);

    // 触发任务切换
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

总结

通过本教程,你学习了:

  • ✅ FreeRTOS队列的工作原理和应用场景
  • ✅ 队列创建和配置的方法
  • ✅ 队列发送和接收API的使用
  • ✅ 队列的阻塞机制和超时处理
  • ✅ 队列集的使用方法
  • ✅ 邮箱的概念和应用
  • ✅ 实际项目中的队列通信模式

关键要点

  1. 队列是任务间数据传递的核心机制
  2. FIFO顺序保证数据按序处理
  3. 数据复制保证数据安全
  4. 阻塞机制实现任务同步

  5. 选择合适的队列大小

  6. 队列长度 = 预期缓存的消息数量
  7. 消息大小 = 实际数据类型的大小
  8. 考虑内存限制和性能需求

  9. 正确使用阻塞和超时

  10. 0: 不等待,立即返回
  11. portMAX_DELAY: 永久等待
  12. pdMS_TO_TICKS(ms): 等待指定时间

  13. 中断中使用FromISR版本API

  14. xQueueSendFromISR()
  15. xQueueReceiveFromISR()
  16. 处理pxHigherPriorityTaskWoken参数

  17. 队列集用于监听多个队列

  18. 创建队列集
  19. 添加队列到队列集
  20. 使用xQueueSelectFromSet()等待

进阶挑战

尝试以下挑战来巩固学习:

挑战1: 多级数据处理流水线

创建一个3级数据处理流水线: - 级别1: 数据采集(传感器读取) - 级别2: 数据预处理(滤波、校准) - 级别3: 数据分析(算法处理)

要求: - 使用队列连接各级 - 每级独立运行 - 支持并行处理

提示: 每级之间使用独立的队列

挑战2: 优先级队列

实现一个支持优先级的消息队列: - 高优先级消息优先处理 - 相同优先级按FIFO顺序 - 支持紧急消息插队

提示: 使用多个队列和队列集

挑战3: 环形缓冲区队列

使用队列实现一个环形缓冲区: - 固定大小的缓冲区 - 支持覆盖最旧的数据 - 提供统计信息(丢失数据数量)

提示: 使用xQueueOverwrite()

完整代码示例

综合示例: 数据采集系统

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdio.h>

// 传感器数据结构
typedef struct
{
    uint8_t ucSensorID;
    float fValue;
    uint32_t ulTimestamp;
} SensorData_t;

// 队列句柄
QueueHandle_t xRawDataQueue;
QueueHandle_t xProcessedDataQueue;

// 传感器任务
void vSensorTask(void *pvParameters)
{
    uint8_t ucSensorID = (uint8_t)(uint32_t)pvParameters;
    SensorData_t xData;

    while(1)
    {
        // 读取传感器
        xData.ucSensorID = ucSensorID;
        xData.fValue = ReadSensor(ucSensorID);
        xData.ulTimestamp = xTaskGetTickCount();

        // 发送原始数据
        xQueueSend(xRawDataQueue, &xData, portMAX_DELAY);

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 数据处理任务
void vProcessTask(void *pvParameters)
{
    SensorData_t xRawData, xProcessedData;

    while(1)
    {
        // 接收原始数据
        if(xQueueReceive(xRawDataQueue, &xRawData, portMAX_DELAY) == pdTRUE)
        {
            // 数据处理(滤波、校准等)
            xProcessedData = xRawData;
            xProcessedData.fValue = ProcessData(xRawData.fValue);

            // 发送处理后的数据
            xQueueSend(xProcessedDataQueue, &xProcessedData, portMAX_DELAY);
        }
    }
}

// 数据存储任务
void vStorageTask(void *pvParameters)
{
    SensorData_t xData;

    while(1)
    {
        // 接收处理后的数据
        if(xQueueReceive(xProcessedDataQueue, &xData, portMAX_DELAY) == pdTRUE)
        {
            // 存储数据
            printf("[Storage] Sensor %d: %.2f at %lu ms\n",
                   xData.ucSensorID, xData.fValue, xData.ulTimestamp);

            SaveToStorage(&xData);
        }
    }
}

int main(void)
{
    // 系统初始化
    HAL_Init();
    SystemClock_Config();

    // 创建队列
    xRawDataQueue = xQueueCreate(10, sizeof(SensorData_t));
    xProcessedDataQueue = xQueueCreate(10, sizeof(SensorData_t));

    // 创建传感器任务
    xTaskCreate(vSensorTask, "Sensor1", 256, (void *)1, 2, NULL);
    xTaskCreate(vSensorTask, "Sensor2", 256, (void *)2, 2, NULL);
    xTaskCreate(vSensorTask, "Sensor3", 256, (void *)3, 2, NULL);

    // 创建处理任务
    xTaskCreate(vProcessTask, "Process", 256, NULL, 2, NULL);

    // 创建存储任务
    xTaskCreate(vStorageTask, "Storage", 256, NULL, 1, NULL);

    // 启动调度器
    vTaskStartScheduler();

    while(1);
}

测试环境

硬件环境: - 开发板: STM32F407 Discovery - 调试器: ST-Link V2 - 传感器: 模拟传感器(可选)

软件环境: - IDE: STM32CubeIDE v1.10.1 - HAL库版本: v1.27.1 - FreeRTOS版本: V10.3.1 - 编译器: GCC ARM 10.3

下一步学习

建议按以下顺序继续学习:

1. 同步机制

2. 高级通信

3. 内存管理

4. 系统设计

参考资料

官方文档

  1. FreeRTOS官方网站
  2. https://www.freertos.org/
  3. 包含完整的API文档和教程

  4. FreeRTOS队列API参考

  5. https://www.freertos.org/a00018.html
  6. 详细的队列API说明

  7. FreeRTOS队列集文档

  8. https://www.freertos.org/RTOS-queue-sets.html
  9. 队列集的使用指南

推荐书籍

  1. 《Mastering the FreeRTOS Real Time Kernel》
  2. FreeRTOS作者编写
  3. 免费下载: https://www.freertos.org/Documentation/RTOS_book.html

  4. 《嵌入式实时操作系统FreeRTOS原理与实践》

  5. 中文书籍,适合入门
  6. 包含大量实例

在线资源

  1. FreeRTOS论坛
  2. https://forums.freertos.org/
  3. 活跃的社区支持

  4. STM32社区

  5. https://community.st.com/
  6. ST官方技术支持

  7. GitHub示例代码

  8. https://github.com/FreeRTOS/FreeRTOS
  9. 官方示例和Demo

常见问题解答

Q1: 队列和信号量有什么区别?

A: 主要区别在于数据传递:

特性 消息队列 信号量
数据传递 传递数据 不传递数据
用途 任务间通信 任务同步
容量 可存储多个消息 只有计数值
FIFO 先进先出 无顺序概念
开销 较大(需要复制数据)

选择建议: - 需要传递数据 → 使用消息队列 - 只需要通知事件 → 使用信号量

Q2: 如何选择合适的队列大小?

A: 考虑以下因素:

队列长度: - 生产速度 vs 消费速度 - 可接受的延迟 - 内存限制

计算方法:

队列长度 = (生产速率 - 消费速率) × 最大延迟时间

示例: - 生产: 10个/秒 - 消费: 8个/秒 - 最大延迟: 5秒 - 队列长度 = (10 - 8) × 5 = 10个

Q3: 队列满了怎么办?

A: 有几种处理策略:

  1. 阻塞等待

    xQueueSend(xQueue, &data, portMAX_DELAY);  // 永久等待
    

  2. 超时等待

    if(xQueueSend(xQueue, &data, pdMS_TO_TICKS(1000)) != pdTRUE) {
        // 超时处理
    }
    

  3. 丢弃数据

    if(xQueueSend(xQueue, &data, 0) != pdTRUE) {
        // 记录丢失
        lostCount++;
    }
    

  4. 覆盖最旧数据(邮箱)

    xQueueOverwrite(xMailbox, &data);
    

Q4: 如何在中断中使用队列?

A: 必须使用FromISR版本的API:

void EXTI0_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    // 清除中断标志
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);

    uint32_t ulData = 100;

    // 使用FromISR版本
    xQueueSendFromISR(xQueue, &ulData, &xHigherPriorityTaskWoken);

    // 触发任务切换
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

注意事项: - 不能使用阻塞等待 - 必须处理pxHigherPriorityTaskWoken - 中断处理要快速

Q5: 队列和任务通知有什么区别?

A: 任务通知是轻量级的通信机制:

特性 队列 任务通知
数据量 可传递任意大小 只能传递32位值
缓冲 可缓存多个消息 只能缓存1个
开销 较大 很小
灵活性
适用场景 复杂数据传递 简单事件通知

选择建议: - 需要传递复杂数据 → 使用队列 - 只需要简单通知 → 使用任务通知


反馈与支持:

如果你在学习过程中遇到问题: - 💬 在评论区留言讨论 - 📧 发送邮件到: support@embedded-platform.com - 🐛 报告问题: GitHub Issues

贡献代码: 欢迎提交改进建议和示例代码!


版权声明: 本教程采用 CC BY-SA 4.0 许可协议。

最后更新: 2024-01-15
文档版本: 1.0