FreeRTOS 教程指南 学习笔记 第九章 任务通知

FreeRTOS 教程指南 学习笔记 第九章 任务通知

一、简介

我们已经看到,使用FreeRTOS的应用程序被构建为一组独立的任务,这些自主任务很可能必须相互通信,以便它们能够共同提供有用的系统功能。

通过中介对象进行通信

这本书已经描述了任务之间可以相互交流的各种方式。到目前为止,所描述的方法都需要创建一个通信对象。通信对象的示例包括队列、事件组和各种不同类型的信号量
当使用一个通信对象时,事件和数据不会被直接发送到一个接收任务,或一个接收ISR,而是被发送到该通信对象。同样地,任务和ISR从通信对象接收事件和数据,而不是直接从发送事件或数据的任务或ISR接收事件和数据。这一点如图76所示。
在这里插入图片描述

Task Notifications—Direct to Task Communication

“任务通知”允许任务与其他任务交互,并与ISR同步,而不需要一个单独的通信对象。通过使用任务通知,任务或ISR可以将事件直接发送到接收任务。这一点如图77所示。
在这里插入图片描述
任务通知功能是可选的。要包含任务通知功能,在FreeRTOSConfig.h中将configUSE_TASK_NOTIFICATIONS设置为1。
当configUSE_TASK_NOTIFICATIONS设置为1时,每个任务都有一个“通知状态”,可以是“挂起”或“非挂起”,还有一个“通知值”,这是一个32位的无符号整数。当任务收到通知时,其通知状态被设置为挂起。当任务读取其通知值时,其通知状态被设置为不挂起。
任务可以在一个超时时间段内阻塞状态等待,使其通知状态挂起。
本章旨在让读者更好地理解:

  • A任务的通知状态和通知值。
  • 如何以及何时可以使用任务通知来代替通信对象,例如信号量。
  • 使用任务通知代替通信对象的优点。

二、任务通知;好处和限制

任务通知的性能好处

使用任务通知来向任务发送事件或数据比使用队列、信号量或事件组来执行等效的操作要快得多。

任务通知的内存占用好处

使用任务通知来向任务发送事件或数据所需的RAM,比使用队列、信号量或事件组来执行等效操作所需的RAM要少得多。这是因为每个通信对象(队列、信号量或事件组)必须创建才能使用,而启用任务通知功能,每个任务只有8个字节的RAM开销。

任务通知的限制

任务通知比通信对象更快,使用更少的RAM,但任务通知不能在所有场景中使用。本节记录了不能使用任务通知的场景:

  • 向ISR通信对象发送事件或数据
    通信对象可用于将事件和数据从ISR发送到任务,并从任务发送到ISR。
    任务通知可用于将事件和数据从ISR发送到任务,但它们不能用于将事件或数据从任务发送到ISR。
  • 启用多个接收任务
    一个通信对象可以被任何知道其句柄(可能是队列句柄、信号量句柄或事件组句柄)的任何任务或ISR访问。任意数量的任务和ISR都可以处理发送到任何给定通信对象的事件或数据。
    任务通知被直接发送到接收任务,因此只能由被通知的任务进行处理。然而,在实际情况下,这很少是一个限制。因为虽然有多个任务和ISR发送到相同的通信对象是常见的,但却很少有多个任务和ISR接收到来自相同的通信对象。
  • 缓冲多个数据项
    队列是一次可以保存多个数据项的通信对象。已发送到队列但尚未从队列接收的数据将在队列对象中缓冲。
    任务通知通过更新接收任务的通知值来向任务发送数据。一个任务的通知值一次只能保存一个值。
  • 向多个任务广播
    一个事件组是一个通信对象,可用于一次向多个任务发送一个事件。
    任务通知被直接发送到接收任务,因此只能由接收任务进行处理。
  • 在阻止状态等待发送完成
    如果通信对象暂时处于一个不能写入数据或事件的状态(例如,当队列满时,不能将数据发送到队列),那么试图写入对象的任务可以选择进入阻塞状态,等待写入操作完成。
    如果任务尝试向已经挂起通知的任务发送任务通知,则发送任务无法在“阻止”状态下等待接收任务重置其通知状态。我们可以看到,在使用任务通知的实际情况下,这很少是一个限制。

