FreeRTOS 教程指南 学习笔记 第六章 中断管理(二)

FreeRTOS 教程指南 学习笔记 第六章 中断管理(二)

六、将工作延迟到RTOS守护进程任务

到目前为止,所有延迟中断处理示例,要求应用程序作者,为每个使用延迟处理技术的中断创建一个任务。也可以使用xTimerPendFunctionCallFromISR()API函数来将中断处理延迟到RTOS守护进程任务,不需要为每个中断创建一个单独的任务。将中断处理延迟到守护进程任务称为“集中式延迟中断处理”。
第5章描述了与软件计时器相关的FreeRTOS API函数如何向计时器命令队列上的守护进程任务发送命令。xTimerPendFunctionCall()和xTimerPendFunctionCallFromISR()API函数使用相同的计时器命令队列向守护进程任务发送一个“执行函数”命令。发送到守护任务的函数将在守护任务的上下文中执行。
集中式延迟中断处理的优点包括:

  • 较低的资源使用
    它消除了为每个延迟中断创建一个单独的任务。
  • 简化的用户模型
    延迟中断处理函数是一个标准的C函数。

集中式延迟中断处理的缺点包括:

  • 灵活性较小
    不可能单独设置每个延迟中断处理任务的优先级。每个延迟的中断处理函数都在守护进程任务的优先级上执行。如第5章所述,守护进程任务的优先级是由FreeRTOSConfig.h中的configTIMER_TASK_PRIORITY编译时配置常量设置的。
  • 不确定性
    xTimerPendFunctionCallFromISR()将一个“执行功能”命令发送到计时器命令队列的后面。已经在计时器命令队列中的命令,将在xTimerPendFunctionCallFromISR()发送“执行功能”之前,被守护进程任务处理。

不同的中断有不同的时间约束,因此在同一应用程序中使用这两种延迟中断处理的方法是很常见的。

The xTimerPendFunctionCallFromISR() API Function

xTimerPendFunctionCallFromISR()是xTimerPendFunctionCall()的中断安全版本。这两个API函数都允许由应用程序作者提供的函数由RTOS守护进程任务执行。要执行的函数和该函数的输入参数的值都将被发送到定时器命令队列上的守护程序任务。函数实际如何执行,依赖于守护进程任务相对于其他任务的优先级。

//Listing 100. The xTimerPendFunctionCallFromISR() API function prototype
BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, void *pvParameter1, uint32_t ulParameter2, BaseType_t *pxHigherPriorityTaskWoken );

//Listing 101. The prototype to which a function passed in the xFunctionToPend parameter of xTimerPendFunctionCallFromISR() must conform
void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 );

/*参数xFunctionToPend:一个指向将在守护进程任务中执行的函数的指针(实际上只是函数名)。该函数的原型必须与Listing 101中所示的原型相同*/
/*参数pvParameter1:将作为守护进程任务的pvparather1参数执行的函数的值。该参数有一个void*类型,允许使用它来传递任何数据类型。例如,整数类型可以直接转换为一个void*,或者void*可以用来指向一个结构。*/
/*参数ulParameter2:将传递给守护进程任务执行的函数的ulParameter2值。*/
/*参数pxHigherPriorityTaskWoken:xTimerPendFunctionCallFromISR()写入计时器命令队列。如果RTOS守护进程任务处于阻止状态,并等待计时器命令队列上的数据可用,那么写入计时器命令队列将导致守护进程任务离开阻止状态。如果守护进程任务的优先级高于当前执行的任务(被中断的任务),那么,在内部,xTimerPendFunctionCallFromISR()将*pxHigherPriorityTaskWoken设置为pdTRUE。
								如果xTimerPendFunctionCallFromISR()将此值设置为pdTRUE,那么在退出中断之前必须执行上下文切换。这将确保中断直接返回到守护进程任务,因为守护进程任务将是最高优先级的准备就绪状态任务。*/
/*返回值:有两个可能的返回值:
		 1.如果命令被成功写入计时器命令队列,将返回pdPASS。 
		 2. 如果无法将“执行函数”的命令写入计时器命令队列,因为计时器命令队列已满,则将返回pdFAIL。第5章介绍了如何设置计时器命令队列的长度。*/
Example 18. Centralized deferred interrupt processing

