在上一篇我們介紹了FreeRTOS任務的一些基本操作和功能,今天我們會介紹一個很好很強大的功能——任務通知
任務通知可以在不同任務之間傳遞信息,它可以取代二值信號量、計數信號量、事件標志組、深度為1的消息隊列等功能,因為它更快,占用RAM更少,是FreeRTOS自8.2以來推出的重大改進功能。
一、任務通知相關變量
1.1、TCB中的通知相關成員
FreeRTOS每個任務都有一個通知指示值,和一個32位通知值:
任務數據結構(TCB)中與隊列通知相關的成員
#if ( configUSE_TASK_NOTIFICATIONS == 1 ) volatile uint32_t ulNotifiedValue; volatile eNotifyValue eNotifyState; #endif
- ulNotifiedValue就是任務中的通知值,任務通知實際上就是圍繞這個變量作文章,下面會以“通知值”來代替這個成員變量,
- eNotifyState用來標志任務是否在等待通知,它有以下3種情況
eNotified | 任務已經被通知 | 帶發送通知功能的函數都會首先把eNotifyState設置為eNotified,表示任務已經被通知 |
eWaitingNotification | 任務正在等待通知 | 接收通知功能的函數會首先把eNotifyState設置為eWaitingNotification,表示任務已經阻塞了正在等待通知 |
eNotWaitingNotification | 空狀態 | 表示任務即沒有收到新的通知,也沒有正在等待通知,接收通知功能函數在接收到通知處理后,會把eNotifyState設置為eWaitingNotification |
根據上一節中的TCB我們的精簡,我們現在為TCB補上接下來會用到新的成員:
typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /*任務堆棧棧頂*/ ListItem_t xGenericListItem; /*任務狀態列表項項引用的列表,指示任務狀態(准備態、阻塞態、掛起態)*/ ListItem_t xEventListItem; /*狀態列表項*/ UBaseType_t uxPriority; /*任務優先級*/ StackType_t *pxStack; /*任務堆棧起始地址*/ char pcTaskName[ configMAX_TASK_NAME_LEN ];/*任務名字*/ volatile uint32_t ulNotifiedValue; /*任務通知值*/ volatile eNotifyValue eNotifyState; /*通知狀態標志*/ } tskTCB;
二、任務通知API函數
2.1、發送通知
xTaskNotifyGive() | 發送通知,通知值設定為自增方式(每次調用通知值加1) |
vTaskNotifyGiveFromISR() | 上面函數的中斷版本 |
xTaskNotify() | 發送通知,可以自定義通知方式 |
xTaskNotifyFromISR() | 上面函數的中斷版本 |
xTaskNotifyAndQuery() | 發送通知,自定義通知方式,並且讀出上一次的通知值 |
xTaskNotifyAndQueryFromISR() | 上面函數的中斷版本 |
其中有5個API是宏,它們關系如下:
非中斷版本:
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) | xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL ) |
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) | xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) ) |
#define xTaskNotifyGive( xTaskToNotify ) | xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL ) |
中斷版本:
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) | xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) ) |
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) | xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) ) |
可以發現,實際上他們底層只有兩個函數,
-
- xTaskGenericNotify()
- xTaskGenericNotifyFromISR()
再加上一個直接指向頂層的函數
-
- vTaskNotifyGiveFromISR()
接下來我們直接看這3個函數,會更便於理解隊列通知以及使用
2.1.1生成通知信號函數xTaskGenericNotify():
函數原型:
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
輸入參數:
xTaskToNotify | 被通知的任務的句柄 |
ulValue | 輸入值 |
eAction | 進行通知方式 |
*pulPreviousNotificationValue | 這個任務上一次的通知值 |
其中參數eAction最為重要,它決定了我們以什么方式發送通知,我們查看它是一個eNotifyAction類型的結構體,查看這個結構體,可以留意到發送通知的方式分為以下5種:
typedef enum { eNoAction = 0, eSetBits, eIncrement, eSetValueWithOverwrite, eSetValueWithoutOverwrite } eNotifyAction;
現在我們來截取xTaskGenericNotifyFromISR()一段操作通知值的代碼,看看這5種信號是怎么改變通知值的:
switch( eAction ) { case eSetBits : pxTCB->ulNotifiedValue |= ulValue; break; case eIncrement : ( pxTCB->ulNotifiedValue )++; break; case eSetValueWithOverwrite : pxTCB->ulNotifiedValue = ulValue; break; case eSetValueWithoutOverwrite : if( eOriginalNotifyState != eNotified ) { pxTCB->ulNotifiedValue = 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; }
源碼分析(展開折疊查看)
這個函數主要做了3件事:
- 將TCB中的通知狀態標志eNotifyState設置為已經收到通知的狀態
- 根據需求更新TCB中的通知值ulNotifiedValue
- 解除目標任務的阻塞狀態

#if( configUSE_TASK_NOTIFICATIONS == 1 ) BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue ) { /* xTaskToNotify:被通知的任務句柄 ulValue: 更新的通知值 eAction: 枚舉類型,指明更新通知值的方法, *pulPreviousNotificationValue:用於獲取上次的通知值 */ TCB_t * pxTCB; eNotifyValue eOriginalNotifyState; BaseType_t xReturn = pdPASS; configASSERT( xTaskToNotify ); pxTCB = ( TCB_t * ) xTaskToNotify; taskENTER_CRITICAL(); { if( pulPreviousNotificationValue != NULL ) { *pulPreviousNotificationValue = pxTCB->ulNotifiedValue; } /*獲取上一次的通知狀態,保存在局部變量eOriginalNotifyState中,稍后覆蓋方法會用到*/ eOriginalNotifyState = pxTCB->eNotifyState; /*更新TCB的通知狀態為eNotified(已通知狀態)*/ /*【1】*/ pxTCB->eNotifyState = eNotified; /*更新TCB的通知值(pxTCB->ulNotifiedValue),根據eAction的不同,達到傳輸不同種類通知的目的*/ /*【2】*/ switch( eAction ) { case eSetBits : pxTCB->ulNotifiedValue |= ulValue; break; case eIncrement : ( pxTCB->ulNotifiedValue )++; break; case eSetValueWithOverwrite : pxTCB->ulNotifiedValue = ulValue; break; case eSetValueWithoutOverwrite : if( eOriginalNotifyState != eNotified ) { pxTCB->ulNotifiedValue = 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; } traceTASK_NOTIFY(); /* If the task is in the blocked state specifically to wait for a notification then unblock it now. */ /* 如果目標任務是阻塞狀態,特別是如果在等待通知,那么解除阻塞 */ if( eOriginalNotifyState == eWaitingNotification ) /*【3】*/ { ( void ) uxListRemove( &( pxTCB->xGenericListItem ) ); prvAddTaskToReadyList( pxTCB ); /* The task should not have been on an event list. */ /* 任務不應該已經添加進事件列表 */ 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. */ /* 如果一個任務阻塞等待通知,那么xNextTaskUnblockTime應該設置為阻塞任務時間外的時間。 如果任務因為一些原因(除了一個超時)被解除阻塞,xNextTaskUnblockTime通常保持不變。 因為當滴答計數器等於xNextTaskUnblockTime的時候,它會被重置為一個新的值。 無論如何,如果tickless idling 被使用,它可能是首先進入睡眠模式, 所以在這里重置xNextTaskUnblockTime來確保它被更新*/ 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 */
2.1.2、生成通知信號函數中斷版本 xTaskGenericNotifyFromISR()
函數原型:
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken )
作為xTaskGenericNotify()的中斷版本,xTaskGenericNotifyFromISR()中加了一些中段相關處理,我們可以看到,他們輸入參數都相同(增加一個pxHigherPriorityTaskWoken參數用於指示執行期間是否有任務切換發生)。
所以這個函數實際上和xTaskGenericNotify()的操作相同。
- 將TCB中的通知狀態標志eNotifyState設置為已經收到通知的狀態
- 根據需求更新TCB中的通知值ulNotifiedValue
- 解除目標任務的阻塞狀態
2.1.3、一個自增通知的中斷定制版 vTaskNotifyGiveFromISR():
函數原型:
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )
這個函數是xTaskGenericNotifyFromISR()的壓縮定制版,去掉了3個輸入參數ulValue、eAction、pulPreviousNotificationValue和對應這3個參數相關的處理程序,因為它是特別為自增型通知定制的,與xTaskGenericNotifyFromISR()這個函數提高了效率,大概FreeRTOS作者認為自增通知是常用的通知類型,所以特意寫了這個優化版本的函數。
所以這個函數實際上也和xTaskGenericNotify()的操作相同
源碼分析:
- 將TCB中的通知狀態標志eNotifyState設置為已經收到通知的狀態
- 根據需求更新TCB中的通知值ulNotifiedValue(自增)
- 解除目標任務的阻塞狀態

