在之前的文章中已經講解了很多種用于任務件通信的機制,包括隊列、事件組和各種不同類型的信号量。使用這些機制都需要建立一個通信對象。
事件和資料不會直接發送到接收任務或接收ISR,而是發送到通信對象(也就是發送到隊列、事件組、信号量)。同樣,任務和ISR從通信對象接收事件和資料,而不是直接從發送事件或資料的任務或ISR接收事件和資料。
任務通知允許任務與其他任務互動,并與ISR同步,而不需要單獨的通信對象。通過使用任務通知,任務或ISR可以直接向接收任務發送事件。
1.任務通知的優缺點
1.1優點
1.更快,使用任務通知将事件或資料發送到任務要比使用隊列、信号量或事件組更快。
2.更節約記憶體,使用任務通知将事件或資料發送到任務所需的RAM比使用隊列、信号量或事件組執行等效操作所需的RAM少得多。因為通信對象(隊列、信号量或事件組),要先建立,才能使用,而啟用任務通知功能每個任務隻有8位元組的RAM的固定開銷。
1.2缺點
部分情況無法使用
(1)向ISR發送事件或資料
通信對象可用于将事件和資料從ISR發送到任務,并從任務發送到ISR。任務通知可用于将事件和資料從ISR發送到任務,但是它們不能用于将事件或資料從任務發送到ISR。
(2)有多個接收任務
通信對象可以被任何知道其句柄的任務或ISR通路。任意數量的任務和ISR都可以接收通信對象。任務通知直接發送到接收任務,是以隻能由接收通知的任務處理。
(3)緩沖多個資料項
隊列是一種通信對象,一次可以儲存多個資料項。已發送到隊列但尚未從隊列接收的資料将在隊列對象中進行緩沖。任務通知通過更新接收任務的通知值來向任務發送資料。任務的通知值一次隻能儲存一個值。
(3)發送到多個任務
事件組是一個通信對象,可用于一次向多個任務發送事件。任務通知直接發送給接收任務,是以隻能由接收任務處理。
(3)在阻塞态下等待發送完成
如果一個通信對象暫時處于不能再寫入資料或事件的狀态(例如,當隊列已滿時,不能再向隊列發送資料),那麼嘗試寫入該對象的任務可以選擇進入阻塞态,等待機會去完成寫入操作。如果任務試圖将任務通知發送給已經有通知挂起的任務,則發送任務不能在阻塞态下等待接收任務重置其通知狀态。
2.使用任務通知
要開啟任務通知功能,首先需要在FreeRTOSConfig.h中将configUSE_TASK_NOTIFICATIONS設定為1。
當configUSE_TASK_NOTIFICATIONS設定為1時,每個任務都有一個“通知狀态”,可以是“Pending”或“Not-Pending”,以及一個“通知值”,這是一個32位無符号整數。當任務收到通知時,其通知狀态設定為“Pending”。當任務讀取其通知值時,其通知狀态設定為“Not-Pending”。任務可以在Blocked狀态下等待其通知狀态變為“Pending”。
2.1簡易版
xTaskNotifyGive() API函數
xTaskNotifyGive()直接向任務發送通知,并增加接收任務的通知值。調用xTaskNotifyGive()将把接收任務的通知狀态設定為pending(如果它還沒有挂起的話)。
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
ulTaskNotifyTake() API函數
ulTaskNotifyTake()允許任務在Blocked狀态下等待其通知值大于零,并在傳回之前減少(減去1)或清除任務的通知值。
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
例子1:延遲中斷處理
const TickType_t xInterruptFrequency = pdMS_TO_TICKS( 500UL );
static void vHandlerTask( void *pvParameters )
{
const TickType_t xMaxExpectedBlockTime = xInterruptFrequency + pdMS_TO_TICKS( 10 );
uint32_t ulEventsToProcess;
for( ;; )
{
ulEventsToProcess = ulTaskNotifyTake( pdTRUE, xMaxExpectedBlockTime );
if( ulEventsToProcess != 0 ) {
while( ulEventsToProcess > 0 ) {
vPrintString( "Handler task - Processing event.\r\n" );
ulEventsToProcess--;
}
} else {
//如果運作到這,表示逾時時間内,中斷沒有發生
}
}
}
static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR( xHandlerTask,&xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
#define mainINTERRUPT_NUMBER 3
static void vPeriodicTask( void *pvParameters )
{
const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );
vTaskDelay( xDelay500ms );
vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
如上是一個利用任務通知來延遲中斷處理的例子vPeriodicTask函數模拟周期性中斷,在中斷前會列印Periodic task - About to generate an interrupt.中斷後會列印Periodic task - Interrupt generated.
ulExampleInterruptHandler函數為中斷處理函數,但在這個函數中并不真正進行處理,隻是采用vTaskNotifyGiveFromISR函數,給出任務通知,vHandlerTask函數,才是真正的處理函數,vHandlerTask函數作為一個任務來運作,調用ulTaskNotifyTake函數進入阻塞态,以等待任務通知的到來。
ulTaskNotifyTake()中xClearCountOnExit參數被設定為pdTRUE,這将導緻在ulTaskNotifyTake()傳回之前,接收任務的通知值被清除為零。也就是說必須處理所有的任務通知。
例子2:UART發送
外圍裝置上的一些操作需要較長的時間才能完成。比如高精度ADC轉換,以及在UART上傳輸大資料包。可以通過輪詢(重複讀取)外設的狀态寄存器,以确定操作何時完成。然而,輪詢非常浪費CPU,因為它占用CPU,而沒有執行任何實質性操作。
當然我們可以利用中斷,操作處理任務take信号量進入阻塞,等待發生中斷,在其對應中斷處理函數中give信号量,來替代輪詢。
比如uart發送函數,利用二進制信号量
BaseType_t xUART_Send( xUART *pxUARTInstance, uint8_t *pucDataSource, size_t uxLength )
{
BaseType_t xReturn;
/* 確定信号量為0 */
xSemaphoreTake( pxUARTInstance->xTxSemaphore, 0 );
/* 啟動傳輸*/
UART_low_level_send( pxUARTInstance, pucDataSource, uxLength );
/* 等待傳輸完畢 */
xReturn = xSemaphoreTake( pxUARTInstance->xTxSemaphore, pxUARTInstance->xTxTimeout );
return xReturn;
}
/*發送完畢中斷服務函數*/
void xUART_TransmitEndISR( xUART *pxUARTInstance )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 清除中斷 */
UART_low_level_interrupt_clear( pxUARTInstance );
/* give信号量 */
xSemaphoreGiveFromISR( pxUARTInstance->xTxSemaphore, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
但是這種方式需要建立信号量才能使用,建立信号量就涉及占用RAM。可以用任務通知代替:
BaseType_t xUART_Send( xUART *pxUARTInstance, uint8_t *pucDataSource, size_t uxLength )
{
BaseType_t xReturn;
/* 儲存調用該函數的任務句柄. */
pxUARTInstance->xTaskToNotify = xTaskGetCurrentTaskHandle();
/* 確定任務通知為0. */
ulTaskNotifyTake( pdTRUE, 0 );
/*啟動傳輸. */
UART_low_level_send( pxUARTInstance, pucDataSource, uxLength );
/* 阻塞等待發送完成.*/
xReturn = ( BaseType_t ) ulTaskNotifyTake( pdTRUE, pxUARTInstance->xTxTimeout );
return xReturn;
}
/*-----------------------------------------------------------*/
/* 發送完成中斷服務函數. */
void xUART_TransmitEndISR( xUART *pxUARTInstance )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 清除中斷. */
UART_low_level_interrupt_clear( pxUARTInstance );
/* 發送任務通知. */
vTaskNotifyGiveFromISR( pxUARTInstance->xTaskToNotify, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
2.2複雜版
xTaskNotify()API函數
xTaskNotify()比xTaskNotifyGive()更靈活、更強大,由于這種額外的靈活性和強大功能,使用起來也稍微複雜一些。
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
eAction是一個枚舉型變量
eAction的值 | 作用 |
eNoAction | 接收任務的通知狀态設定為挂起,而不更新其通知值。未使用xTaskNotify() ulValue參數。替代二進制信号量 |
eSetBits | 接收任務的通知值與xTaskNotify() ulValue參數中傳遞的值按位或比對。例如,如果ulValue設定為0x01,則接收任務的通知值将設定0位。另一個例子,如果ulValue是0x06(二進制0110),那麼接收任務的通知值将設定第1位和第2位。替代事件組 |
eIncrement | 接收任務的通知值遞增。未使用xTaskNotify() ulValue參數。替代計數信号量 |
eSetValueWithoutOverwrite | 如果在調用xTaskNotify()之前接收任務有一個挂起的通知,則不采取任何操作,xTaskNotify()将傳回pdFAIL。如果在調用xTaskNotify()之前接收任務沒有挂起的通知,則将接收任務的通知值設定為xTaskNotify() ulValue參數中傳遞的值。 |
eSetValueWithOverwrite | 接收任務的通知值被設定為xTaskNotify() ulValue參數中傳遞的值,而不管在調用xTaskNotify()之前接收任務是否有挂起的通知。 |
xTaskNotifyWait()API函數
xTaskNotifyWait()是ulTaskNotifyTake()的一個更強大的版本。它允許任務使用可選的逾時等待,以便調用任務的通知狀态變為挂起(如果它尚未挂起)。xTaskNotifyWait()提供了在進入函數和退出函數時在調用任務的通知值中清除比特的選項。
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
參數 | 作用 |
ulBitsToClearOnEntry | 如果調用任務在調用xTaskNotifyWait()之前沒有通知挂起,那麼在進入該函數時,在任務的通知值中清除在ulBitsToClearOnEntry中設定的任何位。例如,如果ulBitsToClearOnEntry是0x01,那麼任務通知值的第0位将被清除。如果将ulBitsToClearOnEntry設定為0xffff (ULONG_MAX)将清除任務通知值中的所有位。 |
ulBitsToClearOnExit | 如果調用任務因為收到通知而退出xTaskNotifyWait(),或者因為在調用xTaskNotifyWait()時已經有通知挂起,那麼在ulBitsToClearOnExit中設定的任何位将在任務退出xTaskNotifyWait()函數之前在任務的通知值中被清除。當任務的通知值儲存在*pulNotificationValue(參見下面的pulNotificationValue的描述)中後,這些位将被清除。例如,如果ulBitsToClearOnExit是0x03,那麼任務的通知值的第0位和第1位将在此之前被清除将ulBitsToClearOnExit設定為0xfffffff (ULONG_MAX)将清除任務通知值中的所有位。 |
pulNotificationValue | 用于傳遞出任務的通知值(被清除前的值)。pulNotificationValue是一個可選參數,如果不需要,可以設定為NULL。 |
xTicksToWait | 調用任務保持阻塞态以等待其通知狀态變為挂起的最長時間。 |
傳回值 | pdTRUE成功接到通知,pdFALSE未接收到通知 |
例子:ADC
void vADCTask( void *pvParameters )
{
uint32_t ulADCValue;
BaseType_t xResult;
const TickType_t xADCConversionFrequency = pdMS_TO_TICKS( 50 );
for( ;; ) {
xResult = xTaskNotifyWait(0,0,&ulADCValue,xADCConversionFrequency * 2 );
if( xResult == pdPASS ) {
ProcessADCResult( ulADCValue );
} else {
}
}
}
void ADC_ConversionEndISR( xADC *pxADCInstance )
{
uint32_t ulConversionResult;
BaseType_t xHigherPriorityTaskWoken = pdFALSE, xResult;
ulConversionResult = ADC_low_level_read( pxADCInstance );
xResult = xTaskNotifyFromISR( xADCTaskToNotify,ulConversionResult, eSetValueWithoutOverwrite,&xHigherPriorityTaskWoken );
configASSERT( xResult == pdPASS );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
ADC_ConversionEndISR函數是ADC轉換結束的ISR,vADCTask函數是等待ADC值的任務。
ADC_low_level_read是讀取ADC值函數,讀取後調用xTaskNotifyFromISR函數發送任務通知,ulValue參數為ulConversionResult,即ADC轉換的結果,eAction參數為eSetValueWithoutOverwrite,如果在調用xTaskNotify()之前接收任務有一個挂起的通知,則不采取任何操作,xTaskNotify()将傳回pdFAIL。如果在調用xTaskNotify()之前接收任務沒有挂起的通知,則将接收任務的通知值設定為xTaskNotify() ulValue參數中傳遞的值。
可見xTaskNotify()API函數和xTaskNotifyWait()API函數組合實作了傳值的功能,這是簡易版做不到的,不過大多數情況下,使用簡易版就夠了
嵌入式物聯網需要學的東西真的非常多,千萬不要學錯了路線和内容,導緻工資要不上去!
分享大家一個資料包,差不多150多G。裡面學習内容、面經、項目都比較新也比較全!
掃碼進群領資料
轉載自:大叔的嵌入式小站
文章來源于FreeRTOS全解析-11.任務通知(Task Notifications)
原文連結:https://mp.weixin.qq.com/s/oherrF4sg8ylkEVjRm1K_w