目录
一、概念及其作用
1.1概念
1.2特点
1.3工作原理
二、相关API
2.1创建队列
2.2任务中写队列
2.3任务中读队列
2.4中断中写队列
2.5中断中读队列
三、实现原理
3.1消息队列控制块
3.2消息队列的创建
3.3消息的发送
3.3.1任务中发送
3.3.2中断中发送
3.4消息的接收
3.4.1任务中接收
3.4.2中断中接收
一、概念及其作用
1.1概念
FreeRTOS中的消息队列是用与多任务间通信的一种机制。本身队列也是一种FIFO(先进先出)的数据共享结构,凭借这个特点,FreeRTOS操作系统可以实现任务解耦、任务间数据传输。
- 任务解耦:任务之间通过队列进行通信,可以减少任务之间的耦合度,提高代码的模块化程度
- 任务间数据传输:作为共享资源,各任务可以通过向队列读、写数据,实现数据共享和同步
队列中的数据通过发送入队,读取时可以决定是否出队(即是否清除)
FreeRTOS中使用消息队列传输数据默认通过数据拷贝,也就是将发送的数据拷贝到队列中(属于值传递),费时但原始数据可以清除或者覆写
uCOS中的消息队列则采用引用传递,传递的是消息指针。这种方式需要保证传递的消息一直是可见且有效的,像局部变量这种生命周期短的就不能作为消息,但它的好处是节省时间!
因此,虽然默认使用值传递,当要发送的数据太大时,可以考虑发送消息缓冲区的指针
1.2特点
- FIFO先入先出
- 尾写入头读出(可变成头写入尾读出)
- “数据中转站”
- “多对多”
- 消息不定长
- 解决无序
1.3工作原理
队列的实质是:RAM的一段内存空间
二、相关API
队列的使用流程: 创建队列、写队列、读队列、删除队列(需要的情况下使用...)
2.1创建队列
掌握动态的即可
#inlcude "FreeRTOS.h"
#include "queue.h"
/*创建队列*/
QueueHandle_t xQueueCreat(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)
func:dynamically creat queue and return QueueHandle //动态创建队列并返回句柄
params: uxQueueLength 队列一次可容纳消息的最大长度
uxItemSize 每个消息体大小 字节为单位,未知的可用sizeof
return: NULL: 创建失败
Any other value: 成功并返回句柄
matters needing attention:
Queue can be used between task and task or task and isr;
Queues can be created before the Scheduler is started;
2.2任务中写队列
#inlcude "FreeRTOS.h"
#include "queue.h"
/*任务中写队列*/
BaseType_t xQueueSend(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait)
//xQueueSendToFront用于头部紧急插入消息
func:transmit message to queue in task
params: xQueue 要发送进消息的队列句柄
pvItemToQueue 要发送的消息的地址
xTicksToWait 阻塞等待时间
return: pdPass 发送成功
errQUEUE_FULL 队列已满发送失败
matters needing attention: None
2.3任务中读队列
#inlcude "FreeRTOS.h"
#include "queue.h"
/*任务中读队列*/
BaseType_t xQueueReceive(QueueHandle_t xQueue,const void *pvBuffer,TickType_t xTicksToWait)
func:receive message from queue in task
params: xQueue 要读取消息的队列句柄
pvBuffer 接收消息的缓冲区
xTicksToWait 阻塞等待时间
return: pdPass 发送成功
errQUEUE_FULL 队列已满发送失败
matters needing attention: None
2.4中断中写队列
#inlcude "FreeRTOS.h"
#include "queue.h"
/*中断中写队列 xQueueSendToFrontFromISR用于头部紧急插入消息*/
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken)
func:transmit message to queue in ISR
params: xQueue 要发送进消息的队列句柄
pvItemToQueue 要发送的消息的地址
pxHigherPriorityTaskWoken NULL即可
return: pdTrue 发送成功
errQUEUE_FULL 队列已满发送失败
matters needing attention:
调用此函数,会触发上下文切换
启用调度器之前,不能调用此函数
2.5中断中读队列
#inlcude "FreeRTOS.h"
#include "queue.h"
/*中断中读队列*/
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,const void *pvBuffer,BaseType_t *pxHigherPriorityTaskWoken)
func:receive message from queue in ISR
params: xQueue 要发送进消息的队列句柄
pvBuffer 接收消息的缓冲区
pxHigherPriorityTaskWoken NULL即可
return: pdPass 发送成功
pdFAIL 消息队列为空
matters needing attention:
调用此函数,会触发上下文切换
启用调度器之前,不能调用此函数
可以看到:中断中相关的API都没有xTicksToWait,因为本身中断就是为了紧急响应的,快响应快解决!!!
三、实现原理
3.1消息队列控制块
3.2消息队列的创建
xQueueCreat(实际接口xQueueGenericCreat )
xQueueGenericCreat其中参数queueQueue_Type_BASE 属于队列的一种类型,基于队列创建了很多这样的类型,包括互斥锁、计数信号量、二值信号量、递归锁,可以看出:信号量基于队列实现
-
判断队列空间是否为空
-
是则队列大小赋值为0
-
否则计算队列大小=Length*ItemSize
-
-
申请内存空间(QCB+队列大小),找到队列操作空间首地址
-
初始化消息队列api(队列句柄、长度、size、队列操作空间首地址)
-
判断队列空间是否为空,是则把QCB首地址赋值到队列头指针
-
否则把队列操作空间首地址赋值给队列头指针
-
确定队列Length、ItemSize
-
-
队列重置函数api(队列句柄、操作队列的状态 (一般是传进了pdTRUE))
-
进入临界段
-
头指针赋值,未读消息个数为0,写入指针赋值给头指针,读出指针赋值为头指针+(长度-1)*ItemSize,读写锁解锁
-
判断新建队列状态是否为pdFALSE,是则判断等待发送任务列表项是否有任务,是则移除进行上下文切换
-
否则新建队列,初始化发送、接收列表项
-
退出临界段
-
3.3消息的发送
3.3.1任务中发送
xQueueSend——实际接口为xQueueGenericSend(多了一个参数queSend_TO_BACK,有关入队类型的,尾插、头插或覆盖入队)
-
采用了for循环,为了快速处理数据拷贝的工作
-
挂起调度器——不让任务打断
-
锁定队列——不让中断打断
-
队列上锁——把发送和接收锁都赋值为上锁初始值
3.3.2中断中发送
xQueueSendFromISR——实际接口为xQueueGenericSendFromISR
-
关闭中断同时保存中断状态值
-
队列解锁prvUnlockQueue
-
进入临界段
-
获取发送锁的状态值
-
遍历直到发送锁解锁为止
-
解除等待消息任务,进行上下文切换
-
发送锁减1
-
3.4消息的接收
3.4.1任务中接收
xQueueReceive——实际接口为xQueueGenericReceive(多一个出队模式参数 xJustPeeking)
-
pdFAlSE——出队后,删除已读队列项或消息空间
-
pdTRUE——出队后不删除,然后恢复出队地址,让其他任务或中断继续读取
-
判断是否删除已读消息
-
是则更新消息等待读取的记录值,让它减1
-
否 将未读取之前的地址重新赋值给出队指针
-
3.4.2中断中接收
和中断中发送类似,主要是用到了发送锁和接收锁