消息队列

队列是任务间通信的主要形式。它们可以用于在任务之间 以及中断和任务之间发送消息。在大多数情况下,队列用作线程安全的 FIFO(先进先出)缓冲区, 新数据被发送到队列的后面,但也可以发送到前面。

queueyuanli

向队列中写入和从队列中读取。此示例中创建队列来保存 5 个项目,并且队列永远不会满。

工作机制

FreeRTOS 队列使用模型既简单又灵活, 这两者通常是不可兼得的。消息通过队列以副本的方式发送, 这意味着数据(可以是更大的缓冲区的指针)本身被复制到队列中, 而不是队列始终只存储对数据的引用。

  • 已经包含在 C 语言变量(整数、 小结构体等)中的小消息可以直接送入队列。没有 必要为消息分配一个缓冲区, 然后将变量复制到分配的缓冲区中。同样,可以直接从队列中将消息读取到 C 变量中 。

    此外,以这种方式向队列发送消息, 允许发送任务立即覆盖发送到队列的变量或缓冲区(直接覆盖队列中的数据), 即使发送的消息仍在队列中。

    由于变量中包含的数据已复制到队列中, 变量本身可以重复使用。不要求发送消息的任务和接收消息的任务约定哪个任务拥有该消息,以及哪个任务负责在不需要该消息时 将其清空。

  • 使用通过复制传递数据的队列不会导致无法将队列用于通过引用传递数据。当消息的大小达到一定程度, 将整条消息逐字节复制到队列中是不现实的, 此时可将消息定义为保存若干指针并复制消息的 一个指针至队列。

  • 内核独自负责分配用于队列存储区的内存 。

  • 可变大小的消息可以通过定义队列来保存结构体, 其中包含一个指向队列消息的成员, 以及另一个保存队列消息大小的成员。

  • 单个队列可用于接收不同的消息类型, 以及来自多个地点的消息, 方法是将队列定义为保存一个结构体,该结构的一个成员持有消息类型, 另一个成员保存消息数据(或消息数据的一个指针)。如何解释数据 取决于消息类型。

    正是使用这种方式,管理 FreeRTOS-Plus-TCP IP 堆栈的任务才能使用一个队列来接收 ARP 定时器事件、 从以太网硬件接收的数据包、 从应用程序接收的数据包、网络故障事件等的通知。

  • 该实现适用于在内存保护环境中使用 。一个被限制在受保护的内存区域的任务可以将数据传递给一个被限制在不同的受保护内存区域的任务, 因为通过调用队列发送函数 来调用 RTOS 将提高微控制器的权限等级 。队列存储区 仅可由 RTOS 访问(具有完整权限)。

  • 提供一个单独的 API 用于中断内部。将 RTOS 任务中使用的 API 与中断服务程序中使用的 API 分开, 意味着 RTOS API 函数的实现 不承担每次执行时检查其调用上下文的开销。 使用单独的中断 API 也意味着,在大多数情况下,创建 RTOS 感知的中断服务程序对终端用户而言更简单—— 与其他 RTOS 产品相比。

  • 从任何意义上来说,API 都更加简单。

阻塞队列

队列 API 函数允许指定阻塞时间。

当一个任务试图从一个空队列中读取时,该队列将进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行)直到队列中的数据变得可用,或者阻塞时间过期。

当一个任务试图写入到一个满队列时,该队列将进入阻塞状态(因此它不会消耗任何 CPU 时间,且其他任务可以运行)直到队列中出现可用空间,或者阻塞时间过期。

如果同一个队列上有多个处于阻塞状态的任务, 那么具有最高优先级的任务将最先解除阻塞。

请注意,中断只能使用以 “FromISR” 结尾的 API 函数。

API

xQueueCreate

创建新队列并返回一个可以引用该队列的 句柄。

1
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,或保留为未定义状态(默认为 1), 才可使用此 RTOS API 函数。

