【ESP32+freeRTOS学习笔记-(十)任务通知机制】

1、任务通知(Task Notifications)概念

前面的几章已经描述了任务之间的各种通信方式。到目前为止描述的方法都需要创建具体的通信对象。这些通信对象包括队列、事件组和各种不同类型的信号量。

当使用通信对象时,事件和数据不会直接发送到接收任务或接收的ISR,而是发送到具体的通信对象。同样,任务和ISR从通信对象接收事件和数据,而不是直接从发送事件或数据的任务或ISR接收。如下图所示。
在这里插入图片描述

任务通知机制,允许任务与任务,中断的ISR与任务之间(只能是ISR发送到任务)直接进行通讯,而无需通过通信对象这个中介。如下示意:
在这里插入图片描述

1.1 概念

任务通知功能是可选的。要包含任务通知功能,请在FreeRTOSConfig.h中将configUSE_task_NOTIFICATIONS设置为1。
当configUSE_TASK_NOTIFICATIONS设置为1时,每个任务都有一个状态位叫做“通知状态”,它有“挂起pending”或“未挂起 not-pending”两种状态,以及一个“通知值”,它是一个32位无符号整数。当任务收到通知时,其通知状态设置为挂起。当任务读取其通知值时,其通知状态设置为未挂起。任务在等待通知时,可以进入“阻塞”状态,阻塞的时间可以通过设置等待时长来设定。

1.2 使用任务通知的优势

1、性能优势

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

2、资源占用优势

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

1.3 无法使用任务通知的场景

1、不能从任务到ISR
通信对象可用于将事件和数据从ISR发送到任务,以及从任务发送到ISR。
任务通知可用于将事件和数据从ISR发送到任务,但不能用于将事件或数据从任务发送到ISR

2、接收任务只能一个

任何知道其句柄(可能是队列句柄、信号量句柄或事件组句柄)的任务或ISR都可以访问通信对象。任何数量的任务和ISR都可以处理发送给任何给定通信对象的事件或数据。
任务通知直接发送给接收任务,因此只能由接收通知的任务处理。

3、不能接收多个值

队列是一个通信对象,一次可以容纳多个数据项。已发送到队列但尚未从队列接收的数据将缓冲在队列对象内。
任务通知通过更新接收任务的通知值向任务发送数据。任务的通知值一次只能保存一个值。

4、发送通知的任务无法进入阻塞等待通知成功传送

如果通信对象暂时处于无法向其写入更多数据或事件的状态(例如,当队列已满时,无法向队列发送更多数据),则尝试向对象写入的任务可以选择进入“阻塞”状态,以等待其写入操作完成。
如果任务尝试向已挂起通知的任务发送任务通知,则发送任务不可能在“阻塞”状态下等待接收任务重置其通知状态。

2、使用任务通知

任务通知是一个非常强大的功能,通常可以用来代替二进制信号量、计数信号量、事件组,有时甚至是队列。通过使用xTaskNotify()API函数发送任务通知和xTaskNotifyWait()API功能接收任务通知,可以实现广泛的使用场景。

FreeRTOS还提供了xTaskNotifyGive()和 ulTaskNotifyTake()两个相对简单的API函数对应 xTaskNotify()和xTaskNotifyWait()函数。

简单版全能版
xTaskNotifyGive()xTaskNotify
ulTaskNotifyTake()xTaskNotifyWait()

2.1 xTaskNotifyGive()

在这里插入图片描述
xTaskNotifyGive()直接向任务发送通知,并将接收任务的通知值递增(加一)。调用xTaskNotifyGive()将接收任务的通知状态设置为挂起(如果尚未挂起)。xTaskNotifyGive()API函数用于允许将任务通知用作二进制或计数信号量的更轻、更快的替代方案。

每个任务都有一个32位通知值,在创建任务时将其初始化为零。任务通知是直接发送给任务的事件,它可以取消接收任务的阻塞,并可以有选择地更新接收任务的通知值。

参数

