FreeRTOS-3-消息队列

这是 FreeRTOS 系列教程的第 3 篇,介绍 FreeRTOS 中的消息队列。

队列

FreeRTOS中队列也可以称之为消息队列。在FreeRTOS中长用来进行数据传输。队列提供了

  • 从任务到任务
  • 中断到任务
  • 任务到中断
    三种消息的传递方向。

任务能够从队列中读取消息,当队列中的消息为空时,读取消息的任务将被阻塞。用户还可以指定阻塞的任务时间xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。消息队列是一种异步的通信方式。

事实上FreeRTOS中的队列不仅仅支持先进先出,也是支持先进后出的。

创建消息队列时FreeRTOS会先给消息队列分配一块内存空间,这块内存的大小等于[消息队列控制块大小+(单个消息空间大小×消息队列长度)],接着再初始化消息队列,此时消息队列为空。FreeRTOS的消息队列控制块由多个元素组成,当消息队列被创建时,系统会为控制块分配对应的内存空间,用于保存消息队列的一些信息,如消息的存储位置、头指针pcHead、尾指针pcTail、消息大小uxItemSize以及队列长度uxLength等。同时每个消息队列都与消息空间在同一段连续的内存空间中,在创建成功时,这些内存就被占用了,只有删除消息队列时,这段内存才会被释放。创建成功时就已经分配好每个消息空间与消息队列的容量,无法更改,每个消息空间可以存放不大于消息大小(uxItemSize)的任意类型的数据,所有消息队列中的消息空间总数即消息队列的长度,这个长度可在消息队列创建时指定。

任务或者中断服务程序都可以给消息队列发送消息。当发送消息时,如果队列未满或者允许覆盖入队,FreeRTOS会将消息复制到消息队列队尾,否则会根据用户指定的阻塞超时时间进行阻塞。在这段时间中,如果队列一直不允许入队,该任务将保持阻塞状态以等待队列允许入队。当其他任务从其等待的队列中读取入数据(队列未满)时,该任务将自动由阻塞态转换为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转换为就绪态,此时发送消息的任务或者中断程序会收到一个错误代码errQUEUE_FULL。

所以消息队列所占内存实际上就是:

  • 消息控制块
  • 消息所占用的空间

队列中的阻塞

阻塞机制只可以在任务中使用,无法在中断里使用。

消息队列中阻塞的处理只有三种方式

  • 当消息队列已满,永远等待下去
  • 当消息队列已满,指定等待时间,如果时间到了还是没有空闲位置则发送消息的任务或者是中断程序会受到一个错误码errQUEUE_FULL
  • 当队列消息已满,不等待,而去执行其他任务

假如有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权。

队列的使用

消息队列可用于发送不定长消息的场合,包括任务与任务间的消息交换。队列是FreeRTOS主要的任务间通信方式,可以在任务与任务间、中断和任务间传送信息,发送到队列的消息是通过复制方式实现的,这意味着队列存储的数据是原始数据,而不是原始数据的引用。

数据结构和函数

消息队列的控制块:

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
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
int8_t * pcHead; /**< Points to the beginning of the queue storage area. */
int8_t * pcWriteTo; /**< Points to the free next place in the storage area. */

union
{
QueuePointers_t xQueue; /**< Data required exclusively when this structure is used as a queue. */
SemaphoreData_t xSemaphore; /**< Data required exclusively when this structure is used as a semaphore. */
} u;

List_t xTasksWaitingToSend; /**< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */
List_t xTasksWaitingToReceive; /**< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */

volatile UBaseType_t uxMessagesWaiting; /**< The number of items currently in the queue. */
UBaseType_t uxLength; /**< The length of the queue defined as the number of items it will hold, not the number of bytes. */
UBaseType_t uxItemSize; /**< The size of each items that the queue will hold. */

volatile int8_t cRxLock; /**< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
volatile int8_t cTxLock; /**< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */

#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /**< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif

#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif

#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;

pcWriteTo指向队列消息存储区下一个可用消息空间。

pcReadFromuxRecursiveCallCount是一对互斥变量,使用联合体来确保两个互斥的结构体成员不会同时出现。当结构体用于队列时,pcReadFrom指向出队消息空间的最后一个,也就是读取消息时是从pcReadFrom指向的空间读取消息内容。

xTasksWaitingToSend是一个发送消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序。由于队列已满,想要发送消息的任务无法发送消息。

xTasksWaitingToReceive是一个获取消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序。由于队列是空的,想要获取消息的任务无法获取消息。

消息队列创建函数

分为动态创建队列xQueueCreate()和静态创建队列xQueueCreateStatic()使用静态创建消息队列函数创建队列时需要的形参更多,需要的内存在编译时预先分配好,一般很少使用这种方法。

初始化消息队列:

静态创建队列函数

