【FreeRTOS 教程 八】直达任务通知
目录
一、FreeRTOS 直达任务通知:
(1)直达任务通知基本介绍:
(2)更新目标通知的值:
(3)性能优势和使用限制:
二、直达任务通知 API:
(1)向任务发送通知:
(2)向任务发送通知(ISR):
(3)唤醒阻塞中任务:
(4)直接向任务发送通知:
(5)向任务发送通知并查询任务的通知值:
(6)向任务发送通知并查询任务的通知值(ISR):
(7)在中断上下文中发送通知给任务:
(8)使任务等待接收通知:
(9)清除指定任务的通知状态:
(10) 清除任务通知值中指定的位:
三、用作轻量级二进制信号量:
(1)优点:
(2)实现:
(3)用法示例:
四、用作轻量级计数信号量:
(1)优点:
(2)实现:
(3)用法示例:
五、用作轻量级事件组:
(1)优点:
(2)实现:
(3)用法示例:
六、用作轻量级邮箱:
(1)限制:
(2)实现:
七、任务间通信:
八、FreeRTOS教程示例代码下载:
一、FreeRTOS 直达任务通知:
- 从 FreeRTOS V8.2.0 开始可用直达任务通知。
- 自 V10.4.0 起支持单任务多条通知。
(1)直达任务通知基本介绍:
可以参考通过 FreeRTOS 通知减少 RAM 占用空间并加速执行。
每个 RTOS 任务都有一个任务通知数组。每个任务,都有“挂起”或“非挂起”的通知状态和一个 32 位通知值。常量 configTASK_NOTIFICATION_ARRAY_ENTRIES 设置任务通知数组中的索引数量。在 FreeRTOS V10.4.0 版本前,任务只有单条任务通知,而无通知数组。
直达任务通知是直接发送到任务的事件,而不是通过中间对象(如队列、事件组或信号量)间接发送至任务的事件。向任务发送“直达任务通知”会将目标任务通知设为“挂起”状态。正如任务可以阻塞中间对象(如等待信号量可用的信号量),任务也可以阻塞任务通知,以等待通知状态变为“挂起”。
(2)更新目标通知的值:
向任务发送“直达任务通知”也可以使用下列任一方法更新目标通知的值(可选):
-
覆盖原值,无论接收任务是否读取被覆盖的值。
-
覆盖原值,但前提是接收任务已读取被覆盖的值。
-
在值中设置一个或多个位。
-
对值进行增量(添加 1)。
调用 xTaskNotifyWait()或xTaskNotifyWaitIndexed() 读取通知值会将该通知的状态清除为“非挂起”。此外,也可以通过调用 xTaskNotifyStateClear()或xTaskNotifyStateClearIndexed() 将通知状态明确设置为“未挂起”。
注意:“数组中的每条通知均独立运行 —— 任务一次只能阻塞数组中的一个通知,并且不会被发送到任何其他数组索引的通知解除阻塞。
默认情况下,RTOS 任务通知功能处于启用状态,并且可以通过将 configUSE_TASK_NOTIFICATIONS 在 FreeRTOSConfig.h 中设置为 0 从构建中排除(每个任务每个数组索引节省 8 个字节)。
注意事项:FreeRTOS 流和消息缓冲区在数值索引为 0 时使用任务通知。如需在调用流或消息缓冲区 API 函数时保持任务通知的状态,请使用数组索引大于 0 的任务通知。
(3)性能优势和使用限制:
任务通知具有高度灵活性,使得它们可以在必须要创建单独队列、二进制信号量、计数信号量或事件组的情况下进行使用。与通过诸如二进制信号量等中间对象来解除任务阻塞状态相比,通过直接通知解除 RTOS 任务阻塞状态的速度快 45%,使用的 RAM 也更少,不过这些性能优势也有一些意料之内的使用限制:
- RTOS 任务通知仅可在只有一个任务可以接收事件时使用。不过,这个条件在大多数真实世界情况下是满足的。比如,中断解除了一个任务的阻塞状态,该任务将处理由中断接收的数据。
- 仅可在使用 RTOS 任务通知代替队列的情况下:当某个接收任务可在阻塞状态下等待通知(因而不花费任何 CPU 时间)时,发送任务不能在阻塞状态下等待发送完成(在发送不能立刻完成的情况下)。
二、直达任务通知 API:
(1)向任务发送通知:
函数原型:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
BaseType_t xTaskNotifyGiveIndexed( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify );
- 每项任务都有一个“任务通知”数组(或简称“通知”),每条通知都包含一个状态和一个 32 位的值。直达任务通知是直接发送给任务的事件,可以解除接收任务的阻塞状态,还可以通过多种不同的方式更新接收任务的某个通知值。例如,通知可覆盖接收任务的某个通知值,或仅设置接收任务的某个通知值中的一个或多个位。
- xTaskNotifyGive() 宏可在将任务通知用作速度更快的轻量级二进制或计数信号量的替代方案时使用。FreeRTOS 信号量通过 xSemaphoreGive() API 函数释放,而 xTaskNotifyGive() 与其等效,使用接收 RTOS 任务的某个通知值代替信号量。
- xTaskNotifyGive() 与 xTaskNotifyGiveIndexed() 是等效宏,唯一区别在于 xTaskNotifyGiveIndexed() 可以操作数组中的任何任务通知,而 xTaskNotifyGive() 总是操作数组中索引为 0 的任务通知。
- 当任务通知值用作二进制或计数信号量的等效物时,接收通知的任务应该使用 ulTaskNotifyTake() API 函数来等待通知,而不是使用 xTaskNotifyWait() API 函数。
- 注意: 数组中的所有通知均独立操作,即一项任务在同一时间只能在数组中的一条通知上处于阻塞状态,并且不会被发送到其他数组索引的通知解除阻塞状态。
- xTaskNotifyGive() 不能在中断服务程序中调用。请使用 vTaskNotifyGiveFromISR() 代替。
- 必须在 FreeRTOSConfig.h 中将 configUSE_TASK_NOTIFICATIONS 设置为 1(或保留为未定义状态),才可使用这些宏。常量 configTASK_NOTIFICATION_ARRAY_ENTRIES 决定了每项任务的任务通知数组中的索引数。
- 向后兼容性信息:
- 在 FreeRTOS V10.4.0 之前,每项任务只有一个“通知值”,所有任务通知 API 函数都只能操作这一个值。用通知值数组替代单个通知值需要一组新的 API 函数,以处理数组中的特定通知。
- xTaskNotifyGive() 是原始 API 函数,为保持向后兼容,始终操作数组中索引为 0 的通知值。调用 xTaskNotifyGive() 等同于调用 xTaskNotifyGiveIndexed(),其中 uxIndexToNotify 参数设置为 0。
参数/返回值 | 说明 |
---|---|
xTaskToNotify | 接收通知的 RTOS 任务的句柄,通知值会递增。可通过以下方法获取任务句柄:<br>- 使用 xTaskCreate() 创建任务,并通过 pxCreatedTask 参数获取句柄;<br>- 使用 xTaskCreateStatic() 创建任务,并存储返回值作为句柄;<br>- 调用 xTaskGetHandle() ,通过任务名称获取句柄。当前正在执行的 RTOS 任务的句柄由 xTaskGetCurrentTaskHandle() API 函数返回。 |
uxIndexToNotify | 目标任务的通知值数组中要向其发送通知的索引。uxIndexToNotify 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。xTaskNotifyGive() 没有此参数,并且总是将通知发送到索引 0。 |
返回值 | xTaskNotifyGiveIndexed() 是一个宏,调用 xTaskNotifyIndexed() ,并将 eAction 参数设置为 eIncrement ,因此所有调用都返回 pdPASS 。 |
用法示例:
/* main() 创建的两个任务的原型。*/
static void prvTask1( void *pvParameters );
static void prvTask2( void *pvParameters );
/* main() 创建的任务的句柄。*/
static TaskHandle_t xTask1 = NULL, xTask2 = NULL;
/* 创建两个任务,它们相互发送通知,然后启动 RTOS 调度器。*/
void main( void )
{
xTaskCreate( prvTask1, "Task1", 200, NULL, tskIDLE_PRIORITY, &xTask1 ); // 创建任务1
xTaskCreate( prvTask2, "Task2", 200, NULL, tskIDLE_PRIORITY, &xTask2 ); // 创建任务2
vTaskStartScheduler(); // 启动 RTOS 调度器
}
/*-----------------------------------------------------------*/
/* prvTask1() 使用带 'Indexed' 的 API 版本。*/
static void prvTask1( void *pvParameters )
{
for( ;; ) // 无限循环
{
/* 向 prvTask2() 发送通知,使其从阻塞状态唤醒。*/
xTaskNotifyGiveIndexed( xTask2, 0 );
/* 阻塞等待 prvTask2() 通知此任务。*/
ulTaskNotifyTakeIndexed( 0, pdTRUE, portMAX_DELAY );
}
}
/*-----------------------------------------------------------*/
/* prvTask2() 使用原始版本的 API(不带 'Indexed')。*/
static void prvTask2( void *pvParameters )
{
for( ;; ) // 无限循环
{
/* 阻塞等待 prvTask1() 通知此任务。*/
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
/* 向 prvTask1() 发送通知,使其从阻塞状态唤醒。*/
xTaskNotifyGive( xTask1 );
}
}
(2)向任务发送通知(ISR):
函数原型:
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken );
void vTaskNotifyGiveIndexedFromISR( TaskHandle_t xTaskHandle,
UBaseType_t uxIndexToNotify,
BaseType_t *pxHigherPriorityTaskWoken );
- 可在中断服务程序 (ISR) 中使用的 xTaskNotifyGive() 和 xTaskNotifyGiveIndexed() 版本 。
参数 | 说明 |
---|---|
xTaskToNotify | 接收通知的 RTOS 任务的句柄,通知值会递增。可通过以下方法获取任务句柄:使用 |
uxIndexToNotify | 目标任务的通知值数组中要向其发送通知的索引。uxIndexToNotify 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。xTaskNotifyGiveFromISR() 没有此参数,并且总是将通知发送到索引 0。 |
*pxHigherPriorityTaskWoken | 必须初始化为 0。如果发送通知导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 vTaskNotifyGiveFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 vTaskNotifyGiveFromISR() 将此值设置为 pdTRUE ,则应在中断退出前请求上下文切换。pxHigherPriorityTaskWoken 是可选参数,可设置为 NULL 。 |
用法示例:
/* 这是一个通用外设驱动程序中的发送函数示例。RTOS 任务调用发送函数,
然后在阻塞状态(因此不使用 CPU 时间)等待,直到收到传输完成的通知。
传输由 DMA 执行,DMA 结束中断用于通知任务。 */
static TaskHandle_t xTaskToNotify = NULL; // 用于存储将要通知的任务句柄
/* 外设驱动程序的发送函数。 */
void StartTransmission( uint8_t *pcData, size_t xDataLength )
{
/* 在这一点上,xTaskToNotify 应该为 NULL,因为没有传输正在进行。
如果必要,可以使用互斥锁来保护对外设的访问。 */
configASSERT( xTaskToNotify == NULL );
/* 存储调用任务的句柄。 */
xTaskToNotify = xTaskGetCurrentTaskHandle();
/* 开始传输 - 传输完成时会生成一个中断。 */
vStartTransmit( pcData, xDataLength ); // 假设这是启动传输的函数
}
/*-----------------------------------------------------------*/
/* 发送结束中断。 */
void vTransmitEndISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 在这一点上,xTaskToNotify 不应该为 NULL,因为有一个传输正在进行。 */
configASSERT( xTaskToNotify != NULL );
/* 通知任务传输已完成。 */
vTaskNotifyGiveIndexedFromISR( xTaskToNotify, 0, &xHigherPriorityTaskWoken );
/* 没有传输正在进行,所以没有任务需要通知。 */
xTaskToNotify = NULL;
/* 如果 xHigherPriorityTaskWoken 现在设置为 pdTRUE,则应执行上下文切换以确保中断直接返回到最高优先级任务。
用于此目的的宏取决于使用的端口,可能称为 portEND_SWITCHING_ISR()。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 启动传输,然后进入阻塞状态(因此不消耗任何 CPU 时间)以等待其完成的任务。 */
void vAFunctionCalledFromATask( uint8_t ucDataToTransmit, size_t xDataLength )
{
uint32_t ulNotificationValue;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
/* 通过调用上面的函数来启动传输。 */
StartTransmission( ucDataToTransmit, xDataLength );
/* 等待传输完成。 */
ulNotificationValue = ulTaskNotifyTakeIndexed( 0, pdFALSE, xMaxBlockTime );
if( ulNotificationValue == 1 )
{
/* 传输按预期结束。 */
}
else
{
/* 调用 ulTaskNotifyTake() 超时。 */
}
}
(3)唤醒阻塞中任务:
函数原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn,
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
- 每个任务都有一组“任务通知”(或仅“通知”),每个通知都包含状态和一个 32 位值。直达任务通知直接发送给任务的事件,可以取消接收任务的阻塞状态,还可以选择通过多种方式更新接收任务的某个通知值。例如,通知可覆盖接收任务的通知值中的一个,或仅设置接收任务的通知值中的一个或多个比特位。
ulTaskNotifyTake()
是一个宏,用于将任务通知作为一种速度更快、重量更轻的二进制或计数信号量替代品。FreeRTOS 信号量是使用xSemaphoreTake()
API 函数提取的,是使用通知值代替信号量的等效宏。ulTaskNotifyTake()
和ulTaskNotifyTakeIndexed()
是等效的宏——唯一的区别是ulTaskNotifyTakeIndexed()
可以在数组内的任何任务通知上运行,而ulTaskNotifyTake()
始终在数组索引 0 处的任务通知上运行。- 当任务使用通知值作为二进制或计数信号量时,其他任务和中断应使用
xTaskNotifyGive()
宏或xTaskNotify()
函数将数据发给任务,其中函数的eAction
参数设置为eIncrement
(这两者是等效的)。 ulTaskNotifyTake()
可以在退出时清除任务的通知值为 0,在这种情况下,通知值起到二进制信号量的作用;或在退出时递减任务的通知值,在这种情况下,通知值更像是计数信号量。- RTOS 任务可以使用
ulTaskNotifyTake()
[可选] 进入阻塞状态以等待任务通知指。任务处于“阻塞”状态时不会占用任何 CPU 时间。 - 注意: 数组中的每条通知均独立运行——任务一次只能阻塞数组中的一个通知,并且不会被发送到任何其他数组索引的通知解除阻塞。
- 然而当通知被挂起时,
xTaskNotifyWait()
将返回,ulTaskNotifyTake()
将在任务的通知值不为零时返回,并在返回之前递减任务通知值。 - 必须在 FreeRTOSConfig.h 中将
configUSE_TASK_NOTIFICATIONS
设置为 1(或保留为未定义),这些宏才能可用。常量configTASK_NOTIFICATION_ARRAY_ENTRIES
设置每个任务的任务通知数组中的索引数。
参数/返回值 | 说明 |
---|---|
uxIndexToWaitOn | 调用任务的通知值数组中的索引,调用任务将在该索引上等待非零通知。uxIndexToWaitOn 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。xTaskNotifyTake() 没有此参数,总是在索引 0 处等待通知。 |
xClearCountOnExit | 如果收到 RTOS 任务通知,且 xClearCountOnExit 设置为 pdFALSE ,那么 RTOS 任务的通知值将在 ulTaskNotifyTake() 退出前递减。这相当于成功调用 xSemaphoreTake() 后,计数信号量的值被递减。如果收到 RTOS 任务通知且 xClearCountOnExit 设置为 pdTRUE ,则 RTOS 任务的通知值将在 ulTaskNotifyTake() 退出前重置为 0。这等同于在成功调用 xSemaphoreTake() 后,将二进制信号量的值保留为 0(或空,或不可用)。 |
xTicksToWait | 表示如果调用 ulTaskNotifyTake() 时尚未收到通知,在阻塞状态下等待收到通知的最长时间。处于阻塞状态的 RTOS 任务不会消耗任何 CPU 时间。时间以 RTOS 滴答周期为单位。pdMS_TO_TICKS() 宏可用于将以毫秒为单位的时间转换为以滴答为单位的时间。 |
返回值 | 被递减或清除之前的任务通知值的值。 |
用法示例:
/* 一个中断处理程序。中断处理程序不执行任何处理,
而是解除一个高优先级任务的阻塞,在该任务中处理产生中断的事件。
如果任务的优先级足够高,那么中断将直接返回到任务(因此它将中断一个任务但返回到一个不同的任务),
这样处理就会在时间上连续发生——就像所有的处理都是在中断处理程序本身中完成的一样。*/
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* 清除中断。*/
prvClearInterruptSource();
/* xHigherPriorityTaskWoken 必须初始化为 pdFALSE。如果调用
vTaskNotifyGiveFromISR() 解除处理任务的阻塞,并且处理任务的优先级高于当前运行任务的优先级,
那么 xHigherPriorityTaskWoken 将自动设置为 pdTRUE。*/
xHigherPriorityTaskWoken = pdFALSE;
/* 解除处理任务的阻塞,以便任务可以执行由中断引起的任何处理。
xHandlingTask 是任务的句柄,它是在任务创建时获得的。*/
vTaskNotifyGiveIndexedFromISR( xHandlingTask, 0, &xHigherPriorityTaskWoken );
/* 如果 xHigherPriorityTaskWoken 现在设置为 pdTRUE,则强制上下文切换。
用于此目的的宏取决于端口,可能称为 portEND_SWITCHING_ISR。*/
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 一个阻塞等待通知的任务,该通知表明外设需要服务,
每次被通知时处理外设中所有待处理的事件。*/
void vHandlingTask( void *pvParameters )
{
BaseType_t xEvent;
for( ;; )
{
/* 无限期地(没有超时,因此不需要检查函数的返回值)阻塞等待通知。
在这里,RTOS 任务通知被用作二进制信号量,因此通知值在退出时被清除为零。
注意!实际应用中不应无限期阻塞,而应偶尔超时以处理可能阻止中断发送更多通知的错误条件。*/
ulTaskNotifyTakeIndexed( 0, /* 使用第 0 个通知 */
pdTRUE, /* 在退出前清除通知值。 */
portMAX_DELAY ); /* 无限期阻塞。 */
/* RTOS 任务通知被用作二进制(而不是计数)信号量,
因此只有在外设中所有待处理事件都已处理完毕后才返回等待进一步的通知。*/
do
{
xEvent = xQueryPeripheral();
if( xEvent != NO_MORE_EVENTS )
{
vProcessPeripheralEvent( xEvent );
}
} while( xEvent != NO_MORE_EVENTS );
}
}
(4)直接向任务发送通知:
函数原型:
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyIndexed( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction );
- 如果使用 RTOS 任务通知来实现二进制或计数信号量行为, 则应使用更简单的 xTaskNotifyGive() API 函数,而不是 xTaskNotify()。
- 每项任务都有一个“任务通知”数组(或简称“通知”),每条通知都包含一个状态和 一个 32 位值。直达任务通知是直接发送给任务的事件, 可以解除接收任务的阻塞状态,还可以通过多种不同的方式 更新接收任务的某个通知值。例如,通知可覆盖接收任务的某个通知值, 或仅设置接收任务某个通知值中的一个或多个位。
- xTaskNotify() 用于直接向 RTOS 任务发送事件,并且可能解除该任务的阻塞状态, 同时还可以按照以下任一方式更新接收任务的某个通知值:
- 将一个 32 位数字写入通知值
- 将通知值加一(递增)
- 设置通知值中的一个或多个位
- 保持通知值不变
- xTaskNotify() 和 xTaskNotifyIndexed() 是等效函数,唯一区别在于 xTaskNotifyIndexed() 可以操作数组中的任何任务通知,而 xTaskNotify() 总是操作数组中索引为 0 的任务通知。
- 不得从中断服务程序 (ISR) 调用此函数。 请使用 xTaskNotifyFromISR() 代替。
- 必须在 FreeRTOSConfig.h 中将 configUSE_TASK_NOTIFICATIONS 设置为 1 (或保留为未定义状态),才可使用这些函数。常量 configTASK_NOTIFICATION_ARRAY_ENTRIES 决定了每项任务的任务通知数组中的索引数。
向后兼容性信息:
- 在 FreeRTOS V10.4.0 之前,每项任务只有一个“通知值”, 所有任务通知 API 函数都只能操作这一个值。用通知值数组组 替代单个通知值需要一组新的 API 函数,以处理数组中的特定通知。 xTaskNotify() 是原始 API 函数, 为保持向后兼容, 始终操作数组中索引为 0 的通知值。调用 xTaskNotify() 相当于调用 xTaskNotifyIndexed(), 其 uxIndexToNotify 参数设置为 0。
参数/枚举值 | 描述 |
---|---|
xTaskToNotify | 接收通知的 RTOS 任务(即目标任务)的句柄。可通过以下方法获取任务句柄:使用 xTaskCreate() 创建任务,并通过 pxCreatedTask 参数获取句柄;使用 xTaskCreateStatic() 创建任务,并存储返回值作为句柄;调用 xTaskGetHandle() ,通过任务名称获取句柄。当前正在执行的 RTOS 任务的句柄由 xTaskGetCurrentTaskHandle() API 函数返回。 |
uxIndexToNotify | 目标任务的通知值数组中要向其发送通知的索引。uxIndexToNotify 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。xTaskNotify() 没有此参数,并且总是将通知发送到索引 0。 |
ulValue | 用于更新目标任务的通知值。请参考下文 eAction 参数的说明。 |
eAction | 一种枚举类型,可以取下列任一值,以执行相关操作: |
eNoAction | 目标任务接收事件,但其通知值不会更新。在这种情况下,不会使用 ulValue 。 |
eSetBits | 目标任务的通知值将与 ulValue 进行按位“或”操作。例如,如果 ulValue 设置为 0x01,则目标任务通知值中的第 0 位将被设置。同样,如果 ulValue 设置为 0x04,则目标任务通知值中的第 2 位将被设置。通过这种方式,RTOS 任务通知机制可以作为事件组的轻量级替代方案。 |
eIncrement | 目标任务的通知值将增加 1,这样调用 xTaskNotify() 相当于调用 xTaskNotifyGive() 。在这种情况下,不会使用 ulValue 。 |
eSetValueWithOverwrite | 目标任务的通知值无条件设置为 ulValue 。通过这种方式,RTOS 任务通知机制可以作为 xQueueOverwrite() 的轻量级替代方案。 |
eSetValueWithoutOverwrite | 如果目标任务当前没有挂起的通知,则其通知值将设置为 ulValue 。如果目标任务已有挂起的通知,则其通知值不会更新,以免之前的值在使用前被覆盖。在这种情况下,调用 xTaskNotify() 会失败,返回 pdFALSE 。通过这种方式,RTOS 任务通知机制可以在长度为 1 的队列上作为 xQueueSend() 的轻量级替代方案。 |
返回值 | 除了 eAction 设置为 eSetValueWithoutOverwrite 且目标任务的通知值无法更新(因为目标任务已有挂起的通知)时,其他情况下均返回 pdPASS 。 |
用法示例:
/* 在引用 xTask1Handle 的任务的第 0 个通知值中设置第 8 位。 */
xTaskNotifyIndexed( xTask1Handle, 0, ( 1UL << 8UL ), eSetBits );
/* 向引用 xTask2Handle 的任务发送通知,可能会将任务从阻塞状态移除,
但不更新任务的通知值。 */
xTaskNotify( xTask2Handle, 0, eNoAction );
/* 将引用 xTask3Handle 的任务的通知值设置为 0x50,
即使任务尚未读取其先前的通知值。 */
xTaskNotify( xTask3Handle, 0x50, eSetValueWithOverwrite );
/* 将引用 xTask4Handle 的任务的通知值设置为 0xfff,
但仅当这样做不会在任务获取该值之前(通过调用 xTaskNotifyWait() 或 ulTaskNotifyTake())覆盖任务的现有通知值时。 */
if( xTaskNotify( xTask4Handle, 0xfff, eSetValueWithoutOverwrite ) == pdPASS )
{
/* 任务的通知值已更新。 */
}
else
{
/* 任务的通知值未更新。 */
}
(5)向任务发送通知并查询任务的通知值:
函数原型:
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue );
BaseType_t xTaskNotifyAndQueryIndexed( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue );
- xTaskNotifyAndQueryIndexed() 执行的操作与 xTaskNotifyIndexed() 相同,另外还可通过额外的 pulPreviousNotifyValue 参数返回目标任务之前的通知值(函数被调用时的通知值,而不是函数返回时的通知值)。
- xTaskNotifyAndQuery() 执行的操作与 xTaskNotify() 相同,另外还可通过额外的 pulPreviousNotifyValue 参数返回目标任务之前的通知值(函数被调用时的通知值,而不是函数返回时的通知值)。
- 不得从中断服务程序 (ISR) 调用此函数。请使用 xTaskNotifyAndQueryFromISR() 代替。
参数/返回值 | 描述 |
---|---|
xTaskToNotify | 接收通知的 RTOS 任务(即目标任务)的句柄。可通过以下方法获取任务句柄:使用 xTaskCreate() 创建任务,并通过 pxCreatedTask 参数获取句柄;使用 xTaskCreateStatic() 创建任务,并存储返回值作为句柄;调用 xTaskGetHandle() ,通过任务名称获取句柄。当前正在执行的 RTOS 任务的句柄由 xTaskGetCurrentTaskHandle() API 函数返回。 |
uxIndexToNotify | 目标任务的通知值数组中要向其发送通知的索引。uxIndexToNotify 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。 |
ulValue | 用于更新目标任务的通知值。请参考下文 eAction 参数的说明。 |
eAction | 一种枚举类型,可以取下列任一值,以执行相关操作: |
pulPreviousNotifyValue | 可用于在 xTaskNotifyAndQuery() 修改任何位之前传出目标任务的通知值。pulPreviousNotifyValue 是可选参数,如果不需要,可设置为 NULL。如果不使用 pulPreviousNotifyValue ,可以考虑使用 xTaskNotify() 替代 xTaskNotifyAndQuery() 。 |
eNoAction | 目标任务接收事件,但其通知值不会更新。在这种情况下,不会使用 ulValue 。 |
eSetBits | 目标任务的通知值将与 ulValue 进行按位“或”操作。例如,如果 ulValue 设置为 0x01,则目标任务通知值中的第 0 位将被设置。同样,如果 ulValue 设置为 0x04,则目标任务通知值中的第 2 位将被设置。通过这种方式,RTOS 任务通知机制可以作为事件组的轻量级替代方案。 |
eIncrement | 目标任务的通知值将增加 1,这样调用 xTaskNotify() 相当于调用 xTaskNotifyGive() 。在这种情况下,不会使用 ulValue 。 |
eSetValueWithOverwrite | 目标任务的通知值无条件设置为 ulValue 。通过这种方式,RTOS 任务通知机制可以作为 xQueueOverwrite() 的轻量级替代方案。 |
eSetValueWithoutOverwrite | 如果目标任务当前没有挂起的通知,则其通知值将设置为 ulValue 。如果目标任务已有挂起的通知,则其通知值不会更新,以免之前的值在使用前被覆盖。在这种情况下,调用 xTaskNotify() 会失败,返回 pdFALSE 。通过这种方式,RTOS 任务通知机制可以在长度为 1 的队列上作为 xQueueSend() 的轻量级替代方案。 |
返回值 | 除了 eAction 设置为 eSetValueWithoutOverwrite 且目标任务的通知值无法更新(因为目标任务已有挂起的通知)时,其他情况下均返回 pdPASS 。 |
用法示例:
uint32_t ulPreviousValue;
/* 在引用 xTask1Handle 的任务的第 0 个通知值中设置第 8 位。
将任务的先前第 0 个通知值(在设置第 8 位之前)存储在 ulPreviousValue 中。 */
xTaskNotifyAndQueryIndexed( xTask1Handle,
0,
( 1UL << 8UL ),
eSetBits,
&ulPreviousValue );
/* 向引用 xTask2Handle 的任务发送通知,
可能会将任务从阻塞状态移除,但不更新任务的通知值。
将任务的通知值存储在 ulPreviousValue 中。 */
xTaskNotifyAndQuery( xTask2Handle, 0, eNoAction, &ulPreviousValue );
/* 将引用 xTask3Handle 的任务的通知值设置为 0x50,
即使任务尚未读取其先前的通知值。
任务的先前通知值没有兴趣,因此最后一个参数设置为 NULL。 */
xTaskNotifyAndQuery( xTask3Handle, 0x50, eSetValueWithOverwrite, NULL );
/* 将引用 xTask4Handle 的任务的通知值设置为 0xfff,
但仅当这样做不会在任务获取该值之前(通过调用 xTaskNotifyWait() 或 ulTaskNotifyTake())覆盖任务的现有通知值时。
任务的先前通知值保存在 ulPreviousValue 中。 */
if( xTaskNotifyAndQuery( xTask4Handle,
0xfff,
eSetValueWithoutOverwrite,
&ulPreviousValue ) == pdPASS )
{
/* 任务的通知值已更新。 */
}
else
{
/* 任务的通知值未更新。 */
}
(6)向任务发送通知并查询任务的通知值(ISR):
函数原型:
BaseType_t xTaskNotifyAndQueryFromISR(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue,
BaseType_t *pxHigherPriorityTaskWoken );
BaseType_t xTaskNotifyAndQueryIndexedFromISR(
TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue,
BaseType_t *pxHigherPriorityTaskWoken );
- xTaskNotifyAndQueryIndexedFromISR()执行的操作与 xTaskNotifyIndexedFromISR()相同,另外还可通过额外的 pulPreviousNotifyValue 参数返回目标任务之前的通知值(函数被调用时的通知值,而不是函数返回时的通知值)。
- xTaskNotifyAndQueryFromISR()执行的操作与 xTaskNotifyFromISR()相同,另外还可通过额外的 pulPreviousNotifyValue 参数返回目标任务之前的通知值(函数被调用时的通知值,而不是函数返回时的通知值)。
参数/返回值 | 描述 |
---|---|
xTaskToNotify | 接收通知的 RTOS 任务(即目标任务)的句柄。可通过以下方法获取任务句柄:使用 xTaskCreate() 创建任务,并通过 pxCreatedTask 参数获取句柄;使用 xTaskCreateStatic() 创建任务,并存储返回值作为句柄;调用 xTaskGetHandle() ,并通过任务名称获取句柄。当前正在执行的 RTOS 任务的句柄由 xTaskGetCurrentTaskHandle() API 函数返回。 |
uxIndexToNotify | 目标任务的通知值数组中要向其发送通知的索引。uxIndexToNotify 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。 |
ulValue | 用于更新目标任务的通知值。请参考下文 eAction 参数的说明。 |
eAction | 一种枚举类型,可以取下列任一值,以执行相关操作: |
pulPreviousNotifyValue | 可用于在 xTaskNotifyAndQueryFromISR() 修改任何位之前传出目标任务的通知值。pulPreviousNotifyValue 是可选参数,如果不需要,可设置为 NULL。如果不使用 pulPreviousNotifyValue ,可以考虑使用 xTaskNotify() 替代 xTaskNotifyAndQueryFromISR() 。 |
pxHigherPriorityTaskWoken | *pxHigherPriorityTaskWoken 必须初始化为 pdFALSE (0)。如果发送通知导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xTaskNotifyAndQueryFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 xTaskNotifyAndQueryFromISR() 将此值设置为 pdTRUE ,则应在退出中断前请求上下文切换。pxHigherPriorityTaskWoken 是可选参数,可设置为 NULL。 |
eNoAction | 目标任务接收事件,但其通知值不会更新。在这种情况下,不会使用 ulValue 。 |
eSetBits | 目标任务的通知值将与 ulValue 进行按位“或”操作。例如,如果 ulValue 设置为 0x01,则目标任务通知值中的第 0 位将被设置。同样,如果 ulValue 设置为 0x04,则目标任务通知值中的第 2 位将被设置。通过这种方式,RTOS 任务通知机制可以作为事件组的轻量级替代方案。 |
eIncrement | 目标任务的通知值将增加 1,这样调用 xTaskNotify() 相当于调用 xTaskNotifyGive() 。在这种情况下,不会使用 ulValue 。 |
eSetValueWithOverwrite | 目标任务的通知值无条件设置为 ulValue 。通过这种方式,RTOS 任务通知机制可以作为 xQueueOverwrite() 的轻量级替代方案。 |
eSetValueWithoutOverwrite | 如果目标任务当前没有挂起的通知,则其通知值将设置为 ulValue 。如果目标任务已有挂起的通知,则其通知值不会更新,以免之前的值在使用前被覆盖。在这种情况下,调用 xTaskNotify() 会失败,返回 pdFALSE 。通过这种方式,RTOS 任务通知机制可以在长度为 1 的队列上作为 xQueueSend() 的轻量级替代方案。 |
返回值 | 除了 eAction 设置为 eSetValueWithoutOverwrite 且目标任务的通知值无法更新(因为目标任务已有挂起的通知)时,其他情况下均返回 pdPASS 。 |
用法示例:
void vAnISR( void )
{
/* 必须初始化为 pdFALSE! */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t ulPreviousValue;
/* 在引用 xTask1Handle 的任务的第 0 个通知值中设置第 8 位。
将任务的先前第 0 个通知值(在设置第 8 位之前)存储在 ulPreviousValue 中。 */
xTaskNotifyAndQueryIndexedFromISR( xTask1Handle,
0,
( 1UL << 8UL ),
eSetBits,
&ulPreviousValue,
&xHigherPriorityTaskWoken );
/* 任务的先前通知值保存在 ulPreviousValue 中。 */
/* 如果引用 xTask1Handle 的任务处于阻塞状态,等待通知,
那么它现在将从阻塞状态转移到就绪状态。如果其优先级高于当前执行任务(中断的这个任务)
的优先级,那么 xHigherPriorityTaskWoken 将被设置为 pdTRUE,将变量传递到 portYIELD_FROM_ISR() 调用中将导致中断直接返回到未阻塞的任务。
如果 xHigherPriorityTaskWoken 仍然是 pdFALSE,那么将其传递到 portYIELD_FROM_ISR() 将没有效果。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
(7)在中断上下文中发送通知给任务:
函数原型:
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
BaseType_t xTaskNotifyIndexedFromISR( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
- 可在中断服务程序 (ISR) 中使用的 xTaskNotify() 和 xTaskNotifyIndexed() 版本 。
- 参数/返回值同上。
用法示例:
/* 中断处理程序本身不执行任何处理。相反,它解锁一个高优先级任务,
在该任务中处理产生中断的事件。如果任务的优先级足够高,则中断将直接返回到任务
(因此它将中断一个任务但返回到不同的任务),这样处理将在时间上连续发生 -
就像所有处理都是在中断处理程序本身中完成的一样。
通过 RTOS 任务通知将中断外设的状态发送到任务。 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulStatusRegister;
/* 读取中断状态寄存器,该寄存器有一个位用于每个中断源
(例如,可能是一个 Rx 位,一个 Tx 位,一个缓冲区溢出位等)。 */
ulStatusRegister = ulReadPeripheralInterruptStatus();
/* 清除中断。 */
vClearPeripheralInterruptStatus( ulStatusRegister );
/* xHigherPriorityTaskWoken 必须初始化为 pdFALSE。如果调用
xTaskNotifyFromISR() 解锁处理任务,并且处理任务的优先级高于当前运行任务的优先级,
那么 xHigherPriorityTaskWoken 将自动设置为 pdTRUE。 */
xHigherPriorityTaskWoken = pdFALSE;
/* 解锁处理任务,以便任务可以执行由中断引起的任何必要处理。
xHandlingTask 是任务的句柄,它是在任务创建时获得的。
处理任务的第 0 个通知值与中断状态进行按位或操作 - 确保已经设置的位不会被覆盖。 */
xTaskNotifyIndexedFromISR( xHandlingTask,
0,
ulStatusRegister,
eSetBits,
&xHigherPriorityTaskWoken );
/* 如果 xHigherPriorityTaskWoken 现在设置为 pdTRUE,则强制上下文切换。
用于执行此操作的宏取决于端口,可能称为 portEND_SWITCHING_ISR。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* ----------------------------------------------------------- */
/* 一个任务阻塞等待通知,通知外设需要服务,
每次被通知时处理外设中所有待处理的事件。 */
void vHandlingTask( void *pvParameters )
{
uint32_t ulInterruptStatus;
for( ;; )
{
/* 无限期地阻塞(没有超时,因此不需要检查函数的返回值)
等待通知。注意!实际应用不应无限期地阻塞,而应偶尔超时以处理可能阻止中断发送
更多通知的错误条件。 */
xTaskNotifyWaitIndexed( 0, /* 等待第 0 个通知 */
0x00, /* 进入时不清除任何位。 */
ULONG_MAX, /* 退出时清除所有位。 */
&ulInterruptStatus, /* 接收通知值。 */
portMAX_DELAY ); /* 无限期阻塞。 */
/* 处理接收到的通知值中设置的任何位。这假设外设为 Rx 中断设置位 1,
为 Tx 中断设置位 2,为缓冲区溢出中断设置位 3。 */
if( ( ulInterruptStatus & 0x01 ) != 0x00 )
{
prvProcessRxInterrupt();
}
if( ( ulInterruptStatus & 0x02 ) != 0x00 )
{
prvProcessTxInterrupt();
}
if( ( ulInterruptStatus & 0x04 ) != 0x00 )
{
prvClearBufferOverrun();
}
}
}
(8)使任务等待接收通知:
函数原型:
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWaitOn,
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
- 如果使用 RTOS 任务通知来实现二进制或计数信号量行为,则应使用更简单的 ulTaskNotifyTake() API 函数,而不是 xTaskNotifyWait()。
- xTaskNotifyWait() 用于使调用任务等待接收通知,可以为其设置一个可选的超时时间。如果接收 RTOS 任务在等待通知时已经处于阻塞状态,则在等待的通知到达时,接收 RTOS 任务将解除阻塞状态,通知也将清除。
参数/返回值 | 描述 |
---|---|
uxIndexToWaitOn | 调用任务的通知值数组中用于等待接收通知的索引。uxIndexToWaitOn 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。xTaskNotifyWait() 没有此参数,总是在索引 0 的位置等待通知。 |
ulBitsToClearOnEntry | 调用 xTaskNotifyWait() 时,如果通知已挂起,则在进入 xTaskNotifyWait() 函数(即任务等待新通知之前)时,ulBitsToClearOnEntry 中设置的任何位都会在调用 RTOS 任务的通知值中被清除。例如,如果 ulBitsToClearOnEntry 设为 0x01,则任务通知值中的第 0 位将在进入函数时被清除。如果 ulBitsToClearOnEntry 设为 0xffffffff(ULONG_MAX )则将清除任务通知值中的所有位,相当于将值清零。 |
ulBitsToClearOnExit | 如果在调用 xTaskNotifyWait() 函数时收到了通知,则在 xTaskNotifyWait() 函数退出之前,ulBitsToClearOnExit 中设置的任何位都会在调用 RTOS 任务的通知值中被清除。RTOS 任务的通知值保存到 *pulNotificationValue 之后,这些位即被清除。例如,如果 ulBitsToClearOnExit 设为 0x03,则在函数退出之前,任务通知值中的第 0 位和第 1 位将被清除。如果 ulBitsToClearOnExit 设为 0xffffffff(ULONG_MAX ),则将清除任务通知值中的所有位,相当于将值清零。 |
pulNotificationValue | 用于传出 RTOS 任务的通知值。复制到 *pulNotificationValue 的值是 RTOS 任务的通知值,该值是在应用 ulBitsToClearOnExit 设置清除任何位之前的值。如果无需通知值,可以将 pulNotificationValue 设置为 NULL。 |
xTicksToWait | 调用 xTaskNotifyWait() 时没有挂起通知的情况下,在阻塞状态下等待接收通知的最长时间。RTOS 任务在阻塞状态下不会消耗 CPU 时间。时间以 RTOS 滴答周期为单位。可以使用 pdMS_TO_TICKS() 宏将以毫秒为单位的时间转换为以滴答为单位的时间。 |
返回值 | 如果收到了通知,或者在调用 xTaskNotifyWait() 时通知已挂起,则返回 pdTRUE 。如果调用 xTaskNotifyWait() 超时且在超时前没有收到通知,则返回 pdFALSE 。 |
用法示例:
/* 该任务展示了 RTOS 任务通知值中的位被用来传递不同的事件给任务,
与事件组中的标记可能用于相同目的的方式相同。 */
void vAnEventProcessingTask( void *pvParameters )
{
uint32_t ulNotifiedValue;
for( ;; )
{
/* 无限期地阻塞(没有超时,因此不需要检查函数的返回值)
等待通知。
RTOS 任务的通知值中的位由通知任务和中断设置,以指示发生了哪些事件。 */
xTaskNotifyWaitIndexed( 0, /* 等待第 0 个通知。 */
0x00, /* 进入时不清除任何通知位。 */
ULONG_MAX, /* 退出时将通知值重置为 0。 */
&ulNotifiedValue, /* 在 ulNotifiedValue 中传出通知值。 */
portMAX_DELAY ); /* 无限期阻塞。 */
/* 处理已在通知值中锁定的任何事件。 */
if( ( ulNotifiedValue & 0x01 ) != 0 )
{
/* 第 0 位被设置 - 处理由第 0 位代表的事件。 */
prvProcessBit0Event();
}
if( ( ulNotifiedValue & 0x02 ) != 0 )
{
/* 第 1 位被设置 - 处理由第 1 位代表的事件。 */
prvProcessBit1Event();
}
if( ( ulNotifiedValue & 0x04 ) != 0 )
{
/* 第 2 位被设置 - 处理由第 2 位代表的事件。 */
prvProcessBit2Event();
}
/* 等等。 */
}
}
(9)清除指定任务的通知状态:
函数原型:
BaseType_t xTaskNotifyStateClear( TaskHandle_t xTask );
BaseType_t xTaskNotifyStateClearIndexed( TaskHandle_t xTask,
UBaseType_t uxIndexToClear );
- 每项RTOS任务都有一个任务通知数组。每条任务通知都有通知状态,可以是“挂起”或“非挂起”,以及一个32位通知值。
- 如果通知被发送到通知数组中的索引,那么该索引处的通知被称为“待定”,直到任务读取其通知值,或通过调用xTaskNotifyStateClear()将通知状态明确清除为“非挂起”为止。
- xTaskNotifyStateclear()和xTaskNotityStateClearlndexed()是等效宏一一唯一的区别是xTaskNotifyStateClearlndexed可以在数组内任何任务通知上运行,而xTaskNotifyStateClear始终在数组索引0处的任务通知上运行。
- ConfigUSETASK_NOTIFICATIONS必须在FreeRTOSConfig.h中设置为1(或保留为未定义)才能使用这些宏。常量 COnfigTASK NOTIFICATION ARRAY_ENTRIES 设置每个任务的任务通知数组中的索引数。
向后兼容性信息:
- 在FreeRTOSV10.4.0之前,每个任务有一个单一的“通知值,且所有任务通知API函数都在该值上
- 运行。用通知值的数组更换单个通知值需要新的API函数集,该函数集应能在数组内处理具体通知。
- xTaskNotifyStateClear()是原始API函数,并且通过始终在数组内索引 0 处的通知值上运行来保持
- 向后兼容性。调用xTaskNotifyStateClear(等于调用xTaskNotifyStateClearlndexed(),其中
- uxlndexToNotify参数设置为 0。
参数 | 描述 |
---|---|
xTask | 将清除其通知状态的 RTOS 任务的句柄。将 xTask 设置为 NULL 以清除调用任务的通知状态。要获取任务句柄,请使用 xTaskCreate() 创建任务并使用 pxCreatedTask 参数,或使用 xTaskCreateStatic() 创建任务并存储返回值,或在调用 xTaskGetHandle() 时使用任务名称。当前执行的 RTOS 任务的句柄由 xTaskGetCurrentTaskHandle() API 函数返回。 |
uxIndexToClear | 目标任务数组中要执行的通知值的索引。例如,将 uxIndexToClear 设置为 1 将清除数组内索引为 1 时的通知状态。uxIndexToClear 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。ulTaskNotifyStateClear() 没有此参数,并且始终作用于索引 0 的通知上。 |
返回值 | 如果 xTask 引用的任务有挂起的通知,则通知已清除,然后返回 pdTRUE 。如果 xTask 引用的任务有待处理的通知,那么返回 pdFALSE 。 |
用法示例:
/* 一个示例 UART 发送函数。该函数启动 UART 传输,然后等待通知传输完成。
传输完成通知是从 UART 中断发送的。在开始传输之前清除调用任务的通知状态,
以确保在任务尝试在其通知状态上阻塞之前不会偶然已经挂起。 */
void vSerialPutString( const char * const pcStringToSend,
unsigned short usStringLength )
{
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 5000 );
/* xSendingTask 保存等待传输完成的任务句柄。如果 xSendingTask 为 NULL,
则表示没有传输正在进行。除非前一个字符串的传输完成,否则不要开始发送新字符串。*/
if( ( xSendingTask == NULL ) && ( usStringLength > 0 ) )
{
/* 确保调用任务的第 0 个通知状态不是已经挂起。 */
xTaskNotifyStateClearIndexed( NULL, 0 );
/* 存储传输任务的句柄。这用于在传输完成时解锁任务。 */
xSendingTask = xTaskGetCurrentTaskHandle();
/* 开始发送字符串 - 传输由中断控制。 */
UARTSendString( pcStringToSend, usStringLength );
/* 在阻塞状态(因此不使用任何 CPU 时间)等待,直到 UART ISR 发送第 0 个通知给 xSendingTask,
以通知(并解锁)任务传输完成。 */
ulTaskNotifyTake( 0, pdTRUE, xMaxBlockTime );
}
}
(10) 清除任务通知值中指定的位:
函数原型:
uint32_t ulTaskNotifyValueClear( TaskHandle_t xTask,
uint32_t ulBitsToClear );
uint32_t ulTaskNotifyValueClearIndexed( TaskHandle_t xTask,
UBaseType_t uxIndexToClear,
uint32_t ulBitsToClear );
- 每项 RTOS 任务都有一个任务通知数组。每条任务通知都有通知状态,可以是“挂起”或“非挂起”,以及一个 32 位通知值。
- ulTaskNotifyValueClearIndexed() 清除 ulBitsToClear 位掩码指定的位(该掩码位于 xTask 所引用任务的数组索引 uxIndexToClear 的通知值中)。
- ulTaskNotifyValueClear() 和 ulTaskNotifyValueClearIndexed() 是等效的宏 - 唯一的区别是 ulTaskNotifyValueClearIndexed() 可以在数组内的任何任务通知上运行,而 ulTaskNotifyValueClear() 始终在数组索引 0 处的任务通知上运行。
- configUSE_TASK_NOTIFICATIONS 必须在 FreeRTOSConfig.h 中设置为 1(或保留为未定义)才能使用这些宏。常量 configTASK_NOTIFICATION_ARRAY_ENTRIES 设置每个任务的任务通知数组中的索引数。
参数 | 描述 |
---|---|
xTask | 将清除其通知值中的位的 RTOS 任务的句柄。将 xTask 设置为 NULL,以清除调用任务通知值中的位。要获取任务句柄,请使用 xTaskCreate() 创建任务并使用 pxCreatedTask 参数,或使用 xTaskCreateStatic() 创建任务并存储返回值,或在调用 xTaskGetHandle() 时使用任务名称。当前执行的 RTOS 任务的句柄通过执行 xTaskGetCurrentTaskHandle() API 函数的任务或文件本地定义。 |
uxIndexToClear | 目标任务通知值数组中的索引,用于清除其中的位。uxIndexToClear 必须小于 configTASK_NOTIFICATION_ARRAY_ENTRIES 。ulTaskNotifyValueClear() 没有此参数,并始终在索引 0 处清除通知值中的位。 |
ulBitsToClear | 要在 xTask 的通知值中清除的位的位掩码。将某个位设置为 1,可清除任务通知值中的相应位。将 ulBitsToClear 设置为 0xffffffff(32 位架构上的 UINT_MAX)可将通知值清除为 0。将 ulBitsToClear 设置为 0,可在不清除任何位的情况下查询任务的通知值。 |
返回值 | ulBitsToClear 指定位清零前目标任务的通知值。 |
用法示例:
#define MESSAGE_RECEIVED_BIT 8
#define TICKS_UNTIL_TIMEOUT 100
unsigned long ulNotification, ulMessageReceivedMask;
/* 清除任何已接收消息事件。*/
ulMessageReceivedMask = 1u << MESSAGE_RECEIVED_BIT;
ulTaskNotifyValueClear( ulMessageReceivedMask );
/* 发送一个需要响应的消息。*/
send_message();
/* 阻塞此任务,直到它有另一个挂起的通知。在此示例中,任务仅使用其通知值中的 MESSAGE_RECEIVED_BIT,因此下一个事件只能是接收到消息。*/
xTaskNotifyWait( 0u, /* 进入时不清除任何通知位。*/
0u, /* 退出时不清除任何通知位。*/
&ulNotification,
TICKS_UNTIL_TIMEOUT );
/* 如果没有超时,则唯一可能的事件是已接收到消息。*/
if( ulNotification == 0u )
{
/* 处理响应超时。*/
process_response_timeout();
}
else if( ulNotification == ulMessageReceivedMask )
{
/* 处理响应事件。*/
process_response();
ulTaskNotifyValueClear( ulMessageReceivedMask );
}
else
{
/* 示例任务应该只接收 MESSAGE_RECEIVED_EVENTS。*/
process_error();
}
三、用作轻量级二进制信号量:
(1)优点:
与通过二进制信号量解除任务阻塞状态不同,通过直接通知解除 RTOS 任务阻塞状态的速度提高 45%,而且使用的 RAM 减少。
(2)实现:
- 二进制信号量是一种最大计数为 1 的信号量,因此称为“二进制”。只有在信号量可用的情况下,任务才能“获取”信号量,而只有在其计数为 1 的情况下,信号量才可用。
- 当使用任务通知代替二进制信号量时,接收任务的通知值会用于替代二进制信号量的计数值,而且 ulTaskNotifyTake()(或 ulTaskNotifyTakeIndexed())API 函数会用于代替信号量的 xSemaphoreTake() API 函数。ulTaskNotifyTake() 函数的 xClearOnExit 参数设置为 pdTRUE,这样每次获取通知时计数值均归零——模拟二进制信号量。
- 同样,xTaskNotifyGive()(或 xTaskNotifyGiveIndexed())或者 vTaskNotifyGiveFromISR()(或 vTaskNotifyGiveIndexedFromISR())函数用于代替信号量的 xSemaphoreGive() 和 xSemaphoreGiveFromISR() 函数。
(3)用法示例:
/* 这是一个通用外设驱动程序中的发送函数示例。
一个RTOS任务调用发送函数,然后在阻塞状态(因此不使用CPU时间)
等待传输完成的通知。传输由DMA执行,DMA结束中断用于通知任务。 */
/* 存储将在传输完成时被通知的任务的句柄。 */
static TaskHandle_t xTaskToNotify = NULL;
/* 在目标任务的任务通知数组中使用的索引。 */
const UBaseType_t xArrayIndex = 1;
/* 外设驱动程序的发送函数。 */
void StartTransmission( uint8_t *pcData, size_t xDataLength )
{
/* 在这一点上,xTaskToNotify 应该为 NULL,因为没有传输正在进行。
如果必要,可以使用互斥锁来保护对外设的访问。 */
configASSERT( xTaskToNotify == NULL );
/* 存储调用任务的句柄。 */
xTaskToNotify = xTaskGetCurrentTaskHandle();
/* 开始传输 - 传输完成时会生成一个中断。 */
vStartTransmit( pcData, xDataLength );
}
/*-----------------------------------------------------------*/
/* 发送结束中断。 */
void vTransmitEndISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 在这一点上,xTaskToNotify 不应该为 NULL,因为有一个传输正在进行。 */
configASSERT( xTaskToNotify != NULL );
/* 通知任务传输已完成。 */
vTaskNotifyGiveIndexedFromISR( xTaskToNotify,
xArrayIndex,
&xHigherPriorityTaskWoken );
/* 没有传输正在进行,所以没有任务需要通知。 */
xTaskToNotify = NULL;
/* 如果 xHigherPriorityTaskWoken 现在设置为 pdTRUE,那么应该执行一个
上下文切换,以确保中断直接返回到最高优先级任务。用于此目的的宏
取决于使用的端口,可能称为 portEND_SWITCHING_ISR()。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 启动传输,然后进入阻塞状态(因此不消耗任何CPU时间)以等待其完成的任务。 */
void vAFunctionCalledFromATask( uint8_t ucDataToTransmit,
size_t xDataLength )
{
uint32_t ulNotificationValue;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
/* 通过调用上面的函数来启动传输。 */
StartTransmission( ucDataToTransmit, xDataLength );
/* 等待传输完成的通知。注意第一个参数是 pdTRUE,这将任务的通知值
清零,使得通知值表现得像一个二进制(而不是计数)信号量。 */
ulNotificationValue = ulTaskNotifyTakeIndexed( xArrayIndex,
pdTRUE,
xMaxBlockTime );
if( ulNotificationValue == 1 )
{
/* 传输按预期结束。 */
}
else
{
/* 调用 ulTaskNotifyTake() 超时。 */
}
}
四、用作轻量级计数信号量:
(1)优点:
与通过信号量解除任务阻塞状态不同,通过直接通知解除RTOS任务阻塞状态的速度提高45%,而且使用的RAM更少。
(2)实现:
- 计数信号量指的是计数值范围从0到信号量创建时所设最高值的一种信号量。只有在信号量可用的情况下,任务才能“获取”信号量,而只有在计数大于零的情况下,信号量才可用。
- 当使用任务通知代替计数信号量时,接收任务的通知值会用于替代计数信号量的计数值,而且 ulTaskNotifyTake()(或 ulTaskNotifyTakeIndexed())API 函数会用于代替信号量的 xSemaphoreTake() API 函数。ulTaskNotifyTake() 函数的 xClearOnExit 参数设置为 pdFALSE,因此每次接收通知时,计数值只会递减(而不是清除),模拟计数信号量。
- 同样,xTaskNotifyGive()(或 xTaskNotifyGiveIndexed())或者 vTaskNotifyGiveFromISR()(或 vTaskNotifyGiveIndexedFromISR())函数用于代替信号量的 xSemaphoreGive() 和 xSemaphoreGiveFromISR() 函数。
(3)用法示例:
示例一:使用接收任务的通知值作为计数信号量。
/* 一个中断处理程序,它不直接处理中断,
而是将处理推迟到一个高优先级的RTOS任务。
ISR使用RTOS任务通知来解除RTOS任务的阻塞并增加RTOS任务的通知值。 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* 清除中断。 */
prvClearInterruptSource();
/* xHigherPriorityTaskWoken必须初始化为pdFALSE。
如果调用vTaskNotifyGiveFromISR()解除了处理任务的阻塞,
并且处理任务的优先级高于当前运行任务的优先级,
那么xHigherPriorityTaskWoken将自动设置为pdTRUE。 */
xHigherPriorityTaskWoken = pdFALSE;
/* 解除处理任务的阻塞,以便任务可以执行由中断引起的任何处理。
xHandlingTask是任务的句柄,它是在任务创建时获得的。
vTaskNotifyGiveFromISR()也增加了接收任务的通知值。 */
vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
/* 如果xHigherPriorityTaskWoken现在设置为pdTRUE,则强制上下文切换。
用于此目的的宏取决于端口,可能称为portEND_SWITCHING_ISR。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 一个阻塞等待通知的任务,该通知表明外设需要服务。 */
void vHandlingTask( void *pvParameters )
{
BaseType_t xEvent;
const TickType_t xBlockTime = pdMS_TO_TICKS( 500 );
uint32_t ulNotifiedValue;
for( ;; )
{
/* 阻塞等待通知。在这里,RTOS任务通知被用作计数信号量。
每次ISR调用vTaskNotifyGiveFromISR()时,任务的通知值都会增加,
每次RTOS任务调用ulTaskNotifyTake()时,通知值都会减少,
因此实际上保存了未处理中断的数量。
第一个参数设置为pdFALSE,因此通知值只会减少而不会清零,
一次处理一个延迟的中断事件。参见下面的例子2,了解更实用的方法。 */
ulNotifiedValue = ulTaskNotifyTake( pdFALSE,
xBlockTime );
if( ulNotifiedValue > 0 )
{
/* 执行由中断引起的任何处理。 */
xEvent = xQueryPeripheral();
if( xEvent != NO_MORE_EVENTS )
{
vProcessPeripheralEvent( xEvent );
}
}
else
{
/* 在预期的时间内没有收到通知。 */
vCheckForErrorConditions();
}
}
}
示例二:提供更加实用和有效的实现。
在此实现中,从 ulTaskNotifyTake() 返回的值用于了解必须处理多少未完成的 ISR 事件,从而允许在每次调用 ulTaskNotifyTake() 时将RTOS任务的通知计数清零。假设中断服务程序 (ISR) 如上面的示例 1 所示。
/* 在目标任务的任务通知数组中使用的索引。 */
const UBaseType_t xArrayIndex = 0;
/* 一个阻塞等待通知的任务,该通知表明外设需要服务。 */
void vHandlingTask( void *pvParameters )
{
BaseType_t xEvent;
const TickType_t xBlockTime = pdMS_TO_TICKS( 500 ); // 最大阻塞时间,转换为系统滴答计数
uint32_t ulNotifiedValue; // 用于存储接收到的通知值
for( ;; ) // 无限循环,持续运行任务
{
/* 与之前类似,阻塞等待来自ISR的通知。
这次,第一个参数设置为pdTRUE,清除任务的通知值到0,
意味着每个未处理的延迟中断事件必须在再次调用ulTaskNotifyTake()之前处理。 */
ulNotifiedValue = ulTaskNotifyTakeIndexed( xArrayIndex,
pdTRUE, // 清除通知值
xBlockTime );
if( ulNotifiedValue == 0 )
{
/* 在预期的时间内没有收到通知。 */
vCheckForErrorConditions(); // 检查错误条件
}
else
{
/* ulNotifiedValue 保存了未处理中断的数量。
逐个处理每个中断。 */
while( ulNotifiedValue > 0 )
{
xEvent = xQueryPeripheral(); // 查询外设状态
if( xEvent != NO_MORE_EVENTS ) // 如果有事件需要处理
{
vProcessPeripheralEvent( xEvent ); // 处理外设事件
ulNotifiedValue--; // 减少未处理中断计数
}
else
{
break; // 如果没有更多事件,跳出循环
}
}
}
}
}
五、用作轻量级事件组:
(1)优点:
事件组是一组二进制标志(或位),应用程序编写者可以对其中的每一个指定含义。RTOS 任务可以进入“阻塞”状态以等待组内的一个或多个标志激活。待验证的 RTOS 任务处于“阻塞”状态时不会占用任何 CPU 时间。
(2)实现:
- 当使用任务通知代替事件组时,使用接收任务的通知值代替事件组,接收任务通知值中的位被用作事件标志,xTaskNotifyWait() API 函数被用于代替事件组的 xEventGroupWaitBits() API 函数。
- 同样,使用 xTaskNotify() 和 xTaskNotifyFromISR() API 函数(其 eAction 参数设置为 eSetBits)分别代替 xEventGroupSetBits() 和 xEventGroupSetBitsFromISR() 函数来设置位。
- 与 xEventGroupSetBitsFromISR() 相比,xTaskNotifyFromISR() 具有显著的性能优势,这是因为 xTaskNotifyFromISR() 完全在 ISR 中执行,而 xEventGroupSetBitsFromISR() 必须将某些处理推迟到 RTOS 守护程序任务。
- 与使用事件组时的情况不同的是,接收任务无法指定它只想在位组合同时处于活动状态时解除阻塞状态。相反,任务在任何位变为活动状态时结束阻塞,并且必须测试位组合本身。
(3)用法示例:
/* 此示例演示了单个RTOS任务如何被用来处理来自两个不同中断服务例程的事件——发送中断和接收中断。
许多外设将使用相同的处理程序,在这种情况下,外设的中断状态寄存器可以简单地与接收任务的通知值进行位或运算。
首先,定义一些位来表示每个中断源。 */
#define TX_BIT 0x01 // 表示发送中断的位
#define RX_BIT 0x02 // 表示接收中断的位
/* 将从中断接收通知的任务的句柄。该句柄是在任务创建时获得的。 */
static TaskHandle_t xHandlingTask;
/*-----------------------------------------------------------*/
/* 发送中断服务例程的实现。 */
void vTxISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 清除中断源。 */
prvClearInterrupt();
/* 通过在任务的通知值中设置TX_BIT来通知任务传输已完成。 */
xTaskNotifyFromISR( xHandlingTask,
TX_BIT,
eSetBits,
&xHigherPriorityTaskWoken );
/* 如果xHigherPriorityTaskWoken现在设置为pdTRUE,则应执行上下文切换以确保中断直接返回到最高优先级任务。
用于此目的的宏取决于使用的端口,可能称为portEND_SWITCHING_ISR()。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 接收中断服务例程的实现与上述相同,除了在接收任务的通知值中设置的位不同。 */
void vRxISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 清除中断源。 */
prvClearInterrupt();
/* 通过在任务的通知值中设置RX_BIT来通知任务接收已完成。 */
xTaskNotifyFromISR( xHandlingTask,
RX_BIT,
eSetBits,
&xHigherPriorityTaskWoken );
/* 如果xHigherPriorityTaskWoken现在设置为pdTRUE,则应执行上下文切换以确保中断直接返回到最高优先级任务。
用于此目的的宏取决于使用的端口,可能称为portEND_SWITCHING_ISR()。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/
/* 被中断服务例程通知的任务的实现。 */
static void prvHandlingTask( void *pvParameter )
{
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 500 );
BaseType_t xResult;
uint32_t ulNotifiedValue; // 用于存储接收到的通知值
for( ;; )
{
/* 等待中断的通知。 */
xResult = xTaskNotifyWait( pdFALSE, /* 进入时不清除位。 */
ULONG_MAX, /* 退出时清除所有位。 */
&ulNotifiedValue, /* 存储通知的值。 */
xMaxBlockTime );
if( xResult == pdPASS )
{
/* 收到通知。查看哪些位被设置。 */
if( ( ulNotifiedValue & TX_BIT ) != 0 )
{
/* 发送ISR已设置一个位。 */
prvProcessTx();
}
if( ( ulNotifiedValue & RX_BIT ) != 0 )
{
/* 接收ISR已设置一个位。 */
prvProcessRx();
}
}
else
{
/* 在预期的时间内没有收到通知。 */
prvCheckForErrors();
}
}
}
六、用作轻量级邮箱:
(1)限制:
RTOS 任务通知可用于向任务发送数据,但相比使用 RTOS 队列实现有一些限制,因为:
- 只能发送 32 位值。
- 该值保存为接收任务的通知值,然后在任何时间只能有一个通知值
(2)实现:
因此,使用“轻量级邮箱”这个短语代替“轻量级队列”。任务的通知值就是邮箱值。
- 使用 xTaskNotify()(或 xTaskNotifyIndexed())和 xTaskNotifyFromISR()(或 xTaskNotifyIndexedFromISR())API 函数将数据发给任务,其中函数的 eAction 参数设置为 eSetValueWithOverwrite 或 eSetValueWithoutOverwrite。
- 如果 eAction 设置为 eSetValueWithOverwrite,则即使接收任务已有挂起的通知,也会更新接收任务的通知值。
- 如果 eAction 设置为 eSetValueWithoutOverwrite,则只有在接收任务没有挂起通知时才会更新接收任务的通知值,因为更新通知值会在接收任务处理之前覆盖以前的值。
- 任务可以使用 xTaskNotifyWait()(或 xTaskNotifyWaitIndexed())读取自己的通知值。
七、任务间通信:
【FreeRTOS 教程 一】任务结构体及其基础创建使用
【FreeRTOS 教程 二】任务优先级与任务控制
【FreeRTOS 教程 三】协程状态、优先级、实现及调度
【FreeRTOS 教程 四】队列创建与发布项目到队列
【FreeRTOS 教程 五】FreeRTOS 内存管理细致讲解
【FreeRTOS 教程 六】二进制信号量与计数信号量
【FreeRTOS 教程 七】互斥锁与递归互斥锁
八、FreeRTOS教程示例代码下载:
通过网盘分享的文件:FreeRTOS教程示例代码