FreeROTS 任务通知和实操 详解

news2025/2/23 7:15:50

目录

什么是任务通知?

任务通知值的更新方式

任务通知的优势和劣势

任务通知的优势

任务通知的劣势

任务通知相关 API 函数

1. 发送通知

2. 等待通知

任务通知实操

1. 模拟二值信号量

2. 模拟计数型信号量

3. 模拟事件标志组

4. 模拟消息邮箱


什么是任务通知?

FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS 官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加 省内存(无需创建队列)。

在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!

任务通知值的更新方式

FreeRTOS 提供以下几种方式发送通知给任务 :

  • 发送消息给任务,如果有通知未读, 不覆盖通知值
  • 发送消息给任务,直接覆盖通知值
  • 发送消息给任务,设置通知值的一个或者多个位
  • 发送消息给任务,递增通知值

通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。

任务通知的优势和劣势

任务通知的优势

  • 1. 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
  • 2. 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。

任务通知的劣势

  • 1. 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
  • 2. 通知只能一对一,因为通知必须指定任务。
  • 3. 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
  • 4. 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保 持一个数据。

任务通知相关 API 函数

1. 发送通知

                        函数                        描述
xTaskNotify()发送通知,带有通知值
xTaskNotifyAndQuery()发送通知,带有通知值并且保留接收任务的原通知值
xTaskNotifyGive()发送通知,不带通知值
xTaskNotifyFromISR()在中断中发送任务通知
xTaskNotifyAndQueryFromISR()在中断中发送任务通知
vTaskNotifyGiveFromISR()在中断中发送任务通知
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                        uint32_t ulValue,
                        eNotifyAction eAction );

参数:

xTaskToNotify:

  • 需要接收通知的任务句柄;

ulValue:

  • 用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;

eAction:

  • 一个枚举,代表如何使用任务通知的值;
                枚举值                                描述
eNoAction发送通知,但不更新值(参数ulValue未使用)
eSetBits被通知任务的通知值按位或ulValue。(某些场景下可代替事 件组,效率更高)
eIncrement被通知任务的通知值增加1(参数ulValue未使用),相当于 xTaskNotifyGive
eSetValueWithOverwrite被通知任务的通知值设置为 ulValue。(某些场景下可代替 xQueueOverwrite ,效率更高)
eSetValueWithoutOverwrite

如果被通知的任务当前没有通知,则被通知的任务的通知值设为ulValue。

如果被通知任务没有取走上一个通知,又接收到了一个通 知,则这次通知值丢弃,在这种情况下视为调用失败并返回 pdFALSE

(某些场景下可代替 xQueueSend ,效率更高)

返回值:

  • 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他情况均返回pdPASS。
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
                                uint32_t ulValue,
                                eNotifyAction eAction,
                                uint32_t *pulPreviousNotifyValue );

参数:

xTaskToNotify:

  • 需要接收通知的任务句柄;

ulValue:

  • 用于更新接收任务通知值, 具体如何更新 由形参 eAction 决定;

eAction:

  • 一个枚举,代表如何使用任务通知的值;

pulPreviousNotifyValue:

  • 对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。

返回值:

  • 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他情况均返回pdPASS。
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

参数:

  • xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。

返回值:

  • 总是返回 pdPASS。

2. 等待通知

等待通知API函数只能用在任务,不可应用于中断中!

                函数                                                描述
ulTaskNotifyTake()获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减 一。当任务通知用作二值信号量或者计数信号量的时候,使用此函数来 获取信号量。
xTaskNotifyWait()获取任务通知,比 ulTaskNotifyTake()更为复杂,可获取通知值和清除通知值的指定位
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
                            TickType_t xTicksToWait );

参数:

xClearCountOnExit:

  • 指定在成功接收通知后,将通知值清零或减 1,
  • pdTRUE:把通知值清零(二值信号量);pdFALSE:把通知值减一(计数型信号量);

xTicksToWait:阻塞等待任务通知值的最大时间;

  • 超时时间,0 表示不超时,
  • portMAX_DELAY表示卡死等待

返回值:

  • 0:接收失败
  • 非0:接收成功,返回任务通知的通知值
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                            uint32_t ulBitsToClearOnExit,
                            uint32_t *pulNotificationValue,
                            TickType_t xTicksToWait );

参数:

ulBitsToClearOnEntry:

  • 函数执行前清零任务通知值那些位 。

ulBitsToClearOnExit:

  • 表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被保存到形参 *pulNotificationValue 中。

pulNotificationValue:

  • 用于保存接收到的任务通知值。 如果不需要使用,则设置为 NULL 即可  

xTicksToWait:等待消息通知的最大等待时间。

  • 超时时间,0 表示不超时,
  • portMAX_DELAY表示卡死等待

任务通知实操

首先得打开CubeMX,将FreeRTOS移植到STM32F103C8T6,具体看我之前写过的文章

将FreeRTOS移植到STM32F103C8T6

1. 模拟二值信号量

