DIY:自制 DAPLink

0x00 序言:各种 IDE 和调试器

如标题所言,本文的主要任务是自行制作一个 DAPLink(事实上是「采用 CMSIS-DAP 协议的调试器」,但简中互联网已经将所有 CMSIS-DAP 兼容的调试器都称为 DAPLink 了,本文亦从之),用于调试 Cortex-M 单片机。笔者主要使用 RP2040 和 STM32 这两种 MCU,这两个月来体验了各种开发环境,因此在谈论硬件之前,先简单评论几句这些 IDE。

首先说说 PlatformIO。这是笔者当初使用 arduino 框架时,使用的 IDE。主要优点在于开箱即用,在 VS Code 中即可完成编码、下载、调试和串口监控。但问题也比较明显:

  • 黑盒构建系统。它使用了私有的 build 方式(尽管底层是用 CMake 实现的),用户需要将库的 .h.c/.cpp 文件放在 PlatformIO 指定的目录。当然,这是它的特性,使得 arduino 用户可以无缝迁移到 PlatformIO;但对于笔者这种主业偏软件的开发者来说,一个黑盒构建系统显然是难以接受的。
  • GUI 鲁棒性堪忧,宛如本科生的大作业。叠加上其黑盒特性,有时难以定位问题。
  • PlatformIO 会与其他插件互相干扰,造成一些麻烦。例如,PlatformIO 不兼容 clangd,在使用 PlatformIO 时需要将 clangd 换成默认的 C/C++ LSP server。这些都是琐碎的小问题,虽然能逐一解决,但虚耗精力。

笔者过去两个月主要使用 CLion 编写 RP2040 程序。它也是开箱即用的,直接支持 openocd 和 pyocd 调试,不过串口监控则需另找软件实现(如 MobaXterm)。特点:

  • 基于 CMake 的构建系统。这是非常现代化的设计,使得软件工程师可以轻易参与到硬件开发中。笔者绝不愿意把时间浪费在学习 Keil 的私有构建系统上,但愿意学几天 CMake,因为它是开放且通用的。CLion 可以完美适配 pico sdk,且能将 STM32CubeMX 生成的项目转化为 CMake 项目,因此也算是完美支持 STM32 HAL。
  • 基于 clangd 的语言引擎,见 Jetbrains 文档。clangd 是目前最优秀的 C/C++ LSP server。

另外,笔者使用 SEGGER Embedded Studio 开发 STM32。特点:

  • 黑盒构建系统。能很好地支持 STM32CubeMX 项目(见官方 wiki),但对 RP2040 不甚友好(见树莓派论坛)。
  • 开箱即用,而且有大量优化,例如将 printf 默认实现为 semihosting,一方面不再需要连接串口,另一方面可以节省 MCU 的 ROM 空间。
  • 与 J-Link 密切配合,原生支持 RTT 技术。

另外,这些 IDE 针对各种调试器的支持矩阵如下。其中「gdb server」表示用 openocd 或 pyocd 运行 gdb server(监听 TCP 端口),然后 IDE 作为 gdb client 连入。「原生支持」表示 IDE 厂商为调试器提供了专门适配。

PlatformIO CLion SEGGER STM32CubeIDE
DAPLink gdb server gdb server 原生支持 gdb server
STLINK gdb server gdb server 原生支持 原生支持
J-Link gdb server gdb server 原生支持 原生支持

SEGGER Embedded Studio 是原生支持 CMSIS-DAP 的,但需要将 Target Connection 设为 J-Link。这十分反直觉,笔者是从一篇论坛讨论中得知的。

下面,我们粗略评测各种调试器在各种烧录软件下的表现。笔者手头有 STLINK-V3SET、J-Link edu mini、树莓派 debugprobe(支持 DAP 协议,复用了 CMSIS-DAP 项目代码,见本站的源码阅读文章),以及各种国产 DAPLink。合影:

