From panic to composure, mastering these techniques will make you a HardFault hunter.
Introduction
As an STM32 developer, you have likely encountered a scenario where your program runs smoothly, only to suddenly freeze, with the debugger indicating that it has entered the <span>HardFault_Handler</span>. Staring at a pile of register values, you might feel confused: “Where did it go wrong?”
Don’t panic! Although HardFaults can be troublesome, with the right localization methods, they can actually help us identify bugs in our code. Today, we will systematically explain how to quickly locate HardFault exceptions in STM32.
1. What is HardFault?
1.1 Concept Explanation
HardFault (hardware fault) is a type of exception in ARM Cortex-M series processors that is triggered when the CPU encounters an unresolvable error. It serves as the “catch-all” mechanism for all Fault exceptions.
In Cortex-M, there are several types of Fault exceptions:
| Exception Type | Priority | Description |
|---|---|---|
| HardFault | Fixed -1 | Default handling for all faults |
| MemManage | Configurable | Memory management error (related to MPU) |
| BusFault | Configurable | Bus access error |
| UsageFault | Configurable | Instruction execution error |
Important: In Cortex-M0/M0+, only HardFault exists. In M3/M4/M7, if MemManage, BusFault, or UsageFault are not enabled, these errors will also escalate to HardFault.
1.2 Common Trigger Causes
Common causes of HardFaultMemory access errorsAccessing unmapped addressesAccessing alignment errorsStack overflowDangling pointersInstruction execution errorsDivision by zero operationsUndefined instructionsIllegal jumpsPeripheral configuration errorsClock not enabledRegister access errorsInterrupt-related issuesPriority configuration errorsVector table errors
2. HardFault Localization Process
2.1 Overall Localization Strategy
When the program enters HardFaultIs the debugger connected?YesNoCheck the call stack and registersOutput error information via serial portAnalyze the stack frame to obtain the fault addressCheck the Fault status registerMemory access errorDetermine the fault typeCheck pointers and array accessesBus errorCheck peripheral clocks and addressesInstruction errorCheck function pointers and jumpsLocate the specific line of codeFix the bug and verify
3. Detailed Localization Methods
Method 1: Use the Debugger for Direct Inspection
This is the simplest and most direct method. When the program enters HardFault:
Step 1: Check the call stack (Call Stack)
In IDEs like Keil, IAR, and STM32CubeIDE, the call stack window will display the function call chain, allowing you to directly locate the line of code where the error occurred.
Step 2: Check key registers
| Register | Meaning | Importance |
|---|---|---|
| PC (R15) | Program counter, points to the erroneous instruction | ⭐⭐⭐⭐⭐ |
| LR (R14) | Link register, return address | ⭐⭐⭐⭐ |
| SP (R13) | Stack pointer | ⭐⭐⭐ |
| PSR | Program status register | ⭐⭐⭐ |
Practical Tips:
- • The PC value usually points to the address of the erroneous instruction; check the corresponding code in the disassembly window
- • The LR value can help trace the call relationship
- • If the SP value is abnormal (too small or out of range), it indicates a stack overflow
Method 2: Enhanced HardFault_Handler
The default HardFault_Handler is just an infinite loop; we can rewrite it to obtain more information:
/**
* @brief Enhanced HardFault handler
* @note Can print error information and locate the specific line of code
*/
__attribute__((naked)) void HardFault_Handler(void)
{
__asm volatile
(
" tst lr, #4 \n"
" ite eq \n"
" mrseq r0, msp \n"
" mrsne r0, psp \n"
" ldr r1, [r0, #24] \n"
" ldr r2, handler_address_const \n"
" bx r2 \n"
" handler_address_const: .word hard_fault_handler_c \n"
);
}
/**
* @brief C language implementation of HardFault analysis function
* @param stack: stack frame pointer
*/
void hard_fault_handler_c(uint32_t *stack)
{
printf("\r\n\r\n");
printf("=====================================================\r\n");
printf(" HardFault Exception Handler\r\n");
printf("=====================================================\r\n");
printf("R0 = 0x%08X\r\n", stack[0]);
printf("R1 = 0x%08X\r\n", stack[1]);
printf("R2 = 0x%08X\r\n", stack[2]);
printf("R3 = 0x%08X\r\n", stack[3]);
printf("R12 = 0x%08X\r\n", stack[4]);
printf("LR = 0x%08X\r\n", stack[5]);
printf("PC = 0x%08X <--- Fault instruction address\r\n", stack[6]);
printf("PSR = 0x%08X\r\n", stack[7]);
printf("BFAR= 0x%08X\r\n", (*((volatile uint32_t *)(0xE000ED38))));
printf("CFSR= 0x%08X\r\n", (*((volatile uint32_t *)(0xE000ED28))));
printf("HFSR= 0x%08X\r\n", (*((volatile uint32_t *)(0xE000ED2C))));
printf("DFSR= 0x%08X\r\n", (*((volatile uint32_t *)(0xE000ED30))));
printf("AFSR= 0x%08X\r\n", (*((volatile uint32_t *)(0xE000ED3C))));
printf("=====================================================\r\n");
// Analyze CFSR register
uint32_t cfsr = (*((volatile uint32_t *)(0xE000ED28)));
printf("\r\nFault type analysis:\r\n");
// UsageFault (CFSR[31:16])
if (cfsr & (1 << 25)) printf(" - Division by zero error\r\n");
if (cfsr & (1 << 24)) printf(" - Unaligned access\r\n");
if (cfsr & (1 << 19)) printf(" - Co-processor access error\r\n");
if (cfsr & (1 << 18)) printf(" - Illegal exception return\r\n");
if (cfsr & (1 << 17)) printf(" - Illegal instruction state\r\n");
if (cfsr & (1 << 16)) printf(" - Undefined instruction\r\n");
// BusFault (CFSR[15:8])
if (cfsr & (1 << 15)) printf(" - BFAR register valid\r\n");
if (cfsr & (1 << 12)) printf(" - Bus error during exception stack\r\n");
if (cfsr & (1 << 11)) printf(" - Bus error during exception unstack\r\n");
if (cfsr & (1 << 10)) printf(" - Precise data access violation\r\n");
if (cfsr & (1 << 9)) printf(" - Imprecise data access violation\r\n");
if (cfsr & (1 << 8)) printf(" - Bus error during instruction fetch\r\n");
// MemManage Fault (CFSR[7:0])
if (cfsr & (1 << 7)) printf(" - MMAR register valid\r\n");
if (cfsr & (1 << 4)) printf(" - Memory management error during exception stack\r\n");
if (cfsr & (1 << 3)) printf(" - Memory management error during exception unstack\r\n");
if (cfsr & (1 << 1)) printf(" - Data access violation\r\n");
if (cfsr & (1 << 0)) printf(" - Instruction fetch violation\r\n");
printf("\r\n");
printf("Please check the code at PC=0x%08X in disassembly\r\n", stack[6]);
printf("=====================================================\r\n\r\n");
while(1); // Stop here to wait for debugging
}
Usage Instructions:
- 1. Add the above code to your project
- 2. Configure the serial port or use SWV to view the output
- 3. Run the program; detailed information will be automatically printed when HardFault occurs
Output Example:
=====================================================\n HardFault Exception Handler\n=====================================================\nR0 = 0x00000000\nR1 = 0x20000100\nR2 = 0x00000005\nR3 = 0x40013800\nR12 = 0x00000000\nLR = 0x08000459\nPC = 0x0800045C <--- Fault instruction address\nPSR = 0x21000000\nBFAR= 0x00000008\nCFSR= 0x00008200\nHFSR= 0x40000000\nDFSR= 0x00000000\nAFSR= 0x00000000\n=====================================================\nFault type analysis:\n - BFAR register valid\n - Precise data access violation\nPlease check the code at PC=0x0800045C in disassembly\n=====================================================
Method 3: Analyze the Fault Status Registers
ARM Cortex-M provides dedicated registers to record Fault information:
Fault Register GroupHFSRHardFault StatusCFSRConfigurable Fault StatusMMFARMemory Management AddressBFARBus Error AddressUFSRUsage FaultBFSRBus FaultMMFSRMemManage Fault
Key Register Addresses:
#define SCB_HFSR (*(volatile uint32_t*)0xE000ED2C) // HardFault status register\n#define SCB_CFSR (*(volatile uint32_t*)0xE000ED28) // Configurable Fault status register\n#define SCB_MMFAR (*(volatile uint32_t*)0xE000ED34) // Memory management error address\n#define SCB_BFAR (*(volatile uint32_t*)0xE000ED38) // Bus error address\n#define SCB_AFSR (*(volatile uint32_t*)0xE000ED3C) // Auxiliary Fault status register
CFSR Register Interpretation:
| Bit Field | Name | Description |
|---|---|---|
| [25] | DIVBYZERO | Division by zero error |
| [24] | UNALIGNED | Unaligned access |
| [19] | NOCP | Co-processor does not exist |
| [18] | INVPC | Illegal exception return |
| [17] | INVSTATE | Illegal instruction state |
| [16] | UNDEFINSTR | Undefined instruction |
| [15] | BFARVALID | BFAR valid |
| [12] | STKERR | Stack error |
| [11] | UNSTKERR | Unstack error |
| [9] | PRECISERR | Precise data bus error |
| [8] | IBUSERR | Instruction fetch bus error |
Method 4: Use addr2line Tool for Localization
When you only have the PC address and want to quickly locate the source code line:
Steps:
- 1. Note the PC address, for example:
<span>0x08000AB4</span> - 2. Use the addr2line tool from the GCC toolchain:
arm-none-eabi-addr2line -e your_project.elf -a 0x08000AB4 -f -p
- 3. The output will show:
0x08000ab4: main at main.c:156
Directly locating to line 156 of <span>main.c</span>!
4. Practical Cases
Case 1: Dangling Pointer Causing HardFault
Error Code:
void test_function(void)
{
uint32_t *ptr = NULL;
*ptr = 0x12345678; // Accessing null pointer, triggering HardFault
}
Debug Information:
PC = 0x0800123C\nCFSR= 0x00008200 (BusFault: PRECISERR + BFARVALID)\nBFAR= 0x00000000 (Accessed address 0)
Localization: BFAR shows access to address 0; checking the disassembly with PC reveals it was a null pointer dereference.
Case 2: Stack Overflow
Error Code:
void recursive_function(int depth)
{
char buffer[1024]; // Large local array
recursive_function(depth + 1); // Infinite recursion
}
Debug Information:
SP = 0x20000010 (Stack pointer has reached the bottom of the stack)\nCFSR= 0x00001000 (BusFault: STKERR - Stack error)
Localization: The SP is abnormally low, indicating a stack overflow. Check for recursion or large local variables.
Case 3: Peripheral Clock Not Enabled
Error Code:
void config_uart(void)
{
// Forgot to enable USART1 clock
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
USART1->BRR = 0x1D4C; // Accessing peripheral with clock not enabled, triggering HardFault
}
Debug Information:
PC = 0x080005A8\nCFSR= 0x00008200 (BusFault: PRECISERR)\nBFAR= 0x4001100C (USART1 register address)
Localization: BFAR points to the peripheral address; check if the clock for that peripheral is enabled.
5. Preventive Measures
Preventing HardFaultCode StandardsCheck pointers for NULL before useCheck array access boundariesAvoid large local variablesCompiler OptionsEnable warning options -WallEnable optimization -O2Enable stack protectionRuntime ChecksUse assert statementsMPU Memory ProtectionWatchdog Timeout DetectionStatic AnalysisUse PC-LintUse CoverityCode Reviews
Best Practices:
- 1. Enable all Fault exceptions (only for M3 and above):
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk | // Enable UsageFault\n SCB_SHCSR_BUSFAULTENA_Msk | // Enable BusFault\n SCB_SHCSR_MEMFAULTENA_Msk; // Enable MemManageFault
- 2. Use assertions:
#define ASSERT(expr) \
if (!(expr)) { \
printf("Assert failed: %s, file %s, line %d\r\n", \
#expr, __FILE__, __LINE__); \
while(1); \
}
- 3. Set reasonable stack size:
// In startup file\nStack_Size EQU 0x00000800 // Adjust according to actual needs
6. Conclusion
Key points for locating HardFault:
✅ Use enhanced HardFault_Handler to obtain detailed information✅ Analyze CFSR, HFSR, and other Fault registers to determine error types✅ Use PC address and call stack to locate specific lines of code✅ Use tools like addr2line for assistance in analysis✅ Develop good coding habits to prevent HardFault from the source
Remember:HardFault is not scary; what is scary is not knowing how to locate it. Mastering these methods will transform you from a “HardFault phobic” to a “HardFault hunter,” quickly finding and resolving issues!
References:
- • ARM Cortex-M4 Technical Reference Manual
- • Joseph Yiu: The Definitive Guide to ARM Cortex-M3/M4
- • STM32 Programming Manual
Recommended Debugging Tools:
- • Ozone Debugger (powerful SEGGER debugger)
- • STM32CubeIDE (free and feature-rich)
- • J-Link RTT Viewer (real-time log output)
Happy Debugging! May your code never HardFault!
【Previous Recommendations】Embedded Software Module Decoupling Advanced: Building High Cohesion, Low Coupling System ArchitectureSTM32 Startup Journey: The Wonderful Journey from Power-On to Main FunctionEmbedded Software Engineers, How to Enhance Your Skills? A Complete Roadmap from Beginner to Mastery