FreeRTOS——消息队列

目录

一、概念及其作用

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中断中接收

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

### FreeRTOS 中串口通信的消息队列实现 在嵌入式开发中,FreeRTOS消息队列机制能够有效地处理来自不同任务之间的数据交换。对于串口通信而言,通常涉及到接收和发送两个方向上的数据流控制。 #### 创建消息队列 为了便于管理和调度,在初始化阶段应当创建专门用于保存接收到的数据以及待发送指令的消息队列: ```c // 定义全局变量来保存队列句柄 QueueHandle_t xRxedChars; // 接收字符缓冲区 QueueHandle_t xStringsToSend; // 发送字符串缓冲区 void vInitQueues(void){ // 初始化接收队列, 可容纳20个unsigned char类型的元素 xRxedChars = xQueueCreate(20, sizeof(unsigned char)); // 初始化发送队列, 存储指向char*指针的数组 xStringsToSend = xQueueCreate(10, sizeof(char *)); } ``` 上述代码片段展示了如何利用 `xQueueCreate()` 来建立两个不同类型的消息队列实例[^2]。第一个参数表示该队列为最大可存储项的数量;第二个参数则指定每条消息所占字节数大小。 #### 处理串口中断事件 当发生UART中断时(比如新字符到达),应立即将这些信息推送到相应的消息队列里等待后续的任务去消费它们: ```c void UART_IRQHandler(void){ portBASE_TYPE xHigherPriorityWoken = pdFALSE; unsigned char receivedChar; while(UART_GetFlagStatus(UART_FLAG_RXNE)){ /* 获取新的输入字符 */ receivedChar = UART_ReceiveData(); /* 将其放入到接收队列中 */ xQueueSendToBackFromISR(xRxedChars,&receivedChar,&xHigherPriorityWoken); } /* 如果有更高优先级的任务被唤醒,则在此处执行上下文切换 */ portYIELD_FROM_ISR(xHigherPriorityWoken); } ``` 这段C语言代码实现了从硬件层面上捕获到来自外部设备传来的单个ASCII码值,并通过调用`xQueueSendToBackFromISR()`将其安全地加入到之前定义好的接收队列当中[^3]。注意这里的最后一个布尔型参数用来指示是否有更高级别的线程因为这次操作而变得就绪状态,从而可能触发立即性的任务重排。 #### 构建消费者/生产者模型 最后一步就是编写具体的应用逻辑——即所谓的“生产者”负责准备要传输的信息,“消费者”则是实际完成物理I/O动作的部分: ```c static void prvUartTxTask(void *pvParameters){ const TickType_t xBlockTime = pdMS_TO_TICKS(200UL); // 设置阻塞时间长度 for (;;){ char *pcString; if(pdPASS == xQueueReceive(xStringsToSend,(void *)&pcString,xBlockTime)){ // 执行具体的发送过程... printf("%s", pcString); // 假设我们使用动态分配内存的方式构建了这个字符串, // 则在这里释放它以防止泄露。 vPortFree(pcString); } } } static void prvUartRxTask(void *pvParameters){ static char cInputBuffer[8]; size_t uxIndex = 0; for (;;){ unsigned char receivedChar; if (pdTRUE == xQueueReceive(xRxedChars, &receivedChar, portMAX_DELAY)) { // 对于简单回显功能来说可以直接打印出来 putchar(receivedChar); // 或者收集一系列连续键入形成完整的命令行再做进一步解析... if ('\r' != receivedChar && '\n' != receivedChar) { cInputBuffer[uxIndex++] = receivedChar; if (sizeof(cInputBuffer)-1 == uxIndex || ' '==receivedChar ) { cInputBuffer[uxIndex]='\0'; // 进行必要的业务处理 uxIndex=0; } } } } } ``` 以上两段伪代码分别代表了一个典型的发送端(`prvUartTxTask`) 和接收端 (`prvUartRxTask`)工作流程。前者不断尝试从未满载的状态下获取下一个待发报文并输出之;后者持续监听着已填充完毕的新鲜资料以便即时响应用户的交互请求[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值