STM32CubeMX——FREERTOS学习:消息队列Queue

2023-05-16

什么是队列

队列,也叫消息队列,就是把消息一条一条的排个队。

比如创建了一个消息队列,这个消息队列可以存10条消息。任务A可以往里存消息,任务B也可以往里存。这个存的消息是要讲先来后到的,存满了10条消息就不能存了。假设任务C从里面去消息,也是一条一条的取,按照先后顺序取,先存的先取,后存的后取。信息取一个,队列里面就少一个,取10个之后,消息队列就空了。

往队列里面存消息,如果队列已经满了,可以一直等着,直到队列里面有空位置,也可以等一会,没空位离开。

从队列里面取消息,如果队列已经空了,可以一直等着,直到队列里面有消息再取,也可以等一会,没消息就离开。

跟队列相关的几个函数

//1、创建消息队列
osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id);

//2、往消息队列里面写消息
osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec);

//3、从消息队列中获取消息,把消息拿走
osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec);

//4、从消息队列中获取一个消息,但是不拿走,只是看一眼
osEvent osMessagePeek (osMessageQId queue_id, uint32_t millisec);

//5、获取消息队列中的消息数量
uint32_t osMessageWaiting(osMessageQId queue_id);

//6、获取消息队列还剩多少空间
uint32_t osMessageAvailableSpace(osMessageQId queue_id);

//7、删除消息队列
osStatus osMessageDelete (osMessageQId queue_id)

   

1、创建消息队列

/**
* @brief 创建并初始化一个消息队列
* @参数 queue_def     所定义的队列\ref osMessageQ.
* @参数  thread_id    线程ID(一般也用不上就用NULL)
* @返回值 返回消息队列的句柄,也就是消息队列的ID 
*/
osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id)
{
  (void) thread_id;
  
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
     //如果同时支持动态创建和静态创建
  if ((queue_def->buffer != NULL) && (queue_def->controlblock != NULL)) {
    return xQueueCreateStatic(queue_def->queue_sz, queue_def->item_sz, queue_def->buffer, queue_def->controlblock);
  }
  else {
    return xQueueCreate(queue_def->queue_sz, queue_def->item_sz);
  }
#elif ( configSUPPORT_STATIC_ALLOCATION == 1 )
//如果只支持静态创建
  return xQueueCreateStatic(queue_def->queue_sz, queue_def->item_sz, queue_def->buffer, queue_def->controlblock);
#else  
//如果只支持动态创建
  return xQueueCreate(queue_def->queue_sz, queue_def->item_sz);
#endif
}

osMessageCreate函数只不过是CMSIS重新封装了一下而已,实质上还是调用FreeRTOS的原生函数:xQueueCreateStatic()和xQueueCreate()。

消息队列的要素:

      uxQueueLength,队列的长度,就是有多少个数据的意思

      uxItemSize,每个数据单元的大小

下面创建一个队列深度为16,每个消息单元为uint8_t的消息队列

//定义消息队列的ID
osMessageQId myQueue01Handle;

//定义消息队列的名称,大小,类型
osMessageQDef(myQueue01, 16, uint8_t);

//创建消息队列
myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);

2、往消息队列中写消息

**
* @简介 往消息队列中写消息
* @参数  queue_id  消息队列的ID.
* @参数  info      消息内容,发送的消息需要强制转化为uint32_t类型
* @参数  millisec  超时时间.
* @返回值 状态信息,是否写入成功
*/
osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)
{
  portBASE_TYPE taskWoken = pdFALSE;
  TickType_t ticks;
  
  ticks = millisec / portTICK_PERIOD_MS; //超时时间转换为tick单位
  if (ticks == 0) {
    ticks = 1;
  }
  
  //Cortex-M3支持两种操作模式handler模式和thread模式,
  if (inHandlerMode()) { //handler模式就用中断的方式发送消息
    if (xQueueSendFromISR(queue_id, &info, &taskWoken) != pdTRUE) {
      return osErrorOS;
    }
    portEND_SWITCHING_ISR(taskWoken);
  }
  else { //thread模式
    if (xQueueSend(queue_id, &info, ticks) != pdTRUE) {
      return osErrorOS;
    }
  }
  
  return osOK;
}