#if( configUSE_TASK_NOTIFICATIONS == 1 ) void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken ) { TCB_t * pxTCB; eNotifyValue eOriginalNotifyState; UBaseType_t uxSavedInterruptStatus; configASSERT( xTaskToNotify ); /* RTOS ports that support interrupt nesting have the concept of a maximum system call (or maximum API call) interrupt priority. Interrupts that are above the maximum system call priority are keep permanently enabled, even when the RTOS kernel is in a critical section, but cannot make any calls to FreeRTOS API functions. If configASSERT() is defined in FreeRTOSConfig.h then portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion failure if a FreeRTOS API function is called from an interrupt that has been assigned a priority above the configured maximum system call priority. Only FreeRTOS functions that end in FromISR can be called from interrupts that have been assigned a priority at or (logically) below the maximum system call interrupt priority. FreeRTOS maintains a separate interrupt safe API to ensure interrupt entry is as fast and as simple as possible. More information (albeit Cortex-M specific) is provided on the following link: http://www.freertos.org/RTOS-Cortex-M3-M4.html */ portASSERT_IF_INTERRUPT_PRIORITY_INVALID(); pxTCB = ( TCB_t * ) xTaskToNotify; uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); { eOriginalNotifyState = pxTCB->eNotifyState; pxTCB->eNotifyState = eNotified; /*【1】*/ /* 'Giving' is equivalent to incrementing a count in a counting semaphore. */ /* 給予是等價於在計數信號量中增加一個計數 */ ( pxTCB->ulNotifiedValue )++; /*【2】*/ traceTASK_NOTIFY_GIVE_FROM_ISR(); /* If the task is in the blocked state specifically to wait for a notification then unblock it now. */ /* 如果任務在阻塞狀態,明確地等待一個通知,然后馬上解除阻塞*/ if( eOriginalNotifyState == eWaitingNotification ) /*【3】*/ { /* The task should not have been on an event list. */ /* 任務不應該已經加入事件列表了 */ configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL ); if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) { ( void ) uxListRemove( &( pxTCB->xGenericListItem ) ); prvAddTaskToReadyList( pxTCB ); } else { /* The delayed and ready lists cannot be accessed, so hold this task pending until the scheduler is resumed. */ /* 延時和准備列表無法被訪問,所以保持這個任務掛起,知道調度器被恢復 */ vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); } if( pxTCB->uxPriority > pxCurrentTCB->uxPriority ) { /* The notified task has a priority above the currently executing task so a yield is required. */ /* 通知任務已經比當前執行任務更高,所以需要進行切換 */ if( pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken = pdTRUE; } } else { mtCOVERAGE_TEST_MARKER(); } } } portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); } #endif /* configUSE_TASK_NOTIFICATIONS */
接下來我們回到API函數,看看這6個API的功能,以及它們調用的是哪個底層函數:
2.2接收通知
ulTaskNotifyTake() | 提取通知 | 適用於二值通知(eNoAction)和自增通知 |
xTaskNotifyWait() | 等待通知 | 適用所有通知,但不附帶自增通知的自減功能 |
2.2.1、ulTaskNotifyTake()
函數原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
輸入參數:
BaseType_t xClearCountOnExit | 退出時是否清除 |
TickType_t xTicksToWait | 最大等待時間 |
- xClearCountOnExit:這個參數可以傳入pdTRUE或者pdFALSE
- xTicksToWait:在等待通知的時候,任務會進入阻塞狀態,任務進入阻塞狀態的最大時間(以Tick為單位,可以用pdMS_TO_TICKS()將毫秒換為tick)
源碼分析:(展開折疊查看)
這個函數主要做了4件事:
- 改變TCB中的eNotifyState為正在等待通知狀態
- 讓任務進入阻塞或掛起狀態等待通知
- 收到通知后,對通知值ulNotifiedValue進行操作(刪除或自減)
- 改變TCB中的eNotifyState為空狀態,因為讀取通知的操作已經完成了

