FreeRTOS-使用消息队列集合
FreeRTOS的消息队列模块,不仅提供了通用的消息队列功能,而且还提供了消息队列集合
功能。对于需要从多个消息队列中获取数据的场景,FreeRTOS提供的消息队列集合功能,简化了等待多个消息队列有数据的代码编写方式。
关于FreeRTOS消息队列内部的一些实现细节,可以参考FreeRTOS-消息队列内部细节
普通消息队列的使用,可以参考FreeRTOS-使用消息队列
考虑在某个场景下,我们必须要创建多个消息队列,并且从这多个消息队列中获取数据。使用普通消息队列提供的API 时,我们得不停地在多个消息队列之间进行轮训,以查看哪个消息队列有数据了,但轮训方式必然效率低下。
而消息队列集合
则可以高效的处理这种问题,我们只需要将 所有需要等待数据的消息队列
,都添加到一个消息队列集合中,之后等待这个消息队列集合即可。没有数据时,等待消息的任务不占用CPU时间,一但消息队列集合
中的任何一个消息队列
接收到消息了,任务就会结束等待,并可以从消息队列集合中提取到 接收到消息的消息队列的句柄
,之后只要使用该句柄从对应的消息队列中提取实际消息即可。
FreeRTOS的消息队列集合
,其本质也是一个消息队列。不同的是,一般的消息队列中存放的是消息,而消息队列集合
中存放的是 消息队列句柄
,用来通知某个消息队列有数据了。
如下图所示,假设消息队列 queue1
和queueu2
都被“关联到”到消息队列集合queue_set
中了。之后,消息队列queue1
和queueu2
均接收到一条消息,此时,消息队列集合queue_set
中就会被推送两个消息(两个消息分别为queue1
和queueu2
的句柄),表示该消息队列集合中存在消息队列接收到消息了。我们从消息队列集合queue_set
中提取出接收到消息的 消息队列句柄,根据该句柄就可以到对应消息队列中获取消息了。
因此,消息队列集合
的使用可以总结为三步:
- 创建一个消息队列集合
- 将需要等待消息的 消息队列 均关联到创建的消息队列集合上
- 等待消息队列集合有消息。一有消息,则可以从中提取到实际接收到消息的 消息队列 的句柄。利用该句柄,再到实际消息队列中提取消息。
使用消息队列集合
,需要在工程配置文件FreeRTOSConfig.h
中添加如下配置:
1 |
创建消息队列集合的API为:
1 | QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength ); |
uxEventQueueLength:该值应该为添加到消息队列集合中的 消息队列的大小的总和。如前文图片中
queue1
和queue2
的大小均为3,则queue_set
需要设置为6。因为queue_set中保存的消息是接收到消息的那个消息队列的句柄,每收到一个消息,消息队列集合中都会被推送对应收到消息的消息队列句柄,所以为了保存所有接收事件,需要将消息队列集合的大小设置为其所包含的所有消息队列的大小之和(注意,消息队列的大小,指的是包含“消息”的数量,并不是实际所占内存大小,消息队列集合由于存的“消息”是消息队列句柄,即一个指针,因此实际消息存储所占大小应该是uxEventQueueLength*4 )。返回值:
消息队列集合
句柄,用来唯一识别创建的这个消息队列集合。如果堆内存资源不够,则返回NULL。
消息队列集合
创建成功后,需要将那些需要同时等待的消息队列“添加到”这个消息队列集合中,API为:
1 | BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, |
- xQueueOrSemaphore:要添加到消息队列集合中的消息队列句柄。(从名字可看到,信号量也可以添加到消息队列集合中,因为FreeRTOS的信号量就是使用消息队列来实现的,本文不涉及信号量。)
- xQueueSet:消息队列集合句柄,即
xQueueCreateSet
函数的返回值。
添加完成之后,使用监听API,消息队列集合中的任何一个消息队列收到消息时,我们都能收到事件通知:
1 | QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, |
- xQueueSet:消息队列集合句柄。
- xTicksToWait:监听等待的时间,应该使用
pdMS_TO_TICKS(ms)
将时间转换成内核可以识别的tick数。 - 返回值:即收到消息的消息队列句柄。为NULL表示超时了,没有收到消息。
FreeRTOS-消息队列内部细节一文中,我们介绍了消息队列的内部实现细节。上文提到,消息队列集合,实际也是一个消息队列,只是这个队列中存放的是那些接收到消息的消息队列句柄
。即消息队列集合
中的任意一个消息队列
收到消息时,内核都会推送收到消息的这个消息队列句柄
到消息队列集合中,如下图所示:
当调用xQueueAddToSet( queue1, queue_set )
将消息队列queue1
加入到消息队列集合queue_set
中,实际是让消息队列queue1
控制信息中的pxQueueSetContainer
成员指向了消息队列集合queue_set
。
之后,其它任务调用xQueueSend(queue1, &data)
向消息队列queue1
中发送数据时,函数内部实际是做了两件事:一是向消息队列queue1
中推送数据,此外,还将消息队列queue1
的句柄发送到了消息队列集合queue_set
中(消息队列集合本身也是消息队列)。因此,调用xQueueSelectFromSet
监听消息队列集合的任务,就可以在任意消息队列收到消息后收到通知,并根据通知(那个收到消息的消息队列句柄),去对应消息队列中获取消息。
我们以一个示例程序,演示消息队列集合
API 的使用。
我们创建两个发送任务sender1
和sender2
,以及两个消息队列queue1
和queue2
。sender1
向消息队列queue1
中发送消息,sender2
向消息队列queue2
中发送消息。
消息队列queue1
和queue2
,均添加在消息队列集合queue_set
中。
再创建一个接收任务receiver
,该任务监听queue_set
,一有事件,则提取出消息队列句柄,再从对应的消息队列中获取数据。
main函数代码:
1 | struct MyData { |
发送任务的代码:
1 | void sender1_task( void *pvParameters ) { |
接收任务的代码:
1 |
|
运行结果如下所示:
1 | start FreeRTOS |
注意,上文代码仅仅是为了演示消息队列集合
的使用方法。实际开发中,我们应该尽量避免使用多个消息队列(浪费资源)。一般情况下,使用一个更大的消息队列,并通过如下自定义数据类型中的type
字段来区分数据的不同发送方(或区分数据的不同处理方式),发送变长数据也可以通过传递数据指针来实现:
1 | struct MyData { |
ps:需要注意文章代码中的日志输出函数,产品代码中如果需要使用的话,需要考虑线程安全性(多任务安全性),因为中断/任务切换可能发生在另一个任务正在输出日志但还未输出完的时候,这就可能造成日志错乱
FreeRTOS交流QQ群-663806972