往队列中写消息函数osMessagePut(),是CMSIS重新封装了一下,本质也是调用xQueueSendFromISR()或者xQueueSend()。函数内部自动判断是在中断状态还是在正常状态,方便用户使用。

往消息队列中写个0x08进去,超时时间1ms;

osMessagePut(myQueue01Handle,0x08,1);

3、从消息队列中获取消息

/**
* @brief 从消息队列中获取一个消息,没消息就等消息
* @param  queue_id  消息队列的ID
* @param  millisec  超时时间
* @retval event 返回event
*/
osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec)
{
  portBASE_TYPE taskWoken;
  TickType_t ticks;
  osEvent event;
  
  event.def.message_id = queue_id;
  event.value.v = 0;
  
  if (queue_id == NULL) {  //找不到消息队列,那就报错
    event.status = osErrorParameter;
    return event;
  }
  
  taskWoken = pdFALSE;
  
  ticks = 0;
  if (millisec == osWaitForever) { //超时时间设置为portMAX_DELAY
    ticks = portMAX_DELAY;
  }
  else if (millisec != 0) {
    ticks = millisec / portTICK_PERIOD_MS; //超时时间转化为TICK单位
    if (ticks == 0) {
      ticks = 1;
    }
  }
  
  if (inHandlerMode()) { //中断里面获得消息
    if (xQueueReceiveFromISR(queue_id, &event.value.v, &taskWoken) == pdTRUE) {
      /* We have mail */
      event.status = osEventMessage;
    }
    else { 
      event.status = osOK;
    }
    portEND_SWITCHING_ISR(taskWoken);
  }
  else {//线程模式获得消息,把消息存到&event.value.v中
    if (xQueueReceive(queue_id, &event.value.v, ticks) == pdTRUE) {
      /* We have mail */
      event.status = osEventMessage;
    }
    else {
      event.status = (ticks == 0) ? osOK : osEventTimeout;
    }
  }
  
  return event;
}

event结构体

typedef struct  {
  osStatus                 status;     ///< status code: event or error information
  union  {
    uint32_t                    v;     ///< message as 32-bit value
    void                       *p;     ///< message or mail as void pointer
    int32_t               signals;     ///< signal flags
  } value;                             ///< event value
  union  {
    osMailQId             mail_id;     ///< mail id obtained by \ref osMailCreate
    osMessageQId       message_id;     ///< message id obtained by \ref osMessageCreate
  } def;                               ///< event definition
} osEvent;

event结构体主要包含3个内容:

1、状态osStatus:

        如果什么都没发生那就是osOK

        如果有信息发过来,那就是osEventMessage          

        如果是邮箱发过来,那就是osEventMail 

        。。。。。。

2、值value:

        就拿osEventMessage来说,收到的值可以是一个32位的值,也可以是一个指针

3、定义:还没用到,用到再解释

osMessageGet()也是CMSIS封装的函数,本质还是调用xQueueReceiveFromISR()或者xQueueReceive(),封装之后使用更方便,函数内部可以判断是在中断中还是在任务中。

 消息队列发送与接收实验(传递一个数据)

知道了消息队列的发送和接收函数就可以做一些简单的实验。建两个任务,如果的创建这里不解释,请参考任务创建章节。一个任务往队列中写消息,一个任务从队列中读消息。

//Task02用来往队列中写消息
//按键按下再松开就往队列中发送数据0x08
void StartTask02(void const * argument)
{

  for(;;)
  {

    if(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET)
    {
      HAL_Delay(20);
      while(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET);
      osMessagePut(myQueue01Handle,0x08,1);
    }
    osDelay(1);
  }

}


//Task03用来从队列中读消息
//消息内容如果是8的话就发送一些内容出去
//读队列任务也不添加阻塞
void StartTask03(void const * argument)
{

  osEvent QueueEvent;
  uint8_t Tx_Buffer[] = "FreeRTOS queue test\r\n";

  for(;;)
  {
  
    QueueEvent = osMessageGet (myQueue01Handle, osWaitForever);
    
    if((uint8_t)QueueEvent.value.v == 8)
    {
      HAL_UART_Transmit(&huart1,Tx_Buffer,sizeof(Tx_Buffer),10);
    }
    osDelay(1);
    
  }
}