xQueueCreateStatic()是一个函数宏,其实质是函数xQueueGenericCreateStatic( ( uxQueueLength ), ( uxItemSize ), ( pucQueueStorage ), ( pxQueueBuffer ), ( queueQUEUE_TYPE_BASE ) )
这个函数使用数组的形式来申请静态内存空间。

使用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 1 /* 创建一个可以最多可以存储10个64位变量的队列 */
2 #define QUEUE_LENGTH 10
3 #define ITEM_SIZE sizeof( uint64_t )
4
5 /* 该变量用于存储队列的数据结构 */
6 static StaticQueue_t xStaticQueue;
7
8 /* 该数组作为队列的存储区域,大小至少有uxQueueLength * uxItemSize个字节 */
9 uint8_t ucQueueStorageArea[ QUEUE_LENGTH * ITEM_SIZE ];
10
11 void vATask( void *pvParameters )
12 {
13 QueueHandle_t xQueue;
14
15 /* 创建一个队列 */
16 xQueue = xQueueCreateStatic( QUEUE_LENGTH, /* 队列深度 */
17 ITEM_SIZE, /* 队列数据单元的单位 */
18 ucQueueStorageArea, /* 队列的存储区域 */
19 &xStaticQueue ); /* 队列的数据结构 */
20 /* 剩余代码 */
21 }

队列删除函数

队列删除函数是根据消息队列句柄直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列,但是需要注意的是,如果某个消息队列没有被创建,当然是无法删除的。

向队列发送数据

消息队列发送函数有多个,都是使用宏定义进行展开的,有些只能在任务中调用,有些只能在中断中调用。

xQueueSend()与xQueueSendToBack()

xQueueSend()等同于xQueueSendToBack()。

xQueueSend()是一个宏,宏展开是调用函数xQueueGenericSend(),这个函数的实现过程在后面会详细讲解。该宏是为了向后兼容没有包含xQueueSendToFront()和xQueueSend-ToBack()这两个宏的FreeRTOS版本。xQueueSend()等同于xQueueSendToBack()。

xQueueSend()用于向队列尾部发送一个队列消息。消息以复制的形式入队,而不是以引用的形式。该函数绝对不能在中断服务程序中被调用,中断中必须使用带有中断保护功能的xQueueSendFromISR()来代替。

xQueueSend()用于向队列尾部发送一个队列消息。消息以复制的形式入队,而不是以引用的形式。该函数绝对不能在中断服务程序中被调用,中断中必须使用带有中断保护功能的xQueueSendFromISR()来代替。

xQueueSendFromISR()与xQueueSendToBackFromISR()

xQueueSendFromISR()是一个宏,宏展开是调用函xQueueGenericSendFromISR()。该宏是xQueueSend()的中断保护版本,用于在中断服务程序中向队列尾部发送一个队列消息,等价于xQueueSendToBackFromISR()。

队列集

有时我们需要多次接受数据,此时可以使用多个队列集来完成这个任务。

队列设置允许所有任务从多个队列接收数据,而无需任务依次轮询每个队列以确定哪个队列(如果有)包含数据。

与使用接收结构的单个队列实现相同功能的设计相比,使用队列集从多个源接收数据的设计不那么整洁,效率也较低。因此,建议仅在设计约束绝对必要时才使用队列集。

队列集中的内容可以是队列,也可以是信号量。

队列集的创建

相关函数:

1
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength);

参数说明:
uxEventQueueLength:

当队列集合中的一个队列接收数据时,接收队列的句柄被发送到队列集合。 uxEventQueueLength 定义了正在创建的队列集在任何时候可以容纳的队列句柄的最大数量。队列句柄仅在队列集中的队列接收数据时发送给队列集。如果队列已满,则无法接收数据,因此如果队列集中的所有队列都已满,则无法向队列集发送队列句柄。因此,队列集中一次必须容纳的最大项目数是该组中每个队列长度的总和。例如,如果集合中有三个空队列,每个队列的长度为五,那么集合中的队列总共可以在集合中的所有队列都满之前接收十五个项目(三个队列乘以五个项目)。在该示例中,uxEventQueueLength 必须设置为 15,以保证队列集可以接收发送给它的每个项目。信号量也可以添加到队列集中。二进制和计数信号量将在本书后面介绍。为了计算必要的uxEventQueueLength,二进制信号量的长度为 1,计数信号量的长度由信号量的最大计数值给出。作为另一个例子,如果队列集将包含长度为 3 的队列和长度为 1 的二进制信号量,uxEventQueueLength 必须设置为 4 (3+1)。

返回值:
如果返回NULL则表示没有足够的空间创建队列集了, 如果返回非0值则代表成功创建.返回的值就是该队列集的句柄

添加队列或信号量

1
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet );

这个函数可以向队列集中添加一个队列或者是信号量.


FreeRTOS-3-消息队列
https://ysc2.github.io/ysc2.github.io/2024/01/15/FreeRTOS-3-消息队列/
作者
Ysc
发布于
2024年1月15日
许可协议