PSoC 烧录后突然连不上:从老 J-Link 的报错,到 WCH-Link 的 SWD 复位接管


2026 年 9 月 10 日,我在调试一块 PSoC 4000T 触摸板时,遇到了一个很容易被误判成硬件损坏的问题:第一次使用 J-Link 能识别芯片,擦除和下载也走到了最后,却报出 Writing target memory failed。换成 WCH-Link 的 CMSIS-DAP 模式后,连芯片 ID 都读不到了。

最后恢复连接的办法,是通过 WCH-Link 在复位后的启动窗口内发送完整的 SWD 接管序列,让芯片进入 Test Mode,再交给 OpenOCD 执行读回、擦除和下载。

更有意思的是:恢复连接后读回 Flash,发现之前 J-Link 实际已经写入了程序。

这篇文章记录这次排查中的证据、实现方法,以及恢复烧录之后遇到的调试问题。

最初的现象:下载失败,还是程序已经开始运行?

这次使用的环境如下。版本只代表本次实测组合,不意味着其他版本一定有相同表现。

项目 本次使用
工程目标 CY8C4025LQI-T412,CPU 使用 24 MHz
实板识别 CY8C4045LQI-T412,Silicon ID 为 0x3603
J-Link 老款 J-Link ARM-OB STM32,硬件 V7.00,探针固件编译于 2012 年
J-Link 软件 SEGGER J-Link Commander V8.82
WCH-Link WCH-LinkE,RV 身份查询返回固件 2.18、variant 0x12
ARM 调试接口 WCH-Link 的 CMSIS-DAP 模式,接口报告 FW Version 2.0.0
OpenOCD Infineon Programming Tools 1.9 所带的 0.12.0+dev-5.19.0.4782
开发环境 Windows、ModusToolbox、VS Code、Cortex-Debug

这里的 WCH-Link“DAP 模式”提供 CMSIS-DAP 接口;不需要把探针改刷成另一套 DAPLink 固件。

第一次使用 J-Link 时,日志中有两段值得单独看:

The connected J-Link does not support the device specific init.
Using generic init.
Programming flash [100%] Done.
Writing target memory failed.

第一段说明这支老探针没有执行器件专用初始化。更详细的日志还提到,它不支持该初始化要求的固件内 PCode 执行能力。升级电脑上的 J-Link 软件,并不会自动让老探针具备这项能力。

第二段只能说明整个下载流程没有成功结束。进度达到 100% 也不能代替最终校验,但最后报错同样不能证明所有程序字节都没有写进去。

后来通过 WCH-Link 读回完整的 32 KiB Flash,与当时保留的固件比较,IAP 程序记录和 APP 的有效载荷都匹配。差异主要在填充区域,以及从 TRIAL 变为 ATTEMPTED 的启动状态。

因此,这次能确认的是:J-Link 已经写入了程序载荷,但整个下载流程没有被报告为成功。 至于最后一次失败究竟发生在保护记录处理、校验还是恢复目标状态的哪一步,现有证据不足以唯一定位。

为什么换了下载器,反而连 ID 都读不到?

这块板子的两个引脚同时承担了维护接口和调试接口:

PSoC 引脚 SWD 用途 正常固件中的用途
P3.2 SWDIO SCB1 I²C SDA
P3.3 SWCLK SCB1 I²C SCL

原来的 IAP 和 APP 都会把这两个引脚用于 I²C。程序一旦启动,调试器面对的引脚状态就可能已经发生变化。

换成 WCH-Link 后,电脑能正常识别 CMSIS-DAP,探针也报告支持 SWD,但 OpenOCD 随后失败:

Using CMSIS-DAPv2 interface with VID:PID=0x1a86:0x8012
CMSIS-DAP: SWD supported
Error connecting DP: cannot read IDR
DAP 'psoc4.cpu' initialization failed

这些日志把问题分成了两层:电脑到探针的 USB 通信已经建立,失败发生在探针到 PSoC 的调试端口访问阶段。此时继续反复安装 USB 驱动,并不能直接解决目标侧的问题。

接线仍然要先检查:SWDIO、SWCLK、GND、目标供电,以及探针 nRST 到 PSoC XRES 的连接。因为还有 CH32 接在维护总线上,单板排查时也需要隔离它对这些线和复位线的驱动。探针上的 3.3 V 供电输出,也不能直接当成电压参考输入来理解。

但本次在 XRES 已接好、外部干扰排除后,常规连接依然失败。这时需要检查的是:下载器是否真正执行了 PSoC 所需的复位接管流程。

接上 RST,还需要赶上启动窗口

PSoC 的复位接管有时序要求。根据编程规范,芯片完成内部复位和启动代码后,会在一个约 400 µs 的窗口内等待接管序列;成功进入 Test Mode 后,用户程序不会继续启动。这个窗口不等于“XRES 一释放就立即开始的 400 µs”,因此规范建议从复位后持续尝试,而不是在电脑上估算一个固定延时再发命令。Infineon 编程规范,第 4.3 节

这也解释了为什么“把 SWD 频率降得越低越稳”的经验在这里不能直接套用。PSoC 4000T 的接管步骤要求至少 1.5 MHz,本次最终使用 2 MHz。PSoC 4000T Architecture TRM,第 18.5 节

除了 SWCLK 频率,还要考虑命令之间的空隙。如果每次读写寄存器都从电脑单独发送一个 USB 请求,即使每次 SWD 传输很快,多次 USB 往返和系统调度也可能让整个接管序列错过窗口。

本次使用的是 DAP_SWD_Sequence:把关键 SWD 输入、输出片段放进一个请求,由探针连续执行。它支持在同一命令中交替输出和采样 SWDIO。Arm 命令说明

实测这支 WCH-Link 的能力字节没有声明 DAP_ExecuteCommands 原子批处理,但它支持 DAP_SWD_Sequence。这是两项不同能力,不能因为前者未声明就放弃后者,也不能仅凭探针名称推断支持情况。

一个 64 字节请求完成关键接管动作

最终脚本将下面这些动作编码进一个 64 字节请求:

顺序 动作 本次使用的值
1 SWD line reset 56 个高电平时钟,随后 8 个低电平时钟
2 读取 DPIDR 本板期望 0x0BC11477
3 写 DP CTRL/STAT 0x54000000
4 写 DP SELECT 0x00000000
5 配置 AHB-AP CSW 0x03000042
6 写 AHB-AP TAR 0x40030014
7 写 AHB-AP DRW 0x80000000,设置 Test Mode 位

这里列的是本次 PSoC 4000T 实现使用的寄存器值,不能不加检查地套到其他 PSoC 系列。

请求中的 18 个序列片段合计 380 个 SWCLK 周期。在 2 MHz 下,线上的时钟时间约为:

380 / 2,000,000 = 190 µs

190 µs 是协议时钟时间的计算值,不是示波器测得的总执行时间,也不包含电脑到探针的传输延迟。它的作用是让关键动作尽量连续,实际能否接管仍由返回结果判断。

一次尝试的流程是:拉低 XRES,保持 20 ms,释放 XRES,然后在约 12 ms 的外层尝试期内重复发送接管请求。失败后重新复位,最多尝试 8 次。这里的 12 ms 是脚本用来覆盖内部启动过程的尝试期限,芯片本身的接管窗口并没有被延长。

接管后也不能看到一段看似正确的回包就开始擦除。脚本继续检查完整 DPIDR、Test Mode 位、SROM 是否退出特权忙状态,以及 CPU 是否确实停住。

本次 OpenOCD 的 cmsis-dap cmd 只在日志中暴露四个响应字节,Tcl 层用它做初步判断;Python 侧的包校验会检查完整响应中的 ACK、DPIDR 和奇偶位。真正下载前,OpenOCD 还会通过正常 DAP 访问再次验证目标状态。

最终的成功标志是:

MaoDX: reset acquire PASS; DPIDR=0x0BC11477, Test Mode confirmed, CPU halted

把接管放在 OpenOCD 初始化的正确位置

接管需要在“适配器已经初始化、DAP 尚未初始化”时执行。太早无法使用 CMSIS-DAP 命令,太晚则已经在读取 DPIDR 时失败。

项目使用的配置如下:

source [find interface/cmsis-dap.cfg]
cmsis-dap vid_pid 0x1a86 0x8012
cmsis-dap backend usb_bulk
transport select swd

set PSOC4_USE_ACQUIRE 0
source [find target/infineon/psoc4.cfg]
adapter speed 2000

source [find tools/wchlink-acquire.tcl]

最后一行是本项目的自定义脚本,不是 OpenOCD 自带文件。它在 transport init 阶段插入一次复位接管,然后恢复原始命令继续初始化。

原生目标脚本仍可能打印:

Test Mode acquire not supported by selected adapter

在这个组合下,它指的是原生接管路径不支持当前适配器。自定义接管稍后执行,是否成功要看其验证结果,不能仅凭前面这句提示下结论。

接管成功后,下载流程保持为:

验证目标状态 → 探测 Flash → 按镜像类型擦除/写入 → 校验 → 关闭 OpenOCD

中间没有再调用通用 reset init。否则可能刚接管完,就又让用户程序启动并把 SWD 引脚改回 I²C。

下载结束时,则先关闭 OpenOCD,再通过探针产生 XRES 脉冲、释放目标。这样可以避免程序已经开始复用 SWD 引脚,而 OpenOCD 仍在做退出阶段的调试访问。

