0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

FreeRTOS任务通知简介及实现

CHANBAEK 来源:南山府嵌入式 作者:南山府嵌入式 2022-12-06 16:24 次阅读

1- 简介

每个系统任务都会有一个任务通知。然后每个任务通知都具有挂起或者未挂起的状态,以及32位的通知。常量configTASK_NOTIFICATION_ARRAY_ENTRIES()是用来设置任务通知索引的数组。

任务通知是直接发送给任务事件,不是通过中间对象(队列、事件组、信号量)间接发送给任务的。

当任务发送任务通知时,会将目标任务通知的状态设定位挂起状态。就像任务阻塞中间对象一样,例如,信号在等待信号量的可用情况,任务组织这个等待通知的状态变为挂起的状态。

任务通知也可以是以下几种方式:

  • 值覆盖,不管接受任务是否已经读取被覆盖的值。
  • 值覆盖,仅当接收任务已读取被覆盖的值时。
  • 在设置值中设置一个或多个位。
  • 递增值(加1)

注意:

每个数组中的通知都是独立的,一个任务一次只能阻塞数组中的一个通知,而且不会通过发送到任何其他数组索引的直通解除阻塞状态。

1.1 优势和使用限制

任务通知的灵活性允许它们在需要创建单独队列、二进制信号量、计数信号量或事件组的情况下使用。使用直接通知解除RTOS任务的阻塞速度比使用中间对象(如二进制信号量)解除阻塞的速度快45% *,并且使用的RAM更少。正如所料,这些性能优势需要一些用例限制。

1-当只有一个任务可以接收事件时,才可以使用RTOS任务通知。然而,实际应用中的大多数用例都满足这个条件,例如中断解除阻塞,任务将处理由中断接收到的数据。

2-只有在使用RTOS任务通知代替队列的情况下:接收任务可以在阻塞状态下等待通知(这样不会消耗任何CPU时间),如果发送任务不能立即完成,则发送任务不能在阻塞状态下等待发送完成。

1.2 用例

通知使用xTaskNotifyIndexed()和xTaskNotifyGiveIndexed() API函数(和它们的中断安全等效)发送,并保持等待,直到接收RTOS任务调用xTaskNotifyWaitIndexed()或ulTaskNotifyTakeIndexed() API函数。这些API函数都有一个不带“I索引”前缀的等价函数。非“索引”版本总是在数组索引0处的任务通知上操作。例如,xTaskNotifyGive(TargetTask)等价于xTaskNotifyGiveIndexed(TargetTask, 0) -两者都在索引0处增加任务通知由任务处理的TargetTask引用的任务。

2-将 RTOS 任务通知用作轻量级二进制信号量

与使用二进制信号量解锁任务相比,使用直接通知解锁 RTOS 任务的速度快 45%,并且使用的 RAM 更少。

二进制信号量是最大计数为 1 的信号量,因此称为“二进制”。任务只有在信号量可用时才可以“获取”信号量,并且信号量仅 如果计数为 1,则可用。

当使用任务通知代替二进制信号量时,接收任务的通知值会代替二进制信号量的count值,并且会使用ulTaskNotifyTake()(或ulTaskNotifyTakeIndexed()) API函数代替信号量的xSemaphoreTake() API函数。

ulTaskNotifyTake()函数的xClearOnExit参数被设置为pdTRUE,因此每次收到通知时count值都返回0——模拟二进制信号量。

同样,xTaskNotifyGive()(或xTaskNotifyGiveIndexed())或vTaskNotifyGiveFromISR()(或vTaskNotifyGiveIndexedFromISR())函数被用来代替信号量的xSemaphoreGive()和xSemaphoreGiveFromISR()函数。

2.1 使用范例

