到目前为止,所有延迟中断处理示例,要求应用程序作者,为每个使用延迟处理技术的中断创建一个任务。也可以使用xTimerPendFunctionCallFromISR()API函数来将中断处理延迟到RTOS守护进程任务,不需要为每个中断创建一个单独的任务。将中断处理延迟到守护进程任务称为“集中式延迟中断处理”。
第5章描述了与软件计时器相关的FreeRTOS API函数如何向计时器命令队列上的守护进程任务发送命令。xTimerPendFunctionCall()和xTimerPendFunctionCallFromISR()API函数使用相同的计时器命令队列向守护进程任务发送一个“执行函数”命令。发送到守护任务的函数将在守护任务的上下文中执行。
集中式延迟中断处理的优点包括:
集中式延迟中断处理的缺点包括:
不同的中断有不同的时间约束,因此在同一应用程序中使用这两种延迟中断处理的方法是很常见的。
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章介绍了如何设置计时器命令队列的长度。*/
示例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()的中断安全版本
//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()是等效的。
队列提供了一种简单而方便的将数据从中断传递到任务的方法,但是如果数据到达的频率较高,那么使用队列并不有效。
FreeRTOS下载中的许多演示应用程序都包括一个简单的UART驱动程序,它使用一个队列从UART的接收ISR中传递字符。在这些演示中,使用队列有两个原因:演示从ISR中使用的队列,以及故意加载系统以测试FreeRTOS端口。以这种方式使用队列的ISR绝对不能表示有效的设计,除非数据到达的速度变慢,否则建议生产代码不要复制该技术。适合用于生产代码的更高效的技术包括:
这个示例演示了从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。
Constant | Description |
---|---|
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定义的优先级上执行。 |
每个中断源都具有一个数字优先级和一个逻辑优先级:
中断的数字优先级和逻辑优先级之间的关系取决于处理器架构;在某些处理器上,分配给中断的数字优先级越高,中断的逻辑优先级就越高,而在其他处理器架构上,分配给中断的数字优先级越高,中断的逻辑优先级就越低。
通过将configMAX_SYSCALL_INTERRUPT_PRIORITY设置为比configKERNEL_INTERRUPT_PRIORITY更高的逻辑中断优先级来创建一个完整的中断嵌套模型。如图61所示,它显示了这样一个场景:
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的中断的优先级永远不能保持在其默认值。