FreeRTOS - 中断管理

中断对于嵌入式实时系统来说重要性不言而喻。在FreeRTOS系统中,突发的、周期性的、无法预期的事情称作事件Event,嵌入式系统需要对这些事件进行识别和处理,一般会使用中断的机制来检测这些事件的发生,当然也可以使用查询(标志位)的方式识别事件是否发生。通常我们需要中断处理函数ISR尽可能的简短,这个原则是在裸机应用开发中也有的,但是有的事件会触发大量的耗时的CPU运算,例如当我们收到一帧JPEG数据之后产生中断,然后需要将JPEG数据解码成原始像素信息,我们不能将所有处理都放在ISR中,因为这对于MCU系统来说会消耗很多CPU时间,并且解码JPEG很有可能不是一个很急迫的事件或者说优先级很高的事件,所以我们需要将JPEG解码放在主程序(指非ISR程序而不是main程序)中去处理,而不是在ISR中处理,这时候我们就需要确定在 ISR Code 和 NON-ISR Code 中的运行时间的分配问题以及ISR程序如何与NON-ISR程序进行通讯的问题。

需要说明一下,系统API函数和宏定义以 FromISR 和 FROM_ISR 结尾的才能在中断处理函数ISR中使用,其余的API或者宏定义如果在ISR中使用可能会导致错误。

FreeRTOS允许中断嵌套,参考文章《Mastering the FreeRTOS Real Time Kernel-A Hands On Tutorial Guide》的6.8节内容。这里首先说明一下处理器中断系统中的数值优先级Numberic prioirty 和 逻辑优先级Logical priority,数值优先级是写入在中断优先级寄存器中的用于计算中断的优先级的数值,逻辑优先级表示的是通过计算数值优先级得到的中断的实际优先级,有的系统是数值优先级和逻辑优先级呈正比,但是有的是呈反比,比如ARM Cortex-M处理器就是后者,中断的数值优先级越高表示的实际的逻辑优先级就越低,相反,数值优先级越小,表示的逻辑优先级越高。

中断嵌套指的是在某个中断正在处理的时候,发生一个更高优先级的中断,这就发生了中断嵌套。在FreeRTOS系统中,要支持完整的中断嵌套,需要将configMAX_SYSCALL_INTERRUPT_PRIORITY(或configMAX_API_CALL_INTERRUPT_PRIORITY)表示的逻辑优先级设置成高于configKERNEL_INTERRUPT_PRIORITY表示的逻辑优先级。以一个例子说明(引用原文):

  The processor has seven unique interrupt priorities. 处理器有7个优先级1 - 7,数值优先级越高代表的逻辑优先级越高。
  Interrupts assigned a numeric priority of 7 have a higher logical priority than interrupts assigned a numeric priority of 1.
  configKERNEL_INTERRUPT_PRIORITY is set to one. 内核中断优先级设置为1,也就是最低优先级。
  configMAX_SYSCALL_INTERRUPT_PRIORITY is set to three. 设置可调用FreeRTOS API的中断优先级最高为3。

  Interrupts that use priorities 1 to 3, inclusive, are prevented from executing while the
kernel or the application is inside a critical section. ISRs running at these priorities can
use interrupt-safe FreeRTOS API functions. Critical sections are described in Chapter 7.

优先级低于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,不允许在系统内核正处于临界区的时候,抢占CPU执行ISR程序,所以这些中断是可以调用以 FromISR 和 FROM_ISR 结尾的系统API。


  Interrupts that use priority 4, or above, are not affected by critical sections, so nothing
the scheduler does will prevent these interrupts from executing immediately—within the
limitations of the hardware itself. ISRs executing at these priorities cannot use any
FreeRTOS API functions.

优先级高于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,可以在任何时候从系统内核中抢占CPU,所以这些中断的相应时间是非常快的,但是它们不允许调用系统的API函数。


  Typically, functionality that requires very strict timing accuracy (motor control, for
example) would use a priority above configMAX_SYSCALL_INTERRUPT_PRIORITY
to ensure the scheduler does not introduce jitter into the interrupt response time.

对响应时间有十分严格要求的应用,需要使用高于configMAX_SYSCALL_INTERRUPT_PRIORITY代表的逻辑优先级的中断,以保证系统内核不会对中断的响应有干扰。那么系统是怎么实现低于configMAX_SYSCALL_INTERRUPT_PRIORITY表示的逻辑优先级的中断不会抢占系统内核的呢?看看STM32F1的FreeRTOS porting源码,其中taskENTER_CRITICAL的最终定义为:

void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;

	/* This is not the interrupt safe version of the enter critical function so
	assert() if it is being called from an interrupt context.  Only API
	functions that end in "FromISR" can be used in an interrupt.  Only assert if
	the critical nesting count is 1 to protect against recursive calls if the
	assert function also uses a critical section. */
	if( uxCriticalNesting == 1 )
	{
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}

其中的portDISABLE_INTERRUPTS()函数定义如下:

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}