(1)创建两个任务和设置按键引脚为输入

(2)设置两个按键分别发送和接收二值信号量

用到函数

  • xTaskNotifyGive()
  • ulTaskNotifyTake()
void StartTaskSend(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				xTaskNotifyGive(TaskReceiveHandle);
				printf("任务通知:模拟二值信号量发送成功\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}

void StartTaskReceive(void const * argument)
{
  uint32_t rev = 0;
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				rev = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
				if(rev != 0)
					printf("任务通知:模拟二值信号量接受成功\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}

(3)打开串口助手,查看结果

2. 模拟计数型信号量

模拟计数型信号量跟模拟二值信号量基本相同:

将ulTaskNotifyTake()函数中第一个参数从pdTRUE改为pdFALSE

(1)代码示例:

用到函数

  • xTaskNotifyGive()
  • ulTaskNotifyTake()
void StartTaskSend(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				xTaskNotifyGive(TaskReceiveHandle);
				printf("任务通知:模拟计数型信号量发送成功\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}

void StartTaskReceive(void const * argument)
{
	uint32_t rev = 0;
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				rev = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);
				if(rev != 0)
					printf("任务通知:模拟计数型信号量接受成功,rev = %d\r\n",rev);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}

(2)打开串口助手,查看结果

3. 模拟事件标志组

(1)代码示例:

用到函数

  • xTaskNotify()
  • xTaskNotifyWait()
void StartTaskSend(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				printf("将bit0位置1\r\n");
				xTaskNotify(TaskReceiveHandle,0x01,eSetBits);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				printf("将bit1位置1\r\n");
				xTaskNotify(TaskReceiveHandle,0x02,eSetBits);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}

void StartTaskReceive(void const * argument)
{
  uint32_t notify_rev = 0, event_bit = 0;
  for(;;)
  {
		xTaskNotifyWait(0,0xFFFFFFFF,&notify_rev,portMAX_DELAY);
		if(notify_rev & 0x01)
		{
			event_bit |= 0x01;
		}
		if(notify_rev & 0x02)
		{
			event_bit |= 0x02;
		}
		if(event_bit == (0x01 | 0x02))
		{
			printf("任务通知模拟事件标志组接收成功\r\n");
			event_bit = 0;
		}
    osDelay(10);
  }
}

(2)打开串口助手,查看结果

4. 模拟消息邮箱

模拟邮箱大概就是向任务发送数据,但是与队列不同,任务邮箱发送消息受到了很多限制。

  • 只能发送一个32位的值。
  • 消息邮箱的值被保存为一个任务的通知值,而且只能保存一个任务的值,相当于队列长度为1

(1)代码示例:

用到函数

  • xTaskNotify()
  • xTaskNotifyWait()
void StartTaskSend(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				printf("按键1按下\r\n");
				xTaskNotify(TaskReceiveHandle,1,eSetValueWithOverwrite);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				printf("按键2按下\r\n");
				xTaskNotify(TaskReceiveHandle,2,eSetValueWithOverwrite);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}

void StartTaskReceive(void const * argument)
{
  uint32_t notify_rev = 0;
  for(;;)
  {
    xTaskNotifyWait(0,0xFFFFFFFF,&notify_rev,portMAX_DELAY);
    printf("接收到的通知值为:%d\r\n",notify_rev);
    osDelay(10);
  }
}

(2)打开串口助手,查看结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1142449.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高防CDN:网络攻防的坚强防线

在当今数字化时代,网络攻击已经成为一种常态,对企业和个人的网络资产构成了严重威胁。为了应对这些风险,高防CDN(Content Delivery Network)已经崭露头角,它不仅提供内容分发,还整合了强大的网络…

电脑上使用的备忘记事软件哪一款好用点?

生活中充斥着大大小小的任务,如工作方面、学习方面、生活方面等,多种事务掺杂交错在一起非常容易忘记,为避免忘记重要的事情,大家可以借助电脑上好用的备忘录工具来进行记事。 支持在电脑上使用的备忘录软件是比较多的&#xff0…

论文阅读 - Learning Human Interactions with the Influence Model

NIPS01 早期模型 要求知识背景: 似然函数,极大似然估计、HMM、期望最大化 目录 1 Introduction 2 The Facilitator Room 3 T h e I n f l u e n c e M o d e l 3 . 1 ( R e ) i n t r o d u c i n g t h e I n f l u e n c e M o d e l 3 . 2 L e…

SpringCloud Alibaba【三】Gateway

Gateway配置与使用 前言新建gateway子项目pom.xml配置文件启动类访问接口方式 测试拓展 前言 在工作中遇到一种情况,一个父项目中有两个子项目。实际使用时,需要外网可以访问,宝信软件只能将一个端口号发布在外网上,所以需要运用…

多线程---线程安全问题及解决

文章目录 一个线程不安全的案例造成线程不安全的原因抢占式执行多个线程修改同一个变量修改操作不是原子的内存可见性问题指令重排序问题 如何让线程变得安全?加锁volatile 一个线程不安全的案例 题目:有较短时间让变量count从0加到10_0000 解决方案&a…

【AD9361 数字接口CMOS LVDS】A CMOS

〇、综述 本章介绍并行数据端口和串行外设接口(SPI),用于在AD9361和BBP之间传输数据和控制/状态信息。 下图显示了这些接口,并提供了AD9361和BBP在宽带无线系统中的使用方式的高级视图。数据接口工作在两种模式之一:标…

LeetCode题:70爬楼梯,126斐波那契数

目录 70:爬楼梯 题目要求: 解题思路:(类似斐波那契数) 递归解法: 非递归解法: 126:斐波那契数 题目要求: 解题思路: 递归解法: 非递归解…

汇总区间(Java)

大家好我是苏麟 , 这篇文章也是凑数的 ... 描述 : 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 n…

Redis(02)| 数据结构-SDS

一、键值对数据库是怎么实现的? 在开始讲数据结构之前,先给介绍下 Redis 是怎样实现键值对(key-value)数据库的。 Redis 的键值对中的 key 就是字符串对象,而 value 可以是字符串对象,也可以是集合数据类型…

vue3 + Element-plus + Echarts 5.2 切换不更新、导出PDF不显示 解决方案

vue3 Element-plus Echarts 5.2 切换不更新、导出PDF不显示 解决方案 1、使用 el-tabs 切换导致 Echarts 不显示问题2、折线图 Echarts 不更新问题3、异常抛出: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading ‘type‘)4、Echa…

OpenHarmony docker环境搭建所见的问题和解决

【摘要】OpenHarmony docker环境搭建需要一台安装Ubuntu的虚拟机,并且虚拟机中需要有VScode。 整个搭建流程请参考这篇博客:OpenHarmony docker环境搭建-云社区-华为云 (huaweicloud.com) 上篇博主是用Ubuntu的服务器进行环境搭建的,在使用VS…

基于单片机的智能清洁小车设计—控制系统设计

收藏和点赞,您的关注是我创作的动力 文章目录 概要 一、研究的主要内容和目标二、总体方案设计2.1智能清洁小车的硬件系统组成2.2智能清洁小车的硬件结构图 三、 小车结构设计5.1基本布局和功能分析5.2小车二维及三维图小车三维图: 四、 原理图程序 五、…

2.OsgEarth封装

环境:Osg3.6.5 OsgEarth3.2 Qt5.15.2 基于qt将osgEarth封装,在Qt中作为GLWidget进行呈现。 1.Earth类的封装 基于地球的初始化顺序进行了封装,并暴露出了一些必要的属性,类似viwer、map、mapNode等。最为重要的是…

Fourier分析导论——第1章——Fourier分析的起源(E.M. Stein R. Shakarchi)

第 1 章 Fourier分析的起源 (The Genesis of Fourier Analysis) Regarding the researches of dAlembert and Euler could one not add that if they knew this expansion, they made but a very imperfect use of it. They were both persuaded that an arbitrary and d…

基于单片机的空气质量检测系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 技术交流认准下方 CSDN 官方提供的联系方式 文章目录 概要 一、主要内容二、系统方案设计2.1 系统方案设计2.2 主控制器模块选择 三、 系统软件设计4.1 程序结构分析4.2系统程序…

汇编学习(1)

汇编、CPU架构、指令集、硬编码之间的关系 ● 汇编语言:这是一种低级语言,用于与硬件直接交互。它是由人类可读的机器码或指令组成的,这些指令告诉CPU如何执行特定的任务。每条汇编指令都有一个对应的机器码指令,CPU可以理解和执…

25 行为型模式-备忘录模式

1 备忘录模式介绍 备忘录模式(memento pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态. 2 备忘录模式原理 3 备忘录模式实现 /*** 发起人角色**/ public class Originator {private Strin…

城中村智能电表改造解决方案

随着我国城市化进程的加快,城中村作为城市发展的矛盾焦点,其居住环境、管理水平等问题日益凸显。城中村用电管理存在着用电安全隐患、电费核算不准确、偷电现象屡禁不止等问题。为了提高城中村用电管理水平,确保用电安全,推进智能…

通过pip,查看tensorflow和tensorflow-probaility版本

查看tensorflow和tensorflow-probability版本 如果在加载tensorflow 和 tensorflow-probablity时,没有成功的话,可以看下这两个包的版本,网上可以搜一下,这两个包版本是否搭配。 从上述信息总可以看到tensorflow包的版本是2.13.0…

DSP开发例程(4): logbuf_print_to_uart

目录 DSP开发例程: logbuf_print_to_uart新建工程源码编辑app.cfgos.cmain.c 调试说明 DSP开发例程: logbuf_print_to_uart SYS/BIOS 提供了 xdc.runtime.Log, xdc.runtime.LoggerBuf 和 xdc.runtime.LoggerSys 这几个模块用于日志记录. 日志信息在 应用程序调试和状态监控中非…