多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

CH347F一芯多用:OpenOCD调试与flashrom读写SPI Flash

CH347F一芯多用:OpenOCD调试与flashrom读写SPI Flash 做嵌入式调试这些年我一直想找一块“通用嘴子”既能给STM32做SWD调试又能顺手把板子上的SPI NOR Flash离线读写。以前要么带ST-Link和USB转SPI两套工具要么用FT232H来回配置总差点意思。后来试了WCH的CH347F这芯片一个USB口下面集成了UART、SPI、I2C和JTAG/SWD相关引脚配合OpenOCD和flashrom两套软件基本把“一芯多用”变成了日常操作。这篇文章不聊纸面参数直接从我实际搭建的过程讲起。我会把接线、驱动、OpenOCD的SWD配置、flashrom的SPI Flash操作、以及两个功能之间的模式切换都拆开讲顺便把“OpenOCD已停止”和“swd/jtag communication failure”这类报错的排查思路也整理出来。适合手里有CH347F模块、想省一块调试器钱或者想搞一套便携烧录台的开发者参考。1. 为什么是CH347F一个USB口集齐调试和烧录能力1.1 芯片的核心能力与接口分布CH347F是沁恒推出的一款USB总线转接芯片表面看它是一个多协议桥接器实际拆开看它的价值在于内部集成了多套独立的外设控制器而不是简单用GPIO去模拟。常规型号主要提供这几组接口两个UART其中UART0支持最高9Mbps波特率UART1也具备较高波特率。一组SPI主机接口支持Mode 0/1/2/3速度档位很多最高可以跑到几十MHz量级足够应付常见SPI NOR Flash。一组I2C主机接口速率可配置。一组JTAG接口包含TCK、TDI、TDO、TMS四根线同时也可以被OpenOCD用作SWD调试口。芯片本身由USB供电内部带LDO对外输出3.3V。这一点很关键JTAG和SPI并不是同时存在的工作模式而是通过芯片的工作模式选择来切换。CH347F通常有Mode 0、Mode 1、Mode 2等运行模式不同模式开放不同接口组合。比如一种常见模式打开UART和SPI/I2C另一种模式打开UART和JTAG。正是因为这套机制我才能让同一个芯片在“SPI Flash烧录器”和“SWD调试器”之间来回切换实现标题里说的“一芯多用”。1.2 与FT232H等方案的横向对比之前很多人在调试和烧录之间二选一时喜欢用FT232H。FT232H本身也是一个USB转多功能接口芯片它的SPI和JTAG都是靠MPSSE引擎跑出来的理论上也能用OpenOCD做SWD用flashrom写SPI Flash。但实际用下来两者的差别还是不小。我整理了手头几块模块的对比感受对比维度CH347FFT232H原生JTAG引脚有TCK/TDI/TDO/TMS直出无靠MPSSE动态配置SPI支持硬件SPI控制器速度档位丰富MPSSE引擎模拟OpenOCD支持需要较新版本有专门ch347驱动libftdi驱动生态成熟flashrom支持有ch347_spi支持有ft2232_spi支持模式切换芯片级模式切换硬件引脚或软件指令运行时配置MPSSE通道成本通常更低模块价格偏高上手复杂度模式概念需要理解一般直接配通道FT232H的优势是生态老资料多教程一抓一大把。但CH347F在硬件SPI速度和JTAG引脚直接性上都有优势而且模式一旦搞明白操作逻辑非常清晰调试用JTAG模式烧Flash用SPI模式互不干扰。1.3 模式选择机制与“一芯多用”的关键CH347F内部功能的切换本质上是对芯片工作模式的一次配置。在Windows下WCH官方提供了一个配置工具可以选择当前芯片的模式配置完成后芯片会按新模式重新枚举。在Linux下也有对应的命令行工具可以通过USB控制指令来切换不需要物理拆芯片。我在实际使用中把这一机制当作“工作台切换开关”需要给STM32下载程序、看寄存器就切到JTAG/SWD模式需要读写板载Flash就切到SPI模式。整个过程不需要换硬件也不需要拔线只是跑一条配置命令。真正需要注意的是模式切换后USB设备描述符可能会变化。比如JTAG模式下设备显示为某个名称切到SPI模式后名称可能就变了。做好这个心理准备后面配置udev规则和OpenOCD/falshrom参数时就不容易懵。2. 工具链三板斧驱动、OpenOCD与flashrom的安装配置2.1 Linux下的驱动与权限配置CH347F在Linux下可以走内核自带驱动也可以走libusb用户态驱动。对于OpenOCD和flashrom这类工具直接访问USB设备是更通用的方式因此权限问题必须优先搞定。我习惯创建一个udev规则让普通用户也能访问设备。先确认设备的VID/PIDCH347常见VID是1a86PID会因为模式不同而不同。写一条这样的规则sudo tee /etc/udev/rules.d/99-ch347.rules EOF SUBSYSTEMusb, ATTR{idVendor}1a86, MODE0666 EOF sudo udevadm control --reload-rules sudo udevadm trigger这里把设备权限设置成0666开发机上够用实验室共享设备时可以再收紧。插拔一次USB再执行lsusb能看到芯片设备即可。如果在Linux下使用WCH官方驱动或某个带内核模块的版本可能会和libusb冲突。我的建议是做开发调试时不要加载官方内核驱动直接让libusb控制设备这样OpenOCD和flashrom都能访问同一个设备。否则会遇到“设备忙”的错。2.2 OpenOCD的CH347支持与版本要求OpenOCD对CH347的支持是后来加入的并非远古版本就能用。我在Ubuntu 22.04上第一次用系统源里的OpenOCD时发现根本没有ch347驱动折腾了半天才意识到是版本太旧。解决方式很直接源码编译新版OpenOCD。官方仓库已经包含ch347驱动推荐直接拉最新release或master分支。git clone https://github.com/openocd-org/openocd.git cd openocd ./bootstrap ./configure --enable-internal-libjaylink --enable-sysfsgpio --enable-ch347 make -j$(nproc) sudo make install编译依赖主要是libtool、autoconf、automake、pkg-config、libusb-1.0。如果缺包先补sudo apt install libtool autoconf automake pkg-config libusb-1.0-0-dev编译好之后执行openocd --version确认版本。接下来在配置文件里指定ch347适配器基本写法我在后面SWD部分会详细给。2.3 flashrom的CH347支持flashrom对CH347 SPI编程器的支持也依赖较新版本。同样建议源码编译git clone https://github.com/flashrom/flashrom.git cd flashrom make -j$(nproc) sudo make install编译前确认依赖有libusb-1.0-0-dev、libpci-dev、libftdi1-dev、zlib1g-dev等。flashrom启动时会自己枚举支持的编程器执行flashrom --help能看到ch347_spi类似字样。实际调用时使用类似flashrom -p ch347_spi:busspi,cs0 -r backup.bin这行命令的意思是使用ch347_spi编程器走SPI总线片选0把Flash内容读出来保存到backup.bin。后面会展开讲更多参数的用法。3. SWD调试STM32接线、配置与底层逻辑3.1 接线要点与原理图级讲解SWD只需要两根数据线SWDIO和SWCLK再加上GND。CH347F在JTAG模式下TMS引脚对应SWDIOTCK引脚对应SWCLK。很多模块上丝印可能仍然标着TMS/TCK它和ST-Link的SWDIO/SWCLK是一一对应的。我常用的接线表如下CH347F引脚STM32 SWD引脚说明TMSSWDIO数据输入输出TCKSWCLK时钟GNDGND必须共地3V3VDD / VREF如果不隔离可以接目标板3.3VTDI/TDO不接或保留SWD模式下不需要有一个细节如果目标板和CH347F模块各自独立供电务必先共地再连接信号线。如果目标板电压不是3.3V而是5V系统或1.8V系统就需要加电平转换。CH347F的IO电平按3.3V设计直接接5V引脚有风险。我吃过一次亏板子上一颗Flash是1.8V供电结果我直接拿3.3V的CH347F去连导致数据不稳定甚至概率性识别失败。后来加了TXS0108E电平转换芯片才稳定。3.2 OpenOCD配置文件写法SWD调试的第一步是让OpenOCD正确识别CH347F。我的配置文件分成Interface部分和Target部分。Interface部分长这样adapter driver ch347 transport select swd adapter speed 1000初始化完成后OpenOCD会通过libusb找到CH347设备。如果你的电脑上有多个CH347设备可以用设备描述符或序列号指定不同版本OpenOCD写法略有不同常见写法是ch347_device_desc WCH CH347IF具体字符串以你实际设备的描述符为准。可以先在系统里找到设备信息再填进去。Target部分选择STM32系列比如F103source [find target/stm32f1x.cfg]连起来就是openocd -f interface/ch347.cfg -f target/stm32f1x.cfg启动后日志里能看到识别到的芯片IDCODE和target信息。如果到了这一步说明SWD通路已经通了。3.3 从connect到halt的完整调试流程OpenOCD启动后会进入独立进程默认监听4444端口。此时可以用telnet连接telnet localhost 4444在OpenOCD命令行里执行reset halt flash banksreset halt的意思是让目标芯片复位后立即停下来。对于新板子我很建议先执行这个命令它能确认芯片内部调试逻辑是否正常。如果返回target haltedPC地址一个合理的值恭喜SWD通路完全正常。接着就可以烧写程序了。最简单的方式是直接用OpenOCD脚本openocd -f interface/ch347.cfg -f target/stm32f1x.cfg -c program app.elf verify reset exit这条命令会完成擦除、写入、校验、复位运行。实际开发中我更多是让OpenOCD一直跑再用gdb连接:arm-none-eabi-gdb app.elf (gdb) target remote :3333 (gdb) loadOpenOCD默认还开了3333端口给gdb。这种方式调试效率很高打断点、看变量、改寄存器和用ST-Link没有体验差别。3.4 SWD通信失败的典型原因网上搜“swd/jtag communication failure”和“openocd已停止”时八成都是通道没打通。我自己踩过几个坑按出现频率排序如下现象常见原因解决方向Error: JTAG error occurred接线错误或接触不良重新确认SWDIO/SWCLK/GND连接Target not foundRST引脚被外部拉死检查复位电路必要时手动复位openocd已停止版本太旧/配置参数不匹配换新版OpenOCD确认ch347驱动clock speed too highSWCLK频率太高下调adapter speed先从100kHz试偶尔能连上经常连不上供电不稳或共地不良加粗GND线确认目标板供电目标芯片进入了低功耗模式SWD时钟丢失先做复位握手或按住复位再启动OpenOCD“openocd已停止”这个报错在Windows下经常表现为进程闪退Linux下则是Segmentation fault或者USB error。多数情况是驱动版本与OpenOCD版本不匹配或者CH347正被别的进程占用。用lsusb看设备用fuser或lsof查端口确保设备没被WCH官方工具占住。我还有一个独家习惯新板子第一接把adapter speed设成100kHz。速度快不一定好先用低速率建立握手确认无误后再逐步提速。这个习惯帮我排掉了很多假故障。4. SPI Flash烧录换一种身份继续干活4.1 硬件连接与注意事项进入SPI模式后CH347F的SPI引脚和Flash芯片的接线如下CH347F SPI引脚SPI NOR Flash引脚说明SCK/CLKCLK/SCK时钟MOSI/COPIDI/IO0主出从进MISO/CIPODO/IO1主进从出CS0CS#片选3V3VCC供电GNDGND地有一个容易被忽略的地方很多SPI NOR Flash的WP#和HOLD#引脚不能悬空。WP#是写保护HOLD#是暂停通信。如果这两根线悬空上电瞬间Flash可能进入写保护或总线暂停状态导致读ID正常、擦除写入失败。我的做法是直接把这两个引脚接到3.3V不行再通过10kΩ电阻上拉。另外确认Flash型号并查数据手册的供电范围。常见W25Q系列是2.7V到3.6VGD25Q系列类似。如果目标板上的Flash是1.8V供电就不能和3.3V的CH347F直接对接必须加电平转换。很多所谓“不支持某颗Flash”的问题根源其实是电平不匹配。4.2 flashrom命令与实战进入SPI模式并确认设备就绪后直接用flashrom操作。最稳健的顺序是先探测Flash再读备份最后写新固件。探测Flashflashrom -p ch347_spi:busspi,cs0这条命令会尝试读取芯片的JEDEC ID并打印出识别到的型号。如果识别成功会显示类似“Found Winbond flash chip W25Q128JV (16384 kB)”。读取完整内容到文件flashrom -p ch347_spi:busspi,cs0 -r backup.bin这里-r是读backup.bin是输出文件。读出来后我强烈建议先用hexdump看一眼首尾hexdump -C backup.bin | head hexdump -C backup.bin | tailFlash全空时读出来全是FF。如果全空说明芯片大概率是干净的。如果出现奇怪的数据先不要急着擦除多读几次对比可能是接触不良或电平问题。写入新固件flashrom -p ch347_spi:busspi,cs0 -w app.bin写之前flashrom会自动擦除、写入、校验。如果Flash之前有写保护位被置位擦除会失败这时需要先运行flashrom -p ch347_spi:busspi,cs0 --wp-disable很多人在这一步卡住提示“Failed to disable write protection”大概率就是WP引脚没处理好的原因。4.3 读、写、校验的流程设计用CH347F给SPI Flash烧录我总结了一套不容易翻车的三步流程第一步备份原始固件。任何板子到手不先读一次原厂固件就直接擦是最危险的事。我见过有人在调试路由器时不小心把ART分区擦没了最后只能整机返修。正确做法是先用-r全片读取最好连续读两次用cmp确认两次文件一致。第二步验证写入。写固件不要依赖flashrom的自动校验就完事我会再加一步单独读取并与源文件比对flashrom -p ch347_spi:busspi,cs0 -r verify.bin cmp verify.bin app.bin echo OK这样做的好处是如果写入过程中因为CS线毛刺或供电波动导致个别bit写错flashrom的自动校验有时会报告成功但单独对读文件对比能更直观发现异常。第三步收尾检查。拆除夹子或线缆前再次确认所有引脚都断开避免带电拔插。带电拔插SPI线是损坏Flash引脚的常见原因不要问我怎么知道的。5. 模式切换的工程化操作把“一芯多用”固化到工作流5.1 当前工具的切换模式CH347F模式切换在Windows下有官方工具图形界面点几下就行。Linux下则可以用WCH提供的命令工具或者直接通过libusb控制指令切换。命令行切模式后设备会重新枚举等待几秒再执行lsusb确认。我会把两种模式对应的PID记录下来这样脚本可以自动判断当前处于什么状态。当前模式典型PID适用场景Mode 1SPI/I2C/UART视固件版本而定flashrom烧录SPI FlashMode 2JTAG/UART视固件版本而定OpenOCD调试STM32具体PID以你手里的设备为准因为不同版本CH347固件可能有差异。用lsusb看到设备后记下来即可。5.2 脚本化切换手动切换一次两次还行频繁调试和烧录时来回敲命令很烦。我通常写一个简单的Shell脚本把切换、调用OpenOCD或flashrom的操作包起来。简化版伪代码#!/bin/bash MODE$1 DEVICE1a86:xxxx # 替换成CH347F的实际VID:PID switch_mode() { # 这里调用WCH官方工具或自定义libusb指令完成模式切换 ./ch347tool --mode $1 sleep 2 lsusb | grep $DEVICE } case $MODE in swd) switch_mode jtag openocd -f interface/ch347.cfg -f target/stm32f1x.cfg ;; flash) switch_mode spi flashrom -p ch347_spi:busspi,cs0 -w app.bin ;; *) echo Usage: $0 {swd|flash} ;; esac在这个基础上还可以加一个“自动等待设备重新枚举”的函数避免睡眠时间不够导致设备被占用。只要脚本稳定整个调试和烧录流程就能顺畅很多。5.3 扩展一块CH347F服务多个目标板因为CH347F模式的切换是芯片层面的实际排线时我会做一小块转接板把JTAG/SWD信号和SPI信号分开引出再通过拨码开关或跳线选择接通哪一个目标。这样CH347F模块固定在一个位置要调试哪块板子就拨到哪个通道。对于经常混用不同STM32型号的场景OpenOCD命令也可以参数化。把芯片型号作为脚本参数传入配合target配置文件的软链一条命令就能在不同目标板之间切换。长期用下来这套东西已经代替了我桌上的一堆独立调试器。6. 常见问题排查与速查表6.1 从错误日志倒推问题OpenOCD和flashrom的日志其实已经把问题指向写得很明白了关键是别急着翻板子查线先静下心把日志读完。比如OpenOCD报Error: JTAG error occurred后面可能带着unexpected JTAG IDCODE或者all ones字样。看到all ones说明SWDIO上读回来全是高电平大概率是数据线没接上或者目标板没供电。看到all zeros大概率是SWDIO对地短路或者GND接触不良。flashrom报No EEPROM/flash device found的时候不要怀疑芯片坏了先做三件事量CS#在发送命令时是不是有低电平脉冲量时钟线是不是有波形再把WP#和HOLD#上拉。三步走完九成问题能解决。6.2 故障速查表操作报错现象排查思路我最后的解决动作OpenOCD启动Error: couldnt find device设备未被libusb识别重插USB检查udev规则OpenOCD启动Error: claimed interface failed设备被其他进程占用关掉WCH官方工具检查僵尸进程OpenOCD连接swd/jtag communication failureSWCLK频率过高或接线不良降速到100kHz重插杜邦线OpenOCD连接target not halted复位引脚异常检查RST外部电容/上拉OpenOCD烧写verification errorFlashClock配置不对确认目标时钟源关闭看门狗flashrom读取Found unknown flash chip电平不匹配或接触不良检查WP/HOLD上拉确认3.3V电平flashrom写入Write protect errorWP#未上拉接10kΩ上拉到3.3Vflashrom擦除Erase failed芯片锁定或损坏用--wp-disable确认不是假片模式切换后lsusb看不到设备模式指令不对或设备枚举失败拔插USB重跑切换命令再补充一条经验杜邦线是玄学。SWD调试和SPI烧录频率稍高一点劣质杜邦线带来的毛刺就能让通信随机失败。我花了很长时间才意识到问题不是CH347F也不是OpenOCD而是那根10cm的杜邦线。后来换了短线或者干脆用排线焊接的小转接板一切稳定了很多。6.3 OpenOCD启动后立即退出的处理很多人在Windows下双击OpenOCD窗口一闪就退这就是“openocd已停止”的直观表现。通常是因为OpenOCD没有找到配置文件或者找到设备但连接目标失败。处理方法很简单在终端里手动执行完整命令不要双击快捷方式这样就能看到完整日志openocd -d -f interface/ch347.cfg -f target/stm32f1x.cfg-d是打开调试输出。日志会明确指出是哪一步失败。如果配置文件名都对了但还是退出优先检查是不是版本里没有ch347驱动这一步占很大比例。我的另一个习惯是专门为CH347F建一个独立的OpenOCD工作目录把interface配置和常用的target配置都放在里面避免和系统自带的配置文件互相干扰。7. 写在最后这套组合给我带来了什么玩了这套CH347F OpenOCD flashrom的方案之后我最大的感受是桌面明显干净了以前并排放着ST-Link、USB转SPI模块、以及一堆转接线现在一个模块加几根线就能搞定大部分场景。虽然模式切换需要额外一步但对固定流程的开发任务来说这点成本完全可以接受。如果要从零复制这套环境我的建议是先把软件版本问题解决OpenOCD和flashrom都上源码编译的最新版然后再考虑接线和硬件。很多人一上来就去挑芯片、改线结果版本太老根本不支持CH347白折腾一天。最后再分享一个小技巧给CH347F配一个带指示灯的转接板模式切换后观察指示灯状态就能快速确认芯片是否已经重新枚举。这个状态提示在野外调试时特别实用省去每次都要拿电脑去lsusb的麻烦。希望这篇实战记录能帮你把“一芯多用”真正用起来。
返回列表