5- FreeRTOS任务通知

简介: 5- FreeRTOS任务通知

1- 简介


每个系统任务都会有一个任务通知。然后每个任务通知都具有挂起或者未挂起的状态,以及32位的通知。常量configTASK_NOTIFICATION_ARRAY_ENTRIES()是用来设置任务通知索引的数组。

任务通知是直接发送给任务事件,不是通过中间对象(队列、事件组、信号量)间接发送给任务的。

当任务发送任务通知时,会将目标任务通知的状态设定位挂起状态。就像任务阻塞中间对象一样,例如,信号在等待信号量的可用情况,任务组织这个等待通知的状态变为挂起的状态。

任务通知也可以是以下几种方式:

  • 值覆盖,不管接受任务是否已经读取被覆盖的值。
  • 值覆盖,仅当接收任务已读取被覆盖的值时。
  • 在设置值中设置一个或多个位。
  • 递增值(加1)


注意:
每个数组中的通知都是独立的,一个任务一次只能阻塞数组中的一个通知,而且不会通过发送到任何其他数组索引的直通解除阻塞状态。


1.1 优势和使用限制

任务通知的灵活性允许它们在需要创建单独队列、二进制信号量、计数信号量或事件组的情况下使用。使用直接通知解除RTOS任务的阻塞速度比使用中间对象(如二进制信号量)解除阻塞的速度快45% *,并且使用的RAM更少。正如所料,这些性能优势需要一些用例限制。

1-当只有一个任务可以接收事件时,才可以使用RTOS任务通知。然而,实际应用中的大多数用例都满足这个条件,例如中断解除阻塞,任务将处理由中断接收到的数据。

2-只有在使用RTOS任务通知代替队列的情况下:接收任务可以在阻塞状态下等待通知(这样不会消耗任何CPU时间),如果发送任务不能立即完成,则发送任务不能在阻塞状态下等待发送完成。


1.2 用例

通知使用xTaskNotifyIndexed()和xTaskNotifyGiveIndexed() API函数(和它们的中断安全等效)发送,并保持等待,直到接收RTOS任务调用xTaskNotifyWaitIndexed()或ulTaskNotifyTakeIndexed() API函数。这些API函数都有一个不带“I索引”前缀的等价函数。非“索引”版本总是在数组索引0处的任务通知上操作。例如,xTaskNotifyGive(TargetTask)等价于xTaskNotifyGiveIndexed(TargetTask, 0) -两者都在索引0处增加任务通知由任务处理的TargetTask引用的任务。


2-将 RTOS 任务通知用作轻量级二进制信号量

与使用二进制信号量解锁任务相比,使用直接通知解锁 RTOS 任务的速度快 45%,并且使用的 RAM 更少。



二进制信号量是最大计数为 1 的信号量,因此称为“二进制”。任务只有在信号量可用时才可以“获取”信号量,并且信号量仅 如果计数为 1,则可用。


当使用任务通知代替二进制信号量时,接收任务的通知值会代替二进制信号量的count值,并且会使用ulTaskNotifyTake()(或ulTaskNotifyTakeIndexed()) API函数代替信号量的xSemaphoreTake() API函数。

ulTaskNotifyTake()函数的xClearOnExit参数被设置为pdTRUE,因此每次收到通知时count值都返回0——模拟二进制信号量。

同样,xTaskNotifyGive()(或xTaskNotifyGiveIndexed())或vTaskNotifyGiveFromISR()(或vTaskNotifyGiveIndexedFromISR())函数被用来代替信号量的xSemaphoreGive()和xSemaphoreGiveFromISR()函数。


2.1 使用范例

1/*这是通用外设驱动程序中的传输函数的一个例子。
 2RTOS任务调用传输函数,然后处于阻塞状态(因此不占用CPU时间),
 3直到收到传输完成的通知。传输由DMA执行,DMA端中断用于通知任务。 */
 4
 5/* 存储传输完成时将收到通知的任务句柄。 */
 6static TaskHandle_t xTaskToNotify = NULL;
 7
 8/* 目标任务要使用的任务通知数组中的索引。 */
 9const UBaseType_t xArrayIndex = 1;
