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 使用范例

/*这是通用外设驱动程序中的传输函数的一个例子。
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 );

    /* Notify the task that the transmission is complete. */
    vTaskNotifyGiveIndexedFromISR( xTaskToNotify,
                                   xArrayIndex,
                                   &xHigherPriorityTaskWoken );

    /*此时,xTaskToNotify不应该为NULL,因为传输正在进行中。 */
    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,
	它的作用是将任务的通知值清除回0,
	使通知值类似于二进制(而不是计数)信号量。 */
    ulNotificationValue = ulTaskNotifyTakeIndexed( xArrayIndex,
                                                   pdTRUE,
                                                   xMaxBlockTime );

    if( ulNotificationValue == 1 )
    {
        /* The transmission ended as expected. */
    }
    else
    {
        /* The call to ulTaskNotifyTake() timed out. */
    }
}

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:

/* An interrupt handler that does not process interrupts directly,
but instead defers processing to a high priority RTOS task.  The
ISR uses RTOS task notifications to both unblock the RTOS task
and increment the RTOS task's notification value. */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;

    /* Clear the interrupt. */
    prvClearInterruptSource();

    /* xHigherPriorityTaskWoken must be initialised to pdFALSE.
    If calling vTaskNotifyGiveFromISR() unblocks the handling
    task, and the priority of the handling task is higher than
    the priority of the currently running task, then
    xHigherPriorityTaskWoken will be automatically set to pdTRUE. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* Unblock the handling task so the task can perform
    any processing necessitated by the interrupt.  xHandlingTask
    is the task's handle, which was obtained when the task was
    created.  vTaskNotifyGiveFromISR() also increments
    the receiving task's notification value. */
    vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );

    /* Force a context switch if xHigherPriorityTaskWoken is now
    set to pdTRUE. The macro used to do this is dependent on
    the port and may be called portEND_SWITCHING_ISR. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

/* A task that blocks waiting to be notified that the peripheral
needs servicing. */
void vHandlingTask( void *pvParameters )
{
BaseType_t xEvent;
const TickType_t xBlockTime = pdMS_TO_TICS( 500 );
uint32_t ulNotifiedValue;

    for( ;; )
    {
        /* Block to wait for a notification.  Here the RTOS
        task notification is being used as a counting semaphore.
        The task's notification value is incremented each time
        the ISR calls vTaskNotifyGiveFromISR(), and decremented
        each time the RTOS task calls ulTaskNotifyTake() - so in
        effect holds a count of the number of outstanding interrupts.
        The first parameter is set to pdFALSE, so the notification
        value is only decremented and not cleared to zero, and one
        deferred interrupt event is processed at a time.  See
        example 2 below for a more pragmatic approach. */
        ulNotifiedValue = ulTaskNotifyTake( pdFALSE,
                                            xBlockTime );

        if( ulNotifiedValue > 0 )
        {
            /* Perform any processing necessitated by the interrupt. */
            xEvent = xQueryPeripheral();

            if( xEvent != NO_MORE_EVENTS )
            {
                vProcessPeripheralEvent( xEvent );
            }
        }
        else
        {
            /* Did not receive a notification within the expected
            time. */
            vCheckForErrorConditions();
        }
    }
}

3.2 Example 2:

这个例子展示了一个更实用和高效的RTOS任务的实现。在这个实现中,ulTaskNotifyTake()的返回值用于知道有多少未处理的ISR事件必须被处理,允许RTOS任务的通知计数在每次ulTaskNotifyTake()被调用时被清除回零。中断服务例程(ISR)假定如上例1所示。

/* The index within the target task's array of task notifications
to use. */
const UBaseType_t xArrayIndex = 0;

/* A task that blocks waiting to be notified that the peripheral
needs servicing. */
void vHandlingTask( void *pvParameters )
{
BaseType_t xEvent;
const TickType_t xBlockTime = pdMS_TO_TICS( 500 );
uint32_t ulNotifiedValue;

    for( ;; )
    {
        /* As before, block to wait for a notification form the ISR.
        This time however the first parameter is set to pdTRUE,
        clearing the task's notification value to 0, meaning each
        outstanding outstanding deferred interrupt event must be
        processed before ulTaskNotifyTake() is called again. */
        ulNotifiedValue = ulTaskNotifyTakeIndexed( xArrayIndex,
                                                   pdTRUE,
                                                   xBlockTime );

        if( ulNotifiedValue == 0 )
        {
            /* Did not receive a notification within the expected
            time. */
            vCheckForErrorConditions();
        }
        else
        {
            /* ulNotifiedValue holds a count of the number of
            outstanding interrupts.  Process each in turn. */
            while( ulNotifiedValue > 0 )
            {
                xEvent = xQueryPeripheral();

                if( xEvent != NO_MORE_EVENTS )
                {
                    vProcessPeripheralEvent( xEvent );
                    ulNotifiedValue--;
                }
                else
                {
                    break;
                }
            }
        }
    }
}

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

