任务通知

news2024/9/23 1:40:57

Q: 什么是任务通知?

A: FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS 官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加省内存(无需创建队列)。 在大多数情况下任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!

任务通知值的更新方式

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

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

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

任务通知的优势和劣势

任务通知的优势

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

任务通知的劣势

  • 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB(TCB可以简易理解为任务创建时在内存中开辟的空间) 。
  • 通知只能一对一,因为通知必须指定任务。
  • 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。(而信号量或者事件标志组就可以阻塞等待
  • 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保 持一个数据。(这也是为什么任务通知只能模拟长度为1的队列

任务通知相关 API 函数

发送通知

发送通知,带有通知值 

 

  • xTaskToNotify:需要接收通知的任务句柄
  • ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定
  • eAction:一个枚举,代表如何使用任务通知的值
  • 返回值: 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE(按照枚举的表格,这种情况可能出现在枚举被设置为“eSetValueWithoutOverWrite”时出现), 而其他情况均返回pdPASS

关于枚举值:

其中,eSetBits可以模拟事件标志组elncrement可以模拟信号量eSetValueWithOverWrite可以模拟为覆写的消息队列eSetValueWithoutOverwrite可以模拟为不覆写的消息队列

发送通知,带有通知值并且保留接收任务的原通知值

  • xTaskToNotify:需要接收通知的任务句柄
  • ulValue:用于更新接收任务通知值, 具体如何更新 由形参 eAction 决定
  • eAction:一个枚举,代表如何使用任务通知的值
  • pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()
  • 返回值: 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE(按照枚举的表格,这种情况可能出现在枚举被设置为“eSetValueWithoutOverWrite”时出现), 而其他情况均返回pdPASS

发送通知,不带通知值

 

  • xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1 (模拟信号量
  • 返回值: 总是返回 pdPASS

等待通知

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

 获取任务通知的函数1

  • xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,pdTRUE:把通知值清零(类似二值信号量);pdFALSE:把通知值减一(类似计数型信号量
  • xTicksToWait:阻塞等待任务通知值的最大时间
  • 返回值: 0:接收失败 ;非0:接收成功,返回任务通知的通知值

 获取任务通知的函数2

 

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

注意“1清零,0不清零”是对于“位”来说,即如果希望全部清零,那应该是0xFFFFFFFF

  • pulNotificationValue:用于保存接收到的任务通知值。 如果 不需要使 用,则设置为 NULL 即可 
  • xTicksToWait:等待消息通知的最大等待时间 

实操演示

在 C:\mjm_CubeMX_proj 路径下,复制一份Cube的母版并重命名为 :mjm_freeRTOS_note:

打开相应的Cube文件:

1. 配置按钮的GPIO:

2. 找到左侧的Middleware --> FREERTOS,然后在下方找到"Task and Queues",然后创建两个任务:

3. 生成代码打开Keil:

模拟二值信号量

#include "stdio.h"

void StartTask_send(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("KEY1 has been pressed\r\n");
				xTaskNotifyGive(Task_receiveHandle); //相当于一个指定了对象,一对一的二值信号量释放,使通知量加1	
				printf("Task note: successfully give\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET); //等待按键松开,防止出现按钮一直按着,就一直删除创建任务			
		} 

    osDelay(1);
  }
}


void StartTask_receive(void const * argument)
{
  uint32_t recv = 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){ //按键消抖
				printf("KEY2 has been pressed\r\n");
				recv = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); //收到通知将通知清零,模拟二值信号量获取
				if(recv != 0){
					printf("Task note: successfully take\r\n");		
				}
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET); //等待按键松开,防止出现按钮一直按着,就一直删除创建任务			
		} 
		
    osDelay(1);
  }
}

实现效果1

打开串口助手:

分别按下KEY1和KEY2: 

但如果再按一下KEY2:

由于模拟二值信号量已经被获取了,再次获取注定是不成功的,且我将等待时间拉到了最长,所以会一直死等,进入接收阻塞

模拟计数型信号量

根据上面所讲,模拟二值和模拟计数信号量的唯一区别,就是将ulTaskNotifyTake()函数的第一个参数从 pdTRUE 改成 pdFALSE

#include "stdio.h"