10
11/* 外设驱动的传输功能 */
12void StartTransmission( uint8_t *pcData, size_t xDataLength )
13{
14/*此时xTaskToNotify应该为NULL,因为没有正在进行传输。
15如果有必要,可以使用互斥量来保护对外设的访问。*/
16configASSERT( xTaskToNotify == NULL );
17
18/* 存储调用任务的句柄。 */
19xTaskToNotify = xTaskGetCurrentTaskHandle();
20
21/* 开始传输:在传输完成时产生一个中断。 */
22vStartTransmit( pcData, xDatalength );
23}
24/*-----------------------------------------------------------*/
25
26/* 结束中断传输 */
27void vTransmitEndISR( void )
28{
29BaseType_t xHigherPriorityTaskWoken = pdFALSE;
30
31/* 此时,xTaskToNotify不应该为NULL,因为传输正在进行中。 */
32configASSERT( xTaskToNotify != NULL );
33
34/* Notify the task that the transmission is complete. */
35vTaskNotifyGiveIndexedFromISR( xTaskToNotify,
36xArrayIndex,
37&xHigherPriorityTaskWoken );
38
39/*此时,xTaskToNotify不应该为NULL,因为传输正在进行中。 */
40xTaskToNotify = NULL;
41
42/*如果xHigherPriorityTaskWoken现在设置为pdTRUE,
43那么应该执行切换,以确保中断直接返回到最高优先级的任务。
44用于该目的的宏取决于所使用的端口,可以称为portEND_SWITCHING_ISR()。 */
45portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
46}
47/*-----------------------------------------------------------*/
48
49/* 发起传输的任务,然后进入阻塞状态(因此不消耗任何CPU时间),以等待传输完成。 */
50void vAFunctionCalledFromATask( uint8_t ucDataToTransmit,
51size_t xDataLength )
52{
53uint32_t ulNotificationValue;
54const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
55
56/* 通过调用上面显示的函数开始传输。 */
57StartTransmission( ucDataToTransmit, xDataLength );
58
59/* 等待传输完成的通知。注意,第一个参数是pdTRUE,
60它的作用是将任务的通知值清除回0,
61使通知值类似于二进制(而不是计数)信号量。 */
62ulNotificationValue = ulTaskNotifyTakeIndexed( xArrayIndex,
63pdTRUE,
64xMaxBlockTime );
65
66if( ulNotificationValue == 1 )
67{
68/* The transmission ended as expected. */
69}
70else
71{
72/* The call to ulTaskNotifyTake() timed out. */
73}
74}


3-使用 RTOS 任务通知作为轻量级计数信号量

与使用信号量解锁任务相比,使用直接通知解锁 RTOS 任务的速度快 45%,并且使用的 RAM 更少。

计数信号量是这样一种信号量,其计数值可以为0,直到创建信号量时设置的最大值。只有当信号量可用时,任务才能获取信号量,并且只有当信号量的计数大于0时,信号量才可用。

当使用任务通知来代替计数信号量时,接收任务的通知值会代替计数信号量的计数值,并且使用ulTaskNotifyTake()(或ulTaskNotifyTakeIndexed()) API函数来代替信号量的xSemaphoreTake() API函数。ulTaskNotifyTake()函数的xClearOnExit参数被设置为pdFALSE,因此每次收到通知时,计数值只减少(而不是清除)——模拟计数信号量。

同样,xTaskNotifyGive()(或xTaskNotifyGiveIndexed())或vTaskNotifyGiveFromISR()(或vTaskNotifyGiveIndexedFromISR())函数被用来代替信号量的xSemaphoreGive()和xSemaphoreGiveFromISR()函数。


下面通过两个例子来看。

下面的第一个例子使用接收任务的通知值作为计数信号量。第二个示例提供了高效的实现方式。


3.1 Example 1:

1/* An interrupt handler that does not process interrupts directly,
 2but instead defers processing to a high priority RTOS task. The
 3ISR uses RTOS task notifications to both unblock the RTOS task
 4and increment the RTOS task's notification value. */
 5void vANInterruptHandler( void )
 6{
 7BaseType_t xHigherPriorityTaskWoken;
 8
 9/* Clear the interrupt. */
10prvClearInterruptSource();
11
12/* xHigherPriorityTaskWoken must be initialised to pdFALSE.
13If calling vTaskNotifyGiveFromISR() unblocks the handling
14task, and the priority of the handling task is higher than
15the priority of the currently running task, then
16xHigherPriorityTaskWoken will be automatically set to pdTRUE. */
17xHigherPriorityTaskWoken = pdFALSE;
18
19/* Unblock the handling task so the task can perform
20any processing necessitated by the interrupt. xHandlingTask
21is the task's handle, which was obtained when the task was
22created. vTaskNotifyGiveFromISR() also increments
23the receiving task's notification value. */
24vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
25
26/* Force a context switch if xHigherPriorityTaskWoken is now
27set to pdTRUE. The macro used to do this is dependent on
28the port and may be called portEND_SWITCHING_ISR. */
29portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
30}
31/*-----------------------------------------------------------*/
32
33/* A task that blocks waiting to be notified that the peripheral
34needs servicing. */
35void vHandlingTask( void *pvParameters )
36{
37BaseType_t xEvent;
38const TickType_t xBlockTime = pdMS_TO_TICS( 500 );
39uint32_t ulNotifiedValue;
40
41for( ;; )
42{
43/* Block to wait for a notification. Here the RTOS
44task notification is being used as a counting semaphore.
45The task's notification value is incremented each time
46the ISR calls vTaskNotifyGiveFromISR(), and decremented
47each time the RTOS task calls ulTaskNotifyTake() - so in
48effect holds a count of the number of outstanding interrupts.
49The first parameter is set to pdFALSE, so the notification
50value is only decremented and not cleared to zero, and one
51deferred interrupt event is processed at a time. See
52example 2 below for a more pragmatic approach. */
53ulNotifiedValue = ulTaskNotifyTake( pdFALSE,
54xBlockTime );
55
56if( ulNotifiedValue > 0 )
57{
58/* Perform any processing necessitated by the interrupt. */
59xEvent = xQueryPeripheral();
60
61if( xEvent != NO_MORE_EVENTS )
62{
63vProcessPeripheralEvent( xEvent );
64}
65}
66else
67{
68/* Did not receive a notification within the expected
69time. */
70vCheckForErrorConditions();
71}
72}
73}

