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赋值逻辑
    //代码逻辑...
}

作者:小牛马的自我修养

物联沃分享整理
物联沃-IOTWORD物联网 » MCU无法接仿真器时的HardFault问题查找与分析

发表回复