这里中断指的是硬件中断,优先级最低的硬件中断都会抢占优先级最高的任务进程。所以硬件中断的中断函数应尽可能地短,否则会影响任务进程。
在FREERTOS中,由于任务中调用的API接口函数许多时候需要进入阻塞状态,而硬件中断处理函数应尽可能短,故任务中调用的API函数不适用于硬件中断。故,在FREERTOS中采用了将二者的API接口函数分开的方式,任务中不可调用硬件中断API函数,硬件中断中也不应调用任务相关的API函数。在定义函数中,与中断相关API函数都加有“FromISR”的扩展。中断中调用的函数名一定要含有“FromISR”。
若中断触发了上下文开关,则进入中断时执行的任务与退出中断后执行的任务不一致(中断结束后触发了任务调度函数,从而更改其他任务)。在抢占模式下
1,若任务调用API函数使得另一个高优先级任务退出阻塞态,此时高优先级任务会直接抢占此时正在执行的低优先级任务。
2,若硬件中断处理函数调用API函数使得一个高优先级任务退出阻塞态进入就绪态,由于硬件中断优先级最高,故高优先级任务并不会抢占硬件中断函数,但硬件中断函数执行完之后需要调用任务调度函数来调度高优先级任务来执行,而xHigherPriorityTaskWoken参数就是为了这个目的,当有高优先级时需要任务调度时xHigherPriorityTaskWoken将被置为pdTRUE,表示需要任务调度。需要注意的是该参数在第一次使用之前需要置为pdFALSE。硬件中断函数中调用的API函数中需要对xHigherPriorityTaskWoken置pdTRUE以便快速切换任务,若不实施该操作会使得高优先级任务在下一次任务调度时才运行。若没有高优先级任务则尽量不要置为pdTRUE,因为若中断频繁会导致任务频繁切换,影响任务正常运行。故该功能为可选功能,若不用应置为NULL
在任务中调用宏taskYIELD() 来请求上下文开关即任务调度器。在中断中应使用上面两种宏,这两种宏功能完全一致,有时仅提供一种。函数原型如下:
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
若portYIELD_FROM_ISR( xHigherPriorityTaskWoken )的输入参数为pdFALSE,则中断结束后并不会调用任务调度函数。若输入参数为pdTRUE,则在中断结束后会调用任务调度函数调度任务执行。
对于大部分系统,该函数可以放在中断处理函数中任意位置,一小部分规定必须放在中断处理函数结尾。
中断服务程序必须记录中断产生的原因,并清楚中断标志。任何一个中断运行必需的进程一般经常在任务中执行。这样就可以使得中断服务程序快速推出。这个方法就叫做推迟中断进程。因为中断必需函数在之后的任务中执行了。
推迟中断的原因,若中断处理函数较长,会导致影响正常任务的运行,使得任务执行时间抖动较大,不利于任务执行。
将中断进程推迟到任务中执行可以使得应用层按顺序处理相关内容。也可以使用所有任务相关API函数。
如图,任务2即为推迟后的中断进程,当中断ISR结束后,马上会跳转到高优先级的任务二来执行中断相关函数。结束后任务一接着执行。
二进制信号量API的中断安全版本可以使得当任意时间一个特定中断触发后,将一个阻塞任务退出阻塞状态,有效的同步任务与中断。这可以使得大部分的中断事件进程在任务中执行。仅保留一小部分快速执行的程序在中断函数中。二进制信号量就是为了将中断进程推迟到任务中。
如果一个中断是对时间要求严格的,那么推迟中断任务应设置一个比较高的优先级,并且在中断处理函数中应调用portYIELD_FROM_ISR(),使得在中断结束后马上调用推迟中断任务。这样就可以保证整个事件进程就像在同一个时间段内执行。
当中断推迟任务执行完后就可以采用拿走信号量(Taking a semaphore)模式进入阻塞状态,继续等待下一次中断来临。当下一次中断到来后中断函数给与一个信号量(giving a semaphore),之后任务又可从阻塞态进入就绪态。
‘Taking a semaphore’ 与‘giving a semaphore’ 在不同情况下有着不同的定义,在上述同步情况下,二进制信号量可以描述为一个长度为一的队列,在任何时间队列最多可含有一个数据,所以该队列总是处于空或满两种状态。通过调用xSemaphoreTake(),推迟中断进程将会进入阻塞读取队列状态,当队列为空时,任务进入阻塞态,当中断发生后,中断函数调用xSemaphoreGiveFromISR()将信号量放到队列中,使得队列状态为满,这会使得任务退出阻塞态进入就绪态。并且移除队列中的信号量,使得队列再次变为空,当任务完成进程后会再次阻塞读取队列。。。
xSemaphoreCreateBinary()
创建一个类型为SemaphoreHandle_t的信号量,函数原型如下所示:
SemaphoreHandle_t xSemaphoreCreateBinary( void );
返回值:NULL表示内存不足创建失败。非NULL则表示创建成功。
xSemaphoreTake()
取走信号量,可以取走除了递归互斥体外的所有类型信号量。它不可被中断服务函数调用,函数原型如下:
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
参数:
xSemaphore:信号量
xTicksToWait :阻塞等待最大时间,当FreeRTOSConfig.h中的INCLUDE_vTaskSuspend置为1时,可以调用portMAX_DELAY表示一直等待。
返回值:pdPASS成功,pdFALSE信号量无效或等待超时
xSemaphoreGiveFromISR()
中断函数调用将信号量放入到队列。函数原型如下:
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken );
参数:
xSemaphore:信号量
pxHigherPriorityTaskWoken :中断传递函数,当中断函数将信号量放到队列中后,如果正在等待的中断进程任务的优先级高于现在中断打断的正在执行的任务,则该函数会将pxHigherPriorityTaskWoken 设置为pdTRUE,若中断进程任务较低,则该函数不对pxHigherPriorityTaskWoken 进行操作(不同版本FREERTOS可能有差别,有的会置为pdFALSE),故在初始化时应将pxHigherPriorityTaskWoken 初始为pdFALSE,在该函数之后一般会调用portYIELD_FROM_ISR(),来决定中断结束是否执行任务调度。
返回值:pdPASS:成功。pdFAIL表示队列已满。
下面的任务为每经过500ms产生一个软件中断,在产生前与产生后都有相关打印,代码如下:
/* The number of the software interrupt used in this example. The code shown is from
the Windows project, where numbers 0 to 2 are used by the FreeRTOS Windows port
itself, so 3 is the first number available to the application. */
#define mainINTERRUPT_NUMBER 3
static void vPeriodicTask( void *pvParameters )
{
const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Block until it is time to generate the software interrupt again. */
vTaskDelay( xDelay500ms );
/* Generate the interrupt, printing a message both before and after
the interrupt has been generated, so the sequence of execution is evident
from the output.
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( "Periodic task - About to generate an interrupt.\r\n" );
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
}
下面函数为延时中断进程任务:
static void vHandlerTask( void *pvParameters )
{
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Use the semaphore to wait for the event. The semaphore was created
before the scheduler was started, so before this task ran for the first
time. The task blocks indefinitely, meaning this function call will only
return once the semaphore has been successfully obtained - so there is
no need to check the value returned by xSemaphoreTake(). */
xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
/* To get here the event must have occurred. Process the event (in this
Case, just print out a message). */
vPrintString( "Handler task - Processing event.\r\n" );
}
}
下面函数为中断处理函数,首先定义一个xHigherPriorityTaskWoken并置为pdFALSE,然后调用放置信号量函数,之后调用portYIELD_FROM_ISR(),根据放置信号量函数中的xHigherPriorityTaskWoken的值来决定中断结束是否调度任务。代码如下:
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;
/* 'Give' the semaphore to unblock the task, passing in the address of
xHigherPriorityTaskWoken as the interrupt safe API function's
pxHigherPriorityTaskWoken parameter. */
xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
/* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR(). If
xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR()
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 );
}
main函数主要用于创建两个任务,以及信号量,并将软件中断与中断函数关联起来。代码如下:
int main( void )
{
/* Before a semaphore is used it must be explicitly created. In this example
a binary semaphore is created. */
xBinarySemaphore = xSemaphoreCreateBinary();
/* Check the semaphore was created successfully. */
if( xBinarySemaphore != NULL )
{
/* Create the 'handler' task, which is the task to which interrupt
processing is deferred. This is the task that will be synchronized with
the interrupt. The handler task is created with a high priority to ensure
it runs immediately after the interrupt exits. In this case a priority of
3 is chosen. */
xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );
/* Create the task that will periodically generate a software interrupt.
This is created with a priority below the handler task to ensure it will
get preempted each time the handler task exits the Blocked state. */
xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, 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();
}
/* As normal, the following line should never be reached. */
for( ;; );
}
上述实例仅适用于当中断产生频率较低的情况,若中断发生速度较快,当第二个中断来临时,队列为空,然后中断将信号量放入到队列,因此任务在执行完第一个中断进程后马上会接着执行第二个,但当第三个中断来临时任务还没有来的及取走信号量,此时中断会放置信号量失败,此时任务就无法知道第三个中断的到来。
另外上面的实例中任务等待信号量采用一直等待方式,若有硬件错误产生会导致任务无法进入就绪态,从而永远不知道错误的发生,故一般采用等待一定时间,超时后采用错误处理,下面为一个UART接收任务的例子:
static void vUARTReceiveHandlerTask( void *pvParameters )
{
/* xMaxExpectedBlockTime holds the maximum time expected between two interrupts. */
const TickType_t xMaxExpectedBlockTime = pdMS_TO_TICKS( 500 );
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* The semaphore is 'given' by the UART's receive (Rx) interrupt. Wait a
maximum of xMaxExpectedBlockTime ticks for the next interrupt. */
if( xSemaphoreTake( xBinarySemaphore, xMaxExpectedBlockTime ) == pdPASS )
{
/* The semaphore was obtained. Process ALL pending Rx events before
calling xSemaphoreTake() again. Each Rx event will have placed a
character in the UART’s receive FIFO, and UART_RxCount() is assumed to
return the number of characters in the FIFO. */
while( UART_RxCount() > 0 )
{
/* UART_ProcessNextRxEvent() is assumed to process one Rx character,
reducing the number of characters in the FIFO by 1. */
UART_ProcessNextRxEvent();
}
/* No more Rx events are pending (there are no more characters in the
FIFO), so loop back and call xSemaphoreTake() to wait for the next
interrupt. Any interrupts occurring between this point in the code and
the call to xSemaphoreTake() will be latched in the semaphore, so will
not be lost. */
}
else
{
/* An event was not received within the expected time. Check for, and if
necessary clear, any error conditions in the UART that might be
preventing the UART from generating any more interrupts. */
UART_ClearErrors();
}
}
}