FreeRTOS教程4 消息队列

这篇具有很好参考价值的文章主要介绍了FreeRTOS教程4 消息队列。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、准备材料

正点原子stm32f407探索者开发板V2.4

STM32CubeMX软件(Version 6.10.0)

Keil µVision5 IDE(MDK-Arm)

野火DAP仿真器

XCOM V2.6串口助手

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、阻塞状态

当出现下面几种情况时,任务会进入阻塞状态

  1. 当某个任务向队列写入数据,但是被写的队列已满时,任务将进入阻塞状态等待队列出现新的位置
  2. 当某个任务从队列读取数据,但是被读的队列是空时,任务将进入阻塞状态等待队列出现新的数据

当出现下面几种情况时,任务会退出阻塞状态

  1. 进入阻塞状态的任务达到设置的阻塞超时时间之后会退出阻塞状态
  2. 向满队列中写数据的任务等到队列中出现新的位置
  3. 从空队列中读数据的任务等到队列.中出现新的数据

当存在多个任务处于阻塞状态时,如果同时满足解除阻塞的条件,则所有等待任务中 优先级最高的任务 或者 优先级均相同但等待最久的任务 将被解除阻塞状态

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、实验目标

  1. 创建一个用于任务间、任务与中断间信息传输的深度为 10 的队列 TEST_QUEUE
  2. 创建一个任务 TASK_SEND 实现按键扫描响应,当 KEY2、KEY1、KEY0 按键按下时分别向队列 TEST_QUEUE 中发送不同消息
  3. 创建一个任务 TASK_RECEIVE 实现从队列 TEST_QUEUE 中接收信息,根据接收到的不同信息通过串口输出不同内容
  4. 启动一个 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

参考资料

STM32Cube高效开发教程(基础篇)

Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf文章来源地址https://www.toymoban.com/news/detail-839703.html

到了这里,关于FreeRTOS教程4 消息队列的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FreeRTOS - 消息队列

    消息队列(queue):可以在任务与任务间、中断和任务间传递消息,实现任务接收来自其他任务或中断的不固定的消息 1、使用消息队列检测串口输入 2、通过串口发送字符串openled1,openled2,openled3,分别打开板载led1,led2,led3 3、通过串口发送字符串closeled1,closeled2,closele

    2023年04月08日
    浏览(47)
  • FreeRTOS 消息队列 详解

    目录 什么是队列? 消息队列特点 1. 数据入队出队方式 2. 数据传递方式 3. 多任务访问 4. 出队、入队阻塞 消息队列相关 API 函数 1. 创建队列 2. 写队列 3. 读队列 消息队列实操 队列又称 消息队列 ,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任 务

    2024年02月08日
    浏览(43)
  • 【FreeRTOS】【应用篇】消息队列【下篇】

    本篇文章主要对 FreeRTOS 中消息队列的概念和相关函数进行了详解 消息队列【下篇】详细剖析了消息队列中发送、接收时队列消息控制块中各种指针的行为,以及几个发送消息和接收消息的函数的运作流程 笔者有关于 【FreeRTOS】【应用篇】消息队列【上篇】——队列基本概念

    2024年02月10日
    浏览(40)
  • FreeRTOS源码分析-7 消息队列

    目录 1 消息队列的概念和作用 2 应用 2.1功能需求 2.2接口函数API 2.3 功能实现 3 消息队列源码分析 3.1消息队列控制块 3.2消息队列创建 3.3消息队列删除 3.4消息队列在任务中发送 3.5消息队列在中断中发送 3.6消息队列在任务中接收 3.7消息队列在中断中接收  消息队列(queue),可

    2024年02月14日
    浏览(39)
  • 【FreeRTOS】详细讲解FreeRTOS中消息队列并通过示例讲述其用法

      在裸机系统中,两个程序间需要共享某个资源通常使用全局变量来实现;但在含操作系统(下文就拿 FreeRTOS 举例)的开发中,则使用消息队列完成。那么这两者有啥区别呢?🤔🤔🤔   其实在FreeRTOS系统中也能够像裸机似的,使用全局变量实现多程序共享某个资源(这里

    2024年02月09日
    浏览(40)
  • FreeRTOS通过消息队列实现串口命令解析(串口中断)

    作者:Jack_G 时间:2023.08.08 版本:V1.0 上次修改时间: 环境: quad quad quad quad STM32Cube MX V6.8.1 quad quad quad quad STM32CubeH7 Firmware Package V1.11.0 / 04-Nov-2022 quad quad quad quad Keil: V5.29 正常配置,不过 需要勾选全局中断 ,后续在接收中断中将接收到的数据送入消息队列。 在usa

    2024年02月14日
    浏览(36)
  • 正点原子STM32嵌入式学习-keil5安装教程

    前言:本人没有什么嵌入式的经验,但是看到硬件的同事做开发板比较好玩,比较感兴趣。刚好有这样一个机会,可以跟随《原子教你玩STM32(库函数版)》课程线下学习,在此,将本次课程的学习做一个记录。相信对我这样一个小白来说,帮助还是会非常大的。 目录 一.下

    2024年03月10日
    浏览(78)
  • 【STM32】FreeRTOS消息队列和信号量学习

    一、消息队列(queue) 队列是一种用于实现任务与任务之间,任务与中断之间消息交流的机制。 注意:1.数据的操作是FIFO模式。 2.队列需要明确数据的大小和队列的长度。 3.写和读都会出现堵塞。 实验:创建一个消息队列,两个发送任务,一个接收任务。 其中任务一任务三

    2024年02月13日
    浏览(40)
  • FreeRTOS中断调用API消息队列发送函数导致系统死机(memcpy函数卡死)

    背景:写一组在FreeRTOS系统下的串口驱动 ,芯片使用的是杰发科的 AC781x系列 , ARM® CortexM3 内核,96MHz主频。 项目场景:计划使用dma接收数据,设置dma半满中断与全满中断,在半满中断中把前半部分数据传入消息队列,在全满中断中把后半部分数据传入消息队列。 问题1: 在中

    2024年02月15日
    浏览(47)
  • 用正点原子STM32F103ZET6精英板控制舵机转动实验保姆教程

    正点原子精英板一块 Jlink4线SWD下载线一根 精英板usb供电/下载线一根(负责给开发板供电) SG90舵机一个(三线:其中红线接5V电压,棕线接GED,橙黄色为信号线接开发板PB5引脚,舵机所给为3pin杜邦线母头,可用单根杜邦线(一头子一头母)将舵机线飞开接到精英板上)。 以

    2023年04月15日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包