3.2 Example 2:

1这个例子展示了一个更实用和高效的RTOS任务的实现。在这个实现中,ulTaskNotifyTake()的返回值用于知道有多少未处理的ISR事件必须被处理,允许RTOS任务的通知计数在每次ulTaskNotifyTake()被调用时被清除回零。中断服务例程(ISR)假定如上例1所示。
 2/* The index within the target task's array of task notifications
 3to use. */
 4const UBaseType_t xArrayIndex = 0;
 5
 6/* A task that blocks waiting to be notified that the peripheral
 7needs servicing. */
 8void vHandlingTask( void *pvParameters )
 9{
10BaseType_t xEvent;
11const TickType_t xBlockTime = pdMS_TO_TICS( 500 );
12uint32_t ulNotifiedValue;
13
14for( ;; )
15{
16/* As before, block to wait for a notification form the ISR.
17This time however the first parameter is set to pdTRUE,
18clearing the task's notification value to 0, meaning each
19outstanding outstanding deferred interrupt event must be
20processed before ulTaskNotifyTake() is called again. */
21ulNotifiedValue = ulTaskNotifyTakeIndexed( xArrayIndex,
22pdTRUE,
23xBlockTime );
24
25if( ulNotifiedValue == 0 )
26{
27/* Did not receive a notification within the expected
28time. */
29vCheckForErrorConditions();
30}
31else
32{
33/* ulNotifiedValue holds a count of the number of
34outstanding interrupts. Process each in turn. */
35while( ulNotifiedValue > 0 )
36{
37xEvent = xQueryPeripheral();
38
39if( xEvent != NO_MORE_EVENTS )
40{
41vProcessPeripheralEvent( xEvent );
42ulNotifiedValue--;
43}
44else
45{
46break;
47}
48}
49}
50}
51}

4-将 RTOS 任务通知用作轻量级事件组


事件组是一组二进制标志(或位),应用程序编写人员可以为每个标志指定含义。RTOS进程可以进入阻塞状态,等待组内的一个或多个标志变为活动状态。当RTOS任务处于阻塞状态时,不会消耗任何CPU时间。

当使用任务通知代替事件组时,使用接收任务的通知值代替事件组,接收任务的通知值中的比特位用作事件标志,并且使用xTaskNotifyWait() API函数代替事件组的xEventGroupWaitBits() API函数。

同样,使用xTaskNotify()和xTaskNotifyFromISR() API函数(其eAction参数设置为eSetBits)来代替xEventGroupSetBits()和xEventGroupSetBitsFromISR()函数。

与xEventGroupSetBitsFromISR()相比,xTaskNotifyFromISR()具有显著的性能优势,因为xTaskNotifyFromISR()完全在ISR中执行,而xEventGroupSetBitsFromISR()必须推迟一些处理到RTOS进程任务。

与使用事件组时不同,接收任务不能指定只在同时有多个比特位处于活动状态时才离开阻塞状态。相反,在任何比特位处于活动状态时,进程将解除阻塞,并且必须自己测试比特位的组合。


