转载自https://blog.csdn.net/zhoutaopower/article/details/107467305
在 FreeRTOS 中,还有一个东西也可以用作任务与任务,中断与任务的同步,它叫任务通知(Task Notifications) ;
如果我们通过信号量、队列、事件组的形式来同步,在 FreeRTOS 中,叫通过了一个 Communication Object;也就是说通过了一个用于联系接收方和发送方的中间模块;
FreeRTOS 的任务通知(Task Notifications),是属于直接任务通讯方式(Direct to Task Communication):
任务通知(Task Notifications)不借助其他的通讯介质,直接进行传递;
注:如果要使能任务通知,需要在 FreeRTOSConfig.h 中设置 configUSE_TASK_NOTIFICATIONS 为 1;
既然是这样,那么任务通知和之前的信号量、事件组、队列有何异同呢?
相同之处:
1、他们都能够作为同步的作用,并且能够形成阻塞;
不同之处:
1、任务通知作为同步作用,它维护了一个叫做 Notification Value 的 32bits 值,一方设置这个值,另一方获取这个值,合理的使用这个值,可以让任务通知运用灵活;
2、任务通知不需要额外的创建,他是以任务的 Handle 作为任务的识别,相比信号量、队列、事件组,他们需要创建更多的内存空间,所以使用任务通知,可以节约内存;
3、由于任务通知是直接任务通讯方式(Direct to Task Communication),这样在速度上是快于信号量、队列、事件组;
4、由于任务通知,需要指定任务,所以任务通知只能够从任务->任务,或者中断->任务,不能从任务->中断;
5、因为任务通知指定单个任务,所以任务通知无法指定多个任务接收;
6、由于任务通知只用了一个 32bit 的数来做 notification value 来做同步,所以没法缓存更多的数据;
7、任务通知也分为写的一方和读的一方,写的一方叫做 Notify,读的一方叫 WaitNotify;接收通知的任务可以因为等待通知而进入阻塞状态,但是发送通知的任务即便不能立即完成通知发送也不能进入阻塞状态,换句话来说,就是对于接收方,可以形成阻塞,针对发送方,即便是对方还没来得及处理,也不形成阻塞。
Using Task Notifications
任务通知有较为强大的功能,常常可以替代二进制信号量,计数信号量,事件组,甚至有时候可以替代队列;如此多的场景使用,均可以使用 xTaskNotify() 和 xTaskNotifyWait() 来实现;
先不看 xTaskNotify() 和 xTaskNotifyWait() 这两个复杂的函数,通知有两个简单的版本,先从这个简单的版本开始玩起;
任务通知的简单应用场景,可以使用:
xTaskNotifyGive / vTaskNotifyGiveFromISR:向指定的任务给出一个通知;
ulTaskNotifyTake:获取本任务的通知,会导致阻塞;
由于任务通知只能够从 任务->任务 或者 中断->任务,所以 FromISR 版本的 API 只有 Give 的;
xTaskNotifyGive
xTaskNotifyGive 的原型为:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
它有一个入参和一个返回值:
xTaskToNotify:要通知的任务的 Handle,在创建任务的时候就决定了;
Return:这个函数调用了 xTaskNotify() ,后面在将这个函数的返回值;
它用来替代二值信号量和计数信号量,是同步的轻量级版本;
每调用一次这个函数,对应任务的 Notification Value 会自增 1;
任务初始化的时候,Notification Value 的值为 0;
vTaskNotifyGiveFromISR
vTaskNotifyGiveFromISR 是 xTaskNotifyGive 的 ISR 版本
- void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
- BaseType_t *pxHigherPriorityTaskWoken );
他有两个入参:
xTaskToNotify:要通知的任务的 Handle,在创建任务的时候就决定了;
pxHigherPriorityTaskWoken:是否会引起有任务解除,并且解除的任务是最高优先级;
每调用一次这个函数,对应任务的 Notification Value 会自增 1;
ulTaskNotifyTake
ulTaskNotifyTake 函数用于获取当前任务的 Notification Value 值,如果为 0,则任务进入 Blocked 状态;
ulTaskNotifyTake 的原型为:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
两个入参,一个返回值:
xClearCountOnExit:如果传入 pdTRUE,在这个函数返回之前, Notification Value 会被清 0;如果传入 pdFALSE,如果此刻的 Notification Value 大于 0,则在函数返回之前,这个 Notification Value 会减 1;
xTicksToWait:如果 Notification Value 为 0,则该值代表进入 Blocked 的时间;
Return:返回值代表在 Notification Value 被清 0 或者减 1 之前的值;
它用来替代二值信号量和计数信号量,是同步的轻量级版本;
Example 1:
使用任务通知来达到和信号量一样的效果,以《FreeRTOS --(15)信号量之概述》中的第一个例子为例:
1、定期拉起一个软件中断;
2、这个软件中断的 ISR 给另一个任务发送同步通知;
3、接收这个同步通知的任务接收到通知便打印;
下面是这个接收任务通知的任务,使用 ulTaskNotifyTake 接口,传入 pdTRUE,代表要清除 Notification Value ,也就是说,它当做二值信号量来用;
在取不到通知的时候,进入阻塞状态;
/* The rate at which the periodic task generates software interrupts. */ const TickType_t xInterruptFrequency = pdMS_TO_TICKS( 500UL ); static void vHandlerTask( void *pvParameters ) { /* xMaxExpectedBlockTime is set to be a little longer than the maximum expected time between events. */ const TickType_t xMaxExpectedBlockTime = xInterruptFrequency + pdMS_TO_TICKS( 10 ); uint32_t ulEventsToProcess; /* As per most tasks, this task is implemented within an infinite loop. */ for( ;; ) { /* Wait to receive a notification sent directly to this task from the interrupt service routine. */ ulEventsToProcess = ulTaskNotifyTake( pdTRUE, xMaxExpectedBlockTime ); if( ulEventsToProcess != 0 ) { /* To get here at least one event must have occurred. Loop here until all the pending events have been processed (in this case, just print out a message for each event). */ while( ulEventsToProcess > 0 ) { vPrintString( "Handler task - Processing event.\r\n" ); ulEventsToProcess--; } } else { /* If this part of the function is reached then an interrupt did not arrive within the expected time, and (in a real application) it may be necessary to perform some error recovery operations. */ } } }
下面是周期性任务,手动拉起的 IRQ 的 ISR,它通过 vTaskNotifyGiveFromISR 来给上面那个任务通知;
static uint32_t ulExampleInterruptHandler( void ) { BaseType_t xHigherPriorityTaskWoken; /* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as it will get set to pdTRUE inside the interrupt safe API function if a context switch is required. */ xHigherPriorityTaskWoken = pdFALSE; /* Send a notification directly to the task to which interrupt processing is being deferred. */ vTaskNotifyGiveFromISR( /* The handle of the task to which the notification is being sent. The handle was saved when the task was created. */ xHandlerTask, /* xHigherPriorityTaskWoken is used in the usual way. */ &xHigherPriorityTaskWoken ); /* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR(). If xHigherPriorityTaskWoken was set to pdTRUE inside vTaskNotifyGiveFromISR() then calling portYIELD_FROM_ISR() will request a context switch. If xHigherPriorityTaskWoken is still pdFALSE then calling portYIELD_FROM_ISR() will have no effect. The implementation of portYIELD_FROM_ISR() used by the Windows port includes a return statement, which is why this function does not explicitly return a value. */ portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); }
它的执行结果是:
周期性任务触发一次中断,任务被唤醒执行一次;
执行时序为:
t1 时刻 Idle 任务被来的周期性任务抢占;
周期性任务拉起一个中断,立马执行 ISR;
在 ISR 中给另一个高优先级任务发送任务通知,导致高优先级任务解除阻塞;
高优先任务执行完毕后,再次获取任务通知,获取不到,进入阻塞;
周期性任务得以回到刚刚被打断的地方继续执行;
t2 时刻执行完,轮到 Idle 进行执行;
Example 2:
上面那个例子,将 xClearOnExit 设置为了 pdTRUE,也就是将任务通知当成了二值信号量来用;
在下面这个例子,将他作为计数信号量来用,设置为: pdFALSE,也就是每次获取后,Notification Value 自行减 1;
下面部分是任务获取本任务的任务通知:
static void vHandlerTask( void *pvParameters ) { /* xMaxExpectedBlockTime is set to be a little longer than the maximum expected time between events. */ const TickType_t xMaxExpectedBlockTime = xInterruptFrequency + pdMS_TO_TICKS( 10 ); /* As per most tasks, this task is implemented within an infinite loop. */ for( ;; ) { /* Wait to receive a notification sent directly to this task from the interrupt service routine. The xClearCountOnExit parameter is now pdFALSE, so the task's notification value will be decremented by ulTaskNotifyTake(), and not cleared to zero. */ if( ulTaskNotifyTake( pdFALSE, xMaxExpectedBlockTime ) != 0 ) { /* To get here an event must have occurred. Process the event (in this case just print out a message). */ vPrintString( "Handler task - Processing event.\r\n" ); } else { /* If this part of the function is reached then an interrupt did not arrive within the expected time, and (in a real application) it may be necessary to perform some error recovery operations. */ } } }
下面是 ISR 发送任务通知,连续调用 3 次 vTaskNotifyGiveFromISR,也就是会导致 Notification Value 加 3;
static uint32_t ulExampleInterruptHandler( void ) { BaseType_t xHigherPriorityTaskWoken; xHigherPriorityTaskWoken = pdFALSE; /* Send a notification to the handler task multiple times. The first ‘give’ will unblock the task, the following 'gives' are to demonstrate that the receiving task's notification value is being used to count (latch) events - allowing the task to process each event in turn. */ vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken ); vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken ); vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken ); portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); }
执行结果预期的,这个任务的打印出来 3 次,然后进入 Blocked,等待下次软件拉起中断后,再次给 3 次通知:
上面描述了任务通知的简单用法,上面的 API 的都是间接的调用了下面的 APIs;
xTaskNotify
xTaskNotify 用于复杂的任务通知,是 xTaskNotifyGive 的高级版本;它不仅仅能够使得 Notification Value 加 1,而且可以控制Notification Value 的值以及行为;
xTaskNotify 的原型为:
- BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
- uint32_t ulValue,
- eNotifyAction eAction );
它有三个入参以及一个返回值:
xTaskToNotify:具体任务的句柄;
ulValue:Notification Value 的值;
eAction:一个枚举,代表了这个通知如何使用 Notification Value ;
Return:下面描述;
eNotifyAction 枚举含义如下:
eNotifyAction Value | Descriptions | Usage |
eNoAction | 入参 ulValue 这个值没有用处,xTaskNotify 仅仅作为简单的类似二值信号量使用 | 替代二值信号量 |
eSetBits | 接收任务通知那端将会使用 ulValue 来和当前的任务的 notification value 做或操作;比如如果传入的 ulValue 为 0x01,那么接收端获取到的 notification value的最低位将会被设置为 1; | 替代事件组 |
eIncrement | 入参 ulValue 这个值没有用处,只是简单的增加 notification value,相当于 xTaskNotifyGive 函数调用 | 替代计数信号量 |
eSetValueWithoutOverwrite | 如果接收任务通知那端,还有未处理的任务通知,那么使用这个参数,将会返回 pdFAIL;否则,传入的 ulValue 将会更新对应任务的 notification value;这也很好的体现了 without Overwirte 的含义,因为任务通知只能够存储一个 32bit 的数,如果这个数没被接收端处理,也就是说他还有效,那么现在在往里面更新 notification value 就会导致之前的被覆盖 | 一定程度上替代队列 |
eSetValueWithOverwrite | 不管接收任务通知那端是否有未处理的通知,直接更新 notification value | 替代邮箱 |
xTaskNotifyFromISR
他是 xTaskNotify 的 ISR 版本:
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken );
参数含义不在多说;
xTaskNotifyWait
在任务侧,使用 xTaskNotifyWait 来等待通知,他是 ulTaskNotifyTake 的高级版本:
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
他有 4 个入参和一个返回值:
ulBitsToClearOnEntry:当任务调用 xTaskNotifyWait 的时候,如果此刻还没有已经准备好的 notification,那么所有被 ulBitsToClearOnEntry 设置为 1 的 bit 都会被清除为 0;比如,如果 ulBitsToClearOnEntry 为 0x01,那么代表 notification value 的 0bit 将会被清除为 0;
ulBitsToClearOnExit:如果任务因为收到一个通知或者在调用 xTaskNotifyWait 的时候,已经有通知挂起了,那么在退出xTaskNotifyWait 之前,ulBitsToClearOnExit 指定的所有 bit 将会被清除为 0;
pulNotificationValue:notification value 的值,它是在被 ulBitsToClearOnExit 更改之前的值;可以设置为 NULL,表示不关心这个值;
xTicksToWait:如果导致了阻塞,那么这个是阻塞的最长时间;
Return:当收到了通知,返回 pdTRUE;否则返回 pdFALSE;
由于这几个 APIs 的参数非常非常多,而且难以理解,下面结合几个例子来说明一下;
Example 3:
类似于 UART 这样的外设,当传输大量数据的时候,如果使用 poll 的方式,这样就在 poll 的时候,浪费 CPU 的时间,最好是以中断的方式来驱动,为了避免这样的 CPU 时间浪费,RTOS 中都推荐使用中断驱动;
如果使用常用的信号量的方式,UART 发送的代码如下,发送完数据后,等待信号量:
/* Driver library function to send data to a UART. */ BaseType_t xUART_Send( xUART *pxUARTInstance, uint8_t *pucDataSource, size_t uxLength ) { BaseType_t xReturn; /* Ensure the UART's transmit semaphore is not already available by attempting to take the semaphore without a timeout. */ xSemaphoreTake( pxUARTInstance->xTxSemaphore, 0 ); /* Start the transmission. */ UART_low_level_send( pxUARTInstance, pucDataSource, uxLength ); /* Block on the semaphore to wait for the transmission to complete. If the semaphore is obtained then xReturn will get set to pdPASS. If the semaphore take operation times out then xReturn will get set to pdFAIL. Note that, if the interrupt occurs between UART_low_level_send() being called, and xSemaphoreTake() being called, then the event will be latched in the binary semaphore, and the call to xSemaphoreTake() will return immediately. */ xReturn = xSemaphoreTake( pxUARTInstance->xTxSemaphore, pxUARTInstance->xTxTimeout ); return xReturn;
在发送完成中断中释放信号量:
/* The service routine for the UART's transmit end interrupt, which executes after the last byte has been sent to the UART. */ void xUART_TransmitEndISR( xUART *pxUARTInstance ) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* Clear the interrupt. */ UART_low_level_interrupt_clear( pxUARTInstance ); /* Give the Tx semaphore to signal the end of the transmission. If a task is Blocked waiting for the semaphore then the task will be removed from the Blocked state. */ xSemaphoreGiveFromISR( pxUARTInstance->xTxSemaphore, &xHigherPriorityTaskWoken ); portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); }
如果使用任务通知来重写这代码,将会变成如下:
UART 发送部分:
/* Driver library function to send data to a UART. */ BaseType_t xUART_Send( xUART *pxUARTInstance, uint8_t *pucDataSource, size_t uxLength ) { BaseType_t xReturn; /* Save the handle of the task that called this function. The book text contains notes as to whether the following line needs to be protected by a critical section or not. */ pxUARTInstance->xTaskToNotify = xTaskGetCurrentTaskHandle(); /* Ensure the calling task does not already have a notification pending by calling ulTaskNotifyTake() with the xClearCountOnExit parameter set to pdTRUE, and a block time of 0 (don't block). */ ulTaskNotifyTake( pdTRUE, 0 ); /* Start the transmission. */ UART_low_level_send( pxUARTInstance, pucDataSource, uxLength ); /* Block until notified that the transmission is complete. If the notification is received then xReturn will be set to 1 because the ISR will have incremented this task's notification value to 1 (pdTRUE). If the operation times out then xReturn will be 0 (pdFALSE) because this task's notification value will not have been changed since it was cleared to 0 above. Note that, if the ISR executes between the calls to UART_low_level_send() and the call to ulTaskNotifyTake(), then the event will be latched in the task’s notification value, and the call to ulTaskNotifyTake() will return immediately.*/ xReturn = ( BaseType_t ) ulTaskNotifyTake( pdTRUE, pxUARTInstance->xTxTimeout ); return xReturn; }
在接收完成中断中发送任务通知:
/* The ISR that executes after the last byte has been sent to the UART. */ void xUART_TransmitEndISR( xUART *pxUARTInstance ) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* This function should not execute unless there is a task waiting to be notified. Test this condition with an assert. This step is not strictly necessary, but will aid debugging. configASSERT() is described in section 11.2.*/ configASSERT( pxUARTInstance->xTaskToNotify != NULL ); /* Clear the interrupt. */ UART_low_level_interrupt_clear( pxUARTInstance ); /* Send a notification directly to the task that called xUART_Send(). If the task is Blocked waiting for the notification then the task will be removed from the Blocked state. */ vTaskNotifyGiveFromISR( pxUARTInstance->xTaskToNotify, &xHigherPriorityTaskWoken ); /* Now there are no tasks waiting to be notified. Set the xTaskToNotify member of the xUART structure back to NULL. This step is not strictly necessary but will aid debugging. */ pxUARTInstance->xTaskToNotify = NULL; portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); }
任务通知也适用于 UART 接收,在处理接收数据的任务中,uxWantedBytes 指定了期望接收数据的个数:
/* Driver library function to receive data from a UART. */ size_t xUART_Receive( xUART *pxUARTInstance, uint8_t *pucBuffer, size_t uxWantedBytes ) { size_t uxReceived = 0; TickType_t xTicksToWait; TimeOut_t xTimeOut; /* Record the time at which this function was entered. */ vTaskSetTimeOutState( &xTimeOut ); /* xTicksToWait is the timeout value - it is initially set to the maximum receive timeout for this UART instance. */ xTicksToWait = pxUARTInstance->xRxTimeout; /* Save the handle of the task that called this function. The book text contains notes as to whether the following line needs to be protected by a critical section or not. */ pxUARTInstance->xTaskToNotify = xTaskGetCurrentTaskHandle(); /* Loop until the buffer contains the wanted number of bytes, or a timeout occurs. */ while( UART_bytes_in_rx_buffer( pxUARTInstance ) < uxWantedBytes ) { /* Look for a timeout, adjusting xTicksToWait to account for the time spent in this function so far. */ if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) != pdFALSE ) { /* Timed out before the wanted number of bytes were available, exit the loop. */ break; } /* The receive buffer does not yet contain the required amount of bytes. Wait for a maximum of xTicksToWait ticks to be notified that the receive interrupt service routine has placed more data into the buffer. It does not matter if the calling task already had a notification pending when it called this function, if it did, it would just iteration around this while loop one extra time. */ ulTaskNotifyTake( pdTRUE, xTicksToWait ); } /* No tasks are waiting for receive notifications, so set xTaskToNotify back to NULL. The book text contains notes as to whether the following line needs to be protected by a critical section or not. */ pxUARTInstance->xTaskToNotify = NULL; /* Attempt to read uxWantedBytes from the receive buffer into pucBuffer. The actual number of bytes read (which might be less than uxWantedBytes) is returned. */ uxReceived = UART_read_from_receive_buffer( pxUARTInstance, pucBuffer, uxWantedBytes ); return uxReceived; }
在接收数据的 ISR 中,将数据 Copy 到 buffer 中:
void xUART_ReceiveISR( xUART *pxUARTInstance ) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* Copy received data into this UART's receive buffer and clear the interrupt. */ UART_low_level_receive( pxUARTInstance ); /* If a task is waiting to be notified of the new data then notify it now. */ if( pxUARTInstance->xTaskToNotify != NULL ) { vTaskNotifyGiveFromISR( pxUARTInstance->xTaskToNotify, &xHigherPriorityTaskWoken ); portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); } }
Example 4:
下面是一个获取 ADC 采样数据的例子;
假设 ADC_ConversionEndISR 是 ADC 采样转换完成的 ISR,vADCTask 是对应的处理任务;
ADC_ConversionEndISR 中使用 xTaskNotifyFromISR 带 eSetValueWithoutOverwrite 参数来传递转换后的 ulConversionResult 数据:
/* The interrupt service routine that executes each time an ADC conversion completes. */ void ADC_ConversionEndISR( xADC *pxADCInstance ) { uint32_t ulConversionResult; BaseType_t xHigherPriorityTaskWoken = pdFALSE, xResult; /* Read the new ADC value and clear the interrupt. */ ulConversionResult = ADC_low_level_read( pxADCInstance ); /* Send a notification, and the ADC conversion result, directly to vADCTask(). */ xResult = xTaskNotifyFromISR( xADCTaskToNotify, /* xTaskToNotify parameter. */ ulConversionResult, /* ulValue parameter. */ eSetValueWithoutOverwrite, /* eAction parameter. */ &xHigherPriorityTaskWoken ); /* If the call to xTaskNotifyFromISR() returns pdFAIL then the task is not keeping up with the rate at which ADC values are being generated. configASSERT() is described in section 11.2.*/ configASSERT( xResult == pdPASS ); portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); }
处理 ADC 数据的任务:
/* A task that uses an ADC. */ void vADCTask( void *pvParameters ) { uint32_t ulADCValue; BaseType_t xResult; /* The rate at which ADC conversions are triggered. */ const TickType_t xADCConversionFrequency = pdMS_TO_TICKS( 50 ); for( ;; ) { /* Wait for the next ADC conversion result. */ xResult = xTaskNotifyWait( /* The new ADC value will overwrite the old value, so there is no need to clear any bits before waiting for the new notification value. */ 0, /* Future ADC values will overwrite the existing value, so there is no need to clear any bits before exiting xTaskNotifyWait(). */ 0, /* The address of the variable into which the task's notification value (which holds the latest ADC conversion result) will be copied. */ &ulADCValue, /* A new ADC value should be received every xADCConversionFrequency ticks. */ xADCConversionFrequency * 2 ); if( xResult == pdPASS ) { /* A new ADC value was received. Process it now. */ ProcessADCResult( ulADCValue ); } else { /* The call to xTaskNotifyWait() did not return within the expected time, something must be wrong with the input that triggers the ADC conversion, or with the ADC itself. Handle the error here. */ } } }
每次 ADC 采样转换完成,都更新数据给 ADC Task;