1. 程式人生 > >FreeRTOS(8)---FreeRTOS 任務通知

FreeRTOS(8)---FreeRTOS 任務通知

FreeRTOS 任務通知

FreeRTOS 任務通知

每個RTOS任務都有一個32位的通知值,任務建立時,這個值被初始化為0。RTOS任務通知相當於直接向任務傳送一個事件,接收到通知的任務可以解除阻塞狀態,前提是這個阻塞事件是因等待通知而引起的。傳送通知的同時,也可以可選的改變接收任務的通知值。

可以通過下列方法向接收任務更新通知:

  • 不覆蓋接收任務的通知值
  • 覆蓋接收任務的通知值
  • 設定接收任務通知值的某些位
  • 增加接收任務的通知值

相對於用前必須分別建立佇列、二進位制訊號量、計數訊號量或事件組的情況,使用任務通知顯然更靈活。更好的是,相比於使用訊號量解除任務阻塞,使用任務通知可以快45%、使用更少的RAM(使用GCC編譯器,-o2優化級別)。

使用API函式xTaskNotify()和xTaskNotifyGive()(中斷保護等價函式為xTaskNotifyFromISR()和vTaskNotifyGiveFromISR())傳送通知,在接收RTOS任務呼叫API函式xTaskNotifyWait()或ulTaskNotifyTake()之前,這個通知都被保持著。如果接收RTOS任務已經因為等待通知而進入阻塞狀態,則接收到通知後任務解除阻塞並清除通知。

RTOS任務通知功能預設是使能的,可以通過在檔案FreeRTOSConfig.h中設定巨集configUSE_TASK_NOTIFICATIONS為0來禁止這個功能,禁止後每個任務節省8位元組記憶體。

雖然RTOS任務通知速度更快並且佔用記憶體更少,但它也有一些限制:

  • 只能有一個任務接收通知事件。
  • 接收通知的任務可以因為等待通知而進入阻塞狀態,但是傳送通知的任務即便不能立即完成通知傳送也不能進入阻塞狀態。

傳送通知-方法1

函式描述

BaseType_t xTaskNotify( TaskHandle_txTaskToNotify, uint32_t ulValue, eNotifyAction eAction);      

向指定任務傳送指定的通知值。如果打算使用RTOS任務通知實現輕量級的二進位制或計數訊號量,推薦使用API函式xTaskNotifyGive()來代替本函式。

此函式不可以在中斷服務例程中呼叫,中斷保護等價函式為xTaskNotifyFromISR()。

引數描述

  • xTaskToNotify:被通知的任務控制代碼。
  • ulValue:通知更新值
  • eAction:列舉型別,指明更新通知值的方法

列舉變數成員以及作用如下表所示。

列舉成員 描述
eNoAction 傳送通知但不更新通知值,這意味著引數 ulValue 未使用
eSetBits 被通知任務的通知值按位或上 ulValue 。使用這種方法可以在某些場景下代替事件組,但執行速度更快
eIncrement 被通知任務的通知值增加1,這種情況下,引數ulValue未使用,API函式 xTaskNotifyGive() 跟這個等價。
eSetValueWithOverwrite 被通知任務的通知值設定為 ulValue。使用這種方法,可以在某些場景下代替 xQueueOverwrite() ,但執行速度更快。
eSetValueWithOverwrite 如果被通知任務當前沒有通知,則被通知任務的通知值設定為ulValue;如果被通知任務還沒取走上一個通知,又接收到了一個通知,則這次通知值丟棄,在這種情況下,函式xTaskNotify()呼叫失敗並返回pdFALSE。使用這種方法可以在某些場景長度為1的xQueueSend(),但速度更快。

返回值

引數eAction為eSetValueWithoutOverwrite時,如果被通知任務還沒取走上一個通知,又接收到了一個通知,則這次通知值未能更新並返回pdFALSE,否則返回pdPASS。

傳送通知-方法2

函式描述

BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify );      

其實這是一個巨集,本質上相當於xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement )。可以使用該API函式代替二進位制或計數訊號量,但速度更快。在這種情況下,應該使用API函式ulTaskNotifyTake()來等待通知,而不應該使用API函式xTaskNotifyWait()。

此函式不可以在中斷服務例程中呼叫,中斷保護等價函式為vTaskNotifyGiveFromISR()。

引數描述

  • xTaskToNotify:被通知的任務控制代碼。

用法舉例

staticvoid prvTask1( void *pvParameters );
staticvoid prvTask2( void *pvParameters );
 
