注: 本文搬运自我的个人博客,原网址点击打开链接
1. 摘要
Cortex-M内核实现了一个高效异常处理模块,可以捕获非法内存访问和数个程序错误条件。本应用笔记从程序员角度描述Cortex-M Fault异常,并且讲述在软件开发周期中的Fault用法。
2. 简介
Cortex-M3(以下简称CM3)和Cortex-M4(以下简称CM4)内核的Fault异常可以捕获非法内存方法和非法编程行为。Fault异常能够检测到以下情况:
总线Fault:在取址、数据读/写、取中断向量、进入/退出中断时寄存器堆栈操作(入栈/出栈)时检测到内存访问错误。
存储器管理Fault:检测到内存访问违反了MPU定义的区域。
用法Fault:检测到未定义的指令异常,未对齐的多重加载/存储内存访问。如果使能相应控制位,还可以检测出除数为零以及其他未对齐的内存访问。
硬Fault:如果上面的总线Fault、存储器管理Fault、用法Fault的处理程序不能被执行(例如禁能了总线Fault、存储器管理Fault、用法Fault异常或者在这些异常处理程序执行过程中又出现了Fault)则触发硬Fault。
本应用笔记描述CM3和CM4的Fault异常用法。系统控制寄存器组中的寄存器可以控制Fault异常或者提供引发异常的原因信息。
更深入的文档
完整的异常描述见《Cortex - M3 Technical Reference Manual》或者《Cortex -M4 Technical Reference Manual》,这两本参考手册都可以在www.arm.com中找到。
另一个很好的参考书是由Joseph Yiu编写的《The Definitive Guide to the ARM Cortex-M3》 (这本书有中文版:宋岩译的《ARM Cortex-M3权威指南》)。
3. Cortex-M Fault异常和寄存器
每个符合CMSIS规范的编译器所提供的启动文件(Startup_device)都会定义好设备所有的异常和中断向量。这些向量表定义了异常或中断处理程序的入口地址。下表给出了一个典型的向量表,Fault异常向量用蓝色标注。
1: :
3: :
5: __Vectors DCD __initial_sp ; 栈顶
7: DCD Reset_Handler ; 复位处理程序入口
9: DCD NMI_Handler ; NMI 处理程序入口
11: DCD HardFaul t_Handler ; 硬Fault处理程序入口
13: DCD MemManage_Handler ; 存储器管理处理程序入口
15: DCD BusFault_Handler ; 总线Fault 处理程序入口
17: DCD UsageFault_Handler ; 用法 Fault 处理程序入口
19: DCD 0 ; 保留
21: :
23: :
通常总是使能硬Fault异常的,硬Fault异常具有固定的优先级,并且优先级高于其它Fault异常以及中断,但低于NMI。硬Fault异常处理程序在以下情况下会被执行:其它非硬Fault异常(非硬Fault异常是指总线、存储器管理和用法Fault 异常,下同。)被禁能,并且这些Fault异常被触发;在执行一个非硬Fault异常处理程序中又产生非硬Fault异常。
所有非硬Fault具有可编程的优先级。当Cortex-M内核复位后,这些非硬Fault被禁能,你可以在应用软件中通过设置“系统Handler控制及状态寄存器(SHCSR)”来使能非硬Fault异常。这个寄存器属于系统控制模寄存器组(SCB)
3.1 Fault异常的控制寄存器
在这里有必要介绍一下系统控制模块寄存器组(SCB)的成员,这个寄存器组的定义可以在core_cm3.h文件中,该文件属于CMSIS Cortex-M3 内核外设接口抽象层的一部分(关于不清楚CMSIS的,可以自行查找资料)。定义如下:
1.定义系统控制寄存器组结构体
/** @brief System Control Block (SCB) register structure definition */
typedef struct
{
__I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register*/
__IO uint32_t ICSR; /*!< Offset: 0x04 Interrupt Control State Register*/
__IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register*/
__IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register*/
__IO uint32_t SCR; /*!< Offset: 0x10 System Control Register*/
__IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register*/
__IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15) */
__IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
__IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register*/
__IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register*/
__IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
__IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register*/
__IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register*/
__IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register*/
__I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register*/
__I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register*/
__I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register*/
__I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register*/
__I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register*/
} SCB_Type;
2. 定义系统控制寄存器组物理空间基地址
#define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */
3. 定义指向系统控制寄存器组的指针
#define SCB ((SCB_Type *)SCB_BASE) /*!< SCB configuration struct * /
通过以上三步,我们就可以使用结构体指针SCB来访问系统控制寄存器组的寄存器了,比如给系统控制寄存器SCR赋值:SCB->SCR=0xFF;
SCB->CCR寄存器控制除数为零和未对齐内存访问是否触发用法Fault。
SCB->SHCSR寄存器可用来使能非硬Fault异常。如果一个非硬Fault异常被禁能并且相关Fault发生,这时异常会升级为硬Fault。SCB->SHP寄存器组控制异常的优先级。
Fault异常控制寄存器列表:
地址/访问 |
寄存器 |
复位值 |
描述 |
0xE000ED14 RW 特权级 |
SCB->CCR |
0x00000000 |
配置和控制寄存器:包含控制除数为零和未对齐内存访问是否触发用法Fault的使能位。 |
0xE000ED18 RW 特权级 |
SCB->SHP[12] |
0x00 |
系统处理程序优先级寄存器:控制异常处理程序的优先级 |
0xE000ED24 RW特权级 |
SCB->SHCSR |
0x00000000 |
系统处理程序控制和状态寄存器 |
3.1.1 SCB->CCR 寄存器
蓝色部分控制是否使能相应的用法Fault
名称 |
描述 |
|
[31:10] |
- |
保留 |
[9] |
STKALIGN |
表示进入异常时的堆栈对齐。 0:4字节对齐 1:8字节对齐 进入异常时,处理器使用压入堆栈的PSR位[9]来指示堆栈对齐。从异常返回时,这个堆栈位被用来恢复正确的堆栈对齐。 |
[8] |
BFHFNMIGN |
使能时,使得以优先级位-1或-2运行的处理程序忽略加载和存储指令引起的数据总线故障。它用于硬故障、NMI和FAULTMASK升级处理程序中: 0:加载和存储指令引起的数据总线故障会引起锁定。 1:以优先级-1或-2运行的处理程序忽略加载和存储指令引起的数据总线故障。 仅在处理程序和其数据处于绝对安全的存储器时将该位设为1。一般将该位用于探测系统设备和桥接器以检测并纠正控制路径问题。 |
[7:5] |
- |
保留 |
[4] |
DIV_0_TRP |
当处理器进行除0操作(SDIV或UDIV指令)时,会导致故障或停止。 0:不捕获除以零故障 1:捕获除以零故障。 当该位设为0时,除以零返回的商数为0。 |
[3] |
UNALIGN_TRP |
使能非对齐访问捕获: 0:不捕获非对齐半字和字访问 1:捕获非对齐半字和字访问。 如果该位设为1,非对齐访问产生一个使用故障。无论UNALIGN_TRP是否设为1,非对齐的LDM、STM、LDRD和STRD指令总是出错。 |
[2] |
- |
保留 |
[1] |
USERSETM PEND
|
使能对STIR的无特权软件访问。 0:禁能 1:使能 |
[0] |
NONEBASE THRDENA
|
指示处理器如何进入线程模式: 0:处理器仅在没有有效异常时才能够进入线程模式。 1:处理器可以从EXC_RETURN值控制下的任何级别进入线程模式 |
3.1.2 SCB->SHP 寄存器组
以下SCB->SHP 寄存器组的寄存器用来设置异常处理程序的优先级:
SCB->SHP[0]:存储器管理Fault的优先级
SCB->SHP[1]:总线Fault的优先级
SCB->SHP[2]:用法Fault的优先级
为了编程中断和异常的优先级,CMSIS提供了函数NVIC_SetPrioriity和NVIC_GetPriority。这两个函数也位于core_cm3.h中,源码为:
/** \brief Set Interrupt Priority
This function sets the priority for the specified interrupt. The interrupt number can be positive
to specify an external (device specific) interrupt, or negative to specify an internal (core)
interrupt.
Note: The priority cannot be set for every core interrupt.
\param [in] IRQn Number of the interrupt for set priority
\param [in] priority Priority to set
*/
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if(IRQn < 0) {
/* set Priority for Cortex-M System Interrupts */
SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); }
else {
/* set Priority for device specific Interrupts */
NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); }
}
/** \brief Get Interrupt Priority
This function reads the priority for the specified interrupt. The interrupt number can be positive
to specify an external (device specific) interrupt, or negative to specify an internal (core)
interrupt.
The returned priority value is automatically aligned to the implemented priority bits of the
microcontroller.
\param [in] IRQn Number of the interrupt for get priority
\return Interrupt Priority
*/
static __INLINE uint32_t NVIC_GetPriority(IRQn_Type IRQn)
{
if(IRQn < 0) {
/* get priority for Cortex-M system interrupts */
return((uint32_t)(SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] >> (8 - __NVIC_PRIO_BITS))); }
else {
/* get priority for device specific interrupts */
return((uint32_t)(NVIC->IP[(uint32_t)(IRQn)] >> (8 - __NVIC_PRIO_BITS))); }
}
可以通过下面的示例代码更改异常优先级:
:
:
NVIC_SetPriority (MemoryManagement_IRQn, 0xF0);
NVIC_SetPri ority (BusFault_IRQn, 0x80);
NVIC_SetPriority ( UsageFault_IRQn, 0x10);
:
UsageFault_prio = NVIC_GetPriority ( UsageFault_IRQn);
:
:
3.1.3 SCB->SHCSR寄存器
与Fault异常相关位见下表的蓝色部分
位 |
名称 |
描述 |
[31:19] |
- |
保留 |
[18] |
USGFAULTENA |
用法Fault使能位,设为1时使能 |
[17] |
BUSFAULTENA |
总线Fault使能位,设为1时使能 |
[16] |
MEMFAULTENA |
存储器管理Fault使能位,设为1使能 |
[15] |
SVCALLPENDED |
SVC调用挂起位,如果异常挂起,该位读为1 |
[14] |
BUSFAULTPENDED |
总线Fault异常挂起位,如果异常挂起,该位读为1 |
[13] |
MEMFAULTPENDED |
存储器Fault故障异常挂起位,如果异常挂起,该位读为1 |
[12] |
USGFAULTPENDED |
用法Fault异常挂起位,如果异常挂起,该位读为1 |
[11] |
SYSTICKACT |
SysTick 异常有效位,如果异常有效,该位读为1 |
[10] |
PENDSVACT |
PendSV异常有效位,如果异常有效,该位读为1 |
[9] |
- |
保留 |
[8] |
MONITORACT |
调试监控有效位,如果调试监控有效,该位读为1 |
[7] |
SVCALLACT |
SVC调用有效位,如果SVC调用有效,该位读为1 |
[6:4] |
- |
保留 |
[3] |
USGFAULTACT |
用法Fault异常有效位,如果异常有效,该位读为1 |
[2] |
- |
保留 |
[1] |
BUSFAULTACT |
总线Fault异常有效位,如果异常有效,该位读为1 |
[0] |
MEMFAULTACT |
存储器管理Fault异常有效位,如果异常有效,该位读为1 |
尽管可以写SCB->SHCSR寄存器的所有位,但建议软件只写异常使能位。下面的例子用于使能所有非硬Fault(存储器管理Fault、总线Fault、用法Fault异常):
SCB - >SHCSR |= 0x00007000; /*enable Usage Fault, Bus Fault, and MMU Fault*/
注:要包含core_cm3.h头文件。
3.2 Fault异常的状态和地址寄存器
Fault状态寄存器组(SCB->CFSR和SCB->HFSR)和Fault地址寄存器组(SCB->MMAR和SCB->BFAR)包含Fault的详细信息以及异常发生时访问的内存地址。
地址/访问 |
寄存器 |
复位值 |
描述 |
0xE000ED28 RW 特权级 |
SCB->CFSR |
0x00000000 |
可配置Fault状态寄存器:包含指示存储器管理Fault、总线Fault或用法Fault的原因位 |
0xE000ED2C RW 特权级 |
SCB->HFSR |
0x00000000 |
硬Fault状态寄存器:包含用于指示硬Fault原因位。 |
0xE000ED34 RW特权级 |
SCB->MMFAR |
不可知 |
存储器管理Fault地址寄存器:包括产生存储器管理Fault的位置的地址 |
0xE000ED38 RW特权级 |
SCB->BFAR |
不可知 |
总线Fault地址寄存器:包括产生总线Fault的位置的地址 |
3.2.1 SCB->CFSR寄存器
SCB->CFSR寄存器的位分配表:
bit31 bit16 |
bit15 bit8 |
bit7 bit0 |
用法Fault状态寄存器(UFSR) |
总线Fault状态寄存器(BFSR) |
存储器管理Fault状态寄存器(MMFSR) |
SCB->CFSR寄存器可以被分成三个组:
存储器管理Fault 状态寄存器:地址0x0xE000ED28,可以按字节访问
总线Fault状态寄存器:地址0xE000ED29,可以按字节访问
用法Fault状态寄存器:地址0xE000ED2A,可以按半字访问
3.2.1.1 存储器管理Fault状态寄存器MMFSR:指示存储器访问Fault的原因
位 |
名称 |
描述 |
MMARVALID |
存储器管理Fault地址寄存器(MMAR)有效标志: 0:MMAR中的值不是一个有效Fault地址 1:MMAR中保留一个有效Fault地址。 如果发生了一个存储器管理Fault,并由于优先级的原因升级成一个硬Fault,那么硬Fault处理程序必须将该位设为0。 |
|
[6:5] |
- |
保留 |
[4] |
MSTKERR |
进入异常时的入栈操作引起的存储器管理Fault: 0:无入栈Fault 1:进入异常时的入栈操作引起了一个或一个以上的访问违犯。 当该位设为1时,依然要对SP进行调节,并且堆栈的上下文区域的值可能不正确。处理器没有向MMAR中写入Fault地址。 |
[3] |
MUNSTKERR |
异常返回时的出栈操作引起的存储器管理Fault: 0:无出栈Fault 1:异常返回时的出栈操作已引起一个或一个以上的访问违犯. 该Fault与处理程序相连,这意味着当该位为1时,原始的返回堆栈仍然存在。 处理器不能对返回失败的SP进行调节,并且不会执行新的存储操作。处理器没有向MMAR中写入Fault地址。 |
[2] |
- |
保留 |
[1] |
DACCVIOL |
数据访问违犯标志: 0:无数据访问违犯Fault 1:处理器试图在不允许执行操作的位置上进行加载和存储。 当该位为1时,异常返回的压入堆栈的PC值指向出错指令。处理器已在MMAR中加载了目标访问的地址。 |
[0] |
IACCVIOL |
指令访问违犯标志: 0:无指令访问违犯错误 1:处理器试图从不允许执行操作的位置上进行指令获取。 即使MPU被禁能,这一故障也会在XN(CM3内核的0xE0000000~0xFFFFFFFF区域)区寻址时发生。 当该位为1时,异常返回的压入堆栈的PC值指向出错指令。处理器没有向MMAR中写入故障地址。 |
3.2.1.2 总线Fault状态寄存器BFSR:指示总线访问Fault原因
名称 |
描述 |
|
[7] |
BFARVALID |
总线Fault地址寄存器(BFAR)有效标志: 0:BFAR中的值不是有效故障地址 1:BFAR中保留一个有效故障地址。 在地址已知的总线故障发生后处理器将该位设为1。该位可以被其他Fault清零,例如之后发生的存储器管理Fault。 如果发生总线Fault,并由于优先级原因升级为一个硬Fault,那么硬Fault处理程序必须将该位设为0。 |
[6:5] |
- |
保留 |
[4] |
STKERR |
进入异常时的入栈操作引起的总线Fault: 0:无入栈故障 1:进入异常时的入栈操作已引起一个或一个以上的总线故障。 当处理器将该位设为1时,依然要对SP进行调节,并且堆栈的上下文区域的值可能不正确。处理器没有向BFAR中写入Fault地址。 |
[3] |
UNSTKERR |
异常返回时的出栈操作引起的总线Fault: 0:无出栈Fault 1:异常返回时的出栈操作已引起一个或一个以上的总线Fault。 该Fault与处理程序相连, 这意味着当处理器将该位设为1时,原始的返回堆栈仍然存在。处理器不能对返回失败的SP进行调节,并且不会执行新的存储操作,也未向BFAR中写入Fault地址。 |
[2] |
IMPRECISERR |
非精确数据总线错误: 0:无非精确数据总线错误 1:已发生一个数据总线错误,但是堆栈帧中的返回地址与引起错误的指令无关。 当处理器将该位设为1时,不向BFAR中写入Fault地址。 这是一个异步Fault。因此,如果在当前进程的优先级高于总线Fault优先级时检测到该Fault,总线Fault被挂起并仅在处理器从所有更高优先级进程中返回时开始变为有效。如果在处理器进入非精确总线Fault的处理程序前发生一个精确Fault,那么处理程序同时对IMPRECISERR 和其中一个精确Fault状态位进行检测,判断它们是否置位为1。 |
[1] |
PRECISERR |
精确数据总线错误: 0:非精确数据总线错误 1:已发生一个数据总线错误,且异常返回的压入堆栈的PC值指向引起Fault的指令。 当处理器将该位设为1时,向BFAR中写入Fault地址。 |
[0] |
IBUSERR |
指令总线错误: 0:无指令总线错误 1:指令总线错误。 处理器检测到预取指令时的指令总线错误,但仅在其试图签发Fault指令时才将IBUSERR 标志设为1。 当处理器将该位设为1时,不向BFAR中写入Fault地址。 |
3.2.1.3 用法Fault状态寄存器UFSR:指示产生用法Fault的原因
名称 |
描述 |
|
[15:10] |
- |
保留 |
[9] |
DIVBYZERO |
0:无除以零Fault或除以零捕获未使能 1:处理器已执行SDIV或UDIV指令(除以零)。 当处理器将该位设为1时,异常返回的压入堆栈的PC值指向执行除以零的指令。 注:通过将CCR中的DIV_0_TRP位设为1使能除以零捕获,默认是不使能的。 |
[8] |
UNALIGNED |
0:无非对齐访问Fault,或非对齐访问捕获未使能 1:处理器已进行了一次非对齐的存储器访问。 注:通过将CCR中的UNALIGN_TRP位设为1来使能非对齐访问捕获,默认是不使能的。非对齐的LDM、STM、LDRD和STRD指令总是出错,与UNALIGN_TRP的设置无关。 |
[7:4] |
- |
保留 |
[3] |
NOCP |
无协处理器用法Fault。处理器不支持协处理器指令: 0:试图访问一个协处理器未引起用法Fault 1:处理器已试图访问一个协处理器。 |
[2] |
INVPC |
EXC_RETURN的无效PC加载引起的无效PC加载用法Fault: 0:没有发生无效PC加载用法Fault 1:处理器已试图将EXC_RETURN非法载入PC,作为一个无效的上下文或一个无效的EXC_RETURN值。 当该位被设为1时,异常返回的压入堆栈的PC值指向尝试执行非法PC加载的指令。 |
[1] |
INVSTATE |
无效状态用法Fault: 0:未发生无效状态用法Fault 1:处理器已试图执行一个非法使用EPSR的指令。 当该位设为1时,异常返回的压入堆栈的PC值指向一个尝试非法使用EPSR的指令。 如果一个未定义的指令使用了EPSR,则该位不被置位为1。 |
[0] |
UNDEFINSTR |
未定义的指令用法Fault: 0:无未定义的指令用法Fault 1:处理器已试图执行一个未定义的指令。当该位设为1时,异常返回的压入堆栈的PC值指向未定义的指令。 未定义的指令是一条不能被处理器译码的指令。 |
3.2.2 SCB->HSFR寄存器
SCB->HSFR寄存器提供关于激活硬Fault处理程序的事件的信息,写入1清零相应位。
位 |
名称 |
描述 |
[31] |
DEBUGEVT |
硬Fault因调试事件产生,保留供调试使用。对寄存器执行写操作时,必须向该位写入0;否则,该行为不可预知。 |
[30] |
FORCED |
指示硬Fault是否由*产生,非硬Fault的处理程序无法执行时,会*成硬Fault。 0:硬Fault不是因为非硬Fault*产生的 1:硬Fault是通过非硬Fault*产生的。 当该位设为1时,硬Fault处理程序必须读其他Fault状态寄存器以找出Fault原因。 |
[29:2] |
- |
保留 |
[1] |
VECTTBL |
指示一个在异常处理过程中读向量表而引起的总线Fault: 0:读向量表未引起总线Fault 1:读向量表引起了总线Fault。 这一错误通常情况下都由硬Fault处理程序来处理。 |
[0] |
- |
保留 |
3.2.3 SCB->MMFAR和SCB->BFAR寄存器
为了确定产生了哪个Fault异常以及什么原因引起的Fault异常,你需要检测Fault状态寄存器。
如果SCB->CFSR寄存器的BFARVALID位有效(为1),则SCB->BFAR寄存器的值表示引起总线Fault的内存地址。
如果SCB->CFSR寄存器的MMFARVALID位有效(为1),则SCB->MMFAR寄存器的值表示引起存储器管理Fault的内存地址。
参考资料:
1. ARM Cortex-M3权威指南 Joseph Yiu著 宋岩译
2. Application Note 209 Using Cortex-M3 and Cortex -M4 Fault exceptions Copyright 2010 ARM
3. LPC178x/7x用户手册 NXP