#if( configUSE_TASK_NOTIFICATIONS == 1 ) uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) { TickType_t xTimeToWake; uint32_t ulReturn; taskENTER_CRITICAL(); { /* Only block if the notification count is not already non-zero. */ /* 僅僅當通知值為0的時候, 才進行阻塞操作 */ if( pxCurrentTCB->ulNotifiedValue == 0UL ) { /* Mark this task as waiting for a notification. */ /* 屏蔽這個任務來等待通知 */ pxCurrentTCB->eNotifyState = eWaitingNotification; /*改變TCB中的eNotifyState 為 eWaitingNotification*/ /*【1】*/ if( xTicksToWait > ( TickType_t ) 0 ) { /* The task is going to block. First it must be removed from the ready list. */ /* 任務將會阻塞。 但首先必須從准備列表移除 */ if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 ) { /* The current task must be in a ready list, so there is no need to check, and the port reset macro can be called directly. */ /* 當前任務必須在准備列表,所以沒有必要再檢查,接口重置宏可以被直接調用 */ portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); } else { mtCOVERAGE_TEST_MARKER(); } #if ( INCLUDE_vTaskSuspend == 1 ) /*【2】*/ { /* 如果設置了 xTicksToWait 為 portMAX_DELAY。任務會直接掛起*/ if( xTicksToWait == portMAX_DELAY ) { /* Add the task to the suspended task list instead of a delayed task list to ensure the task is not woken by a timing event. It will block indefinitely. */ /* 把任務添加到掛起任務列表,而不是延時任務列表(阻塞狀態會倒計時)。這是為了確認任務沒有被時間事件喚醒。任務會被無限期的阻塞(直接掛起) */ vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) ); } else { /* Calculate the time at which the task should be woken if no notification events occur. This may overflow but this doesn't matter, the scheduler will handle it. */ /* 如果沒有通知事件發生,計算任務應該被喚醒的時間 這個可能溢出,但是沒有關系,調度器會處理它的*/ xTimeToWake = xTickCount + xTicksToWait; /*計算喚醒時間*/ prvAddCurrentTaskToDelayedList( xTimeToWake ); /*把計算好的時間添加到延時列表。交給調度器處理*/ } } #else /* INCLUDE_vTaskSuspend */ { /* Calculate the time at which the task should be woken if the event does not occur. This may overflow but this doesn't matter, the scheduler will handle it. */ /* 如果沒有通知事件發生,計算任務應該被喚醒的時間 這個可能溢出,但是沒有關系,調度器會處理它的*/ xTimeToWake = xTickCount + xTicksToWait; prvAddCurrentTaskToDelayedList( xTimeToWake ); } #endif /* INCLUDE_vTaskSuspend */ traceTASK_NOTIFY_TAKE_BLOCK(); /* All ports are written to allow a yield in a critical section (some will yield immediately, others wait until the critical section exits) - but it is not something that application code should ever do. */ /* 在臨界區,所有接口被寫入,來立刻允許一次切換(有一些會馬上切換,其他會等待知道重要部分退出) , 但它不是一些應用代碼應該做的重要的事*/ portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } taskEXIT_CRITICAL(); taskENTER_CRITICAL(); { traceTASK_NOTIFY_TAKE(); ulReturn = pxCurrentTCB->ulNotifiedValue;/*設置返回值為收到的通知值*/ if( ulReturn != 0UL ) /*【3】*/ { if( xClearCountOnExit != pdFALSE ) { pxCurrentTCB->ulNotifiedValue = 0UL;/*清零通知值*/ } else { ( pxCurrentTCB->ulNotifiedValue )--;/*減少通知值(對自增通知的特殊處理方法)*/ } } else { mtCOVERAGE_TEST_MARKER(); } pxCurrentTCB->eNotifyState = eNotWaitingNotification;/* 清除等待/接收通知狀態 */ /*【4】*/ } taskEXIT_CRITICAL(); return ulReturn; } #endif /* configUSE_TASK_NOTIFICATIONS */
*在我們查看源碼的時候,我們可以留意到,當我們設置輸入參數為xTicksToWait為portMAX_DELAY的時候,而且INCLUDE_vTaskSuspend(激活掛起狀態)宏定義為1的時候任務不是阻塞,而是直接掛起。

