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}EnumKeyOvoid 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.mzph.cn/bicheng/57260.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

苹果仍在研发更大尺寸的 iMac | Swift 周报 issue 60

文章目录 前言新闻和社区消息称苹果仍在研发更大尺寸的 iMac 屏幕超过 30 英寸最新&#xff01;苹果大动作Apple Entrepreneur Camp 现已开放申请 提案通过的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组自主整理周报的第六十期&#xff0c;每个模块已初…

我谈傅里叶变换幅值谱的显示

在图像处理和分析中通常需要可视化图像傅里叶变换的幅值谱。通过幅值谱&#xff0c;可以直观地观察频率成分的分布&#xff0c;帮助理解图像的结构和特征。 很多刊物中直接显示傅里叶变换的幅值谱。 FFT fftshift(fft2(double(Img))); FFT_mag mat2gray(log(1abs(FFT)));由…

跨时钟域处理(单bit)_2024年10月21日

慢时钟域同步到快时钟域&#xff1a;打两拍 在快时钟域clk下对慢时钟域信号进行打两拍&#xff08;亚稳态概率很低&#xff09; 脉冲宽度改变&#xff0c;但不影响同步结果 快时钟域同步到慢时钟域&#xff08;两种方法&#xff09; ① 脉冲展宽同步 在快时钟域clk下对快时…

基于卷积神经网络的蔬菜识别系统,resnet50,mobilenet模型【pytorch框架+python源码】

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; 基于卷积神经网络的蔬菜识别系统&#xff0c;resnet50&#xff0c;mobilenet【pytorch框架&#xff0c;python&#xff0c;tkinter】_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于卷积神…

单神经元建模:基于电导的模型[神经元结构、静息电位和等效电路]

文章目录 神经元结构、静息电位和等效电路神经元结构静息电位能斯特方程1. **描述浓度比的非线性关系**&#xff1a;2. **化学势与电势的关系**&#xff1a;3. **对称性**&#xff1a;4. **热力学与平衡**&#xff1a;总结&#xff1a; GHK方程Nernst方程和GHK方程的对比 等效电…

《仓库猎手模拟》风灵月影游戏辅助使用教程

《仓库猎手模拟》是一款休闲独立的模拟经营佳作&#xff0c;让玩家沉浸于经济管理的乐趣中&#xff0c;亲手利用工具探索仓库的每个角落&#xff0c;发掘并鉴定珍稀物品。借助修改器&#xff0c;玩家能更轻松地享受游戏过程&#xff0c;体验寻宝与经营的双重乐趣。 修改器安装&…

【C语言】文件操作(2)(文件缓冲区和随机读取函数)

文章目录 一、文件的随机读取函数1.fseek函数2.ftell函数3.rewind函数 二、文件读取结束的判断1.被错误使用的feof2.判断文件读取结束的方法3.判断文件结束的原因feofferror判断文件读取结束原因示例 三、文件缓冲区 一、文件的随机读取函数 在上一篇的文章中&#xff0c;我们讲…

Android10 recent键相关总结

目录 初始化流程 点击Recent键流程 RecentsActivity 显示流程 RecentsModel 获取数据管理类 RecentsActivity 布局 已处于Recent界面时 点击recent 空白区域 点击返回键 recent组件配置 Android10 Recent 功能由 System UI&#xff0c;Launcher共同实现。 初始化流程 …

如何克隆Git仓库的子目录:稀疏检出

一、环境 Git 2.34.1 二、前言 一般来说&#xff0c;我们在克隆git仓库的时候&#xff0c;都是一整个仓库都克隆出来的。如果假设现在有一个很大的仓库&#xff0c;仓库里有多个子项目&#xff0c;而我们只想克隆其中一个子项目的时候&#xff0c;应该怎么做呢&#xff1f; …

【Java后端】之 ThreadLocal 详解

想象一下&#xff0c;你有一个工具箱&#xff0c;里面放着各种工具。在多人共用这个工具箱的时候&#xff0c;很容易出现混乱&#xff0c;比如有人拿走了你的锤子&#xff0c;或者你找不到合适的螺丝刀。为了避免这种情况&#xff0c;最好的办法就是每个人都有自己独立的工具箱…