1/* This example demonstrates a single RTOS task being used to process
  2events that originate from two separate interrupt service routines -
  3a transmit interrupt and a receive interrupt. Many peripherals will
  4use the same handler for both, in which case the peripheral's
  5interrupt status register can simply be bitwise ORed with the
  6receiving task's notification value.
  7
  8First bits are defined to represent each interrupt source. */
  9#define TX_BIT 0x01
 10#define RX_BIT 0x02
 11
 12/* The handle of the task that will receive notifications from the
 13interrupts. The handle was obtained when the task
 14was created. */
 15static TaskHandle_t xHandlingTask;
 16
 17/*-----------------------------------------------------------*/
 18
 19/* The implementation of the transmit interrupt service routine. */
 20void vTxISR( void )
 21{
 22BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 23
 24/* Clear the interrupt source. */
 25prvClearInterrupt();
 26
 27/* Notify the task that the transmission is complete by setting the TX_BIT
 28in the task's notification value. */
 29xTaskNotifyFromISR( xHandlingTask,
 30TX_BIT,
 31eSetBits,
 32&xHigherPriorityTaskWoken );
 33
 34/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
 35should be performed to ensure the interrupt returns directly to the highest
 36priority task. The macro used for this purpose is dependent on the port in
 37use and may be called portEND_SWITCHING_ISR(). */
 38portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
 39}
 40/*-----------------------------------------------------------*/
 41
 42/* The implementation of the receive interrupt service routine is identical
 43except for the bit that gets set in the receiving task's notification value. */
 44void vRxISR( void )
 45{
 46BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 47
 48/* Clear the interrupt source. */
 49prvClearInterrupt();
 50
 51/* Notify the task that the reception is complete by setting the RX_BIT
 52in the task's notification value. */
 53xTaskNotifyFromISR( xHandlingTask,
 54RX_BIT,
 55eSetBits,
 56&xHigherPriorityTaskWoken );
 57
 58/* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
 59should be performed to ensure the interrupt returns directly to the highest
 60priority task. The macro used for this purpose is dependent on the port in
 61use and may be called portEND_SWITCHING_ISR(). */
 62portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
 63}
 64/*-----------------------------------------------------------*/
 65
 66/* The implementation of the task that is notified by the interrupt service
 67routines. */
 68static void prvHandlingTask( void *pvParameter )
 69{
 70const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 500 );
 71BaseType_t xResult;
 72
 73for( ;; )
 74{
 75/* Wait to be notified of an interrupt. */
 76xResult = xTaskNotifyWait( pdFALSE, /* Don't clear bits on entry. */
 77ULONG_MAX, /* Clear all bits on exit. */
 78&ulNotifiedValue, /* Stores the notified value. */
 79xMaxBlockTime );
 80
 81if( xResult == pdPASS )
 82{
 83/* A notification was received. See which bits were set. */
 84if( ( ulNotifiedValue & TX_BIT ) != 0 )
 85{
 86/* The TX ISR has set a bit. */
 87prvProcessTx();
 88}
 89
 90if( ( ulNotifiedValue & RX_BIT ) != 0 )
 91{
 92/* The RX ISR has set a bit. */
 93prvProcessRx();
 94}
 95}
 96else
 97{
 98/* Did not receive a notification within the expected time. */
 99prvCheckForErrors();
100}
101}
102}


5-将 RTOS 任务通知用作轻量级邮箱RTOS任务通知可用于向任务发送数据,但与RTOS队列相比,发送数据的方式要严格得多,因为:

  • 只能发送 32 位值
  • 该值被保存为接收任务的通知值,并且在任何时间只能有一个通知值,因此短语“轻量级邮箱”优先于“轻量级队列”。任务的通知值是邮箱值。
  • 数据使用xTaskNotify()(或xTaskNotifyIndexed())和xTaskNotifyFromISR()(或xTaskNotifyIndexedFromISR()) API函数发送到任务,其eAction参数设置为eSetValueWithOverwrite或eSetValueWithoutOverwrite。如果eAction设置为eSetValueWithOverwrite,那么即使接收任务已经有一个待处理的通知,也会更新接收任务的通知值。如果eAction设置为esetvaluewithoutooverwrite,则只有在接收任务没有待处理通知时才更新接收任务的通知值——因为更新通知值将覆盖接收任务处理之前的值。
    任务可以使用xTaskNotifyWait()(或xTaskNotifyWaitIndexed())读取它自己的通知值。

相关文章
|
6月前
【FreeRTOS】中断管理(二)
【FreeRTOS】中断管理
125 0
|
6月前
【FreeRTOS】中断管理(三)
【FreeRTOS】中断管理
|
6月前
|
API 调度
【FreeRTOS】软件定时器的使用
【FreeRTOS】软件定时器的使用
169 0
|
6月前
|
消息中间件 算法 调度
|
6月前
【FreeRTOS】任务通知的使用
【FreeRTOS】任务通知的使用
|
6月前
|
API C语言
【FreeRTOS】中断管理(一)
【FreeRTOS】中断管理
121 0
|
6月前
|
移动开发
【FreeRTOS】事件组的使用
【FreeRTOS】事件组的使用
|
6月前
|
存储
FreeRTOS事件组
FreeRTOS事件组
53 0
|
6月前
|
API
FreeRTOS软件定时器的原理以及使用实例
FreeRTOS软件定时器的原理以及使用实例
145 0
|
6月前
|
API 调度
FreeRTOS深入教程(中断管理)
FreeRTOS深入教程(中断管理)
303 0