MCU无法接仿真器时的HardFault问题查找与分析
1、问题现象
这是一个IAR的裸机工程,用的LPC1778芯片。从打印信息上看到,上电后跑到”systime init”就没反应了,此句代码如下,正常会打印”systime init OK!"。
printf("systime init OK!");
2、问题原因
2.1、问题定位思路
触发硬件错误时打印当前栈帧与fault相关寄存器,通过栈回溯定位到出错时的代码,再通过fault相关寄存器查明错误原因。
2.2、实现硬件错误中打印栈帧与fault相关寄存器代码
代码逻辑如下,触发硬件错误时,先将当前MSP存入R0,再跳转执行HardFault_printf打印栈信息。注:函数的形参存放在R0-R3,放不下则存放到栈中,返回值存放在R0。注:裸机的栈是MSP,有操作系统时需根据异常返回值LR来决定触发硬件错误时刻的栈是MSP还是PSP。
void HardFault_Handler(void)
{
__asm("mrs r0,msp");
__asm("b HardFault_printf");
}
void HardFault_printf(uint32_t *msp_value)
{
printf("HardFault Handler\r\n");
printf("MSP:0x%08x\r\n\n",msp_value);
printf("r0: 0x%08x\r\n", msp_value[0]);
printf("r1: 0x%08x\r\n", msp_value[1]);
printf("r2: 0x%08x\r\n", msp_value[2]);
printf("r3: 0x%08x\r\n", msp_value[3]);
printf("r12: 0x%08x\r\n", msp_value[4]);
printf("1r: 0x%08x\r\n", msp_value[5]);
printf("pc: 0x%08x\r\n", msp_value[6]);
printf("xPSR: 0x%08x\r\n\n", msp_value[7]);
printf("Oxe000ed28 = 0x%08x\r\n", (*((volatile uint32_t *)0xe000ed28)));
printf("Oxe000ed2c = 0x%08x\r\n", (*((volatile uint32_t *)0xe000ed2c)));
printf("Oxe000ed30 = 0x%08x\r\n", (*((volatile uint32_t *)0xe000ed30)));
printf("Oxe000ed3c = 0x%08x\r\n\n", (*((volatile uint32_t *)0xe000ed3c)));
printf("Oxe000ed34 = 0x%08x\r\n", (*((volatile uint32_t *)0xe000ed34)));
printf("Oxe000ed38 = 0x%08x\r\n", (*((volatile uint32_t *)0xe000ed38)));
while(1);
}
编译运行后,串口打印"switch in"就没反应了(代码逻辑如下),HardFault_printf完全没打印,因为代码仅仅是重定义了HardFault_Handler函数,在里面加了打印,所以先看看打印接口的实现原理。
printf("systime init OK!"); //没修改HardFault_Handler之前,在这里面死掉
//其他初始化代码...
printf("switch init OK!"); //修改HardFault_Handler之后,在这里面死掉
排查发现此工程是异步打印,将要打印的数据存放进环形缓存区,每次调用printf函数,如果串口空闲,都会打印16个字节(串口使能了fifo,一次最大16字节),忙碌则直接返回。进入HardFault_Handler函数时,串口还在工作中,导致HardFault_printf里的信息一条都没打印出来。因此将串口打印改成阻塞式的同步打印逻辑,HardFault_printf里的打印信息如下。
r0: 0x1000ebb2
r1: 0xe000e100
r2: 0x00000002
r3: 0x00000000
r12: 0x000005b5
1r: 0x0001f315
pc: 0x00020598
xPSR: 0x01000000
0xe000ed28 = 0x01000000
0xe000ed2c = 0x40000000
0xe000ed30 = 0x00000000
0xe000ed3c = 0x00000000
0xe000ed34 = 0xe000ed34
0xe000ed38 = 0xe000ed38
2.3、找到出错语句
通过上述打印的寄存器可以看到,出错时的pc值为0x00020598,通过IAR自带工具生成反汇编文件,查找此地址,得到出错语句是” temp =*((unsigned long long *(&spi buf[2])); ” 它是将 char* 类型强制转换为 unsigned long long * 类型去解引用。
2.4、分析出错原因
中断里还打印了下述寄存器值,根据" 0xe000ed28 = 0x01000000 " 分析后得出错误原因是未对齐访问导致的fault。
印象中,只有M0不支持非对齐访问,所以找到LPC177X手册看看有无相关说明,还真有,如下图。非对齐的 LDM、STM、LDRD 和 STRD 指令总是出错,再查看之前的反汇编,使用的正是LDRD指令,才因此出错。
UNALIGN_TRP位默认是0,不捕获非对齐半字和字访问 ,也就是说使用支持非对齐访问的汇编指令时,默认情况不会报错。
3、解决方案
搞懂了前因后果,那就可以对症下药了,实现一个API,传入unsigned char *,返回unsigned long long就行了,编译烧录运行,问题解决。
unsigned long long get_unsigned_long_long_value(unsigned char *value)
{
unsigned long long result = 0;
for(unsigned char i=0; i<sizeof(unsigned long long); i++)
result |= (unsigned long long)(value[i]) << (i*8);
return result;
}
void fun(void)
{
//代码逻辑...
// temp =*((unsigned long long *(&spi buf[2]));
temp = get_unsigned_long_long_value(&spi buf[2]); //更改后的temp赋值逻辑
//代码逻辑...
}
作者:小牛马的自我修养