初识适配器模式

适配器模式 引入 生活中的例子&#xff1a;当我们使用手机充电时&#xff0c;充电器起到了转换器的作用&#xff0c;它将家用的220伏特电压转换成适合手机充电的5伏特电压。 适配器模式的三种类型 命名原则&#xff1a;适配器的命名应基于资源如何传递给适配器来进行。 类适配…

第14篇:下一代网络与新兴技术

目录 引言 14.1 下一代网络&#xff08;NGN&#xff09;的定义与特点 14.2 IPv6协议的改进与未来应用 14.3 软件定义网络&#xff08;SDN&#xff09; 14.4 网络功能虚拟化&#xff08;NFV&#xff09; 14.5 量子通信网络 14.6 软件定义广域网&#xff08;SD-WAN&#x…

xlsx xlsx-style-vite 实现前端根据element 表格导出excel且定制化样式 背景 列宽等

前言 先看下最终效果图吧&#xff0c;需要的可以参考我的实现方式 这是最终导出的表格文件 类似这种的&#xff0c;特定单元格需要额外标注&#xff0c;表头也有月份然后细分的&#xff0c;表格组件是这样的 注意 别使用xlsx-style 这个库&#xff0c;太多问题了&#xff0c;…

【C语言刷力扣】1768.交替合并字符串

题目&#xff1a; 解题思路&#xff1a; 将 word1 和 word2 元素依次添加至 ans 的后面。 时间复杂度&#xff1a; &#xff0c; n是word1的长度 m是word2的长度 空间复杂度&#xff1a; char* mergeAlternately(char* word1, char* word2) {int len1 strlen(word1);in…

【Linux】top命令查看CPU、内存使用率、解释

1. top 命令 top 是最常用的实时监控工具之一&#xff0c;可以显示 CPU 的总利用率以及各个进程的 CPU 使用情况。在Linux命令行直接输入top即可查看动态原始数据 top 在 top 命令的输出中&#xff0c;最上面的一行会显示 CPU 的使用情况&#xff1a; us&#xff08;User&a…

图片怎么转文字?11种好用的方法!

如何快速将图片的文字提取出来&#xff0c;可以大量节省手打的时间&#xff0c;无论是截图&#xff0c;或者批量提取照片文字&#xff0c;都经常需要这个操作&#xff01; 作为一名社畜&#xff0c;俺也经常用到各种图片转文字工具&#xff0c;今天通过测评12个主流的图片转文…

面对AI算力需求激增,如何守护数据中心机房安全?

随着人工智能&#xff08;AI&#xff09;技术飞速发展&#xff0c;AI算力需求呈现爆发式增长&#xff0c;导致对数据设备电力的需求指数级攀升。这给数据中心带来前所未有的挑战和机遇&#xff0c;从提供稳定的电力供应、优化高密度的部署&#xff0c;到数据安全的隐私保护&…

OpenVLA-首个开源视觉语言动作大模型

官网&#xff1a;https://openvla.github.io/ 现在大模型已经卷到了机器人领域。 在视觉语言模型&#xff08;VLM&#xff09;的基础上&#xff0c; 加入机器人的动作&#xff08;Action) 这一模态&#xff0c; 视觉语言动作大模型&#xff08;VLA&#xff09;是目前大模型应用…

2024新手攻略:项目管理工具+PMP备考经验不容错过!

&#xff08;一&#xff09;热门工具大盘点 禅道是一款专注于软件开发项目管理的工具。它涵盖了项目管理的各个环节&#xff0c;包括需求管理、任务分配、缺陷跟踪等。禅道的优势在于其对软件开发流程的深入理解和支持&#xff0c;能够帮助开发团队更好地管理项目进度和质量。…

如何应对 Android 面试官 -> ANR 如何优化?线上 ANR 如何监控?

前言 本章主要围绕 ANR 如何监控以及优化&#xff1b; 基本概念 ANR(Android Not Responding) 是指应用程序未响应&#xff0c;Android 系统对于一些事件需要在一定的时间范围内完成&#xff0c;如果超过预订时间未能得到有效响应或者响应时间过长&#xff0c;都会造成 ANR。 …