深入理解异常和中断(Cortex-M3)

深入理解异常和中断(Cortex-M3)

中断一般是由硬件(如外设和外部输入引脚)产生的事件。

异常一般指CPU内部产生的打断。但是,也可以把中断称为一种异常

我们一般称为系统异常和外部中断。

中断管理

以STM32F10x_StdPeriph_Driver为例,介绍和中断和异常相关的文件。

stm32f10x.h:由ST提供,定义了各种外设寄存器基地址,各种外设初始化的结构体。

system_stm32f10x.c:由ST提供,提供如SystemInit的系统时钟初始化函数,由启动文件去调用。

system_stm32f10x.h:ST提供。

misc.c:由ST提供,是标准库的文件,提供了操作NVIC和Systick的函数,如NVIC的初始化。

core_cm3.c:由ARM公司提供,包含内核设置的汇编函数。

core_cm3.h:由ARM公司提供,包含了内核的一些寄存器基地址定义,寄存器位的设置,NVIC的访问函数。

Cortex-M处理器具有多个用于中断和异常管理的可编程寄存器,这些寄存器多数位于NVIC和系统控制块(SCB)中。实际上,SCB是作为NVIC的一部分实现的,不过CMSIS-Core将其寄存器定义在了单独的结构体中。

NVIC和SCB位于系统控制空间(SCS),地址从0xE000E000开始,大小为4KB。SCS中还有SysTick定时器、存储器保护单元(MPU)以及用于调试的寄存器等。该地址区域中基本上所有的寄存器都只能由运行在特权访问等级的代码访问。唯一的例外为软件触发中断寄存器(STIR),它可被设置为非特权模式访问。

中断优先级

对于Cortex-M处理器(包括ARMv6-M和ARMv7-M)异常是否能被处理器接受以及何时被处理器接受并执行异常处理,是由异常的优先级和处理器当前的优先级决定的。更高优先级的异常(优先级编号更小)可以抢占低优先级的异常(优先级编号更大),这就是异常/中断嵌套的情形。有些异常(复位、NMI和HardFault)具有固定的优先级,其优先级由负数表示,