#if ( INCLUDE_vTaskSuspend == 1 ) /*【2】*/ { /* 如果設置了 xTicksToWait 為 portMAX_DELAY。任務會直接掛起*/ if( xTicksToWait == portMAX_DELAY ) { /* Add the task to the suspended task list instead of a delayed task list to ensure the task is not woken by a timing event. It will block indefinitely. */ /* 把任務添加到掛起任務列表,而不是延時任務列表(阻塞狀態會倒計時)。這是為了確認任務沒有被時間事件喚醒。任務會被無限期的阻塞(直接掛起) */ vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) ); } else { /* Calculate the time at which the task should be woken if no notification events occur. This may overflow but this doesn't matter, the scheduler will handle it. */ /* 如果沒有通知事件發生,計算任務應該被喚醒的時間 這個可能溢出,但是沒有關系,調度器會處理它的*/ xTimeToWake = xTickCount + xTicksToWait; /*計算喚醒時間*/ prvAddCurrentTaskToDelayedList( xTimeToWake ); /*把計算好的時間添加到延時列表。交給調度器處理*/ } }
官方例子:
2.2.2、xTaskNotifyWait()
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
輸入參數:
- ulBitsToClearOnEntry:在進入阻塞之前把指定的比特清除
- ulBitsToClearOnExit:在接收到通知並處理后把指定的比特位清除
- *pulNotificationValue:用於儲存新到的通知值
- xTicksToWait:最大阻塞時間
返回值:
兩種情況
源碼分析:(展開折疊查看)
這個函數和ulTaskNotifyTake很像,主要做了5件事:
- 根據ulBitsToClearOnEntry先清除一下通知值相應的位
- 改變TCB中的eNotifyState為正在等待通知狀態
- 讓任務進入阻塞或掛起狀態等待通知
- 收到通知后,對通知值ulNotifiedValue進行操作(根據ulBitsToClearOnExit清除相應位)
- 改變TCB中的eNotifyState為空狀態,因為讀取通知的操作已經完成了

