Skip to main content
控制嵌入式调试操作,包括连接设备、烧录固件、复位和访问内存。J-Link 探针使用 J-Link 后端,其他受支持的探针使用 OpenOCD 后端。

导入

方法

基于 Net 的 API 提供了用于嵌入式调试操作的方法。

方法参考

Net.get(name, type=NetType.Debug)

按名称获取一个调试 Net。
参数: 返回: 调试 Net 实例 注意: 调试 Net 必须在 channel 字段中配置目标设备名称(例如 ‘NRF52840_XXAA’、‘R7FA0E107’)。 连接目标设备(为该探针启动 gdbserver)。后端(J-Link 或 OpenOCD)会根据探针自动选择。
参数: connect() 先对 script 归类,然后才应用 openocd_configjlink_script。如果您同时给出 script 和其中之一,只有当 script 属于同一个后端时,显式参数才会胜出。属于另一个后端的 script 会先抛出 ValueError 返回: dict - 带有连接信息的状态字典 抛出: 如果该探针已有 gdbserver 在运行,而您既没有传 force 也没有传 ignore_if_connectedconnect() 会抛错。在 OpenOCD 上是 RuntimeError,在 J-Link 上是 JLinkAlreadyRunningError(来自 lager.debug)。
脚本覆盖只有在 gdbserver 重新启动时才生效。如果服务器已经起来,请传 force=True 用新脚本重启它。ignore_if_connected=True 会提前返回,不会重启。在 J-Link 上,它仍然会为后续操作重新指向该脚本。在 OpenOCD 上,覆盖用的 cfg 只会交给本次 connect() 调用启动的那个守护进程。之后的任何一次重启,包括自愈式重启,都会使用保存在 Net 上的 cfg。无效输入指的是一个既不存在、又不是合法 base64 的路径,或者空字符串。Net 会静默忽略这类输入,之前落地的脚本继续生效。覆盖的作用域是本 Net 和本次会话。Lager 把它写到按 Net 区分的路径下,而不是写到 Net 记录与 HTTP 调试服务共享的那份全局 Box 配置。disconnect() 会清除该覆盖。因此,用不同脚本连接的两个调试 Net 不会再互相干扰。
openocd_config 覆盖必须是完整的 cfg,而不是片段。Lager 在接口配置和目标配置之后才应用它。只要 Net 带有探针通道,启动命令行就仍然带着 lager 自己的 -c 'ftdi channel N'。除非先有一份 cfg 选定了 ftdi 适配器驱动,否则 OpenOCD 不认识这条命令。一份只包含比如 adapter speed 1000 的 cfg 会在启动时以 invalid command name "ftdi" 失败退出。

disconnect()

断开与目标设备的连接。
返回: dict - 状态字典

reset(halt=False)

复位设备。
参数: 返回: str - 复位操作的合并输出 自愈(两种后端都有): 刚执行完 flash() 之后有一小段时间,调试服务器还联系不上。在 J-Link 上,原因是重启后的 GDB 服务器的 PID 还观察不到;在 OpenOCD 上,原因是守护进程或 RPC 的短暂故障。在这个窗口里直接调用会抛错。reset() 在 J-Link 和 OpenOCD 两种后端上都会以有界退避重试,并且只在服务器确实已经停掉时才重启它。它绝不会拆掉已经起来的服务器,因此已挂接的 RTT 会话不受影响。调用方不再需要自己写重试包装。 DA1469x 例外: 在 DA1469x 上,flash() 会有意让服务器保持停止状态。烧录以一次软件复位收尾,而不是重启服务器,并且文档给出的流程是显式地、带暂停语义地重新连接。因此自愈在 DA1469x 上仍然会重试,但绝不会自动启动服务器。确实已经停掉的服务器仍然会照旧抛出原来的错误。自愈绝不会把服务器拉起到未暂停状态,因为未暂停的服务器可能返回无效的 QSPI-XIP 读数。

halt()

让目标停在原地,不做复位。仅 OpenOCD。
在 OpenOCD 上,flash() 需要有守护进程在运行。没有守护进程时它会抛出 RuntimeError,并提示您先调用 connect() 返回: str - 暂停操作的合并输出 reset() 一样,halt()flash() 之后服务器还联系不上的那一小段窗口里会重试。 这和 reset(halt=True) 不是一回事。后者执行的是 OpenOCD 的 reset halt,它会拉一下 nRESET,并从复位向量重新进入。halt() 发出的是一条纯粹的 halt,因此内核停在原地,nRESET 完全不会被触碰。 这个区别在从 QSPI 就地执行(XIP)的器件上很要紧。给这类器件烧录之后,reset(halt=True) 会重新运行引导程序,而不是停在您刚写入的镜像上。未暂停就重新挂接则有读到无效 XIP 数据的风险。烧录之后要在不扰动镜像的前提下挂接,就地暂停才是正确做法。
J-Link 没有独立的”就地暂停”原语,因为 reset_devicegdb_reset 都会先复位。在该后端上 halt() 会抛出 NotImplementedError,错误信息会指出”先暂停”的 .JLinkScript 才是受支持的途径。