1/*这是通用外设驱动程序中的传输函数的一个例子。
 2RTOS任务调用传输函数,然后处于阻塞状态(因此不占用CPU时间),
 3直到收到传输完成的通知。传输由DMA执行,DMA端中断用于通知任务。 */
 4
 5/* 存储传输完成时将收到通知的任务句柄。 */
 6static TaskHandle_t xTaskToNotify = NULL;
 7
 8/* 目标任务要使用的任务通知数组中的索引。 */
 9const UBaseType_t xArrayIndex = 1;
10
11/* 外设驱动的传输功能 */
12void StartTransmission( uint8_t *pcData, size_t xDataLength )
13{
14/*此时xTaskToNotify应该为NULL,因为没有正在进行传输。
15如果有必要,可以使用互斥量来保护对外设的访问。*/
16configASSERT( xTaskToNotify == NULL );
17
18/* 存储调用任务的句柄。 */
19xTaskToNotify = xTaskGetCurrentTaskHandle();
20
21/* 开始传输:在传输完成时产生一个中断。 */
22vStartTransmit( pcData, xDatalength );
23}
24/*-----------------------------------------------------------*/
25
26/* 结束中断传输 */
27void vTransmitEndISR( void )
28{
29BaseType_t xHigherPriorityTaskWoken = pdFALSE;
30
31/* 此时,xTaskToNotify不应该为NULL,因为传输正在进行中。 */
32configASSERT( xTaskToNotify != NULL );
33
34/* Notify the task that the transmission is complete. */
35vTaskNotifyGiveIndexedFromISR( xTaskToNotify,
36xArrayIndex,
37&xHigherPriorityTaskWoken );
38
39/*此时,xTaskToNotify不应该为NULL,因为传输正在进行中。 */
40xTaskToNotify = NULL;
41
42/*如果xHigherPriorityTaskWoken现在设置为pdTRUE,
43那么应该执行切换,以确保中断直接返回到最高优先级的任务。
44用于该目的的宏取决于所使用的端口,可以称为portEND_SWITCHING_ISR()。 */
45portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
46}
47/*-----------------------------------------------------------*/
48
49/* 发起传输的任务,然后进入阻塞状态(因此不消耗任何CPU时间),以等待传输完成。 */
50void vAFunctionCalledFromATask( uint8_t ucDataToTransmit,
51size_t xDataLength )
52{
53uint32_t ulNotificationValue;
54const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
55
56/* 通过调用上面显示的函数开始传输。 */
57StartTransmission( ucDataToTransmit, xDataLength );
58
59/* 等待传输完成的通知。注意,第一个参数是pdTRUE,
60它的作用是将任务的通知值清除回0,
61使通知值类似于二进制(而不是计数)信号量。 */
62ulNotificationValue = ulTaskNotifyTakeIndexed( xArrayIndex,
63pdTRUE,
64xMaxBlockTime );
65
66if( ulNotificationValue == 1 )
67{
68/* The transmission ended as expected. */
69}
70else
71{
72/* The call to ulTaskNotifyTake() timed out. */
73}
74}

3-使用 RTOS 任务通知作为轻量级计数信号量

与使用信号量解锁任务相比,使用直接通知解锁 RTOS 任务的速度快 45%,并且使用的 RAM 更少。

计数信号量是这样一种信号量,其计数值可以为0,直到创建信号量时设置的最大值。只有当信号量可用时,任务才能获取信号量,并且只有当信号量的计数大于0时,信号量才可用。

当使用任务通知来代替计数信号量时,接收任务的通知值会代替计数信号量的计数值,并且使用ulTaskNotifyTake()(或ulTaskNotifyTakeIndexed()) API函数来代替信号量的xSemaphoreTake() API函数。ulTaskNotifyTake()函数的xClearOnExit参数被设置为pdFALSE,因此每次收到通知时,计数值只减少(而不是清除)——模拟计数信号量。

同样,xTaskNotifyGive()(或xTaskNotifyGiveIndexed())或vTaskNotifyGiveFromISR()(或vTaskNotifyGiveIndexedFromISR())函数被用来代替信号量的xSemaphoreGive()和xSemaphoreGiveFromISR()函数。