这里定义了一个ulNewBASEPRI 变量,这个变量会被编译成一个寄存器的形式,而不是从栈中获取的内存空间,msr指令将通用寄存器的内容传送至特殊功能寄存器,所以这里程序的功能就是将configMAX_SYSCALL_INTERRUPT_PRIORITY的值写入到了状态寄存器中去了。ARM Cortex M3的内部寄存器参考文章:https://blog.csdn.net/zwl1584671413/article/details/79960114。有一个特殊寄存器叫做BASEPRI, 这个寄存器有9位,它定义了被屏蔽优先级的阈值,当它被设定为某个值后,所有优先级号大于等于此值的中断都被关闭,若设为0,则不关闭任何中断,默认值为0。通过写入到BASEPRI我们就实现了在临界区的时候可以调用系统API的功能了。

这里再说说STM32F1/4的中断系统,它们使用的是Coretex-M3/4内核,都只使用了4个bit位来表示中断中断优先级,也就是最高只能表示16个优先级即 0 - 15,但是STM32又将这4个位分为了两个部分,一个是抢占优先级,一个是子优先级,但是这种分类对于FreeRTOS或者其他系统来说不好管理,因为一般只有一个抢占优先级,子优先级的作用是当两个同样抢占优先级的中断发生的时候,子优先级越高的优先级优先执行。但是这个优先级貌似没什么用,所以FreeRTOS官方建议不使用子优先级,而是将4个位都配置为抢占优先级。参考文章:https://www.cnblogs.com/yangguang-it/p/7152549.html。状态寄存器中有BASEPRI 这个寄存器有9位,它定义了被屏蔽优先级的阈值;当它被设定为某个值后,所有优先级号大于等于此值得中断都被关闭,若设为0,则不关闭任何中断,默认值为0,例如在STM32F103的官方适配文件port.c中的 xPortPendSVHandler 函数:

__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

	mrs r0, psp
	isb

	ldr	r3, =pxCurrentTCB		/* Get the location of the current TCB. */
	ldr	r2, [r3]

	stmdb r0!, {r4-r11}			/* Save the remaining registers. */
	str r0, [r2]				/* Save the new top of stack into the first member of the TCB. */

	stmdb sp!, {r3, r14}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r3, r14}

	ldr r1, [r3]
	ldr r0, [r1]				/* The first item in pxCurrentTCB is the task top of stack. */
	ldmia r0!, {r4-r11}			/* Pop the registers and the critical nesting count. */
	msr psp, r0
	isb
	bx r14
	nop
}

其中有行代码:

	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0

这里就是将 configMAX_SYSCALL_INTERRUPT_PRIORITY 的值设置到 BASEPRI 寄存器中去,用于屏蔽优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 该数值的中断信号。

这里还要再提一点, Cortex M3/M4 内核的中断优先级有4 bits共16个优先级,但是 Cortex M0 内核的中断优先级只有2 bits共4个优先级,且(好像)没有 BASEPRI 寄存器,所以在对 Cortex M0 内核的FreeRTOS移植中没有configMAX_SYSCALL_INTERRUPT_PRIORITY和configKERNEL_INTERRUPT_PRIORITY 这两个参数,因为无法实现中断屏蔽的功能,所以 Cortex M0 内核的处理器(可能)无法在中断中调用 FreeRTOS 的API函数。

FreeRTOS提供了一些同步机制用于实现系统的中断管理:二值信号量、计数信号量、队列。

二值信号量可以用来在发生一个中断的时候唤醒一个处于Blocked状态的任务,这个处于Block状态的任务叫做synchronized handler task,我们可以将大量的处理任务放在这个synchronized handler task中处理,而中断处理函数中只需要使用二值信号量唤醒这个任务即可,这个过程我们称作为将中断推至事件处理任务。如果这个中断属于时间敏感型,那么我们可以将handler task的优先级设置到最高,以保证当ISR返回之后可以立即发生一个任务调度及时切换到handler task,这就保证了中断事件的处理可以连续的被处理。

二值信号量的基本使用方式为 takegive。假设二值信号量是一件物品,中断的Handler task调用take就相当于要拿物品,但是现在还没有物品可以拿,所以任务就会进入Blocked状态等待有物品可拿。当发生中断调用ISR函数时,在ISR内部调用give操作会放置一个物品,然后Handler task就有物品可以拿了,就从Blocked状态转入Ready状态,注意这里Handler task拿了这个物品就会把这个物品消耗掉,这个物品就没了。如果Handler task的优先级较较高,则会在ISR返回之后立即切换到Handler task,在处理完事件之后任务就会返回到 take 操作,再次进入Blocked状态。在传统的信号量术语中,这里的take 和 give操作就相当于P() 和 V()操作。

我们可以发现,二值信号量的主要作用是用来同步中断和任务,整个过程可以简化成以下的过程:

1、中断发生。

2、执行中断处理函数,调用 give 操作唤醒Handler Task。

3、Handler Task在ISR之后立刻执行,任务做的第一件事就是 take 二值信号量,并“吃掉”这个信号量。

4、在 take 之后执行中断事件处理操作,然后再次回到 take 操作发现没有可用的信号量,又会进入到Blocked状态,等待下一个中断ISR中 give 一个信号量。

但是二值信号量只适合较低频率的中断源,如果中断源的频率过高或者Handler Task中的处理时间过长就会导致 give 操作和 take 操作无法同步,导致数据丢失,这时候我们可以使用Queue或者Queue的衍生品Stream Buffer以及Message Buffer来保存数据,然后在Handler Task中处理这被保存的数据。如果某个中断没有数据需要处理,只是对某些事件进行计数,这里就可以使用计数信号量处理这一类的事件。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值