FreeRTOS篇章之任务通知

本文详细介绍了FreeRTOS中任务通知的功能与应用场景,包括任务通知的特性、限制、API函数及如何替代信号量、事件组等传统同步机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。

 

目录

一、任务通知的特性

二、存在的限制

三、API函数

四、应用


 

一、任务通知的特性

通过任务通知,无需单独的通信对象,任务就可以与其他任务进行交互,以及与 ISR 同步。通过使用任务通知,任务或 ISR 可以直接向接收任务发送事件。

任务通知可以通过以下几种方式更新接收任务的通知值:

  • 直接设置而不用覆写接收任务的通知值
  • 覆写接收任务的通知值
  • 设置接收任务通知值的一个或多个 bit位
  • 增加接收任务的通知值

相对于使用中介对象发送到任务(这样的例子对象是队列,信号量,互斥对象和事件组)如下图:

任务通知是一种将事件直接发送到任务的方法,而无需这样做中介对象:

任务通知功能是可选的,要包含任务通知功能,请在 FreeRTOSConfig.h 中将 configUSE_TASK_NOTIFICATIONS 设置为 1。
当 configUSE_TASK_NOTIFICATIONS 设置为 1 时,每个任务都有一个通知状态(待处理或未在等待处理)和一个通知值(一个 32 位无符号整数)。当任务收到通知时,其通知状态将设置为待处理。当任务读取其通知值时,其通知状态将设置为未在等待处理。如果出于节省空间的考虑(每个任务可以节省 4个字节),可以设置 configUSE_TASK_NOTIFICATIONS 为 0来禁用。
任务可在 “被阻止” 状态中等待其通知状态变为待处理,还可以选择指定超时。

 

二、存在的限制

在以下情形中不能使用任务通知:

•  向 ISR 发送事件或数据。
   通信对象可供 ISR 和任务相互发送事件和数据。
   任务通知可用于从 ISR 向任务发送事件和数据,但不能用于从任务向 ISR 发送事件或数据。

•  启用多个接收任务。
   通信对象可由任何知其句柄(可能是队列句柄、信号灯句柄或事件组句柄)的任务或 ISR 访问。任意数量的任务和 ISR 可处理发送到任何给定通信对象的事件或数据。
   任务通知直接发送给接收任务,因此只能由接收通知的任务来处理。这在多数情况下并不是限制,因为尽管通常有多个任务和 ISR 向相同的通信对象发送事件或数据,但是很少有多个任务和 ISR 接收来自同一通信对象的事件或数据。

•  缓冲多个数据项。
   队列是一次可保存多个数据项的通信对象。已发送到队列但尚未从队列中接收的数据将在队列对象中缓冲。
   任务通知通过更新接收任务的通知值向任务发送数据。任务的通知值一次只能保存一个值。

•  广播到多个任务。
   事件组是可用于一次向多个任务发送事件的通信对象。
   任务通知直接发送给接收任务,因此只能由接收任务来处理。

•  在 “被阻止” 状态中等待发送完成。
   如果通信对象暂时处于无法向其中写入更多数据或事件的状态(例如,当队列已满、无法向该队列发送更多数据时),尝试向该对象写入内容的任务可以选择进入“被阻止”状态,以等待其写入操作完成。
   如果一个任务尝试向已有待处理通知的另一个任务发送任务通知,则发送任务无法在“被阻止”状态中等待接收任务重置其通知状态。在大多数使用任务通知的情况下,这极少成为限制。

 

三、API函数

任务通知使用 API函数 xTaskNotify()或 xTaskNotifyGive()来发送,这个通知将一直挂起直到接收任务使用API函数 xTaskNotifyWait()或 ulTaskNotifyTake()来接收通知。如果接收任务在通知到来时已经被阻塞,则会从阻塞态恢复,同时通知被清除。

功能API 接口实际执行函数其它
发送通知xTaskNotifyGive()

xTaskGenericNotify()

没有通知值 (信号量类型)
发送通知(用于中断中)vTaskNotifyGiveFromISR() 
接收通知ulTaskNotifyTake() 单独对应 xTaskNotifyGive()
发送通知xTaskNotify()

xTaskGenericNotify()

带通知值
发送通知(用于中断中)xTaskNotifyFromISR()

xTaskGenericNotifyFromISR()

发送通知xTaskNotifyAndQuery()

xTaskGenericNotify()

带通知值,并且返回原通知值
发送通知(用于中断中)