Cortex-M3和Cortex-M4处理器在设计上具有3个固定的最高优先级以及256个可编程优先级(具有最多128个抢占等级,可编程优先级的实际数量由芯片设计商决定。

中断优先级由优先级寄存器控制,宽度为3~8位。优先级的减少是通过去除优先级配置寄存器的最低位(LSB)实现的。对于未实现的位,读出总是为0。

STM32F1的中断优先级寄存器是4位。

优先级分组

利用系统控制块(SCB)中一个名为优先级分组的配置寄存器,每个具有可编程优先级的优先级配置寄存器可被分为两部分。上半部分(左边的位)为抢占优先级,而下半部分则为子优先级(响应优先级)。

抢占优先级决定运行一个中断处理时能否产生另外一个中断。子优先级(响应优先级)只会用在具有两个相同分组优先级的异常同时产生的情形,此时,具有更高子优先级(数值更小)的异常会被首先处理。

若两个中断同时被确认,且它们具有相同的分组/抢占优先级和子优先级,则异常编号更小的中断的优先级更高(IRQ#0的优先级高于IRQ#1的)。

向量表重定位

向量表:当Cortex-M处理器接受了某异常请求后,处理器需要确定该异常处理(若为中断则是ISR)的起始地址。该信息位于存储器内的向量表中,向量表默认从地址0开始,向量地址则为异常编号乘4,如图所示。向量表一般被定义在微控制器供应商提供的启动代码中。

向量表重定位特性提供了一个名为SCB->向量表偏移寄存器(VTOR)的可编程寄存器。该寄存器将正在使用的存储器的起始地址定义为向量表。

在使用VTOR时,需要将向量表大小扩展为下一个2的整数次方,且新向量表的基地址必须要对齐到这个数值。

应用向量表重定位的情形:

使用bootloader时,需要将中断向量表定位到app中。

应用程序从外部设备加载到RAM中运行,需要修够重定位到新的向量表

动态修改。ROM中可能会有一个中断的多个处理实例,在应用的不同阶段之间进行切换。

中断输入和挂起

每个中断都有多个属性:

每个中断都可被禁止(默认)或使能。

每个中断都可被挂起(等待服务的请求)或解除挂起。

每个中断都可处于活跃(正在处理)或非活跃状态。

挂起状态的意思是,中断被置于一种等待处理器处理的状态。

中断的挂起状态被存储在NVIC的可编程寄存器中,当NVIC的中断输人被确认后,它就会引发该中断的挂起状态。即便中断请求被取消,挂起状态仍会为高。这样,NVIC可以处理脉冲中断请求。

当中断正被处理时,它就会处于活跃状态。注意在中断人口处,多个寄存器会被自动压入栈中,这也被称作压栈。同时,ISR的起始地址会被从向量表中取出。

在中断服务完成后,处理器会执行异常返回,之前自动压栈的寄存器会被恢复出来,而且被中断的程序也会继续执行。中断的活跃状态会被自动清除。

异常处理流程

异常进入流程

多个寄存器和返回地址被压入当前使用的栈。这样就可以将异常处理用普通C函数实现。若处理器处于线程模式且正使用进程栈指针(PSP),则PSP指向的栈区域就会用于该压栈过程,否则就会使用主栈指针(MSP)指向的栈区域。

取出异常向量(异常处理/ISR的起始地址)。为了减少等待时间,这一步可能会和压栈操作并行执行。

取出待执行异常处理的指令。在确定了异常处理的起始地址后,指令就会被取出。

更新多个NVIC寄存器和内核寄存器,其中包括挂起状态和异常的活跃状态,处理器内核中的寄存器包括程序状态寄存器(PSR)、链接寄存器(LR)、程序计数器(PC)以及栈指针(SP).

根据压栈时实际使用的栈,在异常处理开始前,MSP或PSP的数值会相应地被自动调整。PC也会被更新为异常处理的起始地址,而链接寄存器(LR)则会被更新为名为EXC_RETURN的特殊值。该数值为32位,且高27位为1。低5位中有些部分用于保存异常流程的状态信息(如压栈时使用的哪个栈)。该数值用于异常返回。

执行异常处理

栈操作使用主栈指针(MSP)

处理器运行在特权访问等级

如更高优先级的异常产生,则会抢占当前的异常处理,这是异常嵌套。当相同或更低优先级的异常产生,则处于挂起状态,等待当前处理完成后才会得到处理。

在异常处理的结尾,程序代码执行的返回会引起EXC_RETURN数值被加载到程序计数器中(PC),并触发异常返回机制。

异常返回

由于使用了EXC_RETURN数值触发异常返回,异常处理(包括中断服务程序)就可以和普通的C函数/子例程一样实现。在生成代码时,C编译器将LR中的EXC_RETURN数值作为普通返回地址处理。由于EXC_RETURN机制,函数一般不会返回到地址0xFO000000~0xFFFFFFFF。

EXC_RETURN

处理器进入异常处理或中断服务程序(ISR)时,链接寄存器(LR)的数值会被更新为EXC_RETURN数值。当利用BX、POP或存储器加载指令(LDR或LDM)被加载到程序寄存器中时,该数值用于触发异常返回机制。EXC_RETURN中的一些位用于提高异常流程的其他信息。

思考:为什么异常处理可以用C语言函数实现?

对于ARM Cortex-M处理器,异常返回机制由一个特殊的地址EXC_RETURN触发,该数值在异常入口处产生且被存储在链接寄存器(LR)中。当该数值由某个允许的异常返回指令写入PC时,它就会触发异常返回流程。

EXC_RETURN机制的设计允许程序员像编写普通函数一样编写中断服务程序,这是因为它提供了一种从中断返回的标准化方法,使得中断服务程序(ISR)的编写和调用流程与普通函数的调用和返回流程非常相似。

自动化的上下文保存和恢复:当异常发生时,Cortex-M3处理器会自动将必要的寄存器(如R0-R3、R12、LR、PC以及程序状态寄存器)保存到栈中。异常处理完毕后,这些寄存器的值会自动从栈中恢复,这使得程序员可以使用C语言编写异常处理程序而不需要手动管理寄存器的保存和恢复。

EXC_RETURN机制:在进入异常处理函数之前,链接寄存器(LR)会被赋予一个特殊的值EXC_RETURN。当中断或异常处理程序执行完毕后,处理器会将LR中的EXC_RETURN值加载到程序计数器(PC)中,触发异常返回序列。这个序列会根据EXC_RETURN的值来恢复之前保存在堆栈中的寄存器值,包括PC、LR以及其他必要寄存器,从而允许异常处理程序像普通C函数一样返回。

编程模型:Cortex-M3处理器的编程模型允许在C语言中使用一组标准的寄存器和调用约定,这使得C语言编写的函数能够遵循与汇编语言相同的约定,从而在异常处理中使用C语言成为可能。

处理器和编译器的支持:Cortex-M3处理器的架构和编译器的支持使得C语言编写的异常处理程序可以得到高效的执行。编译器能够生成符合Cortex-M3架构要求的目标代码,包括自动的寄存器保存和恢复指令。

中断控制用的NVIC寄存器

中断设置使能寄存器:ISER,写1设置使能

中断清除使能寄存器:ICER,写1清除使能

中断设置挂起寄存器:ISPR,写1设置挂起状态

中断清除挂起寄存器:ICPR,写1清除挂起状态

中断活跃位寄存器:IABR,活跃状态位,只读

中断优先级寄存器:IP,每个中断都有一个中断优先级寄存器

软件触发中断寄存器:STIP,写中断编号设置相应的挂起状态

除了软件触发中断寄存器(STIR)外,所有这些寄存器都只能在特权等级访问。STIR默认只能在特权等级访问,不过可以配置为非特权等级访问。

用于异常或中断屏蔽的特殊寄存器

PRIMASK

在许多应用中,可能都需要暂时禁止所有中断以执行一些时序关键的任务,此时可以使用

PRIMASK寄存器。PRIMASK寄存器只能在特权状态访问。PRIMASK用于禁止除NMI和HardFault外的所有异常,它实际上是将当前优先级改为0(最高的可编程等级)。

FAULTMASK

从行为来说,FAULTMASK和PRIMASK很类似,只是它实际上会将当前优先级修改为-1,这样甚至是HardFault处理也会被屏蔽。当FAULTMASK置位时,只有NMI异常处理才能执行。

BASEPRI

有些情况下,可能只想禁止优先级低于某特定等级的中断,此时,就可以使用BASEPRI寄存器。要实现这个目的,只需简单地将所需的屏蔽优先级写入BASEPRI寄存器。例如,若要屏敲优先级小于等于0x60的所有异常,则可以将这个数值写入BASEPRI。

BASEPRI无法在非特权状态设置。

设置中断的步骤

设置优先级分组。优先级分组默认为0(优先级寄存器中只有第0位用于子优先级),这一步是可选的。

设置中断的优先级。中断的优先级默认为0(最高的可编程优先级),这一步也是可选的。

在NVIC或外设中使能中断。

若存在大量的嵌套中断,除了使能中断外,还应该确保栈空间足够。由于在处理模式中,中断处理总是使用主栈指针(MSP),主栈应该有足够应对最坏情况的栈空间(最大数量的嵌套中断/异常)。计算栈空间时应该考虑中断处理使用的栈以及每级栈帧使用的栈。

软件中断

可以利用软件代码触发异常或中断,之所以要这么做,最常见的原因为,允许多任务环境中处于非特权状态的应用任务,可以访问一些需要在特权状态下才能执行的系统服务。根据要触发的异常或中断的类型,应该使用不同的方法。

若要触发一个中断(异常类型16或之上),最简单的方法为使用CMSIS-Core函数NVIC_SetPendingIRQ:

若要触发SVC异常,则需要执行SVC指令。

深入了解异常处理

对于Cortex-M处理器,可以将异常处理或中断服务程序(ISR)实现为普通的C程序/函数

用于ARM架构的C编译器遵循ARM的一个名为AAPCS(ARM架构过程调用标准,参考文献13)的规范。根据这份标准,C函数可以修改R0~R3、R12、R14(LR)以及PSR。若C函数需要使用R4~R11,就应该将这些寄存器保存到栈空间中,并且在函数结束前将它们恢复。

R0~R3、R12、LR以及PSR被称作“调用者保存寄存器”,若在函数调用后还需要使用这些寄存器的数值,在进行调用前,调用子程序的程序代码需要将这些寄存器的内容保存到内存中(如栈)。这些由处理器硬件完成。

R4~R11为“被调用者保存寄存器”,被调用的子程序或函数需要确保这些寄存器在函数结束时不会发生变化(与进入函数时的数值一样)。这些寄存器的数值可能会在函数执行过程中变化,不过需要在函数退出前将它们恢复为初始值。这些需要手动去保存。

栈帧

在异常入口处被压入栈空间的数据块为栈帧。对于Cortex-M3或不具有浮点单元的Cortex-M4处理器,栈帧都是8个字大小的,对于具有浮点单元的Cortex-M4,栈帧则可能是8或26个字。

AAPCS的另外一个要求为,栈指针的数值在函数入口和出口处应该是双字对齐的。因此,若在中断产生时栈帧未对齐到双字地址上,Cortex-M3和Cortex-M4处理器会自动插人一个字。这样,可以保证栈指针位于异常处理的开始处。“双字栈对齐”特性是可编程的,若异常未完全符合AAPCS,则可以将该特性关闭。

中断等待

中断等待表示从中断请求开始到中断处理开始执行间的时间。对于Cortex-M3和Cortex-M4处理器,若中断系统为零等待的,而且假定系统设计允许取向量和压栈同时执行,则中断等待为12个周期,其中包括寄存器压栈、取向量以及取中断处理的指令。

除了存储器设备或外设产生的等待状态外,其他情况也可能会加大中断等待时间:

处理器正处理另外一个相同或更高优先级的异常。

调试器访问存储器系统。

处理器正执行非对齐传输。从处理器的角度来看,它可能是单次传输,不过由于总线接口需要将非对齐传输转换为多个对齐传输,从总线等级来看它可能会占用几个周期。

末尾连锁(中断咬尾)

若某个异常产生时处理器正在处理另一个具有相同或更高优先级的异常,该异常就会进入挂起状态。在处理器执行完当前的异常处理后,它可以继续执行挂起的异常/中断请求。处理器不会从栈中恢复寄存器(出栈)然后再将它们存入栈中(压栈),而是跳过出栈和压栈过程并会尽快进入挂起异常的异常处理。这样,两个异常处理间隔的时间就会降低很多。对于无等待状态的存储器系统,末尾连锁的中断等待时间仅为6个时钟周期。

丢中断的情况1:中断请求产生于中断悬起过程

有一个中断请求产生,NVIC响应并将中断悬起,此时并未进入中断处理。

在悬起期间,又产生相同的中断请求,那么中断一直悬起并进入中断处理,而产生相同的中断请求则会被忽略。

丢中断的情况2:中断请求产生于中断处理过程

有一个中断请求产生,NVIC响应并将中断悬起并进入中断处理,悬起位解除。

进入中断,还未将中断标志位清除(如外设上某个中断的标志位),这时,再次发生依次中断请求,产生中断标志位,然后中断处理中将这个标志位清除。

则两个中断标志位都被清除,第二个中断进入中断处理无法判断是产生何种中断类型。

相关推荐

为什么使用Laravel框架?
如何打开mobile365

为什么使用Laravel框架?

📅 08-23 👁️ 2271
足坛历史五大最佳门将:布冯仅排第四,第一480场不失球!
iOS14+中广告标识(idfa)获取方式
365betvip5

iOS14+中广告标识(idfa)获取方式

📅 08-14 👁️ 5902
大重九99香烟价格表图大全5款,大重九99香烟价格表
如何打开mobile365

大重九99香烟价格表图大全5款,大重九99香烟价格表

📅 08-06 👁️ 7652
天涯明月刀手游蝴蝶要怎么抓
如何打开mobile365

天涯明月刀手游蝴蝶要怎么抓

📅 10-07 👁️ 4949
山西民俗:晋南的灶火
365娱乐官网官方网站

山西民俗:晋南的灶火

📅 08-12 👁️ 5950