Cortex-M0异常和中断

文章详细阐述了Cortex-M0处理器中的异常和中断管理机制,包括异常的概念、类型、优先级定义,以及向量表、异常流程(压栈、出栈、异常返回)和中断处理的细节。重点讨论了异常嵌套、EXC_RETURN在异常返回中的作用,强调了处理器如何高效地处理中断请求和异常事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.概念

  • 异常是能够引起程序流偏离正常流程的事件,当异常发生时,正在执行的程序就会被挂起,处理器转而执行一块与该事件相关的代码(异常处理)。事件可以是外部输入,也可以是内部产生的,外部产生的事件通常被称作中断或中断请求(IRQ)。
  • 异常发生时执行的软件代码为异常处理,而当异常处理与中断事件相关时,又可称作中断处理或中断服务程序(ISR)。在编译好的程序映像中,异常处理是作为程序代码的一部分出现的。
  • 当异常处理执行完异常后,就会返回到中断前的程序,并且继续执行之前的任务。因此,异常处理程序需要有种手段来记录中断前程序的状态,并且在中断完成之后能够将这些信息恢复,这可以通过硬件机制或硬件和软件配合来实现。
  • 通常的做法是,异常会被划分为多个优先等级,在执行低优先级的异常处理时,更高优先级的异常可以被触发并且执行,这个过程一般被称为异常嵌套。异常的优先级可以是可编程的,也可以是固定的。除了优先级的设置,有些异常(包括多数的中断)可以由软件禁止或使能。

2.异常类型

Cortex-M0处理器内置了中断控制器,并且支持最多32个中断请求(IRQ)输入,以及1个不可屏蔽中断(NMI)输入。根据微控制器产生设计的不同,IRQ和NMI可以由外部产生,也可以由片上外设产生。此外,它还支持多个内部异常。

Cortex-M0的每个异常源都有一个单独的异常编号,NMI的编号为2,而片上外设和外部中断的则为16~47。1 ~ 15的其他编号,用于处理器内部的系统异常,这个范围内的有些编号还没有使用。

每种异常类型都有对应的优先级,有些异常的优先级是固定的,而有些则是可编程的。异常类型、编号和优先级如下:
在这里插入图片描述

  • 不可屏蔽中断(NMI):NMI同IRQ类似,只是它不能被禁止,并且优先级仅次于复位,它对于工业控制和汽车之类的高可靠性系统非常有用。根据微控制器设计的不同,NMI可以用于掉电处理,也可以连接到看门狗单元,以便在系统停止响应时将系统复位。由于NMI不能被控制寄存器禁止,其响应的及时性就得到了保证。
  • 硬件错误:硬件错误异常用于处理程序执行时产生的错误,这些错误可以是试图执行未知的操作码、总线接口或存储器系统的错误,也可以是试图切换至ARM状态之类的非法操作。
  • SVC(请求管理调用):SVC指令执行时就会产生SVC异常,其通常用在具有操作系统的系统中,为应用程序提供了访问系统服务的入口。
  • PendSV(可挂起的系统调用):PendSV是用于带操作系统的应用程序的另外一个异常,SVC异常在SVC指令执行后会马上开始,PendSV在这点上有所不同,它可以延迟执行,在OS上执行PendSV可以确保高优先级任务完成后才执行系统调度。
  • 系统节拍:NVIC中的Sys Tick定时器为OS应用可以利用的另一个特性。几乎所有操纵系统的运行都需要上下文切换,而这一过程通常需要依靠定时器产生定时中断来完成。Cortex-M0处理器内集成了一个简单的定时器,这样就使得设备间移植操作系统更加容易。
  • 中断:Cortex-M0微控制器可以支持1 ~ 32个中断,中断信号可以连接到片上外设,也可以通过I/O端口连接到外部中断源上。外部中断只有在使能后才能使用,如果中断被禁止了,或者处理器正在运行另外一个相同或更高优先级的异常处理,则该中断请求会被存储在挂起状态寄存器中。当高优先级的中断处理完成或返回后,挂起的中断请求才可以执行。应该注意的是,在微控制器的外部接口中,外部中断信号可以是高电平也可以是低电平,或者可以通过编程配置。

