CMSIS-RTOS v2 是 ARM 官方对 FreeRTOS 的标准封装,提供了统一的 RTOS API 接口,完全兼容 STM32CubeMX 自动生成的工程。它在原生 FreeRTOS 队列基础上增加了消息优先级特性,函数命名和参数风格更统一,错误处理更规范。

一、CMSIS-RTOS v2 队列核心函数与基础用例

1. 队列创建与删除

1.1 osMessageQueueNew() - 动态创建队列(最常用)

功能:从 RTOS 堆中分配内存创建消息队列 原型

osMessageQueueId_t osMessageQueueNew(
    uint32_t msg_count,        // 队列最大消息数
    uint32_t msg_size,         // 单个消息的字节大小
    const osMessageQueueAttr_t *attr  // 队列属性,NULL为默认
);

返回值:成功返回队列 ID,失败返回NULL

用例

#include "cmsis_os2.h"

// 全局队列ID
osMessageQueueId_t g_intQueue;    // 存储int类型的队列
osMessageQueueId_t g_structQueue; // 存储结构体的队列

// 自定义传感器数据结构体
typedef struct {
    uint8_t sensorId;
    float value;
    uint32_t timestamp;
} SensorData_t;

void Queue_Init(void) {
    // 1. 创建能容纳10个int的队列
    g_intQueue = osMessageQueueNew(10, sizeof(int), NULL);
    if (g_intQueue == NULL) {
        Error_Handler(); // 内存不足创建失败
    }

    // 2. 创建能容纳5个SensorData_t结构体的队列
    g_structQueue = osMessageQueueNew(5, sizeof(SensorData_t), NULL);
    if (g_structQueue == NULL) {
        Error_Handler();
    }
}

1.2 osMessageQueueNewStatic() - 静态创建队列

功能:使用用户提供的静态内存创建队列,不依赖堆,无内存碎片风险

原型

osMessageQueueId_t osMessageQueueNewStatic(
    uint32_t msg_count,
    uint32_t msg_size,
    void *msg_buffer,          // 存储消息数据的缓冲区
    StaticQueue_t *queue_cb,   // 存储队列控制块的内存
    const osMessageQueueAttr_t *attr
);

用例

#define STATIC_QUEUE_LEN 5
#define STATIC_MSG_SIZE sizeof(int)

// 静态内存(必须全局或static,不能是栈变量)
static uint8_t s_msgBuffer[STATIC_QUEUE_LEN * STATIC_MSG_SIZE];
static StaticQueue_t s_queueCb;
osMessageQueueId_t g_staticIntQueue;

void StaticQueue_Init(void) {
    g_staticIntQueue = osMessageQueueNewStatic(
        STATIC_QUEUE_LEN,
        STATIC_MSG_SIZE,
        s_msgBuffer,
        &s_queueCb,
        NULL
    );
    // 静态创建永远不会失败,无需检查NULL
}
1.3 osMessageQueueDelete() - 删除队列

功能:删除队列并释放动态内存(静态队列仅释放控制块关联)

原型

osStatus_t osMessageQueueDelete(osMessageQueueId_t mq_id);

用例

if (g_intQueue != NULL) {
    osMessageQueueDelete(g_intQueue);
    g_intQueue = NULL; // 防止野指针
}

2. 任务级消息发送函数

osMessageQueuePut() - 发送消息(支持优先级)

功能:将消息复制到队列,支持指定消息优先级(高优先级消息排在前面)

🔥 CMSIS-RTOS v2 独有特性:原生 FreeRTOS 队列只有 FIFO,CMSIS 增加了消息优先级 原型

osStatus_t osMessageQueuePut(
    osMessageQueueId_t mq_id,
    const void *msg_ptr,       // 指向要发送的消息
    uint8_t msg_prio,          // 消息优先级:0(最低)~255(最高)
    uint32_t timeout           // 队列满时的阻塞时间,osWaitForever永久等待
);

返回值osOK成功,osErrorTimeout超时,osErrorResource队列满

用例

void ProducerTask(void *argument) {
    int counter = 0;
    SensorData_t sensorData;
    uint32_t timestamp = 0;

    while (1) {
        // 1. 发送普通int消息(优先级0)
        counter++;
        osStatus_t ret = osMessageQueuePut(g_intQueue, &counter, 0, osWaitForever);
        if (ret == osOK) {
            printf("发送普通消息: %d\n", counter);
        }

        // 2. 发送紧急消息(优先级255,会排在队列最前面)
        if (counter % 10 == 0) {
            int emergencyCode = 0xFF;
            osMessageQueuePut(g_intQueue, &emergencyCode, 255, osWaitForever);
            printf("发送紧急消息!\n");
        }

        // 3. 发送结构体消息
        sensorData.sensorId = 1;
        sensorData.value = 25.0f + (rand() % 100) / 10.0f;
        sensorData.timestamp = timestamp++;
        osMessageQueuePut(g_structQueue, &sensorData, 0, osWaitForever);

        osDelay(1000); // 每秒发送一次
    }
}

注意:CMSIS-RTOS v2 没有单独的SendToFront函数,通过设置最高优先级 (255) 即可实现紧急消息插队

3. 中断级消息发送函数

osMessageQueuePutFromISR() - 中断中发送消息

功能:中断服务例程 (ISR) 专用的消息发送函数,无阻塞

原型

osStatus_t osMessageQueuePutFromISR(
    osMessageQueueId_t mq_id,
    const void *msg_ptr,
    uint8_t msg_prio,
    int32_t *pxHigherPriorityTaskWoken  // 输出参数:是否需要任务切换
);

用例(STM32 外部中断):

osMessageQueueId_t g_buttonQueue; // 按键事件队列

void EXTI0_IRQHandler(void) {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);

    int32_t xHigherPriorityTaskWoken = 0;
    uint8_t buttonEvent = 1; // 1表示按键按下

    // 中断中发送按键事件
    osMessageQueuePutFromISR(g_buttonQueue, &buttonEvent, 0, &xHigherPriorityTaskWoken);

    // 如果有更高优先级任务被唤醒,立即进行任务切换
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

4. 任务级消息接收函数

4.1 osMessageQueueGet() - 接收并删除消息

功能:从队列头部接收消息,并将消息从队列中删除

原型

osStatus_t osMessageQueueGet(
    osMessageQueueId_t mq_id,
    void *msg_ptr,             // 接收消息的缓冲区
    uint8_t *msg_prio,         // 输出参数:获取消息优先级,不需要传NULL
    uint32_t timeout           // 队列空时的阻塞时间
);

用例

void ConsumerTask(void *argument) {
    int recvInt;
    uint8_t msgPrio;
    SensorData_t recvSensor;

    while (1) {
        // 1. 接收int消息,同时获取消息优先级
        osStatus_t ret = osMessageQueueGet(g_intQueue, &recvInt, &msgPrio, osWaitForever);
        if (ret == osOK) {
            printf("接收消息: %d, 优先级: %d\n", recvInt, msgPrio);

            // 处理紧急消息
            if (recvInt == 0xFF) {
                printf("处理紧急事件!\n");
                // 紧急事件处理逻辑
            }
        }

        // 2. 接收结构体消息(不关心优先级,传NULL)
        osMessageQueueGet(g_structQueue, &recvSensor, NULL, osWaitForever);
        printf("传感器数据: ID=%d, 值=%.1f, 时间戳=%lu\n",
               recvSensor.sensorId, recvSensor.value, recvSensor.timestamp);
    }
}
4.2 osMessageQueuePeek() - 查看但不删除消息

功能:查看队列头部消息,但不将其从队列中移除 原型:与osMessageQueueGet()完全相同

用例

void PeekTask(void *argument) {
    int data;
    while (1) {
        // 查看队列头部消息,不删除
        if (osMessageQueuePeek(g_intQueue, &data, NULL, 100) == osOK) {
            if (data == 0xFF) {
                // 是紧急消息,立即接收处理
                osMessageQueueGet(g_intQueue, &data, NULL, 0);
                printf("紧急处理: %d\n", data);
            } else {
                // 普通消息,稍后处理
                osDelay(500);
            }
        }
    }
}

5. 队列状态查询函数

函数 功能 原型
osMessageQueueGetCount() 查询队列中当前消息数 uint32_t osMessageQueueGetCount(osMessageQueueId_t mq_id)
osMessageQueueGetSpace() 查询队列剩余可用空间 uint32_t osMessageQueueGetSpace(osMessageQueueId_t mq_id)
osMessageQueueGetCapacity() 查询队列最大容量 uint32_t osMessageQueueGetCapacity(osMessageQueueId_t mq_id)
osMessageQueueGetMsgSize() 查询单个消息大小 uint32_t osMessageQueueGetMsgSize(osMessageQueueId_t mq_id)

