FreeRTOS教程4 消息队列
1、准备材料
STM32CubeMX软件(Version 6.10.0)
Keil µVision5 IDE(MDK-Arm)
2、学习目标
本文主要学习 FreeRTOS 消息队列的相关知识,包括消息队列概述、创建删除复位队列、写入/读取数据到队列等关于队列的基础知识
3、前提知识
3.1、什么是消息队列?
在一个实时操作系统构成的完整项目中一般会存在多个任务和中断,多个任务之间、任务与中断之间往往需要进行通信, FreeRTOS 中所有的通信与同步机制都是基于队列来实现的,我们可以把队列结构想象成如下图所示样子

在实际使用中,队列深度以及队列中数据类型都可以由用户自定义,消息队列是一个共享的存储区域,其可以被多个进程写入数据,同时也可以被多个进程读取数据,为了让接收任务知道数据的来源,以确定数据应该如何处理,通常可以使用单个队列来传输具有两者的结构数据的值和结构字段中包含的数据源,如下图所示

3.2、创建队列
队列在使用前必须先创建,和创建任务类似, FreeRTOS 也提供了动态或静态内存分配创建队列两个 API 函数,具体函数声明如下所示
/**
* @brief 动态分配内存创建队列函数
* @param uxQueueLength:队列深度
* @param uxItemSize:队列中数据单元的长度,以字节为单位
* @retval 返回创建成功的队列句柄,如果返回NULL则表示因内存不足创建失败
*/
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
/**
* @brief 静态分配内存创建队列函数
* @param uxQueueLength:队列深度
* @param uxItemSize:队列中数据单元的长度,以字节为单位
* @param pucQueueStorageBuffer:队列栈空间数组
* @param pxQueueBuffer:指向StaticQueue_t类型的用于保存队列数据结构的变量
* @retval 返回创建成功的队列句柄,如果返回NULL则表示因内存不足创建失败
*/
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t *pucQueueStorageBuffer,
StaticQueue_t *pxQueueBuffer);
/*example:创建一个深度为5,队列单元占uint16_t大小队列*/
QueueHandle_t QueueHandleTest;
QueueHandleTest = xQueueCreate(5, sizeof(uint16_t));
3.3、向队列写入数据
任务或者中断向队列写入数据称为发送消息。通常情况下,队列被作为 FIFO(先入先出)使用,即数据由队列尾部进入,从队列首读出,当然可以通过更改写入方式将队列作为 LIFO(后入先出)使用,向队列中写入数据主要有三组 FreeRTOS API 函数,具体如下所示
/**
* @brief 向队列后方发送数据(FIFO先入先出)
* @param xQueue:要写入数据的队列句柄
* @param pvItemToQueue:要写入的数据
* @param xTicksToWait:阻塞超时时间,单位为节拍数,portMAXDELAY表示无限等待
* @retval pdPASS:数据发送成功,errQUEUE_FULL:队列满无法写入
*/
BaseType_t xQueueSend(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
/**
* @brief 向队列后方发送数据(FIFO先入先出),与xQueueSend()函数一致
*/
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
/**
* @brief 向队列前方发送数据(LIFO后入先出)
*/
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
/**
* @brief 以下三个函数为上述三个函数的中断安全版本
* @param pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
*/
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken)
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken);
另外还有一组稍微特殊的向队列写入数据的 FreeRTOS API 函数,这组函数只用于队列长度为 1 的队列,在队列已满时会覆盖掉队列原来的数据,具体如下所述
/**
* @brief 向长度为1的队发送数据
* @param xQueue:要写入数据的队列句柄
* @param pvItemToQueue:要写入的数据
* @retval pdPASS:数据发送成功,errQUEUE_FULL:队列满无法写入
*/
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);
/**
* @brief 以下函数为上述函数的中断安全版本
* @param pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
*/
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken);
3.4、从队列接收数据
任务或者中断从队列中读取数据称为接收消息。从队列中读取数据主要有两组 FreeRTOS API 函数,具体如下所示
/**
* @brief 从队列头部接收数据单元,接收的数据同时会从队列中删除
* @param xQueue:被读队列句柄
* @param pvBuffer:接收缓存指针
* @param xTicksToWait:阻塞超时时间,单位为节拍数
* @retval pdPASS:数据接收成功,errQUEUE_FULL:队列空无读取到任何数据
*/
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait);
/**
* @brief 从队列头部接收数据单元,不从队列中删除接收的单元
*/
BaseType_t xQueuePeek(QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait);
/**
* @brief 以下两个函数为上述两个函数的中断安全版本
* @param pxHigherPriorityTaskWoken:用于通知应用程序编写者是否应该执行上下文切换
*/
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, void *pvBuffer);
3.5、查询队列
FreeRTOS 还提供了一些用于查询队列当前有效数组单元个数和剩余可用空间数的 API 函数,具体如下所述
/**
* @brief 查询队列剩余可用空间数
* @param xQueue:被查询的队列句柄
* @retval 返回队列中可用的空间数
*/
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue);
/**
* @brief 查询队列有效数据单元个数
* @param xQueue:被查询的队列句柄
* @retval 当前队列中保存的数据单元个数
*/
UBaseType_t uxQueueMessagesWaiting(const QueueHandle_t xQueue);
/**
* @brief 查询队列有效数据单元个数函数的中断安全版本
*/
UBaseType_t uxQueueMessagesWaitingFromISR(const QueueHandle_t xQueue);
3.6、阻塞状态
当出现下面几种情况时,任务会进入阻塞状态
- 当某个任务向队列写入数据,但是被写的队列已满时,任务将进入阻塞状态等待队列出现新的位置
- 当某个任务从队列读取数据,但是被读的队列是空时,任务将进入阻塞状态等待队列出现新的数据
当出现下面几种情况时,任务会退出阻塞状态
- 进入阻塞状态的任务达到设置的阻塞超时时间之后会退出阻塞状态
- 向满队列中写数据的任务等到队列中出现新的位置
- 从空队列中读数据的任务等到队列.中出现新的数据
当存在多个任务处于阻塞状态时,如果同时满足解除阻塞的条件,则所有等待任务中 优先级最高的任务 或者 优先级均相同但等待最久的任务 将被解除阻塞状态
3.7、删除队列
/**
* @brief 删除队列
* @param pxQueueToDelete:要删除的队列句柄
* @retval None
*/
void vQueueDelete(QueueHandle_t pxQueueToDelete);
3.8、复位队列
/**
* @brief 将队列重置为其原始空状态
* @param xQueue:要复位的队列句柄
* @retval pdPASS(从FreeRTOS V7.2.0之后)
*/
BaseType_t xQueueReset(QueueHandle_t xQueue);
3.9、队列读写过程
如下图展示了用作 FIFO 的队列写入和读取数据的情况的具体过程 (注释1)

