Cortex-M CPU 会在系统发生故障时引发异常。非法内存写入和读取、访问未通电的外设、执行无效指令、除以零以及其他问题都可能导致此类异常。通常在所有情况下都会引发 HardFault 异常。对于某些故障,可以启用不同的异常来专门处理这些情况。

Cortex-M 故障异常
Cortex-M 处理器实现了不同的故障异常。
HardFault 异常
HardFault 是默认异常,在任何与其他(启用的)异常无关的错误上引发。
HardFault 的优先级固定为 -1,即它的优先级高于除 NMI 之外的所有其他中断和异常。因此,当应用程序代码、中断或其他异常中发生错误时,总是可以进入 HardFault 异常处理程序。HardFault 是向量表中的异常编号 3,IRQ 编号为 -13。
内存管理异常
MemManage 异常可利用内存保护单元 (MPU) 来引发内存访问违规异常。
MemManage 是向量表中的异常编号 4,IRQ 编号 -12,并且具有可配置的优先级。
总线故障异常
任何内存访问错误都会引发 BusFault 异常。例如,非法读取、写入或向量捕获。
BusFault 是向量表中的异常编号 5,IRQ 编号为 -11,具有可配置的优先级。BusFault 可以在系统控制块 (SCB) 中明确启用。当 BusFault 未启用时,会引发 HardFault。
UsageFault 异常
执行错误时会引发 UsageFault 异常。加载/存储多个指令上的未对齐访问始终会被捕获。其他未对齐访问以及除以零的异常可在 SCB 中额外启用。
UsageFault 是向量表中的异常编号 6,IRQ 编号为 -10,具有可配置的优先级。当未启用 UsageFault 时,将改为引发 HardFault。
异常处理
发生任何异常时,都会调用从向量表读取的异常处理程序,就像中断一样。异常处理通常在开发和生产固件过程中以不同的方式进行。
开发过程中的异常处理
在固件开发过程中,可能会发生错误。开发人员通常希望分析问题所在以解决错误。
Cortex-M NVIC 提供了各种寄存器来分析崩溃的原因。此外,在大多数情况下,可以恢复崩溃发生位置的调用堆栈和寄存器内容。
调试器可以提供特殊功能来帮助分析。在这种情况下,无需向代码添加任何特殊异常处理程序。调试器只需在向量捕获或断点处中断并进行分析即可。
无需调试器的帮助,异常处理程序可以包含开发代码来收集所需信息,以便轻松地在调试器中使用它们。下面列出的 SEGGER HardFault Handler 读取相应的故障寄存器,并使其在结构中轻松可用。
生产固件中的异常处理
发布固件中也可能出现异常,应该将其捕获。
有些异常可能不是由错误引起的。例如,在未连接调试器的情况下执行 BKPT 指令会导致 HardFault。对于这些异常,固件可以简单地返回并继续执行程序。
系统可能还想从某些错误(但不是崩溃)的情况中恢复。在异常处理程序中,可以分析错误原因并从错误中恢复系统,例如通过终止导致错误的任务。
如果发生崩溃,只需重置系统即可恢复。在更高级的情况下,可以在重置之前生成崩溃转储,以便在重启时发送或保存。
异常分析
可以使用上面描述的 HardFault Handler 分析导致异常的系统状态。此外,一些调试器(例如 Ozone)提供了特殊功能来简化分析。
当 Ozone 检测到目标系统崩溃时,它会自动分析目标状态并提供所有必要的信息。异常窗口显示崩溃的原因以及发生的位置和附加 NVIC 寄存器。调用堆栈窗口还可以从异常中展开,以便轻松导航到错误的位置,即使跨多个异常也是如此。
有关更多信息,请参阅使用臭氧分析 Cortex-M 故障
故障状态寄存器
Cortex-M 系统控制块 (SCB) 包含一些寄存器,可以配置异常并提供有关故障的信息。
硬故障状态寄存器 (HFSR)
HFSR 位于地址 0xE000ED2C 的 SCB 中。它是一个 32 位寄存器。
位域:
[31] DEBUGEVT - 保留供调试器/调试探测器使用。始终写入 0。
[30] FORCED - 如果为 1,则由于禁用或优先级原因,HardFault 是由另一个异常升级引起的。
[1] VECTTBL - 如果为 1,则在读取异常处理向量表时发生 BusFault。
使用故障状态寄存器 (UFSR)
UFSR 是一个 16 位伪寄存器,是地址 0xE000ED28 处可配置故障状态寄存器 (CFSR) 的一部分。也可以通过半字访问 0xE000ED2A 直接访问它。
位域:
[9] DIVBYZERO - 如果为 1,则使用除数 0 执行 SDIV 或 UDIV 指令。
[8] UNALIGNED - 如果为 1,则在未对齐地址上执行 LDM、STM、LDRD 或 STRD,或者在启用陷阱时执行单个加载或存储。
[3] NOCP - 如果为 1,则访问不受支持的(例如不可用或未启用)协处理器。
[2] INVPC - 如果为 1,则将非法或无效的 EXC_RETURN 值加载到 PC。
[1] INVSTATE - 如果为 1,则在无效状态下执行。例如,EPSR 中的 Thumb 位未设置,或者 EPSR 中的 IT 状态无效。
[0] UNDEFINSTR - 如果为 1,则执行未定义的指令。
总线故障状态寄存器 (BFSR) 和总线故障地址寄存器 (BFAR)
BFSR 是 CFSR 中的一个 8 位伪寄存器。它可以通过从 0xE000ED29 开始的字节访问直接访问。BFAR 是一个位于 0xE000ED38 的 32 位寄存器。
位域:
[7] BFARVALID - 如果为 1,则 BFAR 包含导致 BusFault 的地址。
[5] LSPERR - 1f 1,浮点惰性堆栈保存期间发生故障。
[4] STKERR - 如果为 1,则异常入口的堆栈故障。
[3] UNSTKERR - 如果为 1,则异常返回时出栈故障。
[2] IMPRECISERR - 如果为 1,则返回地址与故障无关,例如之前导致的故障。
[1] PRECISERR - 如果为 1,则返回地址指令导致故障。
[0] IBUSERR - 如果为 1,则指令获取故障。
MemManage 故障状态寄存器 (MMFSR) 和 MemManage 故障地址寄存器 (MMFAR)
MMFSR 是 CFSR 中的一个 8 位伪寄存器。它可以通过从 0xE000ED28 开始的字节访问直接访问。MMFAR 是一个位于 0xE000ED34 的 32 位寄存器。
位域:
[7] MMARVALID - 如果为 1,则 MMFAR 包含导致 MemManageFault 的地址。
[5] MLSPERR - 1f 1,浮点惰性堆栈保存期间发生故障。
[4] MSTKERR - 如果为 1,则异常进入堆栈时发生故障。
[3] MUNSTKERR - 如果为 1,则异常返回时出栈时发生故障。
[1] DACCVIOL - 如果为 1,则数据访问违规。
[0] IACCVIOL - 如果为 1,则指令访问违规。
堆栈恢复
在异常入口处,异常处理程序可以检查发生故障时使用了哪个堆栈。当 EXC_RETURN[2] 位被设置时,使用 MSP,否则使用 PSP。
堆栈可用于恢复 CPU 寄存器的值。
CPU寄存器恢复
在异常进入时,一些 CPU 寄存器存储在堆栈中,可以从那里读取以进行错误分析。以下寄存器是可恢复的:
r0 = pStack[0]; // 寄存器 R0
r1 = pStack[1]; // 寄存器 R1
r2 = pStack[2]; // 寄存器 R2
r3 = pStack[3]; // 寄存器 R3
r12 = pStack[4]; // 寄存器 R12
lr = pStack[5]; // 链接寄存器 LR
pc = pStack[6]; // 程序计数器 PC
psr.byte = pStack[7]; // 程序状态字 PSR
故障分析示例
以下示例显示了某些故障是如何/为何发生的,以及如何分析它们。此处提供了一个用于测试这些故障的项目。
BusFault 示例
非法内存写入
/****************************************************************************
*
* _IllegalWrite()
*
* 函数说明
* 通过写入保留地址来触发 BusFault 或 HardFault。
*
* 附加信息
* 写入指令后执行某些指令会引发 BusFault。
* 故障时相关寄存器:
* HFSR = 0x40000000
* FORCED = 1 - BusFault 升级为 HardFault(当 BusFault 未激活时)
* BFSR = 0x00000004
* IMPRECISERR = 1 - 不精确的数据访问违规。返回地址与故障无关
* BFARVALID = 0 - BFAR 无效
*/
static int _IllegalWrite ( void ) {
int r ;
volatile unsigned int * p ;
r = 0 ;
p = ( unsigned int * ) 0x00100000 ; // 0x00100000-0x07FFFFFF 在 STM32F4 上保留
// F44F1380 mov.w r3, #0x00100000
* p = 0x00BADA55 ;
// 4A03 ldr r2, =0x00BADA55
// 601A str r2, [r3] <- 此处执行了非法写入
return r ;
// 9B00 ldr r3, [sp]
// 4618 mov r0, r3
// B002 add sp, sp, #8 <- 此处可能会引发故障
// 4770 bx lr
}
非法内存读取
/****************************************************************************
*
* _IllegalRead()
*
* 功能说明
* 通过从保留地址读取来触发 BusFault 或 HardFault。
*
* 附加信息
* 读取指令后立即触发 BusFault。
* 故障时相关寄存器:
* HFSR = 0x40000000
* FORCED = 1 - BusFault 升级为 HardFault
* BFSR = 0x00000082
* PRECISERR = 1 - 精确数据访问违规
* BFARVALID = 1 - BFAR 有效
* BFAR = 0x00100000 - 读取的地址
*/
static int _IllegalRead ( void ) {
int r ;
volatile unsigned int * p ;
p = ( unsigned int * ) 0x00100000 ; // 0x00100000-0x07FFFFFF 在 STM32F4 上保留
// F44F1380 mov.w r3, #0x00100000 <- 读取地址。将在 BFAR 中找到
r = * p ;
// 681B ldr r3, [r3] <- 此处发生非法读取并引发 BusFault
// 9300 str r3, [sp]
return r ;
}
非法函数执行
/****************************************************************************
*
* _IllegalFunc()
*
* 功能说明
* 通过在保留地址执行来触发 BusFault 或 HardFault。
*
* 附加信息
* 在无效地址执行时触发 BusFault。
* 故障时相关寄存器:
* HFSR = 0x40000000
* FORCED = 1 - BusFault 升级为 HardFault
* BFSR = 0x00000001
* IBUSERR = 1 - 指令预取时发生 BusFault
*/
static int _IllegalFunc ( void ) {
int r ;
int ( * pF )( void );
pF = ( int ( * )( void )) 0x00100001 ; // 0x00100000-0x07FFFFFF 在 STM32F4 上保留
// F44F1380 mov.w r3, #0x00100001
r = pF (); // 4798 blx r3 <- 分支到非法地址,导致从 0x00100000 获取并返回故障异常
return r ;
}
UsageFault 示例
未定义指令执行
/****************************************************************************
*
* _UndefInst()
*
* 函数说明
* 执行未定义的指令时触发 UsageFault 或 HardFault。
*
* 附加信息
* 在无效地址执行时触发 UsageFault。
* 硬故障的相关寄存器:
* HFSR = 0x40000000
* FORCED = 1 - UsageFault 升级为 HardFault
* UFSR = 0x0001
* UNDEFINSTR = 1 - 执行了未定义的指令
*/
static int _UndefInst ( void ) {
static const unsigned short _UDF [ 4 ] = {
0xDEAD , 0xDEAD , 0xDEAD , 0xDEAD }; // 0xDEAD: UDF #<imm> (永久未定义)
int r ;
int ( * pF )( void );
pF = ( int ( * )( void ))((( char * ) & _UDF ) + 1 );
// 4B05 ldr r3, =0x08001C18 <_UDF> <- 加载“RAM Code”指令的地址
// 3301 添加 r3, #1 <- 确保 Thumb 位已设置
r = pF ();
// 4798 blx r3 <- 调用“RAM Code”,将执行 UDF 指令并引发异常
// 9000 str r0, [sp]
return r ;
}
非法状态
/****************************************************************************
*
* _NoThumbFunc()
*
* 功能说明
* 执行未设置 thumb 位的地址时触发 UsageFault 或 HardFault。
*
* 附加信息
* 在无效地址执行时触发 UsageFault。
* 硬故障的相关寄存器:
* HFSR = 0x40000000
* FORCED = 1 - UsageFault 升级为 HardFault
* UFSR = 0x0002
* INVSTATE = 1 - 以无效状态执行指令
*/
static int _NoThumbFunc ( void ) {
int r ;
int ( * pF )( void );
pF = ( int ( * )( void )) 0x00100000 ; // 0x00100000-0x07FFFFFF 在 STM32F4 上保留
// F44F1380 mov.w r3, #0x00100000 <- 请注意,位 [0] 未设置。
r = pF ();
// 4798 blx r3 <- 分支交换,模式更改为 ARM,但 Cortex-M 仅支持 Thumb 模式。
return r ;
}
被零除
/****************************************************************************
*
* _DivideByZero()
*
* 函数说明
* 通过除以零触发 UsageFault 或 HardFault。
*
* 附加信息
* 在除法指令上立即触发 UsageFault。
* 硬故障上的相关寄存器:
* HFSR = 0x40000000
* FORCED = 1 - UsageFault 升级为 HardFault
* UFSR = 0x0200
* DIVBYZERO = 1 - 除以零故障
*/
static int _DivideByZero ( void ) {
int r ;
volatile unsigned int a ;
volatile unsigned int b ;
a = 1 ;
// 2301 movs r3, #1 <- Load divided into a decimal error
b = 0 ;
// 2300 movs r3, #0 <- 加载除数
r = a / b ;
// FBB2F3F3 udiv r3, r2, r3 <- 除以 0 引发故障异常
return r ;
}


4903

被折叠的 条评论
为什么被折叠?