每个队列都需要 RAM 来保存队列状态 以及队列中包含的项目(队列存储区)。 如果使用 xQueueCreate() 创建队列,则所需的 RAM 会自动 从 FreeRTOS 堆中分配。 如果 使用 xQueueCreateStatic() 创建队列, 则 RAM 由应用程序编写者提供,这会产生更多的参数, 但这样能够在编译时静态分配 RAM 。

参数:

  • uxQueueLength

    队列一次可存储的最大项目数。

  • uxItemSize

    存储队列中每个项目所需的大小(以字节为单位)。

    项目通过复制而非引用的方式入队,因此该参数值是每个入队项目将复制的 字节数。队列中的每个项目必须具有相同的大小。

返回:

  • 如果队列创建成功,则返回所创建队列的句柄。如果创建队列所需的内存 无法分配,则返回 NULL。

示例

1
2
3
4
5
6
7
xQueue1 = xQueueCreate(10, sizeof( unsigned long ));
if(xQueue1 == NULL)
}

xQueue2 = xQueueCreate(10, sizeof(struct AMessage *));
if( xQueue2 == NULL ) {
}

xQueueSend

此宏用于调用 xQueueGenericSend() 函数。之所以包含此宏,是为了 向后兼容那些未提供 xQueueSendToFront() 和 xQueueSendToBack() 宏的 FreeRTOS 版本。其功能 等同于 xQueueSendToBack()。

从队列尾部入队一个数据项。数据项通过复制而非引用入队。不得从中断服务程序 。不得从中断服务程序中调用 此函数。

xQueueSendFromISR(),这是一个可在 ISR 中使用的替代函数。

1
BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);

参数:

  • xQueue

    要向其中发布项目的队列的句柄

  • pvItemToQueue

    指向要放入队列中的项目的指针。队列能够存储的项目的大小在创建队列时即已定义,因此 pvItemToQueue 中的这些字节将复制到 队列存储区中。

  • xTicksToWait

    队列已满的情况下,任务处于阻塞状态且愿意等待队列中出现可用空间的 最长时间。

    如果队列已满且 xTicksToWait 设置为 0, 则调用将立即返回。时间以滴答周期为单位定义,如果需要转换为实际时间,可以使用 portTICK_PERIOD_MS 常量 。

返回:

  • 如果成功发布项目,返回 pdTRUE,
  • 否则返回 errQUEUE_FULL。
1
2
3
4
5
6
7
xQueue1 = xQueueCreate(10, sizeof(unsigned long));

unsigned long ulVar = 10UL;

if(xQueueSend(xQueue1, (void*) &ulVar, (TickType_t )10) != pdPASS) {
}

xQueueReceive

从队列中接收项目。该项目通过复制接收,因此必须提供足够大小的缓冲区。创建队列时定义了复制到缓冲区中的字节数。

1
BaseType_t xQueueReceive(QueueHandle_t xQueue, void* pvBuffer, TickType_t xTicksToWait)

这是用于调用 xQueueGenericReceive() 函数的宏。

中断服务程序中不得使用此函数。请参阅 xQueueReceiveFromISR 了解可以选择的替代方案。

参数:

  • xQueue

    要从中接收项目的队列的句柄。

  • pvBuffer

    指向要将所接收项目复制到缓冲区的指针。

  • xTicksToWait

    如果在调用时队列为空, 则任务应阻塞等待项目接收的最长时间。

    如果队列为空,将 xTicksToWait 设置为 0 将导致函数立即返回 。时间是以滴答周期为单位定义的,因此如果需要,应使用常量 portTICK_PERIOD_MS 转换为实时。如果 INCLUDE_vTaskSuspend 设置为 “1”, 则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞 (没有超时)。

返回:

  • 如果从队列中成功接收项目,则返回 pdTRUE;
  • 否则返回 pdFALSE。

示例

1
2
3
4
5
6
struct AMessage xRxedStructure, *pxRxedPointer;

xStructQueue = xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

if( xQueueReceive(xStructQueue, &(xRxedStructure), (TickType_t)10) == pdPASS) {
}