stm32死机问题的处理

STM32出现HardFault_Handler故障的原因主要有两个方面:

1、内存溢出或者访问越界。这个需要自己写程序的时候规范代码,遇到了需要慢慢排查。

2、堆栈溢出。增加堆栈的大小。

 

出现问题时排查的方法:

1、发生异常之后可首先查看LR寄存器中的值,确定当前使用堆栈为MSP或PSP,然后找到相应堆栈的指针,并在内存中查看相应堆栈里的内容。由于异常发生时,内核将R0~R3、R12、Return address、PSR、LR寄存器依次入栈,其中Return address即为发生异常前PC将要执行的下一条指令地址,因此在堆栈中反数第三个字即为出错位置。

2、默认的HardFault_Handler处理方法是B .将它改成BX LR直接返回的形式。然后在这条语句打个断点,一旦在断点中停下来,说明出错了,然后再返回,就可以返回到出错的位置的下一条语句那儿。
这个有时候可能需要在反汇编模式下调试,因为可以是程序跑飞一会儿才出现HardFault_Handler。

 

3、还是将中断函数修改,打印中断时的一些信息:

HardFault_Hander()定义如下:

void HardFault_Handler(void)
{
  uint32_t r_sp ;

  r_sp = __get_PSP(); //获取SP的值
  PERROR(ERROR,Memory Access Error!);
  Panic(r_sp);
 
  while (1);
}

 

死机过程

基本概念:
连接寄存器LR:调动子程序时,自动存储下一次返回的地址,其实就是最近调用的那一次函数的地址。

死机的过程:

死机过程

这里我们最需要关注的是第一步入栈:
其中8个寄存器的顺序是
在这里插入图片描述

其中出现异常时LR里面的值是固定的
在这里插入图片描述
最后更新堆栈指针,我们根据最后使用的堆栈里面的内容,就可以知道出问题时的最后现场。

死机定位

思路简单来讲就是:
找到死机时候的lr寄存器,然后根据lr寄存器的值,找到此时压入的是psp堆栈,还是msp堆栈。然后根据堆栈里的内容(最后压入堆栈的8个寄存器的值)。其中压入到堆栈里面的return address这个值至关重要,这个是上一次,压入堆栈的最后一个函数,由此就可以定位出死机的位置。

使用keil环境直接debug定位

以实际的例子分析:
首先写一个能够使单片机死机的代码,debug跑起来:

故意使用空指针将程序跑死
在这里插入图片描述
在这里插入图片描述
debug的时候点开view,选择register,


在这里插入图片描述
根据LR的值判断,使用的是psp堆栈,然后打开memory windows,查看地址为0x20002FF0的内存数据,即为最后一次入栈的内容。右键选择,long显示

在这里插入图片描述

找到第六个0x08034a17这个地址,view,disassembly window这个窗口,查看反汇编文件,在反汇编窗口,右键,点击show disassembly at address ,输入地址,就可以找到对应汇编文件的位置,同时可以定位到c语言中对应的位置。
在这里插入图片描述
这样就可以定位到,死机之前的位置
2. 还有一种方式就是,通过看函数的调用关系,直接看到程序是死在那里的
点击 view call stack window ,直接可以看到,程序死机之前的函数调用关系
在这里插入图片描述
这样也可快速定位,原理其实是一样的,都是通过看堆栈数据,第一种方法只是自己手动的走了一遍这个流程而已。

完善的解决方案

因为在实际项目中死机问题很多都是难复现的,产品在使用过程中难以长期debug,导致以上的办法实际上不是很实用。因此,我们需要更加方便的解决办法。
思路:相当于给单片机做一个黑盒子,每当系统崩溃时。我们会记录,重要的寄存器以及堆栈信息,存在flash里面。这样一旦出现问题。我们只要重新debug一下,从flash里面将保存的信息读出来。这样难复现的死机问题,我们也是有办法锁定位置的。

  1. 修改.s文件
    将原来的HardFault_Handler 内容换成以上新的内容,hard_fault_handler_c 是一个函数,死机时就会跳转到这个函数。
HardFault_Handler PROC     
                IMPORT  hard_fault_handler_c 
                TST LR, #4  
                ITE EQ     
                MRSEQ R0, MSP 
                MRSNE R0, PSP 
                B  hard_fault_handler_c                
                ENDP 

选择一块区域作为死机时,信息存储的地方。

#define ADDR_FLASH_SECTOR_2     ((u32)0x08008000) 	//扇区2起始地址, 16 Kbytes 
#define SYS_CRASH_INFO_ADDR ADDR_FLASH_SECTOR_2    //记录系统死机时的重要信息   

定义死机存储信息的结构体

