首页 > 其他分享 >FreeRTOS 快速入门(八)之任务通知

FreeRTOS 快速入门(八)之任务通知

时间:2024-08-26 09:24:22浏览次数:14  
标签:eAction 入门 FreeRTOS 通知 函数 任务 接收 ulValue

目录


一、任务通知

1、基本概念

FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能,每个任务都有 一个 32 位 的通知值,在大多数情况下,任务通知可以 替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)。

相对于以前使用 FreeRTOS 内核通信的资源,必须创建队列、二进制信号量、计数信号量或事件组的情况,使用任务通知显然更灵活。按照 FreeRTOS 官方的说法,使用任务通知比通过信号量等 ICP 通信方式解除阻塞的任务要快 45%,并且更加省 RAM 内存空间(使用 GCC 编译器,-o2 优化级别),任务通知的使用无需创建队列。

想要使用任务通知,必须将 FreeRTOSConfig.h 中的宏定义 configUSE_TASK_NOTIFICATIONS 设置为 1,其实FreeRTOS 默认是为 1 的,所以任务通知是默认使能的。

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

  • 发送通知给任务, 如果有通知未读,不覆盖通知值。
  • 发送通知给任务,直接覆盖通知值。
  • 发送通知给任务,设置通知值的一个或者多个位 ,可以当做事件组来使用。
  • 发送通知给任务,递增通知值,可以当做计数信号量使用。

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

2、优势及限制

任务通知的优势:

  • 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都有大的优势。
  • 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。

任务通知的限制:

  • 不能发送数据给 ISR:ISR 并没有任务结构体,所以无法使用任务通知的功能给 ISR 发送数据。但是 ISR 可以使用任务通知的功能,发数据给任务。
  • 数据只能给该任务独享
  • 无法缓冲数据
  • 无法广播给多个任务
  • 如果发送受阻,发送方无法进入阻塞状态等待

3、通知状态和通知值

每个任务都有一个结构体:TCB(Task Control Block),里面有 2 个成员:

  • 一个是 uint8_t 类型,用来表示通知状态
  • 一个是 uint32_t 类型,用来表示通知值
typedef struct tskTaskControlBlock
{
	......
	/* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
	volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
	volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
	......
} tskTCB;

通知状态有 3 种取值:

  • taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
  • taskWAITING_NOTIFICATION:任务在等待通知
  • taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为 pending(有数据了,待处理)
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /* 也是初始状态 */
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )

通知值可以有很多种类型:

  • 计数值
  • 位(类似事件组)
  • 任意数值

二、任务通知的使用

使用任务通知,可以实现轻量级的队列(长度为 1)、邮箱(覆盖的队列)、计数型信号量、二进制信号量、事件组。

任务通知有两套函数,简化版、专业版,列表如下:

  • 简化版函数的使用比较简单,它实际上也是使用专业版函数实现的
  • 专业版函数支持很多参数,可以实现很多功能
简化版专业版
发出通知xTaskNotifyGive
vTaskNotifyGiveFromISR
xTaskNotify
xTaskNotifyFromISR
取出通知ulTaskNotifyTakexTaskNotifyWait

1、xTaskNotifyGive/ulTaskNotifyTake

在任务中使用 xTaskNotifyGive 函数,在 ISR 中使用 vTaskNotifyGiveFromISR 函数,都是直接给其他任务
发送通知:

  • 使得通知值加一
  • 并使得通知状态变为"pending",也就是 taskNOTIFICATION_RECEIVED,表示有数据了、待处理

可以使用 ulTaskNotifyTake 函数来取出通知值:

  • 如果通知值等于 0,则阻塞(可以指定超时时间)
  • 当通知值大于 0 时,任务从阻塞态进入就绪态
  • ulTaskNotifyTake 返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零

使用 ulTaskNotifyTake 函数可以实现轻量级的、高效的二进制信号量、计数型信号量。

原型如下:

/*
 * xTaskToNotify : 任务句柄(创建任务时得到),给哪个任务发通知
 * 返回值 : 必定返回pdPASS
 */
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

