此文汇总了Cortex-M3&M4
体系架构常用的知识点,大部分来源于网络博客、书籍、ARM官方文档等。
Cortex-M3
和Cortex-M4
都基于ARMv7-M
架构,Cortex-M
处理器使用的指令集名为Thumb
(其中包括16位Thumb
和更新的32位Thumb
指令),Cortex-M3
和Cortex-M4
使用了Thumb-2
技术,它允许16位和32位指令的混合使用。与ARM7TDMI
等经典的ARM处理器不同的地方是,ARM7TDMI
有两种状态32位的ARM
状态和Thumb
状态,ARM
状态可以很高兴能执行所有指令,而Thumb
状态是16位的,可以节省代码codesize
,两者通常一起使用,但是ARM
状态和Thumb
状态需要切换,而且切换需要一定的开销,这样就导致软件的复杂度提升。而Cortex-M3
和Cortex-M4
用的Thumb-2
可以同时兼容16位的指令和32位的指令。Cortex-M3
和Cortex-M4
是没有经典ARM
处理器的ARM
状态的,处理器可以在任何时候处于Thumb
状态,而不需要任何切换的开销。
A. 调试状态
当处理器被暂停后(例如,通过调试器或触发断点后),就会暂停指令的执行,进入调试状态。
B. Thumb
状态
若处理器在执行代码时,就处于Thumb
状态。Cortex-M
系列处理器不支持ARM
指令,所以没有ARM
状态。
A. 处理模式(handler mode
)
在执行中断模式(ISR
)时,为处理模式。在处理模式下,处理器总是处于特权访问等级。
B. 线程模式(thread mode
)
在执行普通的应用代码时,为线程模式。线程模式下,处理器可以处于特权访问等级,也可以处于非特权访问等级。实际的访问等级由特殊寄存器CONTROL
控制。从特权访问等级可以随时切换到非特权访问等级,但是从非特权访问等级切换到特权等级必须借助于异常机制才行。
Cortex-M3
和Cortex-M4
处理器的寄存器组中有16个寄存器,其中有13个为通用寄存器,剩下三个为特殊寄存器。
r0
~ r12
其中r0
到r12
为通用寄存器,r0
~r7
被称为低寄存器,由于指令中可用的空间有限,许多16位指令只能访问低寄存器,r8
~r12
为高寄存器,可用于32位指令和几个16位指令,低寄存器的初始值是未定义的。
r13
(sp
)r13
为sp
栈指针寄存器,物理上存在两个栈指针:MSP
和PSP
。MSP
为主栈指针(main sp
),是默认的栈指针寄存器,在复位后或者处理器处于处理模式时,都是用的MSP
(进入处理模式后会自动切换到MSP
)。PSP
为线程栈指针(thread sp
),只能用于线程模式(但线程模式可以使用PSP
,也可以使用MSP
)。值得注意的是,RTOS
操作系统内核大多使用的是MSP
,而在用户态或者用户任务中使用的是PSP
。但是如果是裸机的话,一般不需要特别区分。
r14
(lr
)用于函数或者子程序调用时,返回地址的保存。在函数或者子程序结束时,程序控制可以通过将lr
的数值加载程序计数器pc
中返回调用程序处并继续执行。当执行了函数或子程序调用后,lr
的数值会自动更新。若某函数需要调用另外一个函数或子程序,则他需要首先将lr
的数值保存在栈中,否则,当执行了函数调用后,lr
的当前值会丢失。在异常处理期间,lr
也会被自动更新为特殊的EXC_RETURN
(异常返回)数值,之后该数值会在异常结束时触发异常返回。Cortex-M
处理器中返回地址总是偶数,所以lr
的第0位为0,但是有些跳转或者调用操作需要将lr
的第0比特设为1表示Thumb
状态。
r15
(pc
)pc
寄存器是可读可写的,因为Cortex-M3
和Cortex-M4
只有Thumb
状态,用的全部都是Thumb
指令,所以读pc
寄存器的操作是返回当前指令地址加4。这个是因为流水线的设计(取指->译码->执行)每次取值都合译码相差两个指令流水线阶段,加上Thumb
指令都是以16Bit
为单位的,所以会是+4
。写pc
会引起跳转操作。由于指令需要对齐到半字或字地址,pc
的最低位(LSB
)为0,不过在使用一些跳转/读取存储器指令更新pc
时,需要将新pc
值的LSB
置1以表示Thumb
状态,对于某些高级语言(c/c++
)编译器会自动的将跳转目前的LSB
置位。
xPSR
xPSR
由三个状态寄存器组成:
应用PSR
: APSR
执行PSR
: EPSR
中断PSR
: IPSR
可以通过MRS
和MSR
操作PSR
来统一访问。
APSR
和IPSR
寄存器可以通过 MRS
和MSR
来分别访问。
EPSR
不能通过MSR
和MRS
直接访问。
标志 | 含义 |
---|---|
N | 负标志 |
Z | 零标志 |
C | 进位(或者非借位)标志 |
V | 溢出标志 |
Q | 饱和标志(ARMv6-M中不存在) |
GE[3:0] | 大于或等于标志,对应每个字节通路(只存在于ARMv7E-M,在ARMv6-M或Cortex-M3中不存在) |
ICT / IT | 中断继续指令(ICI)位,IF-THEN 指令状态位用于条件执行(ARMv6-M则不存在) |
T | Thumb状态,总是1,清除此位会引起错误异常 |
Exception Number | 表示处理器正在处理的异常 |
Q
表示包和运算或饱和调整运算过程中产生了饱和,存在于ARMv7-M
,ARMv6-M
不可用。这个位只能通过写APSR
手动清除。一般的运算指令如果溢出会将MSB
丢失,从而导致结果产生严重的畸变。而饱和运算是将结果强制置为最大值或最小值。饱和运算指令的助记符前都带有Q
,如果QADD16
。Cortex-M3
提供了一些饱和调整指令。Cortex-M4
提供了饱和调整指令和一整套饱和运算指令。
Cortex-M4
中,大于等于(GE
)位域在APSR中占四位,在Coret-M3
中不存在,许多SIMD
指令都会更新该标志。多数情况下,每个位表示SIMD
运算的每一个字节或正或溢出。
PRIMASK
、FAULTMASK
和BASEPRI
寄存器这三个寄存器都是用来异常或中断屏蔽,每一个异常和中断的都有一个优先等级,数值越小的优先级越高。这些特殊寄存器可基于优先等级屏蔽异常,只有在特权访问等级才可以对他们进行操作。默认全是0,即都不屏蔽。
寄存器 | 位宽 | 用途 | 操作方法 |
---|---|---|---|
PRIMASK | 1bit | 阻止不可屏蔽中断(NMI)和HardFault异常之外的所有异常和中断,事实是将当前异常优先级提升到0 | MRS 和 MSR、 CPSIE i 和 CPSID i |
FAULTMASK | 1bit | 阻止不可屏蔽中断(NMI)之外的所有异常和中断,事实是将当前异常优先级提升到1 | MRS 和 MSR、 CPSIE f 和 CPSID f |
BASEPRI | 3~8bit | 根据优先级等级屏蔽异常或者中断,为0时不起作用,为非0时,屏蔽具有相同或更低优先级的异常 | MRS 和 MSR |
CONTROL
寄存器CONTROL
寄存器定义了:
1. 栈指针的选择(主栈指针/进程栈指针)。
2. 线程模式的访问级别(特权/非特权)。
3. 对于具有浮点单元的Cortex-M4
处理器,CONTROL
寄存器中有一位表示当前上下文是否使用浮点计算。
CONTROL
寄存器只能在特权访问等级进行修改,而读取操作都可以。通过MRS
和MSR
指令来访问。
位 | 位置 | 功能 |
---|---|---|
nPRIV | 第0位 | 定义线程模式中的特权等级。为0时,线程模式处于特权等级;为1时,线程模式处于非特权等级 |
SPSEL | 第1位 | 定义栈指针的选择。为0时,线程模式使用主栈指针(MSP); 为1时,线程模式使用进程栈指针(PSP)。在处理模式,该位始终为0,对他的操作会被忽略,也就是说在处理模式只能使用MSP。 |
FPCA | 第2位 | 只存在于浮点有浮点单元的Cortex-M4中,异常处理机制使用该位确定异常产生时浮点单元中的寄存器是否需要保存。为1时,当前上下文使用浮点指令,因此需要保存浮点寄存器。FPCA会在执行浮点指令时自动置位。在异常入口处被硬件清除。 |
复位后,CONTROL
寄存器被置位0。意味这默认状态是线程模式具有特权权限,并且使用主栈指针。通过写CONTROL
寄存器,特权线程模式的程序可以切换栈指针的选择或进入非特权访问等级,不过,nPRIV
(CONTROL
的第0位)置位后,运行在线程模式的程序就不能访问CONTROL
寄存器了。运行在非特权等级的程序无法切换回特权访问等级,这样就提供了一个基本的安全模型。若有必要将处理器在线程模式切换回特权访问等级,则需要使用异常机制。在异常处理程序里可以清除nPRIV
位。在返回到线程模式后,处理器就会进入特权访问等级。
nPRIV | SPSEL | 应用场景 |
---|---|---|
0 | 0 | 简单应用,整个应用运行在特权访问等级,主程序和中断处理只会使用一个栈,也就是MSP |
0 | 1 | 具有嵌入式OS的应用,当前执行的任务运行在特权线程模式,当前任务选择使用进程栈指针(PSP),而MSP则应用与OS内核以及异常处理 |
1 | 0 | 具有嵌入式OS的应用,当前执行的任务运行在非特权线程模式,当前任务选择使用进程栈指针(PSP),而MSP则应用与OS内核以及异常处理 |
1 | 1 | 线程模式运行在非特权访问等级,且使用MSP,处理模式中可见,而用户任务则一般无法使用,这是因为在多数嵌入式OS中,应用任务的栈和OS内核以及异常处理使用的栈是相互独立的 |
在修改了CONTROL
寄存器后,从架构来看,应该使用指令同步屏障(ISB
)指令,以确保本次修改对接下来的代码能起到作用。由于Cortex-M3
、Cortex-M4
、Cortex-M0+
、Cortex-M0
以及Cortex-M1
的流水线非常简单,不使用ISB
只能也不会引起什么问题。
S0
~ S31
和 D0
~ D15
S0
~ S31
都为32位寄存器,而且每个都可以通过浮点指令访问,或者利用符号D0
~ D15
(D
代表双字/双精度)成都访问。例如,S0
和S1
组成D0
,而S3
和S2
则成对组成D1
。尽管Coretx-M4
中的浮点单元不支持双精度浮点运算,在传输双精度数据时仍可以使用浮点指令。
FPSCR
由于下面的几个原因,FPSCR
中包含了多个位域
1. 定义一些浮点运算动作。
2. 提供浮点运算结果的状态信息。
位 | 描述 |
---|---|
N | 负标志 |
Z | 负标志 |
C | 负标志 |
V | 负标志 |
AHP | 交替半精度控制位,0 : IEEE半精度格式(默认);1 : 交替半精度格式 |
DN | 默认NaN(非数值)模式控制位,0 : NaN操作数会变为浮点运算的输出(默认);1 : 任何设计一个或者多个NaN的运算会返回默认的NaN |
FZ | 清零模式控制位,0 : 清零模式禁止(默认);1 : 清零模式使能 |
RMode | 舍入模式控制位,表示的舍入模式基本上适用于所有的浮点指令,00 : 最近舍入(RN)模式(默认);01 : 正无穷舍入(RP)模式;10 : 负无穷舍入(RM)模式;11 : 向零舍入(RZ)模式 |
IDC | 输入非正常积累异常位,在产生浮点异常时为1,写0则会清除该位(结果未在正常数值范围内) |
IXC | 不精确的积累异常位,在产生浮点异常时为1,写0则会清除该位 |
UFC | 下溢累计异常位,在产生浮点异常时为1,写0则会清除该位 |
OFC | 溢出积累异常位,在产生浮点异常时为1,写0则会清除该位 |
DZC | 被零除积累异常位,在产生浮点异常时为1,写0则会清除该位 |
IOC | 非法操作积累异常位,在产生浮点异常时为1,写0则会清除该位 |
除了浮点单元寄存器组和FPSCR
,浮点单元还往系统中引入了一些经过存储器映射的寄存器,如果用于使能和禁止浮点单元的协处理器访问控制寄存器CPACR
。浮点单元默认被禁止以降低功耗。在使用任何浮点指令前,都必须通过变成CPACR
来使能浮点单元。
Coretx-M
处理器可以对32位存储器进行寻址,储存空间能够达到4GB
。存储器空间是统一的,指令和数据共用相同的地址空间。支持多个特性:
1. 多个总线接口,指令和数据可以同时访问(哈弗总线)。
2. 基于AMBA
(高级微控制总线架构)的总线接口设计:用于存储器和系统总线流水线操作的AHB
(AMBA
高性能总线)Lite
协议,以及用于调试部件通信的APB
(高级外设总线)协议。
3. 同时支持大小端。
4. 只是非对齐数据传输。
5. 支持排他传输(用于嵌入式OS的信号量操作)。
6. 可位寻址的储存器空间(位段)。
7. 不同储存器区域的储存器属性和访问权限。
8. 可选的存储器保护单元(MPU)。可以在运行时设置存储器属性和访问权限配置。
Cortex-M处理的4GB地址空间被分为了多个存储器区域,主要用于:
1. 程序代码访问(如CODE
区域)
2. 数据访问(如SRAM
区域)
3. 外设(如外设区域)
4. 处理器的内部控制和调试部件(如私有外设总线)
Cortex-M
处理器提供了基于AMBA
(高级为控制总线架构)的通用总线接口。AMBA
支持多种总线协议。对于Cortex-M3
和Cortex-M4
,主要的总线接口使用AHB
(AMBA
高性能总线)Lite
协议。
总线 | 协议 | 用途 |
---|---|---|
PPB | APB协议 | 主要用于调试部件 |
I-CODE | AHB Lite协议 | 访问CODE区域的指令 |
D-CODE | AHB Lite协议 | 访问CODE区域的数据 |
外设总线 | APB协议 或者 AHB Lite协议 | 访问外设 |
私有外设总线PPB
不会用于普通外设,这是因为:
1.PPB
只支持私有访问。
2.只允许32位访问。
3.没有写缓冲,写传输需要更多的时钟周期
4.PPB
中的外设无法使用位段特性
5.只支持小端。即使处理器被配置成打断。
6.可被处理器或调试器访问。其他总线设备则不行(如多处理器环境下)。
Cortex-M3
和Cortex-M4
同时支持大小端。一般情况下,存储器系统可被设计为只支持小端和大端中的一种。
有些情况下,从一些外设寄存器中获得数据的大小端可能会不同。这样,访问这种外设寄存器的应用需要在程序代码中将数据转换为正确的端。
现有的Cortex-M
微控制器大多数是小端的。他们具有小端的存储器系统和外设。
在Cortex-M
处理器中:
1. 取值总是处于小端模式。
2. 对包括系统控制空间(SCS
)、调试部件和私有外设总线PPB
在内的0xE0000000
~ 0xE00FFFFF
区域的访问总是小端的。如果软件应用需要处理大端数据,而所使用的微控制器确实小端的,则可以使用REV
、REVSH
、REV16
等指令将数据在大端和小端间转换。
一般来说,多数经典ARM
处理器(ARM7
/ARM9
/ARM10
)都只允许对齐传输。这就意味着在访问存储器时,字传输地址的Bit[1]
和Bit[0]
为0,而半字传输地址的Bit[0]
为0.
Cortex-M3
和Cortex-M4
处理器都支持普通存储器访问的非对齐数据传输。另外还有一些限制:
1. 多加载/存储指令不支持非对齐传输。
2. 栈操作指令(PUSH
/POP
)必须时对齐的。
3. 排他访问(如LDREX
或STREX
)必须是对齐的,否则就会触发错误异常(usage fault
)。
4. 位段操作不支持非对齐传输,因为结果是不可预测的。
当非对齐传输是由处理器发起时,他们实际上会被处理器的总线接口单元转换为多个对齐传输。这个转换是不可见的,因此应用程序人员无需考虑这个问题。不过,当产生对齐传输时,他会被拆分为几个对齐传输,因此本次数据访问会花费更多的时钟周期,可能对需要高性能的情形不利。若追求更高的性能,确保数据处于合适的对齐是有必要的。
多数情况下,C编译器不会产生非对齐传输,它只会在以下情况中出现:
1. 直接操作指针。
2. 包含非对齐数据的数据结构增加__packed
属性。
3. 内联/嵌入汇编代码。
也可以对Cortex-M3
和Cortex-M4
处理器进行设置,使得非对齐传输出现时可以出发异常,这样就需要设置配置控制器寄存器中的UNALIGN_TRP
(非对齐陷阱)位以及系统控制块SCB
中的CCR
(地址0xE000ED14
)。这样处理之后,处理器就可以在出现非对齐传输时产生使用错误异常(useage fault
)。这对于软件开发过程中测试程序是否会产生非对齐传输非常有用。
利用位段操作,一次加载/存储操作可以访问一个位。Cortex-M3
和Cortex-M4
处理器,两个名为位段区域的预定义存储器区域支持这种操作。其中一个位域SRAM
区域的第一个1MB
,另一个则位于外设区域的第一个1MB
。这个两个区域可以通普通存储器一样访问,而且还可以通过名为位段别名的一块独立存储区域进行访问。
位段特性在从版本r2p1
开始的Cortex-M3
以及所有发行版本的Cortex-M4
上都是可选的。有些厂商的MCU可能不支持。
位段操作并不局限于字传输,字节传输或半字传输也可以执行。例如,在用字节访问指令(LDRB
/STRB
)访问位段别名地址区域时,所产生的对位段区域的访问就是字节大小的。对位段别名的半字传输(LDRH
/STRH
)则会被重映射到位段区域的半字大小的传输。
在位段别名地址上执行非字传输时,地址仍然时字节对齐的。
c/c++语言本身不支持位段操作。例如,c编译器就不知道同一个存储器可以用两个不同的地址来寻址。用C实现位段特性,最简单的方法就是分别声明存储位置的地址和位段别名:
#define DEVICE_REG0 *((volatile unsigned long *) (0x40000000))
#define DEVICE_REG0_BIT0 *((volatile unsigned long *) (0x42000000))
#define DEVICE_REG0_BIT1 *((volatile unsigned long *) (0x42000004))
DEVICE_REG0 = 0xAB; //普通方式访问寄存器
DEVICE_REG0 = DEVICE_REG0 | 0x2; //普通方式设置BIT1
DEVICE_REG0_BIT1 = 0x1; //位段方式设置BIT1
也可以利用C语言的宏定义简化对位段别名的访问。例如,可以实现一个宏,将位段地址和位数转换为位段别名地址,并且用另外一个宏将地址作为一个指针来访问存储器地址。
// 将位段地址和位编号转换为位段别名地址
#define BIT BAND(addr,bitnum) \
((addr & 0xF0000000) + 0x02000000 + \
((addr & 0xFFFFF) << 5) + (bitnum <<2 ))
// 将地址转换为指针
#define MEM_ADDR(addr) \
*((volatile unsigned long *) (addr))
#define DEVICE_REG0 0x40000000
// 普通方式访问寄存器
MEM_ADDR(DEVICE_REG0) = 0xAB;
//普通方式设置BIT1
MEM_ADDR(DEVICE_REG0) = MEM_ADDR(DEVICE_REG0) | 0x2;
//位段方式设置BIT1
MEM_ADDR(BIT BAND(DEVICE_REG0,1)) = 0x1;
对于从ARM某些编译器,可以通过__attribute__((bit band))
关键字和__bit band
命令行选项来使用位段。
Cortex-M3
和Cortex-M4
处理器映射具有默认的存储器访问权限配置,非特权用户程序不允许访问NVIC
等系统控制存储器空间。在没有MPU
或MPU
存在但未使能时会使用默认的存储器访问权限。
存储器区域 | 地址 | 用户程序的非特权访问 |
---|---|---|
供应商定义 | 0xE0100000 ~ 0xFFFFFFFF | 全访问 |
ROM表 | 0xE00FF000 ~ 0xE00FFFFF | 禁止,非特权访问导致总线错误 |
外部PPB | 0xE0042000 ~ 0xE00FEFFF | 禁止,非特权访问导致总线错误 |
ETM | 0xE0041000 ~ 0xE0041FFF | 禁止,非特权访问导致总线错误 |
TPIU | 0xE0040000 ~ 0xE0040FFF | 禁止,非特权访问导致总线错误 |
内部PPB | 0xE000F000 ~ 0xE003FFFF | 禁止,非特权访问导致总线错误 |
NVIC | 0xE000E000 ~ 0xE000EFFF | 禁止,非特权访问导致总线错误。除了软件触发中断寄存器,其可被编程为允许用户访问 |
FPB | 0xE0002000 ~ 0xE0003FFF | 禁止,非特权访问导致总线错误 |
DWT | 0xE0001000 ~ 0xE0001FFF | 禁止,非特权访问导致总线错误 |
ITM | 0xE0000000 ~ 0xE0000FFF | 都允许,写忽略,除非是非特权访问激励端口(实时可配置) |
外部设备 | 0xA0000000 ~ 0xDFFFFFFF | 全访问 |
外部RAM | 0x60000000 ~ 0x9FFFFFFF | 全访问 |
外设 | 0x40000000 ~ 0x5FFFFFFF | 全访问 |
SRAM | 0x20000000 ~ 0x3FFFFFFF | 全访问 |
代码 | 0x00000000 ~ 0x1FFFFFFF | 全访问 |
当为特权访问被组织时,错误异常就会立即产生。根据总线错误异常是否使能以及优先级配置,它可以时硬件错误或总线错误异常。
Cortex-M3
和 Cortex-M4
处理器不支持SWP
指令(交换)。该指令一般用于ARM7TDMI
等传统ARM
处理器的信号量操作。
而在Cortex-M3
和Cortex-M4
处理器支持LDREX
、STREX
和CLREX
指令来支持独占访问。
存储器屏障指令有DMB
、DSB
和ISB
,三个指令对于内存顺序越来越严格,
指令 | 作用 |
---|---|
DMB | 数据存储器隔离,仅当所有在它前面的存储器访问操作都执行完毕后,才提交(commit)在它后面的存储器访问操作 |
DSB | 数据同步隔离,比 DMB 严格,仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令 |
ISB | 指令同步隔离,最严格,它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令 |
对于Cortex-M3
和Cortex-M4
微控制器中运行的多数应用,忽略掉存储器屏障指令不会引起任何问题。这是因为:
1. Cortex-M3
和Cortex-M4
处理器不会重新调整任何处理器传输或指令执行的顺序(在某些超标量处理器或具有乱序执行能力的高性能处理器中可能会出现)。
2. 由于AHBLite
和APB
协议自身非常简单,他们不允许在前面的传输还未完成时就开始新的传输。
在Cortex-M
何种地方需要内存屏障:
如果异常处理程序在异常返回之前禁用SLEEPONEXIT
功能,则在写入SCR
之后,异常返回之前需要执行DSB指令。
禁止中断。如果通过控制NVIC
来屏蔽中断,需要确保在NVIC
屏蔽中断后不会触发中断,要添加ISB
指令。因为这个中断可能已经产生了,并且处于Pending state
,那么如果不使用ISB
指令有可能会发生即使禁用了中断,但是还是会触发这个中断。但是如果使用CPSID
指令去禁止总中断的话,CPU
会自己去同步这个操作,就不需要内存屏障操作。
... ; Interrupt is happening
STR R0, [R1] ; Disable IRQ by writing to NVIC_CLRENA
... ; Instruction N+1 <- Pended interrupt service routine might execute before this instruction
... ; Instruction N+2 <- Pended interrupt service routine might execute before this instruction
... ; Instruction N+3 <- Pended interrupt service routine might execute before this instruction
使能中断。如果一个中断已经处于Pending
状态,通过NVIC
来去除中断屏蔽(总中断已经打开),以及执行CPSIE I
后接受中断(NVIC
已经去除这个中断的屏蔽),在处理器进入异常处理程序之前,可以执行其他指令。Cortex-M3
和Cortex-M4
处理器可以在进入中断服务程序之前执行最多两条附加指令,对于Cortex-M0
,在进入中断服务程序之前,处理器可以执行多达一条附加指令。
使用NVIC
来去除中断屏蔽:
... ; Interrupt is in pending state
STR R0, [R1] ; Enable IRQ by writing to NVIC_SETENA
... ; Instruction N+1 <- Pended interrupt service routine might execute before this instruction
... ; Instruction N+2 <- Pended interrupt service routine might execute before this instruction
... ; Instruction N+3 <- Pended interrupt service routine might execute before this instruction
使用CPSIE
来使能中断:
CPSID I ; Disable IRQ by setting PRIMAS
... ; Interrupt change to pending state
CPSIE I ; Enable IRQ by clearing PRIMASK
... ; Instruction N+1 <- Pended interrupt service routine might execute before this instruction
... ; Instruction N+2 <- Pended interrupt service routine might execute before this instruction
... ; Instruction N+3 <- Pended interrupt service routine might execute before this instruction
另外下面不使用内存屏障也可以触发中断情况:
/* 中断可能发生在__enable_irq(第三句) 和 __disable_irq(第四句)之间 */
__disable_irq(); /* CPSID I */
critical_task1(); /* Time critical task 1 */
__enable_irq(); /* CPSIE I */
__disable_irq(); /* CPSID I */
critical_task2(); /* Time critical task 2 */
__enable_irq(); /* CPSIE I */
更改中断优先级。如果一个中断已经处于Pending
状态,通过NVIC
来更改这个中断的优先级,想使这个优先级的更改立即生效,就需要加一个ISB
指令。
memory map
被更改之后。需要用DSB
+ISB
序列确保cpu
加载了remap
之后的正确的代码。(???)
进入睡眠模式。大多数Cortex-M
处理器不需要特殊处理,但是某些可能有write buffer
的CPU
需要在WFI
和WFE
之前加一个DSB
指令,来确保之前的指令正确的被执行。
更改MPU
设定。如果更改MPU
设置仅影响数据存储器而不影响程序存储器,则Cortex-M
处理器上不需要ISB指令。如果需要使用新的MPU
设置获取后续指令,则需要ISB
指令。
信号量和互斥锁。在使用Cortex-M3
和Cortex-M4
处理器的微控制器设备上,在信号量和互斥量操作中省略DMB
指令不会导致错误。但是,在以下情况下不能保证:
①. 处理器具有缓存.
②. 软件用于多核系统。
ARM
建议在操作系统设计中将DMB
指令用于信号量和Mutex
操作。
Cortex-M0
和Cortex-M0+
处理器没有独占访问指令。
自修改程序。如果一个程序包含自修改的代码,那么如果改变的程序代码在修改后不久就要执行,则需要内存屏障。由于可以预取程序代码,因此应该执行DSB
指令,然后执行ISB
指令以确保流水线被刷新:
... ; Self modifying
STR , []
DSB ; Ensure store is completed before flushing pipeline
ISB ; Flush pipeline
B ; Execute updated program
更相信的信息可以参考ARM Cortex™ -M Programming Guide to Memory Barrier Instructions Application Note 321
。
Cortex-M3
和Cortex-M4
的NVIC
支持最多240个IRQ
、1个不可屏蔽中断(NMI
)、1个SysTick
定时中断及多个系统异常。多数IRQ
有定时器、I/O
端口和通信接口等外设产生。NMI
通常由看门狗定时器或掉电检测器等外设产生,其余的异常则是来自处理器内核,中断还可以利用软件产生。
为了继续执行被中断的程序,异常流程需要刘勇一些手段来保存被中断程序的状态,这样在异常处理完成后还可以被恢复。一般来说,这个过程可以有硬件机制实现,也可以由意见和软件操作共同完成。对于Cortex-M4
处理器,当异常被接受后,有些寄存器会被自动保存到栈中,而且也会在返回流程中自动恢复。利用这种机制,可以将异常处理写作普通的C函数,同时也不会带来额外的软件开销。
内存位置 | 异常类型 | 优先级 | 描述 |
---|---|---|---|
0 | - | - | 栈顶,复位时从向量表的第一个内存位置加载 |
1 | Reset | -3 (最高) | 复位,包括上电和热启动 |
2 | NMI | -2 | 不可屏蔽中断,可在芯片内部产生,也可以在外部产生 |
3 | Hard Fault | -1 | 所有的Fault 都可以能引发,前提是相应的错误处理未使能 |
4 | MemManage Fault | 可编程 | 储存器管理错误,存储器管理单元(MPU)冲突或访问非法位置 |
5 | Bus Fault | 可编程 | 总线错误。预取错误,内存访问错误和其他地址/内存有关。当高级高性能总线(AHB)接口收到从总线的错误响应时产生(若为取值也被称作预取终止,数据访问则为数据终止) |
6 | Usage Fault | 可编程 | 使用错误,如执行未定义的指令或非法的状态转换尝试,访问协处理器等(Cortex-M3和Cortex-M4不支持协处理器) |
7 ~ 10 | 保留 | NA | 保留 |
11 | SVC | 可编程 | 请求管理调用,一般用于OS环境且允许应用任务访问系统服务 |
12 | Debug Monitor | 可编程 | 调试监控。在使用基于软件的调试方案时,断点和监视点等调试时间的异常 |
13 | 保留 | NA | 保留 |
14 | PendSV | 可编程 | 可Pending 的服务调用。OS一般用该异常进行上下文切换 |
15 | SysTick | 可编程 | 系统节拍定时器。当其在处理器中存在时,由定时器外设产生。可用于OS或简单的定时器外设 |
16及16以上 | External Interrupt | 可编程 | 可由片上外设或外设中断产生 |
Cortex-M3
处理器具有多个用于中断和异常管理的可编程寄存器,这些寄存器多数位于NVIC
和系统控制块SCB
中。实际上,SCB
时作为NVIC
的一部分实现的。处理器内核中还有用于中断屏蔽的寄存器,如PRIMASK
、FAULTMASK
和BASEPRI
。
NVIC
和SCB
位于系统控制空间SCS
,地址从0xE000E000
开始,大小为4KB
。SCB
中还有SysTick
定时器、存储器保护单元MPU
以及用于调试的寄存器等。该地址区域中基本上所有的寄存器都只能运行在特权访问等级的代码中访问。唯一的例外为软件触发中断寄存器STIR
,他可被设置为非特全模式访问。
复位后,所有中断都处于禁止状态,且默认的优先级为0。在使用任何一个中断之前,需要:
1. 设置所需中断的优先级(该步骤时可选的)。
2. 使能外设中的可以触发中断的中断产生控制。
3. 使能NVIC
中的中断。
对于Cortex-M
处理器异常是否能被处理器接受以及何时被处理器接受并执行异常处理,是由异常的优先级和处理器当前的优先级决定的。更高的优先级的异常可以抢占低优先级的异常,这就是异常/中断嵌套的情形。有些异常(RESET
、NMI
、HardFault
)具有固定的优先级,其优先级由负数表示,这样,他们的优先级就会比其他的异常高。其他异常具有可编程的优先级,范围为0 ~ 255。
8位的中断优先级配置寄存器会被分为两个部分,分组优先级和子优先级。利用系统控制快SCB
中一个名为优先级分组的配置寄存器,每个具有可编程优先级的优先级配置寄存器可被分为两部分。上半部分为分组优先级,下半部分为子优先级。
优先级分组 | 抢占优先级域 | 子优先级域 |
---|---|---|
0(默认) | Bit [7 : 1] | Bit [0] |
1 | Bit [7 : 2] | Bit [1 : 0] |
2 | Bit [7 : 3] | Bit [2 : 0] |
3 | Bit [7 : 4] | Bit [3 : 0] |
4 | Bit [7 : 5] | Bit [4 : 0] |
5 | Bit [7 : 6] | Bit [5 : 0] |
6 | Bit [7] | Bit [6 : 0] |
7 | 无 | Bit [7 : 0] |
当Cortex-M
处理器接受了某异常请求后,处理器需要确定该异常处理的起始地址。该信息位于存储器内的向量表中,向量表默认从地址0开始,向量地址则为异常编号乘4
。
启动代码中使用的向量表还包含主栈指针MSP
的初始值,这种设计很有必要,因为NMI
等异常可能会紧接着复位产生,而且此时还没有任何初始化操作。一般来说,起始地址(0x00000000
)处应为启动储存器,他可以为Flash
或ROM
,而且在运行时不能对它进行修改。不过,有些应用可能需要在运行时修改或定义向量表。为了进行这种处理,Cortex-M3
和Cortex-M4
处理器实现了一种名为向量表重定位的特性。向量表重定位特性提供了一个名为向量表偏移寄存器(VTOR
)的可编程寄存器。该寄存器正在使用的存储器的起始地址定义为向量表。VTOR
的复位值为0。在使用VTOR
时,需要将向量表大小扩展为下一个2的整数次方,且新向量表的基地址必须要对齐到这个数值。
例1: 微控制器中有32个中断源
向量表大小为(32(用于中断)+ 16(用于系统异常空间)) * 4 = 192(0xC0)
。将其扩大为下一个2的整数次方就得到256字节,因此,向量表的地址可被设置为0x00000000
、0x00000100
、0x00000200
、0x00000300
…等等。例2: 微控制器中有75个中断源
向量表大小为(75(用于中断)+ 16(用于系统异常空间)) * 4 = 364(0x16C)
。其扩大为下一个2的整数次方就得到512字节,因此,向量表的地址可被设置为0x00000000
、0x00000200
、0x00000400
、0x00000600
…等等。
由于中断的最小数量为1
,最小的向量表对齐为128
字节,因此,VTOR
的最低7位保留,且被强制置位0
。
pending
(悬而未决)行为每个中断都有多个属性:
1. 每个中断都可被禁止(默认)或使能。
2. 每个中断都可被pending
(等待服务的请求)或解除Pending
。
3. 每个中断都可处于活跃(正在处理)或非活跃状态。
为了支持这些属性,NVIC中包含了多个可编程寄存器,他们可用于中断使能控制、pending
状态和只读的活跃状态位。
NVIC
在设计上即支持产生脉冲中断请求的外设,也支持产生高电平中断请求的外设。无须配置任何一个NVIC
寄存器以选择其中一种中断类型。对于脉冲中断请求,脉冲宽度至少要为一个时钟周期;而对于电平触发的请求,在ISR
中的操作清除请求之前,请求服务的外设要一直保持信号电平(如写入寄存器以清除中断请求)。尽管外部中断请求在I/O
引脚上的电平可能是低有效的,NVIC
收到的请求信号为高有效。
中断的Pending
状态被存储在NVIC
的可编程寄存器中,当NVIC
的中断输入被确认后,他就会引发该终端的Pending
状态。即便中断请求被取消,Pending
状态仍会为高。这样NVIC
可以处理脉冲中断请求。
Pending
状态的意思是,中断被置于一种等待处理器处理的状态。有些情况下,处理器在中断Pending
时就会进行处理。不过,若处理器已经在处理另外一个更高或同等优先级的中断,或者中断被某个中断屏蔽寄存器给屏蔽掉了,那么在其他的中断处理结束前或中断屏蔽被清除前,Pending
请求会一直保持。NVIC
中的Pending
请求寄存器保存中断请求,即使请求中断的设备源取消了请求信号,已产生的中断仍会被处理。
当处理器开始处理中断请求时,中断的请求信号会被自动清除。
当终端正在处理时,他就处于活跃状态。注意在中断入口处,多个寄存器会被自动压入栈中,这也被称为压栈。同时,ISR
的起始地址会被从向量表中取出。
对于许多微控制器设计,外设会产生电平触发的中断,因此ISR
必须要手动清除中断请求,如写入外设的某个寄存器。在中断服务完成后,处理器会执行异常返回。之前自动压栈的寄存器会被恢复出来,而且被中断的程序也会继续执行。中断的活跃状态会被自动清除。
当中断处于活跃状态时,处理器无法在中断完成和异常返回前再次接受同一个中断请求。
中断的Pending
状态位于中断Pending
状态寄存器中,软件代码可以访问这些寄存器,因此,可以手动清除或设置中断的Pending
状态。若中断请求产生时处理器正在处理另一个具有更高优先级的中断,而在处理器对该中断请求做出响应之前,Pending
状态被清除掉了,该请求就会被取消且不会再得到处理。
若外设持续保持某个中断请求,那么即使软件尝试着清除该Pending
状态,Pending
状态还是会再次置位的。
若在得到处理后,中断源仍在继续保持中断请求,那么这个中断就会再次进入Pending
状态且再次得到处理器的服务。
对于脉冲中断请求,若在处理器开始处理前,中断请求信号产生了多次,他们会被当作一次中断请求。
中断的Pending
状态可以在其正在被处理时再次置位,如果在之前的中断请求正在被处理时产生了新的请求,这样会引发新的Pending
状态,因此处理器在前一个ISR
结束后需要再次处理这个中断。
即使中断被禁止了,他的Pending
状态仍可置位。在这种情况下,若中断稍后被使能了,他仍可以被出发并得到服务。有些时候,这种情况并不是我们所希望的,因此需要在时能NVIC
中的中断前手动清除挂起状态。
一般来说,NMI
的请求方式和中断类似。若当前没有在运行NMI
处理,或者处理器被暂停或处于锁定状态,由于NMI
具有最高优先级且不能被禁止,因此他几乎会立即执行。
若满足下面的条件,处理器会接受请求:
1. 处理器正在运行(未被暂停或处于复位状态)。
2. 异常处于使能抓过娘胎(NMI
和HardFault
为特殊情况,他们总是使能的)。
3. 异常的优先级高于当前等级。
4. 异常未被异常屏蔽寄存器(如PRIMASK
)屏蔽。
注意,对于SVC
异常,如果SVC
指令被意外用在某异常处理中,且该异常处理的优先级不小于SVC
,它就会引起HardFault
异常处理的执行。
①. 多个寄存器和返回地址被压入当前使用的栈。这样就可以将异常处理用普通C函数实现。若处理器处于线程模式且正在使用进程栈指针PSP
,则PSP
指向的栈区域就会用该压栈过程,否则就会使用主栈指针MSP
指向的栈区域。
②. 去除异常向量(异常处理/ISR的起始地址)。为了减少等待时间,这一步可能会和压栈操作并行执行。
③. 取出待执行异常处理的指令。在确定了异常处理的起始地址后,指令就会被去除。
④. 更新多个NVIC
寄存器和内核寄存器,其中包括挂起状态和异常的活跃状态,处理器内核中的寄存器包括程序寄存器PSR
、链接寄存器LR
、程序计数器PC
以及栈指针SP
。
更具压栈时实际使用的栈,在异常处理开始前,MSP
或PSP
的数值会相应地自动调整。PC
也会被更新为异常处理的起始地址,而链接寄存器LR
则会被更新名为EXC_RETURN
的特殊值。该数值为32位,且高27位为1。低5位中有些部分用于保存异常流程的状态信息(如压栈时使用的哪个栈)。该数值用于异常返回。
在异常处理内部,可以执行外设所需的服务。在执行异常处理时,处理器就会处于处理模式。此时:
1. 栈操作使用主栈指针MSP
。
2. 处理器运行在特权访问等级。
若更改优先级的异常在这个阶段产生,处理器会接受新的中断,而当前正在执行的处理器被挂起且被更高优先级的处理抢占,这种情况名为嵌套。
若另一个在这个阶段产生的异常具有相同或更低的优先级,新到的异常就会处理Pending
状态,且等当前异常处理完成后才会得到处理。
在异常处理的结尾,程序代码执行的返回会引起EXC_RETURN
数值被加载到程序计数器PC
,并处罚异常返回机制。
对于某些处理器架构,异常返回会使用一个特殊的指令。不过,这也就意味着异常处理无法像普通C代码那样编写和编译。对于Cortex-M
处理器,异常返回机制有一个特殊的地址EXC_RETURN
触发,该数值在异常入口处产生且被存储在链接寄存器LR
中。当该数值由某个允许的异常返回指令写入PC
时,他就会触发异常返回流程。
当触发了异常返回机制后,处理器会访问栈空间里在进入异常期间被压入栈中的寄存器数值,且将它们恢复到寄存器组中,这个过程被称为出栈。另外,多个NVIC
寄存器(如活跃状态)和处理器内核中的寄存器(如PSR
、SP
和CONTROL
)都会更新。
可用于触发异常返回的指令:
返回指令 | 描述 |
---|---|
BX | 若EXC_RETURN数值仍在LR中,则在异常处理结束时可以使用BX LR指令执行中断返回 |
POP {PC} 或 POP {…, PC} | 在进入异常处理后,LR的值通常会被压入栈中,可以使用操作一个寄存器或包括PC在内的多个寄存器的POP指令,将EXC_RETURN放到程序计数器中,这样处理会执行中断返回 |
加载(LDR)或多加载(LDM) | 可以利用PC为目的寄存器的LDR或LDM指令产生中断 |
在压栈操作的同时处理器会取出之前被中断的程序的指令,并使得程序尽快继续执行,由于使用了EXC_RETURN
数值触发异常返回,异常处理就可以和普通的C函数/子例程一样实现。在生成代码时,C编译器将LR中的EXC_RETURN
数值作为普通返回地址处理。由于EXC_RETURN
机制,函数一般不会返回到地址0xF0000000
~0xFFFFFFFF
。不过,根据架构定义,这段地址区域不能使用程序代码[具有永不执行(XN
)存储属性],因此这样也不会有什么问题。
NVIC
typedef struct
{
/*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */
uint32_t ISER[8];
uint32_t RESERVED0[24];
/*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */
uint32_t ICER[8];
uint32_t RSERVED1[24];
/*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */
uint32_t ISPR[8];
uint32_t RESERVED2[24];
/*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */
uint32_t ICPR[8];
uint32_t RESERVED3[24];
/*!< Offset: 0x200 (R/W) Interrupt Active bit Register */
uint32_t IABR[8];
uint32_t RESERVED4[56];
/*!< Offset: 0x300 (R/W) Interrupt Priority Register (8Bit wide) */
uint8_t IP[240];
uint32_t RESERVED5[644];
/*!< Offset: 0xE00 ( /W) Software Trigger Interrupt Register */
uint32_t STIR;
} NVIC_Type;
符号 | 寄存器名称 | 地址 | 作用 | 访问权限 |
---|---|---|---|---|
ISER[8] | 中断使能寄存器 | 0xE000E100 ~ 0xE000E11C | 通过置位来使能某个中断 | 只能在特权级访问,读写 |
ICER[8] | 中断清除使能寄存器 | 0xE000E180 ~ 0xE000E19C | 通过清零来禁能某个中断 | 只能在特权级访问,读写 |
ISPR[8] | 中断设置Pending 寄存器 |
0xE000E200 ~ 0xE000E21C | 通过置位来触发一个中断 | 只能在特权级访问,读写 |
ICPR[8] | 中断清除Pending 寄存器 |
0xE000E280 ~ 0xE000E29C | 通过清零来清除一个中断的Pending 状态, 来忽略已经触发的中断 |
只能在特权级访问,读写 |
IABR[8] | 中断活跃状态寄存器 | 0xE000E300 ~ 0xE000E31C | 记录当前中断的活跃状态, 正在处理时, 被更高优先级中断嵌套,也处于置位状态 | 只能在特权级访问,读写 |
IP[240] | 中断优先级寄存器 | 0xE000E400 ~ 0xE000E4EF | 设置中断的优先级别 | 只能在特权级访问,读写 |
STIR | 软件触发中断寄存器 | 0xE000EF00 | 可以通过设置这个寄存器的bit位来触发某个中断(和ISPR效果相同) | 非特权和特权都可以访问,只写 |
SCnSCB
中的INTLINESNUM
NVIC
在0xE000E004
地址处还有一个中断控制器类型寄存器,它是一个只读寄存器,给出了NVIC
支持的中断输入的数量,单位位32
。
位 | 名称 | 类型 | 描述 |
---|---|---|---|
4 : 0 | INTLINESNUM | R | 以32位单位的中断输入向量 0 = 1 ~ 32; 1 = 33 ~ 64; … |
利用CMSIS
的设备驱动库,可以使用SCnSCB->ICTR
来访问这个只读寄存器。SCnSCB
表示不在SCB
中的系统控制寄存器。
SCB
中的相关寄存器typedef struct
{
/* (R/ ) CPUID Base Register */
uint32_t CPUID;
/* (R/W) Interrupt Control and State Register */
uint32_t ICSR;
/* (R/W) Vector Table Offset Register */
uint32_t VTOR;
/* (R/W) Application Interrupt and Reset Control Register */
uint32_t AIRCR;
/* (R/W) System Control Register */
uint32_t SCR;
/* (R/W) Configuration Control Register */
uint32_t CCR;
/* (R/W) System Handlers Priority Registers (4-7, 8-11, 12-15) */
uint8_t SHP[12];
/* (R/W) System Handler Control and State Register */
uint32_t SHCSR;
/* (R/W) Configurable Fault Status Register */
uint32_t CFSR;
/* (R/W) HardFault Status Register */
uint32_t HFSR;
/* (R/W) Debug Fault Status Register */
uint32_t DFSR;
/* (R/W) MemManage Fault Address Register */
uint32_t MMFAR;
/* (R/W) BusFault Address Register */
uint32_t BFAR;
/* (R/W) Auxiliary Fault Status Register */
uint32_t AFSR;
/* (R/ ) Processor Feature Register */
uint32_t PFR[2];
/* (R/ ) Debug Feature Register */
uint32_t DFR;
/* (R/ ) Auxiliary Feature Register */
uint32_t ADR;
/* (R/ ) Memory Model Feature Register */
uint32_t MMFR[4];
/* (R/ ) Instruction Set Attributes Register */
uint32_t ISAR[5];
uint32_t RESERVED0[5];
/* (R/W) Coprocessor Access Control Register */
uint32_t CPACR;
} SCB_Type;
SCB->ICSR
中断控制和状态寄存器ICSR
寄存器可以在应用程序中用于:
1. 设置和清除系统异常的挂起状态,其中包括SysTick
、PendSV
、和NMI
。
2. 通过读取VECTACTIVE
可以确定当前执行的异常/中断编号。
位 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
31 | NMIPENDSET | R/W | 0 | 写1挂起NMI, 读出值表示NMI挂起状态 |
28 | PENDSVSET | R/W | 0 | 写1挂起系统调用, 读出值表示挂起状态 |
27 | PENDSVCLR | W | 0 | 写1清除PendSV挂起状态 |
26 | PENDSTSET | R/W | 0 | 写1挂起PendSV异常,读出值表示挂起状态 |
25 | PENDSTCLR | W | 0 | 写1清除SYSTICK挂起状态 |
23 | ISPRPEEMPT | R | 0 | 表示挂起中断下一步变为活跃状态 (用于调试) |
22 | ISRPENDING | R | 0 | 外部中断挂起(除了用于错误的NMI等系统异常) |
21 : 12 | VECTPENDING | R | 0 | 挂起的ISR编号 |
11 | RETTOBASE | R | 0 | 当处理器在执行异常处理时置1,若中断返回且没有其他异常挂起则返回线程 |
9 : 0 | VECTACTIVE | R | 0 | 当前执行的中断服务程序 |
该寄存器的多个位域可被调试器使用以确定系统异常的状态。多数情况下,只有挂起位可用于应用开发。
SCB->VTOR
向量表偏移寄存器SCB->AIRCR
应用中断和复位控制寄存器AIRCR
寄存器用于:
1. 控制异常/中断优先级管理中的优先级分组。
2. 提供系统的端信息(可被软件或调试器使用)。
3. 提供自复位特性。
位 | 域 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
31 : 16 | VECTKEY | R/W | - | 访问简直。写入这个寄存器时必须要将0x05FA写入,否则则会被忽略,高半字的读回值位0xFA05 |
15 | ENDIANESS | R | - | 1表示系统位大端,0则表示系统为小端,复位后才能更改 |
10 : 8 | PRIGROUP | R/W | - | 优先级分组 |
2 | SYSRESETREQ | W | - | 请求芯片控制逻辑产生一次复位 |
1 | VECTCLRACTIVE | W | - | 清除异常的所有活跃状态信息。一般用于调试或OS中,以便系统可以从系统错误中恢复过来(复位更安全) |
0 | VECTRESET | W | - | 复位Corter-M3/M4处理器(调试逻辑除外),但不会复位处理器外的电路,用于调试擦偶走,且不能和SYSRESETREQ同时使用 |
VECTRESET
和VECTCLRACTIVE
位域是为调试器设计的,尽管软件可以利用VECTRESET
触发一次处理器复位,不过由于他不会复位外设等系统中的其他部分,因此多数应用程序是不大会用到他的。若想产生一次系统复位,多数情况下应该使用SYSRESETREQ
。
SCB->SHP[0~11]
SCB->SHP[0]
到 SCB->SHP[11]
的位域定义和中断优先级寄存器的定义相同,不同指出在于他们时用于系统异常的。这些寄存器并未全部实现。
地址 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
0xE000ED18 | SCB->SHP[0] | R/W | 0 (8位) | MemoManage Fault的优先级 |
0xE000ED19 | SCB->SHP[1] | R/W | 0 (8位) | Bus Fault的优先级 |
0xE000ED1A | SCB->SHP[2] | R/W | 0 (8位) | Usage Fault的优先级 |
0xE000ED1B | SCB->SHP[3] | - | - | 未实现 |
0xE000ED1C | SCB->SHP[4] | - | - | 未实现 |
0xE000ED1D | SCB->SHP[5] | - | - | 未实现 |
0xE000ED1E | SCB->SHP[6] | - | - | 未实现 |
0xE000ED18 | SCB->SHP[7] | R/W | 0 (8位) | SVC的优先级 |
0xE000ED20 | SCB->SHP[8] | R/W | 0 (8位) | Debug Monitor的优先级 |
0xE000ED21 | SCB->SHP[9] | - | - | 未实现 |
0xE000ED22 | SCB->SHP[10] | R/W | 0 (8位) | PendSV的优先级 |
0xE000ED23 | SCB->SHP[11] | R/W | 0 (8位) | SysTick的优先级 |
SCB->SHCSR
Usage Fault
、MemManage Fault
和Bus Fault
的使能由系统处理控制和状态寄存器(0xE000ED24
)控制。错误的挂起状态和多数系统异常的活跃状态也可以从这个寄存器中得到。
位 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
18 | USAGEFAULTENA | R/W | 0 | UsageFault使能 |
17 | BUSFAULTENA | R/W | 0 | BusFault使能 |
16 | USAGEFAULTENA | R/W | 0 | MemManageFault使能 |
15 | SVCALLPENDED | R/W | 0 | SVC 挂起,SVC已启动但被更高优先级异常抢占 |
14 | BUSFAULTPENDED | R/W | 0 | BusFault挂起,总线错误已启动但被更高的优先级异常抢占 |
13 | MEMFAULTPENDED | R/W | 0 | MemFault挂起,内存管理错误已启动但被更高的优先级异常抢占 |
12 | USGFAULTPENDED | R/W | 0 | UsageFault挂起,使用错误已启动但被更高的优先级异常抢占 |
11 | SYSTICKACT | R/W | 0 | 若SYSTICK异常活跃则读出为1 |
10 | PENDSVACT | R/W | 0 | 若PendSV异常活跃则读出为1 |
8 | MONITORACT | R/W | 0 | 若调试监控异常活跃则读出为1 |
7 | SVCALLACT | R/W | 0 | 若SVC异常活跃则读出为1 |
3 | USAGEFAULTENA | R/W | 0 | 若使用错误异常活跃则读出为1 |
1 | BUSFAULTACT | R/W | 0 | 若总线错误异常活跃则读出为1 |
0 | MEMFAULTACT | R/W | 0 | 若存储管理器异常活跃则读出为1 |
多数情况下,该寄存器仅用于应用代码使能可配置的错误处理(MemManage Fault
、 Bus Fault
和 Usage Fault
)。
寄存器 | 作用 | 访问方法 |
---|---|---|
PRIMASK | 用于禁止除NMI和HardFault外的所有异常,相当于把当前有效优先级提升到0 | CPSIE/CPSID、MSR/MRS |
FAULTMASK | 用于禁止除NMI外的所有异常,相当于把当前的有效优先级修改为-1 | CPSIE/CPSID、MSR/MRS |
BASEPRI | 禁止优先级低于某些特定等级的中断 | MSR/MRS |
当PRIMASK
置位时,所有的错误时间都会触发HardFault
异常,而不论相应的可配置错误异常(MemManageFault
、BusFault
、UsageFault
)是否使能。
FAULTMASK寄存器只能在特权状态访问,不过不能在NMI和HardFault处理中设置。FAULTMASK会在退出异常处理时会被自动清除,从NMI处理退出时除外。由于这个特点,就有了一个很特殊的用法若要在低优先级的异常处理中触发一个高优先级的异常(NMI
除外)但想在低优先级处理完成后在进行高优先级的处理,可以在低优先级中断处理函数中,先设置FAULTMASK
禁止所有中断和异常(NMI
除外)。在设置高优先级中断或异常的挂起状态,然后等待这个低优先级处理函数的退出触发退出处理而清除FAULTMASK
。
BASEPRI
寄存器还可以通过另一个名称访问,也就是BASEPRI_MAX
。他们实际上是同一个寄存器,不过这个名称访问时,会得到一个条件写操作。BASEPRI
和BASEPRI_MAX
在硬件上是一个寄存器,不过在汇编代码中的编码不同。在使用BASEPRI_MAX
时,处理器会自动比较当前值和新的数值,不过只有新的优先级更高时才会允许修改。无法在非特权状态设置。
用于ARM
架构的C编译器遵循ARM
的一个名为AAPCS
(ARM
架构过程调用标准)的规范。C函数可以修改R0
~ R3
、R12
、R14(LR)
、以及PSR
。若C函数需要使用R4
~ R11
,就应该将这些寄存器保存到栈空间中,并且在函数结束前将它们恢复。
R0
~ R3
、R12
、R14(LR)
、以及PSR被称作“调用者保存寄存器”。C函数可能会修改这寄存器。
R4
~
R11为“被调用者保存寄存器”,经过一个C函数之后,这个C函数需要保证在经过这个函数前后这些寄存器前后的值是一样的。
Cortex-M
如果处理器具有浮点运算单元,则浮点运算单元中的寄存器也有类似的需求,
S0~
S15为“调用者保存寄存器”。
S16~
S31`为“被调用者保存寄存器”。
一般来说,函数调用将R0
~ R3
作为输入参数,R0
则用作返回结果。若返回值为64位,则R1
也会用于返回结果。
要使C函数可以用作异常处理,异常机制需要在异常入口自动保存R0
~ R3
、R12
、LR
以及PSR
,并在异常退出时将它们恢复,这些都要由处理器硬件控制。这样,当返回到被中断的程序时,所有寄存器的数值都会和进入中断时相同。另外,与普通的C函数调用不同,返回地址PC
的数值并没有存储在LR
中(异常机制在进入异常时将EXC_RETURN
代码放入了LR
中,该数值将会在异常返回时用到),因此,异常流程也需要将返回地址保存。这样对于Cortex-M3
或不具有浮点单元的Cortex-M4
处理器,需要在异常处理期间保存的寄存器共有8个。
对于具有浮点单元的Cortex-M4
处理器,若用到了浮点单元,则异常机制还需要保存S0
~ S15
及FPSCR
。CONTROL
寄存器中的FPCA
(浮点上下文活跃)位表示这一操作的执行情况。
在异常入口处被压入栈空间的数据块为栈帧。对于没有浮点单元的Cortex-M
内核,栈帧都是8字大小的。对于有浮点单元的Cortex-M
内核,栈帧则可能是8或26个字。
AAPCS
的另一个要求为,栈指针的指针在函数入口和出口处应该是双字对齐的。因此,若在中断产生时栈帧未对齐到双字地址上,Cortex-M3
和Cortex-M4
处理器会自动插入一个字。这样,可以保证栈指针位于异常处理的开始处。“双字栈对齐”特性是可编程的,若异常未完全未符合AAPCS
,则可以将该特性关闭。
压栈的xPSR
的第9位表示栈指针的数值是否调整过。如果栈指针被强制对齐到双字地址,而且压栈的xPSR
的第九位被置为1,表明插入一段区域。
EXC_RETURN
处理器进入异常处理或中断服务程序时,LR
的数值会被更新为EXC_RETURN
数值。当利用BX
、POP
或存储器加载指令(LDR
或LDM
)被加载到程序寄存器中时,该数值用于出发异常返回机制。
EXC_RETURN
中的一些位用于提高异常流程的其他信息。
位 | 描述 | 数值 |
---|---|---|
31 : 28 | EXC_RETURN指示 | 0xF |
27 : 5 | 保留(全为1) | 0xEFFFFF(23位都是1) |
4 | 栈帧类型 | 1(8字)或0(26字)。当浮点单元不可用时总是为1,在进入异常处理时,其会被置为CONTROL 寄存器的FPCA 位 |
3 | 返回模式 | 1(返回线程)或0(返回处理) |
2 | 返回栈 | 1(返回线程栈)或0(返回主栈) |
1 | 保留 | 0 |
0 | 保留 | 1 |
由于EXC_RETURN
的编码格式,在地址区域0xF0000000
~ 0xFFFFFFFF
中是无法执行中断返回的。不过,由于系统空间中的地址区域已经被架构定义为不可执行的,因此这样不会带来什么问题。
EXC_RETURN
的合法值:
浮点单元在中断前使用 (FPCA = 1) |
浮点单元围在中断前使用 (FPCA = 0) |
---|---|
返回处理模式(总是使用主栈) | 0xFFFFFFE1 |
返回线程模式并在返回后使用主栈 | 0xFFFFFFE9 |
返回处理模式并在返回后使用进程栈 | 0xFFFFFFED |
中断等待表示从中断请求开始到中断处理开始执行间的时间。对于Cortex-M3
和Cortex-M4
处理器,若中断系统为零等待的,而且假定系统设计允许取向量和压栈同时执行,则中断等待为12个周期,其中包括寄存器压栈、取向量以及取中断处理的指令。不过,许多情况下,由于处理器系统中的等待状态,中断等待时间可能会更大。若处理器在执行一次包括缓冲写操作在内的存储器传输,传输要在异常流程开始前完成。执行流程的持续时间还要取决于存储器访问速度。
其他情况也可能会加大中断等待时间:
1. 处理器正在处理另外一个相同或更高的优先级异常。
2. 处理器访问存储器系统。
3. 处理器正执行非对齐传输。从处理器的角度看,他可能是单次传输,不过由于总线接口需要讲非对齐传输转换为多个对齐传输,从总线等级来看它可能会占用几个周期。
4. 处理器正在执行对位段别名的操作。内部总线系统会将其转换为读-修改-写流程,这样会花费两个周期。
5. Cortex-M3
和Cortex-M4
处理器使用多种方式来降低中断处理的等待时间。例如,嵌套中断处理等多种操作都是由处理器硬件自动处理的。另外,还需要利用软件代码来确定要服务的中断或定位ISR
的其实地址。
有些指令需要执行多个时钟周期。若在处理器执行多周期指令时产生了中断请求,该指令可能会被丢弃且在中断处理结束后重新执行。这种在执行还适用于加载双字(LDRD
)和储存双字(STRD
)指令。
另外Cortex-M3
和Cortex-M4
处理器允许中断在多加载和多存储(LDM
/STM
)以及压栈出栈指令执行过程中产生。若在中断产生时LDM/STM/PUSH/POP
中的一个指令正在执行,当前的寄存器访问会结束,并且下一个寄存器编号会被存放在压栈的xPSR
中(中断继续执行ICI
位)。异常处理结束后,LDM/STM/PUSH/POP
会从传输停止的位置继续执行,这种方式还适用于具有浮点单元的Cortex-M4
处理器存储器访问指令(如VLDM/VSTM/VPUSH/VPOP
)。另外还有一个边界情况:若被打断的LDM/STM/PUSH/POP
指令为IF-THEN
(IT
)指令块的一部分,则该指令会被取消且等终端接受重新执行,这是因为ICI
位和IT
执行状态位共用执行程序状态寄存器EPSR
中相同的空间。
对于具有浮点单元的Cortex-M4
处理器,若在处理器执行VSQRT
(浮点平方根)或VDIV
(浮点除法)时产生了中断请求,浮点单元的执行会和压栈操作同步进行。
若某个异常产生时处理器正在处理另一个具有相同或更高优先级的异常,该异常就会进入挂起状态。在处理器执行玩当前的异常处理后,他可以继续执行挂起的异常/中断请求。处理器不会从栈中恢复寄存器(出栈)然后再将它们存入栈中(压栈),而是跳过出栈和压栈过程并会尽快进入挂起异常的异常处理,这样,两个异常处理间隔的时间就会降低很多。对于等待状态的存储器系统,末尾连锁的中断等待仅为6个时钟周期。末尾连锁优化还给处理器带来了更加的能耗效率,这是因为栈存储器访问的总数少了,而每次存储器传输都会消耗能量。
需要注意的是,如果在线程模式通过软件产生一个中并使能了一个中断,因为流水线的问题,在中断到来之前,CPU
可能会最多再执行两天指令才进入ISR
,所以如果要阻止CPU
继续执行指令而直接进入ISR
,需要在触发中断使能中断加ISB
指令。但是在处理模式(CPU
处于ISR
函数中)就不用加ISB
,就是因为末尾连锁机制的原因。比如在ucos
里面,OSSched
会触发PendSV
中断从而引发一次调度,但是这个函数会在线程模式调用,所以函数最后面会有一个ISB
指令。但是同样的在ucos
的SysTick
里面也会触发PendSV
,但是却没有加ISB
指令,就是因为上述末尾连锁的原因。
当异常产生时,处理器会接受异常请求并开始压栈操作。若再压栈操作期间产生了另外一个优先级更高的异常,则更高优先级的后到异常会首先得到服务。
若某个异常请求在另一个刚完成的异常处理出栈期间产生,处理器会舍弃出栈操作且开始取向量以及下一个异常服务的指令。
惰性压栈是个浮点单元寄存器压栈相关的一种特性,因此它只同具有浮点单元的Cortex-M4
设备有关。Cortex-M3
和不具备浮点单元的Cortex-M4
则不需要该特性。
若浮点单元存在且已使能,在其被使用时,浮点单元的寄存器组中的寄存器可能会包含需要保存的数据,若要将每个异常所需的浮点单元寄存器压栈,则每次都需要额外执行17次存储器压栈操作,这样会将中断等待时间增加12 ~ 29个周期。
为了减少中断等待时间,Cortex-M4
处理器实现了一种名为惰性压栈的特性,该特性默认使能。若在浮点单元使能且使用(CONTROL
寄存器中的第2位,名为FPCA
)的情况下产生了异常,则栈帧的长度会增加。不过,这些浮点寄存器的数值实际上是不会写入栈帧中的。遁形压栈机制只会为这些寄存器保留一定的栈空间,不过只有R0
~ R3
、R12
、LR
、返回地址和xPSR
被压栈。这样中断等待时间还会保持12个时钟周期。当出现惰性压栈时,一个命令LSPACT
(惰性压栈保持活跃)的寄存器被置位且浮点单元上下文地址寄存器FPCAR
则存放浮点寄存器预留栈空间的地址。
若异常处理不需要任何浮点运算,浮点单元寄存器在异常处理期间会保持不变,而且不会在异常退出时恢复。若异常处理需要浮点运算,处理器检测到冲突后会停止处理器,将浮点寄存器存到保留栈空间并清除LSPACT
,接下来异常处理会继续执行。这样,浮点单元寄存器只会在必要时压栈。
惰性压栈操作可能会被打断,当在惰性压栈期间产生了中断请求,则遁形压栈操作会停止,取而代之的是普通压栈开始执行。由于触发了惰性压栈的浮点指令还没有执行,压入栈中的PC
值会指向那条浮点指令。当中断服务结束时,异常会返回到浮点指令,而且重新执行这条指令会再次触发惰性压栈操作。
若当前执行上下文(线程或处理)未使用浮点单元,则FPCA
为0(CONTROL
寄存器的第二位),且栈帧会使用较短的形式。