资讯动态

从串口到J-Link RTT:嵌入式调试新思路与中文乱码解决方案

发布时间:2026/9/28 17:02:54 来源:尧图企业网站定制
做嵌入式调试这些年我最早也是一个不折不扣的串口党。每个板子到手先留一路USART焊好排针翻出USB转TTL打开串口调试助手然后祈祷波特率没选错、线序没接反。直到有一次项目里UART资源被业务占满临时又找不到一块CH340同事甩了一句“你手上不是有J-Link吗用RTT Viewer啊。”我才发现原来调试手段还有一条更干净的路。从那以后我大部分项目的调试信息都迁移到了J-Link RTT Viewer上配合Keil开发环境不管是STM32、GD32还是国产CW32L010这类芯片只要SWD能连上这套玩法都能让你省掉一屁股串口接线的麻烦还能顺手把中文乱码问题解决掉。1. 为什么要“告别串口”RTT的原理与优势1.1 传统串口调试的三个老大难先说痛点。串口调试不是不能用而是用起来总有几个绕不开的问题。第一是硬件资源占用。串口调试必须占用一颗MCU的UART外设哪怕你用再简单的调试板也得给它留出TX、RX两个引脚。很多项目做到后面引脚紧张得要死还要为了调试硬挤一路串口出来这本身就是一种浪费。有的板子甚至要专门飞线接到排针丑得没法看。第二是链路脆弱。串口是异步通信双方必须约定好波特率。115200、921600这些数字我闭着眼都能背出来但实际用的时候总有翻车的时候——波特率没对上出来一堆乱码RX/TX接反没有数据GND没共地偶尔飘数据。排查这些问题本身就是时间杀手。第三是带宽瓶颈。串口的速度天然受限于波特率。拿最常见的115200bps来说刨掉起始位停止位有效数据率也就11.5KB/s左右。只要你的日志稍微打多一点整个输出就开始卡、丢、截断。你要在高速信号采集代码里实时打印几十个变量串口根本扛不住。1.2 RTT的工作原理J-Link替你偷看内存RTT的全称是Real Time Transfer是SEGGER提出的一种调试信息传输方案。它的原理说起来特别简单目标芯片的SRAM里划出一块环形缓冲区你自己的代码往缓冲区里写数据J-Link通过SWD或JTAG调试接口周期性地去读取这块内存再把数据搬到USB另一头的PC上RTT Viewer收到后显示出来。整个过程完全不需要UART外设不需要额外引脚不需要波特率匹配。它实际上就是让J-Link充当了一个“内存扒手”不断把调试数据从目标芯片的SRAM里掏出来。用个生活化的类比串口调试就像两个人隔着窗户用对讲机喊话得先约定好频道、音量还得保证有人在窗边值守RTT则像是J-Link这个“探长”直接打开你房间的窗户递出来的纸条随拿随走不需要屋里的人专门守着。1.3 RTT与串口的硬核对比我把两种方式的差异整理成一张表大家感受更直观对比项传统串口调试J-Link RTT硬件占用占用UART外设2个引脚不占用任何外设只用SWD的SWDIO/SWCLK接线需要USB转TTL、杜邦线注意TX/RX交叉J-Link调试线直接连4个SWD引脚速率115200bps下约11.5KB/s实测几十KB/s到几MB/s取决于缓冲与J-Link型号配置复杂度需要约定波特率、数据位、校验位无需波特率连上即可看附加依赖需要串口驱动如CH340驱动需要SEGGER J-Link软件包与RTT源码收发双向支持但下行需要额外逻辑RTT支持多通道双向天然适合做命令行交互从这张表能看出来RTT并不是简单地“换了个输出管道”它在资源占用、吞吐量、双向交互能力上都比传统串口高一个量级。当然它有一个天然前提必须有一只J-Link仿真器且目标芯片处在调试状态。好在做嵌入式开发的人手里基本都会有一只J-Link这个门槛并不高。2. 开搞之前环境准备与组件添加2.1 工具链清单与版本确认先把需要的工具列清楚避免很多人卡在开局Keil MDK版本5.x或6.x都能跑项目用AC5或AC6编译器均可但建议新工程直接用AC6SEGGER J-Link软件包也就是J-Link Software and Documentation Pack建议V7.x以上安装后会自动带上J-Link RTT Viewer目标板与J-Link仿真器J-Link通过SWD接口连接只需VCC、GND、SWDIO、SWCLK四根线SEGGER RTT源码这个通常在J-Link软件包安装目录下路径一般是“C:\Program Files (x86)\SEGGER\SEGGER J-Link\Samples\RTT”也可以从SEGGER官网单独下载。顺便说一句不少国产芯片对J-Link版本有要求比如CW32L010用太老的J-Link软件包可能无法识别目标。遇到这种情况先打开J-Link Commander确认一下能不能连上目标芯片再决定是否升级软件包。这套RTT玩法在Cortex-M全系列上都是通用的。2.2 把SEGGER RTT组件加进Keil工程打开Keil工程后在项目树里新建一个“RTT”分组然后把RTT源码文件夹里的四个文件拖进去SEGGER_RTT.cSEGGER_RTT.hSEGGER_RTT_printf.c 以及配套的SEGGER_RTT_printf.hSEGGER_RTT_Conf.h如果你的芯片是Cortex-M7这类带指令缓存的高端货或者你明确知道需要汇编级优化可以把SEGGER_RTT_ASM_ARMv7M.S也加进去。绝大多数情况下上面四个文件就够用了。加完文件之后在main函数开头调用一次初始化函数然后直接打印#include SEGGER_RTT.h int main(void) { SEGGER_RTT_Init(); SEGGER_RTT_WriteString(0, RTT is ready\r\n); while (1) { // your code } }很多人以为还要配置一大堆东西才能看到输出其实没有RTT的设计初衷就是开箱即用。编译下载后打开RTT Viewer如果一切正常你会在终端窗口里立刻看到“RTT is ready”。2.3 SEGGER_RTT_Conf.h按需调整SEGGER_RTT_Conf.h是RTT的核心配置文件里面有几个关键参数值得解释一下BUFFER_SIZE_UP上行缓冲大小默认1024字节也就是目标芯片往PC传数据的缓冲。日志多的场景建议改到4096或8192。BUFFER_SIZE_DOWN下行缓冲大小默认16字节是PC往目标芯片发数据的缓冲。做简单命令行交互的话保留默认就行。SEGGER_RTT_PRINTF_BUFFER_SIZESEGGER_RTT_printf内部格式化时的临时缓冲默认64字节。如果你的日志单条比较长比如要打印一个结构体建议改到128或256。SEGGER_RTT_MAX_NUM_UP_BUFFERS / SEGGER_RTT_MAX_NUM_DOWN_BUFFERS上行和下行通道的最大数量默认都是2。要做多通道日志分级建议把上行改成4。修改这些参数后记得重新编译整个工程。另外不要把上行缓冲改成所谓的阻塞模式也就是不要轻易让RTT在缓冲区满时死等否则日志一大CPU全耗在写缓冲上你的实时逻辑就全卡死了。保持默认的不阻塞丢数据模式配合调大缓冲才是正道。3. 基础玩法让printf从串口换成RTT3.1 重定向printf到RTTMicroLIB快捷方式很多人的代码里用的是printf格式化输出直接改成SEGGER_RTT_printf也不是不行但老代码里printf散布得到处都是一个个改太费劲。更优雅的做法是重定向fputc让标准printf的输出底层走RTT。在Keil工程里先勾上MicroLIB然后写一个fputc#include stdio.h #include SEGGER_RTT.h int fputc(int ch, FILE *f) { SEGGER_RTT_Write(0, (const char *)ch, 1); return ch; }这样你代码里所有printf底层都会通过RTT通道0输出到RTT Viewer。实测下来这个方案最省心不用动老代码也不影响代码可读性。注意一个坑工程里如果以前为了串口重定向写过类似的fputc函数要么注释掉要么用条件编译切换。否则两个fputc定义直接编译冲突。另外MicroLIB的printf不支持浮点格式化这条限制在串口时代也存在大家知道就好。3.2 SEGGER_RTT_printf与标准printf的差异如果你需要更精细地控制输出也可以直接用SEGGER_RTT_printf。它和标准printf有区别我的经验是支持%d、%u、%x、%p、%c、%s等常用格式基础调试完全够用不支持%f浮点打印打印float变量会得到不可预期的结果默认实现里精度、宽度支持有限不要拿它当完整的标准C格式化函数用。要打浮点数的时候我的写法是先借标准库格式化到临时数组再通过RTT输出char tmp[128]; float voltage 3.24f; snprintf(tmp, sizeof(tmp), voltage %.3f V\r\n, voltage); SEGGER_RTT_WriteString(0, tmp);这个方式虽然多了一步但在调试电源管理、传感器采集这类场景时很好用。你只需要确保临时数组够长别把栈搞爆就行。3.3 RTT Viewer常用操作速查RTT Viewer是SEGGER软件包里那个独立的显示工具安装完J-Link软件包后在开始菜单里就能找到。打开RTT Viewer第一屏会弹连接设置通常选择USB然后在下拉框里选目标芯片型号接口选SWD速度可以先用4MHz。如果你用的是带CDC虚拟串口的J-Link clone记得在USB设备列表里选J-Link而不是那个串口设备。连接成功后RTT Viewer的主界面就是一个终端窗口。窗口下面有个输入框往里面敲字符并回车数据会通过下行通道发到目标芯片。想做命令行交互的朋友这一条后面会细讲。RTT Viewer还支持多终端窗口后续做多通道调试时可以通过菜单里的Terminal选项添加新的终端并为每个窗口指定不同的上行通道。把调试日志和业务数据分开显示排查问题会轻松很多。3.4 缓冲区多大才不丢数据缓冲区大小的选择我直接给经验值嵌入式调试场景上行缓冲给4096字节起步如果日志量大而且主机端显示跟不上直接给8192下行缓冲给512字节足够除非你要通过RTT传输比较大的配置数据SEGGER_RTT_PRINTF_BUFFER_SIZE保持默认即可如果发现长日志被截断再改成256。很多人以为缓冲越大越好其实不至于。缓冲太大浪费SRAM嵌入式芯片的RAM本来就很宝贵。判断标准很简单运行你的满负载日志场景如果RTT Viewer显示没有明显丢行、丢内容那这个缓冲就是够用的。4. 高阶玩法多通道、日志分级与双向交互4.1 多通道设计把调试日志和业务数据分开RTT一个很重要的特性是支持多通道。默认的通道0叫Terminal用来跑printf是最直观的。但实际调试中我们往往不想让普通日志和关键调试信息混在一起。这时可以让RTT同时开多个上行通道通道0放常规日志、通道1放传感器原始数据、通道2放错误告警。初始化多通道时可以用SEGGER_RTT_ConfigUpBuffer。例如SEGGER_RTT_ConfigUpBuffer(1, SENSOR, sensor_buf, sizeof(sensor_buf), SEGGER_RTT_MODE_DEFAULT); SEGGER_RTT_ConfigUpBuffer(2, ERROR, NULL, 0, SEGGER_RTT_MODE_DEFAULT);当第二个参数指定名称、第三个参数传NULL、第四个参数传0时RTT会自己从静态内存里分配缓冲。然后打印时指定通道号SEGGER_RTT_printf(1, sensor0%d, sensor1%d\r\n, s0, s1); SEGGER_RTT_printf(2, fault%d\r\n, fault_code);RTT Viewer里菜单Terminal选择Add Terminal新建窗口后把该窗口的Up Channel设成对应的通道号。我习惯开三个终端窗口叠在一起一个看业务日志、一个看原始数据、一个专门盯错误。排查bug时这种“分开看”的方式省下来的时间用过的都知道有多爽。4.2 用颜色和前缀给日志分级多通道很好但有时候我们想在同一个通道里一眼分清ERROR、WARN、INFO。RTT Viewer的终端窗口支持常见的ANSI颜色控制码虽然在串口助手里不支持但在RTT里可以直接用。做一个简单的日志宏#define LOG_ERROR(...) SEGGER_RTT_printf(0, \x1B[31m[ERR] __VA_ARGS__ \x1B[0m\r\n) #define LOG_WARN(...) SEGGER_RTT_printf(0, \x1B[33m[WRN] __VA_ARGS__ \x1B[0m\r\n) #define LOG_INFO(...) SEGGER_RTT_printf(0, \x1B[32m[INF] __VA_ARGS__ \x1B[0m\r\n)红色错误、黄色警告、绿色信息一看就知道当前程序状态。注意别把样式控制码和普通日志混在一起多个线程同时打印时容易乱。如果日志量和颜色码一起涌出来终端可能偶尔抽搐一下这是正常现象只要数据不丢就行。4.3 用RTT做一个小型命令行RTT的双向通信能力非常适合做调试命令行。配合RTT Viewer下方的输入框可以给目标芯片发送字符串。下位机代码用SEGGER_RTT_Read读取#define CMD_BUF_SIZE 64 static char rx_buf[CMD_BUF_SIZE]; void rtt_command_task(void) { int n SEGGER_RTT_Read(0, rx_buf, CMD_BUF_SIZE - 1); if (n 0) { rx_buf[n] \0; /* 解析并执行命令 */ if (strstr(rx_buf, help) ! NULL) { SEGGER_RTT_WriteString(0, commands: help, version, led_on, led_off\r\n); } else if (strstr(rx_buf, version) ! NULL) { SEGGER_RTT_WriteString(0, app v1.0.3\r\n); } else if (strstr(rx_buf, led_on) ! NULL) { LED_ON(); } else if (strstr(rx_buf, led_off) ! NULL) { LED_OFF(); } } }这个思路扩展性很强像寄存器读写、任务栈使用量查询都能通过这种命令行完成。调试过程中不用反复下载程序输个命令就能改参数工作效率提升非常明显。4.4 在RTOS多任务下的注意事项如果你的工程跑的是FreeRTOS这类RTOS那么多任务同时打印时会有日志交错问题。RTT的环形缓冲区写入并不是原子的两个任务同时调SEGGER_RTT_Write可能出现半串日志被打断的情况。我的做法是给关键打印加临界区保护taskENTER_CRITICAL(); SEGGER_RTT_WriteString(0, task_a start\r\n); SEGGER_RTT_WriteString(0, task_b done\r\n); taskEXIT_CRITICAL();注意临界区不能一直占着不放否则影响实时性。另外在中断服务程序里打印时要特别谨慎最好不要在ISR里用SEGGER_RTT_printf这种带格式化的函数既耗时又容易和主循环的打印冲突。如果一定要ISR打印只做最简单的SEGGER_RTT_Write把格式化工作留给主循环。5. 中文打印解决方案从乱码到正常显示5.1 乱码的根源GB2312源码 vs UTF-8终端RTT调试中最大的坑恐怕就是中文乱码。这个乱码的根源说白了就是编码不一致。Keil在中文Windows系统下默认的源文件编码是ANSI也就是GB2312/GBK编码。你在Keil里写下中文字符串编译后存进Flash的实际就是GB2312的字节。而RTT Viewer在PC端默认按UTF-8解码UTF-8和GB2312的编码规则完全不同相同的字节流被当成UTF-8去解释自然出来的就是一堆天书。这就像你用中文写了一封信对方却用法语字母表来拆解笔画当然读不出原来的意思。要解决这个问题路径其实就两条要么让源码里存的字节变成UTF-8要么让RTT Viewer在显示前把GB2312转成UTF-8。5.2 方案A源码统一转成UTF-8最直接的思路是把源码文件都转成UTF-8编码保存。操作路径是Keil的Edit - Configuration - Editor在Encoding里选择UTF-8。如果是老工程也可以先用VS Code或者Notepad批量转码保存后再回到Keil。这个方案适合新工程但有几个坑要提醒Keil对UTF-8源码的编辑器显示在部分版本上依然有兼容问题尤其是中文注释转成UTF-8之后可能显示成乱码不过编译时注释会被忽略代码本身没问题如果工程里还有其他工具链参与比如git diff、脚本、代码生成器所有环节都必须是UTF-8否则某一步再引入GB2312的中文字符串乱码又会死灰复燃AC5编译器对UTF-8源文件的支持不如AC6稳定旧工程想整体转码建议先把编译器换成AC6。我的建议是新工程、团队协作标准统一的场景源码整体UTF-8是可行方案。但老工程、多人维护的代码动源码编码风险较大不推荐。5.3 方案B运行时GB2312到UTF-8映射表推荐老工程或者只想局部解决中文的情况推荐在代码里做一个轻量级转换发送前把中文字符串从GB2312转成UTF-8再交给RTT输出。这里不推荐网上那些“高位低位直接移位”的简化算法因为GB2312区码对应的Unicode码点并不是线性排布直接用位运算算出来的字符有一半是错的。真正稳妥的做法是维护一张映射表只收录你日志中用到的汉字。调试日志来来去去就那么几个词比如“初始化成功”“温度异常”“电压过高”汉字量很小映射表完全可控。先定义映射结构typedef struct { unsigned short gb; /* GB2312两字节编码如 0xB3C9 表示“成” */ unsigned char utf[3]; /* 对应的UTF-8三字节 */ } GB2UTF8_ITEM;再放一张示例表static const GB2UTF8_ITEM g_gb2utf8_tbl[] { {0xB3C9, {0xE6, 0x88, 0x90}}, /* 成 */ {0xB9A6, {0xE5, 0x8A, 0x9F}}, /* 功 */ {0xB4ED, {0xE9, 0x94, 0x99}}, /* 错 */ {0xCEE5, {0xE8, 0xAF, 0xAF}}, /* 误 */ {0xB5E7, {0xE7, 0x94, 0xB5}}, /* 电 */ {0xD1B9, {0xE5, 0x8E, 0x8B}}, /* 压 */ {0xCEC2, {0xE6, 0xB8, 0xA9}}, /* 温 */ {0xB6C8, {0xE5, 0xBA, 0xA6}}, /* 度 */ {0xB5F7, {0xE8, 0xB0, 0x83}}, /* 调 */ {0xCAD4, {0xE8, 0xAF, 0x95}}, /* 试 */ {0xB3F5, {0xE5, 0x88, 0x9D}}, /* 初 */ {0xCABC, {0xE5, 0xA7, 0x8B}}, /* 始 */ {0xBBAF, {0xE5, 0x8C, 0x96}}, /* 化 */ {0xB7A2, {0xE5, 0x8F, 0x91}}, /* 发 */ {0xCB CD, {0xE9, 0x80, 0x81}}, /* 送 */ {0xCAFD, {0xE6, 0xAD, 0xA3}}, /* 正 */ {0xB3A3, {0xE5, 0xB8, 0xB8}}, /* 常 */ };然后再写一个通用的发送函数static void rtt_write_cn_string(const char *str) { while (*str) { unsigned char c1 (unsigned char)*str; if (c1 0x80) { /* ASCII字符原样输出 */ SEGGER_RTT_Write(0, str, 1); str; } else if (c1 0xB0 c1 0xF7 str[1] ! \0) { /* GB2312汉字两字节 */ unsigned char c2 (unsigned char)str[1]; unsigned short gb (unsigned short)((c1 8) | c2); unsigned char utf[3]; int found 0; for (unsigned int i 0; i sizeof(g_gb2utf8_tbl) / sizeof(g_gb2utf8_tbl[0]); i) { if (g_gb2utf8_tbl[i].gb gb) { utf[0] g_gb2utf8_tbl[i].utf[0]; utf[1] g_gb2utf8_tbl[i].utf[1]; utf[2] g_gb2utf8_tbl[i].utf[2]; found 1; break; } } if (found) { SEGGER_RTT_Write(0, utf, 3); } else { /* 表里没有的字原样输出至少不崩溃 */ SEGGER_RTT_Write(0, str, 2); } str 2; } else { SEGGER_RTT_Write(0, str, 1); str; } } }用的时候先格式化到临时数组再调这个函数输出char tmp[128]; snprintf(tmp, sizeof(tmp), 初始化成功电压%.3fV\r\n, voltage); rtt_write_cn_string(tmp);这个方案的好处是不用动源文件编码不会影响老代码编译出来的体积只有一点点映射表里放多少字就占多少空间。坏处也明显映射表需要维护用到的新汉字要手动加。5.4 如何快速生成映射表手动查编码表太痛苦。我的习惯是用Python写个小脚本把目标汉字批量转成UTF-8字节。在PC上跑一段类似这样的代码输出直接粘贴进C文件即可import sys for ch in 初始化成功电压温度调试发送正常: gb ch.encode(gb2312) utf8 ch.encode(utf-8) gb_hex 0x%02X%02X % (gb[0], gb[1]) utf_hex , .join(0x%02X % b for b in utf8) print({ gb_hex , { utf_hex } }, /* ch */)输出的每一行直接对应映射表的条目根本不用自己算字节。我每次遇到新汉字就跑一下脚本把新条目追加到表里十分钟搞定一套日志用字库。这里再提供一个我个人很喜欢的思路日志文字尽量复用同一批高频汉字比如“正常、异常、成功、失败、开始、结束、温度、电压、电流、速度、错误、警告”这样映射表会非常小查表也快代码维护负担几乎为零。5.5 方案之间怎么选针对中文打印我把三种常用方案摆在一起对比方案推荐场景优势劣势源码转UTF-8新工程、编译链统一改一次一劳永逸Keil对UTF-8支持不完美旧工程风险大运行时映射表老工程、中文词少、想要可控不动源码编码增量维护简单映射表需要自维护硬编码UTF-8数组极少数临时中文最直白零依赖手写字节痛苦不适合长文本在实际项目中我现在默认的姿势是新工程一开始就把源文件设为UTF-8从根上避开乱码老工程用运行时映射表做增量改造只在需要中文输出的函数里调用转换输出其他地方不动。如果你的J-Link软件包版本较新RTT Viewer的终端设置里提供了字符集选项也可以尝试直接选择GB2312编码如果能正常显示那当然最省事。6. 常见问题与排查技巧实录6.1 “no J-Link found”与连接失败这个报错是J-Link用户最常见的噩梦。我通常按下面这个顺序排查现象可能的原因解决办法提示no J-Link foundJ-Link驱动没装好或软件被其它工具卸载过重新安装SEGGER J-Link Software PackUSB口插入无反应USB线只供电不传输数据换一根支持数据传输的USB线台式机插后置USB口J-Link识别到但无法连接目标目标板没上电或SWD接线错误确认VCC连接到目标板3.3VGND共地Keil里能下载但RTT Viewer连不上J-Link被Keil调试会话占用先退出Keil的调试状态再打开RTT Viewer老款J-Link连接国产新芯片失败J-Link软件包版本太老芯片ID无法识别升级J-Link软件包或先用J-Link Commander测试连接如果J-Link在Keil里能正常下载程序只是RTT Viewer连不上绝大多数情况是软件占用问题不是硬件问题。把Keil的调试窗口先关掉甚至拔插一次USB再打开RTT Viewer连接基本都能解决。6.2 日志丢失、卡顿和假死的排查RTT日志丢数据排查思路要从三个方向走第一上行缓冲太小。日志输出频率高、数据量大时缓冲很快被写满新日志就会按配置被丢弃。解决办法是调大BUFFER_SIZE_UP从4096再加到8192。这招九成情况下都能解决。第二SWD时钟速度太低。J-Link和目标的SWD速度如果只有几百kHz数据搬运速度根本跟不上打印速度。把SWD速度调到4MHz以上如果目标板稳定甚至可以用更高的速度。速度调高后日志刷新会明显变快。第三打印调用太频繁。有些代码把调试打印放在了高频中断或者主循环里每一轮都打印一遍那数据必然刷屏。我的习惯是给这类日志加一个节流逻辑例如每100ms只输出一次uint32_t next_log_time 0; uint32_t now get_tick_ms(); if (now next_log_time) { SEGGER_RTT_printf(0, sensor:%d\r\n, sensor_val); next_log_time now 100; }有一个特别容易困惑的点当Keil里触发断点、程序暂停时RTT Viewer也会一起“停摆”。这不是RTT的bug而是因为J-Link暂停了CPU目标芯片不再运行环形缓冲区自然也不再更新。你点在Keil里单步调试时看到RTT Viewer不动了这是正常现象全速运行后马上恢复。6.3 中文在某些版本显示异常如果你已经用了上面的某个方案但还是发现个别字符显示不对先确认一下目标芯片的Flash里存的字节是什么。用调试器直接在内存视图里查看字符串常量区可以直观看到是字节没转对还是转对了但RTT Viewer显示有问题。另外不要在SEGGER_RTT_printf里直接传中文字符串因为SEGGER_RTT_printf内部在处理字符串时只拷贝原始字节不会做任何编码转换。最稳妥的中文输出姿势就是我在5.3节写的先格式化再用自定义输出函数发送。6.4 RTT Viewer和Keil调试同时开的兼容性很多人习惯边用Keil调试边开着RTT Viewer看日志。这里的关键点是J-Link本身是否支持多进程共享。较新版本的SEGGER软件支持多个程序同时访问同一个J-Link但老版本或者兼容性一般的时候很容易出现“RTT Viewer连接成功但数据不刷新”或者Keil下载时报错“Cannot access target”的情况。我的实际经验是先让Keil进入调试模式再手动打开RTT Viewer连接通常可以共存反过来先开RTT Viewer再开Keil共存的成功率低一些。如果遇到冲突不用纠结谁对谁错先把RTT Viewer关了让Keil把程序下载好进入调试状态再重新打开RTT Viewer按这个顺序走基本不打架。6.5 实战中的几个独家小技巧最后分享几个文档里看不到的习惯第一给每条日志加上“房间号”。也就是在printf的开头固定打一个模块前缀比如[ADC]、[MOTOR]、[RF]这样日志一多扫一眼就能定位是哪个模块打的。第二日志里带时间戳。直接用芯片的systick或者任意毫秒计时器打印时带上tick值排查时序问题时方便到飞起。第三不要把敏感操作放在RTT打印前面。有时候RTT打印会影响时序尤其是你在ISR里打印过多、格式化耗时太长整个系统的实时性会被拖崩。调试阶段没问题但定位时序问题时记得先把打印功能关掉用真实的时序数据说话。我个人现在的新板子调试流程是板子到手先接好J-LinkSWD四根线搞定第一件事就是跑通RTT然后才轮到串口。串口只有在和外部设备通信、跑量产测试、或需要脱机记录日志的时候才会专门去接。RTT也不是万能的它没法脱离J-Link独立工作上电自检日志这类场景依然要依赖串口但对于日常开发来说它已经是我最顺手的调试手段。希望这篇文章能帮你把RTT的潜力都用起来也能少踩几个我踩过的坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