三、使用任务通知

任务通知API选项

任务通知是一个非常强大的特性,通常可以用来代替一个二进制信号量、一个计数信号量、一个事件组,有时甚至是一个队列。这种广泛的使用场景可以通过使用xTaskNotify()API功能来发送任务通知,和使用xTaskNotifyWait()API功能来接收任务通知来实现。
但是,在大多数情况下,不需要xTaskNotify()和xTaskNotifyWait()API函数所提供的完全灵活性,而更简单的函数就足够了。因此,xTaskNotifyGive()API函数作为一个更简单但不那么灵活的替代xTaskNotify()提供,而ulTaskNotifyTake()API函数作为一个更简单但不那么灵活的替代xTaskNotifyWait()提供。

The xTaskNotifyGive() API Function

xTaskNotifyGive()直接向任务发送一个通知,并增加(添加一个通知和)接收任务的通知值。调用xTaskNotifyGive()将把接收任务的通知状态设置为挂起,如果它尚未挂起。
xTaskNotifyGiveAPI()函数,允许任务通知作为更轻和更快的二进制或计数信号的替代。

//Listing 145. The xTaskNotifyGive() API function prototype
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
/*参数xTaskToNotify :正在发送通知的目标任务的句柄。*/
/*返回值:xTaskNotifyGive()是一个调用xTaskNotify()的宏。由宏传递到xTaskNotify()的参数被设置为使pdPASS是唯一可能的返回值。*/
The vTaskNotifyGiveFromISR() API Function
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken );
/*参数pxHigherPriorityTaskWoken:如果发送通知的目标任务处于阻塞状态,等待接收通知,则发送通知将导致任务离开阻塞状态。
								如果调用vTaskNotifyGiveFromISR()导致任务离开阻塞状态,并且未阻塞任务的任务优先级高于当前执行的任务(被中断的任务),那么,在内部,将pxHigherPriorityTaskWoken设置为pdTRUE。
								如果给出vTaskNotifyGiveFromISR()将此值设置为pdTRUE,那么在退出中断之前应该执行一个上下文切换。这将确保中断直接返回到最高优先级的准备就绪状态任务。
								与所有中断安全API函数一样,在使用该参数之前,必须先设置为pdFALSE。*/
The ulTaskNotifyTake() API Function

ulTaskNotifyTake()允许任务处于阻塞状态等待其通知值大于零,并减少(减去1)或在返回之前清除任务的通知值。
ulTaskNotifyTake()API函数,允许任务通知作为轻量和更快速的,二进制或计数信号量的替代。

//Listing 147. The ulTaskNotifyTake() API function prototype
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
/*参数xClearCountOnExit:如果xClearCountOnExit设置为pdTRUE,那么在调用ulTaskNotifyTake()返回之前,调用任务的通知值将被清除为零。
						如果xClearCountOnExit设置为pdFALSE,并且调用任务的通知值大于零,则调用任务的通知值将在返回调用ulTaskNotifyTake()之前递减。*/
/*参数xTicksToWait:调用任务应保持在“阻塞”状态等待其通知值大于零的最大时间。
					阻塞时间以滴答周期指定,因此它所表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。
					如果INCLUDE_vTaskSuspend在RTOsConfig.h中设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限等待(不超时)。*/
/*返回值:返回的值是由xClearCountOnExit参数的值确定的:清除为零或减少,之前的通知值。
		  如果指定了阻塞时间(xTicksToWait不是零),并且返回值不是零,则有可能将调用任务置于阻塞状态,等待其通知值大于零,并且在阻塞时间过期之前更新其通知值。
		  如果指定了阻塞时间(xTicksToWait不是零),并且返回值为零,则将调用任务置于阻塞状态,等待其通知值大于零,但指定的阻塞时间在此发生之前已过期。*/