示例18提供了与示例16类似的功能,但没有使用信号量,也没有创建专门用来执行中断所需要的处理的任务。相反,该处理将由RTOS守护进程任务执行。
示例18所使用的中断服务例程如清单102所示。它调用xTimerPendFunctionCallFromISR()来传递一个指向函数vDeferredHandlingFunction()的指针到守护进程任务。延迟中断处理由vDeferredHandlingFunction()函数执行。

static uint32_t ulExampleInterruptHandler( void )
{
	static uint32_t ulParameterValue = 0;
	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 pointer to the interrupt's deferred handling function to the daemon task. The deferred handling function's pvParameter1 parameter is not used so just set to  NULL. The deferred handling function's ulParameter2 parameter is used to pass a  number that is incremented by one each time this interrupt handler executes. */
 	xTimerPendFunctionCallFromISR( vDeferredHandlingFunction, /* Function to execute. */
 									NULL, /* Not used. */
 									ulParameterValue, /* Incrementing value. */
 									&xHigherPriorityTaskWoken );
 	ulParameterValue++;
 	/* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR(). If xHigherPriorityTaskWoken was set to pdTRUE inside xTimerPendFunctionCallFromISR() then calling portYIELD_FROM_ISR() will request a context switch. If  xHigherPriorityTaskWoken is still pdFALSE then calling portYIELD_FROM_ISR() will have  no effect. Unlike most FreeRTOS ports, the Windows port requires the ISR to return a  value - the return statement is inside the Windows version of portYIELD_FROM_ISR(). */
 	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

static void vDeferredHandlingFunction( void *pvParameter1, uint32_t ulParameter2 )
{ 
 	/* Process the event - in this case just print out a message and the value of 
 ulParameter2. pvParameter1 is not used in this example. */
 	vPrintStringAndNumber( "Handler function - Processing event ", ulParameter2 );
}

int main( void )
{
	/* The task that generates the software interrupt is created at a priority below the priority of the daemon task. The priority of the daemon task is set by the configTIMER_TASK_PRIORITY compile time configuration constant in FreeRTOSConfig.h. */
	const UBaseType_t ulPeriodicTaskPriority = configTIMER_TASK_PRIORITY - 1;
 	/* Create the task that will periodically generate a software interrupt. */
 	xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, ulPeriodicTaskPriority, NULL );
 	/* Install the handler for the software interrupt. The syntax necessary to do this is dependent on the FreeRTOS port being used. The syntax shown here can  only be used with the FreeRTOS windows port, where such interrupts are only  simulated. */
 	vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
 	/* Start the scheduler so the created task starts executing. */
 	vTaskStartScheduler();
 
 	/* As normal, the following line should never be reached. */
 	for( ;; );
}

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

七、在中断服务例程中使用队列

二进制文件和计数信号量用于通知事件的产生。队列用于通知事件的产生并传输数据。
xQueueSendToFrontFromISR()是xQueueSendToFront()的中断安全版本,xQueueSendToBackFromISR()是xQueueSendToBack()的中断安全版本,xQueueReceiveFromISR()是xQueueReceive()的中断安全版本

The xQueueSendToFrontFromISR() and xQueueSendToBackFromISR() API Functions
//Listing 105. The xQueueSendToFrontFromISR() API function prototype
BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue, void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );

//Listing 106. The xQueueSendToBackFromISR() API function prototype
BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue, void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );

/*参数xQueue:要发送数据(写入)的队列的句柄。队列句柄将从对用于创建队列的xQueueCreate()的调用中返回。*/
/*参数pvItemToQueue:一个指向将被复制到队列中的数据的指针。队列可以保存的每个项的大小是在创建队列时设置的,因此这些字节将从pvItemToQueue复制到队列存储区域中。*/
/*参数pxHigherPriorityTaskWoken:单个队列可能有一个或多个任务,等待数据可用。调用xQueueSendToFrontFromISR()或xQueueSendToBackFromISR()可以使数据可用,从而导致这样的任务离开阻塞状态。如果调用API函数导致任务离开阻塞状态,并且未阻塞任务的优先级高于当前执行的任务(被中断的任务),那么,API函数将pxHigherPriorityTaskWoken设置为pdTRUE。
								如果xQueueSendToFrontFromISR()或xQueueSendToBackFromISR将这个值设置为pdTRUE,那么在退出中断之前应该执行一个上下文切换。这将确保中断直接返回到最高优先级的准备就绪状态任务。*/