3.异常优先级定义

在Cortex-M0处理器中,每个异常都对应一个优先级。优先级决定了异常是否执行或者是否延迟执行(处于挂起态),Cortex-M0处理器支持3个固定的最高优先级以及4个可编程的优先级。对于具有可编程优先级的异常,优先级配置寄存器为8位宽,而且只能使用最高两位,如下:
在这里插入图片描述
因为第0到5位没有使用,故它们读出始终为0,对它们的写操作没有意义。在这个设定下,可以使用的优先级为0x00(最高)、0x40、0x80和0xc0(最低),再加上3个固定的优先级,Cortex-M0总共具有7个优先级,如下:

在这里插入图片描述

  • 为了使Cortex-M0/M3设备间的软件移植更为简单,处理器没有使用优先级寄存器的最低位,而是使用了最高位。这样在具有较宽优先级寄存器的设备上编写的程序,在优先级位数较少的设备上就可以正常工作。
  • 如果发生了已经使能的异常事件(例如中断、Sys Tick定时器等),此时也没有其他的异常处理正在运行,而且PRIMASK(中断屏蔽寄存器)没有屏蔽掉该异常,那么处理器就会接受该异常并且执行对应的异常处理。
  • 从当前正在运行的任务切换到异常处理的过程叫抢占。如果处理器已经在运行另外一个异常处理,而新异常的优先级大于正在执行的,这时就会发生抢占。正在运行的异常处理就会被暂停,转而执行新的异常,这个过程通常被称为中断嵌套或异常嵌套。新的异常执行完毕后,之前的异常处理会继续执行,并且在其结束后会返回到程序线程中。
  • 不过,如果处理器正在运行的另外一个异常处理的优先级相同或者更高,新的异常将会等待并且进入挂起状态。挂起的中断将会一直等到当前异常等级改变。可以通过NVIC中映射到存储空间的寄存器访问异常的挂起状态,对NVIC的一个寄存器执行写操作可以清除异常挂起状态,清除后,该异常将不再执行。
  • 如果两个异常同时发生,并且它们被赋予相同的优先级,异常编号较小的异常将会首先执行。
  • Cortex-M0处理器对中断嵌套的支持无须任何软件干预。

4.向量表

对于Cortex-M0处理器,内置的中断控制器NVIC支持向量中断,这就意味着不同中断的异常向量是独立的,而且中断服务程序的入口自动分配,无须软件干预。

当Cortex-M0处理器要处理中断服务请求时,它需要首先确定异常处理的起始地址,所需的信息叫做向量表,它存储在存储器空间的开始位置。向量表包括了系统中可用异常的异常向量,以及主栈指针(MSP)的初始值。向量表如下:
在这里插入图片描述
异常向量的存储顺序同异常编号一致,由于每个向量表都是1个字(4字节),异常向量的地址为异常编号乘4。每个异常向量都是异常处理的起始地址,而且其最低位置1,表明异常处理为Thumb代码。

5.异常流程概述

接受异常请求

处理器要接受一个异常,需要满足以下条件:

  • 对于中断和Sys Tick中断请求,中断必须使能。
  • 处理器正在执行的异常处理的优先级不能相同或更大。
  • PRIMASK中断屏蔽寄存器没有屏蔽掉异常。

应该注意的是,对于SVC异常,如果用到SVC指令的异常处理的优先级与SVC异常本身相同或者更大,这种情况就会引起硬件错误异常处理的执行。

