cortex-M3 异常和中断

时间:2022-08-03 14:45:51

最近正努力地深究ucos实时操作系统...........

问题:

1、如何开启、关闭中断

2、如何开启、关闭异常

3、程序只跳转一次时,把跳转需要返回地址直接存储在寄存器LR中。多次跳转呢?

基础概述:

操作模式:

Cortex‐M3 支持 2 个模式和两个特权等级。handler模式和线程模式;  特权级和用户级。

cortex-M3 异常和中断




cortex-M3 寄存器组:

Cortex‐M3 处理器拥有 R0‐R15 的寄存器组。其中 R13 作为堆栈指针 SP。SP 有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。--影子寄存器

如下图:

cortex-M3 异常和中断

这里,R13作为堆栈指针寄存器SP(Stack Pointer),R14作为链接寄存器LR(Link register),R15作为程序计数器PC(program counter);

R0-R12:通用寄存器

R0‐R12 都是 32 位通用寄存器,用于数据操作。但是注意:绝大多数 16 位 Thumb 指令只能访问 R0‐R7,而 32 位 Thumb‐2 指令可以访问所有寄存器。

Banked R13:两个堆栈指针

Cortex‐M3 拥有两个堆栈指针,然而它们是 banked,因此任一时刻只能使用其中的一个。

  • 主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)---master SP
  • 进程堆栈指针(PSP):由用户的应用程序代码使用。 ---process SP
堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。==>u32 的 0x2000 00Fc=......1111 1100b,即最低两位总是0,能够被4整除,不会出现0x2000 00FE这种地址于SP中。

ARM中异常: 凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外部中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的(常用于系统调用)。

R14:连接寄存器

当呼叫一个子程序时,由 R14 存储返回地址。
不像大多数其它处理器,ARM为了减少访问内存的次数(访问内存的操作往往要3 个以上指令周期,带MMU和cache的就更加不确定了),把返回地址直接存储在寄存器中。这样足以使很多只有1级子程序调用的代码无需访问内存(堆栈内存),从而提高了子程序调用的效率。如果多于1级,则需要把前一级的R14值压到堆栈里。在ARM上编程时,应尽量只使用寄存器保存中间结果,迫不得以时才访问内存。在RISC处理器中,为了强调访内操作越过了处理器的界线,并且带来了对性能的不利影响,给它取了一个专业的术语:溅出。

R15:程序计数寄存器

指向当前的程序地址。如果修改它的值,就能改变程序的执行流。

特殊功能寄存器

Cortex‐M3 还在内核水平上搭载了若干特殊功能寄存器,包括:
  • 程序状态字寄存器组(PSRs)
  • 中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)
  • 控制寄存器(CONTROL) 
cortex-M3 异常和中断

特殊功能寄存器只能被专用的 MSR 和 MRS指令访问,而且它们也没有存储器地址。
MRS  <gp_reg>,  <special_reg> ;读特殊功能寄存器的值到通用寄存器 
MSR <special_reg>, <gp_reg> ;写通用寄存器的值到特殊功能寄存器

程序状态寄存器(PSRs 或曰 PSR)

程序状态寄存器在其内部又被分为三个子状态寄存器:
  • 应用程序 PSR(APSR)
  • 中断号 PSR(IPSR) 
  • 执行 PSR(EPSR) 
通过 MRS/MSR 指令,这 3 个 PSRs即可以单独访问,也可以组合访问(2 个组合,3 个组合都可以)。当使用三合一的方式访问时,应使用名字“xPSR”或者“PSR”
详见:cortex-M3权威指南

PRIMASK/FAULTMASK/BASEPRI

这三个寄存器用于控制异常的使能和除能。
PRIMASK 这是个只有1个位的寄存器。当它置1时, 就关掉所有可屏蔽的异常,只剩下NMI
和硬 fault可以响应。它的缺省值是 0,表示没有关中断。
FAULTMASK 这是个只有1个位的寄存器。当它置 1时,只有NMI 才能响应,所有其它的异常,
包括中断和 fault,通通闭嘴。它的缺省值也是0,表示没有关异常。
BASEPRI 这个寄存器最多有9 位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈
值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号
越大,优先级越低)。但若被设成0,则不关闭任何中断,0 也是缺省值。 

