STM32 F4xx Fault 异常错误定位指南

摘要:
在访问、数据读/写、中断变量获取和进入/退出中断期间,在寄存器堆栈操作(堆栈入/堆栈出)期间检测到内存访问错误。如果上述总线故障、内存管理故障和使用故障处理程序无法执行(例如,总线故障、存储器管理故障和利用故障异常被禁用或这些异常处理程序中出现新的故障),则将触发硬故障。这里,我们将在系统生成异常时重复MCU的处理:

STM32 F407 采用 Cortex-M4 的内核,该内核的 Fault 异常可以捕获非法的内存访问和非法的编程行为。Fault异常能够检测到以下几类非法行为:

  • 总线 Fault: 在取址、数据读/写、取中断变量、进入/退出中断时寄存器堆栈操作(入栈/出栈)时检测到内存访问错误。
  • 存储器管理 Fault: 检测到内存访问违反了内存保护单元(MPU, MemoryProtection Unit)定义的区域。
  • 用法 Fault: 检测到未定义的指令异常,未对其的多重加载/存储内存访问。如果使能相应控制位,还可以检测出除数为零以及其他未对齐的内存访问。
  • 硬 Fault: 如果上述的总线 Fault、存储器管理 Fault、用法 Fault 的处理程序不能被执行(例如禁能了总线 Fault、存储器管理Fault、用法Fault 的异常或者在这些异常处理程序中又出现了新的Fault)则触发硬Fault。

为了解释所述的 Fault 中断处理程序的原理,这里重述一下当系统产生异常时 MCU 的处理过程:

  • 有一个压栈的过程,若产生异常时使用 PSP(进程栈指针),就压入到 PSP 中,若产生异常时使用MSP(主栈指针),就压入MSP 中。
  • 会根据处理器的模式和使用的堆栈,设置 LR 的值(当然设置完的LR 的值再压栈)。
  • 异常保存,硬件自动把 8 个寄存器的值压入堆栈(8 个寄存器依次为 xPSR、PC、LR、R12以及 R3~R0)。如果异常发生时,当前的代码正在使用PSP,则上面8 个寄存器压入PSP; 否则就压入MSP。

当系统产生异常时,我们需要两个关键寄存器值,一个是 PC ,一个是 LR (链接寄存器),通过 LR找到相应的堆栈,再通过堆栈找到触发异常的PC 值。将产生异常时压入栈的 PC 值取出,并与反汇编的代码对比就能得到哪条指令产生了异常。
  这里解释一下关于 LR 寄存器的工作原理。如上所述,当 Cortex-M4 处理器接受了一个异常后,寄存器组中的一些寄存器值会被自动压入当前栈空间里,这其中就包括链接寄存器(LR )。这时的 LR 会被更新为异常返回时需要使用的特殊值(EXC_RETURN)。关于EXC_RETURN 的定义如下,其为 32 位数值,高 28 位置 1,第 0 位到第三位则提供了异常返回机制所需的信息,如下表所示。可见其中第 2 位标示着进入异常前使用的栈是 MSP还是PSP。在异常处理过程结束时,MCU 需要根据该值来分配 SP 的值。这也是本方法中用来判断所使用堆栈的原理,其实现方法可以从后面_init_hardfault_isr 中看到。

  异常处理流程:
首先要定义异常处理函数,在M4和M3核中,这两个是一样的,可以直接在stm32_f4xx.s中定义:

.cpu cortex-m3
.thumb

.global HardFault_Handler
.extern hard_fault_handler_c

HardFault_Handler:
TST LR, #4
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
B hard_fault_handler_c

  这里有几个命令要说明一下含义: TST 是Bit级别的与操作。ITE 是 MRSEQ和MRSNE都是两个命令的合体,分别可以拆开成:MRS,EQ和MRS,NE,分别的意思是如果两者相等,则把MSP的值赋值到R0,如果R0和PSP不等,则把PSP赋植到R0.ITE读为 if-then-else

  关于HardFault_Handler 这个函数,一般在stm32_f4xx.s的中断向量表中,我的系统中的代码如下所示:

  

  g_pfnVectors:
  .word  _estack
  .word  Reset_Handler
  .word  NMI_Handler
  .word  HardFault_Handler
  .word  MemManage_Handler
  .word  BusFault_Handler
  .word  UsageFault_Handler

  接下来就是整个流程的代码实现:

/ hard fault handler in C,
// with stack frame location as input parameter
void hard_fault_handler_c (unsigned int * hardfault_args)
{
unsigned int stacked_r0;
unsigned int stacked_r1;
unsigned int stacked_r2;
unsigned int stacked_r3;
unsigned int stacked_r12;
unsigned int stacked_lr;
unsigned int stacked_pc;
unsigned int stacked_psr;

stacked_r0 = ((unsigned long) hardfault_args[0]);
stacked_r1 = ((unsigned long) hardfault_args[1]);
stacked_r2 = ((unsigned long) hardfault_args[2]);
stacked_r3 = ((unsigned long) hardfault_args[3]);

stacked_r12 = ((unsigned long) hardfault_args[4]);
stacked_lr = ((unsigned long) hardfault_args[5]);
stacked_pc = ((unsigned long) hardfault_args[6]);
stacked_psr = ((unsigned long) hardfault_args[7]);

printf ("

[Hard fault handler - all numbers in hex]
");
printf (“R0 = %x
”, stacked_r0);
printf (“R1 = %x
”, stacked_r1);
printf (“R2 = %x
”, stacked_r2);
printf (“R3 = %x
”, stacked_r3);
printf (“R12 = %x
”, stacked_r12);
printf (“LR [R14] = %x subroutine call return address
”, stacked_lr);
printf (“PC [R15] = %x program counter
”, stacked_pc);
printf (“PSR = %x
”, stacked_psr);
printf (“BFAR = %x
”, (*((volatile unsigned long )(0xE000ED38))));
printf (“CFSR = %x
”, (((volatile unsigned long )(0xE000ED28))));
printf (“HFSR = %x
”, (((volatile unsigned long )(0xE000ED2C))));
printf (“DFSR = %x
”, (((volatile unsigned long )(0xE000ED30))));
printf (“AFSR = %x
”, (((volatile unsigned long *)(0xE000ED3C))));
printf (“SCB_SHCSR = %x
”, SCB->SHCSR);

while (1);
}

/* hard fault interrupt handler */
void _int_hardfault_isr( )
{
__asm(“TST LR, #4”);
__asm(“ITE EQ”);
__asm(“MRSEQ R0,MSP”);
__asm(“MRSNE R0,PSP”);
__asm(“B hard_fault_handler_c”);
}

void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
DEBUG_ERR(" hard fault handler ");
_int_hardfault_isr();
while (1)
{
}
}

  上面的这些代码,一般的工程师就可以看懂了,就不多做介绍了,假如你有啥这方面的问题,欢迎交流和沟通,反正是我的板子可以正常使用这些代码了。

参考文档:

https://community.arm.com/cn/b/blog/posts/3-thumb-2

免责声明:文章转载自《STM32 F4xx Fault 异常错误定位指南》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇兼容pc端和移动端的轮播图插件 swiper.jsMySQL中多表删除方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

icmp 流量抓取 转发 代理(2)

客户端C到服务器S的icmp包经过本机P时被截获,在上一篇中已经介绍了如何获取原始目的地址,你必须将数据转发到原始目的地址S,并且在收到从原始目的地址的响应之后转发给客户端。此时,要实现透明代理,则你返回给客户端的icmp响应的源地址必须为客户端请求的原始目的地址S。由于使用的是raw socket,无法用IP_TRANSPARENT的socket选项绑定...

C# Winform利用POST传值方式模拟表单提交数据(Winform与网页交互)

其原理是,利用winfrom模拟表单提交数据。将要提交的參数提交给网页,网页运行代码。得到数据。然后Winform程序将网页的全部源码读取下来。这样就达到windows应用程序和web应用程序之间传參和现实数据的效果了。 ­     首先创建一个windows应用程序和web应用程序。 ­     在web应用程序中,将网页切换到源码并把源码中一些无用的...

vscode 中使用php-cs-fixer和PHP Formatter 插件规范化PHP代码

什么是PHP-CS-Fixer?    它是php-fig组织定义的PHP代码规范,良好的代码规范可以提高代码可读性,团队沟通维护成本    使用它可以按照指定的规范格式化您的PHP代码,此工具不仅可以检测有不符合规范的代码,而且还可以修复它们     1.使用php-cs-fixer.phar格式化php文件         1.下载php-cs-f...

提高github下载速度的方法【100%有效】可达到2MB/s

转:https://blog.csdn.net/kcx64/article/details/83866633 在国内从github上面下载代码的速度峰值通常都是20kB/s。这种速度对于那些小项目还好,而对于大一些的并且带有很多子模块的项目来讲就跟耽误时间。虽然有很多提速的方法,但是实际用起来并不稳定。这里提供一种新的方法,下载速度可以达到 1~2MB/s...

深度解析C语言中的sizeof

1)解析C语言中的sizeof 一、sizeof的概念  sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。 二、sizeof的使用方法  1、用于数据类型   sizeof使用形式:...

VMP加壳(二):VMP的虚拟化原理

  介绍VMP虚拟化原理之前,先简单介绍一下计算机运行的原理。总所周知,现代计算机的核心部件是CPU、内存、磁盘、键盘、显示器等;最最最核心的就属CPU、内存和磁盘了。用户按开机键,CPU会把OS从磁盘加载到内存运行。由于CPU只能识别并执行二进制文件,所以代码、数据等都是以二进制存放在磁盘和内存的。        1、为了在软件层面“虚拟化”出底层的硬件...