Cortex-M系列中断和异常(二)

文章目录

  • 1. 中断与异常
    • 1.1 异常的处理流程
      • 1.1.1 接受异常请求
      • 1.1.2 异常进入的流程
      • 1.1.3 异常处理流程
      • 1.1.4 异常返回流程
    • 1.2 中断控制用的NVIC寄存器
      • 1.2.1 中断使能/失能寄存器
      • 1.2.2 中断挂起寄存器/清除挂起寄存器
      • 1.2.3 活跃状态寄存器
      • 1.2.4 中断优先级寄存器
      • 1.2.5 软件触发中断寄存器
      • 1.2.6 中断控制器类型寄存器


1. 中断与异常

    上一篇文章解析了中断和异常的意义,基本属性和中断输入和挂起的流程,这篇文章接着介绍中断具体的执行流程,中断向量控制器NVIC的具体寄存器和系统控制块SCB中的寄存器。

1.1 异常的处理流程

    此处中断和异常是同一个概念,下面统称为异常。

1.1.1 接受异常请求

    处理器能够接受异常并执行异常的条件有如下几个(此处的接受异常是指能够执行中断处理函数):
    (1)处理器处于运行的状态(没有被hold住,没有处于reset状态);
    (2)异常是处于使能状态的,即中断使能控制寄存器的相应bit位被置位,但是NMI和HardFault异常位特殊情况,他们总是被使能的;
    (3)如果当前处理器正在处理异常,此时又来了一个异常,又来了这个异常一定要高于当前优先级才会被立即执行,但是如果低于当前优先级,就会将Pending位置位,处理完当前优先级后,有了空余机会就会处理新来c的这个异常;
    (4)异常没有被异常屏蔽寄存器(如PRIMASK)屏蔽,如果被屏蔽了但中断请求还是来了,那么会将Pending位置位,使能中断之后才会去执行。
    注:SVC异常比较特殊,若SVC指令被意外用在某异常处理过程中,即在其他异常处理过程中call SVC异常,而且该异常的优先级小于SVC,它就会引起HardFault异常。

1.1.2 异常进入的流程

    当系统进入异常,硬件回去做一系列的操作,保证处理完中断处理函数(ISR)之后仍然回到常规业务上,具体的流程如下:
    (1)当处理器决定执行中断,首先会把当前正在运行的线程或者主业务的状态信息保存到栈中(压栈),具体就是将CPU相关寄存器和返回的地址压入当前使用的栈中,这个栈可能是进程栈PSP也可能是主栈MSP,如果处理器处于线程模式且正在使用PSP,那么压栈就会选择进程栈,否则就选MSP。
    (2)压栈之后,处理器要获得中断处理函数的入口地址才能执行中断处理函数,这时从中断向量表中获取ISR的起始地址,通常获取地址的操作和压栈的操作是同时进行的。
    (3)获取到中断处理函数的地址之后就开始执行中断处理函数。
    (4)更新多个NVIC寄存器和内核寄存器,包括挂起状态寄存器(这个需要软件来清掉)和异常的活跃状态,处理器内核寄存器中的寄存器包括程序状态寄存器(PSR),链接寄存器(LR),程序计数器(PC)以及栈指针(SP)。
    由于压栈的过程,PSP或MSP肯定会发生变化,PC也会被更新为异常处理的起始地址,链接寄存器LR会被更新为EXC_RETURN的特殊值。该数值为32为,且高27位为1。低5位中有些部分用于保存异常流程的状态信息(如压栈时使用哪个栈),知道压栈时使用的那个栈,从异常退出之后才知道从哪里恢复数据。

1.1.3 异常处理流程

     所谓异常处理就是执行异常处理函数,在执行函数期间处理器就会处于处理模式。处理模式下栈操作使用主栈指针(MSP),同时处理器运行在特权访问等级。
     处理异常的过程中如果又来了一个异常,如果这个异常的优先级比当前的中断更高,那么就去执行更高优先级的中断,这就叫中断嵌套,如果来的中断优先级和现在的中断优先级一样或者是更低,那么新来的这个中断就会被pending住,直到当前的中断执行完再去相应这个新来的中断。
     在异常处理的结尾,程序代码执行的返回会引起EXC_RETURN数值被加载到程序计数器(PC),并触发异常返回机制。