4、实验一:尝试队列基本操作
4.1、实验目标
- 创建一个用于任务间、任务与中断间信息传输的深度为 10 的队列 TEST_QUEUE
- 创建一个任务 TASK_SEND 实现按键扫描响应,当 KEY2、KEY1、KEY0 按键按下时分别向队列 TEST_QUEUE 中发送不同消息
- 创建一个任务 TASK_RECEIVE 实现从队列 TEST_QUEUE 中接收信息,根据接收到的不同信息通过串口输出不同内容
- 启动一个 RTC 周期唤醒中断,每隔 1s 向队列 TEST_QUEUE 中发送一条消息
4.2、CubeMX相关配置
首先读者应按照 “FreeRTOS教程1 基础知识” 章节配置一个可以正常编译通过的 FreeRTOS 空工程,然后在此空工程的基础上增加本实验所提出的要求
本实验需要初始化开发板上 KEY2、KEY1和KEY0 用户按键做普通输入,具体配置步骤请阅读“STM32CubeMX教程3 GPIO输入 - 按键响应”,注意虽开发板不同但配置原理一致,如下图所示

本实验需要初始化 USART1 作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”,如下图所示

本实验需要配置 RTC 周期唤醒中断,具体配置步骤和参数介绍读者可阅读”STM32CubeMX教程10 RTC 实时时钟 - 周期唤醒、闹钟A/B事件和备份寄存器“实验,此处不再赘述,这里参数、时钟配置如下图所示


由于需要在 RTC 周期唤醒中断中使用 FreeRTOS 的 API 函数,因此 RTC 周期唤醒中断的优先级应该设置在 15~5 之间,此处设置为 7 ,具体如下图所示

单击 Middleware and Software Packs/FREERTOS,在 Configuration 中单击 Tasks and Queues 选项卡,首先双击默认任务修改其参数,然后单击 Add 按钮按要求增加另外一个任务,具体如下图所示

然后在下方单击 Add 按钮增加一个深度为 10 的队列,具体如下图所示