/*
 * xTaskHandle : 任务句柄(创建任务时得到),给哪个任务发通知
 * pxHigherPriorityTaskWoken : 被通知的任务,可能正处于阻塞状态。此函数发出通知后,
      会把它从阻塞状态切换为就绪态。如果被唤醒的任务的优先级,高于当前任务的优先级,则 
      *pxHigherPriorityTaskWoken 被设置为pdTRUE,这表示在中断返回之前要进行任务切换。
 */
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t
						*pxHigherPriorityTaskWoken );

/*
 * xClearCountOnExit : 函数返回前是否清零:
 *                    pdTRUE:把通知值清零
 *                    pdFALSE:如果通知值大于0,则把通知值减一
 * xTicksToWait : 任务进入阻塞态的超时时间,它在等待通知值大于0。
 *                 0:不等待,即刻返回;
 *                 portMAX_DELAY:一直等待,直到通知值大于0;
 *                 其他值:Tick Count,可以用pdMS_TO_TICKS() 把ms转换为Tick Count
 */
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t 
						  xTicksToWait
						);

2、xTaskNotify/xTaskNotifyWait

xTaskNotify 函数功能更强大,可以使用不同参数实现各类功能,比如:

  • 让接收任务的通知值加一:这时 xTaskNotify() 等同于 xTaskNotifyGive()
  • 设置接收任务的通知值的某一位、某些位,这就是一个轻量级的、更高效的事件组
  • 把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成功。这就是轻量级的、长度为1的队列
  • 用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖都成功。类似 xQueueOverwrite() 函数,这就是轻量级的邮箱。

xTaskNotify()xTaskNotifyGive() 更灵活、强大,使用上也就更复杂。xTaskNotifyFromISR() 是它对应的 ISR 版本。

使用 xTaskNotifyWait() 函数来取出任务通知。它比 ulTaskNotifyTake() 更复杂:

  • 可以让任务等待(可以加上超时时间),等到任务状态为"pending"(也就是有数据)
  • 还可以在函数进入、退出时,清除通知值的指定位

原型如下:

/*
 * xTaskToNotify:接收通知的任务句
 * ulValue:用于更新接收任务通知的任务通知值,具体如何更新由形参 eAction 决定
 * eAction:任务通知值更新方式,具体见下面表格
 * 返回值:参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,
 		又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,而其他情况均返回 pdPASS
 */
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
						uint32_t ulValue,
						eNotifyAction eAction );

/*
 * xTaskToNotify:接收通知的任务句
 * ulValue:用于更新接收任务通知的任务通知值,具体如何更新由形参 eAction
 * eAction:任务通知值更新方式,具体见下面表格
 * pxHigherPriorityTaskWoken:在使用之前必须先初始化为 pdFALSE。当调用该函数发送一个任务通知时,
   		目标任务接收到通知后将从阻塞态变为就绪态,并且如果其优先级比当前运行的任务的优先级高,
   		那么 *pxHigherPriorityTaskWoken 会被设置为 pdTRUE,然后在中断退出前执行一次上下文切换,
   		去执行刚刚被唤醒的中断优先级较高的任务。pxHigherPriorityTaskWoken 是一个可选的参数可以设置为 NULL
 * 返回值:参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,
 		又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,而其他情况均返回 pdPASS
 */
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
						        uint32_t ulValue,
								eNotifyAction eAction,
								BaseType_t *pxHigherPriorityTaskWoken );

/*
 * ulBitsToClearOnEntry:ulBitsToClearOnEntry 表示在使用通知之前,将任务通知值的哪些位清 0,
 		实现过程就是将任务的通知值与参数 ulBitsToClearOnEntry 的按位取反值按位与操作。
 		如果 ulBitsToClearOnEntry 设置为 0x01,那么在函数进入前,任务通知值的位1会被清0,其他位保持不变。
 		如果 ulBitsToClearOnEntry 设置为 0xFFFFFFFF (ULONG_MAX),那么在进入函数前任务通知值的所有位都
 		会被清 0,表示清零任务通知值
 * pulNotificationValue:用于保存接收到的任务通知值。如果接收到的任务通知不需要使用,则设置为 NULL 即可。
 		这个通知值在参数 ulBitsToClearOnExit 起作用前将通知值拷贝到 *pulNotificationValue 中
 * xTicksToWait:等待超时时间,单位为系统节拍周期。宏 pdMS_TO_TICKS 用于将单位毫秒转化为系统节拍数
 * 返回值:如果获取任务通知成功则返回 pdTRUE,失败则返回 pdFALSE
 */
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
							uint32_t ulBitsToClearOnExit,
							uint32_t *pulNotificationValue,
							TickType_t xTicksToWait );

