出处:http://blog.sina.com.cn/s/blog_72605ba50102vv87.html
0.前言
本文想解决的问题有: - 如何开启、关闭中断
- 如何开启、关闭异常
- LPC177x/8x支持的中断优先级个数
- 复位后,异常/中断默认的优先级
- 如何设置异常/中断的优先级
- 什么是优先级组,如何设置优先级组,复位后的优先级组
1. Cortex-M3的异常/中断屏蔽寄存器组注:只有在特权级下,才允许访问这3个寄存器。
名 字 | 功能描述 | PRIMASK | 只有单一比特的寄存器。置为1后,就关掉所有可屏蔽异常,只剩下NMI和硬Fault可以响应。默认值是0,表示没有关闭中断。 | FAULTMASK | 只有单一比特的寄存器。置为1后,只有NMI可以响应。默认值为0,表示没有关异常。 |
BASEPRI | 该寄存器最多有9位(由表达优先级的位数决定)。定义了被屏蔽优先级的阈值。当它被设置为某个值后,所有优先级号大于等于此值的中断都被关。若设置成0,则不关断任何中断,0为默认值。 |
注:寄存器BASEPRI的有效位数受系统中表达优先级的位数影响,如果系统中只使用3个位来表达优先级,则BASEPRI有意义的值仅为0x00、0x20、0x40、0x60、0x80、0xA0、0xC0和0xE0 使用MRS/MSR指令访问这三个寄存器,比如: MRS R0, BASEPRI ;读取BASEPRI到R0中 MSR BASEPRI, R0 ;将R0数据写入到BASEPRI中
为了快速的开关中断,CM3还专门设置了一条CPS指令,有四种用法: CPSID I ;PRIMASK=1,关中断 CPSIE I ;PRIMASK=0,开中断 CPSID F ;FAULTMASK=1,关异常 CPSIE F ;FAULTMASK=0,开异常
CMSIS-M3微控制器软件接口标准中的core_cm3.h给出了开关中断或异常的函数: 1.1 开/关中断 1: 8: static __INLINE void __set_PRIMASK(uint32_t priMask) 9: { 10: register uint32_t __regPriMask __ASM("primask"); 11: __regPriMask = (priMask); 12: }
使用__set_PRIMASK(1)关闭中断;__setPRIMASK(0)开启中断。 一些说明:__INLINE是宏定义,对应__inline,这是keil编译器自定义关键字,表示这个函数是内联函数,但并不是强制性内联,编译器最终决定是否内联。 __ASM(“primask”): __ASM也是一个宏,对应__asm,这是keil编译器自定义关键字,关于这个关键字,有相当多的用法,可以在C中内嵌汇编语言、内嵌汇编函数、指定汇编标号以及本代码中的声明一个已命名寄存器变量。这里,已命名的寄存器是("primask"),也就是说寄存器变量__regPriMask等同于编译器已命名的primask。语法为: register type var-name __asm(reg);keil编译器已命名的寄存器变量为:
寄存器 | __asm修饰的字符串 | 处理器 | APSR | "apsr" | All processors | CPSR | "cpsr" | All processors | BASEPRI | "basepri" | Cortex-M3, Cortex-M4 | BASEPRI_MAX | "basepri_max" | Cortex-M3, Cortex-M4 | CONTROL | "control" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | DSP | "dsp" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | EAPSR | "eapsr" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | EPSR | "epsr" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | FAULTMASK | "faultmask" | Cortex-M3, Cortex-M4 | IAPSR | "iapsr" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | IEPSR | "iepsr" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | IPSR | "ipsr" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | MSP | "msp" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | PRIMASK | "primask" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | PSP | "psp" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | PSR | "psr" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 | r0 to r12 | "r0" to "r12" | All processors | r14 or lr | "r14" or "lr" | All processors | r13 or sp | "r13" or "sp" | All processors | r15 or pc | "r15" or "pc" | All processors | SPSR | "spsr" | All processors, apart from Cortex-M series processors. | XPSR | "xpsr" | Cortex-M0, Cortex-M1, Cortex-M3, Cortex-M4 |
1.2 开/关异常 1: 8: static __INLINE void __set_FAULTMASK(uint32_t faultMask) 9: { 10: register uint32_t __regFaultMask __ASM("faultmask"); 11: __regFaultMask = (faultMask & 1); 12: }
使用__set_FAULTMASK(1)来关闭中断和异常;使用__set_FAULTMASK(0)开启中断和异常. 1.3 更精确的优先级屏蔽 1: 8: static __INLINE void __set_BASEPRI(uint32_t basePri) 9: { 10: register uint32_t __regBasePri __ASM("basepri"); 11: __regBasePri = (basePri & 0xff); 12: }
比如想屏蔽优先级不高于0x60的中断,则使用代码:__set_BASEPRI(0x60);如果想取消中断屏蔽,则使用__set_BASEPRI(0)即可。
2.异常/中断和优先级 Cortex-M3的异常包括系统异常和外设中断,系统异常是Cortex-M3内核自带的一些异常,比如复位、总线Fault和SysTick等等(见表2-1),外设中断是指制造CPU的厂家加入的,比如串口、定时器中断等等(见表2-2)。 注:关于异常和中断,想要分个清清楚楚实在有点困难。异常和中断都可以“中断”正常执行的代码流,区别在于,异常是Cortex-M3内核产生的“中断”信号,而中断是Cortex-M3内核外部(片上外设或外部中断信号)产生的“中断”信号。希望你看懂了,有时候你心里明白,但要讲的清清楚楚着实难! 表2-1:系统异常
编号 | 类型 | 优先级 | 简介 | 0 | N/A | N/A | 无 | 1 | 复位 | -3(最高) | 复位 | 2 | NMI | -2 | 不可屏蔽中断(来自外部NMI输入脚) | 3 | 硬Fault | -1 | 只要FAULTMASK没有置位,硬Fault服务例程会被强制执行 | 4 | 存储器管理Fault | 可编程 | MPU访问违例以及访问非法位置均可引发。企图在“非执行区”取址也会引发此Fault。 | 5 | 总线Fault | 可编程 | 总线收到了错误响应,原因可以使预取流产或数据流产,企图访问协处理器也会引发此Fault | 6 | 用法Fault | 可编程 | 由于程序错误导致的异常。通常是使用了一条无效指令,或者是非法的状态转换,例如尝试切换到ARM状态 | 7~10 | 保留 | 保留 | 保留 | 11 | SVCall | 可编程 | 执行系统服务调用指令(SVC)引发的异常 | 12 | 调试监视器 | 可编程 | 调试器(断点、数据观察点,或者是外部调试请求) | 13 | 保留 | 保留 | 保留 | 14 | PendSV | 可编程 | 为系统设备而设的“可挂起请求” | 15 | SysTick | 可编程 | 系统节拍时钟定时器(SysTick) |
表2-2:外设中断 编号 | 类型 | 优先级 | 简介 | 16 | IRQ #0 | 可编程 | 外设中断#0 | 17 | IRQ #1 | 可编程 | 外设中断#1 | ... | ... | 可编程 | ... | 255 | IRQ #239 | 可编程 | 外设中断#239 |
注:表2-1和2-2中的“编号”有着特殊的意义,一是特殊功能寄存器IPSR中会记录当前正在服务的异常并给出了它的编号;二是优先级完全相同的多个异常同时挂起时,则先响应异常编号最小的那一个。 一个发生的异常如果不能被立即响应,就称它被“挂起”,值得一提的是,对于被挂起的中断/异常,中断/异常信号不必由其产生者保持,NVIC的挂起状态寄存器会来保持这个信号。所以哪怕后来挂起的中断源释放了中断请求信号,曾经的中断请求也不会丢失。 除了复位、NMI和硬Fault三个异常具有固定的优先级外,其它所有异常和中断的优先级都是可以编程的。这就涉及到优先级配置寄存器。Cortex-M3优先级配置寄存器共8位,所以可以有256级的可编程优先级。但是大多数Cortex-M3芯片都会精简设计。 LPC177x/8x使用了优先级配置寄存器的5位,所以有32级可编程优先级。复位后,对于所有优先级可编程的异常,其优先级都被初始化为0(最高优先级) 2.1 设置异常/中断的优先级2.1.1 系统异常优先级设置 SHPR1-SHPR3寄存器用于设置有可编程优先级的系统异常,可设置的优先级为0到31。SHPR1-SHPR3可按字节访问。为了提高软件效率,CMSIS简化了SCB寄存器的表述。在CMSIS中,字节数组SHP[0] 到SHP[12]对应于寄存器SHPR1至SHPR3。 表2-3:SHPR1寄存器的位分配 位 | 名称 | 功能 | [31:24] | PRI_7 | 保留 | [23:16] | PRI_6 | 系统处理程序6的优先级,用法Fault | [15:8] | PRI_5 | 系统处理程序5的优先级,总线Fault | [7:0] | PRI_4 | 系统处理程序4的优先级,存储器管理Fault |
表2-3:SHPR2寄存器的位分配 位 | 名称 | 功能 | [31:24] | PRI_11 | 系统处理程序11的优先级,SVCall | [23:0] | - | 保留 |
表2-4:SHPR3寄存器的位分配 位 | 名称 | 功能 | [31:24] | PRI_15 | 系统处理程序15的优先级,SysTick 异常 | [23:16] | PRI_14 | 系统处理程序14的优先级,PendSV | [15:0] | - | 保留 |
注:每个PRI_N域为8位宽,但是处理器仅实现每个域的位[7:3],位[2:0]读取值为零并忽略写入值。 2.1.2 外设中断优先级设置 LPC177x/8x微处理器的中断优先寄存器IPR0~IPR10用于设置外设中断优先级,控制41个外设中断。每个IPRx可以按字节访问,在CMSIS中,字节数组IP[0] 到IP[40]对应于寄存器IPR0~IPR10。 2.1.3 系统异常/外设中断优先级设置C代码 1: 12: static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) 13: { 14: if(IRQn < 0) { 15: SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); 16: } 17: else { 18: NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); 19: } 20: }
其中,参数IRQn为中断ID号,可以为负,也可以为正。当IRQn为负时,设置系统异常的优先级,当IRQn大于等于0时,设置外设中断优先级。__NVIC_PRIO_BITS是指使用到的优先级配置寄存器的位数,LPC177x/8x使用了5位。为什么要使用(8-__NVIC_PRIO_BITS)呢?这是因为优先级配置寄存器是高位对齐的(MSB),这主要方面不同CPU间的移植。参数priority为要设置的优先级值,为0~31,数值越低,表示优先级越大。LPC177x/8x的中断ID为: 系统异常ID:
标号 | 中断ID | 描述 | NonMaskableInt_IRQn | -14 | 不可屏蔽中断 | MemoryManagement_IRQn | -12 | Cortex-M3内存管理中断 | BusFault_IRQn | -11 | Cortex-M3 总线Fault中断 | UsageFault_IRQn | -10 | Cortex-M3 用法Fault 中断 | SVCall_IRQn | -5 | Cortex-M3 SV Call中断 | DebugMonitor_IRQn | -4 | Cortex-M3 调试监视中断 | PendSV_IRQn | -2 | Cortex-M3 Pend SV中断 | SysTick_IRQn | -1 | Cortex-M3 系统Tick中断 |
外设中断ID: 标号 | 中断ID | 描述 | 标号 | 中断ID | 描述 | WDT_IRQn | 0 | 看门狗 | EINT3_IRQn | 21 | 外中断3 | TIMER0_IRQn | 1 | 定时器0 | ADC_IRQn | 22 | AD转换 | TIMER1_IRQn | 2 | 定时器1 | BOD_IRQn | 23 | 欠压检测 | TIMER2_IRQn | 3 | 定时器2 | USB_IRQn | 24 | USB | TIMER3_IRQn | 4 | 定时器3 | CAN_IRQn | 25 | CAN | UART0_IRQn | 5 | UART0 | DMA_IRQn | 26 | 通用DMA | UART1_IRQn | 6 | UART1 | I2S_IRQn | 27 | I2S | UART2_IRQn | 7 | UART2 | ENET_IRQn | 28 | 以太网 | UART3_IRQn | 8 | UART3 | MCI_IRQn | 29 | SD/MMC卡I/F | PWM1_IRQn | 9 | PWM1 | MCPWM_IRQn | 30 | 电机控制PWM | I2C0_IRQn | 10 | I2C0 | QEI_IRQn | 31 | 正交编码接口 | I2C1_IRQn | 11 | I2C1 | PLL1_IRQn | 32 | PLL1锁存 | I2C2_IRQn | 12 | I2C2 | USBActivity_IRQn | 33 | USB活动 | Reserved0_IRQn | 13 | 保留 | CANActivity_IRQn | 34 | CAN活动 | SSP0_IRQn | 14 | SSP0 | UART4_IRQn | 35 | UART4 | SSP1_IRQn | 15 | SSP1 | SSP2_IRQn | 36 | SSP2 | PLL0_IRQn | 16 | PLL0锁存 | LCD_IRQn | 37 | LCD | RTC_IRQn | 17 | RTC | GPIO_IRQn | 38 | GPIO | EINT0_IRQn | 18 | 外中断0 | PWM0_IRQn | 39 | PWM0 | EINT1_IRQn | 19 | 外中断1 | EEPROM_IRQn | 40 | EEPROM | EINT2_IRQn | 20 | 外中断2 | | | |
2.2 设置异常/中断的优先级组 Cortex-M3的异常/中断是可以抢占的,高抢占优先级中断可以抢占低抢占优先级中断。NVIC中有个名字叫做“应用程序中断及复位控制寄存器(AIRCR)”的寄存器,该寄存器的bit[10:8]称为优先级分组(PRIGROUP)段,表示的值为0~7,分别对应8个不同的抢占优先级设置。比如优先级分组段为0时,则8位优先级配置寄存器(LPC177x/8x只使用了其中的5位)的bit[7:1]表示抢占优先级,bit[0:0]表示非抢占优先级;再比如优先级分组段为1时,则8位优先级配置寄存器的bit[7:2]表示抢占优先级,bit[1:0]表示非抢占优先级,依次类推。 复位后,优先级分组(PRIGROUP)段默认值为0,也就是则8位优先级配置寄存器(LPC177x/8x只使用了其中的5位)的bit[7:1]表示抢占优先级,bit[0:0]表示非抢占优先级。而LPC177x/8x只使用了8位优先级配置寄存器其中的bit[7:3],所以对于LPC177x/8x微处理器而言,复位后默认32级优先级全部为可抢占优先级。 2.2.1 设置优先级寄存器组的C代码 1: 12: static __INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) 13: { 14: uint32_t reg_value; 15: 16: uint32_t PriorityGroupTmp = (PriorityGroup & 0x07); 17: 18: reg_value = SCB->AIRCR; 19: 20: reg_value &= ~(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk); 21: 22: reg_value = (reg_value | 23: (0x5FA << SCB_AIRCR_VECTKEY_Pos) | 24: (PriorityGroupTmp << 8)); 25: SCB->AIRCR = reg_value; 26: }
其中,参数PriorityGroup为要设置的优先级分组(PRIGROUP)段的值,取值范围为0~7.由于操作AIRCR寄存器需要访问钥匙,所以要把0x05FA写入到该寄存器的bit[31:16]中,否则写入的值会被忽略。 需要注意的是,在一个设计好的产品中,如果没有十足的把握,不要修改优先级组,不然会有大恶魔纠缠你的。 最后,本文所使用的代码,全部由CMSIS-M3提供。
|