下面通过两个例子来看。

下面的第一个例子使用接收任务的通知值作为计数信号量。第二个示例提供了高效的实现方式。

3.1 Example 1:

1/* An interrupt handler that does not process interrupts directly,
 2but instead defers processing to a high priority RTOS task. The
 3ISR uses RTOS task notifications to both unblock the RTOS task
 4and increment the RTOS task's notification value. */
 5void vANInterruptHandler( void )
 6{
 7BaseType_t xHigherPriorityTaskWoken;
 8
 9/* Clear the interrupt. */
10prvClearInterruptSource();
11
12/* xHigherPriorityTaskWoken must be initialised to pdFALSE.
13If calling vTaskNotifyGiveFromISR() unblocks the handling
14task, and the priority of the handling task is higher than
15the priority of the currently running task, then
16xHigherPriorityTaskWoken will be automatically set to pdTRUE. */
17xHigherPriorityTaskWoken = pdFALSE;
18
19/* Unblock the handling task so the task can perform
20any processing necessitated by the interrupt. xHandlingTask
21is the task's handle, which was obtained when the task was
22created. vTaskNotifyGiveFromISR() also increments
23the receiving task's notification value. */
24vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
25
26/* Force a context switch if xHigherPriorityTaskWoken is now
27set to pdTRUE. The macro used to do this is dependent on
28the port and may be called portEND_SWITCHING_ISR. */
29portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
30}
31/*-----------------------------------------------------------*/
32
33/* A task that blocks waiting to be notified that the peripheral
34needs servicing. */
35void vHandlingTask( void *pvParameters )
36{
37BaseType_t xEvent;
38const TickType_t xBlockTime = pdMS_TO_TICS( 500 );
39uint32_t ulNotifiedValue;
40
41for( ;; )
42{
43/* Block to wait for a notification. Here the RTOS
44task notification is being used as a counting semaphore.
45The task's notification value is incremented each time
46the ISR calls vTaskNotifyGiveFromISR(), and decremented
47each time the RTOS task calls ulTaskNotifyTake() - so in
48effect holds a count of the number of outstanding interrupts.
49The first parameter is set to pdFALSE, so the notification
50value is only decremented and not cleared to zero, and one
51deferred interrupt event is processed at a time. See
52example 2 below for a more pragmatic approach. */
53ulNotifiedValue = ulTaskNotifyTake( pdFALSE,
54xBlockTime );
55
56if( ulNotifiedValue > 0 )
57{
58/* Perform any processing necessitated by the interrupt. */
59xEvent = xQueryPeripheral();
60
61if( xEvent != NO_MORE_EVENTS )
62{
63vProcessPeripheralEvent( xEvent );
64}
65}
66else
67{
68/* Did not receive a notification within the expected
69time. */
70vCheckForErrorConditions();
71}
72}
73}

3.2 Example 2:

1这个例子展示了一个更实用和高效的RTOS任务的实现。在这个实现中,ulTaskNotifyTake()的返回值用于知道有多少未处理的ISR事件必须被处理,允许RTOS任务的通知计数在每次ulTaskNotifyTake()被调用时被清除回零。中断服务例程(ISR)假定如上例1所示。
 2/* The index within the target task's array of task notifications
 3to use. */
 4const UBaseType_t xArrayIndex = 0;
 5
 6/* A task that blocks waiting to be notified that the peripheral
 7needs servicing. */
 8void vHandlingTask( void *pvParameters )
 9{
10BaseType_t xEvent;
11const TickType_t xBlockTime = pdMS_TO_TICS( 500 );
12uint32_t ulNotifiedValue;
13
14for( ;; )
15{
16/* As before, block to wait for a notification form the ISR.
17This time however the first parameter is set to pdTRUE,
18clearing the task's notification value to 0, meaning each
19outstanding outstanding deferred interrupt event must be
20processed before ulTaskNotifyTake() is called again. */
21ulNotifiedValue = ulTaskNotifyTakeIndexed( xArrayIndex,
22pdTRUE,
23xBlockTime );
24
25if( ulNotifiedValue == 0 )
26{
27/* Did not receive a notification within the expected
28time. */
29vCheckForErrorConditions();
30}
31else
32{
33/* ulNotifiedValue holds a count of the number of
34outstanding interrupts. Process each in turn. */
35while( ulNotifiedValue > 0 )
36{
37xEvent = xQueryPeripheral();
38
39if( xEvent != NO_MORE_EVENTS )
40{
41vProcessPeripheralEvent( xEvent );
42ulNotifiedValue--;
43}
44else
45{
46break;
47}
48}
49}
50}
51}

