J-Link RTT库和CmBacktrace库使用总结

         目前一个项目由于资源、PCB体积等各方面限制,LOG_UART口没有引出,导致开发不能打印log,这对于习惯查看log分析问题的我来说,很不习惯,开发效率大大降低,遇到问题就抓狂了(在线调试,有些问题很难发现),于是查看网络,看看有没有其它的调试手段,找到了利用SWD口输出LOG的方法,如下:

1、是利用MCU自带的ITM

2、J-Link RTT

M0和M0+内核不支持ITM,RTT支持ARM的全系列,没办法,我用的M0+内核,只能选择RTT。

至于移植和使用方法,请查看J-Link RTT使用博客,或者自行百度,网上很多介绍的帖子,这里不在赘述,说下使用当中遇到的问题:

1、RTT版本选择

我的工程是基于MDK的,编译器用的V5,采用V660d版本,编译没有问题,采用V754b版本,一堆错误,我也懒得去看错误是啥,目前采用V660d版本。

2、打印不全

(1)可以把BUFFER_SIZE_UP和SEGGER_RTT_PRINTF_BUFFER_SIZE都加大,但是SEGGER_RTT_PRINTF_BUFFER_SIZE要小于BUFFER_SIZE_UP;

(2)SEGGER_RTT_MODE_DEFAULT可以采用阻塞模式(如果不连接上位机,会阻塞代码);

(3)适当加快J-LINK上位机速度;

(4)建议采用J-LINK V9及以上版本;

3、中文乱码

J-Link RTT Viewer不支持中文,解决方案是,先打开J-Link RTT Viewer,然后再打开J-Link RTT Client,J-Link RTT Client支持中文。

4、保存LOG日志

先打开J-Link RTT Viewer,然后再打开J-Link RTT Logger,选择通道(默认是1,我用的0),

LOG位置:C:\Users\Administrator\AppData\Roaming\SEGGER

5、不支持浮点数输出

在SEGGER_RTT_vprintf函数中,增加如下代码:

      case '%':
        _StoreChar(&BufferDesc, '%');
        break;
//----------输出浮点-----------------------------      
       case 'f':
       case 'F':
       {
        float fv;
        fv = (float)va_arg(*pParamList, double);    //取出输入的浮点数值
       
        v = (int)fv;                                //取整数部分
       
        _PrintInt(&BufferDesc, v, 10u, NumDigits, FieldWidth, FormatFlags); //显示整数,支持负数
       _StoreChar(&BufferDesc, '.');                                        //显示小数点
       
        v = abs((int)(fv * 100));               
        v = v % 100;
       _PrintInt(&BufferDesc, v, 10u, 2, FieldWidth, FormatFlags);          //显示小数点后两位
       }
       break;
//--------------------------------------- 
      default:
        break;

注重:实例支持两位小数,其它位自行修改。

RTT最大的缺点就是不支持时间标签,如果带有时间标签就完美了。

(PS:谁有办法解决,请留言,谢谢)

利用RTT库调试时候,发现一个问题,系统很容易进入HardFault,这怎么解决?于是有百度到CmBacktrace库,用于抓取LOG分析,具体下载地址和移植步骤,请看代码出错提示_ARM CortexM系列MCU错误代码自动追踪库的使用经验分享或者自行百度,网上有很多教程。移植过程不在赘述,说下使用当中遇到的问题:

1、库里面的.c和.h文件不要包含其它文件,否则编译报错,里面要用的函数用extern直接在用的地方声明即可;

2、原库输出LOG,只输出一次

查看中断:

    AREA |.text|, CODE, READONLY, ALIGN=2
    THUMB
    REQUIRE8
    PRESERVE8

; NOTE: If use this file's HardFault_Handler, please comments the HardFault_Handler code on other file.
    IMPORT cm_backtrace_fault
    EXPORT HardFault_Handler

HardFault_Handler    PROC
    MOV     r0, lr                  ; get lr
    MOV     r1, sp                  ; get stack pointer (current is MSP)
    BL      cm_backtrace_fault

Fault_Loop
    BL      Fault_Loop              ;while(1)
    ENDP

    END