我们先来测试一下它们的下载速度。测试方法是用 SWD 协议下载 1MB 固件到 STM32H743VIT6,记录下载耗时,同时使用逻辑分析仪观察波形,记录 SWCLK 频率。

一般情况下,SEGGER IDE 和 pyocd 不是全量烧录,而是只烧录 rom 中被更改的部分。因此,我们每次下载前都要完全擦除固件,以免影响测试结果。相关指令如下:

# 擦除固件
openocd -f interface/jlink.cfg -c "transport select swd" -f target/stm32h7x.cfg  -c "init" -c "halt" -c "wait_halt" -c "stm32h7x mass_erase 0"

# openocd 下载 (5MHz)
openocd -f interface/jlink.cfg -c "transport select swd" -f target/stm32h7x.cfg -c "adapter speed 5000" -c "init" -c "halt" -c "flash write_image erase h7-download-test.elf 0x00000000" -c "reset" -c "shutdown"

# pyocd 下载 (5MHz)
pyocd flash -t stm32h743vitx -f 5mhz .\h7-download-test.elf

测试结果如下(擦除和下载过程都计入耗时):

调试器 软件 擦除+下载速度 设定频率 SWCLK 实际最高频率 日志
J-Link edu mini SEGGER 82.76 KB/s 4 MHz 3.01 MHz Total: 13.042s (Prepare: 0.139s, Compare: 0.414s, Erase: 6.594s, Program: 5.432s, Verify: 0.362s, Restore: 0.099s) Program speed: 188 KB/s
J-Link edu mini openocd 61.033 KB/s 5 MHz 3.01 MHz wrote 1019264 bytes from file h7-download-test.elf in 16.308746s (61.033 KiB/s)
J-Link edu mini pyocd 10.89 KB/s 5 MHz 3.01 MHz Erased 1048576 bytes (8 sectors), programmed 1019904 bytes (996 pages), skipped 0 bytes (0 pages) at 10.89 kB/s
STLINK-V3SET SEGGER 61.44 KB/s auto 34.48 MHz erase 149.1 KB/s, download 103.1 KB/s
STLINK-V3SET SEGGER 61.06 KB/s 100 MHz 34.48 MHz real freq applied is 24000 KHz, erase 147.3 KB/s, download 103.1 KB/s
STLINK-V3SET openocd 66.486 KB/s 30 MHz 34.48 MHz wrote 1019264 bytes from file h7-download-test.elf in 14.971294s (66.486 KiB/s)
STLINK-V3SET pyocd 32.05 KB/s 30 MHz 34.48 MHz Erased 1048576 bytes (8 sectors), programmed 1019904 bytes (996 pages), skipped 0 bytes (0 pages) at 32.05 kB/s
树莓派 debugprobe SEGGER 79.55 KB/s 20 MHz 15.87 MHz Total: 13.479s (Prepare: 0.142s, Compare: 0.371s, Erase: 6.605s, Program: 5.907s, Verify: 0.345s, Restore: 0.107s) Program speed: 173 KB/s
树莓派 debugprobe SEGGER 71.08 KB/s 10 MHz 9.01 MHz Total: 15.074s (Prepare: 0.174s, Compare: 0.421s, Erase: 6.618s, Program: 7.385s, Verify: 0.347s, Restore: 0.127s) Program speed: 138 KB/s
树莓派 debugprobe SEGGER 45.34 KB/s auto(2MHz) 1.96 MHz Total: 23.402s (Prepare: 0.363s, Compare: 0.447s, Erase: 6.625s, Program: 15.327s, Verify: 0.353s, Restore: 0.285s) Program speed: 66 KB/s
树莓派 debugprobe openocd 66.144 KB/s 20 MHz 15.87 MHz wrote 1019264 bytes from file h7-download-test.elf in 15.048535s (66.144 KiB/s)
树莓派 debugprobe pyocd 35.62 KB/s 20 MHz 15.87 MHz Erased 1048576 bytes (8 sectors), programmed 1019904 bytes (996 pages), skipped 0 bytes (0 pages) at 35.62 kB/s
创芯工坊 PWLINK2 Lite SEGGER 91.98 KB/s 20 MHz 10.99 MHz Total: 11.697s (Prepare: 0.092s, Compare: 0.366s, Erase: 6.618s, Program: 4.203s, Verify: 0.344s, Restore: 0.072s) Program speed: 243 KB/s
创芯工坊 PWLINK2 Lite openocd 66.163 KB/s 20 MHz 10.87 MHz wrote 1019264 bytes from file h7-download-test.elf in 15.044182s (66.163 KiB/s)
创芯工坊 PWLINK2 Lite pyocd 37.09 KB/s 20 MHz 10.87 MHz Erased 1048576 bytes (8 sectors), programmed 1019904 bytes (996 pages), skipped 0 bytes (0 pages) at 37.09 kB/s
国产 DAPLink (C6T6A) SEGGER 17.93 KB/s 20 MHz 1.47 MHz Total: 58.742s (Prepare: 1.442s, Compare: 0.468s, Erase: 6.716s, Program: 48.785s, Verify: 0.426s, Restore: 0.903s) Program speed: 21 KB/s
国产 DAPLink (C6T6A) openocd 34.459 KB/s 20 MHz 1.09 MHz wrote 1019264 bytes from file h7-download-test.elf in 28.886044s (34.459 KiB/s)
国产 DAPLink (C6T6A) pyocd 17.41 KB/s 20 MHz 1.09 MHz Erased 1048576 bytes (8 sectors), programmed 1019904 bytes (996 pages), skipped 0 bytes (0 pages) at 17.41 kB/s
国产 DAPLink (C8T6) SEGGER 17.93 KB/s 20 MHz 12.05 MHz Total: 58.381s (Prepare: 1.088s, Compare: 0.468s, Erase: 6.727s, Program: 48.770s, Verify: 0.427s, Restore: 0.898s)
国产 DAPLink (C8T6) openocd 34.480 KB/s 15 MHz 5.59 MHz wrote 1019264 bytes from file h7-download-test.elf in 28.868071s (34.480 KiB/s)
国产 DAPLink (C8T6) pyocd 18.01 KB/s 15 MHz 5.62 MHz Erased 1048576 bytes (8 sectors), programmed 1019904 bytes (996 pages), skipped 0 bytes (0 pages) at 18.01 kB/s