4-将 RTOS 任务通知用作轻量级事件组

事件组是一组二进制标志(或位),应用程序编写人员可以为每个标志指定含义。RTOS进程可以进入阻塞状态,等待组内的一个或多个标志变为活动状态。当RTOS任务处于阻塞状态时,不会消耗任何CPU时间。

当使用任务通知代替事件组时,使用接收任务的通知值代替事件组,接收任务的通知值中的比特位用作事件标志,并且使用xTaskNotifyWait() API函数代替事件组的xEventGroupWaitBits() API函数。

同样,使用xTaskNotify()和xTaskNotifyFromISR() API函数(其eAction参数设置为eSetBits)来代替xEventGroupSetBits()和xEventGroupSetBitsFromISR()函数。

与xEventGroupSetBitsFromISR()相比,xTaskNotifyFromISR()具有显著的性能优势,因为xTaskNotifyFromISR()完全在ISR中执行,而xEventGroupSetBitsFromISR()必须推迟一些处理到RTOS进程任务。

与使用事件组时不同,接收任务不能指定只在同时有多个比特位处于活动状态时才离开阻塞状态。相反,在任何比特位处于活动状态时,进程将解除阻塞,并且必须自己测试比特位的组合。

1/* This example demonstrates a single RTOS task being used to process
  2events that originate from two separate interrupt service routines -
  3a transmit interrupt and a receive interrupt. Many peripherals will
  4use the same handler for both, in which case the peripheral's
  5interrupt status register can simply be bitwise ORed with the
  6receiving task's notification value.
  7
  8First bits are defined to represent each interrupt source. */
  9#define TX_BIT 0x01
 10#define RX_BIT 0x02
 11
 12/* The handle of the task that will receive notifications from the
 13interrupts. The handle was obtained when the task
 14was created. */
 15static TaskHandle_t xHandlingTask;
 16
 17/*-----------------------------------------------------------*/
 18
 19/* The implementation of the transmit interrupt service routine. */
 20void vTxISR( void )
 21{
 22BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 23
 24/* Clear the interrupt source. */
 25prvClearInterrupt();
 26
 27/* Notify the task that the transmission is complete by setting the TX_BIT
 28in the task's notification value. */
 29xTaskNotifyFromISR( xHandlingTask,
 30TX_BIT,
 31eSetBits,
 32&xHigherPriorityTaskWoken );
 33
 34/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
 35should be performed to ensure the interrupt returns directly to the highest
 36priority task. The macro used for this purpose is dependent on the port in
 37use and may be called portEND_SWITCHING_ISR(). */
 38portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
 39}
 40/*-----------------------------------------------------------*/
 41
 42/* The implementation of the receive interrupt service routine is identical
 43except for the bit that gets set in the receiving task's notification value. */
 44void vRxISR( void )
 45{
 46BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 47
 48/* Clear the interrupt source. */
 49prvClearInterrupt();
 50
 51/* Notify the task that the reception is complete by setting the RX_BIT
 52in the task's notification value. */
 53xTaskNotifyFromISR( xHandlingTask,
 54RX_BIT,
 55eSetBits,
 56&xHigherPriorityTaskWoken );
 57
 58/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
 59should be performed to ensure the interrupt returns directly to the highest
 60priority task. The macro used for this purpose is dependent on the port in
 61use and may be called portEND_SWITCHING_ISR(). */
 62portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
 63}
 64/*-----------------------------------------------------------*/
 65
 66/* The implementation of the task that is notified by the interrupt service
 67routines. */
 68static void prvHandlingTask( void *pvParameter )
 69{
 70const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 500 );
 71BaseType_t xResult;
 72
 73for( ;; )
 74{
 75/* Wait to be notified of an interrupt. */
 76xResult = xTaskNotifyWait( pdFALSE, /* Don't clear bits on entry. */
 77ULONG_MAX, /* Clear all bits on exit. */
 78&ulNotifiedValue, /* Stores the notified value. */
 79xMaxBlockTime );
 80
 81if( xResult == pdPASS )
 82{
 83/* A notification was received. See which bits were set. */
 84if( ( ulNotifiedValue & TX_BIT ) != 0 )
 85{
 86/* The TX ISR has set a bit. */
 87prvProcessTx();
 88}
 89
 90if( ( ulNotifiedValue & RX_BIT ) != 0 )
 91{
 92/* The RX ISR has set a bit. */
 93prvProcessRx();
 94}
 95}
 96else
 97{
 98/* Did not receive a notification within the expected time. */
 99prvCheckForErrors();
100}
101}
102}

5-将 RTOS 任务通知用作轻量级邮箱

RTOS任务通知可用于向任务发送数据,但与RTOS队列相比,发送数据的方式要严格得多,因为:

  • 只能发送 32 位值

  • 该值被保存为接收任务的通知值,并且在任何时间只能有一个通知值,因此短语“轻量级邮箱”优先于“轻量级队列”。任务的通知值是邮箱值。

  • 数据使用xTaskNotify()(或xTaskNotifyIndexed())和xTaskNotifyFromISR()(或xTaskNotifyIndexedFromISR()) API函数发送到任务,其eAction参数设置为eSetValueWithOverwrite或eSetValueWithoutOverwrite。如果eAction设置为eSetValueWithOverwrite,那么即使接收任务已经有一个待处理的通知,也会更新接收任务的通知值。如果eAction设置为esetvaluewithoutooverwrite,则只有在接收任务没有待处理通知时才更新接收任务的通知值——因为更新通知值将覆盖接收任务处理之前的值。

    任务可以使用xTaskNotifyWait()(或xTaskNotifyWaitIndexed())读取它自己的通知值。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 函数
    +关注

    关注

    3

    文章

    3903

    浏览量

    61310
  • RTOS
    +关注

    关注

    20

    文章

    776

    浏览量

    118800
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61351
收藏 人收藏

    评论

    相关推荐

    转:第27章 FreeRTOS任务消息邮箱

    本章节为大家讲解FreeRTOS消息队列(消息队列长度固定为1)的另一种实现方式----基于任务通知(Task Notifications)的消息队列,这里我们将这种方式
    发表于 09-09 07:52

    FreeRTOS任务通知模拟二值信号量怎么都获取不成功是怎么回事

    如图,所示,救助论坛的大神,元老? FreeRTOS 21080511 任务通知模拟二值信号量.rar (4.02 MB )
    发表于 06-15 04:32

    任务通知发送出问题怎么办

    大家好。我在STM32cubemx里使用FreeRTOS,开启定时器3中断,然后发送一个任务通知给TIM3_Task。但是无论是在回调函数里使用vTaskNotifyGiveFromISR()这个
    发表于 06-18 04:35

    FreeRTOS任务通知相关资料分享

    FreeRTOS--任务通知Notification今天发现了一个奇怪的现象,特意把它记录下来,防止忘记了。先上图于是找了所有的任务句柄,真的是没有操作挂起了这个
    发表于 01-21 12:07

    使用FreeRTOS通知功能加速应用执行

    使用FreeRTOS的直接任务通知实现更高效的交互,与信号量相比,通知机制内存占用更小,速度可提高45%。架构良好的
    发表于 04-14 11:19

    FreeRTOS的直接任务(消息)通知

            之前分享了《FreeRTOS V10.4.0更新了哪些功能?》,今天就来详细讲述其中的一个知识点:FreeRTOS的直接任务(消息)通知,这样做的目的就是减少RAM占用
    的头像 发表于 01-07 09:37 4150次阅读

    FreeRTOS —— 9.任务通知

    9.1本章介绍与范围已经看到,使用FreeRTOS的应用程序被构造为一组独立的任务,并且这些任务很可能必须彼此通信,以便它们可以共同提供有用的系统功能。通过中介对象进行通信本书已经描述了任务
    发表于 12-04 20:21 10次下载
    <b class='flag-5'>FreeRTOS</b> —— 9.<b class='flag-5'>任务</b><b class='flag-5'>通知</b>

    FreeRTOS高级篇8---FreeRTOS任务通知分析

    FreeRTOS版本V8.2.0中推出了全新的功能:任务通知。在大多数情况下,任务通知可以替代二进制信号量、计数信号量、事件组,可以替代长
    发表于 01-26 17:36 10次下载
    <b class='flag-5'>FreeRTOS</b>高级篇8---<b class='flag-5'>FreeRTOS</b><b class='flag-5'>任务</b><b class='flag-5'>通知</b>分析

    FreeRTOS系列第15篇---使用任务通知实现命令行解释器

    虽然这是介绍FreeRTOS系列的文章,但这篇文章偏重于命令行解释器的实现。这一方面是因为任务通知使用起来非常简单,另一方面也因为对于...
    发表于 01-26 17:49 6次下载
    <b class='flag-5'>FreeRTOS</b>系列第15篇---使用<b class='flag-5'>任务</b><b class='flag-5'>通知</b><b class='flag-5'>实现</b>命令行解释器

    FreeRTOS系列第14篇---FreeRTOS任务通知

    每个RTOS任务都有一个32位的通知值,任务创建时,这个值被初始化为0。RTOS任务通知相当于直接向任务
    发表于 01-26 17:49 5次下载
    <b class='flag-5'>FreeRTOS</b>系列第14篇---<b class='flag-5'>FreeRTOS</b><b class='flag-5'>任务</b><b class='flag-5'>通知</b>

    FreeRTOS系列第11篇---FreeRTOS任务控制

    FreeRTOS任务控制API函数主要实现任务延时、任务挂起、解除任务挂起、任务优先级获取和设置
    发表于 01-26 17:54 11次下载
    <b class='flag-5'>FreeRTOS</b>系列第11篇---<b class='flag-5'>FreeRTOS</b><b class='flag-5'>任务</b>控制

    FreeRTOS任务和协程简介实现

    简单来说,FreeRTOS实时系统能够创建多个独立的任务任务之间互不干扰。任务创建之后并不是一起运行的,而是通过优先级顺序进行任务的调用,
    的头像 发表于 12-06 16:33 2289次阅读
    <b class='flag-5'>FreeRTOS</b><b class='flag-5'>任务</b>和协程<b class='flag-5'>简介</b>及<b class='flag-5'>实现</b>

    FreeRTOS任务间通信,怎么实现

    FreeRTOS 是一个可裁剪、可剥夺型的多任务内核,十分好用,而且没有任务数限制,在此之前分析过很多了,简单来说,FreeRTOS实时系统能够创建多个独立的
    的头像 发表于 02-23 09:21 1170次阅读

    FreeRTOS任务通知简介

    任务通知简介 任务通知FreeRTOS 中是一个可选的功能,要使用
    的头像 发表于 07-30 11:34 493次阅读

    FreeRTOS任务通知通用发送函数

    发送任务通知 任务通知通用发送函数 任务任务通知
    的头像 发表于 07-30 11:43 508次阅读
    <b class='flag-5'>FreeRTOS</b><b class='flag-5'>任务</b><b class='flag-5'>通知</b>通用发送函数