1. 为什么我最终还是写了 rttsh 这么个工具做嵌入式开发的人对调试日志这事应该都有一肚子话要说。传统做法大多是 UART 串口输出板子上预留一个调试串口再通过 USB 转串口芯片接到电脑终端里开串口工具盯着看。这套方案本身没什么问题只要环境稳定、pin 脚够用、机器旁边有人盯着就能凑合跑。但真正到了批量验证、自动化回归、CI 编译上传一条龙的阶段UART 的硬伤就捂不住了。J-Link RTT恰好能解决这些场景的大部分痛点而rttsh这个命令行工具就是我把 RTT 真正变成“可脚本化、可自动化”的一套落地实践。先解释一下 RTT 到底是什么。RTTReal Time Transfer实时传输是 SEGGER 在 J-Link 调试器上提供的一项主机与目标芯片之间的双向数据传输技术。它不像 UART 那样需要额外的串口引脚也不像 SWO 那样受限于特定内核的引脚映射而是复用了调试口 SWD 或 JTAG 的数据通道。目标固件里只需要包含 SEGGER 的 RTT 库在内存里维护若干个环形缓冲区调试器侧通过 J-Link 直接访问芯片内存就能把日志、状态、传感数据实时搬到 PC 上。用生活化的类比这就相当于 J-Link 在芯片内存里给你开了一条“虚拟网线”不需要额外硬件只要调试连接还在数据就能抽出来。RTT 的优点是实打实的它不需要占用串口引脚波特率这个老话题直接消失它通过 SWD/JTAG 读取内存速度通常比 UART 的 115200 或 1M 快很多它支持多通道可以把普通日志、错误信息、二进制数据流分开它还是双向的既能让芯片往电脑发日志也能让电脑往芯片里灌命令。对嵌入式开发来说这套能力相当诱人。官方工具 RTT Viewer 也的确能把这些能力展示出来但问题在于它是个图形界面程序适合人工盯着看不适合无人值守的自动化。我一开始也没想过自己写工具。SEGGER 本身提供了 JLink.exe但 JLink.exe 的命令行语法是围绕 Flash 烧录和调试命令设计的对 RTT 数据流的处理并不原生。我的需求其实很具体固件烧进去之后让板子自动跑测试流程RTT 日志实时抓下来判断有没有出现预期关键字把中间产生的数据存成文件最后输出一个供 CI 判断的退出码。这套逻辑如果用 RTT Viewer 加人肉操作来完成等于每个版本发布前都要蹲在工位前加班根本谈不上持续集成。rttsh的定位因此变得很清楚一个可以把 RTT 读、写、存、判断、循环操作全部用脚本表达出来的命令行工具。这个工具适合谁我觉得主要有三类人第一是做固件功能验证的嵌入式工程师手上板子多、测试用例多不想整天盯终端第二是负责 CI/CD 基础设施的工程师需要让自动化测试跑在真实硬件上第三是最近开始尝试AI 在板调试的开发者想让 RTT 数据流成为大模型分析问题的信息来源。下面我把这个项目的设计思路、核心实现、实操脚本、CI 接入经验和常见坑完整梳理一遍。2. 脚本化是怎么设计的核心思路是什么2.1 为什么选择脚本化而不是再加一个图形界面图形界面有图形界面的价值它直观、上手快适合开发阶段快速看日志。我并不是想否定 RTT Viewer而是想明确“人工调试”和“自动验证”是两种完全不同的场景。人工调试的时候你需要的是漂亮的窗口、高亮的日志、随时点击暂停自动验证的时候你需要的是稳定的命令、可预测的输出、可解析的返回码。同一个工具很难同时把这两件事做到极致。我的结论是做命令行工具把脚本化放在第一位。脚本化意味着用户可以在 CI 的 shell 里一行命令完成任务也可以在本地交互式终端里手动敲指令调试。它不需要打开一个会抢焦点的窗口不需要等待 GUI 初始化也不依赖显示器。对跑在虚拟机或者远程构建机上的任务来说命令行形态是唯一可行解。脚本化还有一个隐蔽好处可复现。图形界面的操作路径依赖鼠标位置、菜单状态、当前焦点很难形成稳定的操作记录而脚本文件本身就是操作记录写进版本库之后同一套验证逻辑可以在任何时间、任何机器上重放。我后来在实际项目里体会到脚本化真正解决的不只是“自动化”还有“测试用例的可维护性”。2.2 脚本语法尽量贴近命令行直觉设计脚本语法的时候我给自己定了个原则命令行怎么敲脚本就怎么写不要让用户去学一套自成体系的 DSL。如果每引入一个自动化工具都要求用户先背一整套专属语法学习成本会抵消收益。rttsh 的脚本命令大致分五类。连接与设备配置connect、device、if、speed、sn通道控制channel、up、down数据读写read、write、send、dump、save流程控制wait、expect、assert、timeout目标控制reset、halt、go举个例子一个最基本的 RTT 交互脚本长这样# 连接 STM32F103C8SWD 接口速度 4000KHz connect -d STM32F103C8 -if SWD -speed 4000 # 复位让固件重新启动 reset # 等待串口输出中出现 boot done wait boot done 5000 # 向目标发送 help 命令 send help\n expect usage: 2000 # 退出码 0 表示通过 exit 0这段脚本的语义一目了然连接、复位、等待日志、发送指令、检查回显、退出。它没有引入“变量作用域”“函数重载”之类的抽象概念但对固件验证来说已经足够。我在实际使用中还加了一个-c参数可以让用户在命令行直接传一段脚本字符串适合临时跑一句检查比如rttsh -d STM32F103C8 -c reset; wait alive 3000; exit 0这样就不用每次为了一个小检查去建脚本文件。2.3 核心设计取舍读数据不丢帧写数据要排队嵌入式日志有一个常见问题数据是持续产生的如果主机读慢了环形缓冲会被新数据覆盖如果主机读太快USB 带宽又不一定跟得上。RTT 的环形缓冲机制本身有一定容错能力但实际操作中仍然会出现丢日志。rttsh 的做法是把 RTT 上行数据分成两层处理底层负责不断从 J-Link 拉取数据写入内存中的环形队列上层脚本逻辑根据需要去队列里拿数据。这样即使脚本正在执行expect等待某个关键字底层的数据读取也不会停。我踩过的一个坑早期版本是在脚本执行到wait时才去读取 RTT 数据结果发现固件启动很快日志在进入wait之前就已经把缓冲区冲掉了。改成后台持续读取之后丢帧问题基本消失。这算是一个比较重要的设计经验凡是做嵌入式日志工具数据采集和消费逻辑一定要分开。下行写的设计同样有讲究。脚本往 RTT 通道写数据时如果目标芯片的 RTT 缓冲区已满写入操作会失败。rttsh 内部维护了一个发送队列脚本调用send时只把数据放进队列后台线程按照目标缓冲区状态逐步发送。这样脚本不会被下游阻塞也不容易在写命令时丢指令。2.4 为什么不用现成的脚本语言来扩展有人可能会问为什么不直接用 Python 或者 Lua 写控制逻辑让用户用这些语言操作 J-Link这确实是个合理选择很多开源工具也这么干。Python 的优势是生态丰富数据分析和 AI 对接更方便。但我的场景里有一个矛盾嵌入式测试脚本往往需要在目标板没跑系统、甚至刚上电的情况下执行脚本引擎本身的启动时间、依赖安装、解释器环境都成了不确定因素。rttsh 选择内置一套最小命令集本质上是在“表达力”和“可部署性”之间做了取舍。命令集虽然简单但wait、expect、assert这几个原语搭配循环和标签已经能覆盖绝大多数验证流程。真遇到特别复杂的逻辑用户可以先用 rttsh 把 RTT 数据导成文件再用 Python 去做后续分析。工具不越界反而更好用。3. 核心细节与实现机制3.1 J-Link 连接参数芯片型号、接口、速度、序列号连接 J-Link 的第一道门槛是参数配置。J-Link 需要知道目标芯片的型号、调试接口类型、通信速率以及具体使用哪一台调试器。rttsh 的连接参数设计参考了 J-Link Commander 的习惯方便用户从官方工具迁移过来。# 连接指定串号的 J-Link使用 SWD 接口速率 4000KHz rttsh connect -d STM32F407VE -if SWD -speed 4000 -sn 123456789 # 如果只有一个 J-Link可以省略 -sn rttsh connect -d GD32F303 -if SWD -speed 2000芯片型号的选择很关键因为 J-Link 需要根据型号确定目标内核、Flash 映射和 RAM 地址范围。型号名最好从 SEGGER 的 J-Link Software Pack 文档里查不同厂商芯片有不同写法比如 STM32F103C8 直接按型号写部分国产芯片可能需要用相近内核型号代替。遇到型号不匹配时即使连接成功后续读取 RTT 控制块也可能失败。调试接口方面SWD 足够应付大多数场景只需要两根线加地线。JTAG 在个别芯片上才会用到。速率不是越快越好线材质量、抗干扰能力和目标芯片的调试口驱动能力都会影响稳定性。我习惯先用-speed 4000试跑如果出现掉线再降到-speed 1000。高速连不上时优先降速而不是怀疑芯片坏了。多调试器场景下序列号是唯一可靠标识。没有指定序列号时J-Link 驱动会优先选择可以连接的设备这在只有一块板子的开发机上没问题但在 CI 构建机上很容易拿错设备。所以我的脚本里涉及到批量测试的场景一律带-sn参数。3.2 RTT 通道与缓冲区到底是怎么运作的SEGGER RTT 库在目标芯片 RAM 中维护一个 RTT 控制块Control Block里面记录了上行缓冲区和下行缓冲区的配置信息。默认情况下RTT 会建立三个通道通道 0 是标准终端输出通道 1 和通道 2 是预留的用户自定义通道。固件端通过SEGGER_RTT_WriteString(0, ...)这类 API 往通道写数据主机端则读取对应通道的缓冲区数据。rttsh 连接目标后第一件事就是找到这个控制块。J-Link 理论上可以根据调试接口的信息定位 RAM 地址但实际场景经常需要手动指定。-a参数可以直接指定控制块地址-s参数可以设置搜索范围。比如这样rttsh -d STM32F407VE -a 0x20000000如果控制块地址不确定可以让 rttsh 在 RAM 区域内扫描。扫描范围越大耗时越长所以不建议把整个 RAM 都扫一遍。我更推荐在固件启动早期就固定控制块地址这在后续自动化中能省下不少排查时间。缓冲区大小同样值得关注。默认的 RTT 上行缓冲区大小通常只有几 KB对人工调试够用但 CI 里固件跑批量压力测试时日志量和数据量会大得多。如果缓冲区被写满目标固件写入 RTT 会阻塞或者丢弃直接影响测试结果。我的建议是在固件初始化时通过SEGGER_RTT_ConfigUpBuffer把上行缓冲区调大比如 8KB 或 16KB然后 rttsh 这边加长读取周期两边匹配起来丢数据的情况会少很多。3.3 超时、错误处理与退出码CI 稳定性的基石命令行工具如果要接入 CI就必须有一套明确的退出码约定。rttsh 的退出码规则很简单脚本执行成功返回 0expect或assert失败返回 1连接失败返回 2超时返回 3。上层流水线可以根据不同返回码做不同处理比如超时重试、连接失败报警、断言失败直接构建失败。超时设计是 CI 稳定性的关键。实际嵌入式测试中固件启动时间会受电源、时钟、Flash 加载等因素影响每次的延迟不一定相同。如果脚本里每个wait都写死 5 秒某一次芯片上电慢了半秒脚本就可能稳定失败。rttsh 对每个流程控制命令都支持独立超时参数同时也有一个全局超时作为安全兜底rttsh --script smoke.rtt --global-timeout 60000全局超时会在整个脚本执行超过 60 秒时强制中止避免某个wait因为目标板死机而让构建任务挂死。这类保护机制在 CI 里非常实用因为构建系统通常有自己的 job 超时如果 job 级超时比脚本超时先到排查问题时会更加迷糊。3.4 RTT 数据导出CSV、二进制、滚动窗口数据导出功能是我做这个工具的直接发起点。固件测试经常需要抓传感数据、电压曲线、压力测试结果这些数据走 RTT 通道源源不断产生如果只是打印在终端上后续分析根本没法做。rttsh 的dump命令支持把指定通道数据保存成不同格式。--format text保存纯文本日志适合常规调试信息--format csv按时间戳和通道号组织数据适合后续画图和分析--format bin直接保存原始二进制流适合协议数据、采集数据举个例子抓取通道 1 的二进制数据 5 秒并保存为文件rttsh -d STM32F407VE -c channel 1; dump /tmp/sensor.bin --format bin --duration 5000时间戳是数据导出里很容易被忽略但极其重要的字段。rttsh 默认在文本和 CSV 导出中带上主机侧时间戳这样后续分析时可以把 RTT 日志和外部设备动作比如上位机指令、继电器通断对齐。我没有采用目标芯片内部时钟作为时间戳因为很多固件并没有 RTC 或者没有网络对时主机时间戳虽然不精确到控制周期但足够用于大多数问题定位。4. 实际操作把 rttsh 用起来4.1 最简场景本地抓取 RTT 日志拿到一块板子、一个 J-Link最朴素的需求就是把芯片打印的 RTT 日志显示到终端。rttsh 提供了交互模式运行后会进入一个类似 shell 的环境支持手动输入命令同时也支持直接把抓取的日志实时打印到 stdout。rttsh -d STM32F407VE -if SWD -speed 2000连接成功后输入channel 0就可以看到通道 0 的数据流。按 CtrlC 退出。这个场景看起来和 RTT Viewer 没什么区别但区别在于它不依赖图形界面可以跑在 SSH 会话里也可以被别的脚本通过管道处理rttsh -d STM32F407VE -c channel 0 | grep ERROR一行命令就把 RTT 日志接入了传统的 Unix 管道生态。熟悉嵌入式命令行开发的人会很喜欢这个设计因为它意味着 RTT 日志不再被 GUI 绑定而是成为一条普通的文本流可以随意用 grep、awk、sed 处理。4.2 批量脚本验证固件冒烟流程的骨架“批量脚本验证”是我提到 rttsh 时最常强调的场景。以 STM32 固件为例我希望每次编译完成后自动做一轮冒烟测试判断固件能不能正常启动、各模块初始化有没有报错、命令行接口是否响应。完整流程对应一个脚本文件例如smoke.rtt# 连接设备指定串号避免拿错 connect -d STM32F407VE -if SWD -speed 4000 -sn 123456789 # 目标板已经烧录好固件这里做一次复位 reset # 等待启动日志关键词 wait system_init_done 8000 # 发送命令验证命令行响应 send version\n expect version 1.2.0 3000 send memtest\n expect memtest pass 10000 # 全部通过退出码 0 exit 0这段脚本的执行结果只有两个状态通过和失败。CI 里只需要根据退出码判断构建是否继续。脚本中的wait和expect是轮询式的不是简单的 sleep 之后查一次这样可以避免固件启动时间波动导致的误判。实际执行时wait会持续读取 RTT 数据直到匹配关键词或者超时所以即使日志比预期晚几百毫秒出现也不会失败。一个建议把脚本和固件产物一起提交到代码仓库比如放在tests/rtt/smoke.rtt目录下。这样固件代码变动时对应的验证脚本也一起走评审、一起发版本避免出现“代码更新了测试脚本还停留在上一个版本”的状态。4.3 数据导出与离线分析不只为了看日志批量验证时很多时候不能只看最终通过或失败还要保留证据。rttsh 的dump命令可以在整个测试过程中持续保存数据测试结束后统一分析。我常用的做法是让脚本在运行的同时开两个通道通道 0 记录系统日志通道 1 记录采集数据。rttsh --device STM32F407VE --script full_test.rtt \ --dump-ch 0 --format text --file /artifacts/log.txt \ --dump-ch 1 --format csv --file /artifacts/data.csv测试结束后CI 把这些文件作为 artifact 归档。后续如果固件出了问题哪怕测试本身是通过的也能通过日志回溯现场。如果测试失败而日志显示的是某个传感器数据越界排查方向就明确很多。我之前做压力测试时遇到过这种情况96 小时连续运行第 80 小时开始偶发丢数据但现场早就没人盯终端了。全靠保留的 RTT 日志和采集数据才能定位到问题出在 DMA 缓冲区溢出。没有自动数据导出这种问题根本无从查起。4.4 和 VSCode 开发环境的配合最近很多人习惯用VSCode 搭建 STM32 开发环境配合 Cortex-Debug 插件下载和调试。下载调试是 Cortex-Debug 擅长的但它不擅长在完成下载后自动跑 RTT 脚本验证。我的做法是在 VSCode 的任务系统里加一个 task编译完成之后自动调用 rttsh。{ label: build-and-smoke, type: shell, command: make -j8 rttsh --device STM32F407VE --script tests/rtt/smoke.rtt --global-timeout 30000 }这样按一下快捷键固件编译、烧录、RTT 冒烟测试就连续完成。如果 rttsh 返回失败VSCode 的终端会显示红色错误问题反应得很直接。我把这个 task 绑定到构建前任务每次改完代码都能快速确认“没有把板子跑坏”我个人的开发效率提升非常明显。5. 接进 CI让每轮提交都自动冒烟5.1 CI 里跑 J-Link 的硬条件CI 和本机开发最大的区别是构建机不一定会插着 J-Link也不一定有足够的 USB 权限。要想让 rttsh 在 CI 里稳定运行先要满足几个硬条件。设备物理连接是前提。如果 CI 跑在普通云服务器上理论上不可能接触本地 J-Link这种场景需要用自托管 runner并且把 J-Link 插在 runner 所在机器的 USB 口。虚拟化环境还需要做 USB 直通否则虚拟机里看不到设备。我曾经见过有人把 J-Link 插在宿主机但期望容器内直接访问结果折腾半天。容器默认不会自动共享宿主机的 USB 设备这个问题很常见。软件环境方面runner 上需要安装 SEGGER J-Link Software Pack。rttsh 最终通过 J-Link 的动态库和驱动访问设备驱动缺失时即使硬件连着也会报 no j-link found。Windows 机器安装官方驱动后一般都能识别Linux 机器则要留意把当前用户加入plugdev组或者配置 udev 规则。SUBSYSTEMusb, ATTR{idVendor}1366, MODE0666, GROUPplugdev这段 udev 规则让普通用户可以直接访问 SEGGER 的 USB 设备SEGGER vendor ID 是 0x1366配置完记得udevadm control --reload-rules。5.2 no j-link found 怎么解决一套完整排查顺序“no j-link found” 是使用 J-Link 相关工具时撞到的第一面墙rttsh 也不例外。根据我自己的排查经验按顺序检查基本能解决九成问题。第一步确认设备在系统里有没有被识别。Linux 执行lsusb | grep -i seggerWindows 打开设备管理器看有没有 J-Link 相关设备。这个环节过不了后面的都不用谈。第二步如果设备没有被识别换一条数据线、换一个 USB 口。J-Link 对线材和 USB 供电有一定要求劣质线特别容易出现时连时断。第三步如果设备被识别但 rttsh 仍找不到检查权限。Linux 下用dmesg | tail看有没有 permission denied 的信息Windows 下看驱动是不是被某些优化软件屏蔽。第四步如果你的命令行里指定了-sn序列号确认这个序列号真实存在。可以用 J-Link Configurator 查看已连接设备的序列号。第五步确认没有其他进程占用 J-Link。某些调试工具会独占连接导致新的命令工具无法访问。关掉 VSCode 里的调试会话或者 RTT Viewer 再试。这个排查顺序是我踩了无数次坑之后总结出来的。最容易忽略的是驱动和权限问题每次都想着是设备坏了结果不是线的问题就是权限的问题。5.3 the connected j-link is defective. proper 是怎么回事使用中偶尔会看到一段很吓人的提示the connected j-link is defective. proper [something]。这个问题本质上是 J-Link 固件校验不通过一般出现在这几个原因J-Link 固件升级中断、调试器固件损坏、使用了克隆设备或者固件与当前驱动不匹配。处理办法分几步。先用 SEGGER J-Link Configurator 识别设备如果 Configurator 能识别就重新升级一遍固件。大多数情况下固件重新刷写后问题就能解决。如果 Configurator 自己都识别不到换一台电脑试试有时候是当前 USB 控制器供电不稳定导致固件加载异常。对于克隆设备我建议直接换正版或至少换成能正常识别固件的设备继续用只会让 CI 在莫名其妙的地方失败。CI 场景下更稳妥的做法是在 J-Link 固件刷好之后先跑一个最简单的 rttsh 连接命令预检确认设备可用再进入正式测试流程。预检失败直接标红而不是让后面十多个测试任务都因为设备问题超时。5.4 GitHub Actions / GitLab CI 的接入方式GitHub Actions 和 GitLab CI 默认都跑在云端隔离环境无法直连物理 J-Link所以必须使用自托管 runner。GitHub 在仓库的 Settings 里添加自托管 runnerGitLab 则有 runner 注册机制。关键是runner 机器要插 J-Link而且同一时间只服务一个使用该设备的 job否则多个 job 抢设备会造成互踢。GitLab CI 里一个典型的 RTT 冒烟测试 job 长这样stages: - build - smoke smoke_test: stage: smoke tags: - jlink-runner script: - rttsh --device STM32F407VE --script tests/smoke.rtt --global-timeout 60000 artifacts: paths: - artifacts/log.txt - artifacts/data.csv when: alwaysGitHub Actions 类似只是 yaml 写法不太一样。jobs: smoke: runs-on: self-hosted steps: - run: rttsh --device STM32F407VE --script smoke.rtt - if: always() uses: actions/upload-artifactv4 with: path: | artifacts/log.txt artifacts/data.csv接入 CI 之后有几件事需要持续维护确保 runner 不会被系统休眠USB 口不被其他设备占用J-Link 驱动不要随便升级因为新驱动可能改变设备枚举顺序。我个人的策略是固定一个经过充分测试的 J-Link Software Pack 版本只在专门的时间窗口统一升级。6. 用 rttsh 配合 AI 在板调试6.1 AI 为什么会用到 RTT 流最近嵌入式圈子逐渐有人讨论AI 在板调试说穿了就是把 LLM 当做一个能读长文本的分析助手把嵌入式系统的日志和上下文喂给它让它帮忙找异常模式。这种用法对 RTT 是天然契合的因为 RTT 能实时、低干扰地输出日志而 rttsh 又能把 RTT 数据流接到任意脚本里。两者结合就相当于给大模型装上了一根“探进芯片里的听诊器”。实际场景往往是这样板子偶发死机传统做法是人蹲在终端前面盯日志等复现。运气好等到了人已经看得头昏眼花运气不好几小时白耗。另一种场景是日志量巨大一次压力测试能产生几万行输出人工扫描根本不现实。rttsh 可以把关键窗口内的日志截取出来传递给 AI 模型做初步筛查工程师再根据结果做定向排查。需要注意rttsh 本身并不内置 AI 能力它负责的是“把数据干净地交出来”。AI 分析这一层完全可以由外部脚本实现这也正是脚本化的价值。6.2 我设计 rttsh 怎么配合 AI最直接的做法是先用 rttsh 把 RTT 日志导出成文本文件再用 Python 脚本调用大模型 API。我一般会在异常发生时自动抓取最后 N 秒的日志加上 MCU 状态信息一起发给模型。# 异常恢复后抓取最后 5 秒 RTT 日志 rttsh -d STM32F407VE -c dump /tmp/rtt_tail.log --format text --last 5000然后配合一段 Python 脚本把日志读进来组装成 prompt发给模型分析。下面的伪代码是我实际用过的分析流程import os from openai import OpenAI log open(/tmp/rtt_tail.log).read() client OpenAI(api_keyos.environ[LLM_API_KEY]) resp client.chat.completions.create( modelos.environ.get(LLM_MODEL, gpt-4o-mini), messages[ {role: system, content: 你是嵌入式固件调试助手根据 RTT 日志分析崩溃原因。}, {role: user, content: log[:8000]} ] ) print(resp.choices[0].message.content)关键是 prompt 要写清楚上下文。直接丢一段日志给模型它会泛泛而谈如果告诉它“这是 STM32 在 DMA 中断处理后的崩溃日志请关注堆栈指针和内存访问异常”分析质量会明显提升。6.3 一个实际的崩溃分析流程我用一个具体例子说明整套流程怎么走。某次固件在跑完 2000 次压力测试后触发 hard fault重启后 RTT 缓冲区里遗留了崩溃前的最后一段日志。传统做法是把日志从 RTT Viewer 里复制出来自己翻找寄存器异常点和最后几条操作记录。用 rttsh 配合 AI流程变成这样# 复位前先把当前 RTT 缓冲留存 rttsh -d STM32F407VE -c dump /tmp/crash_before_reset.log --format text --last 8000拿到crash_before_reset.log之后先看最后 50 行通常能发现一些表面异常。再把完整日志发给模型模型会给出一个候选原因列表并标注它认为关键的日志行。经过两轮实验最后定位到是某个外设寄存器在低功耗模式下被意外清零。如果纯靠人工从几千行日志里找大概率要耗掉半天。在执行这类分析时我的原则是模型给的是线索不是结论。它指出可能性之后仍然要在代码里找到对应的写入路径确认因果才能修改。否则很容易出现“模型说是 A 问题实际改了 B 问题”的情况。6.4 用 AI 调试要小心的地方用 AI 分析嵌入式日志有几个容易被忽视的边界。数据安全第一条。RTT 日志里可能包含固件内部函数名、内存地址、业务逻辑甚至密钥相关打印发送到云端模型前要先脱敏。我建议单独准备一份脱敏后的日志格式只保留错误码、状态值、时序信息不保留明文业务字符串。上下文长度要控制。LLM 的上下文窗口有限往模型里塞几十万行日志没有意义反而会让模型注意力分散。我的经验是只保留崩溃或异常前后几秒的数据必要时把日志缩减成“时间戳 错误码 关键函数名”的表格让模型按时间线分析。最后别让 AI 接管决策。rttsh 脚本可以自动采集数据、自动触发分析但固件代码修改必须走正常的代码评审流程。AI 调试的定位永远是辅助定位而不是自动修复。我见过把模型建议直接合入代码的团队后面差点因为一个错误的地址计算把量产固件搞挂。7. 常见问题与排查技巧速查根据我实际使用 rttsh 和 J-Link RTT 的经验整理了一份高频问题速查表基本覆盖了从连接、读取到 CI 接入会遇到的主要坑。现象可能原因处理办法no j-link found驱动未安装、权限不足、USB 未识别从 lsusb/设备管理器开始排查确认权限后重启服务the connected j-link is defectiveJ-Link 固件损坏或设备异常用 J-Link Configurator 重新刷固件检查 USB 供电RTT 连接成功但无输出目标固件未初始化 RTT、控制块地址不对检查SEGGER_RTT_Init()用-a指定控制块地址日志频繁丢失上行缓冲区太小或读取不及时增大缓冲区确认 rttsh 后台持续读取数据脚本运行超时但板子正常目标启动时间波动wait超时太短给wait和expect加余量用全局超时兜底多板同时测试时连错设备未指定 J-Link 序列号所有脚本统一加-sn 序列号在容器里找不到 J-Link容器未共享 USB 设备改用 host network 加 USB 透传或直接在宿主机跑 rttshexpect 总是失败目标输出含 ANSI 转义序列或大小写不同确认匹配关键词实际格式必要时用正则匹配连接成功后没有日志输出这个问题我专门想多提一嘴。它很多时候不是工具的问题而是固件问题。有些固件在调试器 attach 之前会主动关闭 RTT或者在中断里写日志时没做临界区保护。遇到 RTT 无输出先确认固件已经打印过内容再考虑工具参数。换一台调试器试跑也能帮助区分是工具问题还是设备问题。另外RTT 控制块地址搜索的耗时问题值得注意。虽然 rttsh 支持自动搜索但我建议在生产脚本里尽量指定明确的控制块地址。自动搜索意味着要扫描一段可能很大的 RAM 范围在低速调试口上可能耗费数秒甚至带来额外的稳定性风险。固件里通过SEGGER_RTT_SetControlBlockAddress或者链接脚本保证地址固定是更可控的做法。最后分享一个我自己长期使用的技巧在 CI 脚本里不要只依赖退出码也要把 RTT 日志原文作为 artifact 保留。测试通过的时候日志是存档测试失败的时候日志就是案发现场。rttsh 的dump配合 CI 的 artifact 机制能把每一次构建对应的板端行为全部留底。我靠这个习惯解决过好几个只在特定条件下偶发的疑难问题每次翻出历史日志对比都比重新复现快得多。做嵌入式自动化数据留痕这件事怎么强调都不过分。
阅读完成 · 觉得有帮助?