配置 Clock Configuration 和 Project Manager 两个页面,接下来直接单击 GENERATE CODE 按钮生成工程代码即可
4.3、添加其他必要代码
按照 “STM32CubeMX教程9 USART/UART 异步通信” 实验 “6、串口printf重定向” 小节增加串口 printf 重定向代码,具体不再赘述
首先应该在 freertos.c 中添加使用到的头文件,如下所述
#include "stdio.h"
#include "queue.h"
然后在 rtc.c 文件下方重新实现 RTC 的周期唤醒回调函数,在该函数体内发送数据 ”9“ 到队列 TEST_QUEUE 中,具体如下所述
/*周期唤醒回调函数*/
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
uint16_t key_value = 9;
BaseType_t pxHigherPriorityTaskWoken;
//向队列中发送数据,中断安全版本
xQueueSendToBackFromISR(TEST_QUEUEHandle, &key_value, &pxHigherPriorityTaskWoken);
//进行上下文切换
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
}
最后在 freertos.c 中添加任务函数体内代码即可,任务 TASK_SEND 负责当有按键按下时发送不同的数据到队列 TEST_QUEUE 中,任务 TASK_RECEIVE 则负责当队列中有数据时从队列中读取数据并通过串口输出给用户 ,具体如下所述
/*发送任务*/
void TASK_SEND(void *argument)
{
/* USER CODE BEGIN TASK_SEND */
uint16_t key_value = 0;
/* Infinite loop */
for(;;)
{
key_value = 0;
//按键KEY2按下
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
key_value = 3;
//按键KEY1按下
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
key_value = 2;
//按键KEY0按下
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_RESET)
key_value = 1;
//如果有按键按下
if(key_value != 0)
{
BaseType_t err = xQueueSendToBack(TEST_QUEUEHandle, &key_value, pdMS_TO_TICKS(50));
if(err == errQUEUE_FULL)
{
xQueueReset(TEST_QUEUEHandle);
}
//按键消抖
osDelay(300);
}
else
osDelay(10);
}
/* USER CODE END TASK_SEND */
}
/*接收任务*/
void TASK_RECEIVE(void *argument)
{
/* USER CODE BEGIN TASK_RECEIVE */
UBaseType_t msgCount=0,freeSpace=0;
uint16_t key_value=0;
/* Infinite loop */
for(;;)
{
msgCount = uxQueueMessagesWaiting(TEST_QUEUEHandle);
freeSpace = uxQueueSpacesAvailable(TEST_QUEUEHandle);
BaseType_t result = xQueueReceive(TEST_QUEUEHandle, &key_value, pdMS_TO_TICKS(50));
if(result != pdTRUE)
continue;
printf("msgCount: %d, freeSpace: %d, key_value: %d\r\n", (uint16_t)msgCount, (uint16_t)freeSpace, key_value);
osDelay(100);
}
/* USER CODE END TASK_RECEIVE */
}
4.4、烧录验证
烧录程序,打开串口助手,可以发现每隔一定时间 TASK_RECEIVE 任务会从队列中接收到 ”9“,当按键 KEY2 按下时 TASK_SEND 任务向队列中发送 ”3“,同时 TASK_RECEIVE 任务会从队列中接收到 ”3“ 表示任务 TASK_SEND 发送成功,同理按键 KEY1 按下时发送接收 ”2“ ,按键 KEY0 按下时发送接收 ”1“ ,整个过程串口输出信息如下图所示