对于时间‐关键任务而言,PRIMASK 和 BASEPRI 对于暂时关闭中断是非常重要的。而FAULTMASK 则可以被 OS 用于暂时关闭 fault 处理机能,这种处理在某个任务崩溃时可能需要。因为在任务崩溃时,常常伴随着一大堆 faults。在系统料理“后事”时,通常不再需要响应这些 fault——人死帐清。总之 FAULTMASK 就是专门留给 OS用的。
要访问 PRIMASK, FAULTMASK 以及 BASEPRI,同样要使用 MRS/MSR 指令。只有在特权级下,才允许访问这 3个寄存器。
其实,为了快速地开关中断,CM3还专门设置了一条 CPS指令,有4种用法 :
CPSID I   ;PRIMASK=1,  ;关中断 
CPSIE I ;PRIMASK=0, ;开中断
CPSID F ;FAULTMASK=1, ;关异常
CPSIE F ;FAULTMASK=0 ;开异常

控制寄存器(CONTROL)

控制寄存器用于定义特权级别,还用于选择当前使用哪个堆栈指针。
cortex-M3 异常和中断
CONTROL[1] :
在 Cortex‐M3的 handler 模式中,CONTROL[1]总是 0。在线程模式中则可以为 0 或 1。 仅当处于特权级的线程模式下,此位才可写,其它场合下禁止写此位。改变处理器的模式也有其它的方式:在异常返回时,通过修改 LR 的位 2,也能实现模式切换。--详见权威指南
CONTROL[0] 
仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径,就是触发一个(软)中断,再由服务例程改写该位。

如前所述,特权等级和堆栈指针的选择均由 CONTROL 负责。当 CONTROL[0]=0 时,在异常处理的始末,只发生了处理器模式的转换,如下图所示。
中断前后的状态转换:
cortex-M3 异常和中断
但若 CONTROL[0]=1(线程模式+用户级),则在中断响应的始末,both  处理器模式和特权等极都要发生变化,如下图所示。
cortex-M3 异常和中断
CONTROL[0]只有在特权级下才能访问。用户级的程序如想进入特权级,通常都是使用一条“系统服务呼叫指令(SVC)”来触发“SVC 异常”,该异常的服务例程可以选择修改CONTROL[0]。 当然,其他的异常也可。

关于SVC(supervisor call)和PendSV(可悬起系统调用) 下章整理。

异常类型

异常类型,优先级以及位置。位置是指与向量表开始处的字偏移。在优先级列中,数字越小表示优先级越高。表中还显示了异常类型的激活方式,及是同步的还是异步的标注。 cortex-M3 异常和中断
cortex-M3 异常和中断
渐渐地认清自己的状况,这么多年了,菜鸟依旧伴着我。

开/关中断

CM3专门设置的CPS指令,有四种用法:
CPSID I   ;PRIMASK=1,  ;关中断 
CPSIE I ;PRIMASK=0, ;开中断
CPSID F ;FAULTMASK=1, ;关异常
CPSIE F ;FAULTMASK=0 ;开异常

CMSIS-M3微控制器软件接口标准中的core_cm3.h也给出了开关中断或异常的函数:
/**
* @brief Set the Priority Mask value
*
* @param priMask PriMask
*
* Set the priority mask bit in the priority mask register
*/
static __INLINE void __set_PRIMASK(uint32_t priMask)
{
register uint32_t __regPriMask __ASM("primask");
__regPriMask = (priMask);
}
说明:
使用__set_PRIMASK(1)关闭中断;__setPRIMASK(0)开启中断。 __INLINE是宏定义,对应__inline,这是keil编译器自定义关键字,表示这个函数是内联函数,但并不是强制性内联,编译器最终决定是否内联。 __ASM(“primask”): __ASM也是一个宏,对应__asm,这是keil编译器自定义关键字,关于这个关键字,有相当多的用法,可以在C中内嵌汇编语言、内嵌汇编函数、指定汇编标号以及本代码中的声明一个已命名寄存器变量。这里,已命名的寄存器是("primask"),也就是说寄存器变量__regPriMask等同于编译器已命名的primask。语法为:
register type var-name __asm(reg);

开/关异常

/**
* @brief Set the Fault Mask value
*
* @param faultMask faultMask value
*
* Set the fault mask register
*/
static __INLINE void __set_FAULTMASK(uint32_t faultMask)
{
register uint32_t __regFaultMask __ASM("faultmask");
__regFaultMask = (faultMask & 1);
}


以上...