FREERTOS CMSIS-RTOS v2 队列完整指南:核心函数 + 指针传递 + 队列集
CMSIS-RTOSv2队列使用指南 摘要:本文详细介绍了ARM官方封装的CMSIS-RTOSv2实时操作系统中的队列功能。相比原生FreeRTOS,它提供了消息优先级特性(0-255级)、更规范的API接口和错误处理机制。主要内容包括:1)队列创建与删除(动态/静态两种方式);2)任务级和中断级的消息发送/接收函数;3)队列状态查询方法;4)高级用法如指针传递大数据(推荐静态内存池方案)和队列集
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:静态内存池指针传递(推荐)
预先分配一块静态内存作为内存池,生产者从池中获取空闲内存,填充数据后发送指针,消费者处理完成后将内存归还池中。
完整用例:
#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);
}
}
}
队列集重要注意事项
- 队列集大小:必须≥所有加入队列的长度之和,否则可能丢失事件
- 不能嵌套:一个队列不能同时加入多个队列集
- 中断限制:不能在中断中调用
osMessageQueueSetWait() - 消息处理:
osMessageQueueSetWait()只返回有消息的队列 ID,必须手动调用osMessageQueueGet()接收消息
四、CMSIS-RTOS v2 队列使用最佳实践
- 优先使用静态创建:避免内存碎片和创建失败,适合资源受限的嵌入式系统
- 消息优先级合理使用:仅对真正紧急的消息使用高优先级,避免优先级反转
- 大数据用指针传递:配合静态内存池,既高效又安全
- 多队列用队列集:避免创建多个等待任务,简化系统设计
- 中断中仅使用 FromISR 函数:绝对不能在中断中调用普通任务级函数
- 错误处理:所有 RTOS 函数都要检查返回值,特别是内存分配和队列操作
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)