flash(firmware_path, flash_address=None)

把固件烧录到设备。
参数: 返回: str - 烧录操作的合并输出 注意: .bin 内部不带地址,因此缺少 flash_addressflash() 会抛错,而不是默认用 0x0。请传入目标的 Flash 基地址:STM32 为 0x08000000,nRF52 为 0x00000000,DA1469x QSPI 为 0x16000000 OpenOCD 上的 DA1469x: 主线 OpenOCD 没有针对 DA1469x 外部 QSPI 的 Flash 驱动。在该系列上,flash()erase() 改为驱动常驻 RAM 的 flash_loader,与 lager debug <net> flash 走的是同一条路径。请传入绝对的 XIP 地址(0x16000000),与在 J-Link 上完全一样。缺少加载器时会抛错,而不是回退到 OpenOCD 的 program,因为后者够不到 QSPI。
  • 超出 0x160000000x17FFFFFF 的地址会在 Box 触碰探针之前就抛错。错误信息是 flash address 0x... is outside the DA1469x QSPI XIP window
  • 请烧录 .bin。加载器原样写入文件的字节,因此它不解析 .hex.elf 文件。
  • 加载器需要容器中 /home/www-data/customer-binaries/openocd/flash-loaders/da1469x/ 下的 flash_loader.elfflash_loader.elf.bin。在 Box 宿主机上,该目录是 ~/third_party/customer-binaries/openocd/flash-loaders/da1469x/LAGER_FLASH_LOADERS_DIR 可以替换上一级目录。
  • OpenOCD 没有针对该系列的内置目标配置,因此该 Net 需要一份 openocd_config
  • 调试读取丢失一次不会让加载器停下。加载器会一直重试,直到本步骤的截止时间。
  • 失败时抛出的错误会带上加载器最后一行进度信息。如果擦除阶段已经执行过,信息中会说明板子可能已经是空白的。请重新烧录一次。

erase()

擦除目标的 Flash。在多数目标上这是整片擦除,会擦掉全部 Flash 存储,包括保护设置。 在 DA1469x 上,erase() 只擦除外部 QSPI 从 0x16000000 开始的前 1 MiB。两种后端都是如此。OpenOCD 后端使用 flash_loader;J-Link 后端使用范围擦除,在该 Net 的 JLinkScript 中写一行 LAGER_ERASE_RANGE 可以设定不同的范围。
返回: str - 擦除操作的合并输出

read_memory(address, length)

从目标设备读取内存。
参数: 返回: bytes - 内存数据 自愈:reset() 一样,read_memory() 在两种后端上都会以有界退避跨过 flash() 之后那段短暂的稳定窗口重试。它只在没有服务器在运行时才重新连接,绝不会扰动正在使用的会话。erase() 的行为也一样。同样适用 DA1469x 例外。由于不会自动启动服务器,DA1469x 上烧录之后的读取会明确抛错,而不是返回未暂停状态下的无效 XIP 数据。

status()

获取当前连接状态。
返回: dict - running(bool)、pidbackend 它报告的是该探针上是否有 gdbserver 进程在运行。这并不是在陈述目标的状态:服务器可能比它所挂接的器件活得更久。CLI 的 lager debug <net> status 会分别报告这两种状态。

session(speed=None, transport=None, connect=True, ignore_if_connected=True, disconnect_on_exit=True)

带作用域的调试会话。它在进入时连接,并保证退出时拆除。这样,“烧录 → 挂接 RTT → 复位”这一安全顺序只需编码一次,而不必在每个脚本里重新摸索。with 的目标就是该 Net 本身,因此在块内可以使用完整的接口(flashrtt_defmtresetread_memory 等)。
参数: 返回: 一个产出该调试 Net 的上下文管理器。 它为什么与 RTT 相配: 进程内的 RTT 读取器具备重新挂接能力(见下文)。会话内的 flash()reset() 可能让 GDB 服务器短暂重启,而这样的短暂重启不会杀掉您在同一个块里打开的日志流。

rtt(channel=0, search_addr=None, search_size=None, chunk_size=None)