4、瞥一眼消息队列中的消息,但是不拿走

跟osMessageGet()函数的用法差不多,只是不把队列的消息拿走

osEvent osMessagePeek (osMessageQId queue_id, uint32_t millisec)
{
  TickType_t ticks;
  osEvent event;
  
  event.def.message_id = queue_id;
  
  if (queue_id == NULL) {
    event.status = osErrorParameter;
    return event;
  }
  
  ticks = 0;
  if (millisec == osWaitForever) {
    ticks = portMAX_DELAY;
  }
  else if (millisec != 0) {
    ticks = millisec / portTICK_PERIOD_MS;
    if (ticks == 0) {
      ticks = 1;
    }
  }
  
  if (xQueuePeek(queue_id, &event.value.v, ticks) == pdTRUE) 
  {
    /* We have mail */
    event.status = osEventMessage;
  }
  else 
  {
    event.status = (ticks == 0) ? osOK : osEventTimeout;
  }
  
  return event;
}

5、统计消息队列中消息的个数

/**
* @brief  Get the number of messaged stored in a queue.
* @param  queue_id  message queue ID obtained with \ref osMessageCreate.
* @retval number of messages stored in a queue.
*/
uint32_t osMessageWaiting(osMessageQId queue_id)
{
  if (inHandlerMode()) {
    return uxQueueMessagesWaitingFromISR(queue_id);
  }
  else
  {
    return uxQueueMessagesWaiting(queue_id);
  }
}

用法

//定义一个变量
uint8_t n;

//读消息队列里面消息的数量
n = osMessageWaiting(myQueue01Handle);

6、统计消息队列的剩余空间

/**
* @brief  Get the available space in a message queue.
* @param  queue_id  message queue ID obtained with \ref osMessageCreate.
* @retval available space in a message queue.
*/
uint32_t osMessageAvailableSpace(osMessageQId queue_id)  
{
  return uxQueueSpacesAvailable(queue_id);
}

消息队列发送与接收实验(传递一个指针)

很多时候消息不仅仅是一个数据,也可能是一个数组或者结构体,因此传输这类消息的时候就可以用到指针的收发。

//osEvent.value.*p

typedef struct  {
  osStatus                 status;     ///< status code: event or error information
  union  {
    uint32_t                    v;     ///< message as 32-bit value
    void                       *p;     ///< message or mail as void pointer
    int32_t               signals;     ///< signal flags
  } value;                             ///< event value
  union  {
    osMailQId             mail_id;     ///< mail id obtained by \ref osMailCreate
    osMessageQId       message_id;     ///< message id obtained by \ref osMessageCreate
  } def;                               ///< event definition
} osEvent;

消息队列常见的使用方式是多个任务产生消息,一个任务来接收消息。因此消息的来源需要标记一下,也就是定一个消息ID,可以用枚举的方式定义消息ID,如下所示:

typedef enum{
  SendTask1,
  SendTask2,
} ID_t;

最终还是要发消息出去的,也就是数据,因此可以把ID和数据封装在一个结构体里面,如下所示

typedef struct{
  ID_t eDataID;  //任务ID
  uint8_t data1;  //数据1
  uint32_t data2; //数据2
  uint32_t data3; //数据3
} DataGroup;

在task1中,按下按键发送一条消息给消息队列

void StartTask01(void const * argument)
{
   //准备的数据发到消息队列
  DataGroup myDataGroup1_t = {SendTask1, 1, 2, 3}; 

  for(;;)
  {
    if(HAL_GPIO_ReadPin(KEY_2_GPIO_Port,KEY_2_Pin) == GPIO_PIN_RESET)
    {
      HAL_Delay(20);
      while(HAL_GPIO_ReadPin(KEY_2_GPIO_Port,KEY_2_Pin) == GPIO_PIN_RESET);
       
        //按键按下再松开就发送消息到消息队列
      if(osMessagePut(myQueue01Handle,(uint32_t)&myDataGroup1_t,1) != osOK)
      {
        printf("send message fail! \r\n");
      }else{
        osThreadSuspendAll();
        printf("send message sucess! \r\n");
        osThreadResumeAll();
      }
    }
  
  }
}