Example 24. Using a task notification in place of a semaphore, method 1

示例16使用了一个二进制信号量来解除对中断服务例程中的任务——有效地使任务与中断同步。这个示例复制了示例16的功能,但使用一个直接到任务的通知来代替二进制信号量。

//Listing 148. The implementation of the task to which the interrupt processing is deferred (the task that synchronizes with the interrupt) in Example 24
/* 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. */
 		}
 	}
 }

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(xHandlerTask, /* The handle of the task to which the notification is being sent. The handle was saved when the task was created. */ 	
 	&xHigherPriorityTaskWoken );/* xHigherPriorityTaskWoken is used in the usual way. */
 	 
 	/* 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 );
}

在这里插入图片描述

在这里插入图片描述

Example 25. Using a task notification in place of a semaphore, method 2

在示例24中,ulTaskNotifyTake()的xClearOnExit参数被设置为pdTRUE。示例25稍微修改示例24,以演示当ulTaskNotifyTake()的xClearOnExit参数设置为pdFALSE时的行为。
当xClearOnExit是pdFALSE时,调用ulTaskNotifyTake()只会减少(减少1)调用任务的通知值,而不是将其清除为零。因此,通知计数是已发生的事件数量与已处理的事件数量之间的差异。这使得vHandlerTask()的结构可以通过两种方式进行简化:

  1. 等待被处理的事件数量被保存在通知值中,因此它不需要被保存在本地变量中。
  2. 只需要在每次调用ulTaskNotifyTake()之间处理一个事件。
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. */
 		}
 	}
 }

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 );
}

在这里插入图片描述

The xTaskNotify() and xTaskNotifyFromISR() API Functions

xTaskNotify()是一个比xTaskNotifyGive()更强大的版本,可以通过以下任何一种方式更新接收任务的通知值:

  • 增加(一个到)接收任务的通知值,在这种情况下,xTaskNotify()相当于xTaskNotifyGive()。
  • 在接收任务的通知值中设置一个或多个位。这允许使用任务的通知值作为事件组的更轻和更快的替代方案。
  • 当任务通知更新后,只有当读取任务读取了通知值后,才在通知值中写入一个全新的数字。这使得任务的通知值提供与长度为1的队列类似的功能。
  • 将在接收任务的通知值中写入一个全新的数字,即使接收任务自上次更新以来没有读取它的通知值。这允许任务的通知值提供与xQueueOverwrite()API函数相近的功能。其行为结果有时被称为“邮箱”。

xTaskNotify()比xTaskNotifyGive()更灵活、更强大,而且由于其额外的灵活性和功能,它使用起来也有点复杂。
xTaskNotifyFromISR()是xTaskNotify()的一个中断安全版本,可以用于中断服务例程,因此有一个额外的pxHigherPriorityTaskWoken参数。

//Listing 152. Prototypes for the xTaskNotify() and xTaskNotifyFromISR() API functions
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
 						uint32_t ulValue, 
 						eNotifyAction eAction );
/*参数xTaskToNotify:正在发送通知的任务的句柄*/
/*参数ulValue:如何使用ulValue取决于通知操作值。见表52。*/
/*参数eAction:一个枚举的类型的值,它指定如何更新接收任务的通知值。见表52。*/
/*返回值:xTaskNotify()将返回pdPASS,但表52中提到的情况除外。*/
 						
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, 
 							   uint32_t ulValue, 
 							   eNotifyAction eAction,
 							   BaseType_t *pxHigherPriorityTaskWoken );

