8. 消息队列

同学们,回想一下,在我们裸机的编程中,我们是怎么样用全局的一个数组的呢?

8.1. 消息队列的基本概念

队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息, 实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息,当队列中的消息是空时,读取消息的任务将被阻塞, 用户还可以指定阻塞的任务时间,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。 当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据, 任务也会自动从阻塞态转为就绪态。消息队列是一种异步的通信方式。

通过消息队列服务,任务或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个任务可以从消息队列中获得消息。 当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的消息, 即先进先出原则(FIFO),但是也支持后进先出原则(LIFO)。

FreeRTOS中使用队列数据结构实现任务异步通信工作,具有如下特性:

  • 消息支持先进先出方式排队,支持异步读写工作方式。

  • 读写队列均支持超时机制。

  • 消息支持后进先出方式排队,往队首发送消息(LIFO)。

  • 可以允许不同长度(不超过队列节点最大值)的任意类型消息。

  • 一个任务能够从任意一个消息队列接收和发送消息。

  • 多个任务能够从同一个消息队列接收和发送消息。

  • 当队列使用结束后,可以通过删除队列函数进行删除。

8.2. 消息队列的运作机制

创建消息队列时FreeRTOS会先给消息队列分配一块内存空间,这块内存的大小等于消息队列控制块大小加上 (单个消息空间大小与消息队列长度的乘积),接着再初始化消息队列,此时消息队列为空。FreeRTOS的消息队列控制块由多个元素组成, 当消息队列被创建时,系统会为控制块分配对应的内存空间, 用于保存消息队列的一些信息如消息的存储位置,头指针pcHead、尾指针pcTail、消息大小uxItemSize以及队列长度uxLength等。 同时每个消息队列都与消息空间在同一段连续的内存空间中,在创建成功的时候,这些内存就被占用了,只有删除了消息队列的时候, 这段内存才会被释放掉,创建成功的时候就已经分配好每个消息空间与消息队列的容量,无法更改, 每个消息空间可以存放不大于消息大小uxItemSize的任意类型的数据,所有消息队列中的消息空间总数即是消息队列的长度, 这个长度可在消息队列创建时指定。

任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队, FreeRTOS会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞,在这段时间中, 如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。当其他任务从其等待的队列中读取入了数据(队列未满), 该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中还不允许入队, 任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或者中断程序会收到一个错误码。

发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,发送的位置是消息队列队头而非队尾, 这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。

当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空, 该任务将保持阻塞状态以等待队列数据有效。当其他任务或中断服务程序往其等待的队列中写入了数据, 该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据, 任务也会自动从阻塞态转移为就绪态。

当消息队列不再被使用时,应该删除它以释放系统资源,一旦操作完成,消息队列将被永久性的删除。

消息队列的运作过程具体见下图。

消息队列运作过程

图15‑1消息队列运作过程

8.3. 消息队列的阻塞机制

我们使用的消息队列一般不是属于某个任务的队列,在很多时候,我们创建的队列,是每个任务都可以去对他进行读写操作的, 但是为了保护每个任务对它进行读写操作的过程,我们必须有阻塞机制,在某个任务对它读写操作的时候, 必须保证该任务能正常完成读写操作,而不受后来的任务干扰,凡事都有先来后到嘛!

那么,如何实现这个先来后到的机制呢,很简单,因为FreeRTOS已经为我们做好了,我们直接使用就好了,每个对消息队列读写的函数, 都有这种机制,我称之为阻塞机制。假设有一个任务A对某个队列进行读操作的时候(也就是我们所说的出队),发现它没有消息, 那么此时任务A有3个选择:第一个选择,任务A扭头就走,既然队列没有消息,那我也不等了,干其他事情去,这样子任务A不会进入阻塞态; 第二个选择,任务A还是在这里等等吧,可能过一会队列就有消息,此时任务A会进入阻塞状态,在等待着消息的道来, 而任务A的等待时间就由我们自己定义,比如设置1000个系统时钟节拍tick的等待,在这1000个tick到来之前任务A都是处于阻塞态, 当阻塞的这段时间任务A等到了队列的消息,那么任务A就会从阻塞态变成就绪态,如果此时任务A比当前运行的任务优先级还高, 那么,任务A就会得到消息并且运行;假如1000个tick都过去了,队列还没消息,那任务A就不等了,从阻塞态中唤醒, 返回一个没等到消息的错误代码,然后继续执行任务A的其他代码;第三个选择,任务A死等,不等到消息就不走了, 这样子任务A就会进入阻塞态,直到完成读取队列的消息。

