在FreeRTOS中,中断的优先级和任务的优先级是有区别的。
通常需要在中断服务函数(ISR)中调用FreeRTOS的API函数,但许多的API在ISR中是不安全的,其中一些API会将调用的任务转换到阻塞态,如果在ISR中调用了这类API则会出现很多问题。FreeRTOS对于一些系统API函数提供两种版本,一种是供任务调用的,一种是供中断调用的(Interrupt Safe API)。由中断调用的API函数后缀上会有“FromISR”。
注:只有以”FromISR”或”FROM_ISR”结束的API 函数或宏才可以在中断服务例程中。
延迟中断处理(延迟中断处理)
由于种种原因,中断服务程序必须尽可能快地退出,因此,中断所需的任何其他处理可以放在任务中执行,这种方式叫做延迟中断处理。
如果中断处理被推迟到的任务的优先级高于任何其他任务,那么延迟处理将立即执行,效果就像在 ISR 本身中执行。
使用二值信号量(Binary Semaphores)同步
二值信号量可以在中断发生时解锁处在block state的任务,使得任务和中断“同步”。通常延迟中断处理的任务需要设置成较高的优先级,使得ISR中调用portYIELD_FROM_ISR()时候,调度器优先调度延迟中断处理的任务。
如何理解二值信号量:
在这种中断-任务同步场景中,二进制信号量在概念上可以被认为是一个长度为 1 的队列。,队列在任何时候最多可以包含一个项目,因此总是空的或满的(二值)。
在任务中调用xSemaphoreTake()函数,当take到的信号量为空,则会直接阻塞掉当前任务。
当在中断服务程序中调用xSemaphoreGiveFromISR() 函数时,会将信号量放入队列,此时队列为满。此时任务检测到队列满的时候,会退出阻塞状态。
如下图所示:
相关API函数:
API原型 | 函数说明 | 参数说明 | 返回值 |
SemaphoreHandle_t xSemaphoreCreateBinary( void );
|
创建一个二值信号量,在使用二值信号量之前必须要创建它才可以使用 | 无 | NULL 表示没有足够的堆空间分配给队列而导致创建失败。 非 NULL 值表示创建成功。此返回值应当保存下来,以作为操作此定时器的句柄 |
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );
|
创建一个计数信号量 |
|
NULL 表示没有足够的堆空间分配给队列而导致创建失败。 非 NULL 值表示创建成功。此返回值应当保存下来,以作为操作此定时器的句柄 |
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
|
表示获取或接收到一个信号量,只有在信号量存在时才能获取 除了递归互斥锁外,所有信号量都可以使用xSemaphoreTake()获取,但是不能在ISR中调用它。 |
|
pdPASS:获取信号量成功 pdFALSE:信号量获取失败,表示在阻塞等待xTicksToWait的时间到达后,信号量依然不可用 |
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken );
|
xSemaphoreGive的中断安全版本。 表示给定一个信号量(包括二值信号量或者计数信号量) |
|
pdPASS:写信号量成功 pdFALSE:信号量写失败,表示在阻塞等待xTicksToWait的时间到达后,队列满,信号量写入失败 |
void *pvTimerGetTimerID( TimerHandle_t xTimer ); |
获取定时器id,通常用在回调函数中。 当多个定时器的回调函数相同,在回调函数获取id从而辨识是哪一个定时器调用的回调函数。 |
|
获取到的id |
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewTimerPeriodInTicks, TickType_t xTicksToWait ); |
修改定时器的定时时间。 在休眠状态下调用该API,会使得定时器开始运行,不需要执行start函数;在运行状态下调用该API,修改定时时间,定时器还处在运行状态。 中断模式下应使用xTimerChangePeriodFromISR() |
|
pdPASS:更改定时器时间命令成功发送到定时器命令队列 pdFALSE:如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入(超时时间过了依旧不能写入) |
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait ); |
复位定时器,即重新启动定时器,定时器的超时时间从reset定时器开始算起,并不是从一开始启动的时间算起。 在休眠模式下调用该API,会自动启动定时器。 中断模式下应使用 xTimerResetFromISR() |
|
pdPASS:复位定时器的命令成功发送到定时器命令队列 pdFALSE:如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入(超时时间过了依旧不能写入) |
计数信号量(Counting Semaphores )
上面说了,二值信号量可以理解为队列深度为1的队列。那么计数信号量就可以理解为队列深度大于1的队列,不关心队列元素的内容,只关心队列元素的个数。使用计数信号量的时候,应该将FreeRTOSConfig.h文件的configUSE_COUNTING_SEMAPHORES 设置为1
计数信号量的典型用法:
① 事件计数:
某个事件发生的时候,give一个信号量(计数值+1)。处理某个事件的时候,take一个信号量(计数值-1)。最终的计数值就是待处理的时间的个数。
② 资源管理:
计数值指示可用资源的数量。 要获得对资源的控制权,任务必须首先获得一个信号量(计数值-1)。 当计数值达到零时,没有空闲资源。 当一个任务用完资源时,它“返还”信号量(计数值+1)
延迟中断处理到守护任务
使用 xTimerPendFunctionCallFromISR()API 函数来延迟中断到守护进程任务——无需为每个中断创建单独的任务。 将中断处理延迟到守护程序任务称为“集中延迟中断处理”。
xTimerPendFunctionCall() 和 xTimerPendFunctionCallFromISR() API 函数使用定时器命令队列向守护任务发送“执行函数”命令(发送的命令队列的尾部)。 发送到守护任务的函数会在守护任务的中执行。
API原型 | 函数说明 | 参数说明 | 返回值 |
---|---|---|---|
BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend,
void *pvParameter1,
uint32_t ulParameter2,
BaseType_t *pxHigherPriorityTaskWoken );
|
将函数指针和相关 参数写入定时器命令队列 |
|
pdPASS:写入成功 pdFALSE:写入失败。这个api不支持设置超时时间 |
/*
中断回调函数,该中断可以理解为硬件定时器中断
*/
static uint32_t ulTimInterruptHandler( void )
{
static uint32_t ulParameterValue = 0;
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
xTimerPendFunctionCallFromISR( vDeferredHandlingFunction, /* Function to execute. */
NULL, /* Not used. */
ulParameterValue, /* Incrementing value. */
&xHigherPriorityTaskWoken );
ulParameterValue++;
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*推迟到守护任务执行的函数*/
static void vDeferredHandlingFunction( void *pvParameter1, uint32_t ulParameter2 )
{
vPrintStringAndNumber( "Handler function - Processing event ", ulParameter2 );
}
中断嵌套(Interrupt Nesting)
几个关键的宏定义:
宏 | 含义 |
---|---|
configMAX_SYSCALL_INTERRUPT_PRIORITY or
configMAX_API_CALL_INTERRUPT_PRIORITY
|
这两个宏的意义完全一样, 只不过不同版本可能使用的不同的宏
设置可以调用 FreeRTOS API (中断安全)的最高中断优先级。 当中断逻辑优先级小于等于这个宏所代表的优先级时,可以在中断服务程序中调用API函数;逻辑优先级大于这个宏所代表的优先级,表示FREERTOS无法禁止这个中断,在这个中断服务程序中不可以调用任何API函数 |
config KERNEL_INTERRUPT_PRIORITY
|
设置系统心跳时钟(一般是滴答定时器中断)的中断优先级,并且必须始终设置为可能的最低中断优先级。 滴答定时器的中断不是通过硬件本身决定的吗,这个宏会操作硬件寄存器吗? 答:是的,在源文件的port.c中有体现 |
为了说明上面两个宏定义的含义,我们举例:
某种处理器有7种优先级,且数字优先级越大,逻辑优先级越高
将configKERNEL_INTERRUPT_PRIORITY设置为1
configMAX_SYSCALL_INTERRUPT_PRIORITY 设置为3
1. 当内核或应用程序位于临界区内,将阻止执行使用优先级1至3(包括1和3)的中断。因此,以这些优先级运行的ISR可以使用中断安全的FreeRTOS API函数。(这句话难以理解,看完第七章再回来看就明白了)
最低优先级的中断可以中断最高优先级的任务,所有任务不能抢占任何中断函数
configMAX_SYSCALL_INTERRUPT_PRIORITY是FreeRTOS系统能够屏蔽的最高优先级,规定中断优先级比configMAX_SYSCALL_INTERRUPT_PRIORITY高不能调用FreeRTOS API(如此高的中断优先级,已经不在FreeRTOS系统控制范围内)。
屏蔽的意思不是说中断不能够打断任务,而是说中断发生了,但是rtos可以选择不执行回调函数。具体可以看源码!
注意理解上面屏蔽
2、当中断优先级大于等于4的时候,内核调度器不会阻止中断服务程序的运行(即使在临界区内也不阻止)。以这些优先级执行的ISR不能使用任何FreeRTOS API函数
3、因此,在要求快速响应中断的场合,需要将该中断优先级设置为大于configMAX_SYSCALL_INTERRUPT_PRIORITY ,也就是大于等于4
总结:
如果在中断服务程序中调用某一些FreeRTOS的API函数时需要注意,如果有ISR版本的一定要调用末尾带ISR的函数,并且中断服务程序要调用freeRTOS的API接口则中断优先级不能高于配置宏(configMAX_SYSCALL_INTERRUPT_PRIORITY)的值
#ifndef configKERNEL_INTERRUPT_PRIORITY
#define configKERNEL_INTERRUPT_PRIORITY 255
#endif
#define portNVIC_PENDSV_PRI ( ( ( uint32_t configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t )
configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
#define portNVIC_SHPR3_REG ( ( volatile uint32_t * ) 0xe000ed20 )
BaseType_t xPortStartScheduler( void )函数里面会对优先级进行设置
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
/* 设置PendSV和Systick中断优先级为最低,这里是操作寄存器直接设置,
为什么要设置最低,因为任务切换就是在PendSV中运行,为了让其他中断能够及时的运行,
为什么要在PendSV中切换任务,因为PendSV可以挂起延迟执行,具体参考Cortex-M3 权威指南第7
章异常SVC和PendSV */
ref:
FreeRTOS 从入门到精通9--中断管理 - 知乎
freeRTOS中文实用教程3--中断管理之延迟中断处理 - jasonactions - 博客园
学习FreeRTOS之中断管理_4-zhuimengcanyang-ChinaUnix博客
configMAX_SYSCALL_INTERRUPT_PRIORITY理解_百度搜索
学FreeRTOS有感(1)两种中断 - 程序园
freeRTOS 优先级设置 configMAX_SYSCALL_INTERRUPT_PRIORITY_淡痕的博客-CSDN博客