如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此 时中断被悬起。中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND)”和“中断悬起清除寄 存器(CLRPEND)”来读取,还可以写它们来手工悬起中断。
悬起寄存器和“解悬”寄存器也可以有 8 对,其用法和用量都与前面介绍的使能/除能寄存器
完全相同,见表 8.2。
表 8.2 SETPEND/CLRPEND 寄存器族 (此表参考官方技术参考手册作了些改编——译者注)
SETPENDs:0xE000_E200 – 0xE000_E21C ; CLRPENDs:0xE000E280 - 0xE000_E29C
名称 |
类型 |
地址 |
复位值 |
描述 |
SETPEND0 |
R/W |
0xE000_E200 |
0 |
中断 0-31的悬起寄存器,共 32个悬起位 位[n],中断#n悬起(异常号 16+n) |
SETPEND1 |
R/W |
0xE000_E204 |
0 |
中断 32-63的悬起寄存器,共 32个悬起位 |
… |
… |
… |
… |
… |
SETPEND7 |
R/W |
0xE000_E21C |
0 |
中断 224-239的悬起寄存器,共 16个悬起位 |
CLRPEND0 |
R/W |
0xE000_E280 |
0 |
中断 0-31的解悬寄存器,共 32个解悬位 位[n],中断#n解悬(异常号 16+n) |
CLRPEND1 |
R/W |
0xE000_E284 |
0 |
中断 32-63的解悬寄存器,共 32个解悬位 |
… |
… |
… |
… |
… |
CLRPEND7 |
R/W |
0xE000_E29C |
0 |
中断 224-239的解悬寄存器,共 16个解悬位 |
每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8位,但是 CM3允许在最“粗线 条”的情况下,只使用最高 3位。4个相临的优先级寄存器拼成一个 32位寄存器。如前所述,根据 优先级组的设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级。优先级寄存器 都可以按字节访问,当然也可以按半字/字来访问。有意义的优先级寄存器数目由芯片厂商实现的 中断数目决定,优先级配置寄存器的详细信息在附录D中给出(表 D.18)。
表 8.3 中断优先级寄存器阵列 0xE000_E400 – 0xE000_E4EF
名称 |
类型 |
地址 |
复位值 |
描述 |
PRI_0 |
R/W |
0xE000_E400 |
0(8位) |
外中断#0的优先级 |
PRI_1 |
R/W |
0xE000_E401 |
0(8位) |
外中断#1的优先级 |
… |
… |
… |
… |
… |
PRI_239 |
R/W |
0xE000_E4EF |
0(8位) |
外中断#239的优先级 |
表8.3B 系统异常优先级寄存器阵列 0xE000_ED18 - 0xE000_ED23
地址 |
名称 |
类型 |
复位值 |
描述 |
0xE000_ED18 |
PRI_4 |
存储器管理 fault的优先级 |
||
0xE000_ED19 |
PRI_5 |
总线 fault的优先级 |
||
0xE000_ED1A |
PRI_6 |
用法 fault的优先级 |
||
0xE000_ED1B |
- |
- |
- |
- |
0xE000_ED1C |
- |
- |
- |
- |
0xE000_ED1D |
- |
- |
- |
- |
0xE000_ED1E |
- |
- |
- |
- |
0xE000_ED1F |
PRI_11 |
SVC优先级 |
||
0xE000_ED20 |
PRI_12 |
调试监视器的优先级 |
||
0xE000_ED21 |
- |
- |
- |
- |
0xE000_ED22 |
PRI_14 |
PendSV的优先级 |
||
0xE000_ED23 |
PRI_15 |
SysTick的优先级 |
每个外部中断都有一个活动状态位。在处理器执行了其 ISR的第一条指令后,它的活动位就被 置 1,并且直到 ISR返回时才硬件清零。由于支持嵌套,允许高优先级异常抢占某个 ISR。然而, 哪怕中断被抢占,其活动状态也依然为 1(请仔细琢磨前文讲到的“直到 ISR返回时才清零)。活动 状态寄存器的定义,与前面讲的使能/除能和悬起/解悬寄存器相同,只是不再成对出现。它们也能 按字/半字/字节访问,但他们是只读的,如表 8.4所示。
表 8.4 ACTIVE 寄存器族 0xE000_E300_0xE000_E31C(此表参考官方技术参考手册作了些改编——
译者注)
名称 |
类型 |
地址 |
复位值 |
描述 |
ACTIVE0 |
RO |
0xE000_E300 |
0 |
中断0-31的活动状态寄存器,共32个状态 位 位[n],中断 #n活动状态(异常号16+n) |
ACTIVE1 |
RO |
0xE000_E304 |
0 |
中断 32-63 的活动状态寄存器,共 32 个状 态位 |
… |
… |
… |
… |
… |
ACTIVE7 RO 0xE000_E31C 0 中断 224-239 的活动状态寄存器,共 16 个
状态位
PRIMASK用于除能在 NMI和硬 fault之外的所有异常,它有效地把当前优先级改为 0(可编程 优先级中的最高优先级)。该寄存器可以通过 MRS和MSR以下例方式访问:
1. 关中断
MOV |
R0, |
#1 |
MSR 2. 开中断 MOV |
PRIMASK, R0, |
R0 #0 |
MSR |
PRIMASK, |
R0 |
此外,还可以通过CPS指令快速完成上述功能:
CPSID i ;关中断
CPSIE i ;开中断
FAULTMASK更绝,它把当前优先级改为-1。这么一来,连硬fault都被掩蔽了。使用方案与
PRIMASK的相似。但要注意的是,FAULTMASK会在异常退出时自动清零。 掩蔽寄存器虽然能一手遮天,却都动不了NMI,因为NMI是用在最危急的情况下的。因此系统为
它开出单行道,无需挂号只是不要迟到。当NMI激活时,“谁都是省略号,唯独是你不得了,第一优 先谁比你重要”!试想,如果NMI被连接到系统的掉电报警线上,且系统是体外循环机的电源管理 器……如果因为中断被除能就视而不见,则会使体外循环机因断电而失能,体外循环序列可以被意 外终止,病人的生命也将丢失。
在更精巧的设计中,需要对中断掩蔽进行更细腻的控制——只掩蔽优先级低于某一阈值的中断
——它们的优先级在数字上大于等于某个数。那么这个数存储在哪里?就存储在BASEPRI中。不过, 如果往BASEPRI中写0,则另当别论——BASEPRI将停止掩蔽任何中断。例如,如果我们需要掩蔽所 有优先级不高于0x60的中断,则可以如下编程:
MOV R0, #0x60
MSR BASEPRI, R0
如果需要取消 BASEPRI对中断的掩蔽,则示例代码如下:
MOV R0, #0
MSR BASEPRI, R0
另外,我们还可以使用BASEPRI_MAX这个名字来访问BASEPRI寄存器,它俩其实是同一个寄存 器。但是当我们使用这个名字时,会使用一个条件写操作。个中原因如下:尽管它俩在硬件水平上 是同一个寄存器,但是生成的机器码不一样,从而硬件的行为也不同:使用BASEPRI时,可以任意 设置新的优先级阈值;但是使用BASEPRI_MAX时则“许进不许出”——只允许新的优先级阈值比原 来的那个在数值上更小,也就是说,只能一次次地扩大掩蔽范围,反之则不行。就好像绳子打了死 结,只会越拉越紧。举例来说,检视下面的程序片断:
MSR |
BASEPRI_MAX, R0 |
;本次设置被忽略,因为0xf0比0x60的优先级低 |
MOV MSR |
R0, #0x40 BASEPRI_MAX, R0 |
;Ok。扩大掩蔽范围到优先级不高于0x40的中断 |
为了把掩蔽阈值降低,或者解除掩蔽,需要使用“BASEPRI”这个名字。上例中,把设置阈值
为0xf0的那条指令改用BASEPRI,则可以操作成功。显然,在用户级下是不得更改BASEPRI寄存器 的。与其它和优先级有关的寄存器一样,系统中表达优先级的位数,也同样影响BASEPRI中有意义 的位数。如果系统中只使用3个位来表达优先级,则BASEPRI有意义的值仅为0x00, 0x20, 0x40,
0x60, 0x80, 0xA0, 0xC0以及0xE0。
用法fault,总线fault以及存储器管理fault都是特殊的异常,因此给它们开了小灶。其中, 它们的使能控制是通过“系统Handler控制及状态寄存器(SHCSR)”(地址:0xE000_ED24)来实现 的。各种faults的悬起状态和大多数系统异常的活动状态也都在该寄存器中,如表8.5所示。
表8.5 系统Handler控制及状态寄存器SHCSR(地址:0xE000_ED24)
位段 |
名称 |
类型 |
复位值 |
描述 |
18 |
USGFAULTENA |
R/W |
0 |
用法 fault服务例程使能位 |
17 |
BUSFAULTENA |
R/W |
0 |
总线 fault服务例程使能位 |
16 |
MEMFAULTENA |
R/W |
0 |
存储器管理 fault服务例程使能位 |
15 |
SVCALLPENDED |
R/W |
0 |
SVC 悬起中。本来已经要 SVC服务例程,但 是却被更高优先级异常取代 |
14 |
BUSFAULTPENDED |
R/W |
0 |
总线 fault悬起中,细节同上。 |
13 |
MEMFAULTPENDED |
R/W |
0 |
存储器管理 fault悬起中,细节同上 |
12 |
USGFAULTPENDED |
R/W |
0 |
用法 fault悬起中,细节同上 |
11 |
SYSTICKACT |
R/W |
0 |
SysTick异常活动中 |
10 |
PENDSVACT |
R/W |
0 |
PendSV异常活动中 |
9 |
- |
- |
- |
- |
8 |
MONITORACT |
R/W |
0 |
Monitor异常活动中 |
7 |
SVCALLACT |
R/W |
0 |
SVC异常活动中 |
6:4 |
- |
- |
- |
- |
3 |
USGFAULTACT |
R/W |
0 |
用法 fault异常活动中 |
2 |
- |
- |
- |
- |
1 |
BUSFAULTACT |
R/W |
0 |
总线 fault异常活动中 |
0 |
MEMFAULTACT |
R/W |
0 |
存储器管理 fault异常活动中 |
写这些寄存器时要小心,必须确保对活动位的修改是经过深思熟虑的,决不能粗心修改。否则,
如果某个异常的活动位被意外地清零了,其服务例程却不知晓,仍然执行异常返回指令,那么CM3
将视之为无理取闹——在异常服务例程以外做异常返回,从而产生一个fault。
译注:下段文字改编自《Cortex-M3 Technical Reference Manual》, pg8-29,是给那些骨灰级玩家们看的,因为修改这 些位还有更深层次的背景和特效。译文为:上表中的活动位虽然也是可写的,但是改动时必须予以极度的小心,否 则这是玩火行为——设置或者清零这些位,会改变处理器中对异常活动的记录,却不会对应地修复堆栈中的数据(不 会为了此改动而特意执行一次自动入栈或自动出栈操作),于是埋下了破坏堆栈内容而引起程序跑飞的隐患;另外, 其它一些重要的数据结构也得不到清除,后患无穷。事实上,只有操作系统在特殊场合下才会修改它们。例如:在
任务执行系统调用的过程中执行上下文切换(大幅提升实时性),或者在使用软件模拟未定义指令的功能期间(在用 法fault服务例程中),以及软件模拟协处理器的功能期间,执行上下文切换,同样大幅提升实时性。
下面开始讲中断控制及状态寄存器ICSR。对于NMI、SysTick定时器以及PendSV,可以通过此寄存器手工悬起它
们。另外,在该寄存器中,有好多位段都用于调试目的。在大多数情况下,它们对于应用软件都没有什么用处,只 有悬起位对应用程序常常比较有参考价值,如表8.6所示。
表8.5 中断控制及状态寄存器ICSR(地址:0xE000_ED04)
位段 |
名称 |
类型 |
复位值 |
描述 |
31 |
NMIPENDSET |
R/W |
0 |
写 1以悬起 NMI。因为 NMI的优先级最高且从不 掩蔽,在置位此位后将立即进入 NMI服务例程。 |
28 |
PENDSVSET |
R/W |
0 |
写 1 以悬起 PendSV。读取它则返回 PendSV 的 状态 |
27 |
PENDSVCLR |
W |
0 |
写 1以清除 PendSV悬起状态 |
26 |
PENDSTSET |
R/W |
0 |
写 1以悬起 SysTick。读取它则返回 PendSV的 状态 |
25 |
PENDSTCLR |
W |
0 |
写 1以清除 SysTick悬起状态 |
23 |
ISRPREEMPT |
R |
0 |
=1 时,则表示一个悬起的中断将在下一步时进 入活动状态(用于单步执行时的调试目的) |
22 |
ISRPENDING |
R |
0 |
1=当前正有外部中断被悬起(不包括 NMI) |
21:12 |
VECTPENDING |
R |
0 |
悬起的 ISR 的编号。如果不止一个中断悬起, 则它的值是这次中断中,优先级最高的那一个。 |
11 |
RETTOBASE |
R |
0 |
如果异常返回后将回到基级(base level),并 且没有其它异常悬起时,此位为 1。若是在线程 模式下,在某个服务例程中,有不止一级的异常 处于活动状态,或者在异常没有活动时执行了异 常服务例程(此时执行返回指令将产生 fault。 此乃高危行为,大虾也需慎用),则此位为 0 |
9:0 |
VECTACTIVE |
R |
0 |
当前活动的ISR编号,该位段指出当前运行中的 ISR是哪个中断的(提供异常序号),包括NMI和 硬fault。如果多个异常共享一个服务例程,该例程可 根据本位段的值来判定是哪一个异常的响应导致它的执 行。把本位段的值减去16,就得到了外中断的编号,并 可以用此编号来操作外中断相关的使能/除能等寄存器。 |