龙空技术网

freeRTOS使用:消息队列的原理和使用方法

嵌入式之入坑笔记 917

前言:

此刻咱们对“消息队列如何使用”都比较关注,咱们都想要分析一些“消息队列如何使用”的相关内容。那么小编也在网摘上收集了一些对于“消息队列如何使用””的相关内容,希望兄弟们能喜欢,你们一起来了解一下吧!

1、freeRTOS 的消息队列

freeRTOS中提供了任务之间互相通信的另外的一种手段 — 消息队列。

消息队列的作用主要有:

1)为了在任务与任务之间、任务与中断之间的通信而准备的,可以在任务与任务之间,任务与中断之间传递消息;

2)消息队列可以存储有限的、大小固定的数据项目;

它具有的特点有几个:

1)freeRTOS中的消息队列传递的是一个实际的数据,而不是数据的地址。在RTX、ucos-ii、ucos-iii中的消息队列传递的是消息的地址。

2)消息队列不属于某个特定的任务,任何的任务都可以向队列中发送消息,也可以从队列中提取消息。

3)消息队列所能容纳的最大的数据项目就是队列的长度。

特别注意:freeRTOS中的消息队列传递的是一个实际的数据,而不是数据的地址。

2、freeRTOS的消息队列的管理

2.1、队列的出队方式 & 数据存储

通常的队列都是采取先进先出(FIFO)的存储缓冲机制方式,即往队列中发送消息的时候永远是先发到队列的尾部,而从队列中提取消息的时候是从头部提取的。

注:freeRTOS也支持 后进先出(LIFO)的方式。

因为freeRTOS的消息队列传递的是实际的数据,所以数据发送到队列中会导致数据拷贝,会浪费一些时间。但是好处在于一旦消息被拷贝到了消息队列中,那么原始的数据缓冲区或者数据就可以删除或者改变,达到反复利用的效果。

2.2、队列的阻塞

队列阻塞的作用是在当任务从队列中获取消息的时候,所指定的一个等待时间。队列在出队和入队都是可以设置阻塞的。有三种阻塞的时间:

不等待:不等待直接执行之后的内容(为0不等待);

定时等待:任务等待一个固定的时间间隔,可以为 0 ~ portMAX_DELAY;

一直等待:设置为 portMAX_DELAY 的时候,会一直等待。

3、freeRTOS的队列的实现示意

按照freeRTOS官方给出的队列的实现,它的链接如下:

官方的队列示意图如下:

(1)创建队列

(2)向队列中发送数据

(3)从队列中读出数据

4、消息队列的两种通信方式

(1)任务之间通过消息队列通信,如下:

(2)通过在中断中触发消息队列通信,如下:

5、freeRTOS 消息队列实现的API函数

5.1、创建消息队列

函数原型:

 QueueHandle_t xQueueCreate(    UBaseType_t uxQueueLength, /* 消息个数 */    UBaseType_t uxItemSize ); /* 每个消息大小,单位字节 */

函数描述:函数 xQueueCreate 用于创建消息队列。

uxQueueLength:是消息队列支持的消息个数。即队列长度。uxItemSize:是每个消息的大小,单位字节。返回值:如果创建成功会返回消息队列的句柄,失败会返回 NULL。

使用这个函数要注意以下问题:

1)FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址,这点要特别注意。每一次传递都是 uxItemSize 个字节。

2.)uxItemSize:所要发送/接收的消息队列的总字节数。比如要发送10个消息,每个消息占4个字节,所以 uxItemSize = 4(byte) * 10(个) = 40 Byte

5.2、向消息队列中发送消息

1)任务向消息队列发送消息

函数原型:

 BaseType_t xQueueSend(    QueueHandle_t xQueue, /* 消息队列句柄 */    const void * pvItemToQueue, /* 要传递数据地址 */    TickType_t xTicksToWait /* 等待消息队列有空间的最大等待时间 */ );

函数描述:函数 xQueueSend 用于任务中消息发送。

xQueue:是消息队列句柄。pvItemToQueue:要传递的数据地址,每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大 小复制到消息队列空间中。xTicksToWait:是当消息队列已经满时,等待消息队列有空间时的最大等待时间,单位为系统时钟节拍。返回值:如果消息成功发送返回 pdTRUE,否则返回errQUEUE_FULL。

使用这个函数要注意以下问题:

1)FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。

2)此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是 xQueueSendFromISR。

3)如果消息队列已经满且第三个参数为 0,那么此函数会立即返回。

4)如果用户将 FreeRTOSConfig.h 文件中的宏定义INCLUDE_vTaskSuspend 配置为 1 且第三个参数配 置为 portMAX_DELAY,那么此发送函数会永久等待直到消息队列有空间可以使用。

5)消息队列还有两个函数 xQueueSendToBack 和 xQueueSendToFront,函数 xQueueSendToBack 实现的是 FIFO 方式的存取,函数 xQueueSendToFront 实现的是 LIFO 方式的读写。一般说的函 数 xQueueSend 等效于 xQueueSendToBack,即实现的是 FIFO 方式的存取。