而在发送消息操作的时候,为了保护数据,当且仅当队列允许入队的时候,发送者才能成功发送消息; 队列中无可用消息空间时,说明消息队列已满,此时,系统会根据用户指定的阻塞超时时间将任务阻塞, 在指定的超时时间内如果还不能完成入队操作,发送消息的任务或者中断服务程序会收到一个错误码, 然后解除阻塞状态;当然,只有在任务中发送消息才允许进行阻塞状态, 而在中断中发送消息不允许带有阻塞机制的,需要调用在中断中发送消息的API函数接口, 因为发送消息的上下文环境是在中断中,不允许有阻塞的情况。

假如有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权。

8.4. 消息队列的应用场景

消息队列可以应用于发送不定长消息的场合,包括任务与任务间的消息交换,队列是FreeRTOS主要的任务间通信方式, 可以在任务与任务间、中断和任务间传送信息,发送到队列的消息是通过拷贝方式实现的, 这意味着队列存储的数据是原数据,而不是原数据的引用。

8.5. 消息队列控制块

8.5.1. osMessageQueueAttr_t结构体

CMSIS-RTOS使用osMessageQueueAttr_t结构体来描述消息队列,如下所示

cmsis_os2.h
1
2
3
4
5
6
7
8
 typedef struct {
     const char                   *name;   //消息队列名
     uint32_t                 attr_bits;   //FreeRTOS未使用到
     void                      *cb_mem;    //控制块(静态创建)地址
     uint32_t                   cb_size;   //控制块(静态创建)大小
     void                      *mq_mem;    //消息队列数据存储地址
     uint32_t                   mq_size;   //消息队列数据存储大小
 } osMessageQueueAttr_t;

其中cb_mem、cb_size、mq_mem、mq_size四个成员变量用于消息队列的静态创建, 当不对这四个成员变量进行赋值时,将使用动态内存分配的方式创建消息队列。

8.6. 消息队列常用函数讲解

使用队列模块的典型流程如下:

  • 创建消息队列。

  • 写队列操作。

  • 读队列操作。

  • 删除队列。

8.6.1. 消息队列创建函数osMessageQueueNew()

osMessageQueueNew()用于创建一个新的队列并返回可用于访问这个队列的队列句柄。 队列句柄其实就是一个指向队列数据结构类型的指针。

队列就是一个数据结构,用于任务间的数据的传递。每创建一个新的队列都需要为其分配RAM,可选择使用静态或者动态的方式分配。 其函数源码如下所示

cmsis_os2.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//创建消息队列
osMessageQueueId_t osMessageQueueNew (uint32_t msg_count, uint32_t msg_size, const osMessageQueueAttr_t *attr) {
   QueueHandle_t hQueue;
   int32_t mem;              //动态或静态内存分配标志
#if (configQUEUE_REGISTRY_SIZE > 0)
   const char *name;
#endif

   hQueue = NULL;

   //该函数不能在中断中执行,同时消息队列大小、数量不能为0
   if (!IS_IRQ() && (msg_count > 0U) && (msg_size > 0U)) {
      mem = -1;
      //判断消息队列控制块是否为空
      if (attr != NULL) {
         //当消息队列的控制块成员cb_mem、cb_size、mq_mem、mq_size均被赋正确值时。将mem赋1,在后面代码使用静态内存方式创建线程
         if ((attr->cb_mem != NULL) && (attr->cb_size >= sizeof(StaticQueue_t)) &&
            (attr->mq_mem != NULL) && (attr->mq_size >= (msg_count * msg_size))) {
            mem = 1;
         }
      }
      else {
      //当cb_mem、cb_size、mq_mem、mq_size均为空时,将mem赋0,在后面代码使用动态内存方式创建线程
         if ((attr->cb_mem == NULL) && (attr->cb_size == 0U) &&
               (attr->mq_mem == NULL) && (attr->mq_size == 0U)) {
            mem = 0;
         }
      }
   }
   else {
      mem = 0;
   }
   //使用静态内存创建消息队列
   if (mem == 1) {
      hQueue = xQueueCreateStatic (msg_count, msg_size, attr->mq_mem, attr->cb_mem);
   }
   else {
      //使用动态内存创建消息队列
      if (mem == 0) {
         hQueue = xQueueCreate (msg_count, msg_size);
      }
   }

#if (configQUEUE_REGISTRY_SIZE > 0)
   if (hQueue != NULL) {
      if (attr != NULL) {
         name = attr->name;
      } else {
         name = NULL;
      }
      //给消息队列分配名称,并将该队列加入到队列注册表中
      vQueueAddToRegistry (hQueue, name);
   }
#endif

   return ((osMessageQueueId_t)hQueue);
}