可以看出,openocd 烧写算法的速率上限就是 66 KB/s 左右,我们的 STLINK-V3SET、树莓派 debugprobe 和创芯工坊 PWLINK2 Lite 都达到了这个上限。其他的国产 DAPLink 速率较慢,它们的 MCU 是 STM32F103C8 甚至 C6,应该是 CMSIS-DAP 方案。

💡
按照 SEGGER 文档,J-Link edu mini 的 SWD 接口限速 4 MHz,J-Link base 则是 15 MHz。

STLINK-V3SET 内置的 MCU 是 STM32F723IEK6,主频 216 MHz,最后跑出的结果跟 RP2040 差不多,显得有点浪费性能(笑)。不过 V3SET 多出了 jtag、swim、can bus、spi 等接口。

另外,我们也可以看到,整体的下载速度与最高 SWCLK 频率几乎无关(事实上,在下载过程中,SWCLK 频率也很不稳定,例如 STLINK-V3SET 在一些时候能达到 34.48 MHz,在更多时候仅为 ~10 MHz )。因此,下载速率的瓶颈显然不在 swd 频率上,而是在其他地方。

有一篇帖子值得一读:让 CMSIS-DAP DAPLink 功能和性能上升到新高度。此文总结了限制下载速率的因素。

0x01 选型

我们已经考察了各种调试器的性能,现在该为自己的项目选型了。在 J-Link、STLINK、CMSIS-DAP 三种协议中,只有 CMSIS-DAP 是开放的,生态良好,且被各种 IDE 支持,所以我们选择自制一个 CMSIS-DAP 协议的调试器。