其中,eAcrtion 的取值如下:

eAction 取值含义
eNoAction对象任务接收任务通知,但是任务自身的任务通知值不更新,即形参 ulValue 没有用。
eSetBits对象任务接收任务通知,同时任务自身的任务通知值与 ulValue 按位或。
如果 ulValue 设置为 0x01,那么任务的通知值的位 0 将被置为 1。
同样的如果 ulValue 设置为 0x04,那么任务的通知值的位 2 将被置为 1。
在这种方式下,任务通知可以看成是事件标志的一种轻量型的实现,速度更快。
eIncrement对象任务接收任务通知,任务自身的任务通知值加 1,即形参ulValue 没有用。
这个时候调用 xTaskNotify() 等同于调用 xTaskNotifyGive()。
eSetValueWithOverwrite对象任务接收任务通知,且任务自身的任务通知值会无条件的被设置为 ulValue。
在这种方式下,任务通知可以看成是函数 xQueueOverwrite() 的一种轻量型的实现,速度更快。
eSetValueWithoutOverwrite对象任务接收任务通知,且对象任务没有通知值,那么通知值就会被设置为 ulValue。
对象任务接收任务通知,但是上一次接收到的通知值并没有取走,那么本次的通知值将不会更新,同时函数返回 pdFALSE。
在这种方式下,任务通知可以看成是函数 xQueueSend() 应用在队列深度为 1 的队列上的一种轻量型实现,速度更快。

3、xTaskNotifyAndQuery

xTaskNotifyAndQuery()xTaskNotify() 很像,都是调用通用的任务通知发送函数 xTaskGenericNotify() 来实现通知的发送,不同的是多了一个附加的参数 pulPreviousNotifyValue 用于回传接收任务的上一个通知值。

xTaskNotifyAndQuery() 函数不能用在中断中,而是必须使用带中断保护功能的 xTaskNotifyAndQuery()FromISR 来代替。

/*
 * xTaskToNotify:接收通知的任务句
 * ulValue:用于更新接收任务通知的任务通知值,具体如何更新由形参 eAction 决定
 * eAction:任务通知值更新方式,具体见上面表格
 * pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL,则不需要回传,
 		这个时候就等价于函数 xTaskNotify()
 * 返回值:参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,
 		又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,而其他情况均返回 pdPASS
 */
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify, 
								uint32_t ulValue, 
								eNotifyAction eAction, 
								uint32_t *pulPreviousNotifyValue )

/*
 * xTaskToNotify:接收通知的任务句
 * ulValue:用于更新接收任务通知的任务通知值,具体如何更新由形参 eAction 决定
 * eAction:任务通知值更新方式,具体见上面表格
 * pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL,则不需要回传,
 		这个时候就等价于函数 xTaskNotify()
 * pxHigherPriorityTaskWoken:在使用之前必须先初始化为 pdFALSE。当调用该函数发送一个任务通知时,
 		目标任务接收到通知后将从阻塞态变为就绪态,并且如果其优先级比当前运行的任务的优先级高,那么 
 		*pxHigherPriorityTaskWoken 会被设置为 pdTRUE,然后在中断退出前执行一次上下文切换,
 		去执行刚刚被唤醒的中断优先级较高的任务。
 		pxHigherPriorityTaskWoken 是一个可选的参数可以设置为 NULL
 * 返回值:参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,
 		又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,而其他情况均返回 pdPASS
 */
BaseType_t xTaskNotifyAndQueryFromISR(  TaskHandle_t xTaskToNotify, 
										uint32_t ulValue, 
										eNotifyAction eAction, 
										uint32_t *pulPreviousNotifyValue, 
										BaseType_t *pxHigherPriorityTaskWoken )

标签:eAction,入门,FreeRTOS,通知,函数,任务,接收,ulValue
From: https://blog.csdn.net/Teminator_/article/details/141532262