/*返回值:有两个可能的返回值:
		 1.pdPASS 返回当数据已成功发送到队列。 
		 2. 如果由于队列已满,数据无法发送到队列,则返回errQUEUE_FULL*/

xQueueSendFromISR() 和 xQueueSendToBackFromISR()是等效的。

使用来自ISR的队列时的注意事项

队列提供了一种简单而方便的将数据从中断传递到任务的方法,但是如果数据到达的频率较高,那么使用队列并不有效。
FreeRTOS下载中的许多演示应用程序都包括一个简单的UART驱动程序,它使用一个队列从UART的接收ISR中传递字符。在这些演示中,使用队列有两个原因:演示从ISR中使用的队列,以及故意加载系统以测试FreeRTOS端口。以这种方式使用队列的ISR绝对不能表示有效的设计,除非数据到达的速度变慢,否则建议生产代码不要复制该技术。适合用于生产代码的更高效的技术包括:

  • 使用直接内存存取(DMA)硬件来接收和缓冲字符。这种方法实际上没有任何软件开销。然后,可以使用直接到任务的通知来解除任务阻塞,任务仅在检测到传输中断后才会处理缓冲区的数据。
  • 将每个接收到的字符复制到一个线程安全的RAM缓冲区中。同样,可以使用直接到任务的通知来取消任务阻塞,在收到完整消息后或检测到传输中断后,将处理缓冲区的任务。
  • 在ISR中直接处理接收到的字符,然后使用队列将处理过后结果(而不是原始数据)发送给任务。之前的图34已经证明了这一点。
Example 19. Sending and receiving on a queue from within an interrupt

这个示例演示了从xQueueSendToBackFromISR()和xQueueReceiveFromISR()。和前面一样,为了方便起见,中断是由软件生成的。

static void vIntegerGenerator( void *pvParameters )
{
	TickType_t xLastExecutionTime;
	uint32_t ulValueToSend = 0;
	int i;
 	/* Initialize the variable used by the call to vTaskDelayUntil(). */
 	xLastExecutionTime = xTaskGetTickCount();
 	for( ;; )
 	{
 		/* This is a periodic task. Block until it is time to run again. The task will execute every 200ms. */
 	vTaskDelayUntil( &xLastExecutionTime, pdMS_TO_TICKS( 200 ) );
 		/* Send five numbers to the queue, each value one higher than the previous value. The numbers are read from the queue by the interrupt service routine.  The interrupt service routine always empties the queue, so this task is  guaranteed to be able to write all five values without needing to specify a  block time. */
 		for( i = 0; i < 5; i++ )
 		{
 			xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
 			ulValueToSend++;
 		}
 		/* Generate the interrupt so the interrupt service routine can read the values from the queue. The syntax used to generate a software interrupt is  dependent on the FreeRTOS port being used. The syntax used below can only be  used with the FreeRTOS Windows port, in which such interrupts are only  simulated.*/
 		vPrintString( "Generator task - About to generate an interrupt.\r\n" );
 		vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
 		vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
 	}
 }