void StartTask_send(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("KEY1 has been pressed\r\n");
				xTaskNotifyGive(Task_receiveHandle); //相当于一个指定了对象,一对一的二值信号量释放,使通知量加1	
				printf("Task note: successfully give\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET); //等待按键松开,防止出现按钮一直按着,就一直删除创建任务			
		} 

    osDelay(1);
  }
}


void StartTask_receive(void const * argument)
{
  uint32_t recv = 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){ //按键消抖
				printf("KEY2 has been pressed\r\n");
				recv = ulTaskNotifyTake(pdFALSE, portMAX_DELAY); //收到通知将通知减1,模拟计数信号量获取
				if(recv != 0){
					printf("Task note: successfully take\r\n");		
				}
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET); //等待按键松开,防止出现按钮一直按着,就一直删除创建任务			
		} 
		
    osDelay(1);
  }
}

实现效果2

打开串口助手:

按三下KEY1(模拟计数信号量,使得计数量为3)

再按三下KEY2 (使得计数量归0)

最后再按一下KEY2,由于模拟的计数信号量值为0,所以会开始死等,进入接收阻塞

模拟事件标志组

#include "stdio.h"

void StartTask_send(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("KEY1 has been pressed\r\n");
				printf("bit0 has been set to 1\r\n");
				xTaskNotify(Task_receiveHandle, 0x01, eSetBits); //被通知任务的任务值按位或0x01,相当于将bit0置1
				//printf("bit0 has been set to 1\r\n"); 注意这句话不能加在这里,任务通知之后,会立刻跳转到始终等待阻塞的其他任务,这句话的存在可能会导致程序的错误
			}
			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("KEY2 has been pressed\r\n");
				printf("bit1 has been set to 1\r\n");
				xTaskNotify(Task_receiveHandle, 0x02, eSetBits); //被通知任务的任务值按位或0x02,相当于将bit1置1
				//printf("bit1 has been set to 1\r\n"); 注意这句话不能加在这里,任务通知之后,会立刻跳转到始终等待阻塞的其他任务,这句话的存在可能会导致程序的错误
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET); //等待按键松开,防止出现按钮一直按着,就一直删除创建任务			
		}

    osDelay(1);
  }
}


void StartTask_receive(void const * argument)
{
	uint32_t recv = 0;
	uint32_t event_bit = 0; 

  for(;;)
  {
		xTaskNotifyWait(0, 0xFFFFFFFF, &recv, portMAX_DELAY); //函数执行前不清零,将通知值保存到recv中,在函数结束前清零,并一直等待消息通知
		//为什么有8个F,是因为变量类型是32位的整型
		if(recv & 0x01){ //此处其实也可以直接写成 “recv == 0x01”,但是如果代码量大,可能会出现各种状况,“recv & 0x01”的写法是最好且不会出错的,因为这句话就只判断特定的位的值
			event_bit |= 0x01; //将event_bit的 bit0 置1
			printf("bit0 qualified\r\n");
		}
		if(recv & 0x02){
			event_bit |= 0x02; //将event_bit的 bit1 置1
			printf("bit1 qualified\r\n");
		}
		if(event_bit == 0x03){
			printf("good good good\r\n");
			event_bit = 0;
		}
		
    osDelay(1);
  }
}

实现效果3

打开串口助手:

只有KEY1和KEY2都被按下过,才会发送“good good good” 

模拟邮箱

所谓邮箱,其实就是消息的收发,也就是“任务通知”最原始的含义。

其实,和模拟事件标志组的代码差不多,主要区别就是将枚举换成“eSetValueWithOverwrite”或“eSetValueWithoutOverwrite”:

此处使用覆写eSetValueWithOverwrite)或不覆写eSetValueWithoutOverwrite)都可以,因为不管覆不覆写,在我的代码中,只要发送了任意一条通知,接收通知的任务在退出前都会将通知值全部清0,所以实现的效果是一样的。

#include "stdio.h"

void StartTask_send(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("KEY1 has been pressed\r\n");
				printf("bit0 has been set to 1\r\n");
				xTaskNotify(Task_receiveHandle, 0x01, eSetValueWithOverwrite); //被通知任务的任务值按位或0x01,相当于将bit0置1		
			}
			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("KEY2 has been pressed\r\n");
				printf("bit1 has been set to 1\r\n");
				xTaskNotify(Task_receiveHandle, 0x02, eSetValueWithOverwrite); //被通知任务的任务值按位或0x02,相当于将bit1置1
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET); //等待按键松开,防止出现按钮一直按着,就一直删除创建任务			
		}

    osDelay(1);
  }
}