表52
eNotifyAction ValueResultant Effect on Receiving Task
eNoAction通知值没有更新,接收任务的通知状态被设置为挂起。未使用xTaskNotify()的ulValue参数将不使用。eNoAction允许任务通知作为二进制信号量的替代。
eSetBits接收任务的通知值是通过在xTaskNotify()的ulValue参数中传递的值,按位进行’OR’操作排序的。例如,如果将ulValue设置为0x01,则将在接收任务的通知值中0位被设置成1。另一个例子是,如果ulValue是0x06(二进制0110),则将在接收任务的通知值中的位1和位2被设置成1。eSetBits操作允许任务通知作为事件组更快更轻的替代品。
eIncrement接收任务的通知值将会递增。未使用xTaskNotify()的ulValue参数将不使用。增量操作允许使用任务通知来替代二进制或计数信号量,并且相当于更简单的xTaskNotifyGive()API函数。
eSetValueWithoutOverwrite如果接收任务在调用xTaskNotify()之前有一个正被挂起的通知,则不采取任何操作,并且xTaskNotify()将返回pdFAIL。 如果在调用xTaskNotify()之前,没有挂起的通知,则接收任务的通知值将被设置成xTaskNotify()中的ulValue参数值。
eSetValueWithOverwrite接收任务的通知值被设置为在xTaskNotify()参数ulValue值,无论接收任务在调用xTaskNotify()之前是否有挂起的通知。
The xTaskNotifyWait() API Function

xTaskNotifyWait()是ulTaskNotifyTake()的一个更强大的版本。它允许一个任务在一个可选的超时时间内等待,调用任务的通知状态由不挂起变成为挂起。xTaskNotifyWait()提供了在进入函数和在退出函数时,清除通知值的位的选项。

//Listing 153. The xTaskNotifyWait() API function prototype
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
 							uint32_t ulBitsToClearOnExit,
 							uint32_t *pulNotificationValue,
 							TickType_t xTicksToWait );
/*参数ulBitsToClearOnEntry:如果调用任务在调用xTaskNotifyWait()之前没有挂起的通知,那么通知值中,被ulBitsToClearOnEntry中标记的任何位,将在进入函数时被清除。
							例如,如果ulBitsToClearOnEntry为0x01,则任务通知值的第0位将被清除。另一个例子是,将ulBitsToClearOnEntry清除条目设置为0x ffffffff(ULONG_MAX)将清除任务通知值中的所有位,有效地清除该值为0。*/
/*参数ulBitsToClearOnExit:如果调用任务退出xTaskNotifyWait(),因为它收到了通知,或者在调用时已经有通知挂起,那么在任务退出xTaskNotifyWait()功能之前,通知值中,被ulBitsToClearOnExit标记的任何位将被清除。
							将任务的通知值保存在*pulNotificationValue中(参见下面的通知值说明)。
							例如,如果ulBitsToClearOnExit是ox03,则在函数退出之前将把任务通知值的0和1位清除。
							将ulBitsToClearOnExit到清除退出设置为0x ffffffff(ULONG_MAX)将清除任务通知值中的所有位,有效地清除该值为0。*/
/*参数pulNotificationValue:用于传递任务的通知值。被复制到 *pulNotificationValue值是:在被pulNotificationValue清除任何位之前通知值。
							pulNotificationValue是一个可选的参数,如果不需要,可以设置为NULL。*/
/*参数xTicksToWait:调用任务应保持在阻止状态等待通知状态的最大时间。
					阻塞时间以滴答周期指定,因此它所表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。
					如果INCLUDE_vTaskSuspend在RTOsConfig.h中设置为1,则将xToWiit设置为portMAX_DELAY将导致任务无限等待(不超时)。*/
/*返回值:有两个可能的返回值:
		  1、返回pdTRUE,这表示由于收到通知,或者因为调用xTaskNotifyWait()时调用任务已经有一个挂起的通知。如果指定了阻塞时间(xTicksToWait不是零),则有可能将调用任务置于阻塞状态,等待其通知状态成为挂起,但在阻塞时间过期之前将其通知状态设置为挂起。
		  2、返回pdFALSE,这表明xTaskNotifyWait()返回时,将调用任务没有接收到任务通知。如果xTicksToWait不是零,则调用任务将被保持在阻塞状态,以等待其通知状态挂起,但指定的阻塞时间在此发生之前已过期。*/