5、注释详解
注释1:图片来源于 Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf
参考资料
Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf
FreeRTOS教程4 消息队列的更多相关文章
- FreeRTOS 消息队列
以下基础内容转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 的一个重要的通信机制----消息队列,初学者要熟练掌握,因为消息 ...
- FreeRTOS消息队列
FreeRTOS 的一个重要的通信机制----消息队列,消息队列在实际项目中应用较多. 一.消息队列的作用及概念: 消息队列就是通过 RTOS 内核提供的服务,任务或中断服务子程序可以将一个消息(注意 ...
- 【freertos】010-消息队列概念及其实现细节
目录 前言 10.1 消息队列概念 10.2 消息队列的数据传输机制 10.3 消息队列的阻塞访问机制 10.4 消息队列使用场景 10.5 消息队列控制块 10.5.1 队列控制块源码 10.5.2 ...
- RabbitMQ消息队列系列教程(一)认识RabbitMQ
摘要 RabbitMQ是最为流行的消息中间件,是处理高并发业务的利器.本系列教程,将跟大家一起学习RabbitMQ. 目录 RabbitMQ是什么? RabbitMQ的特点是什么? 一.RabbitM ...
- Python消息队列工具 Python-rq 中文教程
原创文章,作者:Damon付,如若转载,请注明出处:<Python消息队列工具 Python-rq 中文教程>http://www.tiangr.com/python-xiao-xi-du ...
- 继续学习freertos消息队列
写在前面:杰杰这个月很忙~所以并没有时间更新,现在健身房闭馆装修,晚上有空就更新一下!其实在公众号没更新的这段日子,每天都有兄弟在来关注我的公众号,这让我受宠若惊,在这里谢谢大家的支持啦!!谢谢^ 在 ...
- RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较
原文:RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较 分享一个朋友的人工智能教程.比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看. 这是网上的一篇教程写的很好,不知原作 ...
- 【FreeRTOS学习04】小白都能懂的 Queue Management 消息队列使用详解
消息队列作为任务间同步扮演着必不可少的角色: 相关文章 [FreeRTOS实战汇总]小白博主的RTOS学习实战快速进阶之路(持续更新) 文章目录 相关文章 1 前言 2 xQUEUE 3 相关概念 3 ...
- ActiveMQ基础教程(四):.net core集成使用ActiveMQ消息队列
接上一篇:ActiveMQ基础教程(三):C#连接使用ActiveMQ消息队列 这里继续说下.net core集成使用ActiveMQ.因为代码比较多,所以放到gitee上:https://gitee ...
- Kafka基础教程(四):.net core集成使用Kafka消息队列
.net core使用Kafka可以像上一篇介绍的封装那样使用(Kafka基础教程(三):C#使用Kafka消息队列),但是我还是觉得再做一层封装比较好,同时还能使用它做一个日志收集的功能. 因为代码 ...
随机推荐
- 从嘉手札<2024-1-10>
冬月初零 年岁缭绕 秋月无影 倏尔迢迢 暗章难牧 纵使再怎么保有年少飞扬的内心 时光仍带去了我二十六年的光阴 出乎意料的收到了很多人的祝福 可喜的是 仍有不少人记挂着我 于我而言 无疑是莫大的荣幸和欣 ...
- C#/.NET/.NET Core优秀项目和框架2024年1月简报
前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍.功能特点.使用方式以及部分功能截图 ...
- P3730 曼哈顿交易 题解
题目链接:曼哈顿交易 比较容易想的题,观察下首先不带修改,考虑维护的东西:次数作为权值,这玩意很显然很难在线维护,考虑下离线算法.看到这种和次数有关的权值,典型的单点加入和删除是非常好找到变化的,那么 ...
- HarmonyOS 实战小项目开发(二)
HarmonyOS 实战小项目开发(二) 日常逼逼叨 在上期实战项目一中,已经对于练手项目的背景,后端搭建等做了一定的简述,那么本期将结合HarmonyOS 页面搭建个人性格测试的移动端.如有一些错误 ...
- 【scikit-learn基础】--『分类模型评估』之评估报告
分类模型评估时,scikit-learn提供了混淆矩阵和分类报告是两个非常实用且常用的工具.它们为我们提供了详细的信息,帮助我们了解模型的优缺点,从而进一步优化模型. 这两个工具之所以单独出来介绍,是 ...
- TDBLookupComboboxEh 一些设置项,自己总结
注意:如果top_seller_nick有重复的值的时候,keyfield 也为top_seller_nick的话,就会造成,选中最下面的那个阿里巴巴的,默认也是第一个天猫的各项值. 因为选后是根据k ...
- JS 数组中找到与目标值最接近的数字,记一次工作中关于二分查找的算法优化
壹 ❀ 引 在最近的工作中,有一个任务是需要修复富文本编辑器字号显示的BUG.大概情况就是,从WPS中复制不同样式的标题.正文到到项目编辑器中,发现没办法设置选中的文本为正文:而且字体字号都显示为默认 ...
- Centos7安装MySQL5.7和Redis6.0流水账
安装mysql 使用rpm包安装 yum remove mariadb-libs.x86_64 yum install perl rpm -ivh mysql-community-common-5.7 ...
- 服务端渲染SSR的理解
服务端渲染SSR的理解 SSR服务端渲染Server Side Render就是当进行请求时,页面上的内容是通过服务端渲染生成的,浏览器直接显示服务端返回的HTML即可. 客户端渲染CSR 通常在构建 ...
- 构建SatelliteRpc:基于Kestrel的RPC框架(整体设计篇)
背景 之前在.NET 性能优化群内交流时,我们发现很多朋友对于高性能网络框架有需求,需要创建自己的消息服务器.游戏服务器或者物联网网关.但是大多数小伙伴只知道 DotNetty,虽然 DotNetty ...