用例:非阻塞接收

// 先检查是否有消息,再非阻塞接收
if (osMessageQueueGetCount(g_intQueue) > 0) {
    int data;
    osMessageQueueGet(g_intQueue, &data, NULL, 0); // 立即返回,不阻塞
    printf("非阻塞接收: %d\n", data);
}

二、高级用法 1:队列传递指针

当需要传递大数据块(如图片、音频、大结构体)时,使用值传递会导致大量内存拷贝,降低系统性能。此时推荐使用指针传递,只传递数据的内存地址。

⚠️ 指针传递核心注意事项

  1. 绝对不能传递栈变量指针:栈内存会在函数返回时被释放,接收方会访问到无效内存
  2. 内存所有权转移:发送方发送指针后,就不能再修改该内存,直到接收方处理完成
  3. 避免内存泄漏:动态分配的内存必须在接收方处理完成后释放
  4. 推荐方案:使用静态内存池循环分配,最安全高效

方案 1:静态内存池指针传递(推荐)

预先分配一块静态内存作为内存池,生产者从池中获取空闲内存,填充数据后发送指针,消费者处理完成后将内存归还池中。

完整用例

#define MEM_POOL_SIZE 5  // 内存池大小
typedef struct {
    uint8_t data[128];    // 128字节大数据块
    uint32_t len;
} BigData_t;

// 静态内存池和空闲队列
static BigData_t s_memPool[MEM_POOL_SIZE];
osMessageQueueId_t g_freeBlockQueue; // 空闲内存块队列

// 初始化内存池
void MemPool_Init(void) {
    g_freeBlockQueue = osMessageQueueNew(MEM_POOL_SIZE, sizeof(BigData_t*), NULL);
    
    // 将所有内存块加入空闲队列
    for (int i = 0; i < MEM_POOL_SIZE; i++) {
        BigData_t *ptr = &s_memPool[i];
        osMessageQueuePut(g_freeBlockQueue, &ptr, 0, osWaitForever);
    }
}

// 生产者任务:获取空闲内存,填充数据,发送指针
void BigDataProducerTask(void *argument) {
    BigData_t *dataPtr;
    uint32_t counter = 0;

    while (1) {
        // 1. 从空闲队列获取一个内存块
        osMessageQueueGet(g_freeBlockQueue, &dataPtr, NULL, osWaitForever);

        // 2. 填充数据
        dataPtr->len = sprintf((char*)dataPtr->data, "大数据块_%lu", counter++);
        printf("生产数据: %s\n", dataPtr->data);

        // 3. 发送指针到数据队列
        osMessageQueuePut(g_bigDataQueue, &dataPtr, 0, osWaitForever);

        osDelay(500);
    }
}

// 消费者任务:接收指针,处理数据,归还内存
void BigDataConsumerTask(void *argument) {
    BigData_t *dataPtr;

    while (1) {
        // 1. 接收数据指针
        osMessageQueueGet(g_bigDataQueue, &dataPtr, NULL, osWaitForever);

        // 2. 处理数据
        printf("处理数据: %s, 长度: %lu\n", dataPtr->data, dataPtr->len);

        // 3. 归还内存块到空闲队列
        osMessageQueuePut(g_freeBlockQueue, &dataPtr, 0, osWaitForever);
    }
}

方案 2:动态内存指针传递

使用pvPortMalloc()动态分配内存,发送指针,消费者处理完成后用vPortFree()释放。

用例

// 生产者
void DynamicProducerTask(void *argument) {
    while (1) {
        // 动态分配内存
        BigData_t *dataPtr = pvPortMalloc(sizeof(BigData_t));
        if (dataPtr == NULL) {
            printf("内存分配失败!\n");
            osDelay(100);
            continue;
        }

        // 填充数据
        sprintf((char*)dataPtr->data, "动态数据");
        dataPtr->len = strlen((char*)dataPtr->data);

        // 发送指针
        osMessageQueuePut(g_bigDataQueue, &dataPtr, 0, osWaitForever);

        osDelay(1000);
    }
}

// 消费者
void DynamicConsumerTask(void *argument) {
    BigData_t *dataPtr;
    while (1) {
        osMessageQueueGet(g_bigDataQueue, &dataPtr, NULL, osWaitForever);
        
        // 处理数据
        printf("处理动态数据: %s\n", dataPtr->data);
        
        // 释放内存
        vPortFree(dataPtr);
    }
}