Task Notifications Used in Peripheral Device Drivers: UART Example

外围驱动程序库提供了在硬件接口上执行公共操作的功能。经常提供此类库的外设示例包括通用异步接收器和发射器(UART)、串行外设接口(SPI)端口、模拟数字转换器(ADC)和以太网端口。通常由这些库提供的功能的示例包括初始化外围设备、向外围发送数据以及从外围接收数据的功能。
一些在外围设备上的操作需要相对较长的时间才能完成。这种操作的例子包括一个高精度的ADC转换,以及在UART上传输一个大的数据包。在这些情况下,可以实现驱动程序库函数来轮询(重复读取)外围设备的状态寄存器,以确定操作何时完成。然而,以这种方式轮询几乎总是浪费的,因为它利用了处理器100%的时间,而没有执行生产处理。在多任务处理系统中,浪费特别昂贵,在该系统中,轮询外围设备的任务可能会阻止确实需要执行高效处理的低优先级任务的执行。
为了避免浪费处理时间,一个有效的RTOS感知设备驱动程序应该被中断驱动,并为启动冗长操作的任务选择等待阻塞状态的操作完成。这样,当执行冗长操作的任务在阻塞状态时,较低优先级的任务可以执行,并且任何任务都不会使用处理时间,除非它们能够有效地使用处理时间。
RTOS感知的驱动程序库通常使用二进制信号量将任务放置入阻塞状态。清单154中所示的伪代码演示了该技术,该伪代码提供了一个在UART端口上传输数据的RTOS感知库函数的大纲。在清单154中:

  • xUART是一个描述UART外围设备的结构,并保存状态信息。该结构中的xTxSemaphore成员是一个SemaphoreHandle_t类型的变量。我们假设这个信号量已经被创建出来了。
  • xUART_Send()函数不包含任何互斥逻辑。如果有多个任务将使用xUART_Send()函数,那么应用程序编写器将必须管理应用程序本身内部的互斥。例如,在调用xUART_Send()之前,可能需要一个获得互斥体的任务。
  • xSemaphoreTake()API功能用于在UART传输启动后将调用任务置于阻塞状态。
  • 在传输完成后,xSemaphoreGiveFromISR()API函数用于:在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 );
}

清单154中演示的技术是完全可行的,而且确实是常见的做法,但它有一些缺点:

  • 该库使用多个信号量,这增加了其RAM内存占用。
  • 符号在创建之前不能使用,因此使用符号的库在显式初始化之前不能使用。
  • 信号量是广泛适用于各种用例的通用对象;它们的逻辑包括允许任意数量的任务在阻塞状态等待信号量可用,以及在信号量可用时选择(以确定的方式)从阻塞状态中删除哪个任务。执行该逻辑需要有限的时间,并且在在清单154所示的场景中,在任何给定的时间都不能有超过一个任务等待信号量,这些处理开销是不必要的。

清单155演示了如何通过使用任务通知来代替二进制信号量来避免这些缺点。
注意:如果库使用任务通知,那么库的文档必须清楚地说明调用库函数可以更改调用任务的通知状态和通知值。

在清单155中:

  • xUART结构中的xTxSemaphore成员已被xTaskToNotify成员所取代。xTaskToNotify是一个TaskHandle_t类型的变量,用于保存正在等待UART操作完成的任务的句柄。
  • xTaskGetCurrentTaskHandle()FreeRTOS API函数用于获取处于正在运行状态的任务的句柄。
  • 该库不创建任何FreeRTOS对象,因此不会产生RAM开销,也不需要显式初始化。
  • 任务通知被直接发送到等待UART操作完成的任务,因此不执行不必要的逻辑。