参数说明
xTaskToNotify要向其发送通知的任务的句柄,请参阅xTaskCreate()API函数的pxCreatedTask参数以获取有关获取任务句柄的信息。

返回值
始终返回pdPASS

2.2 ulTaskNotifyTake()

在这里插入图片描述
ulTaskNotifyTake()允许任务在“阻塞”状态下等待其通知值大于零,并在返回之前减少(从中减去一)或清除任务的通知值。
提供了ulTaskNotifyTake()API函数,以允许将任务通知用作二进制或计数信号量的更轻、更快的替代方案。

参数

参数说明
xClearCountOnExit如果xClearCountOnExit设置为pdTRUE,则在返回对ulTaskNotifyTake()的调用之前,调用任务的通知值将被清除为零。如果xClearCountOnExit设置为pdFALSE,并且调用任务的通知值大于零,则在返回对ulTaskNotifyTake()的调用之前,调用任务的报告值将减小。
xTickToWait阻塞等待时间,时间单位tick

返回值
返回被清零或减值之前的通知值。

2.3 使用任务通知代替信号量,方法1

在之前的中断管理单节里,使用二进制信号量从中断服务例程中解除阻止任务,从而有效地将任务与中断同步。此示例复制了上一示例的功能,但使用直接到任务的通知代替二进制信号量。

ulTaskNotifyTake()xClearCountOnExit参数设置为pdTRUE,这将导致接收任务的通知值在ulTaskNotify Take()返回之前被清除为零。因此,有必要处理在每次调用ulTaskNotifyTake()之间已经可用的所有事件。在示例中,从ulTaskNotifyTake()返回挂起事件的数量(即返回被清除之前的通知值)。
在对ulTaskNotifyTake的调用之间发生的中断事件被锁定在任务的通知值中,如果调用任务已挂起通知,则对ulTaskNotifyTake()的调用将立即返回。

const TickType_t xInterruptFrequency = pdMS_TO_TICKS( 500UL );  //生成软件中断的频率
static void vHandlerTask( void *pvParameters )
{
	/* xMaxExpectedBlockTime 被设置为在两个事件之间间隔周期大10个毫秒。 */
	const TickType_t xMaxExpectedBlockTime = xInterruptFrequency + pdMS_TO_TICKS( 10 );
	uint32_t ulEventsToProcess;
 	for( ;; )
 	{
 		/* 此处阻塞以等待中断ISR直接发送过来的通知,并读取返回值。
 		这里阻塞超时时间比产生两个中断之间的周期要大于10毫秒,因此不会出现中断到达,而阻塞已到期,返回0的情况*/
 		ulEventsToProcess = ulTaskNotifyTake( pdTRUE, xMaxExpectedBlockTime );
 		if( ulEventsToProcess != 0 )
 		{
 			/* 要到达此处,必须至少发生了一个事件。在这里循环,直到处理完所有未决事件(在这种情况下,只需为每个事件打印一条消息)。 */
 			while( ulEventsToProcess > 0 )
 			{
 				vPrintString( " Processing event.\r\n" );
 				ulEventsToProcess--;
 			}
 		}
 		else
 		{
 			/* 如果达到了功能的这一部分,则中断未在预期时间内到达,并且(在实际应用程序中)可能需要执行一些错误恢复操作。 */
 		}
 	} 
}

用于生成软件中断的周期性任务在生成中断之前打印消息,并在生成中断之后再次打印消息。这允许在生成的输出中观察执行顺序。以下显示了中断处理程序。除了直接向中断处理被延迟的任务发送通知外,这几乎没有其他作用。