看到么有,调用完cm_backtrace_fault()函数,就死循环了,这会导致一个问题,如果我的设备正在测试,没有接LOG口,等待发现问题,我再接LOG也无用,因为日志就输出一次然后死循环了。怎么办?我把cm_backtrace_fault()改写了,代码如下:

void cm_backtrace_fault(uint32_t fault_handler_lr, uint32_t fault_handler_sp) {
    uint32_t stack_pointer = fault_handler_sp, saved_regs_addr = stack_pointer;
    const char *regs_name[] = { "R0 ", "R1 ", "R2 ", "R3 ", "R12", "LR ", "PC ", "PSR" };

    while(1){

        stack_pointer = fault_handler_sp;
        saved_regs_addr = stack_pointer;

    #ifdef CMB_USING_DUMP_STACK_INFO
        uint32_t stack_start_addr = main_stack_start_addr;
        size_t stack_size = main_stack_size;
    #endif

        CMB_ASSERT(init_ok);
        /* only call once */
        //CMB_ASSERT(!on_fault);

        on_fault = true;

        cmb_println("");
        cm_backtrace_firmware_info();

    #ifdef CMB_USING_OS_PLATFORM
        on_thread_before_fault = fault_handler_lr & (1UL << 2);
        /* check which stack was used before (MSP or PSP) */
        if (on_thread_before_fault) {
            cmb_println(print_info[PRINT_FAULT_ON_THREAD], get_cur_thread_name() != NULL ? get_cur_thread_name() : "NO_NAME");
            saved_regs_addr = stack_pointer = cmb_get_psp();

    #ifdef CMB_USING_DUMP_STACK_INFO
            get_cur_thread_stack_info(stack_pointer, &stack_start_addr, &stack_size);
    #endif /* CMB_USING_DUMP_STACK_INFO */

        } else {
            cmb_println(print_info[PRINT_FAULT_ON_HANDLER]);
        }
    #else
        /* bare metal(no OS) environment */
        cmb_println(print_info[PRINT_FAULT_ON_HANDLER]);
    #endif /* CMB_USING_OS_PLATFORM */

        /* delete saved R0~R3, R12, LR,PC,xPSR registers space */
        stack_pointer += sizeof(size_t) * 8;

    #if (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M4) || (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M7) || \
        (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M33)
        stack_pointer = statck_del_fpu_regs(fault_handler_lr, stack_pointer);
    #endif /* (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M4) || (CMB_CPU_PLATFORM_TYPE == CMB_CPU_ARM_CORTEX_M7) */

    #ifdef CMB_USING_DUMP_STACK_INFO
        /* check stack overflow */
        if (stack_pointer < stack_start_addr || stack_pointer > stack_start_addr + stack_size) {
            stack_is_overflow = true;
        }
        /* dump stack information */
        dump_stack(stack_start_addr, stack_size, (uint32_t *) stack_pointer);
    #endif /* CMB_USING_DUMP_STACK_INFO */

        /* the stack frame may be get failed when it is overflow  */
        if (!stack_is_overflow) {
            /* dump register */
            cmb_println(print_info[PRINT_REGS_TITLE]);

            regs.saved.r0        = ((uint32_t *)saved_regs_addr)[0];  // Register R0
            regs.saved.r1        = ((uint32_t *)saved_regs_addr)[1];  // Register R1
            regs.saved.r2        = ((uint32_t *)saved_regs_addr)[2];  // Register R2
            regs.saved.r3        = ((uint32_t *)saved_regs_addr)[3];  // Register R3
            regs.saved.r12       = ((uint32_t *)saved_regs_addr)[4];  // Register R12
            regs.saved.lr        = ((uint32_t *)saved_regs_addr)[5];  // Link register LR
            regs.saved.pc        = ((uint32_t *)saved_regs_addr)[6];  // Program counter PC
            regs.saved.psr.value = ((uint32_t *)saved_regs_addr)[7];  // Program status word PSR

            cmb_println("  %s: %08x  %s: %08x  %s: %08x  %s: %08x", regs_name[0], regs.saved.r0,
                                                                    regs_name[1], regs.saved.r1,
                                                                    regs_name[2], regs.saved.r2,
                                                                    regs_name[3], regs.saved.r3);
            cmb_println("  %s: %08x  %s: %08x  %s: %08x  %s: %08x", regs_name[4], regs.saved.r12,
                                                                    regs_name[5], regs.saved.lr,
                                                                    regs_name[6], regs.saved.pc,
                                                                    regs_name[7], regs.saved.psr.value);
            cmb_println("\r\n==============================================================\r\n");
        }

        /* the Cortex-M0 is not support fault diagnosis */
    #if (CMB_CPU_PLATFORM_TYPE != CMB_CPU_ARM_CORTEX_M0)
        regs.syshndctrl.value = CMB_SYSHND_CTRL;  // System Handler Control and State Register
        regs.mfsr.value       = CMB_NVIC_MFSR;    // Memory Fault Status Register
        regs.mmar             = CMB_NVIC_MMAR;    // Memory Management Fault Address Register
        regs.bfsr.value       = CMB_NVIC_BFSR;    // Bus Fault Status Register
        regs.bfar             = CMB_NVIC_BFAR;    // Bus Fault Manage Address Register
        regs.ufsr.value       = CMB_NVIC_UFSR;    // Usage Fault Status Register
        regs.hfsr.value       = CMB_NVIC_HFSR;    // Hard Fault Status Register
        regs.dfsr.value       = CMB_NVIC_DFSR;    // Debug Fault Status Register
        regs.afsr             = CMB_NVIC_AFSR;    // Auxiliary Fault Status Register

        fault_diagnosis();
    #endif

        print_call_stack(stack_pointer);

        CMB_CALL_USER_FUNCTION();
    }
}

