ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

GD32调试新选择:SES与J-Link RTT实战指南

GD32调试新选择:SES与J-Link RTT实战指南 1. 为什么我放弃了串口打印转投 RTT 的怀抱搞 GD32 开发的兄弟大概率都经历过这种场景板子已经装进外壳预留的串口排针被结构件挡得严严实实想临时加一行调试信息只能把板子拆出来、飞线、接 USB 转 TTL折腾半小时就为了看一个变量值。更别提用串口打印时printf本身会阻塞 CPU在中断里调用还可能直接把时序搞崩。我早期做 GD32F303 的电机控制项目时就因为在一个 10kHz 的 PWM 中断里塞了句串口打印导致波形直接畸变排查了一整天才定位到是打印拖慢了中断响应。后来接触到J-Link RTTReal Time Transfer这套方案配合SEGGER Embedded Studio后面简称 SES做开发调试体验完全是另一个层次。RTT 的核心思路很直接J-Link 调试器本身在目标芯片的 RAM 里开辟一小块共享缓冲区MCU 端把调试数据写进这个缓冲区J-Link 通过调试接口SWD/JTAG把数据读出来送到 PC 端显示。整个过程不占用 UART、不需要额外引脚、不打断 CPU 运行速度还比串口快几十倍。这篇文章我打算把 GD32 平台上 SES J-Link RTT 的完整实战流程拆开讲透。从环境搭建、RTT 移植、缓冲区配置到多通道输出、日志落盘、常见连接失败排查都会给到可直接抄作业的步骤和参数。适合已经能点亮 GD32、但调试手段还停留在串口打印阶段的嵌入式开发者也适合想从 Keil 迁移到 SES 工具链的朋友。文中涉及的具体参数和代码都是我在 GD32F303、GD32F450、GD32E230 几款芯片上实测过的不是照搬文档。2. 工具链选型SES 与 J-Link 为什么是 GD32 调试的黄金搭档2.1 SEGGER Embedded Studio 的定位与优势很多人第一次听说 SES 会以为它只支持 SEGGER 自家的芯片其实它是一套完整的跨平台 IDE基于 GCC/Clang 工具链支持 ARM Cortex-M 全系列GD32 用的 Cortex-M3/M4/M23 内核都在支持范围内。我选它主要看中三点。第一是编译速度快。同样一个 GD32F450 的工程Keil MDK 全量编译要 40 多秒SES 用它的多线程编译大概 15 秒左右增量编译差距更明显。第二是调试器集成度高SES 内置了对 J-Link 的深度支持RTT Viewer、RTT Logger、SystemView 这些工具可以直接从 IDE 里一键拉起不用来回切软件。第三是免费用于非商业用途对个人开发者和小团队很友好商业授权价格也比某些主流 IDE 便宜不少。不过要提醒一句SES 的工程结构和 Keil 差异较大它用的是自己的.emProject格式底层是 XML。从 Keil 迁移过来时启动文件、链接脚本、分散加载配置都需要重新整理这部分后面会专门讲。2.2 J-Link 在 GD32 上的兼容性说明J-Link 对 GD32 的支持是通过通用的 Cortex-M 内核识别实现的不需要专门的器件包。我用的是 J-Link EDU 和 J-Link BASE 两款固件版本分别在 V7.88 和 V7.92连接 GD32F303、GD32F450、GD32E230 都没问题。需要注意的是J-Link 的软件包版本要和 GD32 的 Flash 算法匹配早期某些版本对 GD32 的大容量型号比如 GD32F450 的 3MB Flash识别会有偏差建议用 V7.80 以上的版本。连接方式上SWD 是首选只需要 SWCLK、SWDIO、GND 三根线加上可选的 RESET 和 VREF比 JTAG 省引脚。SWD 速率我一般设 4MHzGD32F303 跑 120MHz 主频时这个速率很稳再高在长排线场景下容易出错。如果板子和调试器之间连线超过 15cm建议降到 1MHz 并加地线隔离。2.3 RTT 相比串口打印的本质区别这里必须把 RTT 和串口打印的差异讲清楚否则很多人会误以为 RTT 只是另一种串口。串口打印是单向、阻塞、独占外设的MCU 调用printf数据通过 UART 外设逐字节发送CPU 要等发送寄存器空才能写下一个字节波特率 115200 时打印 100 字节就要约 8.7ms这期间 CPU 基本干不了别的。而 RTT 是双向、非阻塞、共享内存的MCU 把数据memcpy到 RAM 缓冲区就返回耗时通常在微秒级J-Link 在后台通过调试接口轮询读取。实测在 GD32F303 上RTT 打印 100 字节的 CPU 占用时间不到 10 微秒比串口快三个数量级。另一个关键差异是RTT 支持双向通信。PC 端不仅能接收 MCU 的打印还能通过 RTT 向下发送数据MCU 端用SEGGER_RTT_Read就能读到。这意味着你可以做一个简易的命令行交互不用额外接串口就能在线调参数。我在做 PID 调参时就靠这个功能PC 端发新参数、MCU 端实时生效比反复烧录快太多。3. 环境搭建从零配置 SES 与 J-Link 驱动3.1 软件安装与版本匹配先把需要的软件列清楚避免装到一半发现缺东西。软件推荐版本用途备注SEGGER Embedded StudioV7.32 及以上主 IDE官网下载选 ARM 版本J-Link Software PackV7.88 及以上驱动与工具含 RTT Viewer、RTT LoggerGD32 固件库对应型号最新版外设驱动从官方渠道获取SEGGER RTT 源码随 J-Link 包附带RTT 移植在安装目录 Samples 下安装顺序建议先装 J-Link Software Pack再装 SES。因为 SES 安装时会检测系统里的 J-Link 驱动如果顺序反了SES 里可能找不到 J-Link 设备。装完后打开 J-Link Commander输入connect看能否识别到 GD32 内核识别到会显示类似Cortex-M4 identified的信息这一步过了说明驱动没问题。3.2 建立 GD32 标准工程模板SES 新建工程时选Create a new project然后选C/C下的Executable器件选ARM厂商下的GigaDevice具体型号按你用的芯片选。如果列表里没有你的型号选同内核的通用 Cortex-M 配置也行比如 GD32E230 选Cortex-M23。工程建好后需要手动整理目录结构。我习惯这样分Project/ ├── CMSIS/ # 内核头文件 ├── GD32_Lib/ # 官方固件库 │ ├── inc/ │ └── src/ ├── User/ # 用户代码 │ ├── main.c │ └── gd32f30x_it.c ├── RTT/ # RTT 移植文件 │ ├── SEGGER_RTT.c │ ├── SEGGER_RTT.h │ ├── SEGGER_RTT_Conf.h │ └── SEGGER_RTT_ASM_ARMv7M.S └── Startup/ # 启动文件启动文件从官方固件库里拿GD32F303 用startup_gd32f30x_hd.s注意后缀hd表示大容量。链接脚本 SES 会自动生成但默认的 Flash 和 RAM 大小可能和你的芯片不符需要在Project Options的Linker里手动改。比如 GD32F303ZE 是 512KB Flash、64KB RAM就要把FLASH起始地址设 0x08000000、长度 0x80000RAM起始 0x20000000、长度 0x10000。3.3 J-Link 连接配置与速率调优在 SES 里配置 J-Link 的入口是Project Options→Debug→J-Link。关键参数如下Target Interface选SWD除非你的板子只引出了 JTAG。Target Interface Speed初始设4000 kHz连不上就降到1000 kHz。Reset Strategy选Reset Pin或Connect under ResetGD32 在某些低功耗模式下会关闭 SWD 引脚必须用复位连接才能抓到。Host Interface选USB。注意如果板子用了 GD32 的深度睡眠模式SWD 引脚可能被复用为 GPIO导致 J-Link 连不上。解决办法是在Reset Strategy里选Connect under Reset让 J-Link 在芯片复位瞬间抢占 SWD 引脚。速率调优有个经验值排线长度 10cm 以内可以上 8MHz10-20cm 用 4MHz超过 20cm 降到 1MHz 并缩短 SWCLK 和 SWDIO 的走线。我遇到过一块 GD32F450 的板子SWD 走线绕了半个板子4MHz 下十次有三次连不上降到 1MHz 后一次成功。4. RTT 移植实战把调试通道塞进 RAM4.1 RTT 源码文件的作用拆解RTT 移植只需要四个文件但每个文件的作用要搞清楚出问题时才知道去哪找。SEGGER_RTT.c是核心实现包含缓冲区管理、读写指针维护、通道控制逻辑。SEGGER_RTT.h是对外接口常用的SEGGER_RTT_printf、SEGGER_RTT_Write、SEGGER_RTT_Read都在这里声明。SEGGER_RTT_Conf.h是配置文件缓冲区大小、通道数量、锁机制都在这里改。SEGGER_RTT_ASM_ARMv7M.S是汇编实现的中断安全版本用于在中断里调用 RTT 时保证原子性Cortex-M3/M4 需要它M23 用不到。把这四个文件拷到工程的RTT目录在 SES 里右键工程 →Add Existing Files加进去。注意.S文件要确认被识别为汇编源文件SES 里看文件图标是汇编样式才对。4.2 缓冲区大小与通道数量配置SEGGER_RTT_Conf.h里有几个参数直接决定 RTT 能不能用、好不好用。#define BUFFER_SIZE_UP (1024) // 上行缓冲区MCU 到 PC #define BUFFER_SIZE_DOWN (16) // 下行缓冲区PC 到 MCU #define SEGGER_RTT_MAX_NUM_UP_BUFFERS (3) #define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (3) #define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIPBUFFER_SIZE_UP是 MCU 往 PC 发数据的缓冲区默认 1024 字节。如果你要打印大量数据比如波形采样点建议加到 4096 甚至 8192但要注意这会占用 RAM。GD32F303 只有 64KB RAM我一般设 2048够用又不浪费。SEGGER_RTT_MODE_DEFAULT决定缓冲区满时的行为。NO_BLOCK_SKIP是满了就丢弃新数据不阻塞NO_BLOCK_TRIM是满了覆盖旧数据BLOCK_IF_FIFO_FULL是满了就阻塞等待。调试场景推荐NO_BLOCK_SKIP因为阻塞会打乱实时性而丢几条调试信息通常无所谓。但如果你在抓关键日志可以用BLOCK_IF_FIFO_FULL保证不丢代价是可能影响时序。4.3 初始化与基础打印代码RTT 不需要显式初始化第一次调用SEGGER_RTT_printf时会自动初始化控制块。但为了保险我习惯在main开头加一句#include SEGGER_RTT.h int main(void) { SEGGER_RTT_Init(); // 显式初始化确保控制块就绪 SEGGER_RTT_printf(0, RTT ready, SystemCoreClock%d\r\n, SystemCoreClock); while(1) { SEGGER_RTT_printf(0, tick%d\r\n, GetTickCount()); delay_ms(500); } }SEGGER_RTT_printf的第一个参数是通道号0 是默认通道。格式化语法和标准printf基本一致但不支持浮点数要打印浮点得自己转成整数或字符串。这是 RTT 的一个坑很多人第一次用发现%f打印出来是乱码就是因为这个。要打印浮点可以这样处理float voltage 3.3f; int v_int (int)(voltage * 1000); // 转成毫伏整数 SEGGER_RTT_printf(0, voltage%d.%03d V\r\n, v_int/1000, v_int%1000);4.4 中断中安全调用 RTT 的注意事项在中断里调用 RTT 要特别小心。SEGGER_RTT_printf内部有锁机制如果主循环正在打印时中断也来打印可能出现数据交错。解决办法是用SEGGER_RTT_Write配合SEGGER_RTT_LOCK/UNLOCK或者直接用汇编版本的SEGGER_RTT_WriteNoLock。我的做法是在中断里只写不格式化的短数据void TIMER0_IRQHandler(void) { if(timer_interrupt_flag_get(TIMER0, TIMER_INT_FLAG_UP) SET) { timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP); uint32_t tick GetTickCount(); SEGGER_RTT_Write(1, tick, sizeof(tick)); // 通道1二进制写入 } }用通道 1 专门传二进制数据PC 端用 RTT Viewer 的二进制模式或者自己写脚本解析避免和通道 0 的文本日志混在一起。这样既保证了中断里的执行速度又不会和主循环的打印冲突。5. 调试实战RTT Viewer 与日志落盘的组合拳5.1 RTT Viewer 的多通道显示配置打开 J-Link RTT Viewer连接设置里选USB器件选Cortex-M4GD32F303 是 M4接口SWD速率和 SES 里保持一致。连接成功后默认显示通道 0 的数据。要同时看多个通道在All Terminals标签页里能看到所有通道。我一般把通道 0 留给主日志通道 1 留给高频数据通道 2 留给错误信息。在Terminal设置里可以给每个通道配不同颜色错误信息标红一眼就能看到。RTT Viewer 有个很实用的功能是Input栏可以直接往 MCU 发数据。配合SEGGER_RTT_Read就能做交互char cmd[32]; unsigned num SEGGER_RTT_Read(0, cmd, sizeof(cmd)); if(num 0) { if(cmd[0] r) { system_reset(); } else if(cmd[0] p) { pid_kp 0.1f; } }这样不用重新烧录就能改参数、触发复位调试效率提升非常明显。5.2 RTT Logger 实现日志自动落盘RTT Viewer 的数据只在内存里关掉就没了。要长期记录日志用RTT Logger。它的用法是命令行工具基本命令JLinkRTTLogger -Device Cortex-M4 -If SWD -Speed 4000 -RTTChannel 0 log.txt这条命令会把通道 0 的数据实时写入log.txt。我通常在跑长时间测试时开着它比如电机连续运行 8 小时日志全程记录出问题时直接翻文件定位。注意RTT Logger 默认会覆盖同名文件要保留历史日志的话在命令里用时间戳命名比如log_$(date %Y%m%d_%H%M%S).txt。如果不想用命令行也可以在 SES 里配置Debug→RTT→Log to file指定路径即可。但 SES 内置的落盘功能在数据量大时可能丢包高频场景还是推荐 RTT Logger。5.3 用 RTT 做简易示波器的技巧RTT 的高速特性让它能当简易示波器用。思路是把 ADC 采样值以二进制形式通过 RTT 发出来PC 端用 Python 脚本接收并绘图。MCU 端#define BUF_LEN 64 uint16_t adc_buf[BUF_LEN]; // ADC 采样填充 adc_buf 后 SEGGER_RTT_Write(1, adc_buf, BUF_LEN * sizeof(uint16_t));PC 端 Python 脚本用pylink或直接读 RTT Logger 的输出文件用matplotlib实时绘图。实测在 4MHz SWD 速率下每秒传 100KB 数据没问题对应 50kHz 的 16 位采样率看个电机电流波形、PID 响应曲线完全够用。这个方案的好处是不占用 UART、不需要额外硬件缺点是依赖 J-Link 连接板子必须接着调试器。适合实验室调试不适合现场部署。6. 常见问题排查连接失败与数据异常的应对6.1 J-Link 连接失败的排查顺序No J-Link found或Cannot connect to target是最高频的问题。我整理了一个排查顺序按这个走基本能定位。现象可能原因排查方法No J-Link foundUSB 驱动异常设备管理器看是否有 J-Link 设备重装驱动Cannot connect to targetSWD 引脚被复用用 Connect under Reset 模式连接时断时续SWD 速率过高降到 1MHz 试试识别到内核但读不到 FlashFlash 算法不匹配更新 J-Link 软件包到最新版芯片锁死读保护或写保护开启用 J-Link Commander 执行 unlock芯片锁死这个情况要单独说。GD32 如果误开了读保护J-Link 会连不上。解锁方法是在 J-Link Commander 里执行connect unlockunlock会擦除整个 Flash所以执行前确认代码有备份。如果unlock也失败可能需要用Connect under Reset加unlock组合或者用 GD32 官方的烧录工具做全片擦除。6.2 RTT 无输出的几种典型情况连上了 J-Link但 RTT Viewer 里一片空白常见原因有这么几个。缓冲区地址不对。RTT 控制块在 RAM 里的地址是链接时确定的如果 SES 的链接脚本改了 RAM 布局控制块地址会变RTT Viewer 就找不到。解决办法是在 RTT Viewer 的Address栏手动填控制块地址或者用Auto Detection让它自动搜索。自动搜索在 RAM 小于 64KB 时很快大于 128KB 会慢一些。优化等级过高导致控制块被优化掉。SES 默认优化等级可能是-O2或-O3编译器可能把没显式引用的 RTT 控制块优化掉。解决办法是在SEGGER_RTT.c里给控制块加volatile或者在工程设置里把 RTT 相关文件设为-O0。通道号写错。SEGGER_RTT_printf(0, ...)里的 0 是通道号如果你在 RTT Viewer 里只看通道 1自然看不到通道 0 的数据。检查 RTT Viewer 的Terminal选择是否正确。6.3 数据乱码与丢包的解决思路RTT 打印出现乱码通常是波特率不匹配的思维惯性导致的误判——RTT 没有波特率概念乱码一般来自三个原因。一是格式化字符串和参数类型不匹配。比如用%d打印uint32_t在某些编译器下会出错应该用%u或%lu。二是浮点数未转换前面说过 RTT 不支持%f。三是多线程/中断并发写同一通道导致数据交错解决办法是每个上下文用独立通道或者加锁。丢包则多半是缓冲区太小。BUFFER_SIZE_UP设 1024 时如果主循环每秒打印超过 10KB 数据缓冲区会频繁满NO_BLOCK_SKIP模式下就丢数据。解决办法是加大缓冲区或者降低打印频率只打关键信息。我一般会在打印前判断SEGGER_RTT_HasData的剩余空间空间不足就跳过这条日志。6.4 从 Keil 迁移到 SES 的踩坑记录最后说说从 Keil 迁到 SES 的几个坑这些是我实际迁移 GD32F450 工程时遇到的。启动文件不兼容。Keil 用的.s启动文件和 SES 的汇编语法有差异直接拿过来编译会报错。解决办法是用 SES 自带的启动文件模板或者把 Keil 的启动文件按 SES 语法改写。我一般直接用官方固件库里的startup_gd32fxxx.sSES 能识别。分散加载文件要重写。Keil 的.sct文件 SES 不认要在Project Options→Linker→Linker Script里用 SES 的语法重新配。SES 的链接脚本是.icf格式语法和 GCC 的.ld类似。中断向量表名字不同。Keil 里中断服务函数名是固定的SES 里要在启动文件里确认向量表名字和你的xxx_it.c里的函数名一致。不一致的话中断进不去程序跑飞。__weak关键字处理。Keil 和 SES 对__weak的支持方式不同SES 里用__attribute__((weak))。如果固件库里有__weak定义的函数迁移时要注意替换。这些坑踩过一遍后后面再迁其他 GD32 型号就快多了。我现在建新工程基本 20 分钟能跑起来比第一次折腾一整天强太多。7. 进阶玩法RTT 与 SystemView 的联动7.1 SystemView 能解决什么问题RTT 解决的是看数据SystemView 解决的是看时序。它能记录任务切换、中断进出、API 调用等事件用时间轴的方式展示出来。对于跑 RTOS 的 GD32 项目SystemView 能直观看到哪个任务占了多少 CPU、中断延迟多大、有没有优先级反转。GD32F303 跑 FreeRTOS 时我用 SystemView 抓到过一个隐蔽问题一个低优先级任务持有互斥锁时间过长导致高优先级任务被阻塞了 8ms。这个问题用串口打印根本看不出来因为打印本身就会改变时序。SystemView 的记录是硬件级的对系统影响极小。7.2 SystemView 的移植与配置SystemView 的移植比 RTT 稍复杂需要这几个文件SEGGER_SYSVIEW.c、SEGGER_SYSVIEW.h、SEGGER_SYSVIEW_Conf.h、SEGGER_SYSVIEW_Config_FreeRTOS.c。最后一个是 FreeRTOS 的适配层如果你用的是其他 RTOS需要自己写适配。配置上SEGGER_SYSVIEW_Conf.h里要设SEGGER_SYSVIEW_RTT_CHANNEL一般用通道 2避免和 RTT 日志冲突。然后在 FreeRTOS 的FreeRTOSConfig.h里加#define configUSE_TRACE_FACILITY 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1并在任务切换钩子里调用SEGGER_SYSVIEW_OnTaskStartExec和SEGGER_SYSVIEW_OnTaskStopExec。这些钩子 FreeRTOS 已经预留好了填上就行。7.3 用 SystemView 定位实时性问题的实例前面提到的互斥锁阻塞问题SystemView 的时间轴上是这样显示的高优先级任务在等待锁时显示为阻塞态低优先级任务持有锁期间显示为运行态两者时间差就是阻塞时长。点开事件详情能看到是哪个 API 调用导致的阻塞。定位到问题后解决办法是把低优先级任务里持锁的操作拆细或者改用优先级继承的互斥锁。改完后重新抓一次阻塞时间从 8ms 降到 200 微秒实时性满足要求。SystemView 的另一个用途是验证中断延迟。在中断入口和出口各加一个SEGGER_SYSVIEW_RecordEnterISR和SEGGER_SYSVIEW_RecordExitISR时间轴上就能看到中断从触发到执行的延迟。GD32F303 在 120MHz 下这个延迟实测在 1-2 微秒符合 Cortex-M4 的预期。8. 我个人的几条实战心得RTT 的缓冲区地址在每次重新编译后可能变化如果你用脚本自动连接 RTT记得每次编译后更新地址或者用Auto Detection。我吃过一次亏脚本里写死了地址改了链接脚本后忘了更新排查了半天以为 RTT 坏了。SWD 速率不是越高越好。我见过有人为了追求下载速度把速率拉到 15MHz结果在 GD32F450 上十次有五次连不上。稳定比快重要4MHz 对绝大多数场景足够了。RTT 的SEGGER_RTT_printf在-O3优化下偶尔会出现格式化错误尤其是参数多的时候。如果发现打印结果不对先把 RTT 相关文件降到-O1试试。这个坑我在 GD32E230 上遇到过查了两天才定位到是优化问题。最后分享一个提高调试效率的小习惯在main.c里定义一个全局的调试开关宏发布版本把 RTT 调用全部编译掉避免调试代码影响正式固件的体积和时序。#ifdef DEBUG_ENABLE #define DBG_LOG(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else #define DBG_LOG(...) #endif这样一套代码既能调试又能发布不用维护两个分支。GD32 的 Flash 虽然够大但养成这个习惯对代码质量有好处。
返回列表