xTaskNotifyAndQueryFromISR()

 
接收通知xTaskNotifyWait()  
清除通知

xTaskNotifyStateClear()

  

在大多数情况下,并不需要 xTaskNotify() 和 xTaskNotifyWait() API 函数提供的灵活性。更简单的函数就足够了。xTaskNotifyGive() API 函数可替代 xTaskNotify(),它更简单、但灵活性稍差。ulTaskNotifyTake() API 函数可替代 xTaskNotifyWait(),它更简单、但灵活性稍差。

1、xTaskNotifyGive() 和 vTaskNotifyGiveFromISR() API函数

BaseType_t   xTaskNotifyGive( TaskHandle_t    xTaskToNotify );

void   vTaskNotifyGiveFromISR( TaskHandle_t    xTaskToNotify,
                                                    BaseType_t       *pxHigherPriorityTaskWoken );

传入参数:

  • xTaskToNotify :需要通知的任务句柄。这个句柄即是调用 xTaskCreate()创建任务时传递的句柄
  • pxHigherPriorityTaskWoken:如果调用 vTaskNotifyGiveFromISR()发送通知导致被发送通知的任务离开阻塞状态,并且这个任务的优先级高于当前任务(也就是被中断的任务),那么 xSemaphoreGiveFromISR()会在函数内部将 *pxHigherPriorityTaskWoken 设为 pdTRUE。如果 vTaskNotifyGiveFromISR() 将此值设为 pdTRUE,则在中断退出前应当进行一次上下文切换

返回参数(始终返回 pdPASS):

  • pdPASS:发送成功

2、ulTaskNotifyTake() API函数

uint32_t   ulTaskNotifyTake( BaseType_t    xClearCountOnExit,
                                             TickType_t      xTicksToWait );

传入参数:

  • xClearCountOnExit:当设置为 pdFALSE,则在函数退出时任务的通知值递减,这样,通知值就像一个计数信号量;如果是 pdTRUE,那么当函数退出时,任务的通知值将被清除为零,这样,通知值就像一个二进制信号量
  • xTicksToWait:阻塞超时时间。任务进入阻塞态以等待任务通知有效的最长时间。如果 xTicksToWait为 0,则 ulTaskNotifyTake()在通知无效时会立即返回;如果把 xTicksToWait 设置为 portMAX_DELAY,那么阻塞等待将没有超时限制

返回参数:

  • uint32_t:在任务的通知计数清空为零或递减之前的任务通知值

3、xTaskNotify() 和 xTaskNotifyFromISR() API函数

BaseType_t   xTaskNotify( TaskHandle_t    xTaskToNotify,
                                           uint32_t            ulValue,
                                           eNotifyAction    eAction );

BaseType_t   xTaskNotifyFromISR( TaskHandle_t    xTaskToNotify, 
                                                         uint32_t             ulValue, 
                                                         eNotifyAction     eAction, 
                                                         BaseType_t       *pxHigherPriorityTaskWoken );

传入参数:

  • xTaskToNotify :需要通知的任务句柄。这个句柄即是调用 xTaskCreate()创建任务时传递的句柄
  • ulValue:与通知一起发送的数据,如何使用 ulValue取决于 eNotifyAction值
  • eAction:枚举类型,用于指定如何更新接收任务的通知值。
枚举成员描述

eNoAction

通知任务,而不更新其通知值;参数 ulValue未使用。可用作更快速的二进制信号量轻型替代方案。始终返回 pdPASS

eSetBits

对被通知任务的通知值按位执行或运算。可用作更快速的事件组轻型替代方案。始终返回 pdPASS

eIncrement

接收任务的通知值将递增。可用作更快速的计数信号量轻型替代方案。始终返回 pdPASS

eSetValueWithOverwrite

即使任务尚未读取前一个值,也将任务的通知值设置为 ulValue参数中传递的值。始终返回 pdPASS

eSetValueWithoutOverwrite

如果任务当前没有通知值,则设置任务的通知值并返回 pdPASS;如果任务没有取出前一个通知值,则不执行任何操作,将返回 pdFAIL
  • pxHigherPriorityTaskWoken:如果调用 xTaskNotifyFromISR()发送通知导致被发送通知的任务离开阻塞状态,并且这个任务的优先级高于当前任务(也就是被中断的任务),那么 xTaskNotifyFromISR()会在函数内部将 *pxHigherPriorityTaskWoken 设为 pdTRUE。如果 xTaskNotifyFromISR() 将此值设为 pdTRUE,则在中断退出前应当进行一次上下文切换

