stm32死机问题的处理

发布者:不懂之人最新更新时间:2018-12-28 来源: eefocus关键字:stm32  死机问题 手机看文章 扫描二维码
随时随地手机看文章

死机过程


基本概念:


连接寄存器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里面将保存的信息读出来。这样难复现的死机问题,我们也是有办法锁定位置的。


修改.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寄存器的值,在反汇编的代码里查看一下,就知道代码最后死在哪里了。

关键字:stm32  死机问题 引用地址:stm32死机问题的处理

上一篇:关于STM32的IAP与APP互相跳转常见问题分析
下一篇:STM32F103对于固件库V3.5.0打开和关闭总中断

推荐阅读最新更新时间:2024-03-16 16:21

STM32的PWM输入模式设置并用DMA接收数据
环境: 主机:WIN7 开发环境:MDK4.72 MCU:STM32F103 说明: 项目中需要进行红外学习,如果采用输入捕获的方式,因为定时器只能捕获上升沿或者下降沿,所以只能获得周期,而不能得到具体的红外波的高低电平的时间.所以采用PWM输入的方式进行捕获. 采用的是PA8脚,对应TIM1的通道1. 源代码: /********************************************************************* * 函数 ***********************************************************
[单片机]
STM32学习笔记——使用函数库编程控制GPIO口输出
看了网上许多人的代码以及各类开发板所带的例程,大多数使用的都是官方发布的函数库来编程,通过查询后发现,使用函数库来编程可以简化开发过程,并不需要追溯到各个寄存器,通过查看库手册,新手也可以快速应用STM32,因此,决定先从函数库开始入门! 1. 建立带函数库的IAR项目工程 先从网上下载3.5版(据说3.0版以后的固件库才逐渐稳定)stm32固件库(stm32f10x_stdperiph_lib)。由于与固件库版本兼容问题,重新下载安装了IAR6.30版。 1.1 创建项目文件夹 project ; 1.2 解压 stm32f10x_stdperiph_lib.rar 后, 将...stm32f10x_stdperi
[单片机]
<font color='red'>STM32</font>学习笔记——使用函数库编程控制GPIO口输出
STM32(Cortex-M3)启动过程+IAR中xcl及icf文件详解
一:STM32(Cortex-M3)启动过程(入口地址) ARM7和ARM9启动时从绝对地址0X00000000开始执行复位中断程序,即固定了复位后的起始地址,但中断向量表的位置是可变的。 Cortex-M3内核规定中断向量表中第一个32位数据内容为栈顶地址,第二个32位数据内容则是复位中断向量的入口地址。 这样CPU复位后会自动从中断向量表中第二个32位数据中取出复位中断向量的入口地址,PC就跳转到中断服务程序。这也就是为什么调试的时候程序会直接跳到0x08000144(中断向量表中第二个32位数据为0x08000145),而不是停在0x08000000。 Cortex-M3的中断向量表的结构是固定的,而位置的地址是可
[单片机]
STM32高级开发(16)-CMSIS DAP调试工程
最近公司的项目在等供应商的设备有点空闲的时间了,就折腾了下ARM官方开源的CMSIS DAP调试器的方案,用的是X893大神的方案,下面附上他的个人主页和在GitHub上的项目链接(我是用的是其中stlinkv2.1的软硬件方案): (http://akb77.com/g/stm32/cmsis-dap-adapter/) (https://github.com/x893/CMSIS-DAP) 这个调试器方案可以说极具性价比,SWD接口速度可以达到10M的全速,还附带一个最高支持到115200bps的串口,而且连接一根线就可以识别为两个设备,既可以单独的作为一个调试器使用,也可以集成到其他项目的PCB板上作为板载调试器和
[单片机]
<font color='red'>STM32</font>高级开发(16)-CMSIS DAP调试工程
STM32单片机----外部中断EXTI学习
可屏蔽中断和不可屏蔽中断: 按照是否可以被屏蔽,可将中断分为两大类:不可屏蔽中断(又叫非屏蔽中断)和可屏蔽中断。不可屏蔽中断源一旦提出请求,CPU必须无条件响应,而对可屏蔽中断源的请求,CPU可以响应,也可以不响应。CPU一般设置两根中断请求输入线:可屏蔽中断请求INTR(Interrupt Require)和不可屏蔽中断请求NMI(NonMaskable Interrupt)。对于可屏蔽中断,除了受本身的屏蔽位控制外,还都要受一个总的控制,即CPU标志寄存器中的中断允许标志位IF(Iinterrupt Flag)的控制,IF位为1,可以得到CPU的响应,否则,得不到响应。 GPIO口与EXTI外中断的映像图:
[单片机]
<font color='red'>STM32</font>单片机----外部中断EXTI学习
STM32使用串口中断发送和接受数据
这里用到的芯片为STM32F103,使用中断进行串口的接受和发送数据。 这里使用直接操作寄存器的方式,使用库函数和这个类似,只是改为调用响应的库函数即可。 配置串口和复用的IO口 void uart_init(u32 pclk2,u32 bound) { float temp; u16 mantissa; u16 fraction; temp=(float)(pclk2*1000000)/(bound*16);//分频 mantissa=temp; //分频得到的整数部分 fraction=(temp-mantissa)*16; //小数部分 mantissa =4;
[单片机]
STM32串口协议概念及结构体初始化详解+串口点灯+串口收发
一、串口通信协议简介 物理层:规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。其实就是硬件部分。 协议层:协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。其实就是软件部分。 1.RS232标准 2.USB转串口 3.原生的串口到串口 二、初始化结构体解 1.USART初始化结构体 2.同步时钟初始化结构体 3.编程时需要用到的固件库函数 三、串口点灯代码实现 1.USART .C文件 #include bsp_usart.h void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStructure;
[单片机]
<font color='red'>STM32</font>串口协议概念及结构体初始化详解+串口点灯+串口收发
STM32的基本系统是怎样的?
电源 无论是否使用模拟部分和AD部分,MCU外围出去VCC和GND,VDDA、VSSA、Vref(如果封装有该引脚)都必需要连接,不可悬空 对于每组对应的VDD和GND都应至少放置一个104的陶瓷电容用于滤波,并接该电容应放置尽量靠近MCU 用万用表测试供电电压是否正确,调试时最好用数字电源供电,以便过压或过流烧坏板子,电压最好一步一步从进线端测试到芯片供电端 复位、启动选择 Boot引脚与JTAG无关。其仅是用于MCU启动后,判断执行代码的起始地址 在电路设计上可能Boot引脚不会使用,但要求一定要外部连接电阻到地或电源,切不可悬空;STM32三种启动模式对应的存储介质均是芯片内置的,它们是: 用户闪存 = 芯片内置的Fla
[单片机]
<font color='red'>STM32</font>的基本系统是怎样的?
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
热门活动
换一批
更多
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

更多精选电路图
换一换 更多 相关热搜器件
更多每日新闻
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved