问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

FreeRTOS消息队列详解:概念、API与实现原理

创作时间:
作者:
@小白创作中心

FreeRTOS消息队列详解:概念、API与实现原理

引用
CSDN
1.
https://m.blog.csdn.net/2301_79532069/article/details/143983301

FreeRTOS的消息队列是一种用于多任务间通信的机制,基于FIFO(先进先出)的数据共享结构,可以实现任务解耦和数据传输。本文将详细介绍FreeRTOS消息队列的概念、特点、工作原理及相关API函数,并对比了FreeRTOS和uCOS在消息队列实现上的差异。

一、概念及其作用

1.1概念

FreeRTOS中的消息队列是用于多任务间通信的一种机制。本身队列也是一种FIFO(先进先出)的数据共享结构,凭借这个特点,FreeRTOS操作系统可以实现任务解耦、任务间数据传输。

  • 任务解耦:任务之间通过队列进行通信,可以减少任务之间的耦合度,提高代码的模块化程度
  • 任务间数据传输:作为共享资源,各任务可以通过向队列读、写数据,实现数据共享和同步

队列中的数据通过发送入队,读取时可以决定是否出队(即是否清除)。

FreeRTOS中使用消息队列传输数据默认通过数据拷贝,也就是将发送的数据拷贝到队列中(属于值传递),费时但原始数据可以清除或者覆写。

uCOS中的消息队列则采用引用传递,传递的是消息指针。这种方式需要保证传递的消息一直是可见且有效的,像局部变量这种生命周期短的就不能作为消息,但它的好处是节省时间!

因此,虽然默认使用值传递,当要发送的数据太大时,可以考虑发送消息缓冲区的指针。

1.2特点

  • FIFO先入先出
  • 尾写入头读出(可变成头写入尾读出)
  • “数据中转站”
  • “多对多”
  • 消息不定长
  • 解决无序

1.3工作原理

队列的实质是:RAM的一段内存空间

二、相关API

队列的使用流程:创建队列、写队列、读队列、删除队列(需要的情况下使用...)

2.1创建队列

掌握动态的即可

#include "FreeRTOS.h"
#include "queue.h"

/*创建队列*/
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize)

func: dynamically create 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任务中写队列

#include "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任务中读队列

#include "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中断中写队列

#include "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中断中读队列

#include "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消息队列的创建

xQueueCreate(实际接口xQueueGenericCreate )
xQueueGenericCreate其中参数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中断中接收

和中断中发送类似,主要是用到了发送锁和接收锁

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号