参数

  • msg_count :队列能够存储的最大消息单元数目,即队列长度

  • msg_size :队列中消息单元的大小。

  • attr :描述消息队列的结构体

返回值: 如果创建成功则返回一个队列ID,用于访问创建的队列。如果创建不成功则返回NULL

在上面代码中使用“mem”变量作为动态内存分配或者静态内存分配的判别标志,判断依据是对 osMessageQueueAttr_t结构体中的cb_mem、cb_size、mq_mem、mq_size四个成员变量进行赋值判断,当我们对这四个成员变量赋予正确 的值时,将使用静态内存分配的方式创建消息队列,当这四个成员变量为空时将使用动态内存的方式创建消息队列。

8.6.2. 消息队列删除函数osMessageQueueDelete()

osMessageQueueDelete函数是osMessageQueueNew函数的反过程, 队列删除函数是根据消息队列句柄删除的,删除之后这个消息队列的所有信息都会被系统回收清空, 而且不能再次使用这个消息队列了。其函数原型如下

cmsis_os2.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
osStatus_t osMessageQueueDelete (osMessageQueueId_t mq_id) {
   QueueHandle_t hQueue = (QueueHandle_t)mq_id;
   osStatus_t stat;

#ifndef USE_FreeRTOS_HEAP_1
   //osMessageQueueDelete函数不能在中断服务函数中被调用
   if (IS_IRQ()) {
      stat = osErrorISR;
}
//参数出错判断
   else if (hQueue == NULL) {
      stat = osErrorParameter;
   }
   else {
   #if (configQUEUE_REGISTRY_SIZE > 0)
      vQueueUnregisterQueue (hQueue);  //将消息队列从注册表中删除
   #endif

      stat = osOK;
      vQueueDelete (hQueue);                 //删除消息队列
   }
#else
   stat = osError;
#endif

   return (stat);
}

参数

  • mq_id :消息队列ID

返回值:当成功时返回osOK,失败时返回osError、osErrorISR或osErrorParameter。

8.6.3. 向消息队列发送消息函数osMessageQueuePut()

任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满或者允许覆盖入队, FreeRTOS会将消息拷贝到消息队列队尾,否则,会根据用户指定的阻塞超时时间进行阻塞, 在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。 当其它任务从其等待的队列中读取入了数据(队列未满),该任务将自动由阻塞态转为就绪态。 当任务等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态, 此时发送消息的任务或者中断程序会收到一个错误码。osMessageQueuePut函数源码如下所示

