freertos---中断管理(二)

在FreeRTOS中,中断的优先级和任务的优先级是有区别的。

  • 任务的优先级是由用户设置内核管理器管理的软件特性(software feature),与操作系统所在的硬件平台无关
  • 中断的优先级是由硬件平台相关的硬件特性(hardware feature),在中断代码运行的时候任务的代码将无法运行。即使是拥有最小优先级的中断也会打断拥有最高优先级的任务。

通常需要在中断服务函数(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() 函数时,会将信号量放入队列,此时队列为满。此时任务检测到队列满的时候,会退出阻塞状态。

如下图所示:

  • t1:task1处在运行态,task2处在阻塞态,等待二值信号量的到来。
  • t2:执行中断服务程序,给task2发送信号量,导致task2进入就绪态。
  • t3:isr程序执行完成,任务调度器重新进行调度,task1和2都处于就绪态,但是task2优先级高,因此执行task2
  • t4:延迟中断处理完成,task2重新进入阻塞态,等待二值信号量。task1进入运行态

freertos---中断管理(二)_第1张图片

相关API函数:

API原型 函数说明 参数说明 返回值
SemaphoreHandle_t xSemaphoreCreateBinary( void );
创建一个二值信号量,在使用二值信号量之前必须要创建它才可以使用 NULL 表示没有足够的堆空间分配给队列而导致创建失败。
非 NULL 值表示创建成功。此返回值应当保存下来,以作为操作此定时器的句柄
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount );
创建一个计数信号量
  • uxMaxCount:计数信号量的最大值,相当于队列的深度
  • uxInitialCount :初始计数值
NULL 表示没有足够的堆空间分配给队列而导致创建失败。
非 NULL 值表示创建成功。此返回值应当保存下来,以作为操作此定时器的句柄
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

表示获取或接收到一个信号量,只有在信号量存在时才能获取

除了递归互斥锁外,所有信号量都可以使用xSemaphoreTake()获取,但是不能在ISR中调用它

  • xSemaphore:信号量的句柄
  • xTicksToWait:

pdPASS:获取信号量成功

pdFALSE:信号量获取失败,表示在阻塞等待xTicksToWait的时间到达后,信号量依然不可用

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken );

xSemaphoreGive的中断安全版本。

表示给定一个信号量(包括二值信号量或者计数信号量)

  • xSemaphore:信号量的句柄
  • pxHigherPriorityTaskWoken输出型参数。多个任务可能会进入阻塞状态来等待同一个信号量,如果调用该函数导致任务A离开阻塞状态,并且任务A的优先级高于当前正在执行的任务B(被中断的任务),那么,在内部,会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE。表示需要进行上下文切换(调用portYIELD_FROM_ISR),这将确保中断直接返回最高优先级的就绪状态任务。

pdPASS:写信号量成功

pdFALSE:信号量写失败,表示在阻塞等待xTicksToWait的时间到达后,队列满,信号量写入失败

void *pvTimerGetTimerID( TimerHandle_t xTimer );

获取定时器id,通常用在回调函数中。

当多个定时器的回调函数相同,在回调函数获取id从而辨识是哪一个定时器调用的回调函数。

  • xTimer:目标定时器的句柄
获取到的id

BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,

TickType_t xNewTimerPeriodInTicks,

TickType_t xTicksToWait );

修改定时器的定时时间。

在休眠状态下调用该API,会使得定时器开始运行,不需要执行start函数;在运行状态下调用该API,修改定时时间,定时器还处在运行状态。

中断模式下应使用xTimerChangePeriodFromISR()

  • xTimer:目标定时器的句柄
  • xNewTimerPeriodInTicks:定时时间,以ticks为单位
  • xTicksToWait:如果定时器命令队列已满,调用任务应保持在阻塞状态以等待命令队列上腾出可用空间

pdPASS:更改定时器时间命令成功发送到定时器命令队列

pdFALSE:如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入(超时时间过了依旧不能写入)

BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

复位定时器,即重新启动定时器,定时器的超时时间从reset定时器开始算起,并不是从一开始启动的时间算起。

在休眠模式下调用该API,会自动启动定时器。

中断模式下应使用

xTimerResetFromISR()

  • xTimer:目标定时器的句柄
  • xTicksToWait:如果定时器命令队列已满,调用任务应保持在阻塞状态以等待命令队列上腾出可用空间

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 );

将函数指针和相关

参数写入定时器命令队列

  • xFunctionToPend:需要defer到守护任务执行的函数指针
  • pvParameter1:向待执行的函数传递参数
  • ulParameter2:向待执行的函数传递参数
  • pxHigherPriorityTaskWoken :输出型参数,和xSemaphoreGiveFromISR中的含义一样

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)的值

freertos---中断管理(二)_第2张图片

#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博客

你可能感兴趣的:(freertos,中断管理)