有段时间我一直觉得,GD32 这样的国产 MCU 玩不出什么花来,毕竟生态和 STM32 比还是差了一截,资料也散。但后来等我把开发环境从 Keil 切到 SEGGER Embedded Studio(下面简称 SES),再配合 J-Link 的 RTT 功能,这个看法彻底变了。调试效率的提升是非常直观的,尤其是以前做串口日志打印,动不动就遇到 UART 引脚被业务占用、波特率不匹配、还要额外接一根 USB 转串口线,属实烦人。用上 RTT 之后,这些麻烦基本全没了。
这篇文章我就把完整过程写下来,从“为什么选 SES 而不是继续用 Keil”,到 RTT 内部的原理,再到如何把 RTT 移植进 GD32 工程、怎么把 printf 重定向到 RTT,最后附上我踩过的几个坑和排查思路。内容偏向实战,GD32F103 为例,其他型号操作基本一致,照着做就行。
1. 调试方案选型:为什么我把 GD32 开发切到了 SEGGER Embedded Studio
1.1 面对 GD32 开发,Keil、IAR 与 SES 怎么选
先说个大家绕不开的问题,GD32 的开发环境到底怎么选。大部分从 STM32 转过来的同学第一反应是 Keil MDK,因为 GD32 官方确实提供了 Keil 的 Device Pack,装上之后直接用,无缝衔接。Keil 的优势是资料多、教程多、同事之间传工程方便。但用久了你会发现它有几个让人抓狂的地方:代码编辑体验一般,工程文件管理起来不够直观,编译速度越来越慢,尤其是工程大了之后每次编译都像是看进度条等下班。
IAR 也不错,代码体积优化确实有一手,在资源受限的项目里优势明显。但 IAR 是商业软件,License 成本摆在那儿,个人用起来没那么方便。SES 则是个很特殊的存在,它是 SEGGER 自家出的集成开发环境,默认编译器是 SEGGER 基于 LLVM 的编译器,也支持 GCC,对 J-Link 的调试支持是“原生”级别的。最吸引我的地方是它对个人用户免费,官方只对商业用途收费,而且安装包非常小,启动速度快,界面干净。
从 GD32 的支持度来说,SES 在较新的版本里可以直接选择 GD32 系列芯片,配合 J-Link 自动识别下载算法,体验很顺。我整理了下面这个表,方便你根据自己实际情况判断:
| 开发工具 | 编译器 | 调试器支持 | GD32 支持度 | 适合场景 |
|---|---|---|---|---|
| Keil MDK | Arm Compiler 5/6 | CMSIS-DAP / J-Link / 其他 | 官方 Pack,支持完善 | 爱用现成工程、团队协作、维护老项目 |
| IAR EWARM | ICCARM | J-Link / 各种 | 官方支持 | 追求代码体积、有商业 License |
| SEGGER Embedded Studio | SEGGER CC / GCC | J-Link 最稳 | 内置 GD32 设备支持 | 个人开发者、重视调试效率、玩 J-Link |
SES 的调试功能不是简单的“能下个程序,能看个变量”,它和 J-Link 之间的通信链路做了很深度的优化。断点管理、内存查看、实时变量监视都很顺手。我个人的结论很简单:如果你手头有 J-Link,又想提升日常开发体验,SES 值得花一下午时间折腾,这个成本很容易就能赚回来。
1.2 J-Link RTT 到底解决了什么痛点
J-Link 在嵌入式开发圈子里几乎是“标配”级别的调试器,但很多人对它的用法停留在“下载程序”和“Keil 里点个 Debug”。其实 J-Link 自带了一套非常强的高效调试通信机制,叫 RTT,全称是 Real-Time Transfer,实时传输。
RTT 干了一件什么事呢?它允许目标芯片在运行的时候,通过 SWD/JTAG 接口直接向 PC 端发送数据,也能从 PC 端接收数据,全程不需要占用 UART 外设,也不需要额外的调试引脚。目标芯片内部只需要在内存里留一小块缓冲区,J-Link 会通过调试接口周期性地访问这块内存,把数据搬运到电脑上。
这解决了一个非常现实的痛点:调试日志往哪打。传统方案是串口打印,你得留一个 UART,接一个 USB 转串口模块,电脑上开个串口助手,还要设置波特率、数据位、停止位。然后你会发现,产品上电运行后业务逻辑已经占用了所有串口,最后实在没办法,只能临时从 PCB 上飞线出来,或者为了调试专门留一个测试口。RTT 完全不需要这些,它走的是调试通道。只要 J-Link 能连上芯片,RTT 就能工作,数据带宽还远高于普通串口。
我把串口打印和 RTT 做一个直观对比:
| 对比项 | 传统串口打印 | J-Link RTT |
|---|---|---|
| 硬件占用 | 需要空闲 UART、电平转换、USB 转串口 | 零额外硬件,只靠 SWD/JTAG |
| 数据带宽 | 一般 115200 bps,约 11 KB/s | 实测可达数百 KB/s 甚至更高 |
| 对 CPU 影响 | 串口寄存器写入 + 中断,占用资源 | 几行内存写指令,几乎无感 |
| 双向交互 | 需要额外设计通信协议 | 原生支持下行输入,可直接做命令终端 |
| 实时性 | 受波特率和中断影响 | 写入即时生效,无阻塞 |
很多人用 RTT 只看中了“不占串口”,但它的精髓是双向交互和低干扰。你在 PC 端可以直接往 MCU 发命令,MCU 收到后执行并实时返回结果,这不就是调试器版本的串口终端吗?关键还不打断程序的运行,实时任务该怎么跑还怎么跑。这套机制对 GD32 这类性能不错的 MCU 来说,非常适合用来打印调试信息、观察状态变量、做命令交互。
2. RTT 原理拆解:先搞明白它为什么快再用它
2.1 RTT 的快递柜模型:控制块与环形缓冲区
我第一次接触 RTT 的时候,光顾着用,没搞清楚它的机制,结果一遇到“RTT Viewer 没输出”就两眼一抹黑。后来仔细读了一下 SEGGER 的源码和文档,才明白它本质上就是在共用一块“快递柜”。
RTT 在 RAM 中维护了一个控制块(Control Block),这个控制块里记录着每个通道的缓冲区信息,比如缓冲区地址、读指针、写指针、缓冲区大小等等。控制块本身是一个结构体,代码里通常叫做SEGGER_RTT_CB。MCU 侧如果要发送数据,只要往当前通道的写指针位置写入数据,再把写指针往后挪即可,整个过程就是纯内存操作,不涉及外设寄存器、不产生中断。J-Link 那边则像是一个尽职尽责的快递员,不断通过 SWD/JTAG 接口读取这块内存区域,发现你放了新包裹就取走,然后通过 USB 发给电脑上的 RTT Viewer。
因为这个模型本质上就是一块“内存 + 指针”,所以它比串口快很多。串口每发一个字节都要经过移位寄存器,要设置波特率,速度上限很明显。RTT 则是内存写入,CPU 执行几条指令就完事了,数据真正传输到电脑的速度取决于 J-Link 的轮询频率和 USB 带宽。
我实际在 GD32F103 上测过,如果只是往 RTT 里打短字符串,基本是“瞬时完成”。你把SEGGER_RTT_printf放在中断里调用,只要控制好数据量和缓冲区大小,也不会明显拖慢中断响应。这个特性在调试高频业务的时候特别有用,比如 PWM 控制、电机电流采样这些场景,你用串口打印很容易因为波特率不够导致“打不过来”,RTT 则可以稳定地输出。
2.2 为什么串口打印和半主机模式都不适合高频调试
聊完了 RTT 的原理,就不得不回头说一说传统调试方案的局限,否则你不会知道 RTT 这个设计有多聪明。
串口打印慢,是很多人都能直观感受到的。115200 bps 的波特率换算下来,理论每秒也就传输 11KB 左右,再扣掉串口发包的起始位、停止位,实际有效数据更少。打印一行 100 字节左右的日志,可能要耗时近 10 毫秒。如果你的控制周期是 1 毫秒,那问题就大了,日志还没打完,下一个控制周期已经到了。很多人不得不做“标志位 + 主循环统一打印”的套路,但这会丢失实时性,调试结果往往失真。
另一个更隐蔽的坑是 ARM 半主机模式(Semihosting)。在 Keil 里用微库或者默认的 printf 重定向,有时候编译器会走半主机方式,它通过 BKPT 指令或 SVC 异常让 CPU 进入调试陷阱,然后由调试器代为实现输出。这种方式原理上没啥问题,但它在输出时会让 CPU 停滞,尤其是调试器处理速度跟不上时,程序像被按住了暂停键。在实时性要求比较高的项目里,这几乎不可用。RTT 不一样,它不会触发任何异常或陷阱,就是普普通通的内存写操作,CPU 不会停下,也不会被调试器阻塞。这就是“实时传输”和“打断式调试”的本质区别。
2.3 关于 RTT 时延与带宽,很多人理解错了
我经常看到有人问,RTT 的时延到底是多少,为什么有时候 RTT Viewer 里的数据会突然跳出一大段。这其实涉及两个完全不同的概念:写入时延和读取时延。
写入时延是指 MCU 调用SEGGER_RTT_Write把数据放进缓冲区的耗时。因为操作的是内存,写入时延非常低,基本可以忽略。读取时延是指数据已经写进缓冲区了,但 J-Link 还没把它搬到电脑上,直到下一次调试接口轮询时才被取走。这个读取时延和 J-Link 的轮询周期有关,一般在微秒到毫秒级别。所以你看到的“突然跳出一大段”,不是因为 RTT 慢,而是因为数据早就进缓冲区了,J-Link 在某一个瞬间把它们一起搬到了 PC 端。
还有带宽的问题。RTT 的标称带宽很高,但实际能跑多少取决于几个因素:SWD/JTAG 接口速度、J-Link 型号、USB 传输方式、数据缓冲区大小。数据量大时如果缓冲区不够,新数据会把没来得及取走的数据覆盖掉,造成丢日志。因此,讲究一点的工程会在SEGGER_RTT_Conf.h里把上行缓冲区调大,比如 4096 字节甚至更高,同时控制高频日志的打印频率。理解了这些,你在排障时就不会一看到“输出延迟”就怀疑 RTT 本身了。
3. 实操:SES 下从零搭建 GD32 工程并移植 RTT
3.1 准备工作:软件包与源码都从哪来
进入实战环节。先说准备工作,你需要以下几样东西:
- SEGGER Embedded Studio。去 SEGGER 官网注册个账号,之后在下载页面选对应平台版本。它对个人用户免费,下载后直接安装。
- J-Link Software and Documentation Pack。这是 J-Link 的驱动包,里面包含了 J-Link 的驱动、命令行工具 J-Link Commander、RTT Viewer 等工具,还有 RTT 的源码压缩包。安装路径默认在
C:\Program Files\SEGGER\JLink,RTT 源码通常在安装目录下,文件名类似SEGGER_RTT_V*.zip。 - GD32 标准外设库。以 GD32F103 为例,去 GD32 官网或者 Gitee/GitHub 上找
GD32F10x_Firmware_Library,压缩包里包含标准外设库的源码、示例工程和文档。我建议直接下最新的版本,尽量用官方源码,网上有些人二次打包的库可能改过东西,出了问题不好查。 - 一块 GD32F103 的核心板或者开发板,加上一个 J-Link。J-Link 建议用正版或者兼容性好的版本,否则容易遇到各种玄学问题。
这些工具准备好之后,可以先给开发板上电,用 J-Link 连接,打开 J-Link Commander 输入connect指令,看能不能正确识别到芯片。这一步能提前排除驱动或者接线问题,免得后面在 SES 里排查半天。
3.2 新建工程与 GD32 标准库文件组织
打开 SES,菜单选择Project -> New Project,模板选择Empty Project。新建之后,第一件事是给工程指定芯片型号。SES 较新的版本在 Project Options 里可以直接搜索到 GD32 系列,搜索GD32F103C8就能看到。如果确实找不到,可以安装对应的 Device Support Package,SEGGER 官网上有各厂商的设备支持包,下载安装后重启 SES 即可。
芯片型号确定后,接下来是把 GD32 标准库文件加进工程。这里我建议直接在文件系统层面把库文件复制到你的工程目录下,然后通过Project -> Add Existing Files把以下内容加进去:
- 启动文件,GD32F103 是 Cortex-M3 内核,对应的是
startup_gd32f10x_md.s,如果你的芯片是 HD 系列,就选startup_gd32f10x_hd.s,这个不能选错,选错了启动之后时钟和外设配置都会有问题。 - 系统时钟文件
system_gd32f10x.c,这里负责 SystemCoreClock 等全局变量和 SystemInit 的实现。 - 标准外设库的源码文件,比如
gd32f10x_gpio.c、gd32f10x_usart.c、gd32f10x_rcu.c,用到哪些模块就加哪些,不需要全部加进来,否则编译时间会变长。 - 核心头文件,
gd32f10x.h、gd32f10x_libopt.h等。
文件多了之后,我习惯在 SES 里按照Firmware、Startup、User、RTT这样的分组建目录,不是必须,但工程整洁一点,找代码不费劲。这里有个容易忽略的小细节:gd32f10x_libopt.h里有一堆外设头文件的使能宏,如果你在工程里用了某个外设但不小心把它注释掉了,编译时会报“找不到定义”这类错误,所以加库文件时建议顺手检查一遍这个文件。
3.3 J-Link 调试与下载参数配置
工程文件组织好,编译能通过之后,紧接着就是配置调试器。这一步非常关键,很多人在 SES 里连不上 J-Link,都是配置姿势不对。
在 SES 里打开Project -> Options -> Debugger,需要确认几个核心选项:目标和调试器要选 J-Link,接口选 SWD,速度可以先设 4MHz,如果目标板接线较长或者布线干扰大,可以降到 1MHz。Device 选项里要确保选的是 GD32F103C8,这样才能自动匹配下载算法和启动文件。如果 Device 列表里没有,也可以选择Cortex-M3然后手动设置 Flash 下载算法,但我更推荐直接把 Device Support Package 装好,让 SES 自动处理。
下载相关的设置在Project -> Options -> Debugger -> Flash Download,SES 通常会自动识别 J-Link 内置的 GD32 Flash Loader。如果你发现下载时提示No flash loader或者Failed to load flash loader,基本可以确定是 Device 没匹配上。
配置结束之后,直接按 F5 下载并启动调试。如果这一步能顺利跑起来,说明工程底座已经没问题了。我建议在 RTT 移植之前,先写一个最简单的 LED 闪烁程序,确认芯片能跑、调试器稳定。基础打牢了,后面加 RTT 的时候就算出问题,也知道不是最底层的事。
3.4 集成 SEGGER_RTT 并跑通第一个测试程序
接下来就是重头戏,把 RTT 源码加进工程。解压之前提到的SEGGER_RTT_V*.zip,你会看到典型的 RTT 文件结构。真正需要加入工程的只有这几个文件:
SEGGER_RTT.c:RTT 的核心实现,包括初始化、读写函数。SEGGER_RTT_printf.c:可选,提供类似 printf 的格式化输出函数。SEGGER_RTT.h、SEGGER_RTT_Conf.h、SEGGER_RTT_printf.h:对应的头文件和配置头文件。
把这三个 .c 文件加入工程,把头文件所在目录加到Project -> Options -> Code -> Preprocessor的 Include Paths 里。SEGGER_RTT_Conf.h是配置文件,里面有几个宏值得关注,比如BUFFER_SIZE_UP和BUFFER_SIZE_DOWN,分别定义上行缓冲区和下行缓冲区的大小,默认值是 1024 字节,我先按住不表,后面单独讲。
加好文件之后,写一个最简单的主程序来验证:
#include "gd32f10x.h" #include "SEGGER_RTT.h" int main(void) { int count = 0; SEGGER_RTT_Init(); while (1) { SEGGER_RTT_printf(0, "RTT run ok, count = %d\r\n", count++); delay_1ms(500); } }delay_1ms是简单的延时函数,你可以用 SysTick 来实现,也可以先用一个粗一点的循环延时把整个流程跑通。编译下载后,打开 RTT Viewer,菜单里选择File -> Connect,设备选 GD32F103C8,接口 SWD,速度可以保持和调试配置一致,连接成功之后你就会看到不断滚动的打印信息。
第一次跑通 RTT 的感觉,说实话挺爽的,完全没有串口那套波特率、COM 口的繁琐配置,J-Link 一插,屏幕上就开始刷数据了。到这一步,RTT 的集成基本完成,接下来就是怎么把它用出花来。
4. RTT 实战技巧:从 printf 到命令交互
4.1 把 printf 重定向到 RTT,释放串口引脚
SEGGER_RTT_printf虽然好用,但很多现有工程的代码都是直接调用printf,如果你想让所有日志都跑到 RTT 里去,最省事的方法是把printf重定向到 RTT。使用 GCC/SEGGER 编译器的时候,可以通过重写_write函数实现:
#include <stdio.h> #include "SEGGER_RTT.h" int _write(int file, char *ptr, int len) { (void)file; SEGGER_RTT_Write(0, ptr, (unsigned int)len); return len; }然后在代码里直接调用printf即可,所有格式化输出都会走 RTT。需要注意,重定向的写法在不同编译器下不一样,Keil 的 ARM Compiler 你可能需要重新实现fputc,而 SES 默认用的 SEGGER 编译器或者 GCC 则是_write。这块是很多人移植时最容易卡住的地方,建议先确认你的工程实际使用的编译器再动手改。
重定向完成后,UART 引脚就可以彻底释放了,UART 外设留给真正的业务通信,调试信息全部走 RTT。我在实际项目中就是把调试日志和业务数据彻底分开,串口只给业务用,调试看 RTT,再也不用为“调试串口被占用”吵架了。
4.2 用多通道和彩色日志让调试信息更有层次
RTT 支持多通道,默认有 0 通道和 1 通道,你可以在配置里开启最多 16 个通道。每个通道之间是独立的环形缓冲区,互不干扰。这意味着你可以把不同级别的日志分开打,比如通道 0 打普通信息,通道 1 打错误信息,通道 2 打高频的实时数据。
RTT Viewer 里可以为不同通道设置不同的颜色,我常用的配置是通道 0 青色、通道 1 红色、通道 2 黄色。这样调试时一眼就能看到错误级别的信息,不用在满屏白字里大海捞针。设置方法是在 RTT Viewer 的通道配置右键修改,也可以直接在 RTT 的SEGGER_RTT_Conf.h里通过宏定义来预配置。
使用通道也很简单,SEGGER_RTT_printf的第一个参数就是通道号。甚至可以封装几个宏:
#define LOG_INFO(fmt, ...) SEGGER_RTT_printf(0, "[INFO] " fmt "\r\n", ##__VA_ARGS__) #define LOG_ERROR(fmt, ...) SEGGER_RTT_printf(1, "[ERROR] " fmt "\r\n", ##__VA_ARGS__)有了宏之后,代码里想打日志就直接用LOG_INFO("temp = %d", temp);,简单清晰。调试完发布版本时,如果需要把日志关掉,只需要在宏定义里改成空即可,不用动业务代码。
4.3 RTT 回显法:构建一个最简单的调试命令终端
RTT 的双向通信能力是最容易被忽略的亮点。除了往电脑发数据,RTT 还能接收 PC 端发送的字符,这就为构建一个调试命令行提供了基础。所谓 RTT 回显法,就是 PC 端通过 RTT Viewer 输入字符,MCU 侧用函数读取并回显,同时解析这些字符作为命令执行。
MCU 侧读取下行数据非常简单:
char ch; if (SEGGER_RTT_Read(0, &ch, 1) == 1) { SEGGER_RTT_Write(0, &ch, 1); // 回显 // 解析命令 }你可以在主循环里轮询这个函数。我实测下来,用 RTT 做命令终端,输入手感比串口终端还顺,因为不需要接额外的串口线,工作在调试接口上,数据直接走 USB。
再进一步,可以写一个简单的命令行解析逻辑,比如接收"led on"、"led off"来控制 LED,接收"ver"来打印版本号。这在实际调设备时非常有用,你可以在不打断程序运行的情况下修改参数、切换模式、检查状态寄存器。我之前调试一个温控项目,PID 参数完全靠 RTT 命令行动态调整,调完直接观察温度曲线,整个过程程序一次都没重启过,效率比“改代码-重新烧录-看现象”高太多了。
4.4 配合 SES 调试器看变量:比打印高一个维度
RTT 适合打日志、做交互,但如果想看某个变量在程序运行过程中的实时变化,比打印更好的方式是直接利用 SES 的调试视图。程序停在断点时,你在 Watch 窗口里输入变量名,就能看到当前值。但断点会打断 CPU,和“实时传输”的理念有点冲突。
SES 里还有一种不打断运行的变量观察方式,通过 Live Watch 窗口看变量的实时值。配合 RTT,你可以做到:高频数据用 RTT 打印趋势,低频状态用 Live Watch 观察,关键路径用断点单步分析。三种手段互相补充,基本覆盖了调一般的逻辑问题所需的所有手段。
如果你需要把运行数据可视化,SEGGER 还有个工具叫 SystemView,可以实时记录任务调度、中断、事件等行为,配合 RTT 的底层机制,能看到比普通打印更丰富的系统运行状态。不过那套工具需要额外的移植和配置,属于进阶玩法,先不展开。
5. 常见问题与排查技巧实录
5.1 问题速查表:报错、现象与直接对策
这里把我在 RTT 调试过程中遇到的典型问题整理成一张速查表,方便你将来直接对号入座。
| 现象 | 可能原因 | 直接对策 |
|---|---|---|
| No J-Link found | 驱动未安装/USB 线损坏/调试器未识别 | 重装 J-Link 驱动、换线、用 J-Link Commander 验证 |
| 下载时提示找不到 Flash Loader | SES 设备型号没选对 | 安装 Device Support Package,确认 Device 为 GD32 型号 |
| RTT Viewer 能连接但无输出 | 主程序没有调用 SEGGER_RTT_Init | 在 main 函数开头执行初始化 |
| 日志乱码 | 编码不匹配/打印内容含中文 | 确认文件编码,尽量使用英文日志 |
| 日志一卡一顿或莫名跳动 | 上行缓冲区太小/J-Link 读取延迟 | 调大 BUFFER_SIZE_UP |
| 程序能跑但无法连接调试器 | 芯片读保护/选项字节被改 | 用 J-Link Commander 解锁或全片擦除 |
| RTT 数据丢了一部分 | 缓冲区覆盖/打印太频繁 | 降低打印频率或增大缓冲区 |
加了这张表,基本能解决 80% 的常规问题。下面挑几个坑详细说,因为这几个问题我踩过之后记忆深刻。
5.2 诡异的“No J-Link found”到底怎么排查
No J-Link found这个报错,出现频率很高,但它指的不是“没插 J-Link”,而是电脑没有识别到 J-Link 设备。先排除最简单的:驱动装没装。J-Link 驱动包安装后,在设备管理器里应该能看到一个 J-Link 的 USB 设备,如果显示黄色感叹号,重装驱动。
然后是 USB 线的问题,这个非常隐蔽。有些 USB 线只能供电,没有数据线芯,插上去灯亮,但电脑完全不识别。我手里就有好几根这种线,都是从杂牌充电线里随手抓来的。换一根确认能传输数据的线,问题立刻解决。
再就是 J-Link 固件状态。打开 J-Link Commander,输入connect,如果能显示设备型号和固件版本,说明 J-Link 本体没问题。如果提示Cannot connect to J-Link,可以按住 J-Link 板上的复位键,然后重新插 USB,必要时用J-Link Configurator修复固件。实测下来,九成“No J-Link found”问题逃不出这三步排查范围。
5.3 GD32 锁死导致无法连接怎么办
这个坑,几乎每个玩 GD32 的人都会遇到一次。症状是程序写完烧进去之后,突然在 SES 里连接不上调试器了,提示Cannot connect to target,或者连接成功后无法读写 Flash。这种情况大概率是代码里无意间使能了读保护,或者调试口被复用成普通 GPIO 了。GD32 和 STM32 类似,有一套选项字节机制,读保护开启后,调试器默认无法读取 Flash 内容。
处理办法是用 J-Link Commander 执行解锁。先把 J-Link 连接到板子,打开命令行工具,输入:
connect选择设备型号,比如 GD32F103C8,然后输入:
unlock GD32F103C8或者直接使用全片擦除命令,把选项字节和 Flash 一起恢复。执行完以后重新上电,再回到 SES 里连接,基本就能恢复。
这里必须提醒一句:解锁和全片擦除意味着芯片里的程序和数据全部没了,所以这个方法只适合开发调试阶段,千万别在已经量产或者装有重要数据的板子上尝试。养成一个好习惯,每次烧录前先想清楚这板子里面有没有需要保留的东西。
5.4 RTT 丢数据、打印乱码的排查思路
如果你发现 RTT 打出来的数据频繁丢失,或者突然缺了一部分,第一个怀疑对象应该是BUFFER_SIZE_UP太小。某些日志量大、打印频繁的场景下,J-Link 读取速度跟不上 MCU 写入速度,新写入的数据就会覆盖未读取的数据,造成丢失。解决方法是把SEGGER_RTT_Conf.h里的BUFFER_SIZE_UP从默认值改大,比如 4096 字节,代价只是多占一点 RAM,对于 GD32F103 这种动辄几十 KB 内存的芯片来说完全能接受。
打印乱码则大概率是编码问题。RTT Viewer 底层按字节传输,如果你的日志混入了 UTF-8 格式的中文,查看端没有按同样的编码解析,就会乱码。我自己的习惯是调试日志全部用英文,不跟编码较劲。如果确实需要中文日志,先把整个工程的源文件统一成 UTF-8 编码,然后在 RTT Viewer 的显示设置里把编码调成 UTF-8,能解决大部分乱码。
最后还有一个让人容易忽略的坑:在 HardFault 中断里调用SEGGER_RTT_printf。如果程序死机了,CPU 进入异常处理流程,RTT 缓冲区里新添加的数据很可能没有被 J-Link 及时取走,看起来像是“RTT 坏了”。遇到这种情况,先不要怀疑 RTT 本身,用调试器看当前 PC 指针停在了哪,找到真正触发硬错误的原因才是关键。
用 RTT 的时间越长,我越觉得调试这件事对开发效率的影响比很多人想象的大得多。一个顺手、不打断思路的调试工具,能让你把精力全部放在代码逻辑上,而不是消耗在“折腾日志输出”这种杂事上。现在我自己新建 GD32 工程时,默认就会把 RTT 集成进工程模板,哪怕刚开始用不到,也提前留着,等真出问题的时候,能少走不少弯路。最后再分享一个小经验:调试阶段把上行缓冲区调到 4096 字节,日志量再大也不慌;发布版本时再改小,能省一点 RAM。这套用法我用到现在,稳定省心,希望对你有用。