目录
- 一、简介
- 二、Fault exception registers
-
- 1、Control registers
- 2、Status and address registers
- 四、Fault Handlers 的实现
- 五、补充
- 六、实例讲解
一、简介
1、异常类型
HardFault
(硬件错误)是一类在嵌入式系统开发中较为常见的系统异常,优先级仅低于复位和 NMI(不可屏蔽中断)。当系统运行过程中遇到了某些错误时程序就会跳转至 HardFault_Handler
函数中,引发程序故障进而影响程序的正常运行。
一般而言,我们遇到的错误有如下几种:
HardFault
(硬件故障):默认异常,由于异常处理过程中的错误或由于任何其他异常机制无法管理异常而触发。MemManage
(内存管理故障):检测内存管理单元(MPU)中定义的区域的内存访问违规;例如,在一个只有读/写访问权限的内存区域中执行代码。BusFault
(总线故障):在指令取指、数据读/写、中断向量取指和寄存器入栈(保存/恢复)中断(进入/退出)时检测内存访问错误。UsageFault
(使用故障):检测未定义指令的执行,加载/存储多个未对齐的内存访问。启用时,将检测除 0 和其他非对齐内存访问。
2、异常优先级
每个异常都有一个相关联的异常号和一个相关联的优先级号。为简化软件层,CMSIS 只使用 IRQ 编号,因此对中断以外的异常使用负值。下表按优先级顺序列出故障异常:
Exception | Exception Number | 优先级 | IRQ Number | 激活方式 |
---|---|---|---|---|
HardFault |
3 | -1 | -13 | - |
MemManage fault |
4 | 可配置 | -12 | 同步 |
BusFault |
5 | 可配置 | -11 | 精确时同步,不精确时异步 |
UsageFault |
6 | 可配置 | -10 | 同步 |
由这张表可以看出,HardFault
异常总是启用的,并且具有固定的优先级(高于其他中断和异常,但低于不可屏蔽中断 NMI)。因此,在禁用故障异常或在执行故障异常处理程序期间发生故障的情况下,将执行 HardFault
异常。通过下面的例子来理解一下这段话。
在 stm32f4xx_it.c
文件中有如下几个中断服务程序:
void NMI_Handler(void);
void HardFault_Handler(void);
void MemManage_Handler(void);
void BusFault_Handler(void);
void UsageFault_Handler(void);
void SVC_Handler(void);
void DebugMon_Handler(void);
void PendSV_Handler(void);
void SysTick_Handler(void);
以 MemManage_Handler
为例,如果你没有使能这个中断的话,那它肯定也不会进入这个中断,而是会进入 HardFault_Handler
,也就是硬件异常!这也是下面要讲的优先级提升。
而所有其他故障异常(MemManage fault
、Bus Fault
和 Usage Fault
)都具有可编程优先级。重置后,这些异常被禁用,可以在系统或应用软件中使用系统控制块(SCB)中的寄存器启用。
通常,异常优先级和异常掩码寄存器的值一起确定处理器是否进入错误处理程序,以及错误处理程序是否可以抢占另一个错误处理程序。在某些情况下(例如刚才的例子),具有可配置优先级的故障被视为 Hard Fault
。这被称为优先级提升,即故障升级为 HardFault
。升级到 HardFault
发生在:
- 当一个 Fault 触发时,并再次触发相同的 Fault。之所以会升级到
HardFault
,是因为处理程序不能抢占自己(它必须具有与当前优先级相同的优先级)。 - 当一个 Fault 执行时,有一个优先级比它低或者同级的 Fault 被触发。因为新错误的处理程序不能抢占当前执行的错误处理程序。
- 当一个(普通的)中断在执行的时候,有一个优先级比它低或者同级的(普通的)中断触发了 Fault。
- 发生了 Fault,但未启用该错误的处理程序(上面介绍过的例子)。
如果在入栈过程中进入 BusFault handler 时发生 BusFault
,BusFault
不会升级为 HardFault
。这意味着,如果崩溃的栈导致了错误,即使处理程序的入栈失败,也会执行错误处理程序。错误处理程序正常运行,但栈内容已损坏。
注意:只有 Reset(复位中断) 和 NMI(不可屏蔽中断) 可以抢占固定优先级的
HardFault
。HardFault
可以抢占除 Reset、NMI 或其他 HardFault 之外的任何异常。如下图所示:
3、同步异步问题
BusFault
错误处理程序可以使用 BFSR
来确定错误是异步的(IMPRECISERR,不精确的)还是同步的(PRECISERR,精确的)。
同步总线故障也被称为精确总线故障。何谓精确?就是总线触发异常之后,我们可以找到它触发异常的地址。如果同步总线故障发生在 NMI 或 HardFault 处理程序内,则会升级为锁定。缓存维护操作也可能触发总线故障。调试访问也可能触发总线故障。调试器的加载或存储访问是同步的,并且仅对调试器接口可见。
异步总线故障被称为为不精确总线故障,同理,就是总线触发异常之后,我们不能找到它触发异常的地址。它可能发生在处理器设计中存在写缓冲时。因此,在观察到总线错误响应之前,处理器流水线会继续执行后续的指令。当异步总线故障被触发时,总线故障异常被挂起。如果另一个高优先级的中断事件同时到达,则首先执行高优先级的中断处理程序,然后发生 BusFault
。如果总线故障处理程序未启用,则挂起 HardFault
。由异步总线故障引起的硬故障不会升级为锁定。异步错误通常是不可恢复的,因为你不知道是哪段代码导致了错误。
现代的芯片都是多级流水线执行的,它触发的时候,多级流水线人在执行,我们无法锁定是哪一级出了问题,也就无法找到精确的错误地址。
4、异常具体类型
下表中列出了 Fault 类型、Fault Handler、Fault status register 和故障发生的寄存器位名:
HardFault
-HFSR
Fault type | Bit Name |
---|---|
Bus error on a vector read error | VECTTBL |
Fault that is escalated to a hard fault | FORCED |
Fault on breakpoint escalation | DEBUGEVT |
MemManage
-MMFSR
Fault type | Bit Name |
---|---|
Fault on instruction access | IACCVIOL |
Fault on direct data access | DACCVIOL |
Context stacking, because of an MPU access violation | MSTKERR |
Context unstacking, because of an MPU access violation | MUNSTKERR |
During lazy floating-point state preservation | MLSPERR |
BusFault
-BFSR
Fault type | Bit Name |
---|---|
During exception stacking | STKERR |
During exception unstacking | UNSTKERR |
During instruction prefetching, precise | IBUSERR |
During lazy floating-point state preservation | LSPERR |
Precise data access error, precise | PRECISERR |
Imprecise data access error, imprecise | IMPRECISERR |
UsageFault
-UFSR
Fault type | Bit Name |
---|---|
Undefined instruction | UNDEFINSTR |
Attempt to enter an invalid instruction set state | INVSTATE |
Failed integrity check on exception return | INVPC |
Attempt to access a non-existing coprocessor | NOCPC |
Illegal unaligned load or store | UNALIGNED |
Stack overflow | STKOF |
Divide By 0 | DIVBYZERO |
下面就来看一下这些寄存器。
二、Fault exception registers
1、Control registers
系统控制块(SCB)提供系统实施信息和系统控制。这包括系统异常的配置、控制和报告。它的一些寄存器用于控制 Fault 异常。
这里有三个寄存器:
CCR
(The Configuration and Control Register,配置和控制寄存器),控制Usage Fault
的除零和非对齐内存访问的行为SHP
(The System Handler Priority Registers,系统处理程序优先级寄存器),控制异常优先级SHCSR
(The System Handler Control and State Register,系统处理程序控制和状态寄存器),使能系统处理程序,表示Bus Fault
、MemManage fault
和 SVC异常的待处理状态。
Address / Access | Register | Reset Value | Description |
---|---|---|---|
0xE000ED14 RW privileged |
CCR | 0x00000000 | 包含捕获与 UsageFault 的除零和非对齐访问的启用位 |
0xE000ED18 RW privileged |
SHP[12] | 0x00 | 控制异常处理器的优先级 |
0xE000ED24 RW privileged |
SHCSR | 0x00000000 | 表示硬故障原因的位 |
1.1 CCR
DIV_0_TRP
:在处理器执行除数为 0 的SDIV
或UDIV
指令时启用Usage Fault
- 0:除 0 不触发;除以 0 得到的商是 0
- 1:除 0 触发
UNALIGN_TRP
:当对非对齐地址进行内存访问时,启用Usage Fault
:- 0:不捕获非对齐的半字和字访问
- 1:捕获非对齐的半字和字访问;非对齐访问会产生
Usage Fault
- 请注意,使用
LDM
、STM
、LDRD
和STRD
指令的非对齐访问总是会产生Usage Fault
,即使UNALIGN_TRP
设置为 0
1.2 SHP
SHP 寄存器设置异常处理程序的优先级。故障异常通过以下方式控制:
SHP[0]
:内存管理故障的优先级SHP[1]
:总线故障的优先级SHP[2]
:Usage Fault
的优先级
对于编程中断和异常优先级,CMSIS 提供了 NVIC_SetPriority
和 NVIC_GetPriority
函数。故障异常的优先级可以修改如下:
NVIC_SetPriority (MemoryManagement_IRQn, 0x0F);
NVIC_SetPriority (BusFault_IRQn,