在task2中,也按下按键发送一条消息给消息队列

void StartTask02(void const * argument)
{
  DataGroup myDataGroup2_t = {SendTask2,10, 20, 30}; 
  for(;;)
  {
    if(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET)
    {
      HAL_Delay(20);
      while(HAL_GPIO_ReadPin(KEY_1_GPIO_Port,KEY_1_Pin) == GPIO_PIN_RESET);
      if(osMessagePut(myQueue01Handle,(uint32_t)&myDataGroup2_t,1) !=osOK)
      {
        //send failed
      } else{
        osThreadSuspendAll();
        printf("send message sucess! \r\n");
        osThreadResumeAll();       
      }
    }    
    osDelay(1);
  }
}

在task3中,从消息队列中读消息

void StartTask03(void const * argument)
{

  osEvent QueueEvent;
  DataGroup *myDataGroup_r;
  uint8_t n;

  for(;;)
  {
    n = osMessageWaiting(myQueue01Handle);

    QueueEvent = osMessageGet(myQueue01Handle,osWaitForever);
    if(QueueEvent.status == osEventMessage)
    {
      myDataGroup_r = QueueEvent.value.p;
      
      printf("message count = %d\r\n",n);
      printf("ID = %d \r\n",myDataGroup_r->eDataID);
      printf("1stdata = %d\r\n",myDataGroup_r->data1);
      printf("2nddata = %d\r\n",myDataGroup_r->data2);
      printf("3rddata = %d\r\n",myDataGroup_r->data3);
      
      if(myDataGroup_r->eDataID == SendTask1)
      {
        printf("data form task01");
      } else if(myDataGroup_r->eDataID == SendTask2){
        printf("data form task02");
      }
    } 
    osDelay(1);
  }
}

总结:

