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 任务通知的优缺点
优点:
- 效率更高:使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多
- 使用内存更小:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体
缺点:
- 无法发送数据给中断:ISR没有任务结构体,所以无法给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务。
- 只能点对点通信:任务通知只能是被指定的一个任务接收并处理
- 无法缓存多个数据:任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据(无法像队列那样入队和读出,只能保存一个通知值)。
- 发送不支持阻塞:发送方无法进入阻塞状态等待
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() 的所有定义,可以看到主要的内容可以分为下面几步:
- 判断是否需要保存原来的任务通知值,要的话保存,否则不保存
- 记录目标任务先前的通知状态,然后赋值新的状态
- 根据传入的参数不同,选择不同的通知值更新方式
- 根据先前的通知状态,判断是否需要进行解除目标任务的阻塞状态,是否需要进行任务切换。
有了上面的基础,我们接着来看发送任务通知函数的使用。
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() 的所有定义,可以看到主要的内容可以分为下面几步:
- 判断通知值是否为0,如果为0的话,表示还没有任务通知,将当前任务通知状态赋值为等待通知状态,然后判断是否需要阻塞,看情况阻塞或者直接退出
- 如果通知值不为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() 的所有定义,可以看到主要的内容可以分为下面几步:
- 判断任务有没有通知值,如果没有的话,根据参数ulBitsToClearOnEntry将通知值的特定位清零,更新任务通知状态为等待通知状态,判断是否需要阻塞,切换任务
- 如果有通知值,或者在阻塞时间内等到了任务通知的通知值,则先判断是否需要保存通知值,根据需要选择保存或不保存,如何根据参数ulBitsToClearOnExit将通知值的特定位清零。如果没通知值,且阻塞时间到了也没等到任务通知值,那么跳过通知值清零这一步,
- 最后不管成功接收与否,都将任务通知状态标记为未等待通知状态。
了解了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框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
- 任务处理部分
#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);
}
}
- 按键处理部分
/* 这里我们修改了"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框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
- 任务处理部分
/**
* @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;
}
}
}
- 按键处理部分
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框架之按键扫描新思路 ,这里不做过多介绍。我们直接来看代码:
- 任务处理部分
/**
* @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);
}
}
- 按键处理部分
/* 因为这两个宏在 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 运行结果
以上就是本期的所有内容,不能说是很多,只能说是非常多,创造不易,点个关注再走呗。