static uint32_t ulExampleInterruptHandler( void )
{
	BaseType_t xHigherPriorityTaskWoken;
	uint32_t ulReceivedNumber;
	/* The strings are declared static const to ensure they are not allocated on the interrupt service routine's stack, and so exist even when the interrupt service routine is not executing. */
	static const char *pcStrings[] =
	{
 		"String 0\r\n",
 		"String 1\r\n",
 		"String 2\r\n",
 		"String 3\r\n"
	};
	 /* As always, xHigherPriorityTaskWoken is initialized to pdFALSE to be able to detect it getting set to pdTRUE inside an interrupt safe API function. Note that as an interrupt safe API function can only set xHigherPriorityTaskWoken to pdTRUE, it is safe to use the same xHigherPriorityTaskWoken variable in both the call to xQueueReceiveFromISR() and the call to xQueueSendToBackFromISR(). */
 	xHigherPriorityTaskWoken = pdFALSE;
 	/* Read from the queue until the queue is empty. */
 	while( xQueueReceiveFromISR( xIntegerQueue, 
 								&ulReceivedNumber, 
 								&xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
 	{
 		/* Truncate the received value to the last two bits (values 0 to 3 inclusive), then use the truncated value as an index into the pcStrings[] array to select a string (char *) to send on the other queue. */
 		ulReceivedNumber &= 0x03;
 		xQueueSendToBackFromISR( xStringQueue, 
 								&pcStrings[ ulReceivedNumber ], 
 								&xHigherPriorityTaskWoken );
	}
 	/* If receiving from xIntegerQueue caused a task to leave the Blocked state, and  if the priority of the task that left the Blocked state is higher than the  priority of the task in the Running state, then xHigherPriorityTaskWoken will  have been set to pdTRUE inside xQueueReceiveFromISR().
 	 If sending to xStringQueue caused a task to leave the Blocked state, and if the  priority of the task that left the Blocked state is higher than the priority of  the task in the Running state, then xHigherPriorityTaskWoken will have been set  to pdTRUE inside xQueueSendToBackFromISR(). xHigherPriorityTaskWoken is used as the parameter to portYIELD_FROM_ISR(). 
 	 If xHigherPriorityTaskWoken equals pdTRUE 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 );
}

static void vStringPrinter( void *pvParameters )
{
	char *pcString;
 	for( ;; )
 	{
 		/* Block on the queue to wait for data to arrive. */
 		xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
 		/* Print out the string received. */
 		vPrintString( pcString );
 	}
 }

int main( void )
{
 	/* Before a queue can be used it must first be created. Create both queues used by this example. One queue can hold variables of type uint32_t, the other queue can hold variables of type char*. Both queues can hold a maximum of 10 items. A  real application should check the return values to ensure the queues have been  successfully created. */
 	xIntegerQueue = xQueueCreate( 10, sizeof( uint32_t ) );
 	xStringQueue = xQueueCreate( 10, sizeof( char * ) );
 	/* Create the task that uses a queue to pass integers to the interrupt service routine. The task is created at priority 1. */
 	xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );
 	/* Create the task that prints out the strings sent to it from the interrupt service routine. This task is created at the higher priority of 2. */
 	xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );
 	/* Install the handler for the software interrupt. The syntax necessary to do  this is dependent on the FreeRTOS port being used. The syntax shown here can  only be used with the FreeRTOS Windows port, where such interrupts are only  simulated. */
 	vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
 	/* Start the scheduler so the created tasks start executing. */
 	vTaskStartScheduler();
 
 	/* If all is well then main() will never reach here as the scheduler will now be  running the tasks. If main() does reach here then it is likely that there was  insufficient heap memory available for the idle task to be created. Chapter 2 provides more information on heap memory management. */
 	for( ;; );
}

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

八、中断嵌套

在任务优先级和中断优先级之间通常会出现混淆。本节讨论中断优先级,这是所有中断服务例程(ISR)相对于彼此执行的优先级。分配给任务的优先级与分配给中断的优先级完全无关。硬件决定何时执行ISR,而软件则决定何时执行任务。为响应硬件中断而执行的ISR将中断任务,但任务不能抢占ISR。
支持中断嵌套的分支需要在FreeRTOSConfig.h中定义表39中详细说明的一个或两个常量。configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY都定义了相同的属性。旧的FreeRTOS端口使用configMAX_SYSCALL_INTERRUPT_PRIORITY,而更新的FreeRTOS端口使用configMAX_API_CALL_INTERRUPT_PRIORITY。

ConstantDescription
configMAX_SYSCALL_INTERRUPT_PRIORITY设置允许调用中断安全FrereRTOSAPI函数的最高中断优先级。
configMAX_API_CALL_INTERRUPT_PRIORITY同上
configKERNEL_INTERRUPT_PRIORITY设置tick interrupt所使用的中断优先级,并且必须始终设置为尽可能低的中断优先级。如果正在使用的FreeRTOS分支没有使用configMAX_SYSCALL_INTERRUPT_PRIORITY常量,那么任何使用中断安全的FreeRTOS API函数的中断也必须在configKERNEL_INTERRUPT_PRIORITY定义的优先级上执行。

每个中断源都具有一个数字优先级和一个逻辑优先级:

  • 数字优先级
    数字优先级只是分配给中断优先级的数字。例如,如果分配一个中断的优先级为7,那么它的数字优先级为7。同样地,如果一个中断的优先级为200,那么它的数字优先级为200。
  • 逻辑优先级
    中断的逻辑优先级描述了该中断的优先级高于其他中断。
    如果两个优先级不同的中断同时发生,那么处理器将对两个中断中逻辑优先级较高的中断执行ISR,然后对两个中断中逻辑优先级较低的中断执行ISR。
    中断可以中断(嵌套)任何具有较低逻辑优先级的中断,但中断不能中断(嵌套)任何具有相同或更高逻辑优先级的中断。