压栈和出栈

  • 为了使被中断的程序能正确继续执行,在程序切换至异常处理前,处理器当前状态的一部分应该被保存。不同架构处理器的处理方法不同,Cortex-M0处理器采用了硬件自动处理的方法来备份和恢复处理器状态,如果有必要,程序中还需要增加软件处理过程。
  • 当Cortex-M0处理器接受了一个异常以后,寄存器组中的一些寄存器(R0到R3、R12和R14)、返回地址(PC)以及程序状态寄存器(xPSR)会被自动压入当前栈空间里。链接寄存器(LR/R14)则会被更新为异常返回时使用的特殊值(EXC_RETURN),然后异常向量被自动定位而且异常处理开始执行。
  • 异常处理过程执行到最后时,将会利用执行特殊值(EXC_RETURN)来触发异常返回机制。处理器还会查看当前是否还有其他异常需要处理,如果没有,处理器就会恢复之前存储在栈空间的寄存器值,并继续执行中断前的程序。
  • 自动保存和恢复寄存器内容的操作被称为“压栈”和“出栈”,这种机制使得异常处理可以跟普通的C函数一样处理,同时也减小了软件开销以及回路大小(无须另外的寄存器组),因此也就降低了系统的功耗。
    在这里插入图片描述
    自动压栈过程没有备份所有的寄存器,如果其他的寄存器在异常处理过程中被修改了,只能通过软件来保存和恢复。

异常返回指令

和其他的一些处理器不同,Cortex-M0的中断处理无须特殊的返回指令。相反地,Cortex-M0只是用普通返回指令,而加载到PC中的数值则会触发异常返回,这样就使得异常处理可以和普通C函数一样使用。

两个不同的指令可以用于异常返回:
在这里插入图片描述
当其中的一个指令执行,而且EXC_RETURN特殊值被加载到程序计数器(PC)中时,异常返回机制就会启动。如果加载到PC的值不是EXC_RETURN,则其会被当做普通的BX或POP指令。

末尾连锁

如果当其他的异常处理完成后,还有异常处于挂起状态,这时处理器不会返回到中断前的程序,而是重新进入异常处理流程,这也被称为末尾连锁。当末尾连锁发生时,处理器不必马上恢复栈的值,因为这么做的话还得将它们重新压栈。异常的末尾连锁降低了异常处理的开销,因此也提高了能耗效率。

在这里插入图片描述

延迟到达

延迟到达是Cortex-M0的优化机制,它可以加快高优先级异常的处理。如果在低优先级异常压栈过程中发生了高优先级异常,处理器就会首先处理高优先级异常。
在这里插入图片描述
由于每个中断都需要同样的压栈操作,后至的高优先级中断发生后将会继续之前的压栈过程。压栈完成后,高优先级的异常向量就会被取出以替代低优先级的那个。

如果没有延迟到达优化,在低优先级异常开始时,处理器就必须抢占并且重新进入异常处理流程,这样就会带来较长的延迟以及较大栈空间的使用。

6.EXC_RETURN

EXC_RETURN为架构定义的特殊值,用于异常返回机制,这个值在异常被接受并且压栈完成后会自动存储到链接寄存器中(LR或R14)。EXC_RETURN为32位数值,并且高28位置1,第0位到第3位则提供了异常返回机制所需的信息。
在这里插入图片描述
Cortex-M0中EXC_RETURN的bit0保留,且必须为1。EXC_RETURN的bit2表示出栈恢复寄存器时使用的是主栈(MSP)还是进程栈(PSP)。EXC_RETURN的bit3表示处理器要返回线程模式还是处理模式。Cortex-M0处理器使用的EXC_RETURN的合法值如下:

在这里插入图片描述
由于EXC_RETURN的值在异常入口处被自动加载到LR中,异常处理会把它当成普通的返回地址。如果返回地址无须保存在栈上,异常处理也可以像普通函数一样,通过执行“BX LR”来触发异常返回并且返回到中断前的程序。另外,如果异常处理需要执行函数调用,就需要将LR压栈。

在异常处理的最后,已经压栈的EXC_RETURN值将会通过POP指令直接加载到PC,这样就能触发异常返回流程并且返回到中断前的程序。

下面是不同EXC_RETURN值的产生和使用情况:
如果线程正在使用主栈(CONTROL寄存器的第1位为0),在进入第一个异常时,LR的值被置为0xFFFFFFF9,而进入嵌套异常时则为0xFFFFFFF1。
在这里插入图片描述
如果线程使用进程栈(CONTROL寄存器的第1位为1),在进入第一个异常时,LR的值被置为0xFFFFFFFD,而进入嵌套异常时则为0xFFFFFFF1。
在这里插入图片描述
由于EXC_RETURN数值的特殊格式,正常返回指令如果返回到0xFFFFFFFX范围的地址,会被处理器当做异常返回,而不是普通的返回指令。