typedef struct {
    unsigned int crash_time;
    unsigned int is_crash;
    /* register info*/
    unsigned long stacked_r0;
    unsigned long stacked_r1;  
    unsigned long stacked_r2; 
    unsigned long stacked_r3;    
    unsigned long stacked_r12;  
    unsigned long stacked_lr;  
    unsigned long stacked_pc;  
    unsigned long stacked_psr;  
    unsigned long SHCSR;  
    unsigned long MFSR;  
    unsigned long BFSR;   
    unsigned long UFSR;  
    unsigned long HFSR;  
    unsigned long DFSR;  
    unsigned long MMAR;  
    unsigned long BFAR;
} System_Crash_Info;
void hard_fault_handler_c(unsigned int * hardfault_args) 
{     
  static System_Crash_Info crash_info;
  memset(&crash_info, 0, sizeof(System_Crash_Info));
  
  crash_info.is_crash = 1;
  crash_info.crash_time = (unsigned int)HAL_GetTick();
  
  crash_info.stacked_r0 = ((unsigned long) hardfault_args[0]);  
  crash_info.stacked_r1 = ((unsigned long) hardfault_args[1]);  
  crash_info.stacked_r2 = ((unsigned long) hardfault_args[2]);  
  crash_info.stacked_r3 = ((unsigned long) hardfault_args[3]);  
  crash_info.stacked_r12 = ((unsigned long) hardfault_args[4]);    
  crash_info.stacked_lr = ((unsigned long) hardfault_args[5]);   
  crash_info.stacked_pc = ((unsigned long) hardfault_args[6]);  
  crash_info.stacked_psr = ((unsigned long) hardfault_args[7]); 

  crash_info.MFSR = (*((volatile unsigned char *)(0xE000ED28))); //存储器管理fault状态寄存器   
  crash_info.BFSR = (*((volatile unsigned char *)(0xE000ED29))); //总线fault状态寄存器   
  crash_info.UFSR = (*((volatile unsigned short int *)(0xE000ED2A)));//用法fault状态寄存器    
  crash_info.HFSR = (*((volatile unsigned long *)(0xE000ED2C)));  //硬fault状态寄存器     
  crash_info.DFSR = (*((volatile unsigned long *)(0xE000ED30))); //调试fault状态寄存器  
  crash_info.MMAR = (*((volatile unsigned long *)(0xE000ED34))); //存储管理地址寄存器  
  crash_info.BFAR = (*((volatile unsigned long *)(0xE000ED38))); //总线fault地址寄存器  
  
  u8 ret = STMFLASH_EraseSector(STMFLASH_GetFlashSector(SYS_CRASH_INFO_ADDR));
  u8 ret2 = STMFLASH_Write(SYS_CRASH_INFO_ADDR, (u32 *)(&crash_info), (3+sizeof(System_Crash_Info))/4);

  while (1);  
}

正常代码里加上

   System_Crash_Info crash_info = {0};
  crash_info = *(System_Crash_Info*)(SYS_CRASH_INFO_ADDR);
  if (crash_info.is_crash != 1) {
      STMFLASH_EraseSector(STMFLASH_GetFlashSector(SYS_CRASH_INFO_ADDR));
  } else {
      CT_PRINTF("code has ever crash\n");
      //这里查看之前死掉时的情况,或者是将crash_info 里的信息打印出来分析也可以
  }

实际的例子:
在这里插入图片描述
这样代码重新debug,就可以看到之前死机的情况了。直接看lr寄存器的值,在反汇编的代码里查看一下,就知道代码最后死在哪里了。

 

 

  • 9
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: STM32 死机(也称为硬件故障)是指STM32单片机在运行过程中出现了无法恢复的错误,并导致程序停止响应或崩溃的情况。硬件故障是由于程序运行时发生了非法指令、存储器读写错误、中断异常等原因引起的。 对于STM32硬件故障的分析,可以通过以下步骤来进行: 1. 异常向量表(NVIC)定位:硬件故障会导致程序跳转到异常处理器,通过查看异常向量表可以确定具体是哪个异常处理器被触发。 2. 寄存器值分析:查看产生硬件故障时相关寄存器的值,如程序计数器(PC)、堆栈指针(SP)等。这些值可以提供更多关于故障发生时的程序状态的信息。 3. 中断日志分析:如果硬件故障是由于中断异常引起的,可以通过打印中断日志来获取更多信息。中断日志包括中断号、中断源、中断服务例程等。 4. 调试工具使用:使用STM32开发板上的调试工具(如J-Link、ST-Link等)进行调试,可以通过断点、查看寄存器、查看存储器等功能,进一步分析硬件故障。 5. 代码回溯:通过查看产生硬件故障之前执行的代码,可以确定是否存在编程错误或者不合法操作,如访问空指针、溢出问题等。 在分析完硬件故障后,可以根据具体情况采取相应措施来修复问题。常见的修复措施包括修改代码错误、检查硬件连接、更新驱动程序等。同时,也推荐在开发过程中使用断言(assert)功能,及时捕获并处理潜在的硬件故障。 ### 回答2: STM32死机HardFault通常是由于程序错误或硬件故障引起的。下面是对STM32死机HardFault进行分析的常见步骤: 1. 排查硬件问题:确保硬件电源和时钟正常工作,并检查是否存在短路或其他硬件故障,例如引脚连接错误等。 2. 检查编程错误:查看代码中是否有潜在的错误,例如没有初始化变量、数组越界、栈溢出等。使用调试工具(例如GDB)可以更方便地检测程序中的错误。 3. 检查中断问题:HardFault中断可能是由于异常中断导致的。检查中断的优先级设置和中断服务程序是否正确。 4. 查看硬件异常:使用STM32芯片自带的硬件调试模块(例如Cortex-M3的Fault Status Register)可以查看硬件异常的状态。这些状态信息可以提供有关错误原因的线索。 5. 查看堆栈信息:查看堆栈的状态可以帮助我们了解在死机时程序执行的位置。这些信息可以通过调试工具或在程序中添加日志输出来获得。 6. 运行时错误检测:在程序中使用合适的错误检测方法,例如检测指针是否为空、检测函数返回值等,可以帮助我们在错误发生时及时发现问题。 7. 固件更新:如果已确定硬件问题和代码错误都不存在,则可能是芯片固件本身的问题。在这种情况下,更新芯片固件可能是解决问题的方法。 以上是对STM32死机HardFault进行分析的常见步骤,可以根据具体情况进行适当的调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值