FREERTOS CMSIS-RTOS v2 队列完整指南:核心函数 + 指针传递 + 队列集
2026/5/27 22:58:39 网站建设 项目流程

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

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

1. 队列创建与删除

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

功能:从 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.2osMessageQueueNewStatic()- 静态创建队列

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

原型

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.3osMessageQueueDelete()- 删除队列

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

原型

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.1osMessageQueueGet()- 接收并删除消息

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

原型

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.2osMessageQueuePeek()- 查看但不删除消息

功能:查看队列头部消息,但不将其从队列中移除原型:与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 函数都要检查返回值,特别是内存分配和队列操作

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询