1.1.4 异常返回流程

     对于ARM Cortex-M处理器来说,异常返回机制由一个特殊的地址EXC_RETURN触发,该数值在异常入口处产生且被存储在链接寄存器LR中,当该数值由某个允许的异常返回指令写入PC时,它就会触发异常返回流程。具体的过程如下图
Cortex-M系列中断和异常(二)_第1张图片
     当触发了异常返回的机制之后,处理器会访问栈空间里进入异常期间被压入栈中的寄存器的数值,把栈中的数值再恢复到寄存器组中,这个过程叫出栈,在出栈操作的同事,处理器会取出之前被中断的程序指令,继续执行主程序。触发异常返回的指令如下:
Cortex-M系列中断和异常(二)_第2张图片
     PS:由于EXC_RETURN机制,函数一般不会返回地址0xf0000000~0xffffffff,不过,根据架构定义,这段地址区域不能用于程序代码[具有永不执(XN)存储属性],因此这样也不会有什么问题。

1.2 中断控制用的NVIC寄存器

     NVIC中有多个用于中断控制的寄存器(异常类型16~255),这些寄存器位于系统控制空间(SCS)地址区域内,NVIC用于控制的中断的寄存器如下:
Cortex-M系列中断和异常(二)_第3张图片
     除了软件触发中断寄存器(STIR)外,所有这些寄存器都只能在特权等级访问。STIR默认只能在特权等级访问,不过可以配置为非特权等级访问。根据默认设置,系统复位之后:(1)所有中断被禁止(使能位=0);(2)所有中断的优先级为0(最高可编程优先级);(3)所有中断的挂起状态清0;
     PS: 这一章中的所有内容只适用于外部中断,即异常编号16以后的,即枚举值大于等于0的,中断的编号#n,即枚举值。

1.2.1 中断使能/失能寄存器

     注意这里中断使能控制寄存器和失能寄存器不是同一个,但是都是写1来控制。NVIC->ISER[n]和NVIC->ICER[n],两个寄存器都是32bit,每一个bit代表一个中断的使能和失能,这里为什么有[n]呢?是由于Cortex-M4最多支持255个中断,一个使能和失能寄存器肯定是不够用的。由于前16个异常类型为系统异常,因此外部中断#0的异常编号为16,具体细节如下:(ISER=>Interrupt Set Enable Register; ICER=>Interrupt Clear Enable Register)
Cortex-M系列中断和异常(二)_第4张图片
     中断使能/失能寄存器:
Cortex-M系列中断和异常(二)_第5张图片
Cortex-M系列中断和异常(二)_第6张图片
     CMSIS-Core提供了使能和失能中断的函数,具体如下:

void NVIC_EnableIRQ(IRQn_Type IRQn);
void NVIC_DisableIRQ(IRQn_Type IRQn);

1.2.2 中断挂起寄存器/清除挂起寄存器

     如果中断产生了但没有立即被执行,这时候中断挂起寄存器相应的bit位就会被置位,和使能/失能中断寄存器一样,挂起也是通过两个寄存器来使能和失能的,它们分别为NVIC->ISPR[n](使能)NVIC->ICPRn(ISPR=>Interrupt Set Pending Register; ICPR=>Interrupt Clear Pending Register),通过前者挂起一个中断,通过后者清掉挂起位。具体的寄存器如下:
Cortex-M系列中断和异常(二)_第7张图片
     CMSIS-Core提供了设置挂起和清掉挂起的函数,具体如下:

void NVIC_SetPendingIRQ(IRQn_Type IRQn);     // 设置中断挂起
void NVIC_ClearPendingIRQ(IRQn_Type IRQn);   // 清除中断挂起
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn); // 获取中断挂起的状态