事件组是一组二进制标志(或位),应用程序编写人员可以为每个标志指定含义。RTOS进程可以进入阻塞状态,等待组内的一个或多个标志变为活动状态。当RTOS任务处于阻塞状态时,不会消耗任何CPU时间。
当使用任务通知代替事件组时,使用接收任务的通知值代替事件组,接收任务的通知值中的比特位用作事件标志,并且使用xTaskNotifyWait() API函数代替事件组的xEventGroupWaitBits() API函数。
同样,使用xTaskNotify()和xTaskNotifyFromISR() API函数(其eAction参数设置为eSetBits)来代替xEventGroupSetBits()和xEventGroupSetBitsFromISR()函数。
与xEventGroupSetBitsFromISR()相比,xTaskNotifyFromISR()具有显著的性能优势,因为xTaskNotifyFromISR()完全在ISR中执行,而xEventGroupSetBitsFromISR()必须推迟一些处理到RTOS进程任务。
与使用事件组时不同,接收任务不能指定只在同时有多个比特位处于活动状态时才离开阻塞状态。相反,在任何比特位处于活动状态时,进程将解除阻塞,并且必须自己测试比特位的组合。

/* This example demonstrates a single RTOS task being used to process
events that originate from two separate interrupt service routines -
a transmit interrupt and a receive interrupt.  Many peripherals will
use the same handler for both, in which case the peripheral's
interrupt status register can simply be bitwise ORed with the
receiving task's notification value.

First bits are defined to represent each interrupt source. */
#define TX_BIT    0x01
#define RX_BIT    0x02

/* The handle of the task that will receive notifications from the
interrupts.  The handle was obtained when the task
was created. */
static TaskHandle_t xHandlingTask;

/*-----------------------------------------------------------*/

/* The implementation of the transmit interrupt service routine. */
void vTxISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

   /* Clear the interrupt source. */
   prvClearInterrupt();

   /* Notify the task that the transmission is complete by setting the TX_BIT
   in the task's notification value. */
   xTaskNotifyFromISR( xHandlingTask,
                       TX_BIT,
                       eSetBits,
                       &xHigherPriorityTaskWoken );

   /* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
   should be performed to ensure the interrupt returns directly to the highest
   priority task.  The macro used for this purpose is dependent on the port in
   use and may be called portEND_SWITCHING_ISR(). */
   portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

/* The implementation of the receive interrupt service routine is identical
except for the bit that gets set in the receiving task's notification value. */
void vRxISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

   /* Clear the interrupt source. */
   prvClearInterrupt();

   /* Notify the task that the reception is complete by setting the RX_BIT
   in the task's notification value. */
   xTaskNotifyFromISR( xHandlingTask,
                       RX_BIT,
                       eSetBits,
                       &xHigherPriorityTaskWoken );

   /* If xHigherPriorityTaskWoken is now set to pdTRUE then a context switch
   should be performed to ensure the interrupt returns directly to the highest
   priority task.  The macro used for this purpose is dependent on the port in
   use and may be called portEND_SWITCHING_ISR(). */
   portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

/* The implementation of the task that is notified by the interrupt service
routines. */
static void prvHandlingTask( void *pvParameter )
{
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 500 );
BaseType_t xResult;

   for( ;; )
   {
      /* Wait to be notified of an interrupt. */
      xResult = xTaskNotifyWait( pdFALSE,    /* Don't clear bits on entry. */
                           ULONG_MAX,        /* Clear all bits on exit. */
                           &ulNotifiedValue, /* Stores the notified value. */
                           xMaxBlockTime );

      if( xResult == pdPASS )
      {
         /* A notification was received.  See which bits were set. */
         if( ( ulNotifiedValue & TX_BIT ) != 0 )
         {
            /* The TX ISR has set a bit. */
            prvProcessTx();
         }

         if( ( ulNotifiedValue & RX_BIT ) != 0 )
         {
            /* The RX ISR has set a bit. */
            prvProcessRx();
         }
      }
      else
      {
         /* Did not receive a notification within the expected time. */
         prvCheckForErrors();
      }
   }
}


5-将 RTOS 任务通知用作轻量级邮箱

RTOS任务通知可用于向任务发送数据,但与RTOS队列相比,发送数据的方式要严格得多,因为:

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

你可能感兴趣的:(FreeRTOS基础知识篇,RTOS,OS,开发语言,STM32,FreeRTOS)