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: /**
2: * @brief Set the Priority Mask value
3: *
4: * @param priMask PriMask
5: *
6: * Set the priority mask bit in the priority mask register
7: */
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: /**
2: * @brief Set the Fault Mask value
3: *
4: * @param faultMask faultMask value
5: *
6: * Set the fault mask register
7: */
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: /**
2: * @brief Set the Base Priority value
3: *
4: * @param basePri BasePriority
5: *
6: * Set the base priority register
7: */
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: /**
2: * @brief Set the priority for an interrupt
3: *
4: * @param IRQn The number of the interrupt for set priority
5: * @param priority The priority to set
6: *
7: * Set the priority for the specified interrupt. The interrupt
8: * number can be positive to specify an external (device specific)
9: * interrupt, or negative to specify an internal (core) interrupt.
10: *
11: * Note: The priority cannot be set for every core interrupt. */
12: static __INLINE
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
13: {
14: if(IRQn < 0) {
/* set Priority for Cortex-M3 System Interrupts */
15: SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff);
16: }
17: else {
/* set Priority for device specific Interrupts */
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: /**
2: * @brief Set the Priority Grouping in NVIC Interrupt Controller
3: *
4: * @param PriorityGroup is priority grouping field
5: *
6: * Set the priority grouping field using the required unlock sequence.
7: * The parameter priority_grouping is assigned to the field
8: * SCB->AIRCR [10:8] PRIGROUP field. Only values from 0..7 are used.
9: * In case of a conflict between priority grouping and available
10: * priority bits (__NVIC_PRIO_BITS) the smallest possible priority group is set.
11: */
12: static __INLINE
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
13: {
14: uint32_t reg_value;
15: /* only values 0..7 are used */
16: uint32_t PriorityGroupTmp = (PriorityGroup & 0x07);
17:
18: reg_value = SCB->AIRCR;
/* read old register configuration */
19: /* clear bits to change */
20: reg_value &= ~(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk);
21: /* Insert write key and priorty group */
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提供。
本文搬运自我的个人网站,查看原文请
点击打开链接