返回参数:

  • BaseType_t:取决于 eAction的值

4、xTaskNotifyWait() API函数

BaseType_t   xTaskNotifyWait( uint32_t    ulBitsToClearOnEntry, 
                                                  uint32_t    ulBitsToClearOnExit, 
                                                  uint32_t    *pulNotificationValue, 
                                                  TickType_t    xTicksToWait );

传入参数:

  • ulBitsToClearOnEntry:在使用通知之前,先将任务的通知值与参数 ulBitsToClearOnEntry的按位取反值按位与操作。设置参数 ulBitsToClearOnEntry为 0xFFFFFFFF(ULONG_MAX),表示清零任务通知值
  • ulBitsToClearOnExit:在退出函数之前,将任务的通知值与参数 ulBitsToClearOnExit的按位取反值按位与操作。设置参数 ulBitsToClearOnExit为 0xFFFFFFFF(ULONG_MAX),表示清零任务通知值
  • pulNotificationValue:用于向外回传任务的通知值。这个通知值在参数 ulBitsToClearOnExit起作用前将通知值拷贝到 *pulNotificationValue中。可选参数,如果不需要,可将其设置为 NULL
  • xTicksToWait:阻塞超时时间。任务进入阻塞态以等待任务通知有效的最长时间。如果 xTicksToWait为 0,则 xTaskNotifyWait()在通知无效时会立即返回;如果把 xTicksToWait 设置为 portMAX_DELAY,那么阻塞等待将没有超时限制

返回参数:

  • pdPASS:接收成功
  • pdFAIL:接收失败(超时)

 

四、应用

1、替代二值信号量(binary semaphore)