cmsis_os2.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//通过消息队列发送消息
osStatus_t osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout) {
QueueHandle_t hQueue = (QueueHandle_t)mq_id;
   osStatus_t stat;
   BaseType_t yield;

   (void)msg_prio; /* Message priority is ignored */

   stat = osOK;

   //在中断中发送消息队列
   if (IS_IRQ()) {
   //参数出错判断,在中断中并不能设置超时等待
   if ((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
      stat = osErrorParameter;
   }
   else {
      yield = pdFALSE;
      //使用xQueueSendToBackFromISR函数若导致一个任务解锁,yield为pdTRUE
      if (xQueueSendToBackFromISR (hQueue, msg_ptr, &yield) != pdTRUE) {
      stat = osErrorResource;
      } else {
      //使用xQueueSendToBackFromISR函数若导致一个较高优先级任务解锁,yield为pdTRUE,则进行一次任务调度
      portYIELD_FROM_ISR (yield);
      }
   }
   }
   //在非中断中使用此函数
   else {
      //参数出错判断
   if ((hQueue == NULL) || (msg_ptr == NULL)) {
      stat = osErrorParameter;
   }
   else {
      //xQueueSendToBack函数以FIFO方式进行发送
      if (xQueueSendToBack (hQueue, msg_ptr, (TickType_t)timeout) != pdPASS) {
      if (timeout != 0U) {
         stat = osErrorTimeout;
      } else {
         stat = osErrorResource;
      }
      }
   }
   }

   return (stat);
}

参数

  • mq_id :消息队列ID(消息队列句柄)

  • msg_ptr :发送消息的地址

  • msg_prio :发送优先级,在源码中可看到该参数被忽略并不生效。

  • timeout :线程等待时间

返回值: 如果发送数据成功返回osOK,失败返回各种失败代码。

FreeRTOS原来提供的API函数中,用户需要根据不同的运行环境,在中断和普通线程中调用不同的消息队列发送API, 而CMSIS-RTOS提供的osMessageQueuePut函数通过判断IS_IRQ()的返回值判断不同的运行环境(中断与线程)。 在中断中则是调用xQueueSendToBackFromISR函数发送消息队列信息,在线程中则是调用xQueueSendToBack函数进行发送。 FreeRTOS原来提供的消息队列函数较多功能强大,经过CMSIS-RTOS封装之后反而显得更为简洁。

8.6.4. 从消息队列读取消息函数osMessageQueueGet()

当任务试图读队列中的消息时,可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能读取到消息。 在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其他任务或中断服务程序往其等待的队列中写入了数据, 该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。

cmsis_os2.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//获取消息队列信息
osStatus_t osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout) {
   QueueHandle_t hQueue = (QueueHandle_t)mq_id;
   osStatus_t stat;
   BaseType_t yield;

   (void)msg_prio; /* Message priority is ignored */

   stat = osOK;

   //在中断中使用获取消息队列函数
   if (IS_IRQ()) {
      if ((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
         stat = osErrorParameter;
      }
      else {
         yield = pdFALSE;

         if (xQueueReceiveFromISR (hQueue, msg_ptr, &yield) != pdPASS) {
         stat = osErrorResource;
         } else {
         /*  同消息队列发送函数,当使用xQueueReceiveFromISR函数获取消息时,
          *  导致一个较高优先级任务解锁,那么yield的值为pdTRUE,则进行一次任务调度
          */
         portYIELD_FROM_ISR (yield);
         }
      }
   }
   //不在中断中使用获取消息队列函数
   else {
      if ((hQueue == NULL) || (msg_ptr == NULL)) {
         stat = osErrorParameter;
      }
      else {
         if (xQueueReceive (hQueue, msg_ptr, (TickType_t)timeout) != pdPASS) {
            if (timeout != 0U) {
               stat = osErrorTimeout;
            } else {
               stat = osErrorResource;
            }
         }
      }
   }

   return (stat);
}

参数

  • mq_id :消息队列ID(消息队列句柄)

  • msg_ptr :接收消息的地址

  • msg_prio :接收数据优先级,在源码中可看到该参数被忽略并不生效。

  • timeout :线程等待时间

返回值: 如果接收数据成功则返回osOK,失败返回各种失败代码。

与osMessageQueuePut函数类似,osMessageQueueGet是CMSIS-RTOS为FreeRTOS封装后的消息队列接收函数, 以便在不同的环境中使用。

8.7. 消息队列使用注意事项

在使用CMSIS-RTOS提供的消息队列函数的时候,需要了解以下几点:

  1. 在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数 据区域大小不小于消息大小,否则,很可能引发地址非法的错误。

  2. 无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将 消息的地址作为消息进行发送、接收。

  3. 队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同 一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读 出倒是用的比较少

8.8. 消息队列实验

消息队列实验将在STM32CubeIDE中创建了两个任务和一个消息队列,一个是发送消息任务,一个是获取消息任务,两个任务独立运行, 发送消息任务是通过检测按键的按下情况来发送消息,另一个任务是获取消息任务,在消息队列没有消息之前一直等待消息, 一旦获取到消息就把消息打印在串口调试助手里,关于外设以及STM32CubeIDE工程的创建不再赘述。FreeRTOS配置如下所示

消息队列配置

关于配置线程相关的知识点之前我们已经介绍过了 ,其配置如下

接收消息任务线程配置

消息队列配置

发送消息任务线程配置

消息队列配置

添加消息队列配置如下

消息队列配置
  • Queue Name :创建的消息队列类型变量名

  • Queue Size :消息队列能够存储的最大消息单元数目,即消息队列长度

  • Item Size :消息队列中消息单元的大小

  • Allocation :创建消息队列的方式,可选择使用静态创建还是使用动态内存分配。

  • Buffer Name : 在Allocation中选择静态创建消息队列时创建的消息队列的消息缓存数组。

  • Buffer size : 静态创建消息堆栈的消息缓存数值大小。将会根据Queue Size与Item Size自动计算得到。

  • Control Block Name :消息队列内存控制块类型变量名。

使用STM32CubeIDE配置完FreeRTOS之后便可生成工程代码。 生成与FreeRTOS相关的代码在app_freertos.c文件中。

8.8.1. app_freertos.c

本小节只讲解重点部分代码,完整代码请打开工程查看。

8.8.1.1. MX_FREERTOS_Init函数

MX_FREERTOS_Init函数主要用于创建消息队列和线程,这部分的代码由STM32CubeIDE生成,代码如下所示

app_freertos.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void MX_FREERTOS_Init(void) {

   /* creation of Test_Queue */
   Test_QueueHandle = osMessageQueueNew (4, sizeof(uint32_t), &Test_Queue_attributes);

   /* creation of ReceiveTask */
   ReceiveTaskHandle = osThreadNew(Receive_Task, NULL, &ReceiveTask_attributes);

   /* creation of SendTask */
   SendTaskHandle = osThreadNew(Send_Task, NULL, &SendTask_attributes);

}

函数内容相对简单调用osMessageQueueNew创建消息队列,调用osThreadNew创建Receive_Task、Send_Task两个线程。

8.8.1.2. Receive_Task

下面的大部分代码中主要都是由STM32CubeIDE自动生成,其中需要我们做的是在Receive_Task函数体中 填充我们想要实现的功能代码。

app_freertos.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
osThreadId_t ReceiveTaskHandle;
   const osThreadAttr_t ReceiveTask_attributes = {
   .name = "ReceiveTask",
   .priority = (osPriority_t) osPriorityNormal,
   .stack_size = 128 * 4
};

void Receive_Task(void *argument);

void Receive_Task(void *argument)
{

   osStatus_t xReturn = osOK;
   uint32_t r_queue; /* 接收消息的变量 */

   for(;;)
   {

   xReturn = osMessageQueueGet( Test_QueueHandle, /* 消息队列的句柄 */
                           &r_queue,/* 需要接受的消息内容存放地址 */
                           0,                        /* 接收优先级*/
                           osWaitForever);  /*永远等待 */
   if(osOK == xReturn)
      printf("本次接收到的数据为%d\n\n",r_queue);
   else
      printf("数据接收出错,错误代码0x%lx\n",xReturn);

   }

}

在Receive_Task中使用osMessageQueueGet函数接收消息队列中的数据,并将接收到的数据打印出来,若接收数据错误则打印错误代码。 当消息队列无数据时则永远等待数据到来(osWaitForever的值为0xFFFFFFFF)。

8.8.1.3. Send_Task

同Receive_Task线程,我们只需要往Send_Task函数中填充需要我们实现的代码即可,代码如下所示

app_freertos.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
osThreadId_t SendTaskHandle;
const osThreadAttr_t SendTask_attributes = {
   .name = "SendTask",
   .priority = (osPriority_t) osPriorityNormal,
   .stack_size = 128 * 4
};

void Send_Task(void *argument);

void Send_Task(void *argument)
{

   osStatus_t xReturn = osOK;
   uint32_t send_data1 = 1;
   uint32_t send_data2 = 2;


   for(;;)
   {
      if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON )
      {/* K1 被按下 */
         printf("发送消息send_data1!\n");
         xReturn = osMessageQueuePut( Test_QueueHandle, /* 消息队列的句柄*/
                        &send_data1,/* 发送的消息内容 */
                        0,                   /* 发送优先级*/
                        0 );        /* 等待时间 0 */

      }
      if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON )
      {/* K2 被按下*/
         printf("发送消息send_data2!\n");
         xReturn = osMessageQueuePut( Test_QueueHandle, /* 消息队列的句柄 */
                        &send_data2,/* 发送的消息内容 */
                        0,                   /* 发送优先级*/
                        0 );        /* 等待时间 0 */

      }
      osDelay(20);/* 延时20个tick */
   }
}

当按键1被按下时则向消息队列发送send_data1(代码中send_data1的值为1), 当按键2被按下时则向消息队列中发送send_data2(代码中send_data2的值为2), 若此时Receive_Task线程正在等待数据到来则会被唤醒。

8.8.1.4. main函数

main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(void)
{

   HAL_Init();               //初始化时钟

   if(IS_ENGINEERING_BOOT_MODE())
   {
      /* Configure the system clock */
      SystemClock_Config();  //时钟配置
   }


   MX_GPIO_Init();            //初始化GPIO
   MX_USART3_UART_Init(); //初始化串口

   osKernelInitialize();  //初始化内核状态
   MX_FREERTOS_Init();        //创建任务

   osKernelStart();          //启动任务调度器,

   //当成功启动任务调度器之后以下代码将不会被执行
   while (1)
   {

   }

}

8.9. 消息队列实验现象

将程序编译好,将程序下载到开发板中, 按下开发版的 KEY1 按键发送消息 1,按下 KEY2 按键发送消息 2;我们按下 KEY1试试,在串口调试助手中可以看到接收到消息 1,我们按 下 KEY2 试试,在串口调试助手中可以看到接收到消息 2,如下图所示

实验现象