static uint32_t ulExampleInterruptHandler( void )
{
	BaseType_t xHigherPriorityTaskWoken;
  	xHigherPriorityTaskWoken = pdFALSE;
 	/* 直接向中断处理被延迟的任务发送通知。 */
 	vTaskNotifyGiveFromISR(  xHandlerTask, &xHigherPriorityTaskWoken );
   	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

其它代码,请参见【ESP32+freeRTOS学习笔记-(七)中断管理】中的4.3节。部分摘录如下图。
在这里插入图片描述

输出如下(要结合中断管理的4.3节中的其它代码一起判读):
在这里插入图片描述

执行时序
在这里插入图片描述

1、大部分时间里,都是空闲任务在运行。每隔500毫秒,周期任务Periodic抢占时间片运行。
2、Periodic执行时,先打印了“About to generate an interrupt”。然后生成一个软件中断,则ISR立即被调用。
3、ISR直接发送通知给vHandlerTask()任务,并使vHandlerTask()任务即出阻塞状态。然后ISR直接返回到 vHandlerTask()任务,因为这个任务是当前处于Ready状态的所有任务中优先级最高的。

4、vHandlerTask()在重新进入阻塞状态前, 打印出“Processing event.”。
5、Periodic任务再一次成为当下处于Ready状态的最高优先级的任务。在重新进入阻塞状态之前,打印出“Interrupt generated”。当它重新进入阻塞后,空闲任务又开始运行了。

2.4 使用任务通知代替信号量,方法2

在方法1中,ulTaskNotifyTake()xClearOnExit参数设置为pdTRUE。方法2稍微修改了方法1,以演示当ulTaskNotifyTake()xClearOnExit参数设置为pdFALSE时的行为。
当xClearOnExit为pdFALSE时,调用ulTaskNotifyTake()只会减少(减少一)调用任务的通知值,而不是将其清零。因此,通
知计数是已发生的事件数与已处理的事件数之间的差值。这允许以两种方式简化vHandlerTask()的结构:
1.等待处理的事件数保存在通知值中,因此不需要保存在局部变量中。
2.只需在每次调用ulTaskNotifyTake()之间处理一个事件。
使用的vHandlerTask()的实现如下面的代码所示。

在这里插入图片描述

出于演示的目的,中断服务例程也被修改为每个中断发送一个以上的任务通知,这样做可以模拟以高频率发生的多个中断。
在这里插入图片描述

输出结果如下:
在这里插入图片描述

2.5 xTaskNotify() 与 xTaskNotifyFromISR()

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

-> 增加接收任务的通知值(加一),在这种情况下 xTaskNotify()与xTaskNotifyGive()等效。

-> 在接收任务的通知值中设置一个或多个位。这允许将任务的通知值用作事件组的更轻、更快的替代方案。
将一个全新的数字写入接收任务的通知值,但前提是接收任务自上次更新后已读取其通知值。这允许任务的通知值提供与长度为1的队列所提供的功能类似的功能。

-> 在接收任务的通知值中写入一个全新的数字,即使接收任务自上次更新后尚未读取其通知值。这允许任务的通知值提供与xQueueOverwrite()API函数提供的功能类似的功能。由此产生的行为有时被称为“邮箱”。

xTaskNotify()比xTaskNotifyGive()更灵活、更强大,而且由于其额外的灵活性和功能,使用起来也稍微复杂一些。
xTaskNotifyFromISR()是xTaskNotify()的一个版本,可以在中断服务例程中使用,因此具有额外的pxHigherPriorityTaskWoken参数。
调用xTaskNotify()将始终将接收任务的通知状态设置为挂起(如果它尚未挂起)。

在这里插入图片描述
在这里插入图片描述

参数

参数说明
xTaskToNotify接收通知的任务句柄
ulValueulValue的使用方式取决于eNotifyAction值。见下表一。
eAction指定如何更新接收任务的通知值的枚举类型。见下表一。

返回值
xTaskNotify()将返回pdPASS,表一中所示的情况除外。

表一

eNotifyAction 值对接收任务的结果影响
eNoAction接收任务的通知状态设置为挂起,而不更新其通知值。未使用xTaskNotify()ulValue参数。eNoAction动作允许将任务通知用作二进制信号量的更快、更轻的替代方案。
eSetBits接收任务的通知值与xTaskNotify()ulValue参数中传递的值按位“或”运算。例如,如果ulValue设置为0x01,则接收任务的通知值中将设置位0。作为另一个示例,如果ulValue是0x06(二进制0110),那么将在接收任务的通知值中设置位1和位2。eSetBits操作允许将任务通知用作事件组的更快、更轻的替代方案。
eIncrement接收任务的通知值将递增。未使用xTaskNotify()ulValue参数。eIncrement操作允许将任务通知用作二进制或计数信号量的更快、更轻的替代方案,并且与更简单的xTaskNotifyGive()API函数等效。
eSetValueWithoutOverwrite如果接收任务在调用xTaskNotify()之前有一个挂起的通知,则不执行任何操作,xTaskNotify()将返回pdFAIL。如果接收任务没有在调用xTask Notify(()之前挂起通知,则将接收任务的通知值设置为在xTaskNotify()ulValue参数中传递的值。
eSetValueWithOverwrite接收任务的通知值设置为xTaskNotify()ulValue参数中传递的值,而不管在调用xTaskNotify()之前接收任务是否有挂起的通知。

2.6 xTaskNotifyWait()

在这里插入图片描述

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

参数

参数说明
ulBitsToClearOnEntry如果调用任务在调用xTaskNotifyWait()之前没有挂起的通知,则在进入函数时,将在任务的通知值中清除ulBitsToClearOnEntry中设置的任何位。例如,如果ulBitsToClearOnEntry为0x01,则将清除任务通知值的位0。另一个示例是,将ulBitsToClearOnEntry设置为0xffffffff(ULONG_MAX)将清除任务通知值中的所有位,从而有效地将该值清除为0。
ulBitsToClearOnExit如果调用任务退出xTaskNotifyWait()是因为它收到了通知,或者是因为它在调用xTaskNotifyWait()时已经有一个通知挂起,那么在任务退出xTask Notifywait()函数之前,ulBitsToClearOnExit中设置的任何位都将在任务的通知值中清除。任务的通知值保存在*pulNotificationValue中后,这些位将被清除(请参阅下面pulNotificationValue的描述)。例如,如果ulBitsToClearOnExit为0x03,则任务通知值的第0位和第1位将在函数退出之前被清除。将ulBitsToClearOnExit设置为0xffffffff(ULONG_MAX)将清除任务通知值中的所有位,从而有效地将该值清除为0
pulNotificationValue用于传递任务的通知值。复制到*pulNotificationValue的值是由于ulBitsToClearOnExit设置而清除任何位之前的任务通知值。ulNotificationValue是可选参数,如果不需要,可以设置为NULL。
xTicksToWait调用任务应保持在“阻塞”状态以等待其通知状态变为挂起的最长时间。阻塞时间单为为刻度周期,因此它表示的绝对时间取决于刻度频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以刻度为单位的时间。如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(无超时)。

返回值
有两种可能的返回值:
1.pdTRUE
这表示xTaskNotifyWait()返回是因为收到了通知,或者是因为调用xTaskNotifyWait()时调用任务已挂起通知。
如果指定了阻塞时间(xTicksToWait不为零),则调用任务可能被置于“阻塞”状态的时间到期前,通知状态变为“挂起”。
2.pdFALSE
这表示xTaskNotifyWait()返回时调用任务未收到任务通知。
如果xTicksToWait不为零,则表示调用任务保持在“阻塞”状态时间到期后,通知状态未被“挂起”。

2.7 外设驱动程序中使用的任务通知:UART示例

外围驱动程序库提供在硬件接口上执行常见操作的功能。通常提供这种库的外围设备的示例包括通用异步接收机和发射机(UART)、串行外围接口(SPI)端口、模数转换器(ADC)和以太网端口。这些库通常提供的功能的示例包括初始化外围设备、向外围设备发送数据和从外围设备接收数据的功能。

外围设备上的某些操作需要相对较长的时间才能完成。此类操作的示例包括高精度ADC转换和在UART上传输大数据包。在这些情况下,可以实现驱动程序库功能来轮询(重复读取)外围设备的状态寄存器,以确定操作何时完成。然而,这种方式的轮询几乎总是浪费的,因为它利用了100%的处理器时间,而没有执行生产性处理。在多任务系统中,这种浪费尤其昂贵,因为轮询外围设备的任务可能会阻止执行低优先级任务,而该任务确实需要执行生产性处理。

为了避免浪费处理时间的可能性,一个高效的RTOS感知设备驱动程序应该是中断驱动的,并为启动长时间操作的任务提供在阻塞状态下等待操作完成的选项。这样,当执行长操作的任务处于“阻塞”状态时,可以执行优先级较低的任务,除非任务能够高效地使用处理时间,否则任何任务都不会占用处理时间。

做为对比。以下伪代码会采用两种方式实现。一个是用二进制信号量,一个是用任务通知的方式。以此对比相互的优劣势。

2.7.1 二进制信号量完成UART驱动

RTOS感知驱动程序库通常使用二进制信号量将任务置于“阻塞”状态。以下所示的伪代码演示了该技术,它提供了一个RTOS感知库函数的概要,该函数通过UART端口传输数据。

xUART是一种描述UART外设并保存状态信息的结构。结构的xTxSemaphore成员是SemaphoreHandle_t类型的变量。假设信号量已经创建。

xUART_Send()函数不包含任何互斥逻辑。如果有多个任务要使用xUART_Send()函数,那么应用程序编写器将必须管理应用程序本身内的互斥。例如,在调用xUART_Send()之前,可能需要一个任务来获取互斥锁。

xSemaphoreTake()API函数用于在启动UART传输后将调用任务置于Blocked状态。

xSemaphoreGiveFromISR()API函数用于在传输完成后(即UART外围设备的传输结束中断服务例程执行时)将任务从Blocked状态移除。

这个程序的逻辑是发送函数xUART_Send()先拿到二值信号量,然后执行串口发送(异步)后,再次拿二值信号量,此时已无锁,只能阻塞等待。此时,一旦串口发送后会触发中断,在中断中会Give二值信号量。使xUART_Send退出阻塞。

/* 发送数据到UART的驱动函数. */
BaseType_t xUART_Send( xUART *pxUARTInstance, uint8_t *pucDataSource, size_t uxLength )
{
	BaseType_t xReturn;
 	/*通过尝试在没有超时的情况下获取信号量,确保UART的传输信号量不可用。*/
 	xSemaphoreTake( pxUARTInstance->xTxSemaphore, 0 );
 	/* Start the transmission.这是真正的传输任务函数,启动该任务后,程序就进入下一个xSemaphoreTake,进入阻塞 */
 	UART_low_level_send( pxUARTInstance, pucDataSource, uxLength );
 
 	/* 阻塞信号量以等待传输完成。如果获得了信号量,则xReturn将设置为pdPASS。如果信号量获取操作超时,则xReturn将设置为pdFAIL。
 	注意,如果在调用UART_low_level_send()和调用xSemaphoreTake()之间发生中断,则事件将被锁存在二进制信号量中,
 	对xSemaphoeTake的调用将立即返回。 */
 	xReturn = xSemaphoreTake( pxUARTInstance->xTxSemaphore, pxUARTInstance->xTxTimeout );
 
 	return xReturn;
}
/*-----------------------------------------------------------*/
/* UART发送结束中断的服务例程,在最后一个字节发送到UART后执行。 */
void xUART_TransmitEndISR( xUART *pxUARTInstance )
{
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 	/* Clear the interrupt. */
 	UART_low_level_interrupt_clear( pxUARTInstance );
 
 	/* 给出Tx信号量,表示传输结束。如果任务在等待信号量时被阻止,则该任务将从“阻塞”状态中删除。 */
 	xSemaphoreGiveFromISR( pxUARTInstance->xTxSemaphore, &xHigherPriorityTaskWoken );
 	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

以上演示的技术是完全可行的,实际上也是常见的做法,但它有一些缺点:
1、该库使用多个信号量,这增加了其RAM占用空间。
2、 信号量在创建之前无法使用,因此使用信号量的库在显式初始化之前无法使用。
3、信号量是适用于广泛用例的通用对象;它们包括允许任意数量的任务在阻塞状态下等待信号量变为可用的逻辑,以及在信号量变可用时选择(以确定性的方式)从阻塞状态移除哪个任务。执行该逻辑需要有限的时间,实际,这些处理开销是不必要的,在该场景中,在任何给定时间都不能有多个任务等待信号量。
因此,还可以用任务通知实现。如下。

2.7.2 采用任务通知实现UART 驱动

以下的伪代码演示了如何通过使用任务通知代替二进制信号量来避免这些缺点。

注意:如果库使用任务通知,那么库的文档必须明确说明调用库函数可以更改调用任务的通知状态和通知值。

xUART结构的xTxSemaphore成员已被xTaskToNotify成员替换。xTaskToNotify是TaskHandle_t类型的变量,用于保存等待UART操作完成的任务的句柄。

xTaskGetCurrentTaskHandle()FreeRTOS API函数用于获取处于运行状态的任务的句柄。

该库不创建任何FreeRTOS对象,因此不会产生RAM开销,也不需要显式初始化。

任务通知直接发送到等待UART操作完成的任务,因此不会执行不必要的逻辑。

/*  */
BaseType_t xUART_Send( xUART *pxUARTInstance, uint8_t *pucDataSource, size_t uxLength )
{
	BaseType_t xReturn;
 	/* 保存调用此函数的任务的句柄。书中包含了以下行是否需要由关键部分保护的注释。 */
 	pxUARTInstance->xTaskToNotify = xTaskGetCurrentTaskHandle();
 
 	ulTaskNotifyTake( pdTRUE, 0 );
 	/* 开始发送 ,这是真正的传输任务,创建后,就进入下一语句执行ulTaskNotifyTake后再次进入阻塞。*/
 	UART_low_level_send( pxUARTInstance, pucDataSource, uxLength );
 	/* 阻塞,直到通知传输完成。如果收到通知,则xReturn将设置为1,因为ISR已将此任务的通知值增加为1(pdTRUE)。如果操作超时,
 	则xReturn将为0(pdFALSE),因为此任务的通知值在清除为0之后不会更改。注意,如果ISR在调用UART_low_level_send()和调用
 	ulTaskNotifyTake()之间执行,则事件将被锁定在任务的通知值中,并且对ulTaskNotify Take()的调用将立即返回。*/
 	xReturn = ( BaseType_t ) ulTaskNotifyTake( pdTRUE, pxUARTInstance->xTxTimeout );
 	return xReturn;
}
/*-----------------------------------------------------------*/
/* 在最后一个字节发送到UART后执行的ISR。 */
void xUART_TransmitEndISR( xUART *pxUARTInstance )
{
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 	/* 除非有任务等待通知,否则不应执行此函数。使用断言测试此条件。这一步骤并非绝对必要,但有助于调试。
 	configASSERT()在第11.2节中描述。*/
 	configASSERT( pxUARTInstance->xTaskToNotify != NULL );
 
	 /* Clear the interrupt. */
 	UART_low_level_interrupt_clear( pxUARTInstance );
 	/* 直接向调用xUART_Send()的任务发送通知。如果任务被阻止等待通知,则该任务将从“阻止”状态中删除。 */
 	vTaskNotifyGiveFromISR( pxUARTInstance->xTaskToNotify, &xHigherPriorityTaskWoken );
 
 	/*现在没有任务等待通知。将xUART结构的xTaskToNotify成员设置回NULL。这一步骤并非绝对必要,但有助于调试。 */
 	pxUARTInstance->xTaskToNotify = NULL;
 	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

2.7.3 UART接收函数中使用任务通知

任务通知还可以替换接收函数中的信号量,如伪代码清单156所示,它提供了一个RTOS感知库函数的概要,该函数通过UART端口接收数据

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()和xTaskCheckForTimeOut()帮助函数调整阻塞时间。

/* 从UART接收数据的驱动程序 */
size_t xUART_Receive( xUART *pxUARTInstance, uint8_t *pucBuffer, size_t uxWantedBytes )
{
	size_t uxReceived = 0;
	TickType_t xTicksToWait;
	TimeOut_t xTimeOut;
 	/* 记录进入此功能的时间。 */
 	vTaskSetTimeOutState( &xTimeOut );
 	/* xTicksToWait是超时值-它最初设置为此UART实例的最大接收超时。*/
 	xTicksToWait = pxUARTInstance->xRxTimeout;
 	/* 保存调用此函数的任务的句柄。 */
 	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 )
 	{
 		/* 查找超时,调整xTicksToWait以考虑到目前为止在该函数中花费的时间。pdFALSE说明已超时*/
 		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) != pdFALSE )
 		{
 			/*在所需的字节数可用之前超时,请退出循环。 */
 			break;
		 }
 		/* 接收缓冲区尚未包含所需的字节数。等待最多xTicksToWait信号,以通知接收中断服务例程已将更多数据放入缓冲区。
 		如果调用任务在调用此函数时已经有一个挂起的通知,这并不重要,如果有,它只会在循环一次的同时重复此通知。 */
 		ulTaskNotifyTake( pdTRUE, xTicksToWait );
 	}
 	/*没有任务正在等待接收通知,因此将xTaskToNotify设置回NULL。 */
 	pxUARTInstance->xTaskToNotify = NULL;
 	/* 尝试将uxWantedBytes从接收缓冲区读取到pucBuffer。返回实际读取的字节数(可能小于uxWantedBytes)。 */
 	uxReceived = UART_read_from_receive_buffer( pxUARTInstance, pucBuffer, uxWantedBytes );
 	return uxReceived;
}
/*-----------------------------------------------------------*/
/* UART接收中断的中断服务例程 */
void xUART_ReceiveISR( xUART *pxUARTInstance )
{
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
 	/* 将接收到的数据复制到此UART的接收缓冲区并清除中断。*/
 	UART_low_level_receive( pxUARTInstance );
 	/* 如果任务正在等待新数据的通知,请立即通知它。 */
 	if( pxUARTInstance->xTaskToNotify != NULL )
 	{
 		vTaskNotifyGiveFromISR( pxUARTInstance->xTaskToNotify, &xHigherPriorityTaskWoken );
 		portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
 	}
 }

3、总结

总之,任务通知机制,也是任务之间传送事件与数据的一个工具。是一个能替代二进制信号量,计数信号量,事件组,队列更高效的机制。任务通知机制传送效率更高,占用的RAM更小。FreeRTOS提供了两套使用任务通知机制的API函数,一套是简单方便的xTaskNotifyGive()和 ulTaskNotifyTake(),另一套是功能更强大灵活的API函数 xTaskNotify()和xTaskNotifyWait()函数。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ESP32FreeRTOS使用上与传统的FreeRTOS有一些区别。在ESP32中,基本不需要单独配置FreeRTOSConfig.h文件,因为ESP-IDF中的menuconfig功能可以对所有涉及到的内容进行配置,使用起来更加直观和便利。主要的数据类型说明中,有一个重要的数据类型是TickType_t。 在ESP32的魔改版FreeRTOS中,很少使用正经的事件集,而是使用ESP-IDF提供的更方便的事件循环。这使得在ESP32中使用事件循环更加方便。 另外,ESP32的分区表是采用二进制格式而不是CSV文件。ESP-IDF提供了gen_esp32part.py工具来配置和构建分区表。默认情况下,使用的是默认分区表。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【ESP32+freeRTOS学习笔记-(一)freeRTOS介绍】](https://blog.csdn.net/weixin_45499326/article/details/128226443)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [ESPIDF开发ESP32学习笔记ESP32上的FreeRTOS】](https://blog.csdn.net/qq_40500005/article/details/114794039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑牛唱剧本

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值