任务通知比二值信号量快 45%并且内存占用更小

  • 发送处理:xTaskNotifyGive()和 vTaskNotifyGiveFromISR()用来代替 xSemaphoreGive()和 xSemaphoreGiveFromISR()
  • 接收处理:ulTaskNotifyTake()用来替代 xSemaphoreTake()(ps:ulTaskNotifyTake()的第一个参数 xClearCountOnExit应设置为 pdTRUE

2、替代计数信号量(counting semaphore)

与替代二值信号量类似,当使用任务通知来代替计数信号量的时候,接收任务的通知值被用来代替计数信号量的计数值

  • 发送处理:xTaskNotifyGive()和 vTaskNotifyGiveFromISR()用来代替 xSemaphoreGive()和 xSemaphoreGiveFromISR()
  • 接收处理:ulTaskNotifyTake()用来替代 xSemaphoreTake()(ps:ulTaskNotifyTake()的第一个参数 xClearCountOnExit应设置为 pdFALSE

3、替代事件组(event group)

当任务通知用来代替时间组的时候,通知值被用来代替事件组的任务值,通知值的每一位被用来代表某个标志

  • 发送处理:xTaskNotify()和 xTaskNotifyFromISR()用来代替 xEventGroupSetBits()和 xEventGroupSetBitsFromISR()
  • 接收处理:xTaskNotifyWait()用来代替 xEventGroupWaitBits()

4、替代邮箱队列(mailbox)

RTOS任务通知只能用来发送数据到一个任务中,相比用队列实现,有一些限制:

  1. 只能发送 32-bit数据
  2. 保存的是接收任务的值,同一个时刻只能存在一个接收任务

因此,使用 “轻量邮箱” 这个短语代替 “轻量队列”。任务的通知值就是邮箱值

  • 发送处理:使用 xTaskNotify()和 xTaskNotifyFromISR()发送至任务,可以用来替换 xQueueOverwtite()和 xQueueOverwtiteFromISR()
  • 接收处理:使用 xTaskNotifyWait()来获取通知值,可以用来代替 xQueueReceive()和 xQueuePeek()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

<think>嗯,用户想了解FreeRTOS中多个任务通知的使用示例和注意事项。首先,我需要回忆一下FreeRTOS任务通知机制。任务通知是一种轻量级的通信机制,可以用来替代队列、信号量或事件组。每个任务都有一个32位的通知值,可以通过不同的API函数来操作这个值。 用户提到的多个任务通知,可能是指如何同时处理多个任务之间的通知。比如,一个任务可能需要接收来自不同任务通知,或者一个任务需要发送通知给多个任务。这时候需要注意,每个任务只能有一个通知值,所以如果需要处理多个事件,可能需要用位操作来区分不同的通知类型。 根据引用[2],FreeRTOS提供了几种发送通知的方式,比如不覆盖通知值、直接覆盖、设置位或者递增。这些不同的方式适用于不同的场景。比如设置位可以用作事件组,递增可以用作计数信号量。因此,在示例中需要展示如何利用这些不同的发送方式来满足不同的需求。 接下来,我需要考虑使用示例的结构。可能需要两个任务,一个发送通知,另一个接收处理。比如,任务A发送不同位的事件,任务B根据位来判断执行不同的操作。同时,要注意任务通知的优先级,以及接收时的等待时间设置,避免任务长时间阻塞。 注意事项方面,首先任务通知只能有一个接收任务,所以不能像事件组那样多个任务等待同一个事件。其次,覆盖通知值可能会导致数据丢失,需要根据场景选择是否覆盖。另外,使用通知时要注意清除相应的位或值,避免重复处理。还有,任务通知不能用于中断服务例程中发送数据,除非使用带中断保护版本的API。 还需要参考引用[4]中的创建任务的API,确保示例中正确使用xTaskCreate来创建任务。此外,引用[3]中的多任务程序实现步骤可能对示例的结构有帮助,比如如何在main函数中初始化和启动任务。 可能的问题点是,用户可能混淆任务通知和事件组的区别。任务通知虽然可以模拟事件组,但每个任务只能有自己的通知值,而事件组可以被多个任务共享。因此,在需要多个任务同步的情况下,事件组可能更合适,而任务通知更适合一对一的通信。 最后,确保示例代码正确,使用正确的API函数,比如xTaskNotifyGive用于发送通知,ulTaskNotifyTake用于接收,或者xTaskNotify和xTaskNotifyWait。同时,要处理返回值,检查通知是否成功接收。</think>### FreeRTOS任务通知使用示例及注意事项 #### 一、多任务通知的核心特性 FreeRTOS任务通知允许通过32位值实现轻量级通信,支持以下操作[^2]: 1. **不覆盖未读通知**:保留现有值直到被读取 2. **覆盖通知值**:强制更新为最新值 3. **位操作模式**:通过位掩码设置特定标志位 4. **递增模式**:模拟计数信号量 #### 二、典型使用示例 ```c // 任务1:发送通知 void vSenderTask(void *pvParameters) { for(;;) { // 设置bit0标志位(事件组模式) xTaskNotify(TaskHandle_Receiver, 0x01, eSetBits); // 直接覆盖数值(强制更新) xTaskNotify(TaskHandle_Receiver, 0xAA, eSetValueWithOverwrite); vTaskDelay(pdMS_TO_TICKS(1000)); } } // 任务2:接收通知 void vReceiverTask(void *pvParameters) { uint32_t ulNotifiedValue; for(;;) { // 等待通知(阻塞时间10 ticks) xTaskNotifyWait(0x00, 0xFFFFFFFF, &ulNotifiedValue, pdMS_TO_TICKS(10)); if(ulNotifiedValue & 0x01) { // 处理bit0事件 } if(ulNotifiedValue == 0xAA) { // 处理数值更新 } } } ``` #### 三、关键注意事项 1. **单任务接收限制**:每个通知只能由特定任务接收,无法广播[^2] 2. **值覆盖风险**:使用`eSetValueWithOverwrite`可能导致数据丢失 3. **位操作规范**:建议使用宏定义管理标志位,例如: ```c #define EVENT_SENSOR_READY (1 << 0) #define EVENT_DATA_RECEIVED (1 << 1) ``` 4. **中断安全**:中断服务中必须使用`xTaskNotifyFromISR()`系列函数 5. **优先级继承**:需合理设置任务优先级防止优先级反转 #### 四、应用场景对比 | 场景类型 | 推荐实现方式 | 优势 | |----------------|----------------------|--------------------------| | 简单事件通知 | 直接覆盖通知值 | 最低内存消耗 | | 多事件标志 | 位操作模式 | 支持32个独立事件标志 | | 资源计数 | 递增模式 | 替代计数信号量 | | 复杂数据传递 | 队列+通知组合使用 | 兼顾数据完整性和响应速度 | [^1]: 使用任务通知来简单地告知任务事件的发生,使用事件标志来同步多个任务对事件的响应 : 通过对任务通知方式的合理使用,可在一定场合下替代信号量、队列等传统机制
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值