完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
我们在开发嵌入式代码时,经常会遇到处理器无法启动或系统停止响应的情况,引起这种症状的原因可能有很多,其中一些可能是硬件问题(比如供电、时钟等),更常见的是处理器触发了Fault 异常,并停留在Fault 异常处理程序内循环(默认情况下,Fault handler 在启动代码中定义为死循环),我们如何分析产生Fault 异常的原因呢?一、产生Fault 异常的可能原因? 1.1 Fault 异常类别 为了方便我们分析程序产生Fault 的原因,ARM Cortex-M 提供了系统异常处理机制,当Fault 发生时会执行相应的Fault 异常处理程序,我们可以在Fault 异常处理程序中尝试定位或解决相应的Fault 异常。 Cortex-M 提供了哪些Fault 异常类型呢?我们可以从中断向量表中获知(参见博文:中断向量表): 从上表可知,Cortex-M 的Fault 异常主要分为四类(Debug monitor 异常用于前篇博文谈到的两种调试模式中调试监视器模式,也即当发生调试事件时,系统不暂停而是去执行DebugMonitor_Handler 程序):
Cortex-M 处理器是如何检测到系统出现Fault 异常的呢?当处理器进入Fault 异常处理程序后,我们如何判断系统是因为什么原因、在哪个地方引起的Fault 异常呢? 1.2 Fault 状态寄存器 嵌入式处理器的执行状态一般都是通过相应的寄存器展示的,Cortex-M 为了支持Fault 异常处理机制,也提供了一些状态寄存器及地址寄存器,状态寄存器的每一位表示一种Fault 异常原因,地址寄存器则提供发生Fault 的存储地址: Fault 状态寄存器表示的故障原因如下(方括号内数字表示相应寄存器的第几位,未列出的位为保留位,暂没有使用): // MMFSR: MemManage Fault Status Register (lowest byte in SCB->CFSR) [7] MMARVALID - 如果为1,则表示MMFAR寄存器存储的MemManageFault寻址值有效。 [5] MLSPERR - 如果为1,则表示惰性保存浮点状态时发生错误。 [4] MSTKERR - 如果为1,则表示在中断或异常入栈时企图访问不被允许的区域。 [3] MUNSTKERR - 如果为1,则表示在中断或异常出栈时企图访问不被允许的区域。 [1] DACCVIOL - 如果为1,则表示企图从不允许访问的区域读、写数据。 [0] IACCVIOL - 如果为1,则表示企图从不允许访问的区域取指令。 // MMFAR: MemManage Fault Address Register (0xE000ED34, SCB->MMFAR) [31:0] ADDRESS - 当MMARVALID 的值为1时,MMFAR 记录在访问哪个地址时发生了MemManage Fault。 // BFSR: Bus Fault Status Register (2nd byte in SCB->CFSR) [15] BFARVALID - 如果为1,则表示BFAR寄存器存储的BusFault 寻址值有效。 [13] LSPERR - 如果为1,则表示惰性保存浮点状态时发生错误。 [12] STKERR - 如果为1,则表示入栈时发生错误。 [11] UNSTKERR - 如果为1,则表示出栈时发生错误。 [10] IMPRECISERR - 如果为1,则表示不精确的数据总线错误。 [9] PRECISERR - 如果为1,则表示精确的数据总线错误。 [8] IBUSERR - 如果为1,则表示指令提取错误。 // BFAR: Bus Fault Address Register (0xE000ED38, SCB->BFAR) [31:0] ADDRESS - 当BFARVALID的值为1时,BFAR 记录在访问哪个地址时发生了Bus Fault。 // UFSR: Usage Fault Status Register (Upper half-word in SCB->CFSR) [25] DIVBYZERO - 如果为1,则表示企图执行除 0 操作。 [24] UNALIGNED - 如果为1,则表示企图执行非对齐访问。 [19] NOCP - 如果为1,则表示企图执行不受支持的协处理器指令。 [18] INVPC - 如果为1,则表示将非法或无效的EXC_RETURN值加载到PC。 [17] INVSTATE - 如果为1,则表示试图切换到 ARM 状态。 [16] UNDEFINSTR - 如果为1,则表示企图执行未定义指令。 // HFSR: Hard Fault Status Register (0xE000ED2C ,SCB->HFSR) [31] DEBUGEVT - 如果为1,则表示发生了一个调试事件。 [30] FORCED - 如果为1,则表示该Hard Fault 是由MemManage Fault、Bus Fault 或Usage Fault 引起的。 [1] VECTTBL - 如果为1,则表示取中断向量时出错。 // DFSR: Debug Fault Status Register (0xE000ED30 ,SCB->DFSR) [4] EXTERNAL - 如果为1,则表示因外部调试请求触发了调试事件。 [3] VCATCH - 如果为1,则表示因发生向量捕获触发了调试事件。 [2] DWTTRAP - 如果为1,则表示因数据监测点匹配触发了调试事件。 [1] BKPT - 如果为1,则表示因执行BKPT 指令触发了调试事件。 [0] HALTED - 如果为1,则表示调试器请求处理器进入暂停模式。 // AFSR: Auxiliary Fault Status Register (0xE000ED3C, SCB->AFSR) [31:0] Implementation Defined - 允许芯片设计人员添加自己的故障状态信息。 - 允许芯片设计人员添加自己的故障状态信息。 了解了各类Fault 状态寄存器每一位指示的fault 原因,我们就可以在程序进入fault 异常处理程序后,通过查看上述Fault 状态寄存器哪一位被置为 1 了,了解到产生fault 的可能原因。我们如何定位产生fault 的具体代码呢? 1.3 Fault 异常初始化配置 在回答这个问题前,我们还有个问题,这些fault 寄存器及其异常处理程序在使用前需要怎么初始化呢? 我们在使用中断或异常之前,通常需要先为其配置优先级并使能中断,处理器才会处理并响应对应的中断信号,前面介绍的四种 fault 异常只有Hard Fault 是固定优先级-1,默认开启,其余三种都是默认未开启状态。如果我们想让MemManage Fault、Bus Fault、Usage Fault、Debug Fault 异常处理程序可以正常响应,需要先为其配置优先级并使能对应的异常,配置方法如下(如果上述异常未配置或未开启,处理器也会上访为Hard Fault,去执行Hard Fault Handler): #define SCB_BASE 0xE000ED00UL /*!< System Control Block Base Address */ /* Structure type to access the System Control Block (SCB). */ typedef struct { ...... __IOM uint32_t CCR; /*!< Offset: 0x014 (R/W) Configuration Control Register */ __IOM uint8_t SHP[12U]; /*!< Offset: 0x018 (R/W) System Handlers Priority Registers (4-7, 8-11, 12-15) */ __IOM uint32_t SHCSR; /*!< Offset: 0x024 (R/W) System Handler Control and State Register */ __IOM uint32_t CFSR; /*!< Offset: 0x028 (R/W) Configurable Fault Status Register */ __IOM uint32_t HFSR; /*!< Offset: 0x02C (R/W) HardFault Status Register */ __IOM uint32_t DFSR; /*!< Offset: 0x030 (R/W) Debug Fault Status Register */ __IOM uint32_t MMFAR; /*!< Offset: 0x034 (R/W) MemManage Fault Address Register */ __IOM uint32_t BFAR; /*!< Offset: 0x038 (R/W) BusFault Address Register */ __IOM uint32_t AFSR; /*!< Offset: 0x03C (R/W) Auxiliary Fault Status Register */ ...... } SCB_Type; /* Enable the MemManage Fault exception handler */ void MemManage_Handler(void); // 实现MemManage Fault 异常处理函数 NVIC_SetPriority(MemoryManagement_IRQn, priority); // 配置MemManage Fault 中断优先级,实际上配置的是SCB->SHP 寄存器 SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; // 使能MemManage Fault 中断或异常, 实际上设置SCB->SHCSR 的bit 16 为 1 /* Enable the Bus Fault exception handler */ void BusFault_Handler(void); // 实现Bus Fault 异常处理函数 NVIC_SetPriority(BusFault_IRQn, priority); // 配置Bus Fault 中断优先级,实际上配置的是SCB->SHP 寄存器 SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA_Msk; // 使能Bus Fault 中断或异常, 实际上设置SCB->SHCSR 的bit 17 为 1 /* Enable the Usage fault exception handler */ void UsageFault_Handler(void); // 实现Usage fault 异常处理函数 NVIC_SetPriority(UsageFault_IRQn, priority); // 配置Usage fault 中断优先级,实际上配置的是SCB->SHP 寄存器 SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk; // 使能Usage fault 中断或异常, 实际上设置SCB->SHCSR 的bit 18 为 1 /* Hard Fault 异常默认开启,固定优先级 -1,根据需要实现HardFault_Handler */ void HardFault_Handler(void); // 默认的HardFault_Handler 为死循环,根据需要实现对应的Hard Fault 异常处理函数 // 设置寄存器SCB->CCR 的bit 3 为 1,启用捕获非对齐访问故障 了解了如何初始化或配置Fault 中断或异常,再回头考虑如何分析定位Fault 异常呢? 二、如何分析Fault 异常? 在嵌入式软件开发过程中遇到Fault 异常的情况并不少见,大多数情况下,我们可以从fault 状态和地址寄存器得知处理器发生了哪种异常?可能是由什么原因导致的?接下来我们怎么分析定位产生fault 的具体代码呢? 从前篇博文ARM Debug and Trace可知,我们可以通过Debug Port 和Trace Port 获得处理器运行的调试信息和跟踪信息,借助这些调试跟踪信息,我们分析fault 可以有三个方向:
Cortex-M 默认MemManage Fault、Bus Fault、Usage Fault 未开启,仅Hard Fault 默认开启,如果没有启用前三种fault,处理器发生任何fault 都会进入HardFault_Handler。 HardFault_Handler 函数默认是死循环,我们在调试模式下执行到该函数内手动暂停,或者在该函数内添加断点,就可以查看发生Fault 时寄存器和内存的值,也可以从Fault status 寄存器值获知发生了哪种fault,以及产生该fault 的大概原因。 对于Keil MDK,进入调试界面后,我们可以通过点击菜单Peripherals–> Core Peripherals–> Fault Reports 即可查看fault 相关寄存器的值。我们要跟踪Call Stack,首先要确定发生fault 时处理器正在使用哪个堆栈(MSP 还是PSP)?当Cortex-M 进入中断或异常处理程序后,寄存器LR 的值会更新为EXC_RETURN,我们可以通过EXC_RETURN 值判断当前正在使用MSP 还是PSP? 我们从EXC_RETURN 的bit 2 的值可以判断处理器当前使用的是MSP 还是PSP。然后在查找内存中SP 指针指向的位置,就可以获得进入HardFault_Handler 前的栈帧数据,按照寄存器入栈顺序,就可以获得每个寄存器的值,特别是PC 和LR 的值。 我们从Stack Frame 中获取的PC 值即为进入HardFault_Handler 前执行的指令地址,可从反汇编代码窗口查看该地址对应的汇编指令和C语言指令是哪条?Stack Frame 中LR 的值则表示PC 指向指令所在函数的返回地址,或者借助IDE 或Ozone 中的Call Stack 信息,可以得知发生Fault 前的函数调用链,方便我们快速定位产生fault 的代码行。 这些文字描述不够直观,下文通过示例程序展示分析fault 的过程。 三、分析Fault 示例 3.1 如何借助IDE 或Ozone 分析fault? 本文依然使用前篇博文的示例工程.nRF5_SDK_17.0.2_d674ddeexamplesble_peripheralble_app_uart,为了触发Fault 异常,我们在main 函数中添加点代码如下: // .nRF5_SDK_17.0.2_d674ddeexamplesble_peripheralble_app_uartmain.c ...... /**@brief Function for Fault exception test. */ static int fault_test(void) { // Enable fault on divide-by-zero SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk; char txt[32] = "fault exception."; char pkg[32] = "fault"; volatile int txtlen, pkglen, count; memset(pkg, 0, sizeof(pkg)); txtlen = strlen(txt); pkglen = strlen(pkg); count = txtlen / pkglen; return count; } /**@brief Application main function. */ int main(void) { bool erase_bonds; // Initialize log log_init(); // Fault exception test fault_test(); ...... } Studio 点击Debug–>Go 进入调试模式,点击continue execution,代码会执行到函数HardFault_Handler 内,该函数内部只有一条原地跳转命令"b .",点击break 暂停执行。我们可以从右边registers 1 窗口的Groups 内开启SCB 寄存器组,查看跟fault 相关的寄存器值如下: 从上图可以看出,fault 相关寄存器只有HFSR->FORCED 和UFSR->DIVBYZERO 值为 1,说明该fault 是由除零操作引起的。 再查看进入HardFault_Handler 前的栈帧数据,LR 的当前值为0xFFFFFFF9 说明使用的是MSP 栈指针。SEGGER Embedded Studio 提供了CPU-Current Context 和CPU 两个寄存器组,从CPU 寄存器组可以看到MSP和PSP 的值,CPU-Current Context 寄存器组则会自动判断并显示当前使用的实际值,我们获知MSP 值为0x2000FF70。 在memory 1 窗口内通过MSP 栈指针值查询对应的栈帧数据,根据寄存器固定的入栈顺序(栈从高地址向低地址增长),可以查得PC 值为0x0002FAF4。在Disassembly 窗口内通过PC 值查得对应的汇编指令为“sdiv r3, r2, r3”,对应到C 语言代码行“count = txtlen / pkglen;”。结合定位到的C 代码上下文和前面分析的引起fault 原因(除零操作),可以很容易分析出除数pkglen 为0。 现在的IDE 调试越来越方便了,我们可以点击按钮Up One Stack Frame直接查看前一个栈帧执行的汇编指令和C 代码行,比前面的方法方便很多(前一种方法便于我们了解分析fault 的步骤),而且还可以继续往前查看函数调用栈,获知发生fault 前的函数调用链,可以帮我们快速定位产生fault 的代码行。 如果觉得SEGGER Embedded Studio 查询fault 相关寄存器的值比较麻烦,可以使用Ozone 调试工具。我们通过Debug-> Debug With Ozone 打开Ozone 调试软件,点击Download & Reset Program 按钮开始调试,系统会执行到main 函数起始位置暂停。我们继续点击Resume program execution,遇到fault exception 会自动弹出如下窗口: 借助Ozone 分析fault exception 更方便些,遇到fault 自动弹出注释界面,fault 相关寄存器每一位表示什么意思也注释的很详细,不需要我们再去查手册了,能提高不少分析fault 的效率。 3.2 如何设计fault handlers 输出fault log? 当我们开发的嵌入式代码运行出现bug,经常通过log 日志先判断产生bug 的软件模块或大概原因。对于fault 异常只是表现为系统停止运行,陷入到HardFault_Handler 的死循环中,并没有相关log 输出,不方便我们及时判断出是出现了fault exception。我们应该怎么设计fault handlers,让系统发生fault 时通过log 输出fault log 关键信息呢? 我们依然使用前面的示例工程,可以在Project Explorer 窗口看到源文件hardfault_implementation.c,从名字就可以看出该文件是实现fault handler 输出fault log 信息的。我们可以看到该文件的实现代码如下: // .nRF5_SDK_17.0.2_d674ddecomponentslibrarieshardfaulthardfault_implementation.c #include "sdk_common.h" #if NRF_MODULE_ENABLED(HARDFAULT_HANDLER) #include "hardfault.h" #include "nrf.h" #include "compiler_abstraction.h" #include "app_util_platform.h" #ifdef SOFTDEVICE_PRESENT #include "nrf_soc.h" #endif #define NRF_LOG_MODULE_NAME hardfault #include "nrf_log.h" #include "nrf_log_ctrl.h" NRF_LOG_MODULE_REGISTER(); /*lint -save -e14 */ __WEAK void HardFault_process(HardFault_stack_t * p_stack) { // Restart the system by default NVIC_SystemReset(); } /*lint -restore */ void HardFault_c_handler(uint32_t * p_stack_address) { NRF_LOG_FINAL_FLUSH(); #if (__CORTEX_M == 0x04) #ifndef CFSR_MMARVALID #define CFSR_MMARVALID (1 << (0 + 7)) #endif #ifndef CFSR_BFARVALID #define CFSR_BFARVALID (1 << (8 + 7)) #endif HardFault_stack_t * p_stack = (HardFault_stack_t *)p_stack_address; static const char *cfsr_msgs[] = { [0] = "The processor has attempted to execute an undefined instruction", [1] = "The processor attempted a load or store at a location that does not permit the operation", [2] = NULL, [3] = "Unstack for an exception return has caused one or more access violations", [4] = "Stacking for an exception entry has caused one or more access violations", [5] = "A MemManage fault occurred during floating-point lazy state preservation", [6] = NULL, [7] = NULL, [8] = "Instruction bus error", [9] = "Data bus error (PC value stacked for the exception return points to the instruction that caused the fault)", [10] = "Data bus error (return address in the stack frame is not related to the instruction that caused the error)", [11] = "Unstack for an exception return has caused one or more BusFaults", [12] = "Stacking for an exception entry has caused one or more BusFaults", [13] = "A bus fault occurred during floating-point lazy state preservation", [14] = NULL, [15] = NULL, [16] = "The processor has attempted to execute an undefined instruction", [17] = "The processor has attempted to execute an instruction that makes illegal use of the EPSR", [18] = "The processor has attempted an illegal load of EXC_RETURN to the PC, as a result of an invalid context, or an invalid EXC_RETURN value", [19] = "The processor has attempted to access a coprocessor", [20] = NULL, [21] = NULL, [22] = NULL, [23] = NULL, [24] = "The processor has made an unaligned memory access", [25] = "The processor has executed an SDIV or UDIV instruction with a divisor of 0", }; uint32_t cfsr = SCB->CFSR; if (p_stack != NULL) { // Print information about error. NRF_LOG_ERROR("HARD FAULT at 0x%08X", p_stack->pc); NRF_LOG_ERROR(" R0: 0x%08X R1: 0x%08X R2: 0x%08X R3: 0x%08X", p_stack->r0, p_stack->r1, p_stack->r2, p_stack->r3); NRF_LOG_ERROR(" R12: 0x%08X LR: 0x%08X PSR: 0x%08X", p_stack->r12, p_stack->lr, p_stack->psr); } else { NRF_LOG_ERROR("Stack violation: stack pointer outside stack area."); } if (SCB->HFSR & SCB_HFSR_VECTTBL_Msk) { NRF_LOG_ERROR("Cause: BusFault on a vector table read during exception processing."); } for (uint32_t i = 0; i < sizeof(cfsr_msgs) / sizeof(cfsr_msgs[0]); i++) { if (((cfsr & (1 << i)) != 0) && (cfsr_msgs != NULL)) { NRF_LOG_ERROR("Cause: %s.", (uint32_t)cfsr_msgs); } } if (cfsr & CFSR_MMARVALID) { NRF_LOG_ERROR("MemManage Fault Address: 0x%08X", SCB->MMFAR); } if (cfsr & CFSR_BFARVALID) { NRF_LOG_ERROR("Bus Fault Address: 0x%08X", SCB->BFAR); } #if defined(DEBUG) NRF_BREAKPOINT_COND; #endif // defined (DEBUG) #endif // __CORTEX_M == 0x04 HardFault_process((HardFault_stack_t *)p_stack_address); } #endif //NRF_MODULE_ENABLED(HARDFAULT_HANDLER) 从上述代码可知,Nordic SDK 提供了函数HardFault_c_handler 来实现输出fault log 的功能,该功能可以通过宏HARDFAULT_HANDLER 启用。我们先在sdk_config.h 文件中开启宏HARDFAULT_HANDLER_ENABLED : // .nRF5_SDK_17.0.2_d674ddeexamplesble_peripheralble_app_uartpca10040s132configsdk_config.h ...... // HARDFAULT_HANDLER_ENABLED - hardfault_default - HardFault default handler for debugging and release |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1602 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1536 浏览 1 评论
967 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
680 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1579 浏览 2 评论
1860浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
640浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
513浏览 3评论
526浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
500浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-19 07:33 , Processed in 0.804790 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号