协议确定之后,该考虑用何种硬件实现。目前值得考虑的实现方案如下:

  • STM32F103xB。该 MCU 获 DAPLink 项目原生支持。
  • RP2040。使用树莓派 debugprobe 项目,连固件都无需自行编译。此前已经在学习笔记中实践过这一方案
  • CH552。这是增强型 8051 单片机,优势在于免晶振,且价格低廉。项目见 posystorage/CH55x_HS_DAP-Link-v2
  • CH32V203/CH32V305。它们是 RISC-V 内核的 MCU,前者免晶振,后者支持 usb hs(但其他方案的速度瓶颈应该不在 usb fs 上)。项目见 XIVN1987/DAPLink

从稳定性的角度考虑,DAPLink 和 debugprobe 项目最合适,它们接受了社区开发者的广泛验证。debugprobe 对笔者而言是最简单的方案,且在上文的测试中表现优秀。另一方面,沁恒方案也很有吸引力:让我们考虑 J-Link OB 或者 STM32 Nucleo 系列开发板的板载 STLINK,假如只需要一片 CH552 和一些阻容就能实现它们的功能,让开发板自带 SWD 和串口,那何乐而不为呢?功耗、成本、占用面积都未增大多少,而我们只需要一根 usb 线连接电脑,无需掏出调试器和杜邦线了。

因此,笔者打算实践上述所有方案,把坑都踩一遍。现在来设计一块 pcb,由 type c 线连接到电脑,用 CH334P 提供 usb hub 功能,连接到四块待测试的 MCU。这些 MCU 需要提供 swd 功能和串口功能,通过一个 8 pin 接口和一个 stdc14 接口引出。

原理图:

💡
这张原理图的 stdc14 接口有误。按照 ST 文档stdc14 接口的 13 号引脚实际上是 T_VCP_RX,即 target MCU 的 RX、调试器的 TX。上面的原理图上搞反了 13、14 号引脚。

接下来利用 KiCad 的层次原理图,设计四种调试器的电路。RP2040 方案:

💡
为了节省面积并测试稳定性,这个电路省略了大量阻容。晶振部分,用 YXC 的晶振替代 ABM8-272-T3,省略电容和防过驱电阻;供电部分,省略了多个 100nF 电容,且 flash 与 RP2040 共用去耦电容。

DAPLink STM32F103CBT6 方案:

💡
设计 STM32F103CBT6 方案时,笔者参考了合宙的原理图。「稀饭 kicad 」先生在 B 站上发布了用 KiCad 设计 DAPLink 的教程视频,值得一看。

CH552 方案:

💡
图中 JP3 用于拉高 D+,使 CH552 进入 ISP 状态,从而可以下载固件。参考沁恒手册。
其实可以考虑省略硬件 RST 信号,因为我们一般利用 swd 来复位目标 MCU。

CH32V203 方案:

0x02 实验:CH552 方案

焊好 pcb,我们开始测试这些方案。首先是 CH552,新芯片上电之后自动进入 usb 下载模式(尽管文档中称下载时必须提供 5V 供电,但笔者实践上发现初次下载时 3V3 供电亦可工作)。用沁恒的烧录工具刷入 CH55x_DAPLink_3V3_IO_16M.hex 固件之后,可以识别到 CMSIS-DAP 调试器:

连到目标芯片,擦除 flash。过程中出了一点问题:

再重复运行擦除指令,这次成功执行。用 SEGGER 能连接上 CMSIS-DAP 设备:

Connecting 'J-Link' using 'USB'
Loaded C:/Program Files/SEGGER/SEGGER Embedded Studio 8.14a/bin/JLink_x64.dll
Firmware Version: DAPLink CMSIS-DAP
DLL Version: 7.98a
Hardware Version: V2.00
Target Voltage: 3.300
Device "STM32H743VI" selected.
ConfigTargetSettings() start
ConfigTargetSettings() end - Took 10us
InitTarget() start
SWD selected. Executing JTAG -> SWD switching sequence.
DAP initialized successfully.
InitTarget() end - Took 10.8ms
Found SW-DP with ID 0x6BA02477
DPv0 detected
CoreSight SoC-400 or earlier
Scanning AP map to find all available APs
AP[3]: Stopped AP scan as end of AP map has been reached
AP[0]: AHB-AP (IDR: 0x84770001)
AP[1]: AHB-AP (IDR: 0x84770001)
AP[2]: APB-AP (IDR: 0x54770002)
Iterating through AP map to find AHB-AP to use
AP[0]: Core found
AP[0]: AHB-AP ROM base: 0xE00FE000
CPUID register: 0x411FC271. Implementer code: 0x41 (ARM)
Cache: L1 I/D-cache present
Found Cortex-M7 r1p1, Little endian.
FPUnit: 8 code (BP) slots and 0 literal slots
CoreSight components:
ROMTbl[0] @ E00FE000
[0][0]: E00FF000 CID B105100D PID 000BB4C7 ROM Table
ROMTbl[1] @ E00FF000
[1][0]: E000E000 CID B105E00D PID 000BB00C SCS-M7
[1][1]: E0001000 CID B105E00D PID 000BB002 DWT
[1][2]: E0002000 CID B105E00D PID 000BB00E FPB-M7
[1][3]: E0000000 CID B105E00D PID 000BB001 ITM
[0][1]: E0041000 CID B105900D PID 001BB975 ETM-M7
[0][2]: E0043000 CID B105900D PID 004BB906 CTI
I-Cache L1: 16 KB, 256 Sets, 32 Bytes/Line, 2-Way
D-Cache L1: 16 KB, 128 Sets, 32 Bytes/Line, 4-Way
Current Speed: 2000 kHz
ConfigTargetSettings() start
ConfigTargetSettings() end - Took 10us
InitTarget() start

但是 SEGGER 下载失败:

Preparing target for download
Executing Reset script TargetInterface.resetAndStop()
Reset: Halt core after reset via DEMCR.VC_CORERESET.
Reset: Reset device via AIRCR.SYSRESETREQ.
Setting Startup Completion Breakpoint
Startup completion breakpoint set at __startup_complete
Downloading 'h7-download-test.elf' to STM32H743VI
Programming 995.3 KB of addresses 08000000 - 080f8d6d
Failed to preserve target RAM @ 0x24000000-0x2407FFFF.
Failed to prepare for programming.
Communication timed out: Requested 4 bytes, received 0 bytes !
Download failed

openocd 在下载中途也提示失败:

openocd -f interface/cmsis-dap.cfg -c "transport select swd" -f target/stm32h7x.cfg -c "adapter speed 5000" -c "init" -c "halt" -c "flash write_image erase h7-download-test.elf 0x00000000" -c "reset" -c "shutdown"

逻辑分析仪观察到 SWCLK 在大约 7s 后便不翻转:

考虑是否 CH552 无法提供 5 MHz 的 swd 频率。降频到 500 kHz,成功烧录:

openocd -f interface/cmsis-dap.cfg -c "transport select swd" -f target/stm32h7x.cfg -c "adapter speed 500" -c "init" -c "halt" -c "flash write_image erase h7-download-test.elf 0x00000000" -c "reset" -c "shutdown"

[stm32h7x.cpu0] halted due to debug-request, current mode: Handler HardFault
xPSR: 0x01000003 pc: 0x080001ca msp: 0x2407ffe0
Info : Device: STM32H74x/75x
Info : flash size probed value 2048k
Info : STM32H7 flash has dual banks
Info : Bank (0) size is 1024 kb, base address is 0x08000000
Info : Padding image section 0 at 0x080f8d6e with 18 bytes (bank write end alignment)
Warn : Adding extra erase range, 0x080f8d80 .. 0x080fffff
auto erase enabled
wrote 1019264 bytes from file h7-download-test.elf in 28.301851s (35.170 KiB/s)