/* 儲存任務控制代碼 */
staticTaskHandle_t xTask1 = NULL, xTask2 = NULL;
 
/* 建立兩個任務,它們之間反覆傳送通知,啟動RTOS排程器*/
voidmain( void )
{
    xTaskCreate( prvTask1, "Task1",200, NULL, tskIDLE_PRIORITY, &xTask1 );
    xTaskCreate( prvTask2, "Task2",200, NULL, tskIDLE_PRIORITY, &xTask2 );
    vTaskStartScheduler();
}
/*-----------------------------------------------------------*/
 
staticvoid prvTask1( void *pvParameters )
{
    for( ;; )
    {
        /*向prvTask2(),傳送通知,使其解除阻塞狀態 */
        xTaskNotifyGive( xTask2 );
 
        /* 等待prvTask2()的通知,進入阻塞 */
        ulTaskNotifyTake( pdTRUE, portMAX_DELAY);
    }
}
/*-----------------------------------------------------------*/
 
staticvoid prvTask2( void *pvParameters )
{
    for( ;; )
    {
        /* 等待prvTask1()的通知,進入阻塞 */
        ulTaskNotifyTake( pdTRUE, portMAX_DELAY);
 
        /*向prvTask1(),傳送通知,使其解除阻塞狀態 */
        xTaskNotifyGive( xTask1 );
    }
}

獲取通知

函式描述

uint32_t ulTaskNotifyTake( BaseType_txClearCountOnExit, TickType_txTicksToWait );     

ulTaskNotifyTake()是專門為使用更輕量級更快的方法來代替二進位制或計數訊號量而量身打造的。FreeRTOS獲取訊號量的API函式為xSemaphoreTake(),可以使用ulTaskNotifyTake()函式等價代替。

當一個任務使用通知值來實現二進位制或計數訊號量時,其它任務或者中斷要使用API函式xTaskNotifyGive()或者使用引數eAction為eIncrement的API函式xTaskNotify()。推薦使用xTaskNotifyGive()函式(其實是個巨集,我們這裡把它看作一個API函式)。另外需要注意的是,如果在中斷中使用,要使用它們的中斷保護等價函式:vTaskNotifyGiveFromISR()和xTaskNotifyFromISR()。

API函式xTaskNotifyTake()有兩種方法處理任務的通知值,一種方法是在函式退出時將通知值清零,這種方法適用於實現二進位制訊號量;另外一種方法是在函式退出時將通知值減1,這種方法適用於實現計數訊號量。

如果RTOS任務的通知值為0,使用xTaskNotifyTake()可以可選的使任務進入阻塞狀態,直到該任務的通知值不為0。進入阻塞的任務不消耗CPU時間。

引數描述

  • xClearCountOnExit:如果該引數設定為pdFALSE,則API函式xTaskNotifyTake()退出前,將任務的通知值減1;如果該引數設定為pdTRUE,則API函式xTaskNotifyTake()退出前,將任務通知值清零。
  • xTicksToWait:因等待通知而進入阻塞狀態的最大時間。時間單位為系統節拍週期。巨集pdMS_TO_TICKS用於將指定的毫秒時間轉化為相應的系統節拍數。

返回值

返回任務的當前通知值,為0或者為呼叫API函式xTaskNotifyTake()之前的通知值減1。

用法舉例

/* 中斷處理程式。*/
voidvANInterruptHandler( void )
{
BaseType_txHigherPriorityTaskWoken;
 
    prvClearInterruptSource();
 
    /* xHigherPriorityTaskWoken必須被初始化為pdFALSE。如果呼叫vTaskNotifyGiveFromISR()會解除vHandlingTask任務的阻塞狀態,並且vHandlingTask任務的優先順序高於當前處於執行狀態的任務,則xHigherPriorityTaskWoken將會自動被設定為pdTRUE。*/
    xHigherPriorityTaskWoken = pdFALSE;
 
    /*向一個任務傳送通知,xHandlingTask是該任務的控制代碼。*/
    vTaskNotifyGiveFromISR( xHandlingTask,&xHigherPriorityTaskWoken );
 
    /* 如果xHigherPriorityTaskWoken為pdTRUE,則強制上下文切換。這個巨集的實現取決於移植層,可能會呼叫portEND_SWITCHING_ISR */
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken );
}
/*---------------------------------------------------------------------------------------------------*/
 