中断的数字优先级和逻辑优先级之间的关系取决于处理器架构;在某些处理器上,分配给中断的数字优先级越高,中断的逻辑优先级就越高,而在其他处理器架构上,分配给中断的数字优先级越高,中断的逻辑优先级就越低。
通过将configMAX_SYSCALL_INTERRUPT_PRIORITY设置为比configKERNEL_INTERRUPT_PRIORITY更高的逻辑中断优先级来创建一个完整的中断嵌套模型。如图61所示,它显示了这样一个场景:

  • 该处理器有七个独特的中断优先级。
  • 分配的数字优先级为7的中断比分配的数字优先级为1的中断具有更高的逻辑优先级。
  • configKERNEL_INTERRUPT_PRIORITY被设置为1。
  • configMAX_SYSCALL_INTERRUPT_PRIORITY被设置为3。

在这里插入图片描述
参见图61:

  • 当内核或应用程序处于关键部分内时,使用优先级1到3的中断会被阻止(打断,嵌套)执行。在这些优先级上运行的ISR可以使用中断安全的FreeRTOS API函数。在第7章中描述了关键的部分。
  • 使用优先级4或以上的中断不受关键部分的影响,因此调度程序所做的任何事情都不会阻止这些中断立即执行——在硬件本身的限制范围内。在这些优先级上执行的ISR不能使用任何FreeRTOS API函数。
  • 通常,需要非常严格的定时精度的功能(例如,电机控制)将使用高于configMAX_SYSCALL_INTERRUPT_PRIORITY的优先级,以确保调度器不会在中断响应时间中引入抖动。
给ARM Cortex-M和ARM GIC用户的说明

Cortex-M处理器上的中断配置令人困惑,而且容易出错。为了帮助您的开发,FreeRTOS Cortex-M分支会自动检查中断配置,但仅在定义了配置ASSERT()时才会检查。配置ASSERT(),详见第11.2节。
ARM Cortex-M和ARM通用中断控制器(GICs)使用数字上的低优先级数字来表示逻辑上的高优先级中断。这似乎是违反了直觉的,而且很容易被忘记。如果您希望为中断分配一个逻辑上的低优先级,那么必须为它分配一个数值上较高的值。如果您希望为中断分配一个逻辑上的高优先级,那么必须为它分配一个低数值值。
Cortex-M中断控制器允许使用最多8位来指定每个中断优先级,使255成为可能的最低优先级。0是最高的优先级。然而,Cortex-M微控制器通常只实现8个可能位中的一个子集。实际实现的位数取决于微控制器族。
当只实现了8个可能位中的一个子集时,它只能使用字节中最重要的位——而不实现最不重要的位。未实现的位可以取任何值,但通常会将它们设置为1。图62演示了这一点,它显示了二进制101的优先级是如何存储在一个实现四个优先级位的Cortex-M微控制器中的。
在这里插入图片描述
在图62中,二进制值101被移到最重要的4位,因为最不重要的4位没有执行。未执行的位已被设置为1。
一些库函数期望在优先级值移位(shift up)到执行位(最重要的)之后指定。当使用这样的函数时,图62中所示的优先级可以被指定为十进制95。十进制95是二进制101向上移动4,使二进制101 nnnn(其中‘n’是一个未执行的位),并将未执行的位设置为1,即二进制1011111。
一些库函数期望在将优先级值移位(shift up)到执行位(最重要的)之前指定优先级值。当使用这样的函数时,图62中所示的优先级必须指定为十进制5。十进制5是二进制101,没有任何移位。
configMAX_SYSCALL_INTERRUPT_PRIORITY和configKERNEL_INTERRUPT_PRIORITY必须以允许它们直接写入Cortex-M寄存器的方式指定,优先级值是在移位(shift up)到执行位之后的值(nnnn 1111)。
configKERNEL_INTERRUPT_PRIORITY必须始终设置为尽可能低的中断优先级。未执行的优先级位可以设置为1,因此无论实际执行了多少优先级位,该常量都可以始终设置为255。
Cortex-M中断将默认为零-可能的最高优先级。Cortex-M硬件的实现不允许将configMAX_SYSCALL_INTERRUPT_PRIORITY设置为0,因此使用FreeRTOS API的中断的优先级永远不能保持在其默认值。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值