逻辑分析仪观察到 SWCLK 的实际频率是 1.33 MHz 左右,我们在 openocd 中设置的 adapter speed 参数似乎并不影响 SWCLK 频率。

再次尝试使用 SEGGER 烧录,频率设为 100 kHz,仍然失败。pyocd 倒是可以正常下载:

pyocd flash -t stm32h743vitx -f 5mhz .\h7-download-test.elf

0001143 W Board ID 3V3- is not recognized [mbed_board]
0002084 I Loading C:\Users\blue\Desktop\work\0730\h7-download-test\h7-download-test\Debug Internal\h7-download-test.elf [load_cmd]
[==================================================] 100%
0043176 I Erased 1048576 bytes (8 sectors), programmed 1019904 bytes (996 pages), skipped 0 bytes (0 pages) at 24.24 kB/s [loader]

CH552 方案总结:

调试器 软件 擦除+下载速度 设定频率 SWCLK 实际最高频率 备注
CH552 SEGGER - - - 无法使用 SEGGER 下载
CH552 openocd 35.170 KB/s 500 kHz 1.79 MHz 无视用户指定的 swd 频率,但太高会导致失败
CH552 pyocd 24.24 KB/s 5 MHz 1.79 MHz 无视用户指定的 swd 频率

实验发现,CH552 方案鲁棒性很差,出错之后,经常需要手动复位 CH552 或目标 MCU,才能继续工作。另外,在烧录 flash 之后,openocd 擦除指令第一次执行时必定失败,第二次执行则可成功。

结论:posystorage/CH55x_HS_DAP-Link-v2 方案不实用。另外,粗看了一眼代码,复用了 CMSIS-DAP 项目的 DAP.c,使用沁恒提供的 usb 协议栈。代码质量非常之差,似乎作者没有受过软件工程训练。

0x03 实验:CH32V203 方案

CH32V203C8T6 是沁恒推出的 32 位 RISC-V MCU,拥有 64 kB flash 和 20 kB RAM,最高主频 144 MHz,远高于 MCS-51 内核的 CH552,且价格也相当低廉(市价 2.3 CNY;与之对比,8051 内核的 CH552G 为 1.59 CNY)。烧录 CH32V203 需要使用 WCH-Link

我们使用 XIVN1987/DAPLink 项目,21ic 论坛有作者的帖子。这个项目需要用沁恒的 MounRiver IDE 编译,观察代码:

此项目复用了 CMSIS-DAP 的全部代码,使用沁恒的 usb hid 协议栈。代码质量高于前文提到的 CH552 项目。

烧录:

电脑识别到 CMSIS-DAP 和串口:

现在开始测试。SEGGER 下载正常:

Preparing target for download
Executing Reset script TargetInterface.resetAndStop()
Reset: Halt core after reset via DEMCR.VC_CORERESET.
Reset: Reset device via AIRCR.SYSRESETREQ.
Setting Startup Completion Breakpoint
Startup completion breakpoint set at __startup_complete
Downloading 'h7-download-test.elf' to STM32H743VI
Programming 995.3 KB of addresses 08000000 - 080f8d6d
J-Link: Flash download: Bank 0 @ 0x08000000: 1 range affected (1048576 bytes)
J-Link: Flash download: Total: 58.480s (Prepare: 1.080s, Compare: 0.472s, Erase: 6.721s, Program: 48.878s, Verify: 0.428s, Restore: 0.898s)
J-Link: Flash download: Program speed: 21 KB/s
Download successful
Memory map 'after startup completion point' is active

openocd 和 pyocd 也正常工作。测试结果:

调试器 软件 擦除+下载速度 设定频率 SWCLK 实际最高频率 备注
CH32V203 SEGGER 17.90 KB/s 20 MHz 7.14 MHz Total: 58.480s (Prepare: 1.080s, Compare: 0.472s, Erase: 6.721s, Program: 48.878s, Verify: 0.428s, Restore: 0.898s)
CH32V203 SEGGER 17.88 KB/s 10 MHz 5.88 MHz Total: 58.607s (Prepare: 1.141s, Compare: 0.472s, Erase: 6.714s, Program: 48.947s, Verify: 0.432s, Restore: 0.898s)
CH32V203 SEGGER 17.88 KB/s 1 MHz 0.90 MHz Total: 58.613s (Prepare: 1.138s, Compare: 0.483s, Erase: 6.747s, Program: 48.910s, Verify: 0.429s, Restore: 0.903s)
CH32V203 openocd 34.451 KB/s 100 MHz 16.67 MHz
CH32V203 pyocd 17.83 KB/s 100 MHz 16.67 MHz

XIVN1987/DAPLink CH32V203 方案的测试结果与国产 DAPLink(STM32F103 方案)非常相似,体现为 SEGGER 下载速度 ~17.9 kB/s、openocd 下载速度 ~34.5 kB/s。可以反推国产 STM32 DAPLink 的代码来源极有可能与 XIVN1987 一致,为 CMSIS-DAP firmware 的完全移植,且应该均使用了 SW_DP.c 来控制 SWCLK 和 SWDIO。

结论:XIVN1987/DAPLink CH32V203 方案可用。代码质量没有问题,但下载速度略慢。它的 usb 通讯采用了 HID 而不是 winusb,因此 34.5 kB/s 可能已经达到了理论速度上限(usb 2.0 HID 最快每 1ms 传输 64 字节,所以理论上信道的通讯速率上限就是 64 kB/s)。编译产物,flash 占用 25068 B,RAM 占用 9064 B,可以在 CH32V203F6/CH32V203G6 上运行。

0x04 实验:RP2040 方案

现在来测试 RP2040 的 debugprobe 项目。笔者已经非常熟悉这份代码。烧录 v2.0.1 固件

pyocd flash -t rp2040 -f 10mhz debugprobe_on_pico.elf

识别到 winusb 设备和串口设备:

由于产品版与 pico 版固件的差异仅在 LED 和 GPIO 分配上,所以预期我们的 RP2040 版 DAPLink 也能跑出与 debugprobe 产品一致的性能。测试结果如下:

调试器 软件 擦除+下载速度 设定频率 SWCLK 实际最高频率 备注
RP2040 SEGGER 80.19 KB/s 20 MHz 16.67 MHz Total: 13.360s (Prepare: 0.121s, Compare: 0.377s, Erase: 6.602s, Program: 5.809s, Verify: 0.346s, Restore: 0.102s)
RP2040 openocd 66.191 KB/s 20 MHz 16.67 MHz
RP2040 pyocd 36.38 KB/s 20 MHz 16.67 MHz

性能完全符合预期。

结论:RP2040 方案可行,速度很快,且由于直接使用树莓派官方的 debugprobe 代码,稳定性有保证。缺点在于,若制作板载调试器,则比 CH32V203 方案多出 flash 和晶振这两个组件,额外占用 pcb 面积,且价格比 CH32V203 稍高(RP2040 市价 3.8 CNY,晶振 0.2 CNY,flash 1 CNY,加起来是 5 CNY)。我们 DIY 玩家虽然不太考虑价格,但如果大规模做产品,这个成本差距是有点大的。

0x05 实验:STM32F103CBT6 方案

按照 DAPLink 项目的指引,先烧入 bootloader:

pyocd flash -t stm32f103cb -f 10mhz stm32f103xb_bl_crc.hex

0001768 I Loading C:\Users\blue\Downloads\bootloaders-0257-c782a5ba\armcc\stm32f103xb_bl_crc.hex [load_cmd]
[==================================================] 100%
0004294 I Erased 34816 bytes (34 sectors), programmed 34816 bytes (34 pages), skipped 13312 bytes (13 pages) at 18.84 kB/s [loader]