创建一个 RTT(实时传输)会话,用于与目标设备双向通信。
参数: 返回: RTT 上下文管理器,带有以下方法:
  • read_some(timeout) - 带超时读取可用数据(返回 bytes 或 None)
  • write(data) - 向目标写入数据(接受 bytes 或 str)
注意: 使用 RTT 之前调试连接必须已经建立。请先调用 connect() 具备重新挂接能力(两种后端都有): 当 GDB 服务器或守护进程在 RTT 读取器底下重启时,读取器会自行重新挂接。
  • J-Link。 一次 flash() 会短暂释放探针的 USB,并在相同端口上重启 GDB 服务器,这会让 RTT 套接字断开。reset() 通过 J-Link Commander 占用探针,也会造成同样的效果。读取器会重新挂接到同一个 RTT telnet 端口,而不是就此沉默。因此长时间运行的 read_some()rtt_defmt() 循环能够跨过一次烧录继续产出。
  • OpenOCD。 普通的烧录过程中守护进程一直在,因此套接字很少断开。万一断开了(守护进程被强制重启,或者 rtt-server 短暂重启),读取器会重新执行 rtt setuprtt server start 并重新挂接。
两种后端上的重新挂接都是有界的,上限为 30 秒。读取器只在服务器或守护进程重新起来之后才挂接,并且它绝不会去启动服务器。因此,像 DA1469x 那样有意让服务器保持停止的烧录,不会让读取器无限空转。读取器也不会扰动您有意让它停着的 DA1469x。rtt()rtt_defmt() 都不接受 reconnect 参数,因此重新挂接始终是开启的。

示例

烧录固件并复位

烧录前整片擦除

读取内存

CLI 命令(推荐)

在多数使用场景下,CLI 提供的接口更简单:
完整的 CLI 文档请参阅 CLI 调试参考

RTT 流式传输

SEGGER 实时传输(RTT)可以在调试期间与嵌入式设备进行高速双向通信(比 UART 快,且不影响时序)。
RTT 方法:
rtt().read_some() 返回的是未经解码的原始字节。用 defmt(嵌入式 Rust 事实上的标准)打日志的固件发出的是一种压缩的二进制格式 —— 对它调用 .decode('utf-8') 只会得到乱码。对于使用 defmt 的固件,请用下面的 rtt_defmt() 或 CLI 管道,两者都会经 defmt-print 解码。

rtt_defmt() 解码 defmt 日志

rtt_defmt(elf, channel=0) 打开一个 RTT 会话,并把它接入 defmt-print(Lager Box 上已预装),产出的是解码后的日志行而不是原始字节。elf 必须正是烧录在目标上的那个固件 —— defmt 需要它的符号元数据才能解码。
rtt_defmt() 返回一个上下文管理器,它提供: 和 CLI 管道一样,RTT 流本身永远不会结束。请用时间预算或行数上限来限制您的读取循环,然后退出 with 块。

一边解码日志,一边驱动固件

write() 让解码会话变成双向的,于是脚本可以发出一条命令,再对解码后的响应做断言。解码是单向的 —— defmt-print 只看得到上行通道 —— 因此写入会绕过它,直接送到目标。这是同时做到这两件事的唯一办法。RTT telnet 端口只接受一个客户端,所以您无法在 rtt_defmt() 之外再开一个原始的 rtt()
这要求固件在您打开的那个通道上声明了 RTT 下行缓冲区。仅有 defmt-rtt 只会建立上行缓冲区。没有下行缓冲区时,目标会静默丢弃您写入的任何内容。这看起来像是主机侧出了问题,但并不是。
参数: CLI 替代方案: 要交互式地跟看日志,请直接用管道接 CLI:lager debug <net> gdbserver --box <box> --rtt 2>/dev/null | defmt-print -e build/app.elf。请参阅 CLI 调试参考。需要在测试脚本里对日志内容做断言时用 rtt_defmt();只想看日志时用管道。

受支持的设备

J-Link 支持种类广泛的 ARM Cortex-M 及其他微控制器。常见的设备名称: 完整列表请参阅 SEGGER 的受支持设备页面

受支持的硬件

说明

  • 调试 Net 必须在 channel 字段中配置目标设备名称
  • 多数使用场景推荐用 CLI(lager debug
  • Python Net API 面向运行在 Lager Box 上的高级自动化脚本
  • 结束时请务必调用 disconnect() 以释放调试探针
  • erase() 进行整片擦除并清除保护设置(在 DA1469x 上,erase() 擦除的是 1 MiB 的 QSPI 范围)
  • RTT 需要已建立的调试连接(见上面的 RTT 流式传输一节)