客户在使用 STM32G070 的时候,KEIL MDK 为编译工具,当编译优化选项设置为Level0 的时候,程序会出现 Hard Fault 异常,而当编译优化选项设置为 Level1 的时候,则程序运行正常。
表面上看,这似乎是 KEIL MDK 的问题,通过分析,导致这个问题的本质原因是内存地址没有对齐引起的,下面章节将详细分析该问题的来龙去脉以及解决方法。
根据客户的反馈,引起问题的代码很简单,客户定义了几个全局数组,在主程序中访问这几个数组就会出现 Hard Fault 异常,参考代码如下。
把客户提供的代码片段移植到 NUCLEO-G070RB 开发板上,问题很容易就复现了,代码本身功能简单,写法上也没有错误,所以从代码片段本身上看,无法确定问题出在哪里,通过 KEIL 调试器,在汇编窗口单步调试下,最终发现导致 HardFault 异常的语句为下图所示语句。
根据单步调试得知出现问题的语句为 LDR 指令,参考 Cortex M0 编程手册 PM0223 得知 LDR 指令的作用是从内存地址中加载一个 WORD 数据到目的寄存器 Rt 中,其中内存地址根据 Rn 或者 SP 寄存器的值以及立即数 imm 得到。
根据指令的描述,使用 LDR 指令的时候,通过 Rn 和 imm 计算得到的内存地址必须是读取字节数的倍数,LDR 每次读取一个 WORD,所以使用 LDR 指令时,内存地址必须 4字节对齐。如果地址没有对齐,则会导致 HardFault 异常。
结合 LDR 指令的描述,在调试状态下,通过查看寄存器值,图 2 出错语句中根据 Rn和 imm 计算得到的内存地址为 R0=0x2000000B,imm=4 所以内存地址为 0x2000000F,很显然这个地址不是 4 字节对齐的。
而当我们改变编译优化选项为 Level1 时,得到的内存地址为R0=0x20000000,imm=0x04 显然这个地址是按照 4 字节对齐的,所以这种情况下是不会出现 HardFault 异常的,印证了客户的问题现象。
通过上一节的分析,明确了导致该问题的本质原因是内存地址没有对齐,这个内存地址实际上是代码中定义的全局变量 g_curPlaySound_app 指向的地址,也就是全局数组变量 SoundFile 的地址,在编译器不同的优化选项下,分配给 SoundFile 变量的地址是不一样的,在本案例中,编译优化选项 Level0 条件下,SoundFile 分配的地址没有按照WORD 对齐,而在优化选项 Level1 条件下,SoundFile 分配的地址是 WORD 对齐,所以在两种优化选项下,出现了不一样的运行结果。
所以要保证程序不出错,当通过指针访问变量的时候,要确保指针指向的地址是 4 字节对齐的,在 Keil 环境下,可以通过__attribute__((aligned (4))) 关键字实现,如下图所示,通过该关键字,对齐了地址,也就不会出现 HardFault 异常了。
地址未对齐是嵌入式系统中容易忽视的一个细节,忽视这点往往会导致一些奇怪的问题,所以在开发过程中,注意这些细节还是很有必要的。
关键字:HardFault 异常
引用地址:
工程师笔记|一个地址未对齐引起的 HardFault 异常
推荐阅读最新更新时间:2026-03-21 14:44
stm32 HardFault_Handler 异常的处理死机
在系统开发的时候,出现了HardFault_Handler硬件异常,也就是死机,尤其是对于调用了os的一系统,程序量大,检测堆栈溢出,以及数组溢出等,找了半天发现什么都没有的情况下,估计想死的心都有了。如果有些程序开始的时候一切没有问题,但是运行几个小时候,会发现死机了,搞个几天下来估计蛋都碎了一地吧。。。 一般来说运行操作系统 是以下几个问题 1.开始的时候给ucos分配的堆栈太小了,随着项目做多了,这类问题一般很容易解决 #define TASK_IO_SIZE 300 #define TASK_IO_PRIO 6 OS_STK TASK_IO_STK ; 比如修改300到 1000,做开发的时候 如果ram允许,尽量大些,免
[单片机]
关于stm32HardFault_Handler异常(死机)的处理
在系统开发的时候,出现了HardFault_Handler硬件异常,也就是死机,尤其是对 于调用了os的一系统,程序量大,检测堆栈溢出,以及数组溢出等,找了半天发现什么都没有的情况下,估计想死的心都有了。如果有些程序开始的时候一切没有问题,但是运行几个小时候,会发现死机了,搞个几天下来估计蛋都碎了一地吧。。。 一般来说运行操作系统 是以下几个问题 1.开始的时候给ucos分配的堆栈太小了,随着项目做多了,这类问题一般很容易解决 #define TASK_IO_SIZE 300 #define TASK_IO_PRIO 6 OS_STK TASK_IO_STK ; 比如修改300到 1000,做开发的时候 如果ram允许,尽量大些,
[单片机]
实战经验 | 移植 SBSFU 到 STM32G070 的过程
01 前言 客户使用 STM32G070RBT6 给海外用户开发产品,由于当地新需求,产品需要增加安全启动的功能。但是由于 X-Cube-SBSFU 包提供的示例中,只有基于 STM32G071 的示例。客户因此询问该怎么移植。本文将讲解这个移植过程。 02 基于STM32G070和STM32G071的SBSFU 实现差异 在正式讲解之前,我们首先来看一看 STM32G070 和 STM32G071 的 SBSFU 实现差异。 STM32G070 是一个 value line 产品,首先,我们要意识到,有一些安全特性,相比于STM32G071,它是没有的,比如:PCROP,BOOT_LOCK
[单片机]
ARM Cortex-M (STM32) HardFault调试指南
HardFault 是 ARM Cortex-M 处理器中的一种异常。当处理器遇到无法处理的错误,或者配置为处理特定类型错误(如总线错误、内存管理错误、用法错误)的异常处理程序被禁用,或者在处理这些特定错误的过程中又发生了其他错误时,就会触发 HardFault。它是一个“兜底”的异常,表明系统遇到了严重问题。 调试 HardFault 需要耐心和系统的方法。关键在于: 实现一个能捕获足够信息的 HardFault_Handler。 利用调试器获取故障状态寄存器和异常堆栈帧的值。 仔细解读这些值,特别是 CFSR, HFSR, MMFAR, BFAR 以及堆栈中的 PC。 结合反汇编和源代码,定位到触发故障的具体指令和代码
[单片机]
STM32如何定位HardFault错误,一种实用方法
在STM32微控制器的开发过程中,遇到HardFault错误(硬错误)是开发者经常面临的挑战。HardFault通常指示了严重的程序错误,如指针异常、内存访问冲突、堆栈溢出等,这些错误可能导致系统崩溃或不稳定。快速准确地定位并解决HardFault错误对于保证产品可靠性和缩短开发周期至关重要。本文将介绍几种实用的方法,帮助开发者在STM32平台上快速定位HardFault错误。 一、理解HardFault及其触发原因 HardFault是Cortex-M内核的一种异常类型,当内核检测到无法处理的错误时会触发此异常。常见的触发原因包括: 非法内存访问:如访问未初始化的指针、数组越界等。 数据总线错误:如尝试访问不允许的内
[单片机]
stm32调试,进入 HardFault_Handler
一、现象: 进入调试之后程序要不就进入void HardFault_Handler(void),要么就是进入void MemManage_Handler(void), 二、原因: cstack溢出。heap不够。 三、修改: stm32f10x_startup.s Stack_Size EQU 0x0001000 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size __initial_sp ;// ;// ;// Heap_Size EQU 0x00008000
[单片机]
STM32硬件错误HardFault_Handler的处理方法
在用Keil对STM32的程序进行仿真时程序有时会跑飞,停止仿真程序会停在HardFault_Handler函数里的死循环while(1)中。这说明STM32出现了硬件错误。 STM32出现硬件错误可能有以下原因: (1)数组越界操作; (2)内存溢出,访问越界; (3)堆栈溢出,程序跑飞; (4)中断处理错误; 遇到这种情况,可以通过以下2种方式来定位到出错代码段。 方法1: 1.1在硬件中断函数HardFault_Handler里的while(1)处打调试断点,程序执行到断点处时点击“STOP”停止仿真。 1.
[单片机]
STM32的HardFault_Handler问题调试方法
相信很多人在调试STM32的时候都遇到过HardFault_Handler错误,刚开始接触的人,肯定最怕这个错误,因为这个问题的原因却是不好查,我看到网上很多人都给出了比较好的调试方法,我再调试的时候也发现了一个较好的调试方法,拿出来跟大家分享一下: 1) 在HardFault_Handler函数中添加一个break语句,即: void HardFault_Handler(void) { while (1) { break; } } 调试的时候在break这一行设置一个断点,然后全速运行程序,等到出现HardFault_Handler错误的时候,程序就会停在break位置,然后选择F10或F11单步运行
[单片机]