1.2.3 活跃状态寄存器

     每个外部中断都有一个活跃状态位,当处理器开始执行中断时,该位会被置1,而在执行中断返回时会被清0。如果中断执行的过程中被更高优先级的中断抢占,则这两个中断的活跃状态位都为1。 IPSR表示当前正在执行的异常服务,如图4.5,但是它无法告诉你当有中断嵌套的时候,哪些中断是处于活跃状态的。 中断状态寄存器是只读的。
Cortex-M系列中断和异常(二)_第8张图片
Cortex-M系列中断和异常(二)_第9张图片
     CMSIS-Core提供了获取外部中断活跃状态的函数接口,具体如下:

uint32_t NVIC_GetActive(IRQn_Type IRQn);  // 获取中断的活跃状态

1.2.4 中断优先级寄存器

     每个中断都有对应的优先级寄存器,最大位宽为8位,最小为3位,这个和芯片设计有关系。每个寄存器又被分为两组,一组是抢占优先级,另一组是子优先级,STM32中只有抢占优先级。优先级寄存器的数量取决于芯片中实际的外部中断数。具体的寄存器如下:
Cortex-M系列中断和异常(二)_第10张图片
     CMSIS-Core提供了设置中断优先级和获取中断优先级的函数接口,具体如下:

void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority); // 设置中断优先级
uint32_t NVIC_GetPriority(IRQn_Type IRQn);                // 获取中断优先级

     当拿到一款MCU不知道优先级究竟用了多少bit的时候,可以用两种方法来确定:(1)可以使用微控制器供应商提供的CMSIS-Core头文件中的“_NVIC_PRIO_BITS”伪指令。(2)直接往任何一个优先级寄存器中写0xff,然后在读这个寄存器,看看有几个1。例如如果读出0xE0,那么就证明用了3个bit来表示优先级。

1.2.5 软件触发中断寄存器

     除了通过NVIC->ISPR[n]来起一个中断,还可以通过软件触发中断寄存器(NVIC->STIR)来触发中断,具体操作就是往此寄存器中写入中断的编号(CMSIS中的中断枚举值),具体的操作如下图:
在这里插入图片描述
     例如想要触发外部中断3,可以采用如下两种方法:
     (1)利用CMSIS-Core函数:

NVIC_SetPendingIRQ(Timer0_IRQn);  // 假定Timer0_IRQn的中断编号等于3

     (2)利用NVIC->STIR:

NVIC->STIR = 3; 

     NVIC->ISPR[n]寄存器只能在处理器的特权模式下访问,如果想要在非特权模式下产生一个软件中断,可以设置==配置控制寄存器(0xE000ED14)==的bit1(USERSETMPEND)。USERSETMPEND这个bit位默认为清0状态,这就意味着只有特权代码才能使用NVIC->STIR寄存器。
     ==PS:NVIC->ISPR[n]和NVIC->STIR一样无法触发NMI以及Systick等系统异常。系统控制块(SCB)中的其他寄存器用于控制异常。

1.2.6 中断控制器类型寄存器

     NVIC在0xE000E004地址处还有一个中断控制器类型寄存器,它是一个只读寄存器,给出了NVIC支持的中断输入的数量,就是说支持多少个外部中断,单位为32。具体的寄存器如下图:
Cortex-M系列中断和异常(二)_第11张图片
     利用CMSIS的设备驱动库,可以使用SCnSCB->ICTR来访问这个只读寄存器。(SCnSCB表示未在SCB中的系统控制寄存器)。SCnSCB->ICTR这个寄存器只会给出支持的最多数量,==如果想获得中断的确切数量,可以用如下方法:在PRIMASK置位的情况下(禁止中断产生),写入中断使能/挂起寄存器,读回后查看使能/挂起寄存器中实际实现的位数。即写入0xffffffff到中断使能寄存器之后,再读寄存器的值如果还是0xffffffff证明都被使用了。具体如下图:
Cortex-M系列中断和异常(二)_第12张图片

你可能感兴趣的:(Cortex-M系列内核,中断相关寄存器,Cortex-M4)