消息队列Queue是FreeRTOS 一个重要的通信机制。
1、概念及作用
消息队列是通过RTOS内核提供服务,任务或中断服务子程序可以将一个消息(注意,FreeRTOS消息队列传递的是实际数据,并不是数据地址,RTX,uCOS-II 和uCOS-III是传
递的地址)放入到队列。同样,一个或者多个任务可以通过RTOS内核服务从队列中得到消息。通常,先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入到消息队列的消息,即先进先出的原则(FIFO),FreeRTOS的消息队列支持FIFO和LIFO两种数据存取方式。
也许有不理解的初学者会问采用消息队列多麻烦,搞个全局数组不是更简单,其实不然。在
裸机编程时,使用全局数组的确比较方便,但是在加上RTOS 后就是另一种情况了。相比消息队列,使用全局数组主要有如下四个问题:
1)使用消息队列可以让RTOS内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现。
2)使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
3)使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
4)FIFO 机制更有利于数据的处理。
2、任务间消息队列的实现
任务间消息队列的实现是指各个任务之间使用消息队列实现任务间的通信。
运行条件:
1)创建消息队列;
2)创建2个任务Task1和Task2,任务Task1向消息队列放数据,任务Task2从消息队列取数据;
3)FreeRTOS的消息存取采用FIFO方式;任务Task1向消息队列放数据,任务Task2从消息队列取数据,运行过程主要有以下两种情况:
- 如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况,FreeRTOS的消息存放函数xQueueSend支持超时等待,用户可以设置超时等待,直到有空间可以存放消息或者设置的超时时间溢出。
- 如果放数据的速度慢于取数据的速度,那么会出现消息队列为空的情况,FreeRTOS的消息获取函数xQueueReceive支持超时等待,用户可以设置超时等待,直到消息队列中有消息或者设置的超时时间溢出。
3、中断方式消息队列的实现
FreeRTOS中断方式消息队列的实现是指中断函数和FreeRTOS任务之间使用消息队列。下面我们通过如下的框图来说明一下FreeRTOS消息队列的实现,让大家有一个形象的认识。
运行条件:
1)创建消息队列;
2)创建1个任务Task1和一个中断;
3)FreeRTOS的消息存取采用FIFO方式。中断服务程序向消息队列放数据,任务Task1 从消息队列取数据,运行过程主要有以下两种情况:
- 如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况。由于中断服务程序里面的消息队列发送函数xQueueSendFromISR不支持超时设置,所以发送前要通过函数xQueueIsQueueFullFromISR检测消息队列是否满;
- 如果放数据的速度慢于取数据的速度,那么会出现消息队列存为空的情况。在FreeRTOS的任务中可以通过函数xQueueReceive获取消息,因为此函数可以设置超时等待,直到消息队列中有消息存放或者设置的超时时间溢出;
实际应用中,中断方式的消息机制要注意以下四个问题:
1)中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
2)实际应用中,建议不要在中断中实现消息处理,可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
3)中断服务程序中一定要调用专用于中断的消息队列函数,即以FromISR 结尾的函数。
4)在操作系统中实现中断服务程序与裸机编程的区别:
- 如果FreeRTOS工程的中断函数中没有调用FreeRTOS的消息队列API函数,与裸机编程是一样的。
- 如果FreeRTOS工程的中断函数中调用了FreeRTOS的消息队列的API函数,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点与裸机编程稍有区别。FreeRTOS多任务在开启前就设置好优先级分组,一旦设置好不可再修改。
4、API 函数
4.1. xQueueCreate()函数
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
该函数用于创建消息队列:
1)第1个参数是消息队列支持的消息个数;
2)第2个参数是每个消息的大小,单位字节;
3)如果创建成功会返回消息队列的句柄,如果由于FreeRTOSConfig.h文件中heap 大小不足,无法 为此消息队列提供所需的空间会返回NULL。
4)使用这个函数要
注意:FreeRTOS的消息传递是数据的复制,不是
数据地址;每一次传递都是uxItemSize个字节。
4.2. xQueueSend()函数
BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue, TickType_t xTicksToWait);
该函数用于任务中消息发送:
1)第1个参数是消息队列句柄。
2)第2个参数要传递数据地址,每次发送都是将消息队列创建函数xQueueCreate()所指定的单个消息大小 复制到消息队列空间中。
3)第3个参数是当消息队列已经满时,等待消息队列有空间时的最大等待时间,单位系统时钟节拍。
4)如果消息成功发送返回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方式的存取。
4.3. xQueueSendFromISR()函数
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
该函数用于中断服务程序中消息发送。
1)第1个参数是消息队列句柄。
2)第2个参数要传递数据地址,每次发送都是将消息队列创建函数xQueueCreate所指定的单
个消息大小复制到消息队列空间中。
3)第3个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。
4)如果消息成功发送返回pdTRUE,否则返回errQUEUE_FULL。
使用这个函数要注意以下问题:
1)FreeRTOS的消息传递是数据的复制,而不是传递的数据地址。正因为这个原因,用户在创建消息队列时单个消息大小不可太大,因为一定程度上面会增加中断服务程序的执行时间。
2)此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是 xQueueSend。
3)消息队列还有两个函数xQueueSendToBackFromISR和xQueueSendToFrontFromISR,函数 xQueueSendToBackFromISR实现的是FIFO方式的存取,函数xQueueSendToFrontFromISR实现的是LIFO方式的读写。我们这里说的函数xQueueSendFromISR等效于xQueueSendToBackFromISR,即实现的是FIFO方式的存取。
4.4. xQueueReceive()函数
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
该函数用于接收消息队列中的数据。
1)第1个参数是消息队列句柄。
2)第2个参数是从消息队列中复制出数据后所储存的缓冲地址,缓冲区空间要大于等于消息队列创建函数xQueueCreate所指定的单个消息大小,否则取出的数据无法全部存储到缓冲区,从而造成内存溢出。
3)第3个参数是消息队列为空时,等待消息队列有数据的最大等待时间,单位系统时钟节拍。
4)如果接收到消息返回pdTRUE,否则返回pdFALSE。
使用这个函数要注意以下问题:
a)此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是xQueueReceiveFromISR。
b)如果消息
队列为空且第三个参数为0,那么此函数会立即返回。
c) 如果用户将FreeRTOSConfig.h文件中的宏定义INCLUDE_vTaskSuspend配置为1 且第三个参数配置为portMAX_DELAY,那么此函数会永久等待直到消息队列有数据。