7.异常入口流程的细节

当异常发生时,以下的情况会随之发生:

  • 压栈并且栈指针更新。
  • 处理器取出异常向量并且将其写入PC。
  • 寄存器更新(LR、IPSR和NVIC寄存器)。

压栈

在这里插入图片描述
如图所示,当异常发生时,8个寄存器会被自动压栈,这些寄存器包括R0到R3、R12、R14(链接寄存器)、返回地址(下一条指令的地址或程序计数器)和程序状态寄存器(xPSR)。用于压栈的栈为当前活动栈,如果异常发生时处理器处于线程模式,根据CONTROL寄存器第1位的不同,压栈可以使用进程栈或主栈,如果CONTROL[1]为0,则使用主栈。如果异常发生时处理器处于线程模式并且CONTROL[1]置1,则会使用进程栈。
在这里插入图片描述

对于嵌套异常,压栈时总会使用主栈,因为处理器当前处于处理模式,这种情况下只能使用主栈。

将寄存器R0—R3,R12,PC,LR和xPSR保存到栈中的原因是,这些寄存器被称为“调用者保存寄存器”。C函数不必保留这些寄存器的值。为了使异常处理能够像普通C函数一样使用,这些寄存器需要由硬件进行保存和恢复,这样在中断前的程序继续执行时,这些寄存器的值就能和异常发生以前一样。

在这里插入图片描述
如上图所示,压栈时保存到栈里的数据被统称为“栈帧”。在Cortex-M0处理器中,一个栈帧总是双字对齐的,这样就能确保栈的使用遵循AAPCS标准。如果上一个压入的数据可能会处于非双字对齐的,压栈机制就会将压栈的位置自动调整到下一个双字对齐的地址上,并且在栈中的xPSR寄存器中设置标志(第9位),表明发生了双字栈调整。

在出栈过程中,处理器会检查栈中的xPSR的标志,并且根据标志的不同对栈指针做出相应的调整。

寄存器的压栈顺序如下所示:
在这里插入图片描述
当压栈结束后,栈指针会得到更新,并且主栈指针会被选择为当前栈指针(处理模式总是使用主栈),然后异常向量也会被取出。

取出向量并更新PC

压栈结束后,处理器会从向量表中取出异常向量,然后将向量写到PC,并且将从这个地址中开始异常处理的取指。

寄存器更新

异常处理开始执行后,LR的值会被更新为相应的EXC_RETURN值,这个值将会被用作异常返回,IPSR也会被更改为当前处理异常对应的异常编号。

NVIC的许多寄存器也可能会更新,其他的寄存器还包括异常中断时外部中断的状态寄存器,或者为系统异常时对应的中断控制和状态寄存器。

8.异常退出流程的细节

当执行异常返回指令时(使用POP或者BX指令将EXC_RETURN加载到PC),异常退出流程就开始了,这个过程可能包括以下步骤:

  • 寄存器出栈

为了将寄存器的值恢复到异常发生以前的状态,需要使用POP将压栈过程中保存在栈的值取出,并恢复到相应的寄存器中。由于栈帧可以保存在主栈或者进程栈中,处理器会首先检查正在使用的EXC_RETURN的值。如果EXC_RETURN的第2位为0,处理器就开始从主栈中进行出栈操作;如果该位为1,则从进程栈中进行。

在这里插入图片描述

出栈完成后,栈指针需要调整。在压栈时,为了使栈帧为双字对齐的,栈空间里可能包含了4字节的空隙。在这种情况下,栈中的xPSR的第9位为1,这样SP的值也应相应地去掉4字节的空隙。

另外,如果EXC_RETURN的第2、3位为1,这就表明异常退出将返回线程模式,当前的SP也应切换回进程栈。

  • 恢复返回地址,取出并执行

异常返回过程完成后,处理器将恢复的返回地址放到程序计数器中,并且继续执行中断。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值