在CMSIS-Core中,中断和异常的相关寄存器不止存在于NVIC数据结构中,还有一部分在系统控制块(SCB)的数据结构中。
下面是SCB中的寄存器一览表,这些是所有的寄存器,这里面只有一部分与中断和异常有关:
ICSR=>Interrupt Control State Register,此寄存器的主要作用:
向量表偏移寄存器地址为0xE000ED0C,可由CMSIS-Core中的SCB->VTOR来访问。这个寄存器里面始终存着要使用的向量表。具体说明如下:
AIRCR=>Application Interrupt Reset Control Register。多数情况下,可以使用CMSIS-Core函数NVIC_SetPriorityGrouping和NVIC_GetPriorityGrouping来访问PRIGROUP,如下图。VECTRESET和VECTCLRACTIVE位域是为调试器设计的,尽管软件可以利用VECTRESET触发一次处理器复位,不过由于它不会复位外设等系统中的其他部分,因此多数应用程序是不大会用到它的。若想产生一次系统复位,多数情况下(取决于芯片设计和应用复位需求)应该使用SYSRESETREQ。
有一点要注意,VECTRESET和VECTCLRACTIVE不应同时置位,非要这么做的话会导致Cortex-M3/M4设备的复位电路出错,这是因为VECTRESET信号会复位SYSRESETREQ。根据微控制器复位电路设计,将1写入SYSRESETREQ后,处理器可能会在复位实际产生前继续执行几条指令,因此,通常要在系统复位请求后加上一个死循环,保证不会执行后面的代码。
这个寄存器组和中断优先级寄存器组类似,但是SHP寄存器是用于系统的异常,配置系统异常优先级同样可以用CMSIS-Core的接口,NVIC_SetPriority和NVIC_GetPriority。具体的寄存器如下:
注意这一组寄存器都是一个字节8bit的,在很多的操作系统中会把systick和pendsv系统异常配置为最低的优先级,具体的代码如下:
// FreeRTOS
#define configKERNEL_INTERRUPT_PRIORITY 255
#define portNVIC_PENDSV_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL ) // 0xe000ed22
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL ) // 0xe000ed23
#define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
此处为什么要配置0xe000ed20寄存器呢?因为代码中把0xe000ed20当做是一个32bit的寄存器,操作它就可以操作0xe0xe000ed21, 0xe000ed22, 0xe000ed23。
使用错误、存储器管理错误和总线错误异常的使能由此寄存器(0xe000ed24)来操作,错误的挂起状态和多数系统异常的活跃状态也可以从这个寄存器中得到。多数情况下,该寄存器仅用于应用代码使能可配置的错误处理,但是在操作寄存器的时候要多加小心,因为这些系统异常的活跃状态位是可读可写的,因此千万不能把活跃状态位意外地修改掉,不然,若一个已经被激活的系统异常的活跃状态被意外清掉,当系统异常处理产生异常退出时就会出现错误异常。具体的寄存器如下:
很多应用中,为了保护临界区代码的安全,需要暂时关闭中断,以执行一些关键的任务,此时就可以使用PRIMASK寄存器。PRIMASK寄存器只能在特权模式下访问。
PRIMASK寄存器可以理解为一个bit位,或者一个开关,置1就会禁止NMI和HardFault外所有异常,它实际上是将当前优先级改为0(即最高的可编程优先级),此时其他的中断都无法被响应,但是是可以被pending住。将PRIMASK置0就重现使能被禁止的中断和异常。具体的代码如下:
可以使用CMSIS-Core提供的函数来操作寄存器:
void _enable_irq(); // 清除PRIMASK 置0使能中断
void _disable_irq(); // 设置PRIMASK 置1禁止中断
void _set_PRIMASK(uint32_t priMask); // 设置PRIMASK为特定值
uint32_t _get_PRIMASK(void); // 读取PRIMASK的数值
同样可以利用汇编语句CPS(修改处理器状态)指令来修改PRIMASK寄存器的数值,具体代码如下:
CPSIE I ;清除PRIMASK 置0使能中断 E=>Enable
CPSID I ;设置PRIMASK 置1禁止中断 D=>Disable
PRIMASK寄存器还可以通过MRS和MSR指令访问,具体代码如下:
MOV R0, #1 ;将1写入R0寄存器中
MSR PRIMASK, R0 ;将R0写入到PRIMASK寄存器中,即将1写入到PRIMASK中,禁止中断
MOV R0, #0 ;将0写入R0寄存器中
MSR PRIMASK, R0 ;将R0写入到PRIMASK寄存器中,即将0写入到PRIMASK中,使能中断
当PRIMASK置位时,所有的错误事件都会触发HardFault异常,而不论相应的可配置错误异常(如MemManage、总线错误和使用错误)是否使能
如下表是异常和中断的相关优先级,FAULTMASK和PRIMASK类似,只是它把当前优先级修改为-1,此时连硬件错误hardfault都无法被响应,只有NMI异常处理才能执行,因为它的优先级为-2。
FAULTMASK寄存器只能在特权状态访问,不过不能再NMI和Hardfault处理中设置。若在C编程中使用符合CMSIS的设备驱动,可以使用如下函数来设置和清除FAULTMASK寄存器:
void _enable_fault_irq(void); // 清除FAULTMASK 置0使能中断
void _disable_fault_irq(void); // 设置FAULTMASK 置1禁止中断
void _set_FAULTMASK(uint32_t faultMask);
uint32_t _get_FAULTMASK(void);
同样FAULTMASK寄存器同样可以用汇编语句来操作:
CPSIE F ;清除FAULTMASK 置0使能中断 E=>Enable F=>FAULTMASK
CPSID F ;设置FAULTMASK 置1禁止中断 D=>Disable
MOV R0, #1 ;将1写入R0寄存器中
MSR FAULTMASK, R0 ;将R0写入到FAULTMASK寄存器中,即将1写入到FAULTMASK中,禁止中断
MOV R0, #0 ;将0写入R0寄存器中
MSR FAULTMASK, R0 ;将R0写入到FAULTMASK寄存器中,即将0写入到FAULTMASK中,使能中断
FAULTMASK会在退出异常处理是自动被清除,但是从NMI处理中退出时除外。根据这个特点就可以实现在低优先级中断中触发一个高优先级中断,但这个高优先级中断不会抢占当前的低优先级中断,要等当前的低优先级中断处理完成之后再去执行,具体的操作如下:
有些情况下,只想禁止特定优先级的中断,此时就可以使用BASEPRI寄存器。使用方法就是将想要屏蔽的优先级写入到BASEPRI寄存器中。例如:如果想屏蔽优先级小于等于0x60的所有异常(数值越小中断优先级越高),就将0x60写入到BASEPRI寄存器中。具体代码如下:
_set_BASEPRI(0x60); // 利用CMSIS-Core函数禁止优先级在0x60~0xff之间的中断
uint32_t x;
x = _get_BASEPRI(void); // 读出BASEPRI的数值
_set_BASEPRI(0x00); // 写0取消BASEPRI屏蔽
同样的操作也可以用汇编来实现:
MOVS R0, #0x60
MSR BASEPRI, R0 ;禁止优先级在0x60~0xff之间的中断
MRS R0, BASEPRI ;将BASEPRI的值写入R0寄存器中
MOVS R0, #0x0
MSR BASEPRI, R0 ;取消BASEPRI屏蔽
BASEPRI寄存器还可以通过另一个名字来访问,BASPRI_MAX。这两个名字指示的是统一个寄存器,就好像同一个人一个大名一个小名。但是使用BASEPRI_MAX这个名字的时候,会得到一个条件写操作,即处理器会自动比较当前值和新的数值,不过只有新的优先级更高(即数值越小)时才会允许修改,否规是不会的,具体的指令如下:
MOVS R0, #0x60
MSR BASEPRI_MAX, R0 ;禁止优先级小于等于0x60的中断
MOVS R0, #0xf0
MSR BASEPRI_MAX, R0 ;由于数值大于0x60,即优先级低于0x60,所以本次操作无效
MOVS R0, #0x40
MSR BASEPRI_MAX, R0 ;由于数值小于0x60,即优先级高于0x60,所以BASEPRI寄存器被修改为0x40
BASEPRI/BASEPRI_MAX寄存器无法在非特权状态设置。 与其他的优先级寄存器类似,BASEPRI寄存器的格式受实际的优先级寄存器宽度影响。例如如果优先级寄存器值实现了3位,BASEPRI只能被设置为0x00、0x20、0x40···、0xC0和0xE0。