相关文章

  • 【python学习】揭秘Python abc库:从入门到精通,解锁抽象基类的终极技巧
    开篇本文适合Python开发者,尤其是希望在项目中应用面向对象编程(OOP)理念并提高代码复用性和结构性的人群。我们将深入探讨Python中的abc库,了解如何通过定义抽象基类来创建更加灵活、可扩展的代码结构,并介绍一些高级用法、常见的易错点以及实用的小技巧。温格高,2023年环法自行......
  • Pulsar 入门实战(2)--特性及架构
    本文主要介绍Pulsar的特性及架构,对应的pulsar版本为3.3.x。1、Pulsar特性Pulsar实例原生支持多个集群,并且在集群之间实现了无缝的地理复制消息。极低的发布和端到端延迟。无缝扩展至超过一百万个主题。简单的客户端API,支持Java、Go、Python和C++语言。多种订阅......
  • C#入门(14)Switch语句
    前言先前我们已经学了if语句,知道了他有一个相似的兄弟Switch,那我们就以他们的区别来进入本节的学习。Switch和if都是用来控制程序的流程的条件语句,但是它们有一些区别。语法结构:Switch语句由一个或多个case标签和一个可选的默认标签组成,每个case标签后面是要执行的语句序列......
  • Mybatis入门及使用
    目录一、什么是Mybatis 二、Mybatis入门1.快速入门1.1入门程序分析 1.2入门程序实现1.2.1准备工作1.2.1.1创建springboot工程1.2.1.2数据准备 1.2.2配置Mybatis1.2.3编写SQL语句1.2.4单元测试 2.JDBC介绍(了解)2.1介绍2.2JDBC vs Mybatis3.数据库......
  • SmartDeviceControl_FreeRTOS_Hal_Zet6
    SmartDeviceControl_FreeRTOS_Hal_Zet6系统框图显示子设备框图我们不想让文件互相调用遇到的问题:OLED无响应,I2C无法工作解决:使用的是软件I2C,问题在于OLED的驱动中已经包含获取从机应答的函数更新:屏幕为2.8TFT-LCD使用FSMC来模拟8080并口驱动LCDFSMC_D0-FSMC_D......
  • [原创]Windows X64汇编入门(1)
    [原创]WindowsX64汇编入门(1)[原创]WindowsX64汇编入门(1)WindowsX64汇编入门(1)tankaiha  最近断断续续接触了些64位汇编的知识,这里小结一下,一是阶段学习的回顾,二是希望对64位汇编新手有所帮助。我也是刚接触这方面知识,文中肯定有错误之处,大家多指正。文章的标......
  • RabbitMQ 入门示例
    参考:BV15k4y1k7EpRabbitMQ相关概念及简述中简单介绍了RabbitMQ提供的6种工作模式。下面以简单模式为例,介绍RabbitMQ的使用。新建工程先新建Maven工程RabbitMQ作为父工程,在父工程下新建三个子模块:common:公共包producer:生产者consumer:消费者在三个模块中添加......
  • 信息学竞赛免费入门网课视频之语法知识框架
    graphLRA[输入输出]-->T1[DevC++软件安装]-->P1[<ahref='http://cdn.noip.space/soft/devcpp.exe'target='_blank'>软件下载</a>]-->|<spanstyle='background-color:#5cb85c'>已通过</span>|V1[<ahref=&#......
  • 十五张图带你快速入门 shardingsphere-proxy
    ApacheShardingSphere是一款分布式的数据库生态系统,它包含两大产品:ShardingSphere-ProxyShardingSphere-JDBC很多同学对于ShardingSphere-JDBC已经能非常熟悉的使用了,但关于网上关于ShardingSphere-Proxy5.5的使用教程却非常少。所以这篇文章,笔者尝试带大家快速入门......
  • 基于STM32F103的FreeRTOS系列(十一)·信号量·二值信号量与计数信号量详细使用以及移植
    目录1. 信号量简介1.1 同步和互斥1.1.1 同步1.1.2 互斥1.1.3 总结1.2 分类1.2.1 二值信号量1.2.2 计数信号量1.2.3 互斥信号量1.2.4 递归信号量2. 信号量控制块3. 常用信号量API函数3.1 创建信号量函数3.1.1 创建二值信号量 xSe......