改动如下:

(1)增加while(1)循环,LOG可以循环输出;

(2)增加变量初始化:

        stack_pointer = fault_handler_sp;
        saved_regs_addr = stack_pointer;

(3)屏蔽一次输出断言:

        /* only call once */
        //CMB_ASSERT(!on_fault);

(4)为了增加其它功能,比如我希望进入 fault_handler后,能够LED灯闪烁,这样我就知道进入 fault_handler了,这种情况下,我增加了一个宏函数CMB_CALL_USER_FUNCTION();

在cmb_cfg.h中增加:

/* call user function */
#define __CMB_CALL_USER_FUNCTION__
#ifdef  __CMB_CALL_USER_FUNCTION__
extern void user_hardfault_handler(void);
#define CMB_CALL_USER_FUNCTION()        user_hardfault_handler()
#else
#define  CMB_CALL_USER_FUNCTION()
#endif

(5)原库输出没有换行

log打印是这样的:

固件名称:JJ_ZTJX,硬件版本号:V1.2,软件版本号:V1.3在中断或裸机环境下发生错误异常=========== 线程堆栈信息 ===========  addr: 20004B00    data: 080000D1  addr: 20004B04    data: FFFFFFFF  addr: 20004B08    data: 00000001  addr: 20004B0C    data: 08000BC9  addr: 20004B10    data: 00088B80  addr: 20004B14    data: 080067A9  addr: 20004B18    data: 00000000  addr: 20004B1C    data: 000000C9  addr: 20004B20    data: 080099A0  addr: 20004B24    data: 08000A29============================================================= 寄存器信息 =========================  R0 : 00000040  R1 : 00000020  R2 : FFFFFFFF  R3 : 08006331  R12: FFFFFFFF  LR : 08004AE5  PC : 08006332  PSR: 01000000==============================================================查看更多函数调用栈信息,请运行:addr2line -e JJ_ZTJX.axf -a -f s-E- 

输出都在一行,不便于查看,我在 cmb_en_US.h、cmb_zh_CN.h、cmb_zh_CN_UTF8.h增加\r\n换行符。

(6)原库不能输出调用栈信息

输出结果如下:

查看更多函数调用栈信息,请运行:addr2line -e JJ_ZTJX.axf -a -f s

查看代码:

cmb_println(print_info[PRINT_CALL_STACK_INFO], fw_name, CMB_ELF_FILE_EXTENSION_NAME, cur_depth * (8 + 1),call_stack_info);

再看:

"查看更多函数调用栈信息,请运行:addr2line -e %s%s -a -f %.*s\r\n",

解释:

// %.*s 其中的.*表示显示的精度 对字符串输出(s)类型来说就是宽度
// 这个*代表的值由后面的参数列表中的整数型(int)值给出

由于我用的是SEGGER_RTT_printf函数,并不是标准的printf,因此可能不兼容上述用法,因此修改static void print_call_stack(uint32_t sp)函数,代码如下:

static void print_call_stack(uint32_t sp) {
    size_t i, cur_depth = 0;
    uint32_t call_stack_buf[CMB_CALL_STACK_MAX_DEPTH] = {0};

    cur_depth = cm_backtrace_call_stack(call_stack_buf, CMB_CALL_STACK_MAX_DEPTH, sp);

    for (i = 0; i < cur_depth; i++) {
        sprintf(call_stack_info + i * (8 + 1), "%08lx", (unsigned long)call_stack_buf[i]);
        call_stack_info[i * (8 + 1) + 8] = ' ';
    }
    if(i){
        call_stack_info[(i-1) * (8 + 1) + 8] = 0;//防止没有结束符,一直输出
    }
    if (cur_depth) {
//        cmb_println(print_info[PRINT_CALL_STACK_INFO], fw_name, CMB_ELF_FILE_EXTENSION_NAME, cur_depth * (8 + 1),
//                call_stack_info);
        cmb_println(print_info[PRINT_CALL_STACK_INFO], fw_name, CMB_ELF_FILE_EXTENSION_NAME,call_stack_info);
    } else {
        cmb_println(print_info[PRINT_CALL_STACK_ERR]);
    }
}
"查看更多函数调用栈信息,请运行:addr2line -e %s%s -a -f %s\r\n",

LOG输出如下:

查看更多函数调用栈信息,请运行:addr2line -e JJ_ZTJX.axf -a -f 080062f2 08004adc 08000bc0 08006768 08000a20

(7)除0不能触发HardFault异常

官方库提供了两个触发异常的测试函数,其中除0不能触发,结果为-1。

void fault_test_by_div0(void) 
{
    volatile int * SCB_CCR = (volatile int *) 0xE000ED14; // SCB->CCR
    int x, y, z;

    *SCB_CCR |= (1 << 4); /* bit4: DIV_0_TRP. */

    x = 10;
    y = 0;
    z = x / y;
    //printf("z:%d\n", z);
    TRACE_DEBUG("z:%d\n", z);
}

void fault_test_by_unalign(void) {
    volatile int * SCB_CCR = (volatile int *) 0xE000ED14; // SCB->CCR
    volatile int * p;
    volatile int value;

    *SCB_CCR |= (1 << 3); /* bit3: UNALIGN_TRP. */

    p = (int *) 0x00;
    value = *p;
    TRACE_DEBUG("addr:0x%02X value:0x%08X\r\n", (int) p, value);

    p = (int *) 0x04;
    value = *p;
    TRACE_DEBUG("addr:0x%02X value:0x%08X\r\n", (int) p, value);

    p = (int *) 0x03;
    value = *p;
    TRACE_DEBUG("addr:0x%02X value:0x%08X\r\n", (int) p, value);
}

CmBacktrace库缺点就是有点大,占用FLASH有点高。如果只需要输处异常处的PC值,可以用如下方法:

1、修改启动文件:


HardFault_Handler:
	MOV		r0, lr
	MOV		r1, sp
	BL		hardfault_handler

2、重新定义和声明HardFault_Handler函数

void HardFault_Handler(uint32_t lr, uint32_t sp);

然后在void HardFault_Handler(uint32_t lr, uint32_t sp)函数中就可以打印PC值了。

至此,整个调试库应用完毕。

《-------------------------------------------------2021.12.02----------------------------------------------------》

1、死机

现象是灯一直亮,查看LOG是进入HardFault,代码是其他同事写的,只能通过addr2line工具分析看看死在什么地方,如下(PS:博客这里的截图是JJ_AttitudeTransducer_Debug.axf文件修改BUG后,有些显示?,不影响):

J-Link RTT库和CmBacktrace库使用总结_第1张图片

发现每次都是死在BLE_init函数的124X行左右,再往下分析,发现串口是中断2,但是下面却调用的串口1(有时候还会调用其他函数),很奇怪。下面的库肯定没问题,用了好些年了,那只能分析BLE_init函数的124X干啥了。经过分析,这里进行了接收数据拷贝,但是没有判断长度,是不是数组越界了?增加长度判断,测试OK了。

你可能感兴趣的:(单片机,单片机,嵌入式硬件,c语言)