xUART结构的xTaskToNotify成员被任务和中断服务程序接受,要求考虑处理器将如何更新其值:

  • 如果xTaskToNotify由一个内存写操作更新,那么它可以在关键部分之外更新,如清单155所示。如果xTaskToNotify是一个32位变量(TaskHandle_t是一个32位类型),而运行FreeRTOS的处理器是一个32位处理器,那么就会出现这种情况。
  • 如果需要多个内存写操作来更新xTaskToNotify,那么必须只在关键部分内更新,否则中断服务例程可能在处于不一致状态时访问xTaskToNotify。如果xTaskToNotify是一个32位变量,而运行FreeRTOS的处理器是一个16位处理器,那么就会出现这种情况,因为它需要两个16位内存写操作来更新所有32位。

在内部,在FreeRTOS实现中,TaskHandle_t是一个指针,因此大小(TaskHandle_t)总是等于大小(void*)。

/* 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 );
}

任务通知还可以替换接收函数中的信号量,如伪代码清单156所示,该清单提供了在UART端口上接收数据的RTOS感知库函数的大纲。参考清单156:

  • xUART_Receive()函数不包含任何互斥逻辑。如果有多个任务将使用xUART_Receive()函数,那么应用程序编写器将必须管理应用程序本身内部的互斥。例如,在调用xUART_Receive()之前,可能需要一个获得互斥体的任务。
  • UART的接收中断服务例程将UART接收到的字符放到RAM缓冲区中。xUART_Receive()函数从RAM缓冲区中返回字符。
  • xUART_Receive()的uxWantedBytes参数用于指定要接收的字符数。如果RAM缓冲区尚未包含请求的数字字符,则调用任务将置于“阻塞”状态,等待通知缓冲区中的字符数增加。while()循环用于重复此序列,直到接收缓冲区包含所请求的字符数或发生超时为止。
  • 调用任务可以多次进入“阻塞”的状态。因此,阻塞时间被调整以考虑自调用xUART_Receive()以来已经经过的时间。这些调整确保了在xUART_Receive()内花费的总时间不超过xUART结构中的xRxTimeout成员指定的块时间。阻塞时间将使用FreeRTOS 的vTaskSetTimeOutState()和xxTaskCheckForTimeOut()助手功能进行调整。
/* 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;
}


/*-----------------------------------------------------------*/
/* The interrupt service routine for the UART's receive interrupt */
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 );
 	}
Task Notifications Used in Peripheral Device Drivers: ADC Example

上一节演示了如何使用vTaskNotifyGiveFromISR()将任务通知从中断发送到任务。vTaskNotifyGiveFromISR()是一个简单的函数,但它的功能有限;它只能作为一个无价值的事件发送任务通知,它不能发送数据。本节演示如何使用xTaskNotifyFromISR()发送包含任务通知事件的数据。清单157中所示的伪代码演示了该技术,该伪代码为模拟到数字转换器(ADC)提供了一个具有RTOS感知的中断服务例程的大纲。在清单157中:

  • 假设至少每50毫秒开始一次ADC转换。
  • ADC_ConversionEndISR()是ADC的转换结束中断的中断服务例程,它是每次有一个新的ADC值可用时执行的中断。
  • 由vADCTask()实现的任务将处理由ADC生成的每个值。假设任务的句柄在创建任务时存储在xADCTaskToNotify中。
  • ADC_ConversionEndISR()使用xTaskNotifyFromISR()将eAction参数设置为eSetValueWithoutOverwrite,并向vADCTask()任务发送任务通知,并将ADC转换的结果写入任务的通知值。
  • vADCTask()任务使用xTaskNotifyWait()等待通知新的ADC值可用,并从其通知值检索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(0,/* 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(). */ 
 								  &ulADCValue,/* The address of the variable into which the task's notification value (which holds the latest ADC conversion result) will be copied. */ 
 								  xADCConversionFrequency * 2 );/* A new ADC value should be received every xADCConversionFrequency ticks. */
  
 		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. */
 		}
 	}
 }
 