2)中断中向消息队列发送消息

函数原型:

 BaseType_t xQueueSendFromISR (    QueueHandle_t xQueue, /* 消息队列句柄 */    const void *pvItemToQueue, /* 要传递数据地址 */    BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */ );

函数描述:函数 xQueueSendFromISR 用于中断服务程序中消息发送。

xQueue:是消息队列句柄。pvItemToQueue:要传递数据地址,每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大 小复制到消息队列空间中。pxHigherPriorityTaskWoken:用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE, 说明有高优先级任务要执行,否则没有。返回值:如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL。

使用这个函数要注意以下问题:

1)FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。正因为这个原因,用户在创建消息队 列时单个消息大小不可太大,因为一定程度上面会增加中断服务程序的执行时间。

2)此函数是用于中断服务程序中调用的,不可以在任务代码中调用此函数,任务代码中使用的是 xQueueSend。

3)消息队列还有两个函数 xQueueSendToBackFromISR 和 xQueueSendToFrontFromISR,函数 xQueueSendToBackFromISR 实现的是 FIFO 方式的存取,函数 xQueueSendToFrontFromISR 实 现的是 LIFO 方式的读写。我们这里说的函数 xQueueSendFromISR 等效于 xQueueSendToBackFromISR,即实现的是 FIFO 方式的存取。

5.3、从消息队列中读取消息

1)任务中读取

函数原型:

 BaseType_t xQueueReceive(    QueueHandle_t xQueue, /* 消息队列句柄 */   void *pvBuffer, /* 接收消息队列数据的缓冲地址 */    TickType_t xTicksToWait /* 等待消息队列有数据的最大等待时间 */ );

函数描述:函数 xQueueReceive 用于接收消息队列中的数据。

xQueue:是消息队列句柄。pvBuffer:是从消息队列中复制出数据后所储存的缓冲地址,缓冲区空间要大于等于消息队列创建函 数 xQueueCreate 所指定的单个消息大小,否则取出的数据无法全部存储到缓冲区,从而造成内存溢 出。xTicksToWait:是消息队列为空时,等待消息队列有数据的最大等待时间,单位系统时钟节拍。返回值:如果接到到消息返回 pdTRUE,否则返回 pdFALSE。

使用这个函数要注意以下问题:

1)此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是 xQueueReceiveFromISR 。

2)如果消息队列为空且第三个参数为 0,那么此函数会立即返回。

3)如果将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配 置为 portMAX_DELAY,那么此函数会永久等待直到消息队列有数据。

其他的一些从任务中读取消息队列的函数:

2)中断中读取

 BaseType_t xQueueReceiveFromISR(    QueueHandle_t xQueue, //消息队列    void * const pvBuffer, //消息的存放位置    BaseType_t * const pxHigherPriorityTaskWoken )

函数参数和说明同xQueueReceive。

其他的一些从中断中读取消息队列的函数:

6、队列初始化函数

 static void prvInitialiseNewQueue(    const UBaseType_t uxQueueLength, //队列长度    const UBaseType_t uxItemSize, //队列项目长度    uint8_t *pucQueueStorage, //队列项目的存储区    const uint8_t ucQueueType, //队列类型    Queue_t *pxNewQueue ) //队列结构

7、队列复位

 BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )

8、消息队列的应用举例

消息队列举例:

创建两个任务,一个任务发送三个数据给另外一个任务,另一个任务接收并打印,循环发送,每次发送都对发送的数据增加后发送。

代码如下:

 1、创建消息队列QueueHandle_t     NewQueue;	//消息队列NewQueue  = xQueueCreate(3,sizeof(int)*3); //这一步非常重要,所需字节数 = 每个消息的字节数*队列长度2、发送消息队列 && 接收消息队列//task1任务函数 void task1_task(void *pvParameters) //prio = 2{    u32 recStr[3] = {0};    while(1)    {        xQueueReceive(NewQueue, recStr, 0);	//接收消息队列        printf("Tx0 = %d Tx1 = %d Tx2 = %d\r\n",recStr[0],recStr[1],recStr[2]);        LED1 ^= 1;        vTaskDelay(200);   //延时n个时钟节拍	    }}//task2任务函数  void task2_task(void *pvParameters) //prio = 3{    u32 TxStr[3] = {0};	while(1)	{        xQueueSend(NewQueue, TxStr, portMAX_DELAY);	//发送消息队列        TxStr[0] ++;        TxStr[1] += 5;        TxStr[2] += 10;        LED0 ^= 1;        vTaskDelay(500);       //延时n个时钟节拍		}}

作者简介:

本人95后技术男,从事嵌入式软件开发,专注于技术成长和技术分享。目标是每天进一步一点点,通过技术改变自己的生活,创造自己的美好未来!如果你也对嵌入式感兴趣,欢迎关注我呀!

声明:

本文作者:嵌入式之入坑笔记

文章版权归作者所有,转载请注明出处!

标签: #消息队列如何使用