多板 MCU 固件:统一编译、拉取式固件库与两条烧录路径
一个机队里同时跑着 F407(带 Katapult)、H723(无 bootloader)、HC32F460(华大,TF 卡自动刷)三类主板, 固件不可能"一版通吃"。可行的做法只有一条:一台母机编全部板型 → 产物进云端固件库 +
manifest.json→ 设备按清单自己拉取刷写。同一份源码在两套工具链下的产物体积不同(39,676 B vs 46,104 B), 所以"谁是唯一来源、用哪个编译器"必须先定死,再谈 sha256 校验。下面是可以照着执行的 SOP:识别 → 选包 → 烧录 → 校验 → 回滚,含 DFU 远程救机、出厂一键初始化与 USB 写通道排查。
1. 板型矩阵:先分清有几种烧录语义
| 目标板 | MCU | 时钟 | 应用起始 | bootloader | 烧录通道 |
|---|---|---|---|---|---|
| 32 KiB bootloader 板 | STM32F407 | 168 MHz | 0x8008000 |
Katapult(32 K) | USB CDC + flashtool.py |
| 无 bootloader 板 | STM32H723 | 520 MHz | 0x8000000 |
无 | USB DFU 0483:df11 |
| 华大 100 pin 板 | HC32F460 | 168 MHz | 0x008000 |
出厂引导 | TF 卡 user/firmware.bin / SWD 兜底 |
三行三点差异:应用偏移不同、进 bootloader 的方式不同、失败后的恢复手段不同。混用任何一项都会得到"刷写被拒绝"或"刷进去不启动"。
关键一点:app_offset 无法从运行中的固件探测。运行中的 Klipper 会报 MCU 型号和时钟,但报不出自己被烧到了哪个偏移。
所以清单必须带 app_offset 字段,靠"型号 + 时钟 + offset"三元组匹配,而不是靠型号一项。
MCU 型号必须取自运行中固件的上报值:printer/objects/query?mcu 里的 mcu_constants.MCU 与 CLOCK_FREQ。
后台 payload 里的 mcu_model 是主机端 dict 的旧值,已经被它误导过一次——bin 编的是 H723、板子是 F407,刷写直接拒绝。
2. 工程库结构
| 目录 | 内容 |
|---|---|
firmware/ |
Klipper v0.13.0-1384 裁剪树 + 扩展 src/kali.c(协议标识、kali_uid、链路租约、诊断) |
boards/ |
28 个板级 .config + 后台/屏幕共用的 boards.json |
host/hot_reload/ |
kali_hot.py 与单测 |
host/temperature_filter/ |
非热电偶温度滤波 |
testenv/ tools/ verification/ |
host simulator E2E、54 目标构建矩阵、验收记录 |
补丁是否真编进去,不靠"我改过源码"这句话,靠两处物证:out/klipper.dict 里出现 stepper_snapshot,
out/klipper.elf 里有符号 command_stepper_snapshot。两处都在,才说明这份 bin 带了扩展。
3. 工具链:两套编译器,两种字节数
母机(RK3562 / Debian)上装:
apt install gcc-arm-none-eabi # 8.3.1
apt install libnewlib-arm-none-eabi # 缺这个必报 string.h not found
第二条经常被漏——只装主包,编译到一半会停在 string.h not found,看起来像源码问题,其实是 newlib 头文件没装。
上位机没有交叉工具链时(老发行版 LTS 结束后包下架),要在 x86 Debian 上编,实测可用 arm-none-eabi-gcc 12.2.1。
但要注意:主/从固件必须同版本,编译机换了就要连带核对上位机那份 Klipper 的 commit。
同一份源码、同一份 .config(kali-fly-d8-f407-usb.config:F407 / 168 MHz / app 0x8008000 / KALI_MCU=y),两套工具链的产物:
| 构建机 | 工具链 | out/klipper.bin |
相对 |
|---|---|---|---|
| RK3562 母机 | Debian gcc-arm-none-eabi 8.3.1 |
39,676 B | 基准 |
| Mac | ARM GNU 15.3 | 46,104 B | +6,428 B(+16.2%) |
同一份源码、同一份配置,字节就是不同。这不是打包失误,是编译器/链接器版本差异(优化、对齐、库版本都参与)。 它带来一个实际后果:sha256 只在"同一构建机 + 同一工具链"内可比,换编译器必须重新入清单,不能拿旧 sha 比对。
两条路线二选一,先定再干:
| 方案 | 做法 | 代价 |
|---|---|---|
| A:工具链随镜像分发 | 把 15.3 工具链放进镜像/源码包,任何机器编出来字节一致 | 镜像变大,工具链要单独维护 |
| B:每台自编 | 接受字节不一致,靠 KALI_MCU_VERSION + 构建时间 + 主机名 区分 |
无法用 sha 判断"两台是不是同一份固件" |
当前状态是混着的:机队走 A(Mac 15.3 编的包),母机走 B(自己 8.3 编)。这种混用状态不能长期留着, 否则"线上是哪一版"永远说不清。
为什么不在设备上编。 每台设备自己编看起来最省事,实际上三处不通:其一,量产上位机(RK3562 / Debian)常年没有
交叉工具链,包在 LTS 结束后下架,装不上就编不动;其二,一次 make 几分钟,批量初始化时这个时间乘上机台数不可接受,
而且 -j4 编译会把正在打印的机器的 CPU 抢走;其三,编出来的字节没有可比性——两台机器各自编,谁也不知道手里这份
和云端那份是不是同一份。母机编 + 固件库分发,收益就是把这三件事一次性消掉:编译只在有工具链的机器上发生,
设备只做"下载 + 校验 + 写入",固件版本成为可审计的字段而不是一句口头承诺。
4. 固件库与 manifest(拉取式)
固件库放在站点静态目录 /demo/fw/mcu/,同目录一份 manifest.json:
| 目标 | 产物 | 大小 | sha256 |
|---|---|---|---|
F407 168 MHz / app 0x8008000(Katapult 32K) |
stm32f407-168m-0x8008000.bin |
39,676 B | 614a15d1… |
H723 520 MHz / app 0x8000000(无 bootloader) |
stm32h723-520m-0x8000000.bin |
38,900 B | 1d9844dc… |
清单字段:MCU 型号 / 时钟 / app_offset / 烧录方式 / sha256 / 版本 / 已知限制。少一个字段就会在现场多一次试错。
四条设计原则:
- 唯一来源:母机 + 同一编译器 + 同一份源码。库里的包必须是编出来的,不是"某台机器上拷下来的"。
- 拉取式:设备自己
wget,云端不反连设备。现场网络不通只影响那一台,不影响别台,也不需要 VPN 入网。 - 清单驱动:设备自报型号/时钟,匹配不到不刷。这条是防"把 H723 固件刷进 F407"的唯一硬闸。
- 已知限制写进清单:
app_offset不可探测只能按组合匹配;HC32F460(CH340 串口那路)不纳入自动推送, 因为它的烧录链路与 STM32 完全不同(TF 卡或 SWD,见第 10 节)。
⚠️ 一个容易踩的对照错误:清单里 H723 那份是 38,900 B,而第 6 节 DFU 实测用的那份是 37,416 B
(v0.13.0-1384 带 -dirty)。两者都是 H723,但不是同一份构建,md5 不能互换核对。
版本串目前不齐:机队上报 KALI_MCU_VERSION=0.1.0-dev / KALI_BOARD=generic,母机自编那份是
kali-f407-p0/0.2.0-p0。打包规范待定。在规范定下来之前,判断"线上是哪一版"只能靠 sha256,不能靠版本号。
回滚包要存在设备上,不要只存在云端。 失败回滚的前提是"设备手里有上一版",而不是"断网时还能从云端下回来"。 所以刷写前把上一版 bin 落到设备固定路径并记录 sha256;云端固件库负责"新版本",设备本地负责"退得回去"。 两件事分开做,现场网络抖动才不会变成一台停机的机器。
5. 烧录路径 A:Katapult(F407)
适用:板上有 32 KiB Katapult,应用区在 0x8008000。
- 停 Klipper 放开串口(Moonraker
services/stop,免密、不用 sudo)。 - 请求进 bootloader:
flash_usb.py -t stm32f4 -s 0x8008000。 - 确认枚举:
lsusb//dev/serial/by-id/出现usb-katapult_…。 - 写入:
~/klipper/lib/katapult/flashtool.py -d /dev/ttyACM0 -f klipper.bin
# Detected USB device running Katapult
# Application Start: 0x8008000
# MCU type: stm32f407xx
# Verification Complete
- 复位回 Klipper,读
mcu_version确认构建戳。
四个坑,复现即用:
| 坑 | 现象 | 正解 |
|---|---|---|
flash_usb.py 用相对路径找 flashtool |
已进 bootloader,报 /home/klipper/lib/katapult/flashtool.py 不存在 |
用绝对路径 ~/klipper/lib/katapult/flashtool.py 再刷一次 |
flashtool.py 依赖 pyserial |
设备上直接刷不了 | 先在目标机装 pyserial |
| 进了 bootloader 却找不到 bin | 板子停在 Katapult,klippy 卡在 startup |
刷前 ls -la 确认文件存在(一次 scp 断连就成这样);用 flashtool.py 救回 |
拿后台 mcu_model 选包 |
型号/固件错配,刷写被拒 | 读运行中固件的 mcu_constants.MCU |
6. 烧录路径 B:DFU(H723,无 bootloader)
apt-get install -y dfu-util # 0.9-1+b1,主池仍在,LTS 结束后也能装
lsusb | grep -i 0483 # 0483:df11 STM Device in DFU Mode
dfu-util -l
# alt=0 @Internal Flash /0x08000000/8*128Kg ← 1 MB,烧录目标
# alt=1 @Option Bytes /0x5200201C/... ← 别选
若 alt=0 的起始地址不是 0x08000000,说明该板用了自定义 bootloader(如 CanBoot),
要按 CONFIG_FLASH_APPLICATION_ADDRESS 改地址,不能照搬本节命令。
铁律一:固件必须与主机端 Klipper 同版本。 不同版本连上就报 MCU protocol error。
判定方法:两台机器 git describe --tags --always 必须一致。
铁律二:光烧 MCU 不够,.config + out/ 也要一起搬。 Klipper 启动时要拿本机构建产物校验 MCU 固件,缺了直接拒绝启动:
Error configuring printer
No build file /home/klipper/klipper/.config
No build file /home/klipper/klipper/out/klipper.dict
ssh <源机> 'tar cf - -C /home/klipper/klipper .config out' \
| ssh <目标机> 'tar xf - -C /home/klipper/klipper && chown -R klipper:klipper /home/klipper/klipper/.config /home/klipper/klipper/out'
烧前备份整片(256 KB 足够覆盖本应用区):
dfu-util -a 0 -d 0483:df11 -s 0x08000000:0x40000 -U flash_backup_256k.bin
烧录:
sudo dfu-util -a 0 -d 0483:df11 -s 0x08000000:leave -D /tmp/klipper.bin
# Download done.
# File downloaded successfully
-s 0x08000000:leave = 写到 flash 基址,写完离开 DFU 并从新固件启动。
dfu-util: Error during download get_status 是 :leave 之后的已知无害提示(芯片当场复位断开),
只要前面有 Download done. / File downloaded successfully 就是成功。
验证要看到构建戳对上,才算真成功:
| 检查项 | 期望 |
|---|---|
| USB 枚举 | lsusb 从 0483:df11 变 1d50:614e stm32h723xx |
| by-id | usb-Klipper_stm32h723xx_<芯片UID>-if00 出现 |
| 构建戳 | mcu_version 里的时间戳(形如 v0.13.0-1384-g7fdaecea-dirty-20260729_104957-<构建主机名>,只看时间戳部分)与源 bin 的 mtime 对得上 |
| md5 | 源 / 中转 / 目标三处一致(实测 d6f9b6a3d54d8826e1af86996b29217b) |
| klippy.log | Loaded MCU 'mcu' 139 commands |
⚠️ 只认 out/klipper.bin。 配置目录下那份 ~/printer_data/config/klipper.bin/klipper.bin 不是构建产物:
实测与 out/klipper.bin 有 36,472 / 37,416 字节不同(md5 799e2c4a…),搬 .config + out/ 时也必须与所刷 bin 同一份构建,
否则 .dict 对不上。
必改 [mcu] serial:。 MCU 的 USB 序列号来自芯片 UID,每块板都不同。沿用别人的 printer.cfg 时那一行指向的是别人的板子,
Klipper 永远找不到设备:
CFG=/home/klipper/printer_data/config/printer.cfg
cp $CFG /root/printer.cfg.bak_before_mcu_serial
NEW=$(ls /dev/serial/by-id/ | grep Klipper_stm32h723xx)
sed -i "s|^serial: /dev/serial/by-id/usb-Klipper_stm32h723xx_.*-if00|serial: /dev/serial/by-id/$NEW|" $CFG
systemctl restart klipper
代价是这一行不再与模板机逐字节一致——但必须改,交付时要明确说"改了哪一行"。
反过来说,若新 by-id 与原有 [mcu] serial: 完全一致,说明这块板就是原配板,不用改。
shutdown: ADC out of range 是传感器没接,不是配置错:
MCU 'mcu' shutdown: ADC out of range
Sensor 'heater_generic 加热段二' temperature 140375333.709 not in range -235.000:350.0
读到天文数字 = 热敏电阻没接、ADC 输入悬空。判定依据:同一份 printer.cfg、同一款板子在整机上读数正常(177 ℃ / 163~171 ℃)
⇒ 是接线差异,不是引脚/配置错。Klipper 进 shutdown 是正确的安全行为,不要为了让状态变 ready 去改 min_temp/max_temp 绕过保护。
主机侧识别主板按名字配对,别数个数。 同一根 USB 上常同时挂着两块设备:主 MCU(usb-Klipper_stm32h723xx_*)
和一个 CH340(usb-1a86_USB_Serial-if00-port0,接第二加热段/变频器等)。数个数会得出"识别到 2 块 MCU"的假结论。
7. DFU 远程救机:先判"固件坏"还是"没离开 DFU"
症状:klipper state=error,日志 mcu 'mcu': Unable to open serial port: /dev/serial/by-id/usb-Klipper_stm32h723xx_…-if00 [Errno 2],
而 lsusb 里板子是 0483:df11 STM Device in DFU Mode(不是 CDC)。
先做零写入判别:
dfu-util -a 0 -s 0x08000000 -U /tmp/flash_dump.bin # 读 1 MB
md5sum /tmp/flash_dump.bin /home/klipper/klipper/out/klipper.bin
实测结论:前 37,416 字节 md5 完全相同(d6f9b6a3…,差异 0 字节),之后全是 0xFF;
0x08000000 处向量表合法(栈顶 0x20020000、复位向量 0x08000345)⇒ 固件写进去了、没坏,只是芯片没 leave。
这一步只读,不写一个字节,可以在任何现场直接做。
恢复 = 同一份固件重刷 + :leave 复位:
sudo dfu-util -a 0 -s 0x08000000:leave -D /home/klipper/klipper/out/klipper.bin
15 秒内:DFU 设备消失 → by-id 出现 → state=ready → MCU 版本对上 → 挤出机 21.5 ℃。
两个坑:get_status 报错正常(见上);板子暴露两个 DFU 接口(Flash + Option Bytes)时报"多个设备" ⇒ 用 -S <serial> 指定,或 -a 0 明确 alt。
根因怎么溯源:journalctl -k 保留了 USB 时间线。实测那台机器上,上午连续三次 DFU 刷机,最后一次见 CDC 枚举是 14:42:59,
此后再无任何 USB 记录 ⇒ 那次刷机没做收尾(没 leave),板子从当天下午起一直停在 DFU。后台 reflash_mcu 零下发记录,
与稳定性插件无关。结论:机队里这类"远程可救"的板子按本节流程处理,不必等现场。
8. 写通道不可靠:问题在 USB hub,不在芯片
同一批镜像、同板型的另一台机器烧不进去,排查记录:
| 维度 | 实测 |
|---|---|
| 写 | 随机偏移断:libusb_control_transfer returned -1(IO)/ -9(STALL)。直接烧停 38%、-t 512 停 36%、-t 256 停在 0%、mass-erase:force 推到 84% 后仍断 |
| 读 | 同一块板 -U 读 256 KB 完全正常 ⇒ 链路没彻底坏,问题在写通道/时序 |
| 芯片保护 | 读选项字节(-a 1 -d 0483:df11 -s 0x5200201C:0x58 -U opt.bin):OPTSR_CUR=0x0306AAD0(RDP=0xAA 未加读保护)、WPSN_CUR=0xFF(无写保护)⇒ 不是被锁 |
| 主机侧旁证 | irq/108 gt9xx 触摸中断风暴 ~600 次/s、该线程长期 D 态(load 6.3 而 CPU 91.9% 空闲);板子以全速 12 Mbps 挂在 HS hub 后面 |
| 解法 | 现场把打印机板 USB 拔插一次(绕开/重置那个 hub)→ 随后一次烧成(try1 即 File downloaded successfully) |
⚠️ 禁令:不要远程 unbind USB hub。 对 1-1.3 写 unbind 后,整条下游设备链(CH340 + DFU 板)一起掉线,
bind 回绑报"没有那个设备",该内核(5.10)也没有 usbX-portN/disable 端口节点 ⇒ 只能物理拔插或重启主机恢复。
USB 问题排查优先顺序:① /proc/interrupts 看有无设备中断风暴 ② lsusb -t 看拓扑与速率 ③ 让现场拔插。
不要从软件侧拆 hub。
9. 出厂一键初始化
同一套能力有三个入口,口径一致:
| 入口 | 用法 |
|---|---|
| 板端脚本 | ~/factory_init/factory_init.py(--detect 只扫不改 / --no-flash / --json) |
| KlipperScreen 面板 | 更多设置 → 初始化MCU;三个按钮:① 识别板子(只扫不改)· ② 烧录并配置ID · 重读ID + 重启Klipper |
| 云端 | 后台 reflash_mcu 远程命令(实测 11 秒完成) |
流程六步:识别下位机 → Katapult 烧固件 → 写 [mcu] serial → 按型号套 printer.cfg 档案 → 起 Klipper 验证 → 失败回滚。
烧录参数(F407 板):
| 项 | 值 |
|---|---|
| MCU | STM32F407,32 KiB bootloader ⇒ 应用起始 0x8008000 |
| 晶振 | 8 MHz(验证:freq = 168000251 ≈ 168 MHz ⇒ 选对) |
| USB | PA11 + PA12 |
| 烧录 | flashtool.py -d /dev/ttyACM0 -f klipper.bin → Detected USB device running Katapult → Application Start: 0x8008000 → MCU type: stm32f407xx → Verification Complete |
四个必须处理的坑:
- 烧录前 Klipper 必须放开串口,否则 flashtool 直接拒(
/dev/ttyACM0 in use)→ 用 Moonraker 的services/stop|start(免密、不用 sudo),烧完再起。 - 配置档案机制:
~/factory_init/profiles/<MCU型号>/printer.cfg(现有stm32f407xx、stm32h723xx两套)→ 识别到型号后自动比对套用; 比对时忽略[mcu] serial(那行按实测 ID 写);改动前备份printer.cfg.bak.profile_<时间>;无档案则保留现状并提示。实测幂等(一致时输出「无需改动」)。 - 打印保护:脚本先查
print_stats,是printing直接拒绝。 - 改名与重启验证:部署脚本用
ActiveEnterTimestamp验重启是否真生效——服务"返回 0"不等于进程换了。
引脚口径(本机实测优先,与主板上游文档有差异:上游标加热口 PD12/PD13/PD14、热敏口 PC2/PC3/PC4、PA0~PA2 是风扇口):
| 段 | heater_pin |
sensor_pin |
感温 | min_temp |
|---|---|---|---|---|
[extruder] |
PA0 | PC4 | Generic 3950 | -100 |
[heater_generic 加热段二] |
PA1 | PC3 | Generic 3950 | -100 |
[heater_bed](原镜像里是注释掉的) |
PA2 | PC2 | Generic 3950 | -100 |
[fan] 模型风扇 / [heater_fan] 喉管散热 |
PD14 / PD13 | — | — | — |
感温一律 NTC 3950(Generic 3950)+ min_temp: -100(原来是 PT1000 + min_temp: -235 ⇒ 报 ADC out of range)。
通电试加热时若加热棒不动而风扇转,就是上面这个差异。台面上没接热敏/加热棒时读数 -70.9 ℃(输入开路)与 ADC out of range 都是正常现象,不是固件错。
屏端还有一个入口:src/panel_firmware.c(1040 行,纯逻辑拆到 src/mcu_fw.c/.h)。
| 能力 | 做法 |
|---|---|
| MCU 型号信息 | 连上时从 Klipper 读;进 bootloader 时从 USB 读(/sys + by-id + lsusb 三路探测,0483:df11 判 DFU) |
| 固件配置编译 | 读/改 .config 关键项 → make -j4 出 out/klipper.bin,日志上屏 |
| 烧录并配置 ID | 二次确认弹窗(目标 MCU + 步骤序列 + 风险) |
| 模式自动分流 | DFU → 先烧 Katapult → 重启 → 再烧 bin;已是 Katapult → 直接烧 bin |
| 每步状态 | 逐行显示 完成 / 进行中 / 失败 / 待执行,跑完不擦掉 |
| 引导固件 | probe_katapult_bin() 现场发现,不猜文件名 |
安全边界:真机只读探测;真烧录 / 真编译留人拍板(双门默认关、打印中一律禁止、干跑要打印完整命令串)。
测试口径:纯逻辑单测 47 个断言(模式判定 / argv 计划 / 日志取尾 / printer.cfg 改写含 [mcu] 行尾注释坑),
端到端替身覆盖 DFU 与 Katapult 两分支(各 8/8、5/5 步 done)。
诚实记录一条:最后一次兜底单行改动后只重跑了编译,回归/单测/替身未重跑——这种状态必须写进交付说明,不能默认"测试通过"。
10. HC32F460:TF 卡自动刷 + SWD 兜底
上游已原生支持 100pin:commit 235b75be(2024-03-19,PR #6488)给 gpio.c 补 PD0..PD15 / PE0..PE15 枚举、
给 hard_pwm.c 补 D12-D15/E2-E5/E8-E15 的 PWM 映射 ⇒ 装最新 Klipper 直接编译,不用像老教程那样改
gpio.c/hard_pwm.c。核实方法:目标引脚(如 PE13/PE12/PH2/PB12/PB13/PB14/PB15)是否在枚举内,
不在就会报 Unknown pin chip name。
编译参数照抄不得改:
Micro-controller Architecture : Huada Semiconductor HC32F460
Communication interface : Serial (PA3 & PA2) ← 板载 USB 那路
Application Address : 0x008000 ← 应用区(引导程序之后)
Clock Speed : 168 MHz
[ ] Enable extra low-level configuration options ← 不勾 ⇒ 波特率锁 250000
- 6 个串口选项互斥(
PA7/PA8、PA13/PA14、PA3/PA2、PH2/PB10、PA15/PA9、PC0/PC1),实现上全是 USART1 换 pin 复用 ⇒ 想用别的脚(如原厂屏那组PC4/PC5)照PC0/PC1那段加一项即可。 - ⚠️ 波特率是编译期绑死的 ⇒
printer.cfg的baud:必须与固件一致,不一致 =Unable to connect,这是最常见的坑。 - 产物
out/klipper.bin实测 20,392 B(sha25608887a96…),.elf入口0x8000。
路线 A:TF 卡自动刷(推荐,不需要主机)
klipper.bin改名firmware.bin。- 放进 TF 卡
user/目录(路径就是user/firmware.bin,根目录那份不见得被认)。 - 主板单独上 12/24V(不接上位机)→ 自动刷,几十秒,刷完有的会自己重启。
- 刷完断电、取卡,删掉卡上的
user/firmware.bin。 - 主板 USB 接回上位机 →
ls /dev/serial/by-path/取 ID 填进[mcu] serial:。
四个坑:
- 卡必须先格式化;不格式化或路径不对 ⇒ 主板"读卡但不动"(症状:重启后不能自动烧录)。
- ⭐
FIRMWARE.CUR会挡住重刷:板子刷完会把firmware.bin改名成FIRMWARE.CUR防重复刷;若卡上原本就有旧的FIRMWARE.CUR⇒ 板子认为"已刷过"直接跳过。必须清掉卡上旧的FIRMWARE.CUR+System Volume Information,只留firmware.bin。 (误操作后卡上出现的FIRMWARE.CUR205,428 B / 2024-03-17 正是出厂 Marlin,也说明这条路可回滚。) - 供电口径:官方指南与社区文档三处说法不一,本板最终结论 = 主板 USB 保持连接 + 上电(USB 供电触发刷机引导),24V 可断。 ⚠️ 该板 USB 是普通串口,不是 DFU,没有"USB 直刷"这条路。
- 刷成功的铁证:卡里
firmware.bin被改名成FIRMWARE.CUR+ USB 重新枚举 + Klipper 从protocol error→ready。
路线 B:SWD 兜底(板刷坏了才需要)
- 调试口 =
J54 针排针(MCU 左侧),丝印直接写SWD/SWDIO/SWCLK/3V3/GND⇒ 按丝印对名接,别按 PA13/PA14 顺序猜。 (PA13 = SWDIO、PA14 = SWCLK;社区还把这两个脚挪去当 TMC2209 UART,二者不能同时用。) - 接法:
SWDIO→SWDIO、SWCLK→SWCLK、GND→GND(必共地);原厂固件可能占着 SWD 脚 ⇒ 尽量把RST也接上做 connect-under-reset。 - ⚠️ ST-Link 的 3.3V 不要接到板上
3V3(板子用 USB 供电,避免倒灌);只接 USB 5V,不接 24V、不跑电机。
| 项 | 实测值 |
|---|---|
| 工具 | pyOCD 0.45.1(独立 venv,不污染主环境) |
| 探针 | pyocd list 能列出 ST-Link;驱动 = WinUSB(libwdi),固件 V2J37M7 |
| 芯片支持包 | HDSC.HC32F460.1.0.11.pack(3,407,498 B),内含 FlashARM/HC32F460_512K.FLM(26,128 B) |
| 目标类型 | hc32f460petb(HDSC / HC32F460-Series) |
- 备选:
marisusis/openocd_hc32(原生 OpenOCD 没有 HC32F460 的 flash 驱动)、STM32CubeProgrammer、HDSC 官方工具。 - hex 必须严格解析:按记录类型(
04h扩展地址等)解析。本项目 hex 数据覆盖0x8000 ~ 0xCFA7⇒ 不碰0x0~0x7FFF的出厂引导。 ⚠️ 别用"扫到第一个地址"的偷懒解析——会把 hex 的起始地址记录误当数据,得出"覆盖 0x0000,危险"的假警报。 - 纪律:脚本里
assert min_addr >= 0x8000、显式禁止整片擦除、写后读回 sha256 与源文件比对;验收不接受"应该成功了"。 - 实测卡点在硬件:
ProbeError: STLink error (9): Get IDCODE error,3 种连接模式 × 3 种频率 9 组参数 0/9 全同一报错 ⇒ 物理层没通(线没接对 / 缺 RST / 原厂固件占脚),不是软件参数问题。连接失败即自动中止,未做任何擦写,芯片零风险。 - 兜底:照丝印接好仍报 IDCODE error,就把 SWDIO/SWCLK 对调再试一次(不会损坏任何东西)。
11. STM32G0B1:STEP/DIR 引脚怎么选
自研驱动板或评估低端 MCU 时,第一个问题通常是"STEP 接哪个脚"。结论:
- G0B1 没有步进专用引脚。
- STEP 必须用定时器 PWM 通道输出(要硬件调速/硬件脉冲计数);DIR / EN 是静态电平,任意 GPIO 都行。
方法:从数据手册 DS13560 Rev 6 的 Table 13~20 逐页提取 AF 映射,再用 CubeMX pin data 交叉核对 60 个带定时器的引脚,
全部一致(差异只是命名:手册写 TIM1_BKIN、CubeMX 写 TIM1_BK)。
| 定时器 | 通道 | 可用引脚(括号为 AF 号) |
|---|---|---|
| TIM1(高级,128 MHz,CH1-3 有互补) | CH1 | PA8(2)、PC8(2)、PE9(1) |
| CH2 | PA9(2)、PC9(2)、PB3(1)、PE11(1) | |
| CH3 | PA10(2)、PC10(2)、PB6(1)、PE13(1) | |
| CH4 | PA11(2)、PC11(2)、PE14(1) | |
| CH1N / CH2N / CH3N | PA7 / PB13 / PD2 / PE8 · PB0 / PB14 / PD3 / PE10 · PB1 / PB15 / PD4 / PE12 | |
| TIM2(32 位,通道全 AF2) | CH1 | PA0、PA5、PA15、PC4 |
| CH2 | PA1、PB3、PC5 | |
| CH3 | PA2、PB10、PC6 | |
| CH4 | PA3、PB11、PC7 | |
| TIM3(通道全 AF1) | CH1 | PA6、PB4、PC6、PE3 |
| CH2 | PA7、PB5、PC7、PE4 | |
| CH3 | PB0、PC8、PE5 |
选型提示:
- 要互补/死区或最高脉冲频率 → TIM1(高级定时器,128 MHz)。
- 要32 位计数(超长行程不分频也不溢出)→ TIM2。
- 引脚与其它外设打架时按上表换通道即可,DIR/EN 永远不用抢定时器。
- 与 F4 家族不同:G0 属低端线,外设集/AF 表不能照搬。
12. 刷写 SOP 检查清单
按顺序过,任何一步不过就停:
- 识别:读运行中固件的
mcu_constants.MCU+CLOCK_FREQ,不要用后台缓存值。 - 选包:型号 + 时钟 +
app_offset三元组匹配清单,并核对 sha256。匹配不到不刷。 - 空闲判定:
print_stats.state非printing/paused且virtual_sdcard.is_active=false。注意ready≠ 空闲。 - 停服务:放开串口。
- 备份:预存设备上一版 bin(失败要能刷回),必要时整片读回。
- 烧录:Katapult 或 DFU,按第 5/6 节命令。
- 校验 + 复位:写入阶段完成校验,再
:leave复位。 - 验证:klippy 重连、
mcu_version构建戳、KALI_MCU_VERSION、温度读数合理。 - 失败回滚:自动刷回上一版,并记录到审计。
后台"一键升级 MCU"就是把上面九步包进一个 helper:识别 → 选包 → 进 bootloader → 写入 + SHA 校验 + 复位 → 验证 → 失败刷回。 批量升级必须带空闲判定 + 冷却 + 审计,否则一次误操作能把正在打印的机器全部打断。