使用CMSIS封装的函数,跟原版的FreeRTOS函数还是有不小的差异,大家可以根据兴趣来作选择,不管怎样,还是要多阅读函数的实现源码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32CubeMX——FREERTOS学习:消息队列Queue 的相关文章

  • 并行处理队列的好策略是什么?

    我正在编写一个程序 需要递归搜索文件夹结构 并且希望与多个线程并行执行此操作 我已经编写了相当简单的同步方法 最初将根目录添加到队列中 然后将目录出队 对其子目录进行排队等 直到队列为空 我会用一个ConcurrentQueue
  • 如何将列表转换为队列来实现先进先出

    考虑 public List
  • Java 中的列表、队列和集合

    列表 队列和集合有什么区别 简单来说 A list是对象的有序列表 其中同一对象很可能出现多次 例如 1 7 1 3 1 1 1 5 谈论列表中的 第三个元素 是有意义的 您可以在列表中的任意位置添加元素 更改列表中的任意位置的元素或从列表
  • d3.js v5 - Promise.all 替换 d3.queue

    我已经使用 d3 js v4 一段时间了 我了解到 Mike Bostock 已将 v5 版本中的 d3 queue 替换为 Promise 原生 JavaScript 对象 我想与您核实一下我编写的这段代码是否正确地 异步 这些 URL
  • C++ freeRTOS任务,非静态成员函数的无效使用

    哪里有问题 void MyClass task void pvParameter while 1 this gt update void MyClass startTask xTaskCreate this gt task Task 204
  • 帮助尝试理解圆形数组中的模运算

    我有一个小问题试图弄清楚模运算是如何计算的 我正在建立一个队列 所以我有一个圆形数组 我无法弄清楚这个模运算是如何工作的 给定 q 一个 5 个元素长度的字符数组 MAX 常量给出数组的最大长度 5 rare 是一个 int 代表数组 q
  • ConcurrentQueue 保存对象的引用或值? “内存不足”异常

    排队到 ConcurrentQueue 的对象是被复制到队列还是仅复制到它们的引用 我不明白任何场景 解释 我这样定义了一个 ConcurrentQueue BufferElement is a class I created privat
  • Node.js 公牛队列中的作业陷入“等待”状态

    我有一堆工作在公牛队列中 其中一个被卡住了 1 个多小时 通常需要大约 2 分钟才能运行 但没有失败 我无法使用我使用的 bull arena UI 将作业从活动状态中删除 因此我删除了 Redis 中活动作业的密钥 这消除了卡住的活动作业
  • Node.js 中的作业队列

    我正在node js 中寻找一个可以由php 调用的作业队列管理器 这是一个需要发送电子邮件 创建 pdf 文件等的 Web 应用程序 我想对这些应用程序执行异步 php 进程 流程示例 用户请求 php 页面 Php调用作业队列管理器并添
  • 队列上的 IEnumerable 迭代器是否应该使项目出列

    我创建了一个自定义通用队列 它实现了通用 IQueue 接口 该接口使用 System Collections Generic 命名空间中的通用队列作为私有内部队列 示例已清除不相关的代码 public interface IQueue
  • 需要帮助 Discord 机器人队列

    我一直在尝试为不和谐机器人和我的 gt q命令基本上工作为join play queue同时 问题是它只能同时对 2 首歌曲进行排队 所以我需要帮助使其对多首歌曲进行排队 queues check queue def check queue
  • 使用 Celery 创建动态队列

    这是我的场景 当用户登录我的网站时 我会为给定用户排队一堆任务 通常每个任务需要 100 毫秒 每个用户有 100 多个任务 这些任务排队到默认的 Celery 队列中 并且我有数百个工作线程正在运行 当任务在后端完成时 我使用 webso
  • 为什么Cocoa里没有队列?

    我最近发现 Cocoa 中没有内置队列 在本例中为 Touch 为什么不 队列是计算机编程中最基本的数据结构之一 我看到有人建议使用NSMutableArray 但这对于弹出 出列来说效率极低 因为它需要删除索引 0 处的对象 这会将所有元
  • 小型 ARM 微控制器的 RTOS 内核之间的可量化差异 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 有许多不同的 RTOS 可用于微控制器 我专门寻找支持 ARM Cortex M 处理器的 RTOS 另外 我对闭源解决方案不感兴趣 试图从网站
  • NodeJS 推送队列,由 Laravel Worker 消耗

    我正在尝试使用节点应用程序发送到 SQS 的消息 因此 推送 操作由服务器 A 上的 Node App 执行 监听 操作由服务器 B 上的 Laravel App 执行 我的问题 我不知道如何格式化要使用的有效负载php artisan q
  • RabbitMQ 上的 Nack 和拒绝

    我想处理消费者从队列中获取的不成功的消息并将它们重新排队 想象一下我有这样的情况 P gt foo bar baz gt C 其中 foo bar 和 baz 是消息 如果消费者读到baz但出了问题 我可以使用basic reject or
  • 使用java工具的类似Sidekiq的队列?

    我想要一个工作队列 其行为几乎与 ruby 的 sidekiq 完全相同 它不need使用 Redis 但它可以 我只是不能使用 ruby 甚至不能使用 Jruby 基本上 我希望能够创建使用某些参数运行的作业 并且工作池执行作业 工作人员
  • 将非泛型类扩展为泛型类

    org apache commons collections buffer 包中的 Java 类 CircularFifoBuffer 是非泛型的 可以存储任何类的对象 我想创建一个通用版本 它只能保存类 T 的对象 我的第一个想法是扩展
  • java中队列的实现

    在 Java 中实现队列是一个非常常见的面试问题 我在网上冲浪 看到了许多实现 他们做了一些奇特的事情 比如实现队列接口和编写自己的addLast and removeFirst 方法 我的问题是我不能使用LinkedList 类并使用其预
  • 为什么 std::queue 使用 std::dequeue 作为底层默认容器?

    继续阅读cplusplus com http www cplusplus com reference queue queue std queue实现如下 队列被实现为容器适配器 这些类 使用特定容器类的封装对象作为其 底层容器 提供一组特定

随机推荐