J-Link RTT库和CmBacktrace库使用总结
Posted 无痕幽雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了J-Link RTT库和CmBacktrace库使用总结相关的知识,希望对你有一定的参考价值。
目前一个项目由于资源、PCB体积等各方面限制,LOG_UART口没有引出,导致开发不能打印log,这对于习惯查看log分析问题的我来说,很不习惯,开发效率大大降低,遇到问题就抓狂了(在线调试,有些问题很难发现),于是查看网络,看看有没有其它的调试手段,找到了利用SWD口输出LOG的方法,如下:
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值了。
至此,整个调试库应用完毕。
以上是关于J-Link RTT库和CmBacktrace库使用总结的主要内容,如果未能解决你的问题,请参考以下文章
调试备忘录-J-Link RTT的使用(原理 + 教程 + 应用 + 代码)