当前位置: 首页 > article >正文

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

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

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

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

1. 任务通知

1.1 任务通知的引入

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

在这里插入图片描述

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

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

1.2 任务通知简介

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

在这里插入图片描述

下面为相关源代码:

/* 宏定义:定义任务通知相关数组的长度,默认长度为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时,表示有任务通知,通知的内容就是通知值。
  • 任务通知状态数组中存放任务通知的状态,任务通知有三种状态:未等待任务通知状态(默认状态);等待通知状态(接收方已经准备好,调用了接收任务通知函数,等待发送方发送通知);等待接收状态(发送方已经发送任务通知,调用了发送任务通知函数,等待接收方接收)。
  • 任务通知状态相关定义如下:
/* 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”中有定义,源码如下:

/* 发送任务通知,带有通知值 */
#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()

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

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);

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

/* 判断是否开启了任务通知 */
#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中有定义。函数原型如下:

/**
 * @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中有定义。函数原型如下:

/**
 * @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
/**
 * @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”中有定义,源码如下:

/* 发送任务通知,带有通知值 */
#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中有定义:

#define ulTaskNotifyTake( xClearCountOnExit, xTicksToWait ) \
    ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ), ( xClearCountOnExit ), ( xTicksToWait ) )
#define ulTaskNotifyTakeIndexed( uxIndexToWaitOn, xClearCountOnExit, xTicksToWait ) \
    ulTaskGenericNotifyTake( ( uxIndexToWaitOn ), ( xClearCountOnExit ), ( xTicksToWait ) )

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

/**
 * @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 );

下面是函数定义:

/* 必须先开启任务通知, */
#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()这两个函数:

/**
 * @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中有定义:

#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,在等待任务通知前和成功等待任务通知之后将通知值的特点位清零,获取等待超时后的任务通知值,等操作,可用来解决队列消息的读出和时间标志组的获取等操作。

/**
 * @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()的函数原型,我们来看一下具体的定义和如何实现的:

/* 开启任务通知的宏定义 */
#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()这两个函数:

/**
 * @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中有定义,函数原型如下:

/**
 * @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中有定义,函数原型如下:

/**
 * @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. 任务通知操作示例

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

任务配置相关部分

/* 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();		//开启任务调度
}

按键扫描部分

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 代码实现

  • 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
  1. 任务处理部分
#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. 按键处理部分
/* 这里我们修改了"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 代码实现

  • 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
  1. 任务处理部分
/**
* @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. 按键处理部分
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 代码实现

  • 由于本期内容涉及到按键扫描,会用到: STM32框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
  1. 任务处理部分
/**
* @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. 按键处理部分
/*  因为这两个宏在 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 运行结果

在这里插入图片描述

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

在这里插入图片描述


http://www.kler.cn/news/358137.html

相关文章:

  • ubuntu 安装keepalived+haproxy
  • Linux小知识2 系统的启动
  • docker搭建etcd集群环境方式
  • J.D商品详情,一“网”打尽 —— PHP爬虫API数据获取全攻略
  • springcloud之服务集群注册与发现 Eureka
  • 递归、搜索与回溯(二)——递归练习与快速幂
  • sqli-labs less-26 空格绕过
  • 各种排序方法总结
  • Git 多环境(平台)密钥配置(与 gitee和 github上的俩个项目)
  • 即懂——XML Schema的名称空间
  • 【二维数组】对称图形
  • 2024_E_100_连续字母长度
  • ESP32-C3 入门笔记04:gpio_key 按键 (ESP-IDF + VSCode)
  • ssm基于java的招聘系统设计与开发+vue
  • OpenCV答题卡识别
  • 数据结构与算法JavaScript描述练习------第13章检索算法
  • Oracle NUMTODSINTERVAL函数和区间函数
  • 大厂面试提问:Flash Attention 是怎么做到又快又省显存的?
  • java maven
  • C++多款质量游戏及开发建议[OIER建议]