一、消息队列的概念
1、消息队列的基本概念
消息队列简称队列,是一种常用于任务间通信的数据结构。
如下图:消息队列可以在任务与任务间、中断与任务间传递消息,实现任务接收来自其它任务或中断的不固定长度的消息。
< 队列用于任务与任务间 >
< 队列用于任务与中断间 >
相比于裸机的全局数组,使用消息队列有如下优势:
- 消息队列具有超时机制,可以让 FreeRTOS 内核有效地管理任务
- 使用消息队列可以防止多任务的访问冲突
- 使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题,使用全局数组的话,任务得不断去监测标志位以获取数据
- 消息队列具有 FIFO 与 LIFO 储存机制,方便处理数据
2、消息队列的通信机制
消息队列是一种异步的通信方式。
任务能够从队列中读取消息,当队列中的消息为空时,读取消息的任务将被阻塞。用户可以指定阻塞的任务时间 xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过指定的阻塞时间,即使队列中没有有效数据,任务也会自动从阻塞态转为就绪态。
通过消息队列服务,任务或中断服务可以将一条或多条消息放入消息队列中。同样,一个或多个任务可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的消息,即先进先出原则 (FIFO),FreeRTOS 的队列也支持后进先出原则 (LIFO)。
3、FreeRTOS 中消息队列特性
消息支持先进先出方式排队,支持异步读写工作方式
- 读写队列均支持超时机制
- 消息支持后进先出方式排队,向队首发送消息 (LIFO)
- 可以允许不同长度 (不超过队列节点最大值) 的任意类型消息
- 一个任务能够从任意一个消息队列接收和发送消息
- 多个任务能够从同一个消息队列接收和发送消息
- 当队列使用结束后,可以通过删除队列函数进行删除
4、消息队列运作流程图
5、消息队列应用场景
消息队列可用于发送不定长消息的场合。
队列是 FreeRTOS 主要的任务间通信方式,可以在任务与任务间、中断和任务间传送信息,发送到队列的消息是通过复制方式实现的,这意味着队列存储的数据是原始数据,而不是原始数据的引用。
二、消息队列常用 API 函数
消息队列全部 API 函数:
使用消息队列的典型流程如下:
- 创建消息队列
- 发送消息队列
- 读取消息队列
- 删除消息队列
常用 API 函数如下:
- xQueueCreate()
- xQueueSend() 与 xQueueSendFromISR()
- xQueueReceive()
- vQueueDelete()
三、消息队列创建与删除
1、消息队列控制块 (句柄)
说明:句柄中的各个成员,稍微了解即可,Keil 工程中有详细注释
2、队列创建
函数原型:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, // 队列长度,即消息个数
UBaseType_t uxItemSize ); // 类目大小,即消息大小,单位字节
函数描述:
函数 xQueueCreate 用于创建消息队列
- 第 1 个参数是消息队列支持的消息个数
- 第 2 个参数是每个消息的大小,单位字节
- 返回值,如果创建成功会返回消息队列的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,无法为此消息队列提供所需的空间会返回 NULL
队列创建示意图:
应用举例:
3、队列删除
函数原型:
void vQueueDelete( QueueHandle_t xQueue); // 队列句柄
函数描述:
函数 vQueueDelete 用于删除消息队列
第 1 个参数是需要删除的消息队列句柄
说明:
消息队列删除后,系统会清空此队列的全部消息,且不能再次使用此队列。
四、任务中消息队列发送
函数原型:
BaseType_t xQueueSend( QueueHandle_t xQueue, /* 消息队列句柄 */
const void * pvItemToQueue, /* 要传递数据地址 */
TickType_t xTicksToWait /* 等待消息队列有空间的最大等待时间 */);
函数描述:
函数 xQueueSend 用于任务中消息发送。
- 第 1 个参数是消息队列句柄
- 第 2 个参数要传递数据地址,每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大 小复制到消息队列空间中
- 第 3 个参数是当消息队列已经满时,等待消息队列有空间时的最大等待时间,单位系统时钟节拍
- 返回值,如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL
使用这个函数要注意以下问题:
- FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。
- 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是 xQueueSendFromISR。
- 如果消息队列已经满且第三个参数为 0,那么此函数会立即返回。
- 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配置为 portMAX_DELAY,那么此发送函数会永久等待直到消息队列有空间可以使用。
- 消息队列还有两个函数 xQueueSendToBack 和 xQueueSendToFront,函数 xQueueSendToBack 实现的是 FIFO 方式的存取,函数 xQueueSendToFront 实现的是 LIFO 方式的读写。我们这里说的函数 xQueueSend 等效于 xQueueSendToBack,即实现的是 FIFO 方式的存取。
应用举例:
五、中断中消息队列发送
函数原型:
BaseType_t xQueueSendFromISR ( QueueHandle_t xQueue, /* 消息队列句柄 */
const void *pvItemToQueue, /* 要传递数据地址 */
BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */ );
函数描述:
函数 xQueueSendFromISR 用于中断服务程序中消息发送。
- 第 1 个参数是消息队列句柄。
- 第 2 个参数要传递数据地址,每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大 小复制到消息队列空间中。
- 第 3 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE,说明有高优先级任务要执行,否则没有。
- 返回值,如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL。
使用这个函数要注意以下问题:
-
FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。正因为这个原因,用户在创建消息队列时单个消息大小不可太大,因为一定程度上面会增加中断服务程序的执行时间。
-
此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是 xQueueSend。
-
消息队列还有两个函数 xQueueSendToBackFromISR 和 xQueueSendToFrontFromISR,函数 xQueueSendToBackFromISR 实现的是 FIFO 方式的存取,函数 xQueueSendToFrontFromISR 实现的是 LIFO 方式的读写。我们这里说的函数 xQueueSendFromISR 等效于 xQueueSendToBackFromISR,即实现的是 FIFO 方式的存取。
应用举例:
六、消息队列接收
函数原型:
BaseType_t xQueueReceive( QueueHandle_t xQueue, /* 消息队列句柄 */
void *pvBuffer, /* 接收消息队列数据的缓冲地址 */
TickType_t xTicksToWait /* 等待消息队列有数据的最大等待时间 */ );
函数描述:
函数 xQueueReceive 用于接收消息队列中的数据。
- 第 1 个参数是消息队列句柄。
- 第 2 个参数是从消息队列中复制出数据后所储存的缓冲地址,缓冲区空间要大于等于消息队列创建函数 xQueueCreate 所指定的单个消息大小,否则取出的数据无法全部存储到缓冲区,从而造成内存溢出。
- 第 3 个参数是消息队列为空时,等待消息队列有数据的最大等待时间,单位系统时钟节拍。
- 返回值,如果接到到消息返回 pdTRUE,否则返回 pdFALSE。
使用这个函数要注意以下问题:
- 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是 xQueueReceiveFromISR。
- 如果消息队列为空且第三个参数为 0,那么此函数会立即返回。
- 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配置为 portMAX_DELAY,那么此函数会永久等待直到消息队列有数据。
应用举例:
七、消息队列应用编程 - 任务与任务
现场讲解
八、消息队列应用编程 - 中断与任务
现场讲解