WCH-Link 的模式也在下载前处理:本次 RV 模式为 1a86:8010,CMSIS-DAP 为 1a86:8012。脚本先确认探针型号和固件能力,需要时才发送支持的模式切换命令;已经处于 DAP 模式就不再切换。Windows 下原有 WCH 驱动与 WinUSB 的访问方式分别处理,没有为了这次接管重刷探针固件。模式协议实现参考了 wlink 的探针代码和控制命令。

Flash 内容、芯片型号,都要用读回结果确认

这次工程里的 HEX 除了普通 Flash 内容,还带有 Infineon 的保护配置记录。通用下载路径不能把这些记录全部当成普通内存地址。

因此项目增加了 Flash-only 镜像转换:保留 IAP、APP 和元数据,接受并处理当前工程已知的未保护行及 OPEN 配置,遇到未知地址或非 OPEN 请求则停止。它不是一个可以随意删除所有高地址记录的通用 HEX 清理器。

本项目的存储布局为:

区域 地址
IAP 0x00000x1FFF
APP 0x20000x7F7F
元数据行 0x7F800x7FFF

只下载 APP 的 ELF 或 HEX,并不一定会同步更新 IAP 使用的长度、CRC 和启动状态。因此最终下载使用的是打包后的镜像,而不是随手选择构建目录里最后生成的文件。

恢复连接后,OpenOCD 识别到的目标信息包括:

Silicon: 0x3603, Family: 0xC6, Rev.: 0x22 (B1)
Detected Device: CY8C4045LQI-T412
Detected Main Flash size, kb: 32
Chip Protection: OPEN

这也提醒了我:工程名称写着 4025,并不意味着板上一定就是 4025。DPIDR 用来确认调试端口,Silicon ID 才是进一步识别器件的依据。两者不能混为一谈。

本次没有因此重命名工程或提高 CPU 主频;这篇文章中的实板恢复结果来自上述 4045,而不是对所有 4025/4045 批次的全面验证。

恢复烧录以后,还要让 SWD 留得住

复位接管解决了“赶在程序启动前访问芯片”的问题。如果希望程序运行时也能暂停、单步和读取变量,IAP 与 APP 都必须保留 SWD 引脚。

这里还有一个隐藏位置:即使删掉 APP 中显式的 I²C 初始化,cybsp_init() 调用的生成 GPIO 初始化仍可能先配置 P3.2/P3.3。于是我同时处理了 IAP、APP 和生成的 GPIO 初始化,并在重新生成配置、构建之前自动补上条件保护。

项目后来用 MAODX_DEBUG_FLAG 统一选择 PSoC 的独立调试模式:开启时暂停维护 I²C,保留 SWD,并允许触摸自行扫描;关闭时恢复原有 CH32 协同和 I²C 升级流程。这个开关只属于 PSoC,CH32 的维护及升级功能保持正常。

调试模式还暂停了看门狗和试运行状态消耗,避免每次断点或重启都改变升级状态。APP 的向量、长度和 CRC 校验仍保留。

在本次 WCH-Link/OpenOCD 组合中,psoc4 reset_halt 又暴露了软复位后连接丢失的问题。最后使用的调试重启流程是:暂停 OpenOCD 轮询,产生 XRES 脉冲,执行 DAP 断开/重连,重新初始化 DAP、检查目标,再强制请求 CPU 停止。它会在复位释放一段时间后暂停,并不保证停在 main 入口。

恢复后已经验证了源码断点、单步、变量读取和重启。运行中的变量观察使用 Cortex-Debug 的 Cortex Live Watch;普通“变量/监视”窗口在继续执行时不会持续显示变量值。

最后一个看起来像调试故障的路径问题

SWD 连接正常之后,VS Code 还出现过一次镜像校验失败:路径中的反斜杠和数字被 GDB 命令层当成转义字符,OpenOCD 收到的是损坏的文件名。

原来的配置直接把 Windows 工作区路径放进 monitor 命令:

"monitor verify_image {${workspaceFolder}/tools/iap_artifacts/swd-debug-reference.hex}"

后来改成由 OpenOCD 在已配置的搜索目录中寻找文件:

"monitor verify_image [find {tools/iap_artifacts/swd-debug-reference.hex}]"

修正后,通过与 Cortex-Debug 一样的 GDB/MI 命令路径完成了 32768 字节镜像校验,也读到了调试变量。这里无需重新烧录,更不需要再怀疑焊接或芯片。

这次排查最后留下了三条很实用的判断依据:J-Link 的最终报错要与 Flash 读回结果一起看;CMSIS-DAP 初始化成功只说明电脑已经连上探针;PSoC 的 SWD 复用场景必须把复位接管时序纳入下载流程。

真正让我确认问题解决的,是读回内容匹配、完整镜像校验通过,以及新固件运行后仍然能够暂停和读取数据。