/* 一個因為等待通知而阻塞的任務。*/
voidvHandlingTask( void *pvParameters )
{
BaseType_txEvent;
 
    for( ;; )
    {
        /*等待通知,無限期阻塞。引數pdTRUE表示函式退出前會清零通知值。這顯然是用於替代二進位制訊號量的用法。需要注意的是,真實的程式一般不會無限期阻塞。*/
        ulTaskNotifyTake( pdTRUE, portMAX_DELAY);
 
        /* 當處理完所有事件後,仍然等待下一個通知*/
        do
        {
            xEvent = xQueryPeripheral();
 
            if( xEvent != NO_MORE_EVENTS )
            {
                vProcessPeripheralEvent( xEvent);
            }
 
        } while( xEvent != NO_MORE_EVENTS );
    }
}

等待通知

函式描述

BaseType_t xTaskNotifyWait( uint32_tulBitsToClearOnEntry, uint32_tulBitsToClearOnExit,  uint32_t*pulNotificationValue, TickType_txTicksToWait );      

如果打算使用RTOS任務通知實現輕量級的二進位制或計數訊號量,推薦使用API函式ulTaskNotifyTake()來代替本函式。

引數描述

  • ulBitsToClearOnEntry:在使用通知之前,先將任務的通知值與引數ulBitsToClearOnEntry的按位取反值按位與操作。設定引數ulBitsToClearOnEntry為0xFFFFFFFF(ULONG_MAX),表示清零任務通知值。
  • ulBitsToClearOnExit:在函式xTaskNotifyWait()退出前,將任務的通知值與引數ulBitsToClearOnExit的按位取反值按位與操作。設定引數ulBitsToClearOnExit為0xFFFFFFFF(ULONG_MAX),表示清零任務通知值。
  • pulNotificationValue:用於向外回傳任務的通知值。這個通知值在引數ulBitsToClearOnExit起作用前將通知值拷貝到*pulNotificationValue中。如果不需要返回任務的通知值,這裡設定成NULL。
  • xTicksToWait:因等待通知而進入阻塞狀態的最大時間。時間單位為系統節拍週期。巨集pdMS_TO_TICKS用於將指定的毫秒時間轉化為相應的系統節拍數。

返回值

如果接收到通知,返回pdTRUE,如果API函式xTaskNotifyWait()等待超時,返回pdFALSE。

用法舉例

/*這個任務使用任務通知值的位來傳遞不同的事件,這在某些情況下可以代替事件組。*/
voidvAnEventProcessingTask( void *pvParameters )
{
uint32_tulNotifiedValue;
 
    for( ;; )
    {
        /*等待通知,無限期阻塞(沒有超時,所以不用檢查函式返回值)。其它任務或者中斷設定的通知值中的不同位表示不同的事件。引數0x00表示使用通知前不清除任務的通知值位,引數ULONG_MAX 表示函式xTaskNotifyWait()退出前將任務通知值設定為0*/
        xTaskNotifyWait( 0x00, ULONG_MAX,&ulNotifiedValue, portMAX_DELAY );
 
        /*根據通知值處理事件*/
        if( ( ulNotifiedValue & 0x01 ) != 0)
        {
            prvProcessBit0Event();
        }
 
        if( ( ulNotifiedValue & 0x02 ) != 0)
        {
            prvProcessBit1Event();
        }
 
        if( ( ulNotifiedValue & 0x04 ) != 0)
        {
            prvProcessBit2Event();
        }
 
        /* ……*/
    }
}

任務通知並查詢

函式描述

BaseType_t xTaskNotifyAndQuery(TaskHandle_t xTaskToNotify, uint32_tulValue, eNotifyActioneAction, uint32_t*pulPreviousNotifyValue );      

此函式與任務通知API函式xTaskNotify()非常像,只不過此函式具有一個附加引數,用來回傳任務當前的通知值,然後根據引數ulValue和eAction更新任務的通知值。

此函式不能在中斷服務例程中使用,在中斷服務例程中使用xTaskNotifyAndQueryFromISR()函式。

引數描述

  • xTaskToNotify:被通知的任務控制代碼。
  • ulValue:通知更新值
  • eAction:列舉型別,指明更新通知值的方法,列舉變數成員以及作用見xTaskNotify()一節。
  • pulPreviousNotifyValue:回傳未被更新的任務通知值。如果不需要回傳未被更新的任務通知值,這裡設定為NULL,這樣就等價於呼叫xTaskNotify()函式。

返回值

引數eAction為eSetValueWithoutOverwrite時,如果被通知任務還沒取走上一個通知,又接收到了一個通知,則這次通知值未能更新並返回pdFALSE,否則返回pdPASS。