#if( configUSE_TASK_NOTIFICATIONS == 1 ) BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ) { TickType_t xTimeToWake; BaseType_t xReturn; /* */ taskENTER_CRITICAL(); { /* Only block if a notification is not already pending. */ /* 只有沒收到通知,才會阻塞任務(換句話說,如果現在已經收到通知了就不需要阻塞任務了,直接處理就好了) */ if( pxCurrentTCB->eNotifyState != eNotified ) { /* Clear bits in the task's notification value as bits may get set by the notifying task or interrupt. This can be used to clear the value to zero. */ /* 清除任務通知值的比特位,因為這些比特肯能被通知任務或者中斷置位了。 這個可以用於把值清0*/ pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry; /*【1】*/ /* Mark this task as waiting for a notification. */ /* 記錄這個任務為等待通知的狀態*/ pxCurrentTCB->eNotifyState = eWaitingNotification; /*【2】*/ if( xTicksToWait > ( TickType_t ) 0 ) { /* The task is going to block. First it must be removed from the ready list. */ /* 任務馬上就要阻塞了,首先要把它移出准備列表 */ if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 ) { /* The current task must be in a ready list, so there is no need to check, and the port reset macro can be called directly. */ /* 當前任務肯定在准備列表中 ,所以沒有必要檢查了,接口重置宏可以被直接調用 */ portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); } else { mtCOVERAGE_TEST_MARKER(); } #if ( INCLUDE_vTaskSuspend == 1 ) /*【3】*/ { if( xTicksToWait == portMAX_DELAY ) { /* Add the task to the suspended task list instead of a delayed task list to ensure the task is not woken by a timing event. It will block indefinitely. */ /* 把任務加入掛起任務列表,而不是延時任務列表。這是為了確保任務沒有被時間事件喚醒 這個任務會無限阻塞 */ vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) ); } else { /* Calculate the time at which the task should be woken if no notification events occur. This may overflow but this doesn't matter, the scheduler will handle it. */ /* 計算在如果沒有通知事件,發生任務應該被喚醒的時間 。 這個可能會導致溢出,但是沒有關系。調度器會處理它的*/ xTimeToWake = xTickCount + xTicksToWait; prvAddCurrentTaskToDelayedList( xTimeToWake ); } } #else /* INCLUDE_vTaskSuspend */ { /* Calculate the time at which the task should be woken if the event does not occur. This may overflow but this doesn't matter, the scheduler will handle it. */ /* 計算在如果沒有通知事件,發生任務應該被喚醒的時間 。 這個可能會導致溢出,但是沒有關系。調度器會處理它的*/ xTimeToWake = xTickCount + xTicksToWait; prvAddCurrentTaskToDelayedList( xTimeToWake ); } #endif /* INCLUDE_vTaskSuspend */ traceTASK_NOTIFY_WAIT_BLOCK(); /* All ports are written to allow a yield in a critical section (some will yield immediately, others wait until the critical section exits) - but it is not something that application code should ever do. */ /* 寫入所有接口,允許在臨界區進行一次切換*/ portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } taskEXIT_CRITICAL(); taskENTER_CRITICAL(); { traceTASK_NOTIFY_WAIT(); if( pulNotificationValue != NULL ) { /* Output the current notification value, which may or may not have changed. */ /* 設置pulNotificationValue參數為當前通知值,不管它有沒有改變*/ *pulNotificationValue = pxCurrentTCB->ulNotifiedValue; } /* If eNotifyValue is set then either the task never entered the blocked state (because a notification was already pending) or the task unblocked because of a notification. Otherwise the task unblocked because of a timeout. */ /* 如果通知值*/ if( pxCurrentTCB->eNotifyState == eWaitingNotification ) { /* 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 &= ~ulBitsToClearOnExit; /*【4】*/ xReturn = pdTRUE; } /*接收通知完成,修改TCB的eNotifyState為空狀態*/ pxCurrentTCB->eNotifyState = eNotWaitingNotification; /*【5】*/ } taskEXIT_CRITICAL(); return xReturn; } #endif /* configUSE_TASK_NOTIFICATIONS */
官方使用例子:
這個例子是使用通知代替事件組,也就是置位通知
- 等待通知信號,任務會進入阻塞/掛起狀態。
- 判斷通知信號的第幾位被置位了
3、最后筆者寫了四個例子,提供大家參考
使用不帶數據的二值任務通知(可以代替二進制信號量)