/*-----------------------------------------------------------*/
/* 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 );
}
Task Notifications Used Directly Within an Application

本节通过演示任务通知在包含以下功能的假设应用程序中的使用,增强任务通知的功能:

  1. 该应用程序通过一个缓慢的互联网连接进行通信,以向远程数据服务器发送数据,并从其请求数据。从这里开始,远程数据服务器被称为云服务器。
  2. 从云服务器请求数据后,请求任务必须处于阻塞状态,等待接收所请求的数据。
  3. 在将数据发送到云服务器后,发送任务必须处于阻塞状态,等待确认云服务器已正确接收到数据。

软件设计的原理图如图81所示。在图81中:

  • 处理到云服务器的多个互联网连接的复杂性被封装在一个FreeRTOS任务中。该任务在FreeRTOS应用程序中充当代理服务器,并被称为服务器任务。
  • 应用程序任务通过调用CloudRead()从云服务器读取数据。CloudRead()不直接与云服务器通信,而是将读请求通过一个队列发送给服务器任务,并通过任务通知接收从服务器任务请求的数据。
  • 应用程序任务通过调用CloudWrite()向云服务器写入日期。CloudWrite()并不直接与云服务器通信,而是将写请求通过一个队列发送服务器任务,并通过任务通知接收来自服务器任务的写操作的结果。

由CloudRead()和CloudWrite()函数发送到服务器任务的结构如清单158所示。
在这里插入图片描述

typedef enum CloudOperations
{
 	eRead, /* Send data to the cloud server. */
 	eWrite /* Receive data from the cloud server. */
} Operation_t;

typedef struct CloudCommand
{
 	Operation_t eOperation; /* The operation to perform (read or write). */ 
 	uint32_t ulDataID; /* Identifies the data being read or written. */
 	uint32_t ulDataValue; /* Only used when writing data to the cloud server. */
 	TaskHandle_t xTaskToNotify;/* The handle of the task performing the operation. */
} CloudCommand_t;

/* ulDataID identifies the data to read. pulValue holds the address of the variable into which the data received from the cloud server is to be written. */
BaseType_t CloudRead( uint32_t ulDataID, uint32_t *pulValue )
{
	CloudCommand_t xRequest;
	BaseType_t xReturn;
 	/* Set the CloudCommand_t structure members to be correct for this read request. */
 	xRequest.eOperation = eRead; /* This is a request to read data. */
 	xRequest.ulDataID = ulDataID; /* A code that identifies the data to read. */
 	xRequest.xTaskToNotify = xTaskGetCurrentTaskHandle(); /* Handle of the calling task. */
 	/* Ensure there are no notifications already pending by reading the notification value with a block time of 0, then send the structure to the server task. */
 	xTaskNotifyWait( 0, 0, NULL, 0 );
 	xQueueSend( xServerTaskQueue, &xRequest, portMAX_DELAY );
 	/* Wait for a notification from the server task. The server task writes the value  received from the cloud server directly into this task’s notification value, so there is  no need to clear any bits in the notification value on entry to or exit from the  xTaskNotifyWait() function. The received value is written to *pulValue, so pulValue is  passed as the address to which the notification value is written. */
 	xReturn = xTaskNotifyWait( 0, /* No bits cleared on entry. */
 							   0, /* No bits to clear on exit. */
 							   pulValue, /* Notification value into *pulValue. */ 
 							   pdMS_TO_TICKS( 250 ) ); /* Wait a maximum of 250ms. */
 
 	/* If xReturn is pdPASS, then the value was obtained. If xReturn is pdFAIL, then the  request timed out. */
 	return xReturn;
}