然后电脑不识别 u 盘。怀疑 pcb 设计有问题(例如也许需要外置 1.5k D+ 上拉电阻;也许走线有问题)。折腾了几个小时也没解决,考虑到 CH32V 方案已经可用,决定暂且放弃 STM32F103 方案。

有兴趣的读者可以参考:https://oshwhub.com/yacter/stm32-daplink

0x06 实验总结

我们已经跑通了 CH552、CH32V203 和 RP2040 这三种方案。其中,CH552 方案存在重大缺陷,不能采用。详细对比 CH32V203 和 RP2040:

RP2040(debugprobe) CH32V203(XIVN1987/DAPLink)
usb 通讯方式 winusb HID
SEGGER 烧录速率 80.19 KB/s 17.90 KB/s
openocd 烧录速率 66.19 KB/s 34.45 KB/s
pyocd 烧录速率 36.38 KB/s 17.83 KB/s
最小电路所需元件(电容除外) RP2040 + W25Q16JVUXIQ + 晶振 CH32V203G6U6
pcb 占用面积(单位为 $\text{mm}^2$) 68.2(QFN56) + 10.7(USON8) + 13.6(SMD3225) = 92.5 27.0(QFN28)
引出接口 swd、串口 swd、串口、jtag、reset

首先讨论速率问题。虽然纸面上 17.83 KB/s 的速率非常慢,下载 128 KB 的固件需要 7.2 秒,但这是在最极端的情况下测出的——我们在测试前把 flash 清空了。事实上,如果不清空 flash,直接重复下载相同的固件,则 CH32V203 方案的 pyocd 下载速度从原先的 17.83 KB/s 上升到 44.48 KB/s,RP2040 方案从 36.38 KB/s 上升到 181.58 KB/s。考虑到真实开发过程中,一般都是在旧固件的基础上烧录新固件,故 CH32V203 方案的速度似乎也可以接受。

上表中的 pcb 占用面积是根据 KiCad 封装的 margin 层面积计算的。事实上,由于 RP2040 需要更多的布线空间,其面积劣势更加明显。表中采用了 W25Q16JVUXIQ 作为 flash,如果换成我们常用的 W25Q16JVSSIQ(SOIC-8 封装),面积还会进一步增大。

综上所述:RP2040 适合做独立调试器(如同 debugprobe 产品那样);而对于板载调试器,CH32V203 是更优选择。本文接下来设计两个项目,一个是板载 CH32V203 调试器的 Pi Pico 开发板,另一个是基于 RP2040 的独立调试器。

0x07 自带调试器的 Pico 开发板

现在设计一块 Pico 开发板,它仅通过一条 type c 线连接到电脑,便可以进行 swd 调试、使用串口,电脑还能访问 RP2040 的 usb。具体细节:

  • 采用 CH334P 作为 usb hub,连接到 CH32V203G6U6 调试器和 RP2040。
    尽管 CH334 的手册中说只有部分封装提供了免晶振能力,但根据沁恒人员在论坛上的说法,目前在售的 CH334 均可免晶振。笔者实验了一下,发现确实如此,烧录速率也未见降低。
  • 板上要留下 CH32V203 的烧录接口。若采用 usb 烧录,则需要引出 BOOT0;若采用 swd 烧录,则需要引出 SWCLK 和 SWDIO。
  • 引脚兼容树莓派官方 Pico 开发板。

原理图:

现在进行 layout 工作。从树莓派 Pico 手册中查到 Pico 外形:

由图可知,两侧排针之间距离为 17.78mm。我们不做半孔,所以比 Pico 略窄一些。最终效果:

在本文的最后,我们来完成开头提出的目标——自制一个 DAPLink。这次仍然采用 RP2040 方案,提供 stdc14 和标准 10pin 调试端口。原理图:

💡
为了烧录调试器自身,我们在 PCB 上引出 QSPI_SS 管脚。用镊子把 QSPI_SS 短接到地,即可用虚拟 u 盘烧录 debugprobe。

layout:

成品如下图(右)所示。pcb 面积与 debugprobe 产品相近。

pyocd 正常识别: