STM32F1+HAL库+FreeTOTS学习18——任务通知

STM32F1+HAL库+FreeTOTS学习18------任务通知

  • [1. 任务通知](#1. 任务通知)
    • [1.1 任务通知的引入](#1.1 任务通知的引入)
    • [1.2 任务通知简介](#1.2 任务通知简介)
    • [1.3 任务通知的优缺点](#1.3 任务通知的优缺点)
  • [2. 任务相关API函数](#2. 任务相关API函数)
    • [2.1 发送任务通知](#2.1 发送任务通知)
      • [2.1.1 xTaskGenericNotify()](#2.1.1 xTaskGenericNotify())
      • [2.1.2 xTaskNotifyGive()和xTaskNotifyGiveIndexed()](#2.1.2 xTaskNotifyGive()和xTaskNotifyGiveIndexed())
      • [2.1.2 xTaskNotify()和xTaskNotifyIndexed()](#2.1.2 xTaskNotify()和xTaskNotifyIndexed())
      • [2.1.2 xTaskNotifyAndQuery()和xTaskNotifyAndQueryIndexed()](#2.1.2 xTaskNotifyAndQuery()和xTaskNotifyAndQueryIndexed())
    • [2.2 在中断中发送任务通知](#2.2 在中断中发送任务通知)
    • [2.3 接收任务通知](#2.3 接收任务通知)
      • [2.3.1 ulTaskNotifyTake()和ulTaskNotifyTakeIndexed()](#2.3.1 ulTaskNotifyTake()和ulTaskNotifyTakeIndexed())
      • [2.3.1 xTaskNotifyWait ()和xTaskNotifyWaitIndexed()](#2.3.1 xTaskNotifyWait ()和xTaskNotifyWaitIndexed())
    • [2.4 清除任务通知](#2.4 清除任务通知)
      • [2.4.1 xTaskNotifyStateClear()和xTaskNotifyStateClearIndexed()](#2.4.1 xTaskNotifyStateClear()和xTaskNotifyStateClearIndexed())
      • [2.4.2 ulTaskNotifyValueClear()和ulTaskNotifyValueClearIndexed()](#2.4.2 ulTaskNotifyValueClear()和ulTaskNotifyValueClearIndexed())
  • [3. 任务通知操作示例](#3. 任务通知操作示例)
    • 3.1任务通知模拟信号量(二值信号量和数值信号量)
      • [3.1.1 代码实现](#3.1.1 代码实现)
      • [3.1.2 运行结果](#3.1.2 运行结果)
    • [3.2 任务通知模拟队列](#3.2 任务通知模拟队列)
      • [3.2.1 代码实现](#3.2.1 代码实现)
      • [3.2.2 运行结果](#3.2.2 运行结果)
    • [3.3 任务通知模拟事件标志组](#3.3 任务通知模拟事件标志组)
      • [3.2.1 代码实现](#3.2.1 代码实现)
      • [3.2.2 运行结果](#3.2.2 运行结果)

上一期我们学习了FreeRTOS中的事件标志组,这一期我们开始学习任务通知

1. 任务通知

1.1 任务通知的引入

在之前的篇章中,我们介绍了队列、信号量和事件标志组等内容,它们都是为了实现RTOS中任务的消息同步、适应不同的使用场景而引入的,但是他们都存在一个问题,任务与任务之间的消息同步需要使用到一个中间的对象(这个对象可以是队列、事件标志组、信号量),信号量的释放、获取;队列的写入和读出、事件标志组的置位、清除等操作都是间接的操作这个中间对量来完成。

实际上,这种方式会导致任务之间的消息同步变慢。有没有办法能够提高任务之间消息同步的速度提升呢?当然有,那就是引入任务通知。

【注】:任务通知在早期的FreeRTOS版本中并不存在,只在V8.2.0之后的版本才有,下面我们来介绍一下任务通知。

1.2 任务通知简介

  • 在 FreeRTOS 中,每一个任务都有两个用于任务通知功能的数组,分别为任务通知数组任务通知状态数组。其中任务通知数组中的每一个元素都是一个 32 位无符号类型的通知值;而任务通知状态数组中的元素则表示与之对应的任务通知的状态。
  • 任务通知数组任务通知状态数组在任务创建的时候就已经存在了,可以通过任务的任务控制块(任务句柄)访问。
  • 每个任务都会有这两个数组,数组的长度由宏 configTASK_NOTIFICATION_ARRAY_ENTRIES 决定,默认长度为1,且数组初始化之后默认值为0。

下面为相关源代码:

c 复制代码
/* 宏定义:定义任务通知相关数组的长度,默认长度为1 */
#define configTASK_NOTIFICATION_ARRAY_ENTRIES           1                       /* 定义任务通知数组的大小, 默认: 1 */

/* 任务控制块结构体定义,只展示任务通知相关数组,其他部分省略 */
typedef struct tskTaskControlBlock       /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
	/* 、、以上代码省略、、 */
	
  #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
        volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    #endif
    
    /* 、、以下代码省略、、 */
    
} tskTCB;

/* 任务创建函数,里面会调用prvInitialiseNewTask()函数,完成**任务通知数组**和**任务通知状态数组**的初始化 */
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                            const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const configSTACK_DEPTH_TYPE usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask )
    {
    
	/* 、、以上代码省略、、 */
	
	 prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL 		);
	 
	/* 、、以下代码省略、、 */

  	 }
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

/* prvInitialiseNewTask()函数,完成**任务通知数组**和**任务通知状态数组**的初始化 */
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
                                  const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                                  const uint32_t ulStackDepth,
                                  void * const pvParameters,
                                  UBaseType_t uxPriority,
                                  TaskHandle_t * const pxCreatedTask,
                                  TCB_t * pxNewTCB,
                                  const MemoryRegion_t * const xRegions )
{

	/* 、、以上代码省略、、 */
	
	/* 初始任务相关数组为0 */
	#if ( configUSE_TASK_NOTIFICATIONS == 1 )
    {
          memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
          memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
     }
    #endif
	
	/* 、、以下代码省略、、 */

}
  • 任务通知数组中存放的是通知的内容,我们称之为"通知值",当通知值为0时,表示没有任务通知,当通知值不为0时,表示有任务通知,通知的内容就是通知值。
  • 任务通知状态数组中存放任务通知的状态,任务通知有三种状态:未等待任务通知状态(默认状态);等待通知状态(接收方已经准备好,调用了接收任务通知函数,等待发送方发送通知);等待接收状态(发送方已经发送任务通知,调用了发送任务通知函数,等待接收方接收)。
  • 任务通知状态相关定义如下:
c 复制代码
/* Values that can be assigned to the ucNotifyState member of the TCB. */
#define taskNOT_WAITING_NOTIFICATION              ( ( uint8_t ) 0 ) 		/* 任务未等待通知状态 */
#define taskWAITING_NOTIFICATION                  ( ( uint8_t ) 1 )			/* 等待通知状态 */
#define taskNOTIFICATION_RECEIVED                 ( ( uint8_t ) 2 )			/* 等待接收状态 */

1.3 任务通知的优缺点

优点:

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

缺点:

  1. 无法发送数据给中断:ISR没有任务结构体,所以无法给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务。
  2. 只能点对点通信:任务通知只能是被指定的一个任务接收并处理
  3. 无法缓存多个数据:任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据(无法像队列那样入队和读出,只能保存一个通知值)。
  4. 发送不支持阻塞:发送方无法进入阻塞状态等待

2. 任务相关API函数

FreeRTOS 提供了任务通知的一些相关操作函数,其中任务通知相关 API 函数,如下两表:

我们前面提到:

  • 每一个任务都有两个用于任务通知功能的数组,分别为任务通知数组任务通知状态数组
  • 任务通知数组任务通知状态数组在任务创建的时候就已经存在了,可以通过任务的任务控制块(任务句柄)访问。
  • 每个任务都会有这两个数组,数组的长度由宏 configTASK_NOTIFICATION_ARRAY_ENTRIES 决定,默认长度为1,且数组初始化之后默认值为0。
  • 在数组的长度为1的情况下(默认情况),对应的我们会使用表1中的函数完成任务通知的相关操作。
  • 在数组的长度大于1的情况下,需要通过数组成员的下表进行索引,所有我们会使用表2中的函数。

【注】:表1和表2中的函数功能是一一对应的,只是表2中的函数是应对任务通知数组长度大于1的情况,增加了索引,本质上是一样的。在接下来的内容中,我们重点接收表1中相关API函数,表2中会稍微带过一下。

表1

函数 描述
xTaskNotify() 发送任务通知,带有通知值
xTaskNotifyAndQuery() 发送任务通知,带有通知值 ,且保留接收任务原来的通知值
xTaskNotifyGive() 发送任务通知,不带通知值
xTaskNotifyFromISR() 中断中发送任务通知,带有通知值
xTaskNotifyAndQueryFromISR() 中断中发送任务通知,带有通知值 ,且保留接收任务原来的通知值
vTaskNotifyGiveFromISR() 中断中发送任务通知,不带通知值
ulTaskNotifyTake() 接收任务通知,作为信号量使用,获取信号量
xTaskNotifyWait() 接收任务通知,作为队列和事件标志组使用,读取消息
xTaskNotifyStateClear() 清除任务通知状态
ulTaskNotifyValueClear() 清除任务通知值

表2

函数 描述
xTaskNotifyIndexed() 发送任务通知,带有通知值
xTaskNotifyAndQueryIndexed() 发送任务通知,带有通知值 ,且保留接收任务原来的通知值
xTaskNotifyGiveIndexed() 发送任务通知,不带通知值
xTaskNotifyIndexedFromISR() 中断中发送任务通知,带有通知值
xTaskNotifyAndQueryIndexedFromISR() 中断中发送任务通知,带有通知值 ,且保留接收任务原来的通知值
vTaskNotifyGiveIndexedFromISR() 中断中发送任务通知,不带通知值
ulTaskNotifyTakeIndexed() 接收任务通知,作为信号量使用,获取信号量
xTaskNotifyWaitIndexed() 接收任务通知,作为队列和事件标志组使用,读取消息
xTaskNotifyStateClearIndexed() 清除任务通知状态
ulTaskNotifyValueClearIndexed() 清除任务通知值

2.1 发送任务通知

为了实现任务通知模拟信号量、队列、事件标志组这三种对象的通信,满足不同场景下的需求,FreeRTOS分别提供了三个发送任务通知的函数,他们分别是:

表1.1 默认情况下的:

函数 描述
xTaskNotifyGive() 发送任务通知,不带通知值
xTaskNotify() 发送任务通知,带有通知值
xTaskNotifyAndQuery() 发送任务通知,带有通知值 ,且保留接收任务原来的通知值

表1.2 带有索引值的

函数 描述
xTaskNotifyGiveIndexed() 发送任务通知,不带通知值
xTaskNotifyIndexed() 发送任务通知,带有通知值
xTaskNotifyAndQueryIndexed() 发送任务通知,带有通知值 ,且保留接收任务原来的通知值

但是实际上这些函数都是宏定义,在FreeRTOS的源码"task.h"中有定义,源码如下:

c 复制代码
/* 发送任务通知,带有通知值 */
#define xTaskNotifyGive( xTaskToNotify ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( 0 ), eIncrement, NULL )
#define xTaskNotifyGiveIndexed( xTaskToNotify, uxIndexToNotify ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( uxIndexToNotify ), ( 0 ), eIncrement, NULL )

/* 发送任务通知,不带通知值 */
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL )
#define xTaskNotifyIndexed( xTaskToNotify, uxIndexToNotify, ulValue, eAction ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( uxIndexToNotify ), ( ulValue ), ( eAction ), NULL )

/* 发送任务通知,带有通知值 ,且保留接收任务原来的通知值  */
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )
#define xTaskNotifyAndQueryIndexed( xTaskToNotify, uxIndexToNotify, ulValue, eAction, pulPreviousNotifyValue ) \
    xTaskGenericNotify( ( xTaskToNotify ), ( uxIndexToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )

可以看到,上述所有函数都是调用了同一个函数xTaskGenericNotify(),那么我们先来学习xTaskGenericNotify()函数,上述的所有函数也就迎刃而解了。

2.1.1 xTaskGenericNotify()

我们先来看一下函数原型:

c 复制代码
typedef enum
{
    eNoAction = 0,            /* 不修改通知值,只会标记任务通知为等待接收状态 */
    eSetBits,                 /* 设置通知值特定的位. */
    eIncrement,               /* 通知值加1 */
    eSetValueWithOverwrite,   /* 覆写通知值 */
    eSetValueWithoutOverwrite /* 覆写前判断是否处于等待接收状态,如果是,则不覆写,返回失败 */
} eNotifyAction;

/* xTaskGenericNotify()函数参数列表中的通知方式为枚举类型,定义如上👆 */
/**
 * @函数名:       xTaskGenericNotify:发送任务通知函数
 * @参数1:        xTaskToNotify:接收任务通知的任务句柄(任务控制块)
 * @参数1:        uxIndexToNotify:任务通知的相关数组索引,表1.1中的函数无该参数,默认为0。
 * @参数1:        ulValue:通知值							 
 * @参数1:        eAction:通知方式,为枚举类型,在定义在上面 
 * @参数1:        pulPreviousNotificationValue:用于获取发送通知前的通知值
 * @retval      返回值为NULL,表示创建失败,其他值表示为创建事件标志组的句柄
 */
BaseType_t xTaskGenericNotify(	 TaskHandle_t xTaskToNotify,
								 UBaseType_t uxIndexToNotify,
								 uint32_t ulValue,
								 eNotifyAction eAction,
								 uint32_t * pulPreviousNotificationValue);

了解了函数原型,我们来看一下具体怎么实现的:

c 复制代码
/* 判断是否开启了任务通知 */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
	
	/* 函数定义 */
    BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
                                   UBaseType_t uxIndexToNotify,
                                   uint32_t ulValue,
                                   eNotifyAction eAction,
                                   uint32_t * pulPreviousNotificationValue )
    {
    	/* 创建任务控制块,返回值,任务通知状态 */
        TCB_t * pxTCB;
        BaseType_t xReturn = pdPASS;
        uint8_t ucOriginalNotifyState;

		/* 断言:确保索引值小于数组的有效长度 */
        configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );
        configASSERT( xTaskToNotify );
        pxTCB = xTaskToNotify;
		
		/* 进入临界区 */
        taskENTER_CRITICAL();
        {
       		 /* pulPreviousNotificationValue 不等于NULL,说明需要获取发送通知之前的通知值 */
            if( pulPreviousNotificationValue != NULL )
            {
              	/* 获取发送通知之前的通知值 */
                *pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ];
            }
			
			/* 在发送任务通知之前获取任务通知状态 */
            ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];
			
			/* 将任务通知的状态改成等待接收状态,以便于调用接收任务通知函数,判断是否有待接收的任务通知
			  如果没有任务通知,那就进入阻塞或者退出 */
            pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;

			/* 根据通知方式的不同,执行对应的操作 */
            switch( eAction )
            {
           		 /* 设置特定的位 */
                case eSetBits:
                    pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;
                    break;
                    
				/* 通知值加1 */
                case eIncrement:
                    ( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;
                    break;

				/* 覆写通知值 */
                case eSetValueWithOverwrite:
                    pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
                    break;
				
				/* 覆写前判断是否处于等待接收状态,如果是,则不覆写,返回失败 */
                case eSetValueWithoutOverwrite:

					/* 任务不处于等待接收通知状态,可以覆写 */
                    if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
                    {
                        pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
                    }
                    /* 否则返回失败 */
                    else
                    {
                        /* The value could not be written to the task. */
                        xReturn = pdFAIL;
                    }

                    break;

				/* 不修改通知值,只会标记任务通知为等待接收状态 */
                case eNoAction:

                    /* The task is being notified without its notify value being
                     * updated. */
                    break;
                    
				/* 正常不会存在这个情况 */
                default:

                    /* Should not get here if all enums are handled.
                     * Artificially force an assert by testing a value the
                     * compiler can't assume is const. */
                    configASSERT( xTickCount == ( TickType_t ) 0 );

                    break;
            }

			/* 用于调试 */
            traceTASK_NOTIFY( uxIndexToNotify );

            /* 如果在此之前任务因为等待通知而被阻塞,那么就解除阻塞 */
            if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
            {
          		  /* 将任务从阻塞列表移除 */
                listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
				/* 添加任务到就绪列表 */
                prvAddTaskToReadyList( pxTCB );

                /* 任务是因为等待任务通知而被阻塞的,所以不应该在任何一个事件列表中,这个是断言,. */
                configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );

				/* 低功耗相关 */
                #if ( configUSE_TICKLESS_IDLE != 0 )
                    {
                        /* If a task is blocked waiting for a notification then
                         * xNextTaskUnblockTime might be set to the blocked task's time
                         * out time.  If the task is unblocked for a reason other than
                         * a timeout xNextTaskUnblockTime is normally left unchanged,
                         * because it will automatically get reset to a new value when
                         * the tick count equals xNextTaskUnblockTime.  However if
                         * tickless idling is used it might be more important to enter
                         * sleep mode at the earliest possible time - so reset
                         * xNextTaskUnblockTime here to ensure it is updated at the
                         * earliest possible time. */
                        prvResetNextTaskUnblockTime();
                    }
                #endif

				/* 判断是否需要进行任务切换 */
                if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
                {
                    /* The notified task has a priority above the currently
                     * executing task so a yield is required. */
                    taskYIELD_IF_USING_PREEMPTION();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        taskEXIT_CRITICAL();

        return xReturn;
    }

#endif /* configUSE_TASK_NOTIFICATIONS */

上述就是 xTaskGenericNotify() 的所有定义,可以看到主要的内容可以分为下面几步:

  1. 判断是否需要保存原来的任务通知值,要的话保存,否则不保存
  2. 记录目标任务先前的通知状态,然后赋值新的状态
  3. 根据传入的参数不同,选择不同的通知值更新方式
  4. 根据先前的通知状态,判断是否需要进行解除目标任务的阻塞状态,是否需要进行任务切换。

有了上面的基础,我们接着来看发送任务通知函数的使用。

2.1.2 xTaskNotifyGive()和xTaskNotifyGiveIndexed()

此函数用于发送任务通知,通知方式为不带通知值,而是通知值加1,常用来模拟二值信号量和数字信号量。在task.h中有定义。函数原型如下:

c 复制代码
/**
 * @brief       xTaskNotifyGive:发送任务通知,通知方式为不带通知值,而是通知值加1
 * @param       xTaskToNotify : 需要被发送任务通知的目标任务句柄(任务控制块)
 * @retval      返回值默认都是pdPASS,没有实际用处
 */
 BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

/**
 * @brief       xTaskNotifyGiveIndexed:发送任务通知,通知方式为不带通知值,而是通知值加1
 * @param       xTaskToNotify : 需要被发送任务通知的目标任务句柄(任务控制块)
 * @param       uxIndexToNotify : 任务通知相关的数组成员索引值
 * @retval      返回值默认都是pdPASS,没有实际用处
 */
 BaseType_t xTaskNotifyGiveIndexed( TaskHandle_t xTaskToNotify, 
                                    UBaseType_t uxIndexToNotify );

2.1.2 xTaskNotify()和xTaskNotifyIndexed()

此函数用于发送任务通知,发送通知的方法如下:

  • 写入一个32位数字的通知值 (这种方法可以用来模拟队列)
  • 通知值+1 (用来模拟信号量)
  • 设置通知值中的一个或多个位(模拟事件标志组)
  • 保持通知值不变,只标记任务通知状态为等待接收状态。

在task.h中有定义。函数原型如下:

c 复制代码
/**
 * @brief       xTaskNotify:发送任务通知,带有通知值,且不保存原来的通知值
 * @param       xTaskToNotify: 需要被发送任务通知的目标任务句柄(任务控制块)
 * @param       ulValue: 需要写入的通知值
 * @param       eAction : 需要写入的方式
 * @retval      返回值默认都是pdPASS,为其他值时表示写入方式为:覆写前判断是否处于等待接收状态,如果是,则不覆写,返回失败
 */
 BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                         uint32_t ulValue,
                         eNotifyAction eAction );

/**
 * @brief       xTaskNotify:发送任务通知,带有通知值,且不保存原来的通知值
 * @param       xTaskToNotify: 需要被发送任务通知的目标任务句柄(任务控制块)
 * @param       uxIndexToNotify: 任务通知相关的数组成员索引值
 * @param       ulValue: 需要写入的通知值
 * @param       eAction : 需要写入的方式
 * @retval      返回值默认都是pdPASS,为其他值时表示写入方式为:覆写前判断是否处于等待接收状态,如果是,则不覆写,返回失败
 */
 BaseType_t xTaskNotifyIndexed( TaskHandle_t xTaskToNotify,
                                UBaseType_t uxIndexToNotify,
                                uint32_t ulValue,
                                eNotifyAction eAction );

下面是不同的写入方式与通知值之间的对应关系:

  • eNoAction :目标任务接收事件,但其通知值不会更新。在这种情况下, 不会使用 ulValue。
  • eSetBits : 目标任务的通知值将与 ulValue 进行按位"或"操作。
  • eIncrement:目标任务的通知值将增加 1,相当于调用 xTaskNotifyGive()。在这种情况下, 不会使用 ulValue。
  • eSetValueWithOverwrite :目标任务的通知值无条件设置为 ulValue
  • eSetValueWithoutOrwrite : 如果目标任务当前没有挂起的通知,则其通知值将设置为 ulValue。如果目标任务已有挂起的通知,则其通知值不会更新, 以免之前的值在使用前被覆盖。在这种情况下,调用 xTaskNotify() 会失败, 返回 pdFALSE。通过这种方式,可以模拟长度为1的队列写入消息。

2.1.2 xTaskNotifyAndQuery()和xTaskNotifyAndQueryIndexed()

此函数和 xTaskNotify用法上是一样的,只不过多了一个参数存放原来的通知值。具体使用方法可以参照 2.1.2 xTaskNotify()和xTaskNotifyIndexed()

我们这里只展示函数原型:

c 复制代码
```c
/**
 * @brief       xTaskNotifyAndQuery:发送任务通知,带有通知值,且保存原来的通知值
 * @param       xTaskToNotify: 需要被发送任务通知的目标任务句柄(任务控制块)
 * @param       ulValue: 需要写入的通知值
 * @param       eAction : 需要写入的方式
 * @param       pulPreviousNotifyValue : 保存上一次通知值的地址
 * @retval      返回值默认都是pdPASS,为其他值时表示写入方式为:覆写前判断是否处于等待接收状态,如果是,则不覆写,返回失败
 */
 BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
                                 uint32_t ulValue,
                                 eNotifyAction eAction,
                                 uint32_t *pulPreviousNotifyValue );
/**
 * @brief       xTaskNotify:发送任务通知,带有通知值,且不保存原来的通知值
 * @param       xTaskToNotify: 需要被发送任务通知的目标任务句柄(任务控制块)
 * @param       uxIndexToNotify: 任务通知相关的数组成员索引值
 * @param       ulValue: 需要写入的通知值
 * @param       eAction : 需要写入的方式
 * @param       pulPreviousNotifyValue : 保存上一次通知值的地址
 * @retval      返回值默认都是pdPASS,为其他值时表示写入方式为:覆写前判断是否处于等待接收状态,如果是,则不覆写,返回失败
 */
 BaseType_t xTaskNotifyAndQueryIndexed( TaskHandle_t xTaskToNotify,
                                        UBaseType_t uxIndexToNotify,
                                        uint32_t ulValue,
                                        eNotifyAction eAction,
                                        uint32_t *pulPreviousNotifyValue );

2.2 在中断中发送任务通知

和2.1内容类似,FreeRTOS中提供了中断中发送任务通知的API函数,如下表

表1.1 默认情况下的:

函数 描述(默认都是中断中使用)
vTaskNotifyGiveFromISR () 发送任务通知,不带通知值
xTaskNotifyFromISR () 发送任务通知,带有通知值
xTaskNotifyAndQueryFromISR() 发送任务通知,带有通知值 ,且保留接收任务原来的通知值

表1.2 带有索引值的

函数 描述(默认都是中断中使用)
vTaskNotifyGiveIndexedFromISR() 发送任务通知,不带通知值
xTaskNotifyIndexedFromISR() 发送任务通知,带有通知值
xTaskNotifyAndQueryIndexedFromISR() 发送任务通知,带有通知值 ,且保留接收任务原来的通知值

但是实际上这些函数都是宏定义,在FreeRTOS的源码"task.h"中有定义,源码如下:

c 复制代码
/* 发送任务通知,带有通知值 */
#define vTaskNotifyGiveFromISR( xTaskToNotify, pxHigherPriorityTaskWoken ) \
    vTaskGenericNotifyGiveFromISR( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( pxHigherPriorityTaskWoken ) );
#define vTaskNotifyGiveIndexedFromISR( xTaskToNotify, uxIndexToNotify, pxHigherPriorityTaskWoken ) \
    vTaskGenericNotifyGiveFromISR( ( xTaskToNotify ), ( uxIndexToNotify ), ( pxHigherPriorityTaskWoken ) );

/* 发送任务通知,不带通知值 */
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyIndexedFromISR( xTaskToNotify, uxIndexToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( uxIndexToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )

/* 发送任务通知,带有通知值 ,且保留接收任务原来的通知值  */
#define xTaskNotifyAndQueryIndexedFromISR( xTaskToNotify, uxIndexToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( uxIndexToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) \
    xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )

可以看到,上述所有函数都是调用了同一个函数xTaskGenericNotifyFromISR(),但是xTaskGenericNotifyFromISR和2.1中的xTaskGenericNotify()函数实现方法基本一致,只不过是增加了是否需要任务切换的判断,由于篇幅问题,我们这里就不做过多的赘述,内部实现的使用方法,请参照 2.1发送任务通知 中的内容

如果有疑问的地方,可以参照官网内容:RTOS任务通知

2.3 接收任务通知

在接收任务通知中,我们使用到的API函数只有两个,没有中断中使用的接收任务通知函数,如下表:

表3.1 默认情况下的:

函数 描述
ulTaskNotifyTake() 接收任务通知,作为信号量使用,获取信号量
xTaskNotifyWait() 接收任务通知,作为队列和事件标志组使用,读取消息

表3.2 带有索引值的

函数 描述(默认都是中断中使用)
ulTaskNotifyTakeIndexed() 接收任务通知,作为信号量使用,获取信号量
xTaskNotifyWaitIndexed() 接收任务通知,作为队列和事件标志组使用,读取消息

2.3.1 ulTaskNotifyTake()和ulTaskNotifyTakeIndexed()

ulTaskNotifyTake()和ulTaskNotifyTakeIndexed()用来 接收任务通知,作为信号量使用,获取信号量 ,在FreeRTOS提供源码的task.h中有定义:

c 复制代码
#define ulTaskNotifyTake( xClearCountOnExit, xTicksToWait ) \
    ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ), ( xClearCountOnExit ), ( xTicksToWait ) )
#define ulTaskNotifyTakeIndexed( uxIndexToWaitOn, xClearCountOnExit, xTicksToWait ) \
    ulTaskGenericNotifyTake( ( uxIndexToWaitOn ), ( xClearCountOnExit ), ( xTicksToWait ) )

可以看到,ulTaskNotifyTake()和ulTaskNotifyTakeIndexed()都是宏定义,实际上都是调用了ulTaskGenericNotifyTake()函数,所以我们先来看一下ulTaskGenericNotifyTake()的函数原型,再来理解这两个函数:

c 复制代码
/**
 * @brief       ulTaskGenericNotifyTake:获取任务的通知值,并将通知-1或者清零
 * @param       uxIndexToWaitOn : 任务通知相关的数组成员索引值
 * @param       xClearCountOnExit : 设置为pdTRUE,则在函数退出之前,会将通知值清零,为pdFALSE则通知值减1
 * @param       xTicksToWait : 阻塞等待时间
 * @retval      被递减或清除之前的任务通知值的值
 */
 uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWait,
                                      BaseType_t xClearCountOnExit,
                                      TickType_t xTicksToWait );

下面是函数定义:

c 复制代码
/* 必须先开启任务通知, */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )

    uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWait,
                                      BaseType_t xClearCountOnExit,
                                      TickType_t xTicksToWait )
    {
    	/* 定义返回值 */
        uint32_t ulReturn;

		/* 索引值需要小于任务通知相关数字的长度,否则为数组越界访问,不允许出现 */
        configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES );

		/* 进入临界区 */
        taskENTER_CRITICAL();
        {
            /* 判断通知值是否为0 */
            if( pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] == 0UL )
            {
                /* 通知值为0,表示没有消息,设置任务通知状态为等待通知状态 */
                pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;

				/* 判断是否允许阻塞等待任务通知 */
                if( xTicksToWait > ( TickType_t ) 0 )
                {
                	/* 阻塞当前任务 */
                    prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
					/* 用于调试,不必理会 */
                    traceTASK_NOTIFY_TAKE_BLOCK( uxIndexToWait );

                    /* All ports are written to allow a yield in a critical
                     * 挂起PendSV,进行任务切换,阻塞当前任务 */
                    portYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        /* 退出临界区 */
        taskEXIT_CRITICAL();

		/* 进入临界区,阻塞时间到达,从此开始执行 */
        taskENTER_CRITICAL();
        {
        	/* 用于调试 */
            traceTASK_NOTIFY_TAKE( uxIndexToWait );
            
            /* 获取任务通知的通知值 */
            ulReturn = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];

			/* 通知值不为0,则通知值有意义, */
            if( ulReturn != 0UL )
            {
            	/* 需要清零通知值 */
                if( xClearCountOnExit != pdFALSE )
                {
                    pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] = 0UL;
                }
                /* 通知值减1 */
                else
                {
                    pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] = ulReturn - ( uint32_t ) 1;
                }
            }
            /* 通知值无意义 */
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
			
			/* 无论接收通知值成功或失败,都将通知值标记为未等待通知状态 */
            pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
        }
        /* 退出临界区 */
        taskEXIT_CRITICAL();

		/* 返回清零或减一之前的通知值 */
        return ulReturn;
    }

#endif /* configUSE_TASK_NOTIFICATIONS */

上述就是 ulTaskGenericNotifyTake() 的所有定义,可以看到主要的内容可以分为下面几步:

  1. 判断通知值是否为0,如果为0的话,表示还没有任务通知,将当前任务通知状态赋值为等待通知状态,然后判断是否需要阻塞,看情况阻塞或者直接退出
  2. 如果通知值不为0,表示已经有任务通知,根据传入参数对通知值减一或者清零,然后将任务通知状态设置为未等待通知状态。

了解了ulTaskGenericNotifyTake()函数干了什么,我们来总结以下ulTaskNotifyTake()和ulTaskNotifyTakeIndexed()这两个函数:

c 复制代码
/**
 * @brief       ulTaskNotifyTake:获取任务的通知值,并将通知-1
 * @param       xClearCountOnExit : 设置为pdTRUE,则在函数退出之前,会将通知值清零,为pdFALSE则通知值减1
 * @param       xTicksToWait : 阻塞等待时间
 * @retval      被递减或清除之前的任务通知值的值
 */
 uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
                           TickType_t xTicksToWait );
/**
 * @brief       ulTaskNotifyTakeIndexed:获取任务的通知值,并将通知-1
 * @param       uxIndexToWaitOn : 任务通知相关的数组成员索引值
 * @param       xClearCountOnExit : 设置为pdTRUE,则在函数退出之前,会将通知值清零,为pdFALSE则通知值减1
 * @param       xTicksToWait : 阻塞等待事件
 * @retval      被递减或清除之前的任务通知值的值
 */
uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn, 
                                  BaseType_t xClearCountOnExit, 
                                  TickType_t xTicksToWait );

2.3.1 xTaskNotifyWait ()和xTaskNotifyWaitIndexed()

xTaskNotifyWait ()和xTaskNotifyWaitIndexed()用来接收任务通知,作为队列和事件标志组使用,读取消息,在FreeRTOS提供源码的task.h中有定义:

c 复制代码
#define xTaskNotifyWait( ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait ) \
    xTaskGenericNotifyWait( tskDEFAULT_INDEX_TO_NOTIFY, ( ulBitsToClearOnEntry ), ( ulBitsToClearOnExit ), ( pulNotificationValue ), ( xTicksToWait ) )
#define xTaskNotifyWaitIndexed( uxIndexToWaitOn, ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait ) \
    xTaskGenericNotifyWait( ( uxIndexToWaitOn ), ( ulBitsToClearOnEntry ), ( ulBitsToClearOnExit ), ( pulNotificationValue ), ( xTicksToWait ) )

可以看到,xTaskNotifyWait ()和xTaskNotifyWaitIndexed()都是宏定义,实际上都是调用了xTaskGenericNotifyWait()函数,所以我们先来看一下xTaskGenericNotifyWait()的函数原型,再来理解这两个函数:

xTaskGenericNotifyWait()函数用于等待通知值中的特定比特位被置1,在等待任务通知前和成功等待任务通知之后将通知值的特点位清零,获取等待超时后的任务通知值,等操作,可用来解决队列消息的读出和时间标志组的获取等操作。

c 复制代码
/**
 * @brief       xTaskGenericNotifyWait:获取任务的通知值,等待通知值中的特定比特位被置1
 * @param       uxIndexToWaitOn: 任务通知相关的数组成员索引值
 * @param       ulBitsToClearOnEntry: 调用 xTaskNotifyWait() 时,如果通知已挂起,则在进入 xTaskNotifyWait() 函数(即任务等待新通知之						  
 * 				前)时,ulBitsToClearOnEntry 中设置的任何位都会在调用 RTOS 任务的通知值中 被清除 。
 *																		 传入0x00000000表示不清零通知值
 * 																		 传入0x00000001表示清零通知值的第0位
 * 																	     传入0x00000003表示清零通知值的第0位和第1位
 * 
 * @param       ulBitsToClearOnExit: 如果在调用 xTaskNotifyWait() 函数时收到了通知,则在 xTaskNotifyWait() 函数退出之前	
 * 				ulBitsToClearOnExit 中设置的任何位都会在调用 RTOS 任务的通知值中 被清除。
 * 																		 传入0x00000000表示不清零通知值
 * 																		 传入0x00000001表示清零通知值的第0位
 * 																	     传入0x00000003表示清零通知值的第0位和第1位
 * @param       pulNotificationValue: 上一次通知值(上一次指的是通过ulBitsToClearOnExit清除之前的通知值)保存的地方,如果不需要此功能,传入NULL即可
 * @param       xTicksToWait : 等待阻塞时间
 * @retval      返回值为pdTRUE,表示等待任务通知成功,返回值为pdFLASE,表示等待任务通知失败
 */
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWaitOn,
                                   uint32_t ulBitsToClearOnEntry,
                                   uint32_t ulBitsToClearOnExit,
                                   uint32_t * pulNotificationValue,
                                   TickType_t xTicksToWait );

【注】:当任务的通知被"挂起"时,实际上是指任务在等待一个通知的过程中处于阻塞状态。因此通知挂起,说明还没有通知值,这个时候ulBitsToClearOnEntry才能起作用。

了解了xTaskGenericNotifyWait()的函数原型,我们来看一下具体的定义和如何实现的:

c 复制代码
/* 开启任务通知的宏定义 */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )

	/* xTaskGenericNotifyWait()函数定义 */
    BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWait,
                                       uint32_t ulBitsToClearOnEntry,
                                       uint32_t ulBitsToClearOnExit,
                                       uint32_t * pulNotificationValue,
                                       TickType_t xTicksToWait )
    {
    	/* 返回值 */
        BaseType_t xReturn;

		/* 确保索引值小于任务通知相关数组的长度。这是个断言 */
        configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES );

		/* 进入临界区 */
        taskENTER_CRITICAL();
        {
            /* 判断任务通知状态是否不为等待通知状态,如果是,说明没有任务通知 */
            if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )
            {
                /* 开始前清零任务通知值的特定位*/
                pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnEntry;

                /* 修改任务通知状态为等待通知状态 */
                pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;
				
				/* 此时还没有任务通知,那就判断是否要进入阻塞,进行任务切换 */
                if( xTicksToWait > ( TickType_t ) 0 )
                {
                    /* 把当前任务添加到阻塞列表 */
                    prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
					/* 用来调试 */
                    traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWait );

                   /* 挂起PendSV中断,任务切换 */
                    portYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        /* 退出临界区 */
        taskEXIT_CRITICAL();

		/* 如果任务阻塞时间到来,下次就会从这里执行,开始进入临界区 */
        taskENTER_CRITICAL();
        {
        	/* 用于调试 */
            traceTASK_NOTIFY_WAIT( uxIndexToWait );

			/* 判断是否需要保存任务通知值 */
            if( pulNotificationValue != NULL )
            {
                /* 保存清除特定位之前的通知值,即上一次通知值 */
                *pulNotificationValue = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];
            }

            /*再次判断任务是否不为等待接收通知状态,即是否还是没有通知,如果还没有就返回pdFLASE,接收失败*/
            if( pxCurrentTCB->ucNotifyState[ uxIndexToWait ] != taskNOTIFICATION_RECEIVED )
            {
                /* A notification was not received. */
                xReturn = pdFALSE;
            }
            /* 否则说明接收到了通知,将通知值的指定位清零 */
            else
            {
                /* A notification was already pending or a notification was
                 * received while the task was waiting. */
                pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] &= ~ulBitsToClearOnExit;
                xReturn = pdTRUE;
            }

			/* 无论接收是否成功或者失败,都将任务通知状态标记为等待通知状态 */
            pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
        }
        /* 退出临界区 */
        taskEXIT_CRITICAL();

		/* 返回接收成功还是失败 */
        return xReturn;
    }

#endif /* configUSE_TASK_NOTIFICATIONS */

上述就是 xTaskGenericNotifyWait() 的所有定义,可以看到主要的内容可以分为下面几步:

  1. 判断任务有没有通知值,如果没有的话,根据参数ulBitsToClearOnEntry将通知值的特定位清零,更新任务通知状态为等待通知状态,判断是否需要阻塞,切换任务
  2. 如果有通知值,或者在阻塞时间内等到了任务通知的通知值,则先判断是否需要保存通知值,根据需要选择保存或不保存,如何根据参数ulBitsToClearOnExit将通知值的特定位清零。如果没通知值,且阻塞时间到了也没等到任务通知值,那么跳过通知值清零这一步,
  3. 最后不管成功接收与否,都将任务通知状态标记为未等待通知状态。

了解了xTaskGenericNotifyWait()函数干了什么,我们来总结以下xTaskNotifyWait ()和xTaskNotifyWaitIndexed()这两个函数:

c 复制代码
/**
 * @brief       xTaskNotifyWait:获取任务的通知值,等待通知值中的特定比特位被置1,不带索引值
 * @param       ulBitsToClearOnEntry: 调用 xTaskNotifyWait() 时,如果通知已挂起,则在进入 xTaskNotifyWait() 函数(即任务等待新通知之						  
 * 				前)时,ulBitsToClearOnEntry 中设置的任何位都会在调用 RTOS 任务的通知值中 被清除 。
 *																		 传入0x00000000表示不清零通知值
 * 																		 传入0x00000001表示清零通知值的第0位
 * 																	     传入0x00000003表示清零通知值的第0位和第1位
 * 
 * @param       ulBitsToClearOnExit: 如果在调用 xTaskNotifyWait() 函数时收到了通知,则在 xTaskNotifyWait() 函数退出之前	
 * 				ulBitsToClearOnExit 中设置的任何位都会在调用 RTOS 任务的通知值中 被清除。
 * 																		 传入0x00000000表示不清零通知值
 * 																		 传入0x00000001表示清零通知值的第0位
 * 																	     传入0x00000003表示清零通知值的第0位和第1位
 * @param       pulNotificationValue: 上一次通知值(上一次指的是通过ulBitsToClearOnExit清除之前的通知值)保存的地方,如果不需要此功能,传入NULL即可
 * @param       xTicksToWait : 等待阻塞时间
 * @retval      返回值为pdTRUE,表示等待任务通知成功,返回值为pdFLASE,表示等待任务通知失败
 */
 BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                             uint32_t ulBitsToClearOnExit,
                             uint32_t *pulNotificationValue,
                             TickType_t xTicksToWait );
/**
 * @brief       xTaskNotifyWaitIndexed:获取任务的通知值,等待通知值中的特定比特位被置1,带有索引值
 * @param       uxIndexToWaitOn: 任务通知相关的数组成员索引值
 * @param       ulBitsToClearOnEntry: 调用 xTaskNotifyWait() 时,如果通知已挂起,则在进入 xTaskNotifyWait() 函数(即任务等待新通知之						  
 * 				前)时,ulBitsToClearOnEntry 中设置的任何位都会在调用 RTOS 任务的通知值中 被清除 。
 *																		 传入0x00000000表示不清零通知值
 * 																		 传入0x00000001表示清零通知值的第0位
 * 																	     传入0x00000003表示清零通知值的第0位和第1位
 * 
 * @param       ulBitsToClearOnExit: 如果在调用 xTaskNotifyWait() 函数时收到了通知,则在 xTaskNotifyWait() 函数退出之前	
 * 				ulBitsToClearOnExit 中设置的任何位都会在调用 RTOS 任务的通知值中 被清除。
 * 																		 传入0x00000000表示不清零通知值
 * 																		 传入0x00000001表示清零通知值的第0位
 * 																	     传入0x00000003表示清零通知值的第0位和第1位
 * @param       pulNotificationValue: 上一次通知值(上一次指的是通过ulBitsToClearOnExit清除之前的通知值)保存的地方,如果不需要此功能,传入NULL即可
 * @param       xTicksToWait : 等待阻塞时间
 * @retval      返回值为pdTRUE,表示等待任务通知成功,返回值为pdFLASE,表示等待任务通知失败
 */
 BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWaitOn,
                                    uint32_t ulBitsToClearOnEntry,
                                    uint32_t ulBitsToClearOnExit,
                                    uint32_t *pulNotificationValue,
                                    TickType_t xTicksToWait );

2.4 清除任务通知

在清除任务通知中,我们使用到的API函数只有两个,没有中断中使用的接收任务通知函数,如下表:

表3.1 默认情况下的:

函数 描述
xTaskNotifyStateClear() 清除任务通知状态
ulTaskNotifyValueClear() 清除任务通知值

表3.2 带有索引值的

函数 描述(默认都是中断中使用)
xTaskNotifyStateClearIndexed() 清除任务通知状态
ulTaskNotifyValueClearIndexed() 清除任务通知值

由于这个内容的函数使用相对简单,所以我下面直接展示函数原型,内部实现不做介绍。

2.4.1 xTaskNotifyStateClear()和xTaskNotifyStateClearIndexed()

xTaskNotifyStateClear()和xTaskNotifyStateClearIndexed()用来 清除任务通知的状态,在FreeRTOS提供源码的task.h中有定义,函数原型如下:

c 复制代码
/**
 * @brief       xTaskNotifyStateClear:清除任务通知的状态,不带索引值
 * @param       xTask : 需要被清除任务状态的任务句柄
 * @retval      返回值为pdTRUE,表示清除成功,返回值为pdFLASE,表示清除失败
 */
BaseType_t xTaskNotifyStateClear( TaskHandle_t xTask );
/**
 * @brief       xTaskNotifyStateClearIndexed:清除任务通知的状态,带有索引值
 * @param       xTask : 需要被清除任务状态的任务句柄
 * @param       uxIndexToClear : 任务通知相关的数组成员索引值
 * @retval      返回值为pdTRUE,表示清除成功,返回值为pdFLASE,表示清除失败
 */
BaseType_t xTaskNotifyStateClearIndexed( TaskHandle_t xTask, 
                                         UBaseType_t uxIndexToClear );

2.4.2 ulTaskNotifyValueClear()和ulTaskNotifyValueClearIndexed()

ulTaskNotifyValueClear()和ulTaskNotifyValueClearIndexed()用来 清除任务通知的通知值,在FreeRTOS提供源码的task.h中有定义,函数原型如下:

c 复制代码
/**
 * @brief       ulTaskNotifyValueClear:清除任务通知的值,带有索引值
 * @param       xTask : 需要被清除任务通知值的任务句柄
 * @param       ulBitsToClear : 需要被清除的通知值特定为,传入0xffffffff时,表示全部清零,传入0时,可用来查询当前的任务通知值
 * @retval      返回值未清零前的任务通知值
 */
uint32_t ulTaskNotifyValueClear( TaskHandle_t xTask, 
                                 uint32_t ulBitsToClear );
/**
 * @brief       ulTaskNotifyValueClearIndexed:清除任务通知的值,带有索引值
 * @param       xTask : 需要被清除任务通知值的任务句柄
 * @param       uxIndexToClear : 任务通知相关的数组成员索引值
 * @param       ulBitsToClear : 需要被清除的通知值特定为,传入0xffffffff时,表示全部清零,传入0时,可用来查询当前的任务通知值
 * @retval      返回值未清零前的任务通知值
 */
uint32_t ulTaskNotifyValueClearIndexed( TaskHandle_t xTask, 
                                        UBaseType_t uxIndexToClear,
                                        uint32_t ulBitsToClear );

以上就是所有FreeRTOS官方文档中提到的任务通知相关API函数的介绍了,下面我们来看一下实际的任务通知模拟信号量、队列、时间标志组的具体应用。

3. 任务通知操作示例

为了避免篇幅过长,我们下面就直接展示关键部分的代码,包括创建的任务、按键处理以及具体会用到的代码,其他部分的代码,可以参照往期的内容,话不多说,我这里先展示以下公告会用到的部分代码。

任务配置相关部分

c 复制代码
/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 
 */
#define TASK1_PRIO      1                  /* 任务优先级 */
#define TASK1_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task1Task_Handler;  /* 任务句柄 */
void task1(void *pvParameters);					/*任务函数*/

/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 
 */
#define TASK2_PRIO      2                  /* 任务优先级 */
#define TASK2_STK_SIZE  128                 /* 任务堆栈大小 */
TaskHandle_t            Task2Task_Handler;  /* 任务句柄 */
void task2(void *pvParameters);					/*任务函数*/
 
/******************************************************************************************************/

/**
 * @brief       FreeRTOS例程入口函数
 * @param       无
 * @retval      无
 */
void freertos_demo(void)
{

	taskENTER_CRITICAL();           /* 进入临界区,关闭中断,此时停止任务调度*/

    /* 创建任务1 */
    xTaskCreate((TaskFunction_t )task1,
                (const char*    )"task1",
                (uint16_t       )TASK1_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK1_PRIO,
                (TaskHandle_t*  )&Task1Task_Handler);
    /* 创建任务2 */
    xTaskCreate((TaskFunction_t )task2,
                (const char*    )"task2",
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler);

    taskEXIT_CRITICAL();            /* 退出临界区,重新开启中断,开启任务调度 */
    vTaskStartScheduler();		//开启任务调度
}

按键扫描部分

c 复制代码
void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
{
   static uint8_t Key_Val[Key_Name_Max];    //按键值的存放位置
   static uint8_t Key_Flag[Key_Name_Max];   //KEY0~2为0时表示按下,为1表示松开,WKUP反之
    
   Key_Val[KeyName] = Key_Val[KeyName] <<1;  //每次扫描完,将上一次扫描的结果左移保存
   
    switch(KeyName)
    {
        case Key_Name_Key0:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin));    //读取Key0按键值
            break;
        case Key_Name_Key1:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin));   //读取Key1按键值
            break;
        case Key_Name_Key2:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin));   //读取Key2按键值
            break;
//        case Key_Name_WKUP:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin));   //读取WKUP按键值
//            break; 
        default:
            break;
    }
//    if(KeyName == Key_Name_WKUP)     //WKUP的电路图与其他按键不同,所以需要特殊处理
//    {
//        //WKUP特殊情况
//        //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
//        if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
//        {
//            (*OnKeyOneDown)();
//           Key_Flag[KeyName] = 0;
//        }
//        //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
//        if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
//        {
//            (*OnKeyOneUp)();
//           Key_Flag[KeyName] = 1;
//        } 
//    }
//    else                               //Key0~2按键逻辑判断
//    {
        //Key0~2常规判断
          //当按键标志为1(松开)是,判断是否按下
        if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1)
        {
            (*OnKeyOneDown)();
           Key_Flag[KeyName] = 0;
        }
        //当按键标志位为0(按下),判断按键是否松开
        if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0)
        {
            (*OnKeyOneUp)();
           Key_Flag[KeyName] = 1;
        }  
//    }
     
   
}

下面是具体的实验代码。

3.1任务通知模拟信号量(二值信号量和数值信号量)

在STM32F103RCT6上运行FreeRTOS,通过按键控制,控制任务通知模拟二值信号量和数值信号量,具体要求如下:

  • 创建两个任务,分别为任务1和任务2
  • 任务1:按键0按下,发送任务通知给任务2的通知值[0],发送方式为通知值加1,并且不获取发送任务通知前任务通知的通知值;按键1按下,发送任务通知给任务2的通知值[1],发送方式为通知值加1,并且不获取发送任务通知前任务通知的通知值
  • 任务2:接收任务通知,并且在接收到任务通知之后,把任务通知的通知值减一/清零:ulTaskNotifyTake()函数的第一个参数为pdTRUE表示通知值清零,为pdFLASE表示通知值清零。
    【注】:我们这里是同时模拟二值信号量和数值信号量,因此任务通知相关的数值长度为2,但是默认长度是1,需要修改,且调用的函数也需要使用带有索引值的。

3.1.1 代码实现

  1. 任务处理部分
c 复制代码
#define configTASK_NOTIFICATION_ARRAY_ENTRIES           2                       /* 定义任务通知数组的大小, 默认: 1 ,这里被我修改成了2,用来同时使用二值信号量和计数信号量*/

/**
* @brief       task1:用于按键扫描,按键0按下,发送任务通知给任务2的通知值[0],发送方式为通知值加1,并且不获取发送任务通知前任务通知的通知值
* @brief                           按键1按下,发送任务通知给任务2的通知值[1],发送方式为通知值加1,并且不获取发送任务通知前任务通知的通知值
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task1(void *pvParameters)
{
    
    while(1)
    {
		Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);
		Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task);
		vTaskDelay(10);
    }
}	
/**
* @brief       task2:接收任务通知,并且在接收到任务通知之后,把任务通知的通知值减一/清零:ulTaskNotifyTake()函数的第一个参数为pdTRUE表示通知值清零,为pdFLASE表示通知值清零
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task2(void *pvParameters)	
{	
	uint32_t notifyVal = 0;
	
	while(1)
    {	
		notifyVal = ulTaskNotifyTakeIndexed(Task2NotifyIndex_BinarySemaphore,pdTRUE,0);
		/* 返回值为被递减或清除之前的任务通知值的值,不为0表示获取成功,为0表示获取失败  */
		
		// notifyVal = ulTaskNotifyTake( pdTRUE, 0 );	/* 二值信号量的获取也可以使用这个函数,因为我们这是二值信号量的索引值为0,但是数值信号量不可以,只能使用ulTaskNotifyTakeIndexed()函数 */
		
		if(notifyVal != 0)
		{
			
			printf("任务通知模拟二值信号量获取成功\r\n");
		}
		
		notifyVal = ulTaskNotifyTakeIndexed(Task2NotifyIndex_CountingSemaphore,pdFALSE,0);
		/* 返回值为被递减或清除之前的任务通知值的值,不为0表示获取成功,为0表示获取失败  */
		if(notifyVal != 0)
		{
			
			printf("任务通知模拟计数信号量获取成功,当前计数值为:%d\r\n",notifyVal - 1);
		}
		vTaskDelay(1000);
    }

}
  1. 按键处理部分
c 复制代码
/* 这里我们修改了"FreeRTOSConfig.h" 中的 configTASK_NOTIFICATION_ARRAY_ENTRIES 宏,
   使得通知值数组长度变成了2,第一个存放二值信号量,第二个存放计数信号量*/
#define Task2NotifyIndex_BinarySemaphore  (0)
#define Task2NotifyIndex_CountingSemaphore  (1)

void Key0_Down_Task(void)
{
	if(Task2Task_Handler != NULL)
	{
		printf("\r\n按键0按下\r\n\r\n");
		/* 向任务2发送任务通知给通知值[0] */
		printf("任务通知模拟二值信号量释放成功!\r\n");
		xTaskNotifyGiveIndexed(Task2Task_Handler,Task2NotifyIndex_BinarySemaphore);
		
		// xTaskNotifyGive(Task2Task_Handler);  	/* 二值信号量的获取也可以使用这个函数,因为我们这是二值信号量的索引值为0,但是数值信号量不可以,只能使用 xTaskNotifyGiveIndexed()函数 */
	}

}

void Key1_Down_Task(void)
{
	if(Task2Task_Handler != NULL)
	{
		printf("\r\n按键1按下\r\n\r\n");
		/* 向任务2发送任务通知给通知值[0] */
		printf("任务通知模拟计数信号量释放成功!\r\n");
		xTaskNotifyGiveIndexed(Task2Task_Handler,Task2NotifyIndex_CountingSemaphore);
	}
	
}

3.1.2 运行结果

3.2 任务通知模拟队列

在STM32F103RCT6上运行FreeRTOS,通过按键控制,控制任务通知模拟队列,具体要求如下:

  • 创建两个任务,分别为任务1和任务2
  • 任务1:按键0按下,发送任务通知给任务2,发送方式为通知值覆写,发送数据为按键0的键值;按键1按下,发送任务通知给任务2,发送方式为通知值覆写,发送数据为按键1的键值。
  • 任务2:接收任务通知,并且在接收到任务通知之后,读出通知值存放到keyVal中,然后清零通知值,随后在串口打印消息。

3.2.1 代码实现

  1. 任务处理部分
c 复制代码
/**
* @brief       task1:用于按键扫描,按键0按下,发送任务通知给任务2,发送方式为通知值覆写,发送数据为按键0的键值,并且不获取发送任务通知前任务通知的通知值
* @brief                           按键1按下,发送任务通知给任务2,发送方式为通知值覆写,发送数据为按键1的键值,并且不获取发送任务通知前任务通知的通知值
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task1(void *pvParameters)
{
    
    while(1)
    {
		Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);
		Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task);
		vTaskDelay(10);
    }
}	
/**
* @brief       task2:接收任务通知,并且在接收到任务通知之后,读出通知值存放到keyVal中,然后清零通知值,随后在串口打印消息。
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task2(void *pvParameters)	
{	
	uint32_t keyVal = 0;
	
	while(1)
    {	
		xTaskNotifyWait(0x00000000,0xffffffff,&keyVal,portMAX_DELAY);
		
		if(keyVal)
		{
				printf("任务通知模拟消息队列,收到的消息为:%d\r\n",keyVal);
				keyVal = 0;
		}
    }

}
  1. 按键处理部分
c 复制代码
typedef enum
 {
     Key_Name_Key0 = 0x01,
     Key_Name_Key1,
     Key_Name_Key2,
     Key_Name_WKUP,
     Key_Name_Max
 }EnumKeyO

void Key1_Down_Task(void)
{
	printf("\r\n按键1按下\r\n\r\n");
	if(Task2Task_Handler != NULL)
	{
		/* 向任务2发送任务通知给通知值,发送方式为覆写,模拟队列的消息写入*/
		printf("任务通知模拟队列写入消息成功,写入的消息为:%d\r\n",Key_Name_Key1);
		
		xTaskNotify(Task2Task_Handler,Key_Name_Key1,eSetValueWithOverwrite);
	}
}extern TaskHandle_t   	Task2Task_Handler;  /* 任务2句柄 */

void Key0_Down_Task(void)
{
	printf("\r\n按键0按下\r\n\r\n");
	if(Task2Task_Handler != NULL)
	{
		/* 向任务2发送任务通知给通知值,发送方式为覆写,模拟队列的消息写入*/
		printf("任务通知模拟队列写入消息成功,写入的消息为:%d\r\n",Key_Name_Key0);
		
		xTaskNotify(Task2Task_Handler,Key_Name_Key0,eSetValueWithOverwrite);

	}
}

3.2.2 运行结果

3.3 任务通知模拟事件标志组

在STM32F103RCT6上运行FreeRTOS,通过按键控制,控制任务通知模拟事件标志组,具体要求如下:

  • 创建两个任务,分别为任务1和任务2
  • 任务1:按键0按下,将通知值第0位置1;按键1按下,将通知值第1位置1。按键按下,打印相关信息,开启LED指示。
  • 任务2:当按键1和0的事件标志组位都被置1,打印相关信息,并关闭相应的LED指示。

3.2.1 代码实现

  1. 任务处理部分
c 复制代码
/**
* @brief       task1:用于按键扫描,按键0或1按下,自动置位事件标志组,并开启相应的LED指示
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task1(void *pvParameters)
{
    
    while(1)
    {
		Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);
		Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task);
		vTaskDelay(10);
    }
}	
/**
* @brief       task2:当按键1和0的事件标志组位都被置1,打印相关信息,并关闭相应的LED指示
 * @param       pvParameters : 传入参数(未用到)
 * @retval      无
 */
void task2(void *pvParameters)	
{	
	uint32_t NotifyVal = 0;
	while(1)
    {	
		
		/* 等待按键0和1的事件标志位,同时不清清除通知值 */
		xTaskNotifyWait( 0x00000000, 0x00000000, &NotifyVal, portMAX_DELAY );
		
		/* 当按键0和按键1的对应标志位都为1的情况下,清零通知值,然后打印相关信息 */
		if( (NotifyVal & Key0_EventBit) && (NotifyVal & Key1_EventBit) )
		{
			/* 清零通知值 */
			ulTaskNotifyValueClear(NULL,0xffffffff);
			/* 打印相关信息 */
			printf("等待到的事件标志为:%#x\r\n",NotifyVal);
			
			/* 关闭LED指示 */
			HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,LED_OFF);
			HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,LED_OFF);
		}

		vTaskDelay(50);
    }

}
  1. 按键处理部分
c 复制代码
/*  因为这两个宏在 freertos_demo.c 文件中有使用,所以需要放在key.h中,而不是放在key.c中 */
#define Key0_EventBit (0x01 << 0)			/*key0对应标志组的bit0*/
#define Key1_EventBit (0x01 << 1)			/*key1对应标志组的bit1*/

void Key0_Down_Task(void)
{
	printf("\r\n按键0按下\r\n\r\n");
	/* 设置事件标志位 */
	HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,LED_ON);
	
	/* 开启LED指示 */
	 xTaskNotify( Task2Task_Handler,Key0_EventBit,eSetBits );
	
}

void Key1_Down_Task(void)
{
	printf("\r\n按键1按下\r\n\r\n");
	/* 设置事件标志位 */
	HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,LED_ON);
	
	/* 开启LED指示 */
	xTaskNotify( Task2Task_Handler,Key1_EventBit,eSetBits );

}

3.2.2 运行结果

以上就是本期的所有内容,不能说是很多,只能说是非常多,创造不易,点个关注再走呗。

相关推荐
网易独家音乐人Mike Zhou26 分钟前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
PegasusYu3 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
朝九晚五ฺ3 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
猫爪笔记5 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
pq113_65 小时前
ftdi_sio应用学习笔记 3 - GPIO
笔记·学习·ftdi_sio
澄澈i6 小时前
设计模式学习[8]---原型模式
学习·设计模式·原型模式
爱米的前端小笔记6 小时前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
alikami7 小时前
【前端】前端学习
学习
一只小菜鸡..7 小时前
241118学习日志——[CSDIY] [ByteDance] 后端训练营 [06]
学习
lantiandianzi8 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件