void ServerTask( void *pvParameters )
{
	CloudCommand_t xCommand;
	uint32_t ulReceivedValue;
 	for( ;; ) 
 	{
 		/* Wait for the next CloudCommand_t structure to be received from a task. */
 		xQueueReceive( xServerTaskQueue, &xCommand, portMAX_DELAY );
 		switch( xCommand.eOperation ) /* Was it a read or write request? */
 		{
 			case eRead:
 				/* Obtain the requested data item from the remote cloud server. */
 				ulReceivedValue = GetCloudData( xCommand.ulDataID );
 				/* Call xTaskNotify() to send both a notification and the value received from the cloud server to the task that made the request. The handle of the task is  obtained from the CloudCommand_t structure. */
 				xTaskNotify( xCommand.xTaskToNotify, /* The task’s handle is in the structure. */
 							 ulReceivedValue, /* Cloud data sent as notification value. */
 							 eSetValueWithOverwrite );
 				break;
 				
 			case eWrite:
 				/* Send the data to the remote cloud server. SetCloudData() returns a bitwise  status code that only uses the bits defined by the CLOUD_WRITE_STATUS_BIT_MASK definition (shown in Listing 161). */
 				ulBitwiseStatusCode = SetCloudData( xCommand.ulDataID, xCommand.ulDataValue );
 				/* Send a notification to the task that made the write request. The eSetBits action is used so any status bits set in ulBitwiseStatusCode will be set in the  notification value of the task being notified. All the other bits remain  unchanged. The handle of the task is obtained from the CloudCommand_t  structure. */
				xTaskNotify( xCommand.xTaskToNotify, /* The task’s handle is in the structure. */
 							 ulBitwiseStatusCode, /* Cloud data sent as notification value. */
 							 eSetBits );
 				break;
 
 			/* Other switch cases go here. */
 		}
 	}
 }

/* Status bits used by the cloud write operation. */
#define SEND_SUCCESSFUL_BIT ( 0x01 << 0 )
#define OPERATION_TIMED_OUT_BIT ( 0x01 << 1
#define NO_INTERNET_CONNECTION_BIT ( 0x01 << 2 )
#define CANNOT_LOCATE_CLOUD_SERVER_BIT ( 0x01 << 3 )
/* A mask that has the four status bits set. */
#define CLOUD_WRITE_STATUS_BIT_MASK ( SEND_SUCCESSFUL_BIT | 
 									  OPERATION_TIMED_OUT_BIT |
 									  NO_INTERNET_CONNECTION_BIT | 
 									  CANNOT_LOCATE_CLOUD_SERVER_BIT ) 
uint32_t CloudWrite( uint32_t ulDataID, uint32_t ulDataValue ) 
{
	CloudCommand_t xRequest;
	uint32_t ulNotificationValue;
 	/* Set the CloudCommand_t structure members to be correct for this write request. */
 	xRequest.eOperation = eWrite; /* This is a request to write data. */
 	xRequest.ulDataID = ulDataID; /* A code that identifies the data being written. */
 	xRequest.ulDataValue = ulDataValue; /* Value of the data written to the cloud server. */
 	xRequest.xTaskToNotify = xTaskGetCurrentTaskHandle(); /* Handle of the calling task. */
 	/* Clear the three status bits relevant to the write operation by calling xTaskNotifyWait() with the ulBitsToClearOnExit parameter set to  CLOUD_WRITE_STATUS_BIT_MASK, and a block time of 0. The current notification value is  not required, so the pulNotificationValue parameter is set to NULL. */
 	xTaskNotifyWait( 0, CLOUD_WRITE_STATUS_BIT_MASK, NULL, 0 );
 	/* Send the request to the server task. */
 	xQueueSend( xServerTaskQueue, &xRequest, portMAX_DELAY );
 	/* Wait for a notification from the server task. The server task writes a bitwise status  code into this task’s notification value, which is written to ulNotificationValue. */
 	xTaskNotifyWait( 0, /* No bits cleared on entry. */
 					 CLOUD_WRITE_STATUS_BIT_MASK, /* Clear relevant bits to 0 on exit. */
 					 &ulNotificationValue, /* Notified value. */
 					 pdMS_TO_TICKS( 250 ) ); /* Wait a maximum of 250ms. */
 
 	/* Return the status code to the calling task. */
 	return ( ulNotificationValue & CLOUD_WRITE_STATUS_BIT_MASK );
 }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值