ARM Cortex-M HardFault
Who does not know ... As the software runs for hours or even days / weeks and then the whole thing is suddenly in HardFault... :-(
What can help better as than a small assembler snippets in the startup code, that get out the failed PC address and the return address (to the caller).
If the local variables there are not overwritten by the registers used here, the error can be identify quite well with an attached JTAG debugger ...
Cortex-M0 | Cortex-M3/4 |
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
MOVS R5, #4
MOV R6, LR
TST R5, R6
BEQ HardFault_M
MRS R5, PSP
B HardFault_reg
HardFault_M
MRS R5, MSP
HardFault_reg
LDR R6, [R5, #24] ; R6 is the failed PC-address
LDR R7, [R5, #20] ; R7 is the failed LR-address
B .
ENDP
|
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
TST LR, #4
ITE EQ
MRSEQ R10, MSP
MRSNE R10, PSP
LDR R11, [R10, #24] ; R11 is the failed PC-address
LDR R12, [R10, #20] ; R12 is the failed LR-address
B .
ENDP
|
Cortex-M23 (ARMv8M baseline) | Cortex-M33 (ARMv8M mainline) |
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
MOVS R5, #0x40
MOV R6, LR
TST R5, R6
BEQ HardFault_ns
HardFault_s
MOVS R5, #4
MOV R6, LR
TST R5, R6
BEQ HardFault_s_M
MRS R5, PSP
B HardFault_reg
HardFault_s_M
MRS R5, MSP
B HardFault_reg
HardFault_ns
MOVS R5, #2
MRS R5, CONTROL_NS
TST R5, R6
BEQ HardFault_ns_M
MRS R5, PSP_NS
B HardFault_reg
HardFault_ns_M
MRS R5, MSP_NS
HardFault_reg
LDR R6, [R5, #24] ; R6 is the failed PC-address
LDR R7, [R5, #20] ; R7 is the failed LR-address
B .
ENDP
|
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
TST LR, #0x40
BEQ HardFault_ns
HardFault_s
TST LR, #0x04
ITE EQ
MRSEQ R10, MSP
MRSNE R10, PSP
B HardFault_reg
HardFault_ns
MRS R10, CONTROL_NS
TST R10, #2
ITE EQ
MRSEQ R10, MSP_NS
MRSNE R10, PSP_NS
HardFault_reg
LDR R11, [R10, #24] ; R11 is the failed PC-address
LDR R12, [R10, #20] ; R12 is the failed LR-address
B .
ENDP
|
If these two registers are not sufficient to find the error or a detailed implementation required at run-time,, a comprehensive handler should be implemented as the following.
Cortex-M0 | Cortex-M3/4 |
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
MOVS R0, #4
MOV R1, LR
TST R0, R1
BEQ HardFault_M
MRS R0, PSP
B HardFault_reg
HardFault_M
MRS R0, MSP
HardFault_reg
B HardFault_HandlerC
ENDP
|
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
TST LR, #4
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
B HardFault_HandlerC
ENDP
|
void HardFault_HandlerC(unsigned int *hardfault_args)
{
volatile unsigned long stacked_r0 ;
volatile unsigned long stacked_r1 ;
volatile unsigned long stacked_r2 ;
volatile unsigned long stacked_r3 ;
volatile unsigned long stacked_r12 ;
volatile unsigned long stacked_lr ;
volatile unsigned long stacked_pc ;
volatile unsigned long stacked_psr ;
volatile unsigned long _CFSR ;
volatile unsigned long _HFSR ;
volatile unsigned long _DFSR ;
volatile unsigned long _AFSR ;
volatile unsigned long _BFAR ;
volatile unsigned long _MMAR ;
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]) ;
// Configurable Fault Status Register
// Consists of MMSR, BFSR and UFSR
_CFSR = (*((volatile unsigned long *)(0xE000ED28))) ;
// Hard Fault Status Register
_HFSR = (*((volatile unsigned long *)(0xE000ED2C))) ;
// Debug Fault Status Register
_DFSR = (*((volatile unsigned long *)(0xE000ED30))) ;
// Auxiliary Fault Status Register
_AFSR = (*((volatile unsigned long *)(0xE000ED3C))) ;
// Read the Fault Address Registers. These may not contain valid values.
// Check BFARVALID/MMARVALID to see if they are valid values
// MemManage Fault Address Register
_MMAR = (*((volatile unsigned long *)(0xE000ED34))) ;
// Bus Fault Address Register
_BFAR = (*((volatile unsigned long *)(0xE000ED38))) ;
// --> print or handle the information-set
__asm("BKPT #0\n") ; // Break into the debugger
}
|
For the Cortex-M23 and Cortex-M33 a file "HardFault.c" exist in CMSIS 5.0.0 that does this.
Otherwise, there are of course many other sources on the internet that deal with this topic....
In the same way you can see / find out the source of an interrupt, who sits on the DefaultHandler because it is not actually used.
Cortex-M |
Default_Handler\
PROC
EXPORT Default_Handler [WEAK]
LDR R7, =0xE000ED04 ; ICSR
LDR R7, [R7, #0]
UXTB R7, R7 ; R7 is the number of the executing interrupt
B .
ENDP
|