void StartTask_receive(void const * argument)
{
  uint32_t recv = 0; 

  for(;;)
  {
		xTaskNotifyWait(0, 0xFFFFFFFF, &recv, portMAX_DELAY); //函数执行前不清零,将通知值保存到recv中,在函数结束前清零,并一直等待消息通知
		//为什么有8个F,是因为变量类型是32位的整型
		printf("msg received, saying\"%d\"\r\n",recv);
		
    osDelay(1);
  }
}

实现效果4

打开串口助手:

按下KEY1: 

按下KEY2:

就像任务一在向任务二发邮件一样。 

 

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

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

相关文章

图为科技加入深圳市智能交通行业协会 ,打 …

图为科技加入深圳市智能交通行业协会,打造智能交通新生态! 交通是国民经济发展的“大动脉”,交通拥堵、事故频发等问题不仅影响了人们的出行体验,也对经济的发展产生了负面影响。安全、高效、便捷的出行,一直是人们的…

策略路由实现多ISP接入Internet

组网需求&#xff1a; 企业分别从ISP1和ISP2租用了一条链路 PC3用户上网访问Server1时走ISP1PC4用户上网访问Server1时走ISP2 拓扑图 一、ISP1 运营商 R1路由器 <Huawei>sys [Huawei]sys R1 [R1]un in en[R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip addr 2.2.2.2 2…

【电影推荐系统】数据爬取、数据加载进MongoDB数据库

概览 本篇主要介绍数据来源、数据加载进数据库过程 1 数据获取 使用Scrapy爬取豆瓣电影数据&#xff0c;然后利用movielens数据集来造一份rating数据。 1.1 数据集获取 数据集获取&#xff1a;选取movielens 数据集&#xff1a;movielens官网数据集包括&#xff1a;movies…

【计算机网络】网络基础(上)

文章目录 1. 网络发展认识协议 2.网络协议初识协议分层OSI七层模型 | TCP/IP网络传输基本流程情况1&#xff1a;同一个局域网(子网)数据在两台通信机器中如何流转协议报头的理解局域网通信原理(故事版本)一般原理数据碰撞结论 1. 网络发展 计算工作是不可能一个科学家搞出来的…

机器学习(一)---概述

文章目录 1.人工智能、机器学习、深度学习2.机器学习的工作流程2.1 获取数据集2.2 数据基本处理2.3 特征工程2.3.1 特征提取2.3.2 特征预处理2.3.3 特征降维 2.4 机器学习2.5 模型评估 3.机器学习的算法分类3.1 监督学习3.1.1 回归问题3.1.2 分类问题 3.2 无监督学习 1.人工智能…

FastAPI 教程、结合vue实现前后端分离

英文版文档&#xff1a;https://fastapi.tiangolo.com/ 中文版文档&#xff1a;https://fastapi.tiangolo.com/zh/ 1、FastAPI 教程 简 介 FastAPI 和 Sanic 类似&#xff0c;都是 Python 中的异步 web 框架。相比 Sanic&#xff0c;FastAPI 更加的成熟、社区也更加的活跃。 …

8.1作业

文件IO函数实现拷贝文件。子进程先拷贝后半部分&#xff0c;父进程再拷贝前半部分&#xff0c;允许使用sleep函数 #include<stdio.h> #include<string.h> #include<stdlib.h> #include<head.h> int main(int argc, const char *argv[]) {pid_t cpidfo…

构建语言模型:BERT 分步实施指南

学习目标 了解 BERT 的架构和组件。了解 BERT 输入所需的预处理步骤以及如何处理不同的输入序列长度。获得使用 TensorFlow 或 PyTorch 等流行机器学习框架实施 BERT 的实践知识。了解如何针对特定下游任务(例如文本分类或命名实体识别)微调 BERT。为什么我们需要 BERT? 正…

保护云数据库实用指南

在数字化转型时代&#xff0c;越来越多的企业将运营转移到云端&#xff0c;导致对云数据库的依赖越来越大。虽然它们提供了可扩展性和可访问性等显着优势&#xff0c;但它们也带来了独特的安全挑战&#xff0c;需要解决这些挑战以保护敏感数据免受各种威胁。 在本文中&#x…

巨人互动|Facebook海外户Facebook page的功能以及如何应用

Facebook Page是Facebook平台上的一种工具&#xff0c;是企业、品牌、机构、公益组织等组织和个人创建社交媒体宣传页面的主要方式之一&#xff0c;通过Page可以将内容传播到更广泛的受众群体&#xff0c;提高品牌知名度和曝光率。下面将详述Facebook Page的功能以及如何应用。…

第125天:内网安全-隧道技术SMBICMP正反向连接防火墙出入规则上线

知识点 #知识点&#xff1a; 1、入站规则不出网上线方案 2、出站规则不出网上线方案 3、规则-隧道技术-SMB&ICMP-隧道技术&#xff1a;解决不出网协议上线的问题&#xff08;利用出网协议进行封装出网&#xff09; -代理技术&#xff1a;解决网络通讯不通的问题&#xff0…

Redis 集群 (cluster)

是什么 官网&#xff1a;Redis cluster specification | Redis 由于数据量过大&#xff0c;单个Master复制集难以承担&#xff0c;因此需要对多个复制集进行集群&#xff0c;形成水平扩展每个复制集只负责存储整个数据集的一部分&#xff0c;这就是Redis的集群&#xff0c;其作…

递归——在运行的过程中调用自己

递归&#xff0c;就是在运行的过程中调用自己 递归必须要有三个要素&#xff1a; ①、边界条件 ②、递归前进段 ③、递归返回段 当边界条件不满足时&#xff0c;递归前进&#xff1b;当边界条件满足时&#xff0c;递归返回。 递归关键&#xff1a;写出递推公式&#xff0c;找…

maven里面没有plugins dependence问题解决

说明&#xff1a;今天在做Nacos、Dubbo整合的时候&#xff0c;在父模块中做了版本限制&#xff0c;出错后就又把版本控制什么都删掉&#xff0c;回退到最开始的状态&#xff0c;此时父模块下面的服务右侧的 maven里面没有plugins dependence &#xff0c;然后项目全都报错。 问…

openSUSE安装虚拟化 qemu kvm

1) 第一种&#xff1a;图形界面yast安装虚拟化 左下角开始菜单搜索yast 点一下就能安装&#xff0c;是不是很简单呢 2&#xff09;第二种&#xff1a; 命令行安装 网上关于openSUSE安装qemu kvm的教程比较少&#xff0c;可以搜索centos7 安装qemu kvm的教程&#xff0c;然后…

/bin/bash: Resource temporarily unavailable

有现场反馈plsql无法连接数据库了&#xff0c;登录环境查看时发现从root切换到grid时报错/bin/bash: Resource temporarily unavailable [rootdb1 ~]# su - grid Last login: Thu Jul 27 18:45:04 CST 2023 su: failed to execute /bin/bash: Resource temporarily unavailab…

Ajax快速入门

Ajax Ajax就是前端访问服务器端数据的一个技术 还有主要就是异步交互 就是在不刷新整页面的情况下&#xff0c;和服务器交换部分我也数据 比如搜索的联想技术 同步和异步的概念 一个是客户端需要等待服务器完成处理&#xff0c;才能进行别的事 一个是客户端不需要等待服务器处…

剑指offer刷题笔记--Num61-68

1--扑克牌中的顺子&#xff08;61&#xff09; 主要思路&#xff1a; 五个数是顺子的充要条件&#xff1a;① 最大值 - 最小值 < 5&#xff08;大小王除外&#xff09;&#xff1b;② 没有出现重复的值&#xff08;大小王除外&#xff09;&#xff1b; 判断是否出现重复的值…

Centos虚拟机修改密码

1.重启系统 2.在这个选择界面&#xff0c;按e 3.找到如下位置&#xff0c;插入init/bin/sh 4.填写完成后按Ctrlx引导启动 5.输入mount -o remount, rw / (注意空格) 6.重置密码 出现以下为重置成功 7.执行touch /.autorelabel 8.退出exec /sbin/init 9.输入你的新密码…

linux系统编程重点复习--进程的控制

目录 1 复习目标 2 进程相关概念 2.2 并行和并发 2.3 PCB-进程控制块 2.4 进程状态(面试考) 3 创建进程 3.2 ps命令和kill命令 3.3 getpid/getppid 3.4 练习题 4 exec函数族 4.1 函数作用和函数介绍 4.1.1 execl函数 4.1.2 execlp函数 4.2 exec函数族原理介绍 4.3 …