//Notify is used for binary semaphore void vDemoTaskA(void *Parammenters) { for(;;) { //xTaskNotifyGive( HandleOfTaskB ); xTaskNotify( HandleOfTaskB, 0, eNoAction ); vTaskDelay( pdMS_TO_TICKS(5) ); } } void vDemoTaskB(void *Parammenters) { for(;;) { ulTaskNotifyTake( pdTRUE , portMAX_DELAY ); //add your codes here } } void vTaskCreate(void ) { xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL ); xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); } /******************************************************************************* * Function Name : main * Description : * Input : None * Output : None * Return : None *******************************************************************************/ int main( void ) { vTaskCreate(); vTaskStartScheduler( ); while(1) { //should not be here } }
使用自增的任務通知(可以代替計數信號量)

//Notify is used for counting semaphore void vDemoTaskA(void *Parammenters) { for(;;) { xTaskNotifyGive( HandleOfTaskB ); xTaskNotifyGive( HandleOfTaskB ); xTaskNotifyGive( HandleOfTaskB ); vTaskDelay( pdMS_TO_TICKS(5) ); } } void vDemoTaskB(void *Parammenters) { uint32_t notifyCNT; for(;;) { if( notifyCNT = ulTaskNotifyTake( pdFALSE , portMAX_DELAY ) ) { //should come in here for three times each turn //add your codes here } } } void vTaskCreate(void ) { xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL ); xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); } /******************************************************************************* * Function Name : main * Description : * Input : None * Output : None * Return : None *******************************************************************************/ int main( void ) { vTaskCreate(); vTaskStartScheduler( ); while(1) { //should not be here } }
使用置位任務通知(可以代替事件標志組)

//Notify is used for event group void vDemoTaskA(void *Parammenters) { for(;;) { xTaskNotify( HandleOfTaskB, (uint32_t)1 , eSetBits ); xTaskNotify( HandleOfTaskB, (uint32_t)1<<1 , eSetBits ); xTaskNotify( HandleOfTaskB, (uint32_t)1<<3 , eSetBits ); vTaskDelay( pdMS_TO_TICKS(5) ); } } void vDemoTaskB(void *Parammenters) { #define ULONG_MAX 0xffffffff uint32_t ulNotifiedValue=0; for(;;) { xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY ); if( ulNotifiedValue & (1) ) { //add your codes here } if( ulNotifiedValue & (1<<1) ) { //add your codes here } if( ulNotifiedValue & (1<<2) ) { //add your codes here } if( ulNotifiedValue & (1<<3) ) { //add your codes here } //you can add more events below if you need them } } void vTaskCreate(void ) { xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL ); xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); } /******************************************************************************* * Function Name : main * Description : * Input : None * Output : None * Return : None *******************************************************************************/ int main( void ) { vTaskCreate(); vTaskStartScheduler( ); while(1) { //should not be here } }
使用傳遞消息任務通知(可以代替深度為一的消息隊列)

//Notify is used for sending message void vDemoTaskA(void *Parammenters) { uint32_t pTestBuff[5] = { 0,1,2,3,4 }; int i; for(;;) { for(i=0;i<5;i++) { //xTaskNotify( HandleOfTaskB, pTestBuff[i] , eSetValueWithOverwrite ); //The notify can not be overwrite xTaskNotify( HandleOfTaskB, pTestBuff[i] , eSetValueWithoutOverwrite );//The notify can be overwrite vTaskDelay( pdMS_TO_TICKS(5) ); } } } void vDemoTaskB(void *Parammenters) { #define ULONG_MAX 0xffffffff uint32_t ulNotifiedValue=0; uint32_t pRecBuff[5]={0}; int i; for(;;) { for(i=0;i<5;i++) { xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY ); pRecBuff[i] = ulNotifiedValue; //add your codes here } } } void vTaskCreate(void ) { xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL ); xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); } /******************************************************************************* * Function Name : main * Description : * Input : None * Output : None * Return : None *******************************************************************************/ int main( void ) { vTaskCreate(); vTaskStartScheduler( ); while(1) { //should not be here } }