注意:动态内存分配可能产生内存碎片,长时间运行可能导致分配失败,优先使用静态内存池方案。

三、高级用法 2:队列集(Message Queue Set)

队列集用于一个任务同时等待多个队列中的任意一个有消息的场景。例如:一个任务需要同时处理按键事件、传感器数据和串口命令。

队列集核心函数

函数 功能
osMessageQueueSetNew() 创建队列集
osMessageQueueSetAdd() 将队列添加到队列集
osMessageQueueSetRemove() 从队列集移除队列
osMessageQueueSetWait() 等待队列集中任意队列有消息

完整用例:多队列统一等待

// 三个独立的队列
osMessageQueueId_t g_buttonQueue;   // 按键事件队列
osMessageQueueId_t g_sensorQueue;   // 传感器数据队列
osMessageQueueId_t g_cmdQueue;      // 串口命令队列

// 队列集ID
osMessageQueueSetId_t g_queueSet;

void QueueSet_Init(void) {
    // 1. 创建三个业务队列
    g_buttonQueue = osMessageQueueNew(5, sizeof(uint8_t), NULL);
    g_sensorQueue = osMessageQueueNew(10, sizeof(SensorData_t), NULL);
    g_cmdQueue = osMessageQueueNew(5, sizeof(uint32_t), NULL);

    // 2. 创建队列集,最大事件数=所有队列长度之和
    uint32_t maxEvents = osMessageQueueGetCapacity(g_buttonQueue) +
                         osMessageQueueGetCapacity(g_sensorQueue) +
                         osMessageQueueGetCapacity(g_cmdQueue);
    g_queueSet = osMessageQueueSetNew(maxEvents, NULL);

    // 3. 将所有队列添加到队列集
    osMessageQueueSetAdd(g_queueSet, g_buttonQueue, 0);
    osMessageQueueSetAdd(g_queueSet, g_sensorQueue, 0);
    osMessageQueueSetAdd(g_queueSet, g_cmdQueue, 0);
}

// 统一处理任务:等待队列集,处理所有类型的消息
void UnifiedProcessTask(void *argument) {
    osMessageQueueId_t activeQueue;
    uint8_t buttonEvent;
    SensorData_t sensorData;
    uint32_t cmd;

    while (1) {
        // 1. 等待队列集中任意队列有消息,永久等待
        activeQueue = osMessageQueueSetWait(g_queueSet, osWaitForever, NULL);

        // 2. 根据返回的队列ID,处理对应消息
        if (activeQueue == g_buttonQueue) {
            // 处理按键事件
            osMessageQueueGet(g_buttonQueue, &buttonEvent, NULL, 0);
            printf("按键事件: %d\n", buttonEvent);
        }
        else if (activeQueue == g_sensorQueue) {
            // 处理传感器数据
            osMessageQueueGet(g_sensorQueue, &sensorData, NULL, 0);
            printf("传感器数据: %.1f\n", sensorData.value);
        }
        else if (activeQueue == g_cmdQueue) {
            // 处理串口命令
            osMessageQueueGet(g_cmdQueue, &cmd, NULL, 0);
            printf("收到命令: %lu\n", cmd);
        }
    }
}

队列集重要注意事项

  1. 队列集大小:必须≥所有加入队列的长度之和,否则可能丢失事件
  2. 不能嵌套:一个队列不能同时加入多个队列集
  3. 中断限制:不能在中断中调用osMessageQueueSetWait()
  4. 消息处理osMessageQueueSetWait()只返回有消息的队列 ID,必须手动调用osMessageQueueGet()接收消息

四、CMSIS-RTOS v2 队列使用最佳实践

  1. 优先使用静态创建:避免内存碎片和创建失败,适合资源受限的嵌入式系统
  2. 消息优先级合理使用:仅对真正紧急的消息使用高优先级,避免优先级反转
  3. 大数据用指针传递:配合静态内存池,既高效又安全
  4. 多队列用队列集:避免创建多个等待任务,简化系统设计
  5. 中断中仅使用 FromISR 函数:绝对不能在中断中调用普通任务级函数
  6. 错误处理:所有 RTOS 函数都要检查返回值,特别是内存分配和队列操作
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