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结构体来描述消息队列,如下所示
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,可选择使用静态或者动态的方式分配。 其函数源码如下所示
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函数的反过程, 队列删除函数是根据消息队列句柄删除的,删除之后这个消息队列的所有信息都会被系统回收清空, 而且不能再次使用这个消息队列了。其函数原型如下
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函数源码如下所示
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()¶
当任务试图读队列中的消息时,可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能读取到消息。 在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其他任务或中断服务程序往其等待的队列中写入了数据, 该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
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提供的消息队列函数的时候,需要了解以下几点:
在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数 据区域大小不小于消息大小,否则,很可能引发地址非法的错误。
无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将 消息的地址作为消息进行发送、接收。
队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同 一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读 出倒是用的比较少
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生成,代码如下所示
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函数体中 填充我们想要实现的功能代码。
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函数中填充需要我们实现的代码即可,代码如下所示
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函数¶
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,如下图所示