在嵌入式系统开发中,任务间的通信是非常常见的需求。FreeRTOS提供了多种任务间通信的机制,其中之一就是消息队列。消息队列是一种非常灵活和高效的方式,用于在不同的任务之间传递数据。通过消息队列,任务可以异步地发送和接收消息,从而实现任务间的数据交换和协作。
在本篇博文中,我们将深入探讨FreeRTOS中消息队列的使用,包括如何创建和初始化消息队列,以及如何在任务中发送和接收消息。我们还将讨论消息队列的特性和限制,并提供一些实际的示例代码,以帮助读者更好地理解消息队列的工作原理和用法。通过本篇博文,读者将能够掌握在FreeRTOS中有效地利用消息队列进行任务间通信的技巧和方法。
文章目录
- 1.消息队列简介
- 1.1 特性
- 1.2 消息队列数据存储
- 1.3 出队阻塞
- 1.4 入队阻塞
- 1.5 消息队列操作示图
- 1.6 消息队列控制块
- 2.常用消息队列API函数
- 2.1 消息队列创建函数 `xQueueCreate()`
- 2.2 消息队列静态创建函数 `xQueueCreateStatic()`
- 2.3 消息队列删除函数 `vQueueDelete()`
- 2.4 向消息队列发送消息函数
- 2.4.1 `xQueueSend()`与`xQueueSendToBack()`
- 2.4.2 `xQueueSendFromISR()`与 `xQueueSendToBackFromISR()`
- 2.4.3 `xQueueSendToFront()`
- 2.4.4 `xQueueSendToFrontFromISR()`
- 2.4.5 `xQueueGenericSend()`
- 2.4.6 `xQueueGenericSendFromISR()`
- 2.5 从消息队列读取消息函数
- 2.5.1 `xQueueReceive()`与`xQueuePeek()`
- 2.5.2 `xQueueReceiveFromISR()`与 `xQueuePeekFromISR()`
- 3.消息队列使用注意事项
- 4.示例项目
- stm32示例代码:
- 项目解释:
1.消息队列简介
队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息。
1.1 特性
FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:
- 消息支持先进先出方式排队,支持异步读写工作方式。
- 读写队列均支持超时机制。
- 消息支持后进先出方式排队,往队首发送消息(LIFO)。
- 可以允许不同长度(不超过队列节点最大值)的任意类型消息。
- 一个任务能够从任意一个消息队列接收和发送消息。
- 多个任务能够从同一个消息队列接收和发送消息。
- 当队列使用结束后,可以通过删除队列函数进行删除。
1.2 消息队列数据存储
通常队列采用先进先出(FIFO)的存储缓冲机制,也可以使用 LIFO 的存储缓冲,也就是后进先出。
数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。
1.3 出队阻塞
当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间。
1.4 入队阻塞
入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。
1.5 消息队列操作示图
(1)创建队列
(2)向队列发送第一个消息
(3)向队列发送第二个消息
(4)从队列中读取消息
1.6 消息队列控制块
2.常用消息队列API函数
2.1 消息队列创建函数 xQueueCreate()
2.2 消息队列静态创建函数 xQueueCreateStatic()
2.3 消息队列删除函数 vQueueDelete()
- 原型:
void vQueueDelete(QueueHandle_t xQueue)
- 功能:删除一个队列,释放相关资源
- 参数:xQueue为要删除的队列句柄
- 返回值:无
2.4 向消息队列发送消息函数
2.4.1 xQueueSend()
与xQueueSendToBack()
2.4.2 xQueueSendFromISR()
与 xQueueSendToBackFromISR()
2.4.3 xQueueSendToFront()
2.4.4 xQueueSendToFrontFromISR()
2.4.5 xQueueGenericSend()
- 原型:
BaseType_t xQueueGenericSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait, BaseType_t xCopyPosition)
-
功能:向队列发送数据
-
参数:
xQueue
:要发送数据的队列句柄pvItemToQueue
:指向要发送的数据的指针xTicksToWait
:发送数据时的超时时间xCopyPosition
:指定数据拷贝的位置
-
返回值:
如果数据成功发送到队列,则返回pdPASS
;如果队列已满且超时,则返回errQUEUE_FULL
;其他错误情况返回errQUEUE_FULL
。
2.4.6 xQueueGenericSendFromISR()
- 原型:
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue, const void * pvItemToQueue, BaseType_t * pxHigherPriorityTaskWoken, BaseType_t xCopyPosition)
-
功能:从ISR中向队列发送数据
-
参数:
xQueue
:要发送数据的队列句柄pvItemToQueue
:指向要发送的数据的指针pxHigherPriorityTaskWoken
:指向一个变量的指针,用于指示是否有更高优先级的任务需要立即执行xCopyPosition
:指定数据拷贝的位置
-
返回值:如果数据成功发送到队列,则返回
pdPASS
;如果队列已满,则返回errQUEUE_FULL
;其他错误情况返回errQUEUE_FULL
。
2.5 从消息队列读取消息函数
2.5.1 xQueueReceive()
与xQueuePeek()
2.5.2 xQueueReceiveFromISR()
与 xQueuePeekFromISR()
3.消息队列使用注意事项
- 使用
xQueueSend()
、xQueueSendFromISR()
、xQueueReceive()
等这些函数之前应先创建需消息队列,并根据队列句柄进行操作。 - 队列读取采用的是先进先出 (FIFO) 模式,会先读取先存储在队列中的数据。当然也 FreeRTOS 也支持后进先出(LIFO) 模式,那么读取的时候就会读取到后进队列的数据。
- 在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数据区域大小不小于消息大小,否则,很可能引发地址非法的错误。
- 无论是发送或者是接收消息都是以拷贝的方式进行,如果消息过于庞大,可以将消息的地址作为消息进行发送、接收。
- 队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读出倒是用的比较少。
4.示例项目
stm32示例代码:
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "stm32f4xx.h"#define QUEUE_LENGTH 5
#define ITEM_SIZE sizeof(int)QueueHandle_t xQueue;void vSenderTask(void *pvParameters) {int xData = 100;while (1) {xQueueSend(xQueue, &xData, portMAX_DELAY);vTaskDelay(pdMS_TO_TICKS(1000));}
}void vReceiverTask(void *pvParameters) {int xReceivedData;while (1) {xQueueReceive(xQueue, &xReceivedData, portMAX_DELAY);// 处理接收到的数据}
}int main() {xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, 1, NULL);xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, NULL, 2, NULL);vTaskStartScheduler();while (1) {// 该处不会被执行}
}
项目解释:
在这个示例中,我们首先创建了一个队列xQueue
,其长度为5,每个项目的大小为一个int
。然后我们创建了两个任务:vSenderTask
和vReceiverTask
。vSenderTask
任务向队列发送数据,而vReceiverTask
任务从队列接收数据。
在vSenderTask
任务中,我们使用xQueueSend()
函数向队列发送数据。在vReceiverTask
任务中,我们使用xQueueReceive()
函数从队列接收数据,并可以在任务中处理接收到的数据。这两个任务都有一个无限循环,因此它们将一直运行。
在main
函数中,我们创建了两个任务,并启动了FreeRTOS调度器。一旦调度器启动,任务将开始执行其功能。