文件
phonerail/PROGRESS.md
T
zhou yang和Claude Opus 5 2bfe308b30 初始导入:PhoneRail 五路手机直供电源板
原理图/PCB 已在 EasyEDA 里做到 12 页跨页 0 漏接,固件刚完成骨架包 A1。
这次是把已有的一切一次性纳入版本控制,没有改动任何设计内容。

纳入:
- 设计与需求文档、PROGRESS.md、小米8 参考原理图 PDF 与追脚脚本
- .claude/ 下的 EasyEDA 桥接工具链与几何/网表缓存(eda-parts.json、
  pcb-parts.json、pcb-pads.json、p2-ch1.json 等,是重画各页的唯一事实来源)
- 117 张验证截图(本项目所有方向问题都是靠人眼看截图发现的,属于证据不是垃圾)
- firmware/ 的 A1 骨架与 site/ 文档站源码

.gitignore 排除:构建产物(firmware/build、host/build、sdkconfig)、
site/node_modules、__pycache__、桥接日志 _bridge.log、脚本自标的 *.stale。

.gitattributes 默认 `* -text`:现有 .py/.json 大多是 CRLF,原样存,
不让 git 重写行尾。唯一例外是 *.sh 强制 LF —— 带 \r 的 shell 脚本 bash 跑不了。

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
2026-09-06 18:18:14 +08:00

191 KiB
原始文件 Blame 文件历史

进度与交接

更新:2026-09-06 第二十四轮(I²C 干线改走 Inner2)。 详见设计文档 §26.20(上一轮是 §26.17 / §26.18 / §26.19)。


⭐ 新会话从这里开始

一句话现状

原理图 11 页全部验证通过。 下板 PCB1:摆位 342 件、扇出、15 块铺铜、五路功率级 195 段全部下发并回读过。 这一轮把 I²C 的跨板长干线挪到了 Inner2(302 mm 内层,§26.20)—— 目的是腾外层通道给布不通的线,然后立刻跑了第三遍信号布线(--ripup)验证效果:

挪之前 挪之后 + 第三遍布线
已连通网络 148 163
还欠连接 87 条 / 74 网络 68 条 / 59 网络
DRC 连接性错误 1044 855
板上铜 3196 线 / 1000 孔 3516 线 / 1084 孔

铺铜已重铺回读(Inner2 浮铜从 1312.8 mm² 降到 0.2 mm²,剩的那点是新布线蹭出来的碎屑)。

⬅️ 下一步

⛔ 那 74 条 DRC 间距错误的真因已查清(§26.21):漏跑 pcb1_parts_dump.py。 §26.18 重排的 44 个焊盘导进 PCB 了,但缓存没重抓 ⇒ 后面两轮布线里 33 个网络的铜连的是错信号。已拆掉那 530 段 / 2482 mm / 138 孔并重布(跑完对一遍 DRC)。 ⚠️ 规矩:pcb1_import.py --apply 之后下一条命令就是 pcb1_parts_dump.py。 中间插别的操作,后面全部几何和布线都建在沙子上,而六道自检一条都不会响 (它们读的是同一份缓存,自洽得很)。

期间走过两条弯路,都记在 §26.21 末尾:多边形焊盘不许按包围盒判 (QFN 散热盘的盒子会套住邻脚,凭空报 21 处假「重叠」); DRC 的「间距」那一支不陈旧(重载前后逐字相同,不能当旧数据糊弄)。

剩下 68 条连接性(59 个网络,大多各欠 1~2 条)。还能腾外层的两个大头:

候选 现状 说明
BUCK_PG 已连通,顶层 234 mm、跨 121 mm 剩下最大的一块外层占用,挪进 Inner2 能再腾一大片
INA_ALERT 欠 2 条 试过,没跑通:铜整片在顶层且挤在控制块,主碎片周围一个合法孔位都没有
USB_DP/DM 各欠 2 条 不许进内层(90 Ω 以 L2 为参考,§10.1),只能在外层解

常规循环(一步都不能省):

/c/Python314/python -u .claude/pcb1_route_sig.py --refresh --ripup --push
/c/Python314/python .claude/pcb1_pour.py --push --refresh
/c/Python314/python .claude/drc_read.py

⚠️ pcb1_route_sig.py --ripup 认「哪些铜是自己画的」靠覆盖判据,不是坐标比对 —— pcb1_i2c_l3.py 画的 Inner2 干线和 pcb1_gnd_stitch.py 的 6 个缝合孔它都不认得。 2026-09-06 实测跑完一轮 --ripup 之后 55 段内层线一段没丢,但这不是保证 —— 跑完对一遍 _i2c_l3_snapshot.json(或 pcb1_i2c_l3.py 不带参数跑一遍数碎片)。

⚠️ 引擎的层数已经不是写死的 2 了(2026-09-06 改):pcb2_route_sig.Field 现在按 障碍表里真正出现过的层建数组、viafree 取全层最小值。不这么改的话,板上一有 layer 16 的走线就 KeyError: 16 当场崩;就算滤掉,它打的新过孔也不会对内层干线 做净距判断(旧的 viafree 只看 Top/Bottom),孔会直接压在内层线上。

⚠️ pcb1_route_pwr.py --push --verify 是只 verify,不下发 —— main() 里 --verify 在 --push 之前 return,而它照样打「✅ 全部校核通过」, 只在最后一行回读里露馅(「195 段里没被实测铜盖住的 195 条」)。要下发就只写 --push。

⚠️ pcb_fanout.py --clean 会把板上全部走线删光,包括 pcb1_route_pwr 的 195 段。 所以「改了扇出」= 扇出三步 → 重铺铜 → 重跑 route_pwr → 再 route_sig,一步都不能省。

⏱ 实测速率:route_sig 前两个网络(SDA/SCL,各挂 14 个焊盘横跨整板)要 1516 s, 之后 5 个网络只要 68 s。别拿头几个网络外推总时长;全程约 60~90 分钟。 它是跑完才一次性下发的 —— 中途停掉板上一根铜都不会变,但也等于全白跑。


✅ 第二十四轮(2026-09-06,用户拍板):I²C 干线改走 Inner2(§26.20)

SDA/SCL 是全板最长的两个网络(各 14 个焊盘、横跨整板), 既吃布线时间(1516 s)又占着外层最好的通道。用户拍板:走内层,只挪跨板长干线。 新工具 .claude/pcb1_i2c_l3.py(三层迷宫布线 Top / Bottom / Inner2)。

改之前 改之后
I²C 总铜长 596 mm,内层 0 609 mm
Inner2 — 302 mm
顶层 / 底层 596 mm 157 / 150 mm
连通性 SCL 3 片 / SDA 4 片 一模一样

中桥那 13 mm 是唯一留在外层的一段:12V-SW+BR 的中桥(x 61.5~72.6,喂 3.6 A) 是 Inner2 上东西两半唯一不能割的隔断,I²C 过去就必须横着割 ⇒ 只能外层跳。 (上一轮 route_sig 给 SDA 布的就是这个跳法,殊途同归。)

三条这一轮踩到的坑(细节全在 §26.20):

  1. PEN_3V3 决定的不是长度,是平面会不会被切出孤岛。 1.8 的时候两根线在 3V3-SW 里围出 7.2 mm² 孤岛,里面有 R415.2 的 +3V3 焊盘 —— 浮铜检查 / 焊盘覆盖检查 / 连接性 DRC 三样全都不响,只有数连通域看得见。 堵孤岛重绕不收敛(7.2 → 25.4 → 26.8 → 32.5 mm²)。改成 4.0: 干线走 GND 底填,只在器件旁边扎盲端进平面。
  2. expand() 会静默丢掉最后一点上的过孔 —— 引擎 _end_via 的注释早写着,又踩一遍。
  3. own(本网铜放行)要按半线宽内缩,过孔则根本不吃这条放行。

顺带修掉一块和这次无关的浮铜:板子南沿 y 0.59 那条 GND 底填(1312.8 mm²) 在 Inner2 上是孤立的,一个落脚点都没有 —— 它原本靠 x 115116 那条 1.0 mm 缝接出去, 被上一轮信号布线的三个过孔的反焊盘掐断了。新脚本 .claude/pcb1_gnd_stitch.py 在南沿补了 6 个 GND 缝合孔。⚠️ 这一条只有铺铜下发后 verify() 回读实测铜才量得出来, 下发前的 report() 栅格不含别的铺铜,算出来 GND 是一整块。

验收:净距/拐角/孔距 ✅ · 平面完整性 ✅(+12V 1 块 / +3V3 2 块)· pcb1_pour.py --push --verify ✅ 实测和计划对得上 · DRC 里没有一条牵涉 SDA/SCL。


✅ 这一轮 C(2026-09-06):8 A 的源只有 3 个过孔(§26.19)

第一次跑信号布线跑到 35/196 中止 —— 不是布线器的问题,是它报出来的失败把两个上游问题照了出来。

  1. Rs6.2 → +12V 平面只有 3 个 Ø0.3 过孔 ≈ 3~4.5 A,而它要过 8 A。 它是全板 +12V 平面唯一的源。这一条四道验证一条都不管:铺铜断面只量平面本身、 扇出只报「孔放下了没有」、连接性 DRC 只看「连上没有」、逃逸审计只看「出得去吗」。 ⇒ pcb_fanout.py 新增 FORCE_ARRAY(整片阵列钉死,逐个仍走全套净距校核), Rs6.2 钉 12 个孔(≈12 A),摆在焊盘正南那片空地。 规矩:>4 A 的「焊盘 → 平面」转换点,过孔数必须手写。
  2. 顺带把 U13 的 I²C 放了出来。 原来那 3 个孔有两个甩到西边,两根 1 mm 引线 (端帽再往西探 0.5)压死了 U13 南排五个脚唯一的通道;VINSW-TOP 北柱又只差 0.379 mm。 12 个孔改朝南 + 北柱在 y>87 收窄到 x 1.55.3(3.8 mm → 8.4 A), U13 南边变成一整片 x 5.514 / y 91.6~96 的空地。
  3. 扇出不对称又发作了一次:C%06.2 的 GND 孔在 rot 0 的三路往东落到局部 (10.02,27.10)、 在 rot 180 的 CH2/CH3 往西落到 (6.75,27.10),正压在 eFuse 脚 14/15 的出线口上。 已加进 _CH_FORCE_LOCAL。
  4. 剩下 10 个「被围死」不是板上的铜 —— 报出来的挡路物去 pcb-copper.json 里查不到, 是审计自己给邻脚预留的出线段。判据就是这个:查得到才是真障碍。

✅ 这一轮 A(2026-09-06):v11 的两行排法把 8 A 输入链卡死了,已修(§26.17)

§26.16-E 换 XT30 之后把 N-L 块改成两行,摆位的本地检查六项全过, 可是回去重画 §26.11 的顶层宽铜时第一刀就下不去: VIN_RAW 从 J1 出来根本没有 3.49 mm 的路 —— XT30 rot 270 之后 GND 在北针 (13.5,74.5)、 VIN 在南针 (13.5,69.5),而 F1 卧着放和 GND 针同一行,中间又被 D1(17.5,69.5) 堵住, 实测通道只剩 1.674 mm(1 oz 外层约 4.5 A,这里要 8 A)。

这一类错误几何检查一条都报不出来 —— 不重叠、不越框、高度也够。 抓到它的是「照新坐标把顶层宽铜的多边形重推一遍」这个动作本身。 ⇒ 摆位改完不能只看本地检查,必须把 §26.11 那三块多边形重推一次。

三处改动(pcb1_place.py 的 N-L 表):

# 改动 拿回了什么
① F1 卧 → 竖(rot 0 → 90),(17.5,74.5) 不动 F1.1 落到 (17.5, 73.06),正对 J1.2 那片空地
② D1 (17.5,69.5) → (31.0,74.5) rot 270 通道里最后一个障碍搬走,TVS 贴到漏极铜东缘
③ C4/C5/C6 x 25 → 30.5,rot 0 → 180 让出 y 78.85~83.5 那条 4.64 mm 的 VIN_SW 西行干道

顶层三块宽铜重画后实测断面全过:RAW 颈 3.50(要 3.49)、FUSED 5.25、SW 干道 4.75、北柱 5.25 mm。 面积 59 / 30 / 263 mm²,各 1 个连通域,check_top_clearance() 零侵入。

⚠️ 顺带抓到一个静默失效:Rs6.2 一个过孔都没打。 12V-N 为了让开 U13 的 Kelvin 脚,在西边咬了个缺口 (0.5,93.0)-(12.5,101.5), 而 Rs6.2 的焊盘中心 (9.57,93.50) 正落在缺口里 ⇒ 扇出判成「不在铜面上」, 全板唯一的 8 A 源不接电源平面。--push 照样报「失败 0 个」,连接性 DRC 也不响 (+12V 靠别的 55 个焊盘照样连通,只有源那一端是断的)。缺口收成 (0.5,95.6)-(12.5,102.4)。


✅ 这一轮 B(2026-09-06,用户拍板):三组引脚按几何重排(§26.18)

「哪个信号落在哪个物理脚」有三处是自由的,而原来全是按逻辑顺序排的, 和它们在板上要去哪儿一点关系都没有。目标函数用绕开芯片本体的直角最短路 (HPWL 量不出「东边的脚要去西边」有多贵:同一批改动 HPWL 说省 7%,绕障路长说省 15%)。

组 结果 挪了几个
ESP32-S3 U20 22 个信号 1195 → 1020 mm(−15%),绕模块的线 3 根 → 0 12
DAC63204 U6/U7 5 个通道 422 → 352 mm(−17%),一根线都不用新画 4
74HC595 U21/U22/U33 21 个位 1286 → 1141 mm(−11%),每路三键成束 17

合计省 约 390 mm。三处关键决定:

  1. 放开 IO39~IO42(原留给引脚 JTAG)—— 西边从 4 个坑变 8 个。 调试本来就走原生 USB-Serial-JTAG(IO19/IO20 → J22,出厂 eFuse 默认就是它), 板上没有 JTAG 排针。⚠️ 永远别烧 DIS_USB_JTAG,烧了两边都没有。 让出去的只是「USB 起不来时还能进去」这个兜底。 EFUSE_EN1 → IO39、VBUS_EN1 → IO40、EFUSE_EN4 → IO41、VBUS_EN4 → IO42。 空出来的是 IO4 / IO5 / IO7(三个都带 ADC1)+ RXD0/IO44。
  2. DAC 只在已画好线的 5 个通道里置换(U6 管去西半板的 VDAC1/2/4,U7 管去东半板的 VDAC3/5)。 动用 U6.OUT3 / U7.OUT2/3 还能再省 6 mm,但要新画 OUT/FB 两根线,不值。
  3. 595 加了「每路 3 个按键必须落在同一片的连续三个南脚上」的约束 —— 比自由指派多 40 mm, 换来三根线出芯片就是一束。U33(y≈70)从「EDL 专用片」变成「北边那一片」, 接管 CH2/CH3 的六个按键 + HUB_RST,因为它正对 y 87~94 的北排两路。

⛔ U33 现在在 P6-FAN-EDL 那一页上带着 CH2/CH3 的按键网络 —— 页面语义乱了一点, 但三片本来就是一条 24 位链,功能上没区别。

落地时踩到的两条 API

  1. ⚠️⚠️ setState_Net 对 netport 是坏的:调用之后网络名变成空字符串,标签也没了 (和 skill §3b 的 setState_X/Y 同一个毛病)。⇒ 端口只能 删掉重建。
  2. ✅ skill §8b 那句「端口的 dir 读不回来」是错的: getState_Component().name 就是 Netport-BI / Netport-IN / Netport-OUT。 重建前读它原样传回去即可 —— p1_add.py 当年「端口只增不重建」的限制可以解除了。

顺手治了:图面文字叠了好几代

P3 有 44 条 sch_PrimitiveText、P3B 64 条、P6 76 条,大量是同坐标同内容的完全重复 (历代脚本只 create 不 delete),还有互相矛盾的旧说明叠在一起。 四道验证(verify_sch / xpage / port_audit / DRC)一条都不管文字,所以它烂了很久没人发现。 .claude/sch_text.py 整页重写:44→13 / 64→14 / 76→16,顺带改掉换脚后变成假话的几条。

这一轮新增的脚本

文件 用途
pinmap.py ⭐ 三组「逻辑信号 → 物理脚」映射的事实来源 + 落地 + --check 回读对账
sch_text.py 三页图面文字整页重写(删光旧的再写一套)

⚠️ 还欠人眼确认

  • PCB1-v11-NL.png(北左输入链改动前)与改动后的截图 —— 还没截改动后的
  • P3-CONTROL-v2.png / P3B-EXPAND-v2.png / P6-FAN-EDL-v2.png 三张换脚后的原理图
  • 摆位总图 PCB1-v11-place.png(v11 那一版,D1/C4/C5/C6 的新位置还没进图)

上一轮(2026-09-06 第二十二轮):拆线重布 + 两处换件 + 控制块按几何重排(§26.16)

# 改动 依据
1 J1:KF128-5.08-2P → XT30PW-M(C53373776),(8.5, 72.0) rot 270 用户要插拔式;12V 是 8 A,DC-002 那类只有 1 A
2 J26~J30:KF2510-3A(弯针) → 2510-3A(直针)(C2803349) 用户要立式;进深 12.01 → 5.81
3 N-L 块重排 XT30 比 KF128 宽一圈,原位放不下 ⇒ 这一版被第二十三轮推翻,见上面 A
4 控制块:affine() → CTRL 表(退火求解,pcb1_ctrl_place.py) 原来 U21/U22 中心只隔 8.04 mm,中间塞不进一根线
5 22 个附属 R/C:affine() → SAT 表(逐个贴对端引脚) R602/R603 是 USB-C 的 CC 下拉,离 J22 有 77 mm

全板 HPWL 9416 → 8935 mm。摆位大改之后 pcb1_wipe.py --push 把板上全部铜清空过一次 (走线 / 过孔 / 铺铜,元件 / 板框 / 丝印不动),13 个旧账本改名成 .stale。

这一轮沉淀的脚本(仍然有效):

文件 用途
fp_body.py ⭐ 从器件的 3D Model Transform 直接读本体框(--scan / --check)。⚠️ 只给 XY,Z 读不出来
p1p6_conn.py J1 / J26~J30 换件(做到一根导线不动)
pcb1_ctrl_place.py 控制块五颗芯片退火求位置(目标 HPWL)。⚠️ 它写的 _pcb1_ctrl_pos.json 只是参考,pcb1_place.CTRL 是硬编码的事实来源
pcb1_wipe.py 清空板上全部铜(铜层白名单 1/2/15/16)

⛔ 三个要记住的地雷(仍然有效)

  1. pcb1_route_sig.py 的 --net / --limit 试水以前会把计划缓存清成 {}(已修)。
  2. clean() 以前只删得掉 59% 的自己的铜(已修,改成覆盖判据)—— EasyEDA 会合并共线段、在 T 形接点劈线,坐标精确比对认不出来。
  3. 「两个端点钉死 ⇒ 连通性不变」是破的 —— 链会 T 在别的链中段上。 现在先按搭接点切开(409 → 797 条),拆完还要用 pcb1_netlist.analyze() 实算碎片数兜底。

⚠️ 已知偏差(是决定,不是遗漏)

  • 立式风扇座把装配顺序锁死了(用户 2026-09-06 拍板接受):立式插口朝上正对载板底面, 座 + 胶壳约 11.5 mm,13 mm 净空里插拔只剩 1.5 mm ⇒ 必须先插好五根风扇线再合载板。
  • H_FP 里 XT30 的 11.0 和直针座的 9.0 是估计值 —— 3D Model Transform 给不出 Z, 打样前拿实物量。
  • 控制块的通道分配还没改 → 第二十三轮已经做完(§26.18)。
  • POGO2_VUP / POGO2_VDN 离 SW 节点 0.54 / 0.74 mm(§10.3 要「远离」), sw_keepaway() 会一直报,当提示不当错误。

✅ 这一轮(2026-09-06 第二十一轮):五路母版三处整改 + 全链重跑(用户拍板 A+B+C)

设计文档 §26.15。§26.14 抓出的 22 个「几何上没有出路」的引脚,这一轮动手改掉, 改的全是母版(= 五路一起改),改完整条链重跑。

# 改了什么 结果
A SW 不再往南潜:C%05 北移 0.6,SW-run 平着走 y 12.500(1.2 宽),删掉 SW-down 五路 EN 全部解放;SW 节点 9.6 → 8.9 mm / 铜面积 10.9 → 9.4 mm²
B U%8 + Rs%01 + C%13 整体北移 0.8 eFuse 北排那条缝 0.33 → 1.13 mm
C 跟着 B 改的三处:_CH_FORCE_LOCAL['U%__8','1']、F-trunk 北端、K-IN± 相对几何一格没变
— 全链重跑:place → clear → dump → fanout → pour → route_pwr 342 件 / 498 孔 / 15 铜面,功率级 195 段回读 0 条对不上

「几何上出不去」的焊盘:22 → 8(pcb1_escape_audit.py 实测)。 剩下 8 个是和邻脚预留出线段抢地方,不是硬几何 —— 单独布都能通。

⭐ 这一轮踩到的三条

1. 「端帽比端点往外探半个线宽」这一条,一轮里踩了两次。 先是 SW-run 1.5 mm 的西端帽把 EN 封死(在 y=11.5 那一行往西探到 x 11.007, 比端点多探 0.743);改完起点又发现同一个端帽离 U%1.13(BOOT 引脚) 只有 0.080。 ⇒ 凡是宽线的端点,心里都要按「端点 ± w/2 的圆」去想,§26.11 已经写过一次了。

2. 挪 0.1 mm 解决不了拓扑问题。 A 组先试过「SW-run 起点东移 0.35」和「收窄到 0.9」, 两次都卡在 5~20 µm 上 —— 因为 COMP 和 EN 两个脚要并排出来,缺的不是余量是车道。 把 C%05 北移 0.6、让 SW 整条不再下潜,才是解。

3. verify() 只认「同宽」的线会报假阳性。 一段窄线整个落在宽线的圆头端帽里时 EasyEDA 会直接吞掉它 —— 铜一点没少, 回读却报 5 条对不上。判据改成「实测线不窄于计划线,且中心距 ≤ (o_w − p_w)/2」。

4. chamfer_routes(smooth=True) 会挪线(最多 0.15 mm),不只是拿掉铜。 而它的净距校核拿的是切之前的自身路径 ⇒ 两条本轮新画的线会被各自抹到对方身上 (实测 $1N227 vs POGO1_PWR 压成 −0.199,2255 段里 2 段)。 ⇒ 切完再跑一遍 check(),把牵涉到违规的整条退回未切的版本,最多退 4 轮。


✅ 这一轮(2026-09-06 第二十轮):下板信号布线 —— 工具全套建好,⛔ 没下发

# 做了什么 结果
1 新增 pcb1_netlist.py:按实测铜(含回读的铺铜)算全板连通性 还欠 200 个网络 / 384 条连接;顺带发现 GND 其实早就通了
2 新增 pcb1_route_sig.py:下板信号布线器(复用载板栅格引擎,只加不拆) 379 条里布通 284 条(75%),净距 / 钻孔 / 铺铜 三样全 0
3 新增 pcb1_escape_audit.py:逐个焊盘算「能不能走得出去」 563 个待接焊盘里 22 个几何上出不去
4 Kelvin 三条(U13.8/9/10 ↔ Rs6)单独钉死,不许就近扎平面 3 / 3 条,w 0.30
5 USB 差分对「一整束布线 + 平行偏移」 束长 58.1 mm,W 0.28 / S 0.18,对内偏斜 0.00 mm
6 修掉 4 个真 bug(见下) —

⭐⭐ 这一轮最值得记住的四条

1. POLYGON 焊盘的包围盒不是它的形状 —— 又一次。 §26.13 抓的是它的 hole 是假的;这一次是 pad[1], pad[2] 那对宽高。 TPS54622 的散热盘是个带四个缺口的多边形(缺口正是让给 pin 1/7/8/14 的), 按 2.032 × 4.320 的包围盒当障碍,这四个脚整个泡在 GND 铜里: entry_stub 一个出线方向都找不到,BUCK_PG 报「五路全布不通」。 ⇒ POLYGON 焊盘的任何标量属性都不能直接当几何用,只能读 pad[1] 那串点。 现在 pcb1_route_sig.poly_pads() 把 11 个多边形盘按真实轮廓光栅化成矩形条。

2. 网名里带 # 的网络会被引擎的 netof() 拆坏。 引擎用 key.split('#')[0] 从「路径 key」还原网名,而下板有 KEY_OE# 和 IOEXP_INT#。 'IOEXP_INT#' + '#0' 拆回来是 IOEXP_INT ⇒ 校核时它和自己的焊盘被当成异网, 报「线-焊盘 净距 −0.100」,看上去像压线,其实两者本来就该连在一起。 ⇒ 分隔符改成 chr(0)。凡是「用某个字符拼 key」的地方,都要先问一句这个字符会不会出现在数据里。

3. 45° 切角会切进铺铜里 —— seg_slack() 不认铺铜。 切角是往拐角内侧补铜(不是只拿掉铜),而引擎的切角校核只看焊盘 / 过孔 / 走线。 实测 SDA 在 (6.10, 65.50) 被切进 VO1_P 小岛,只剩 0.043。 ⇒ 把铺铜矩形条包装成「假焊盘」一起喂给 chamfer_routes() 和 check()。

4. 0.5 脚距的脚,「放得下」和「栅格上走得通」是两回事。 U8.3(INA226 的 ALERT):西边 1.00 mm 是 GND 扇出孔,东边 1.00 mm 是邻脚的出线段。 0.20 的线需要 1.005 mm(差 5 µm 过不去),0.15 的线只要 0.955 mm —— 可 0.1 mm 栅格上判据要各加一个 QERR 0.0707,变成 1.096 mm,整片不可达。 0.025 mm 栅格的 QERR 只有 0.0177,同一条缝就通了。 ⇒ 新增「细栅格逃逸」:0.1 栅格上出不来的脚,先在 4 mm 内用 0.025 栅格单独走出去。 实测 47 个脚里救回 25 个。

⛔ 剩下 22 个脚是几何上就没有出路,必须动板子

组 焊盘 卡在哪 差多少
A U1.10 U2.10 U3.10 U4.10 U5.10(Buck 的 EN) pcb1_route_pwr 的 SW-run 1.5 mm 铜,圆头端帽从 (20.05,27.10) 往西探到 x 19.30 脚东边净空 0.376,一根 0.2 的线要 0.6
B U14.2/3 U15.2/3/14/15 U16.2/3/14/15 U17.2/3 U18.2/3(eFuse 北排) 上面 INA226 的焊盘边 只隔 0.33 mm,下面自己的散热盘 0.25 mm 要 ≥ 0.6,缺 ~0.7 mm
C U10.3(ALERT)U13.4(SDA) U13.5(SCL) 隔壁 GND 扇出孔正对着出线走廊 5 µm

A 组的解:EN 是每路都有的信号,本来就该进 §10.2 母版 —— 在 pcb1_route_pwr.R 里 加一条 EN 路径,并把 SW-run 的起点往东挪 ~0.35 mm(或前 2 mm 收窄到 1.2)。 收窄反而符合 §10.2-2「SW 铜箔面积压到刚好够载流」。

B 组的解:CH1 那一列从下往上是 C106(顶 43.46) → U14(43.85~47.75) → U8(48.08~53.92) → Rs101(54.27~57.73),缝一律 0.330.39 mm,一点余量都没有。 唯一的空地在 Rs101 顶到通道北界 y 59 之间的 1.27 mm ⇒ 把 U8 + Rs101 整体北移 **0.70.8 mm**(母版改一处 = 五路一起改)。 ⚠️ 这会连带作废 扇出 + 铺铜 + 五路功率级布线(U*.9 → Rs*01.1 的 Kelvin 线也在母版里)。

C 组的解:给那两三个 GND 扇出孔在 pcb_fanout._CH_FORCE_LOCAL 里钉一个偏 0.1~0.2 mm 的位置。

⚠️ 另外 95 条是拥挤,要拆线重布才能再往前

失败集中在控制块(U21U23,x 75107 / y 43~58)和 J24/J25 排针周边; 按网络族数:$14N 9 / $8N 7 / $12N 7 / EFUSE_PG 6 / $10N 6 / INA_ALERT 5 … 现在这个布线器是一遍过、只加不拆(这是有意的:板上已有 691 条线不能被抹掉), 所以先布的把后布的堵死了 —— 这正是商业布线器用 rip-up & reroute(协商式拥塞)解决的那一类。 ⇒ 下一轮要么加拆线重布,要么等 A/B/C 三组改完再看还剩多少。

顺带记两条

  • POGO2_VUP / POGO2_VDN 离 SW 节点只有 0.54 / 0.64 mm(§10.3 要求「远离」)。 已经有 sw_keepaway() 在报,不当错误、只列出来。
  • 8 个 45° 切不动的拐角(转向 89~90°),都在细缝里,chamfer45 试到 0.05 半径仍撞墙。

这一轮的产出(都在 .claude/ 下,没有一个动过板子)

文件 用途
pcb1_netlist.py 按实测铜算连通性:焊盘 / 过孔 / 走线 / 回读的铺铜当图的节点,--todo 列还欠几条,--json 出任务表。⚠️ 判据不能只测焊盘中心那一格:同网通孔盘在回读的环里是个洞
pcb1_route_sig.py 下板信号布线器。--net / --limit / --push / --clean --push / --verify / --refresh。内含 Kelvin 强制表 FORCE、USB 束 + 偏移、细栅格逃逸、铺铜障碍、sw_keepaway()
pcb1_escape_audit.py 逐焊盘的逃逸可达性:两层泛洪 + via_ok() 打通,走不到 4 mm 外就报。--why <焊盘> 列出挡路的铜。⚠️ 判据环不能取自由区最外圈(那一圈 free() 永远是假的)

✅ 这一轮(2026-09-06 第十九轮):五路功率级精调(§26.13)

# 做了什么 结果
1 修 POLYGON 焊盘的假 hole(dump_pads / pcb_fanout / pcb1_parts_dump 三处) 七个散热盘回到扇出名单
2 盘内阵列改成按真实多边形撒点 + 自适应间距 + 补中心孔 U1~U5 0 → 5 个孔;U6/U7 0 → 4;eFuse 2 → 6
3 摆位 v9:L*01 / U*14 / Rs*01 各转 180°,C*05 与三颗输出电容挪位 四条回路的朝向全部理顺
4 新增 pcb1_route_pwr.py:母版 27 条路径 × 5 路 = 210 段,全部顶层 一路 74.8 mm,SW 13.1 → 9.7 mm
5 新增 pcb_fanout._CH_FORCE_LOCAL:5 个焊盘 × 5 路 = 25 个 FORCE_VIA 五路的扇出终于真的一样
6 重跑全链:place → 扇出三步 → 铺铜 → 布线 → DRC 495 孔 / 691 线 / 间距 0 / 物理 0 / 连接性 594

⭐⭐ 这一轮最值得记住的四条

1. 「有孔 = 通孔盘」是错的判据 —— POLYGON 盘的 hole 是假的。 板上 11 个 POLYGON 盘无一例外满足 hole = 0.600 × min(w,h)(U1 的 2.032×4.320 → 1.2192, U6 的 1.70×1.70 → 1.02,J22 的 1.30×0.60 → 0.36),EasyEDA 渲染出来是实心的。 判据应该是层号:layer == 12 才是多层通孔盘。

2. 五路的扇出结果并不相同 —— 明明摆位一模一样。 扇出规划器的候选角度按板坐标排,而 CH2/CH3 整路转了 180°, 同一个焊盘在 CH1 往东扇、在 CH2 就往西扇。母版里算好的走廊于是在 CH2/CH3 上被堵死, 而报出来的是「CH2 的 F-trunk 撞到一个 GND 过孔」——看上去像布线画错了。 ⇒ 关键焊盘按 CH1 的解钉死(FORCE_VIA),钉死之后照样走全套净距校核。

3. DRC 的「物理错误」那一支平时是空的,容易漏看。 0.28/0.25 的线下发之后:间距错误 0、连接性还降了 125 条 —— 而 物理错误·导线 里躺着 50 条「线宽 11mil,应该 ≥ 11.8mil」 (SWNODE/PWR4A 的 strokeWidthMin 都是 0.30)。加宽到 0.30 清零。

4. 回读走线不能按端点比:EasyEDA 会在 T 形接点上把已有的线劈成两条。 西侧那条 +12V 母线下发之后在板上是两条(支线接在中间)。 按端点比报「210 段里 40 段对不上」,而铜一点没少。 ⇒ verify() 用覆盖判据:每 0.2 mm 取一个点,要求被同网同层同线宽的实测线盖住。

⚠️ Kelvin 的那一条:U*8.9 不许有过孔

U*8.9(INA226 的 IN−)已经写进 pcb1_pour.ROUTED_PADS。 让它走 VO*_P 小岛的话,取样点在岛上而不是分流电阻的焊盘上,中间隔着 Rs*01 那三个扇出孔(并联约 0.5 mΩ)。Rs 只有 5 mΩ、满载才 20 mV, 0.5 mΩ × 4 A = 2 mV = 10% 的读数误差,而 DRC 和连通性检查都看不出来 (IN− 和 VO*_P 本来就是同一个网)。pin 8 是 VBUS,照旧进岛。


✅ 上一轮(2026-09-06 第十八轮):F1 → 12 A / R9 → 5 × 2Ω 2512

两件都是用户拍板的选型改动,走完整链路:改原理图 → 导入变更 → 重推摆位 → 重扇出 → 重铺铜 → DRC。详见设计文档 §26.12。

# 做了什么 结果
1 F1 F1206HC10A0TM → F1206HC12A0TM(C310990) 同是 1206,摆位一格没动;封装对象换了(F1206)但焊盘几乎一样
2 R9 立式水泥电阻 → R9A~R9E 5 × 2Ω 2512 串联 10Ω/10W,高 22 → 0.8 mm
3 pcb1_import.py 加 --bom 原来 _p1_sch_bom.json 在仓库里没有生产者
4 pcb_fanout.py 加 guard_not_pushed_twice() 我推重了一次,888 个过孔(见下)
5 P1-INPUT 文字收拾 66 → 14 条,清掉一整代旧说明
6 结果 342 件 / 444 过孔(重复 0)/ 15 块铜面 / DRC 间距 0 + 连接性 725

⚠️ 先纠一条我上一轮写错的

「F1 封装是 1206 但 BOM 写 10A、1206 做不到 10A」—— 错的。 F1206HC10A0TM 本来就是 1206/10 A,BOM 和封装从来没对不上。 换 12 A 的真实理由是降额:8/10 = 80% 超过保险丝常规的 75% 线,且 I²t 只有 15 A²s。

⭐⭐ 这一轮最值得记住的两条

1. pcb_fanout.py --net all --push 不幂等 —— 我连推了两遍。 板上变成 888 个过孔(每个都有同坐标同网的副本),而短线数没变(444): EasyEDA 把共线重叠的新线并进了旧线。连接性 DRC 一个字不报(副本同网,电气上「没错」)。 唯一判据是回读过孔数。现在 --push 前会硬拦;重推必须 --clean → dump_pads.py → --push 一次跑完。

2. H_FP 里那个 9.5 是照封装名 W9.5 抄的,而 R9 是立式件。 RES-TH_L13.5-W9.5-P5.80-D0.7:本体 13.5 长、脚距只有 5.80 ⇒ 它站着 (同族 3D 模型标 H52.0)。那一片 hmax 只有 12.0, 所以「高度预算全过」在它身上假了整整一轮,是用户看出来的,不是检查看出来的。 ⇒ 往 H_FP 加直插件的判据:脚距和本体长度对不上就是立式, 高度不是封装名里的 W,是那个对不上的方向。

⛔ 新增的两个地雷标记

  • p1_add.py 已过期,底稿 p1-base.json 停在 2026-09-04。再推一次会把 R9A~R9E 之间那 6 段导线静默回退。已加硬闸门(要跑得带 --i-know-it-is-stale)。 ⇒ 改完一页就问一遍:还有哪个脚本会重画这一页。
  • 改过原理图必须 pcb1_import.py --bom 再 --verify,否则是拿新 PCB 和旧原理图对账。

截图:PCB1-v8-r9f1.png(R9A~R9E 的 1+2+2 蛇形)/ P1-INPUT-v2.png(原理图)。 还没等到用户回话。


✅ 这一轮(2026-09-06 第十七轮):8 A 顶层宽铜

# 做了什么 结果
1 pcb1_pour.REGIONS 加三块顶层铜面 VINRAW-TOP 46 / VINFUSED-TOP 19 / VINSW-TOP 284 mm²(实测)
2 网名用 pcb1_rules.resolve_auto() 当场反查 $1N223 / $1N227 / $1N236,一个字没写死
3 Grid 分内层 / 外层两套障碍模型 外层连贴片焊盘一起挡;新增 obstacles() 把 438 个过孔 + 441 条走线算进去
4 新校核 check_top_clearance()(不过格子) 当场抓出两处侵入,其中一处栅格看不见
5 新校核 poly_vs_rects() 手写的 24 个点 vs 它该等于的 8 个矩形,0.05 mm 逐格对照
6 下发 + 回读实测铜 15 块铜面全部铺出来,三段各 1 个连通域
7 DRC 间距 0 / 连接性 719(铺顶层铜前是 733)

⭐⭐ 这一轮最值得记住的:线的端点不是铜的端点

北柱东缘写在 x 6.8,Rs6.2 那条 +12V 出线的端点在 x 7.401 —— 看起来隔 0.6 mm。 但那条线 1.0 mm 宽、圆头端帽往外再探 0.5 mm,实际铜到 x 6.901, 真实净距只有 0.101 mm。而 0.25 mm 的栅格上这 0.1 mm 一个格心都落不进去 ⇒ 栅格全绿。真下发下去 EDA 自己会往回缩一点点铜,板子不会报 DRC, 但板上那块铜就不再是计划里那块铜,后面所有断面数字全部作废。

⇒ 凡是手画的铜,都要过一遍 check_top_clearance() 这种真实几何的校核, 栅格只能用来算连通域和断面。

顶层三段长什么样(实测断面,顶层 1 oz)

最窄两处:干道 x=12 处 4.75 mm(10.0 A)、北柱 y=92 处 5.00 mm(10.4 A), 8 A 下 ΔT 分别约 12 / 11 °C。其余各点 8.75~21.5 mm。

截图:PCB1-v7-top8A.png(只开顶层 + 焊盘 + 顶丝印)。还没等到用户回话。

⚠️ 顺带看出来的一件:F1 的封装是 1206,但 BOM 写的是 10 A

FUSE-SMD_L3.2-W1.6-1 = 1206,而 §7 的 BOM 写「保险丝 10A 慢断」。 1206 的贴片保险丝一般做到 5 A 左右就到顶了。8 A 的铜宽本身没问题, 是器件选型和封装对不上。换大封装会动 J1→F1 那一段的摆位 ⇒ 要重推摆位 + 重扇出 + 重铺铜。这一条留给用户拍板。


✅ 这一轮(2026-09-06 第十六轮)做完的

# 做了什么 结果
1 修去耦电容摆位:新增 pcb1_place.DECAP 表 + place_decaps() 7 颗跟着本尊走,最远的储能 3.31 mm,其余 0.76~2.24 mm
2 修 pcb1_parts_dump.py 漏了焊盘自身旋转 8 个元件的 bb 各长 0.260.30 mm(U1U5 / U6 / U7 / U20)
3 新增 pad_clearance_errs():焊盘到焊盘净距 0.2 mm 全板 1171 个焊盘一条不报
4 修 pcb_fanout.TARGET(还停在 §26.8 之前) 删掉 VOn / +5V / 8 A 三段,它们不上 Inner2
5 修 _pour_filter()(指着单板时代的 pcb_pour.py) 改指 pcb1_pour.REGIONS;实测滤掉 17 个铜面外的 +3V3 焊盘
6 扇出规划器补五条(见 §26.9) GND/+12V/+3V3/VOnP 全部失败 0 个
7 扇出下发 438 过孔 + 441 短线,DRC 间距 0
8 实现 pcb1_pour.py --push / --verify 12 块铜面已下发;回读实测铜 24 项全 OK
9 下发前 / 下发后共用同一段 grid_checks() 分两套写只能证明两套代码各自自洽
10 新校核:每块碎片都要有本网过孔落脚(浮铜) 9 组 (网络,层) 的 12 块碎片全部有落脚

⭐⭐ 第 1 条是这一轮最重要的发现:ESP32 的储能电容在 48 mm 外

追「U6.15 为什么扇不出去」追出来的。全板 20 个 IC 的 +3V3 电源脚里 只有 1 个的去耦在 3 mm 以内;C601(22 µF「ESP32 储能」)离 U20.2 48.2 mm, C602(100 nF)45 mm,U20 电源脚周围 12 mm 内一个元件都没有。

根因:控制块是 affine(['P3-CONTROL','P3B-EXPAND'], ...) 按原理图坐标仿射映射摆的, 它保「原理图上挨着的板上也挨着」,保不住「去耦贴着电源脚」; 而 U20 是上面手工单独摆的 —— 芯片被拎出了仿射集合,去耦却留在原地。

⇒ 动控制块布局时先看 DECAP 表,它是这件事的事实来源,check() 里有硬校核。 表的顺序 = 抢位置的顺序,小的高频那颗必须排在大的储能之前(理由见 §26.9)。

⭐⭐ 第 2/3 条:本地检查曾经「全绿而板子是错的」

改完摆位本地六项全过,DRC 却报 4 条间距错误:C607_2 到 U6_13 只有 0.096 mm。 两个原因叠在一起 —— pcb1-parts.json 的包围盒漏了焊盘自身的旋转(QFN 侧边脚是长条焊盘转 90° 用的, U6 的 bb 在 y 上短 0.26 mm);而 check() 只查「包围盒重不重叠」,不重叠不等于够净距。 现在两条都修了,新检查直接照 DRC 那条 0.2 mm 的规则算,量焊盘不量包围盒。

⚠️ 同一套 API 里 getAllPinsByPrimitiveId 的 rotation 是度, pcb_PrimitivePad.getAll() 的是弧度。

下板 PCB1 接下来的顺序

  1. 让用户人眼确认摆位 v9 + 功率级布线(截图已出,见上面「一句话现状」)。 不算硬阻塞,但这一轮动过五路的摆位,值得看一眼。

  2. 设计规则 ✅ §26.7 2. 定铺铜几何 ✅ §26.8

  3. 扇出 ✅ §26.9 / §26.13 4. 铺铜 ✅ §26.10,已下发并回读复核

  4. 8 A 输入链顶层宽铜 ✅ §26.11,已下发并回读复核

  5. 五路功率级精调 ✅ §26.13,已下发并回读复核

  6. 写下板信号布线器 ✅ pcb1_route_sig.py 已建好并跑通 75%(第二十轮), pcb1_route_pwr.py 那 210 段一根没动(这个布线器只加不拆,删除只按坐标删自己画过的)。

  7. A/B/C 三组几何整改 ✅ §26.15,用户拍板 A+B+C,已全链重跑并下发。 全链的正确顺序(动过摆位就照这个走,一步都不能省): pcb1_place --push → pcb1_clear --push → dump_pads → pcb_fanout --net all --push → pcb1_pour --refresh --push → pcb1_route_pwr --push → pcb1_route_sig --push → pcb1_pour --refresh --push(给新过孔补反焊盘)→ pcb1_reload → drc_read。

  8. ⬅️ 下一步:给 pcb1_route_sig.py 加拆线重布(rip-up & reroute),收掉剩下的 91 条。 ⚠️ 直接再跑一遍 pcb1_route_sig.py --refresh --push 也会有进展(它读板上的铜、 只补还缺的),但一遍过的策略已经到顶了 —— 真要清零得能拆别人的线。

    ⚠️ 北左角的顶层现在被 8 A 宽铜占了大半(x 1.528.9 / y 68.693)。 U13 的 I2C、U29、R9 那几条信号得走底层—— 底层整层基本是空的,不占地方。 三条还没布的细线已经有名有姓地写在 pcb1_pour.ROUTED_PADS 里: U13.10(Kelvin VIN+,接北柱顶端那一小截死头)/ DZ1.1 / C1.1(栅极)。

⚠️ 动了铺铜或摆位就要重跑 pcb1_pour.py --push,它自己会先把计划跑绿再下发; 下发完自动 --verify 回读实测铜。只想看不想改板就 --verify(加 --cache 用上次的回读)。

⚠️ 动过摆位就要重跑扇出三步,不能省也不能并: pcb_fanout.py --clean → dump_pads.py → pcb_fanout.py --net all --push。

⚠️ 连接性数字扇出之后会先涨再跌:布线前基线 1145 → 扇出完 1336 → 铺完铜 733。 过孔本身是新对象,铺铜之前每一个都是自己网络里的孤岛。 只看扇出完那一步会以为扇出把事情弄坏了。

⚠️ EasyEDA 客户端这一轮掉线过三次(EDA window ... disconnected)。 掉线时命令可能已经执行完只是回话丢了 —— 别直接重试,先回读板上对象数再决定。

⚠️⚠️ 一条永远别忘的:J24 / J25 的针号是故意错一位的(§26.5,用户已拍板选 A)

p5_btb.py 的 MATE_SWAP = True 让下板这一侧的网络按针号奇偶对调 (两颗封装焊盘编号同手性 + 母座在载板底层、顶视图镜像一次,不改的话下板 VO1_P 会顶在载板 GND 上)。c1_conn.py(载板 C1-CONN)用的是原始表,一个字没动。 ⇒ 判据是网络不是针号:下板 pin(2r−1) 正对载板 pin(2r),差一位是预期的。 每次动完摆位跑 pcb1_place.py --verify 逐个焊盘核。

Inner2 分块的结论(详见设计文档 §26.8)

层 网络 名称 形状
Inner2 +12V 12V-N 北带 y 88~124.5 通板 + 风扇指下伸到 y 66.5,挖掉 U13 那一角
Inner2 +12V 12V-SW+BR 南西块 (0.572, 934) + 中桥 (61.572, 3488),⌐ 形
Inner2 +12V 12V-SE+RC 南东块 (116159.5, 934) + 右通道 (153.5159.5, 3488),L 形
Inner2 +3V3 3V3-SW / 3V3-SE 南部两块,故意不连通(见下)
Inner2 GND GND-FILL-L3 剩余全部
Bottom VOn_P 五块小岛 各约 18 × 13 mm
Inner1 GND GND-L2 整板,只挖 ESP32 天线区

退回走线的四类(都算过,不是省事):+5V 底层 1.2 mm 干线 / VOn 顶层 1.5~2.0 mm / VOn_P 底层小岛 / 8 A 输入链顶层宽铜 ≥3.5 mm。 关键数:内层 0.5 oz 与外层 1 oz 差约 4 倍(8 A 内层要 18.2 mm,外层只要 3.49 mm), 而这块板底层整层是空的 ⇒ 短距离大电流根本不该上内层。

⚠️ +3V3 在 Inner2 上是 2 个连通域,是设计意图不是漏接。 中桥必须纵向穿过 y 41~58.5 那条带,而 +3V3 的南部焊盘横向铺满同一条带 —— 同一层上两个都要连通的 2-D 区域,交叉拓扑上避不开。让 0.5 A 的那个换层:南西 / 南东各自从北边引一条底层干线。

⭐ 这一轮校核当场抓到的一个错:孤岛判定是按铺铜对象算的,不是按网络

第一版把 +12V 写成六个矩形,栅格连通性说「1 个连通域」,看着完全正常。 新加的孤岛规则校核报出来:12V-RC(右通道)里一个 +12V 焊盘都没有 —— 哪怕它和北带紧挨着、铺出来是连续铜,它自己那一块里没有本网焊盘/过孔照样被整块删光 (边框还在、nPoured 是 0)。⇒ 并成三个 L / ⌐ 形多边形,每块各自含风扇座的通孔针。

另外两条:只有通孔焊盘在内层挖反焊盘(贴片对内层透明,板上实测 122 个通孔); 区域间净距不能用包围盒估(L 形的 3V3-SE 包围盒把 12V-FAN 整个罩住, 算出来 0.00 mm 像严重违规,实际相距 0.50 mm)。

下板还欠的几件(都不影响现在动手)

  • C2 / C3 罐高必须 ≤ 11.5 mm(现在按 12.0 建模,H_FP 里那一条)。 载板底面在那块地方是光板,hmax = 12.0,卡着上限过;换一颗矮罐才有余量。§22.3f
  • CH2 的 POGO2_* 三根要横穿 60 mm(信号在 J25、电源在 J24,载板那边决定的,§26.3)。
  • U20 的天线端离下板边只有 1.5 mm,而且上面还盖着载板 —— 这块板的 WiFi 本来就指望不上, 真要用就得把天线引出机箱。
  • 北中块 x 58118 / y 85125 有一块约 60 × 40 的空地(CH2/CH3 的排线车道从这里过), 留给电源面和风扇线,暂时不填。

下板 v6 的布局长什么样(设计文档 §26.3 / §26.4)

 y 125 ┌──────────────────────────────────────────────────────────────┐
       │ P1-INPUT      │  CH2 (北,rot180) │ P1B + P6 + J28 │ CH3 (北,rot180) │
       │ J1/F1/Q1/D1   │  x 30.1~57.3     │ x 58~118       │ x 118.5~145.7   │
       │ C2/C3/R9/U19  │                  │                │                 │
  y 66 ├───────────────┴──────────────────┴────────────────┴─────────────────┤
  62.5 │  ==== J24 (x 5.3~58.7) ====        ==== J25 (x 101.3~154.7) ====     │
  y 59 ├───────────────┬──────────────┬─────────────────┬────────────────────┤
       │ CH1 (南)      │  CH4 (南)    │ P3/P3B 控制块   │  CH5 (南)          │
       │ x 10.6~37.8   │  x 42.3~69.5 │ U20/U6/U21~23   │  x 121.8~149.0     │
   y 0 └───────────────┴──────────────┴─────────────────┴────────────────────┘
   J26/J27 左板边   J29/J30 右板边   J28 顶板边   J22 调试口在右板边

下板新加的工具(.claude/ 下)

文件 用途
pcb1_import.py 下板「从原理图导入变更」:--probe 读全部变更行 / --apply 真的导 / --verify 拿 PCB 元件表和原理图 BOM 逐条对账
pcb1_parts_dump.py 抓 pcb1-parts.json(每件的真实焊盘框 + 每个焊盘的本地坐标)。导入变更之后必须跑
pcb1_env.py 把载板底面折算成下板的高度预算图 pcb1-env.json(zones / lanes / slots / J24-J25 锚点);hmap() 打一张 ASCII 高度地图
pcb1_place.py 下板摆位模型 + 本地六项检查 + --push / --verify(回读焊盘核对插)
pcb1_clear.py 拆掉下板全部铜(导线 / 过孔 / 铺铜边框),保留板框、M3 焊盘、丝印
pcb1_rules.py 下板设计规则:线宽 5 档 / 差分 1 对 / 铺铜全连接;自动命名网按位号+引脚反查。--push / --check
pcb1_place.DECAP 去耦电容跟着本尊走的事实来源(第十六轮加)。表里的电容不参与仿射摆位,芯片摆完之后自动贴到电源脚旁边;check() 里有硬校核
pcb1_place.pad_clearance_errs() 全板焊盘到焊盘净距(0.2 mm,照 DRC 那条规则)。「不重叠」不等于「够净距」
FANOUT_WHY=U6.15 环境变量。打印这个焊盘所有候选孔位卡在哪一道校核上,按次数排序
p1_r9f1.py P1-INPUT 换件(F1 → 12 A / R9 → R9A~R9E)。增量改法:先 create 后 delete、旗删掉重建、坐标全 5 的倍数、引脚坐标断言不过就停手
p1_text_fix.py P1-INPUT 文字去重 + 挪位。幂等(新增块也走「先删再建」),逐条对账最终 14 条
pcb1_import.py --bom 逐页重抓原理图 BOM(_p1_sch_bom.json)。改过原理图先跑它再 --verify,一页一个请求(eda.py 截 20000 字符)
pcb_fanout.guard_not_pushed_twice() 板上已有过孔就拒绝 --push。这脚本只加不删不幂等,推两遍会叠出同坐标副本而短线数和 DRC 都看不出来
pcb1_pour.check_top_clearance() 顶层手画铜面到异网铜的真实几何净距(不过格子)。0.25 mm 栅格漏得掉 0.1 mm 的侵入;走线的圆头端帽比端点还往外探半个线宽
pcb1_pour.poly_vs_rects() 手写的复杂多边形 vs 它该等于的矩形并集,0.05 mm 逐格对照。写错一个坐标,别的检查一个都发现不了
pcb1_pour.obstacles() 板上已有的铜(438 过孔 + 441 走线)。顶层铺铜要躲它们,内层只躲过孔
pcb1_route_pwr.py 五路功率级手工布线的事实来源(§10.2 四条回路)。路径写在 CH1 母版局部坐标里,仿射到五路。不带参数 = 计划 + 五项校核;--push 下发(--clean --push 先删本轮旧线);--verify 按实测铜核覆盖;--ch N 只看一路
pcb1_netlist.py 下板连通性的事实来源:按实测铜(含回读的铺铜)算每个网络还欠几条。--todo / --json / --refresh(第二十轮加)
pcb1_route_sig.py 下板信号布线器(只加不拆)。Kelvin 强制表 / USB 束+偏移 / 细栅格逃逸 / 铺铜当障碍 / sw_keepaway()(第二十轮加)
pcb1_escape_audit.py 逐焊盘的逃逸可达性。「布不通」到底是这一头出不来还是中间挤,只有它分得清(第二十轮加)
pcb1_reload.py 下板专用的客户端重载(DRC 的连接性那一支要 reload 之后才准)。⚠️ 别拿 pcb2_reload.py 当通用工具:那一份写死载板 uuid,而且会把板上全部铺铜边框删掉
pcb_fanout._CH_FORCE_LOCAL 五路通道里必须五路一样的那几个孔(按 CH1 的解钉死再展开)。不钉的话 CH2/CH3 会往反方向扇
pcb1_pour.py 下板铺铜的事实来源:REGIONS 定义每一块。不带参数 = 计划 + 五类校核 + ASCII 分块图;--push = 校核跑绿才下发,完事自动回读复核;--verify(--cache 用上次回读)= 只按实测铜再跑一遍。下发前后走的是同一段 grid_checks()

⚠️ pcb_rules.py 是单板时代的,已被 pcb1_rules.py 取代,别再 --push 它。 ⚠️ pcb_fanout.py 仍然是下板在用的(PCB = 7e94…),载板的 pcb2_fanout.py 是它的壳。

⚠️ pcb_place.py / pcb-parts.json / pcb-pads.json 是单板时代的,别再拿来摆下板。

✅ 已拍板并落地:C745 往南挪 2.7 mm(2026-09-06,用户选 A)

C745 从 A 圈上的 (100.05, 61.70) 挪到 (100.0, 59.02)(pcb2_place.hub_layout() 里单列一条, 不再跟着 A 圈的 for 循环走)。GND 脚 (100.0, 58.60) / +3V3 脚 (100.0, 59.44), 扇出孔实际打在正东 (100.87, 58.60),引线 0.65。 pcb2_place --push → pcb2_clear(跨元件净距全过)→ pcb2_fanout --only C745.2 --push → reload → pour → inner1_verify(五块岛仍各 1 连通域)→ DRC 115 间距(基线)+ 332 连接性。

⇒ 载板上除了 J7n 那 65 个 GND 脚(归 bm24_pre() 管),贴片 GND 焊盘已经全部扇出。

pcb_fanout.py 顺手加了 --only 位号[.引脚],...:板上已经有整轮扇出、只想补一两个漏网的时候用, 不会重复下发另外那 247 个。

当初为什么非挪不可(留作记录)
方向 挡住的东西 余量
南 UP_DM 东行段 y=60.60(宽 0.28) 孔要 0.64,只有 0.41
北 自己的 pin1 → 再往北是 U32 南排焊盘 y=63.55 缝 0.73,要 1.0
东 R715 pin2 缝 0.667,要 1.0
西 UP_DM 竖段 x=99.25 / UP_DP 竖段 x=98.75(相距 0.5) 塞不下 Ø0.6 的孔

并到邻近同网焊盘也不行 —— 最近的 GND 是 C743 pin2(2.70 mm 外),中间横着这两条竖段。

(已作废)等用户拍板:C745 的 GND 脚被 UP 差分对堵死

C745(Hub 的 +3V3_HUB 去耦 0402,GND 脚在 (100.05, 61.28))是全板唯一扇不出去的焊盘。 不是搜索不够狠,是几何上真的没有位置:

方向 挡住的东西 余量
南 UP_DM 东行段 y=60.60(宽 0.28) 孔要 0.64,只有 0.68−0.27=0.41
北 自己的 pin1(+3V3_HUB)→ 再往北是 U32 南排焊盘 y=63.55 缝 0.73,要 1.0
东 R715 pin2 缝 0.667,要 1.0
西 UP_DM 竖段 x=99.25 / UP_DP 竖段 x=98.75(相距 0.5) 两条之间塞不下 Ø0.6 的孔;西边那条缝只有 0.01 mm 宽

并到邻近同网焊盘也不行 —— 最近的 GND 是 C743 pin2(2.70 mm 外),中间横着这两条竖段。

三个选项(都要改布局,所以留给用户定):

  • A. C745 转 90° 并东移到 (100.0, 61.7) 一带(脚变成东西向,GND 脚在西)。 实算过:孔打在 (100.0, 62.47) 时离 UP_DM 竖段 0.75、离 U32 pin56 焊盘框 0.15,都过。
  • B. 把 UP_DM / UP_DP 那两条东行段整体往南挪 0.3~0.5 mm(要重推 pcb2_dp_long.py, 下游 J23 那一头也要跟着动)。
  • C. 不管它 —— 那一颗去耦电容的地就只能靠 pcb2_gnd_patch.py 想办法, 想不出来就是一条连接性 DRC,实物上等于这颗电容白焊。不推荐。

✅ 这一轮(2026-09-06 第十三轮):载板 77 个信号网络全部布通并下发,DRC 回到基线

结果
pcb2_route_sig.py 87 个网络 / 459 条路径 / 1030 线 / 210 孔,一个没布通的都没有
本地净距校核 全部通过 ✔
pcb2_gnd_patch --check 板上悬空的 GND 连通域 0 个
inner1_verify 五块岛各 1 主域、3 焊盘 + 11 过孔全在主域,最窄 29.20 / 7.05 / 7.20 / 7.70 / 7.60
pcb2_route_dp(回归) 11 对差分偏斜全部 ✔(最大 4.75 mm)
DRC 115 条 = 全部是封装自带的「贴片焊盘到贴片焊盘」基线;间距 0 新增、连接性 0

⇒ 载板 PCB2 布线完成。 截图 PCB2-v6-routed-all.png / PCB2-v6-hub.png / PCB2-v6-site4.png。

BM24 那一段是整个重写的(v4 的常量在 v5 上全错)

v5 把 J7n 从 rot 180 改成 rot 0 又挪了位置,站内坐标整体差 u' = u − 5.00 / v' = 32.00 − v(v 轴是镜像的)。 照抄映射也不行 —— v 一镜像,「前排外侧那条缝」换到了另一边,而那边站着 R76n/R77n。 v5 的走法(和 v4 反过来,因为后排 25 个脚在 v5 里全是 GND,没有信号脚要往外挤):

  • 前排 5 个 GND(pin 2/8/12/16/24)→ 往内进中央通道,v=35.00 的 0.30 梳子,4 个孔(跳过 u 50.05)
  • 后排 7 个 GND(pin 1/19/23/29/37/41/47)→ 往外,v=33.10 的梳子,5 个孔(那一带一路空到 v 26)
  • pin52 往东平出到 u=59.60 打一个孔
  • THERM / CC1 独占前排外侧:CC1 一根直线上去(R76n 中心特意取 52.233,让 pin1 正好落在 J7n.22 正北); THERM 打两个孔走顶层跨过 CC1

新增 j7_assert():每次跑都拿板上实际焊盘核一遍那一堆手写常量(五个站 × 13 个 GND 脚 + THERM/CC1 + R76n/R77n)。 没有它的时候,坐标失配的表现是「在空地上画了五套梳子」—— 净距校核当然全过(空地不撞人), 只有 DRC 的连接性会含糊地多出几十条。

这一轮修掉的四个真 bug(都在 pcb2_route_sig.py 里)

  1. 搜索窗口边上没有墙。障碍是裁到窗口里再栅格化的,完全落在窗外的铜一格都不标, 于是贴着窗口边走的线看不见它。实测 SCL 的窗口从 x=44.02 起,J31.6 的焊盘框(42.10~43.80) 整个在窗外,布线器就在 (44.10, 67.40) 打了个孔,离那焊盘只有 0.134。 改法:数组往外扩 RIM=1.2 标障碍,再把外面这一圈整体标成占用 —— 可走区域不变,但看得见窗外的铜。
  2. 钻孔间距不分网络,但 viafree 只看异网铜。于是布线器紧挨着 pcb2_dp_long 已经打好的孔又打一个 ((92.500,70.000) 与 (92.400,70.500) 中心只隔 0.510),DRC 报「钻孔到钻孔 8.1mil,应 ≥ 11.8mil」, 净距校核一声不吭。新增 holefree 距离场(所有过孔、不分网络)。
  3. 手写前置段没有一开始就进障碍表。原来只有 GND 的在循环里补进去,THERM/CC1/+3V3 的要等轮到自己 —— 排在前面的网络照样从这些手写坐标上碾过去。
  4. +3V3_HUB 上 pcb2_dp_long 画的三条会被静默删掉。push_sig() 是「本脚本负责的网络整网删掉重画」, 而那三条被 D± 夹着、后面再也挤不进去。改成从同一个事实来源(pcb2_dp_long.build)取回来当前置段, 既让最小生成树知道那些焊盘已经连上,下发时也原样重画。

四段手写坐标(栅格布线器进不去的地方,理由都写在代码注释里)

网络 为什么手写
THERM_n 出阵列后第一次拐弯的 v 只有 0.053 mm 的窗口(下受前排焊盘、上受 *_DPJ 换层孔),而且那一段必须保持 0.11
+3V3_HUB U32.52→U32.46 pin46 在东排被 VBUS_DET/NONREM0 围死,唯一出路是往 QFN 肚子里走(南排焊盘上沿与散热盘之间那条 1.6 mm 死带)
+3V3_HUB C750.1→R716.2 R716.2 是最东那颗 +3V3 焊盘,南北被两个 GND 脚夹住,只能翻顶层斜拉过来
HUB_RST / VBUS_DET R709 与 R712 之间只有 0.66 mm 的缝。0.15 的线走得过去(上下各 0.23),但栅格判据要 0.371 而半缝只有 0.33

一处已知的口子(不是错,是要记住的事)

+3V3_HUB 现在由 pcb2_route_sig.py 下发(含 pcb2_dp_long 那三条)。 ⚠️ 再单独跑 pcb2_dp_long.py --push 会把 route_sig 布的整个 +3V3_HUB 删掉重画(它按网络整删)。 要改差分的话,顺序是 dp_long --push → route_sig --push,不能只跑前一半。 另外 45° 化把那条顶层干线的近直角拐点切成了两个 51° 的转折(sharp_corners 报「切不动」)—— 净距校核过、不是直角,属于可接受;但它意味着板上那条干线的几何已经和 pcb2_dp_long.py 里写的不完全一样。

为此动的两处布局(都验过并重扇出)

  • R76n/R77n:v 37.15 → 38.0,R76n 的 u 顺带改成 52.233。 理由:J7n 前排逃逸带原来只剩 0.58 mm,THERM 和 CC1 一个往东一个往北必然交叉,同层无解, 想让 THERM 翻顶层又打不下孔(要离前排 0.5 ⇒ v ≥ 36.80,又要离 R76n 0.5 ⇒ v ≤ 36.38)。 挪完逃逸带 1.43 mm,两条各走各的。挪之前先用新写的 pcb2_del_box.py 把 R77n.2 的旧扇出孔删掉。
  • C745 往南 2.7 mm(用户拍板,见下)。

新工具

pcb2_del_box.py:删掉指定框里某个网络的线与过孔(板坐标或站内坐标,站内会自动展开到五个位)。 板上的铜是好几个脚本各画各的,挪了元件之后那颗扇出孔就成了没人认领的孤儿 —— 谁的删除逻辑都不管,DRC 只报一条「过孔没连上任何东西」,很容易被当噪声放过。 判据是完全落在框里才删(半进半出一律不动)。

pcb_fanout.py --only 位号[.引脚],...:板上已经有整轮扇出、只想补一两个漏网的时候用。

上一轮(2026-09-06 第十二轮):GND 扇出 + VOn_P 进岛,两步都下发并复核

# 做了什么 结果
1 pcb_fanout.py 补三个缺口(见下);pcb2_fanout.py --push 247 孔 / 122 短线,只有 C745 pin2 扇不出去
2 pcb2_vo_vias.py 扩到 35 个焊盘(补 C8n1.1 与 J8n.1,各 3 孔)并修出线层 55 孔 / 55 线,净距全过、0 个直角
3 pcb2_reload → pcb2_pour --push → _refresh_geo → inner1_verify 五块岛各 1 连通域,3 焊盘 + 11 过孔全在主域;最窄 31.30 / 7.05 / 7.20 / 7.70 / 7.60(下限 7.0)
4 drc_read --pcb2 450 = 间距 115(与基线一模一样,无新增)+ 连接性 335(布线前 519,−184)
5 截图 PCB2-v5-fanout-all.png / PCB2-v5-fanout-site1.png / PCB2-v5-inner1.png

这一轮修掉的四个「一直在但没暴露」的 bug

  • 扇出规划器从来不看板上已有的走线(只看焊盘 / 过孔 / 钻孔)。下板做扇出时板上一根线都没有, 所以从没暴露;载板 v5 是先布了 178 条差分 + 3V3 才扇出的,不修的话过孔会直接压在 D± 上, 而几何验证抓不到(它只查连通性)。现在 EXIST_LAYERS 把 1/2/15/16 层的线全登记成障碍, 线对线分层比、过孔对线不分层(过孔贯穿)。
  • dump_pads.py 有 88 个焊盘认不到位号:pad 坐标是干净的、pin 坐标带 1 µm 噪声, 两边各自 Math.round 到 0.01,落在 .xx5 上的值进了不同的桶。改成 Python 端最近邻配(容差 0.02), 846 个里认到 805(剩下 41 个是螺钉孔和手机定位柱,本来就不属于任何元件)。 这个 bug 会静默传染:J75 pin15/16 认不到主人,「J7n 的 GND 脚整批跳过」就漏掉两个。
  • 第三遍「并到旁边的同网络焊盘」没有长度上限、也不查走线/钻孔,实测把 C745 pin2 一路并到 9.78 mm 外的 J64 pin3(而且那是另一层的焊盘,画出来根本不通)。 现在 LINK_MAX = 3.0,并且要求目标是通孔焊盘或同层。
  • pcb2_vo_vias.py 的出线层写死 L_TOP。v1 时代器件全在顶层没事;v2 之后 VOn_P 的 35 个焊盘 全在底层,照旧会画出 35 条悬在顶层、一头不搭焊盘的线 —— 而且 fits() 拿这个 lay 去筛障碍, 连净距校核都是照着空荡荡的顶层算的,一条都不会报。

两处和上一轮的计划不一样(都是实算之后改的)

  • U32 pin13 的孔位没有钉在 (94.6, 71.4)。 那个位置过孔放得下(西边走廊 x ∈ [94.59, 94.625]), 但引线出不去:pin13 与 pin14 之间只有 0.20 mm,斜着往西北一定切进 pin14 的净距圈; 平着往西的话过孔又躲不开 USB5_DP 的横段(y 69.75,只差 0.5 < 0.64)。 正解是往东打进 QFN 肚子里 —— 引脚环东沿 95.975 到散热盘西沿 97.600 有 1.625 mm 净宽, 自动搜索把 pin13 放在 (96.50, 70.25)、pin19 放在 (97.25, 71.50),离差分车道都在 2 mm 以上。
  • J7n 的 65 个 GND 脚整批不扇了(SKIP_PAD)。它们是 bm24_pre() 手写死的两条梳子 + pin52 单独出线; 让自动扇出去碰的后果实测是「十个必然失败 + pin 1/2/52 三个『成功』的孔正好落在梳子和车道上」, route_sig 还得写个删除框把它们收回去。

一处对设计文档 §10.3 的有意偏离

§10.3 写的是「功率网络每个焊盘打 3 个过孔」。这一轮 VOn_P 只给 J8n.1(这一路 4 A 的唯一去路)和 C8n1.1(2200 µF 主储能)各 3 个, C8n2C8n6 仍是各 1 个 —— 全按 3 个来会往五块岛里再钻 50 个反焊盘, 最窄断面(下限 7.0)扛不住。这五颗只承担高频纹波,单孔 11.5 A 够用。

上一轮(2026-09-06 第十一轮):v5 差分布线全部落地

详见设计文档 §25.16 补 6。 走法表、顶层参考面、45° 斜段规矩都在那里,这里只记做了什么和踩了什么。

# 做了什么 结果
1 pcb2_dp_long.py 整个重写(v3 存 obsolete/):pair 1 直西、pair 2 正北、pair 3 绕 Hub 北边东行、pair 4 整段顶层、pair 5 绕北边再南下跨 UP、UP 底层直东 本地净距全过
2 SITE_J 按 v5 站内坐标重写:DMJ 底层 45° 斜进、DPJ 翻顶层跳过去(两排之间只有 1.515 mm,孔只能落在 y 36.805~37.31) 五站同构
3 pin5 / pin10 两条 +3V3 车道 + 顶层干线一起画(被 D± 夹住,后面的布线器进不去);C747 → (88.2, 71.5)、C748 → (92.5, 71.2) pcb2_place --push、pcb2_clear 过
4 VO4 岛主体东界 89 → 87(顶层段不许跨岛边);topref 检查 0 处跨界 reload → pour → inner1_verify 五块全 ✅
5 --push:182 线 / 26 孔;DRC 634 = 间距 115(基线)+ 连接性 519 无新增间距错误

⚠️ 这一轮踩到的

  • 0.5 脚距的车道之间打不了孔(孔要离两边车道各 0.645)。出 QFN 的次序只能是「最外侧先拐走,邻居走了才打孔」, pair 4 的两个孔为此上下错 0.9、DP4 先抬 0.6 再打。想改走法先在纸上按这条排一遍。
  • chamfer() 不能对 45° 拐再切一刀:切出来的 22.5° 小段向内挪 0.1,实测把 DP4 抬升段切进 pin10 车道净距圈。已改成转向角 ≤ 46° 不切。
  • 上一版脚本里的 U32 坐标(x 105.55)是 v1 的,U32 早搬到 (100, 68) 了 —— 改布局后所有写死坐标的布线脚本都要重推,别只改一处。
  • 同宽共线的两段下发后被 EDA 合并成一条(182 → 178),但不同宽的共线段(BM24 出线 0.11 / 0.20 / 0.28 三档)保留 —— 换挡点安全。

✅ 这一轮(2026-09-06 第八轮):载板 v4 —— 尾插槽到 J1401 正下方,J7n 到主板下方远处

详见设计文档 §25.16「2026-09-06 补 3」。 用户指令:尾插槽放在主板尾插接口下方;载板尾插座放在主板下方远处、50 mm 排线能到的地方;连接时排线走载板背面。

# 做了什么 结果
1 SLOTS['tail'] → u 4565 / v 1.44.4(J1401 正下方);TAIL_LANE u 44.662.6 / v 4.438.3 不放元件(check() 新增一条) 本地检查过
2 J7n → (53.5, 37.0) rot 0;U5n (49.8, 41.5, rot 180) / R76n (52.8, 39.15) / R77n (55.8, 39.15) 挪到 J7n 北边;J6n (62.5, 41);行 D 西移 4.5 离 H1 4.0 / H3 2.9,pcb2_clear 816 焊盘净距 ≥ 0.20
3 --outline --push --verify --heights 槽 10/10;J71 回读 pin51(VBUS) 西端 (52.47)、pin4/6 北排西端 (54.65/55.0, 121.06)、奇数排南 ✔
4 pcb2_reload → pcb2_pour --push → _refresh_geo → inner1_verify 五块岛各 1 连通域、焊盘全在主域,最窄 7.05 / 7.20 / 7.70 / 7.60 / 20.85(下限 7.0)
5 截图 PCB2-v4-bottom.png / PCB2-v4-ch1.png 自己看过:槽在 J1401 下、J7n 在远端、U5n 在北 —— 还要用户确认

为什么 J7n 从 rot 180 改成 rot 0(不是拍脑袋)

排线只绕 u 轴弯(接口边一个 U 形回折 + 槽里一个台阶),不在面内转 ⇒ J7n 的触点图案是 J1401 图案的平移; J1401 的 pin1/2 端(G1)在 +u ⇒ J7n 的 pin1/2 端要在 −u;封装 pin51 在本地 −X、底层翻层不改 x ⇒ rot 0。 v3b 的 rot 180 把 VBUS 端放到了东边,和 §24.3「VBUS 落到 −X 端」矛盾。翻面次数(回折 1 次、台阶 0 次)算出来公头朝 +z,正好插朝 −z 的 J7n。 剩下唯一没定的是封装奇/偶排分配(§24.3 镜像位),错了只改焊盘编号,D± 换到南排西端。

上一轮(2026-09-06 第七轮):载板 v3 + C8n1 换矮封装(槽位置已被 v4 取代)

详见设计文档 §25.16 末尾「2026-09-06 补」。

# 做了什么 结果
1 槽挪到主板远端边(v 45.5~48.5),手机位内移 2.5,器件全部收进主板下方 本地检查 / 净距 / 镜像回读全过
2 pcb2_pour.py ISLANDS v3:走廊主体 + 主板下方整条带,VO2/VO4 为顶层差分收边 inner1_check / inner1_verify 五块全 ✅(最窄 7.05)
3 C8n1 → Rubycon 6.3TPV2200M10X10.5(C3828985):个人库建器件、c4_site.py 五页重画、PCB 导入 80+80 极性丝印核过(PCB2-v3-c801.png)
4 为差分让位挪 C747/C748、U31 组、U34/C760、R721/722、R717/718 见 pcb2_place.py 注释
5 新工具 pcb2_reload.py(重载客户端解卡)、pcb2_snapshot.py

⚠️ 这一轮踩到的

  • lib_Device.create() 返回 undefined 但器件已建出来;copy() 从系统库复制返回 null 且真没建; 个人库内 copy() 可用;给个人库器件 modify(association.footprint = 系统库封装) 会把封装复制进个人库(新 uuid)。 建完一律 search + get 核对,别看返回值(skill §9y)。
  • 铺铜「重建所有」在大量 API 改动之后必卡(本轮三次),重载后一次就通 ⇒ pcb2_reload.py 然后 --push。
  • pcb2_place.py --push 的 modify() 里 layer 和 x/y/rot 分两次下发(一次下发 rot 偶发不生效)。

上一轮(2026-09-06 第六轮):载板 v2 —— 全部到底层 + 穿线槽(已被 v3 部分取代)

详见设计文档 §25.16。 用户两条指令:正面器件全放背面并在背面重新布局;排线通过板内细长槽穿到载板。 中途补充:器件可以放到主板投影下方(底层),方便布线。

# 做了什么 结果
1 pcb2_snapshot.py:先把 v1 全部铜/元件抓成 _pcb2_snap_topside.json 161 件 / 1586 线 / 416 孔 / 7 铺铜,可回滚
2 实测底层镜像语义(U51/J71/R761 翻到底层读焊盘再翻回来) 本地 y 翻 → 再转 rot;两脚件 pin1 方向不变,SOT23/BM24 南北排对调
3 pcb2_place.py 重写成 v2 顶层 20 / 底层 141;槽 10 条;C8n1 卧倒 keep-out;新增「压槽 / 螺钉头 / 顶层只许顶针」三项检查
4 --clean-copper --outline --mech --push --verify --heights 清 1416 线 + 416 孔 + 7 铺铜(另有 16 条漏网线第二遍才删掉);槽建 10/10;镜像回读全 ✔
5 pcb2_clear.py 跨元件净距全部 ≥ 0.20
6 pcb2-bottom-heights.json 底层 141 件高度图 + 10 条槽,给下板布局避让

⚠️ 这一轮踩到的三条(都写进 skill)

  • 底层元件的焊盘位置不能推:翻层不是绕 y 轴镜像,是本地 y 翻。J23 用顶层那版的 rot 90 放到底层, 口会朝板内;改 rot 270 并把 R717/R718 的 y 对调,--verify 回读核过。
  • 挖槽区域 = pcb_PrimitiveFill.create(12, polygon)(layer 12 = MULTI)。跑道形用两段 90° 弧, 和板框一样逆时针、正角度。
  • 批量 delete() 一遍删不干净:1416 条按 getAll() 删完,回读还剩 16 条(旧 BM24 GND 梳子, 正好压在新槽上)。删除循环要跑到剩余为 0 再收。

上一轮(2026-09-06 第五轮):载板收尾 —— 已被 v2 整体作废,只留作历史

详见设计文档 §25.15。

# 做了什么 结果
1 .claude/pcb2_vo_vias.py:25 个贴片电容各打一个过孔下到 Inner1 的岛 25 线 / 25 孔,全部一段直线,连接性 −31
2 .claude/pcb2_gnd_patch.py:自己算出悬空的 GND 连通域再补孔 5 个域(C749.2/R710.2/U32.19/J23.A1B12/J23.B1A12)→ 6 线 / 5 孔,连接性 −10
3 .claude/pcb2_npth.py:从铺铜反焊盘反解封装自带的非金属化孔 J23 里两个 Ø0.600 @ (213.660, 59.100) / (213.660, 64.900)
4 inner1_verify.py 加了过孔校核 五块岛 5 焊盘 + 5 过孔全在主域,最窄断面 7.20/9.10/10.15/7.35/7.60(下限 7.0)
5 pcb2_gnd_patch.py --check 板上悬空 GND 连通域 0 个
6 DRC 125 = 同封装内 115(与基线一模一样)+ 连接性 10 + 间距 0 + 物理 0

⚠️⚠️ 这一轮最值得记住的:封装自带的孔,API 一个都读不出来

J23 的封装里有两个 Ø0.60 非金属化定位孔,正卡在 A1B12/B1A12 两个 GND 焊盘的外侧角上。 pcb_PrimitivePad.getAll() 里没有、pcb_PrimitiveRegion/pcb_PrimitiveFill 是空的、 getPrimitiveByPrimitiveId('e55') 返回空、getPrimitiveAtPoint() 只回最上面那条线。 只有 DRC(「挖槽区域 到 导线」)和内层铺铜的反焊盘看得见它们。 ⇒ pcb2_npth.py 拟合铺铜环里的圆弧,用「已知过孔的反焊盘半径 − 过孔半径」标定净距 (实测 0.3583,234 个样本,离散度 0.005),再反推孔径。

另外三条

  • 净距校核只看铜,钻孔是另一条规则。 第一版打的孔落在同网 J23_4 的焊盘里, 校核全过,DRC 报「钻孔 到 钻孔 距离 0mil」。同网的通孔焊盘/过孔都要按 DRILL=0.33 再判一遍。
  • 金属化的槽孔焊盘和非金属化孔待遇完全不同:线可以压在焊盘自己的孔上, 不能压在非金属化孔上。混在一起判的话,J23.1/J23.4 这种槽孔焊盘连碰都碰不得 (焊盘上任何一点离自己的槽都不足 0.80 mm)。
  • 只加不删的下发必须记账。 pcb2_gnd_patch 把自己画的坐标存 _pcb2_gnd_patch.json, 下次先删再画。不记账实测多出一对旧坐标的过孔,而且新画的细线被 EasyEDA 并进旧的粗线里。

✅ 这一轮(2026-09-06 第四轮):栅格迷宫布线器,载板剩下的 77 个网络

详见设计文档 §25.14。 事实来源是 .claude/pcb2_route_sig.py(BM24 周围的车道 是手写死的几何,其余交给布线器)。

# 做了什么 结果
1 新写布线器 pcb2_route_sig.py 栅格 0.1/0.05/0.025 三档 + scipy 稀疏图 Dijkstra + 45° 整形;产出过同一份 check()
2 77 个网络 431 条路径 / 991 段导线 / 160 个过孔,净距校核全过
3 BM24 的 65 个 GND 脚(§25.8 的待办) 两条梳子 + 12 个过孔/站,中央通道那 1.64 mm 是这一轮才用上的
4 抓到并修掉 R76n 装反 rot 0 → 180。不改的话五个站的 VBUSn 全布不通
5 +5V 线宽 0.80 → 0.60 §25.10 说了段电流 ≤0.6 A;0.80 在走廊里挤不过去
6 PWR1A 的 strokeWidthMin 0.25 → 0.15 +3V3_HUB 要穿 Hub 卫星圈 0.659 mm 的缝;载流够(420 mA / 0.15 mm 可载 ~0.8 A)
7 布完线重新铺铜 不铺的话 DRC 多出 445 条「铺铜区域到过孔/钻孔」(新过孔没有反焊盘)
8 DRC 166 = 同封装内 115(与 §25.8 基线一模一样)+ 连接性 51 + 间距 0 + 物理 0
9 45° 化(用户拍板) sharp_corners / chamfer45 / smooth_micro 三个工具放在 pcb2_route_dp.py,两个脚本共用。差分对 10 → 0、信号线 72 → 0,DRC 不变

⚠️⚠️ 这一轮最值得记住的一条:expand() 会静默丢掉路径末端的过孔

pcb2_route_dp.expand() 打过孔的条件里有 i + 1 < n - 1 —— 最后一个点被显式跳过。 于是「走到目标正上方再换层落下去」这种路径,过孔没了,两层的铜各画各的、中间断开。

三道校核全都抓不到:净距校核只看撞没撞(断开当然不撞)、 inner1_verify 只看铜面、DRC 的连接性又要 location.reload() 才刷新。

踩到两次:布线器自己的路径断了 5 个网络;手写的 bm24_pre() 断了 50 个 GND 过孔 (DRC 里 GND 55 条,补上之后掉到 10)。 ⇒ _end_via():末点上有换层就补一个重复点,所有路径(含手写的)都要过这一道。

另外三条(都写在 §25.14 里)

  • 障碍要先胀 QERR 再栅格化,否则距离场高估 0.08 mm,净距校核在下发前一刻才炸。
  • location.reload() 会被「离开该页面」确认框挡下来(文档有未保存改动), 点「留在该页面」= 根本没重载,客户端仍卡在后台任务上, 于是 铺铜管理器… 点了没反应。重载前先 save()。
  • 先给所有焊盘留好出线段再开始布线(escape routing 先行), 否则先布的网络会把后布的网络出焊盘那 0.6 mm 占掉。

✅ 这一轮(2026-09-06):载板 Inner1 分割电源面

详见设计文档 §25.11。 事实来源是 .claude/pcb2_pour.py 的 ISLANDS,改铜面改那里再 --push。

# 做了什么 结果
1 五块 VOn_P 岛 + GND 底填落到 Inner1 优先级 L15 VO1<VO2<VO3<VO4<VO5<GND(岛先占地盘)
2 每条边界都按具体那颗排母针的反焊盘卡出来 9 条边界逐条列在 §25.11 的表里;最窄的颈是 VO3 的 7.40 mm(被 J32.27/J32.35 夹住)
3 新脚本 inner1_check.py(下发前预测) 按 ISLANDS 多边形 + 异网焊盘/过孔反焊盘栅格化,五块全过
4 新脚本 inner1_verify.py(下发后实测) 读回 EDA 真吐出来的铜再栅格化:五块各 1 个连通域、5/5 本网通孔焊盘在主域,最窄断面 7.20/7.60/7.70/11.70/12.90 mm
5 新脚本 inner1_diffref.py(§10.1) 顶层 5 段 *J 差分下方都只有 GND-INNER1 一块铜、覆盖 100% —— 这条约束从「可以接受」升级成「已实测满足」
6 查出铺铜默认是十字花连接 铜面把自己的焊盘挖空只留 4 条 0.254 mm 辐条 ⇒ 校核量到「0.3 mm」。给 VOn_P 挂 PLANE-DIRECT(connectMode:1)后铜面是一整块。见下
7 pcb2_rules.py 收编该规则 新增 PLANE_RULE / PLANE_NETS,--push 建条目 + 指派,--check 十三项全过
8 新脚本 shot_pcb_layers.py 只显示指定铜层截图(可带区域),截完自动恢复图层可见性
9 DRC 499 = 同封装内间距 115(与基线一模一样)+ 连接性 384 + 间距错误 0。连接性从 396 掉到 384,正是五块岛接上的那 12 处

截图 PCB2-inner1.png(整板)/ PCB2-inner1-left.png / PCB2-inner1-right.png(走廊两半, 能看清五块岛的轮廓和两条颈从排母针缝里穿下去/上来)。

⚠️ 这一轮最值得记住的一条:铺铜的「段数」不能当连通性判据

getState_PourFills() 把一块铜面吐成十几个 fill,那多半是切块(相邻块共边)不是断开。 「17 段」既可能是一整块被切碎、也可能真碎成 17 座孤岛,段数分辨不出来。 唯一靠得住的是把所有环画进同一张栅格再做连通域 —— 这就是 inner1_verify.py。 而且十字花连接时那十几个 fill 里有一部分是 fill:false 的描边辐条(不是填充区), 只画填充区会得到假的断路。两个坑叠在一起,skill §9j-2 有完整实测表。

⚠️ 另一条:优先级的步长是 2,不是 1

同层建 6 块铜面,回读的优先级是 1,3,5,7,9,11(同层只有一块时是 1)。 所以脚本里只能断言相对次序,不能断言「第 k 块 == k」—— pcb2_pour.py 第一版就是后者,白报了一次「优先级和计划对不上」。已改成断言次序。

✅ 这一轮(2026-09-05 夜):换件实施全部落地

详见设计文档 §22.3f 末尾的实施清单 + 新的 §25.9。 上一轮那三条硬约束消掉了两条。

# 做了什么 结果
1 实测两颗新符号的引脚几何 公母完全一样:第 r 行 = pin 2r−1(左, cx−30)/ 2r(右, cx+30),第 1 行 y = cy+95
2 p5_btb.py 换件 器件换 C55020601;引脚公式 r/41−r → 2r−1/2r;ROW0 100 → 95;补上「坐标必须是 5 的倍数」断言
3 c1_conn.py 换件 器件换 C5307344;引脚公式和 ROW0(95) 都不用动
4 P5-BTB / C1-CONN 重生成 verify_sch 两页 PASS(C1-CONN 要带 --nc c1-nets-nc.json,U34.3 是有意悬空)· xpage 0 漏接 · port_audit 下板 513 / 载板 330 可疑 0
5 §22.2 逐针表重写 两块板共用一张表(VO1_P 两边都是 1/3/5 脚),旧的「两套编号」警告整节作废
6 载板 PCB 从原理图导入变更 只有 14 条:C1-CONN 那 7 个元件各一条「移除+增加」(整页重画 ⇒ UUID 全新)。「包含设计规则」没勾,11 条规则原样保留
7 五个 Ø10 电解整体上移 2.5 mm BAND['c1'] 的 v:−11.0 → −8.0。见下,这是换件逼出来的
8 载板重摆 / 重扇 / 重铺 161 元件本地四项过 · pcb2_clear.py 816 焊盘过 · GND 扇出 210 孔(和换件前一样)· Inner2 重铺 213 段
9 DRC 511 = 同封装内间距 115(和基线一模一样)+ 连接性 396(没布线)+ 其它 0

截图 PCB2-carrier-v4.png / P5-BTB.png / C1-CONN.png。

⚠️ 换成通孔件逼出来的连锁改动:五个大电解必须上移

直插排母 = 顶层也占位,而 J31/J32 的焊盘区高 4.24 mm。 原来的走廊里,上排三站的罐底 y=68.25、下排两站的罐顶 y=66.95,中间只剩 1.30 mm —— 怎么摆都塞不进去(贴片排母时代它只在底层,所以从来没露出来过)。

罐体在服务带里 u 5.75~18.25 这一段是独占的(行 1/2/3 的件都从 u=21 起), 所以 v 可以一直收到「不进手机投影」为止(v ≤ −6.25)。取 v = −8.0(和行 2 齐平):

罐体边 → 手机板边 3.00 mm(keep-out 边 1.75)
keep-out 边 → J31/J32 焊盘 1.63(上排)/ 1.43(下排)mm
实测可行区间 v ∈ [−9.0, −6.75] 本地四项全过,取中间偏保守

这一轮踩到的三个坑(都已写死在脚本里)

  1. pcb2-parts.json 不能在元件摆好之后随便重抓。 焊盘的 x/y 跟着元件转, 但 pad 数组里的 w/h 不转(方向记在 pad.rotation 上)。 摆好之后直接抓会得到「位置转了、尺寸没转」的四不像包围盒, pcb2_place.box() 再按 rot 翻一次 ⇒ 全错:实测凭空报出 12 条假问题 (手排的 0402 互相重叠、J23 越框)。 ⇒ 新脚本 .claude/pcb2_parts_dump.py 把焊盘位置反转回元件本地系再存, 和「刚导入、全是 rot=0」时抓的那一份等价。
  2. pcb_fanout.clean() 在载板上是破坏性的。 它删的是「layer !== 11 的所有导线」—— 下板除板框外没有别的线所以没事,载板上却有 丝印 50 + 装配 100 + 文档 20 = 170 条 会被一起删光,而且 DRC 和几何检查一条都不报。 ⇒ pcb2_fanout.py 里另写了 clean2(),只删过孔和信号层(1/2/15/16) 的导线, 并回读断言 3/9/13 三层还在。 ⚠️ 板框是 layer 11 上的 pcb_PrimitivePolyline,不是 Line —— 「剩余导线里有没有 layer 11」不能当板框还在的证据(第一版断言就是这么写错的)。
  3. 通孔焊盘不需要扇出:pcb_fanout.one_net() 的 todo 本来就带 not p['hole']。 所以 SKIP_HDR 直接关掉即可,J31/J32 那 80 个脚自己就是过孔, §25.8 里「33 个 GND 脚扇不出去」这条自动消失。

⛔ 还剩的一条硬约束(另两条已解决)

1. C2~C6 压在 J24 位置上 / 3. 两板之间没有立柱位 —— 这两条是下板的事, 留到下板重排接口区时一起处理(下面「下一步」第 1 件)。

C2/C3 重选时罐高必须 ≤ 11.5 mm。现在的 C22383480 商城一个参数都没标, §22.3d 那个「约 12 mm」是估的。⚠️ 别换成聚合物(低 ESR 会让输入滤波欠阻尼,同 §6.2 的理)。

✅ 追加一轮(2026-09-05 夜):排母逐针分配按几何重排

铺 Inner1 之前摸底,摸出 §22.2 那张「定稿」表和载板几何对不上,用户拍板改(方案 A)。 全过程见设计文档 §25.10,新的逐针表在 §22.2(事实来源仍是 .claude/p5_btb.py)。

硬冲突:排母南排前 16 位全是 GND 焊盘(Ø1.70 / 间距 2.54 ⇒ 缝里只剩 0.44 mm 铜网), 而 4 A 按 IPC 内层 0.5 oz 要 6.97 mm ⇒ 4 A 的电源面穿不过排母的地针行。 09-04 那版偏偏把 VO4_P/VO5_P 放在 J32 的北排,它们的站却在排母南边。

新规矩:北排喂北边的站,南排喂南边的站,一路都不许跨行。 J31 正对 site1(北)+site4(南),J32 正对 site3(北)+site5(南),site2 取 J31 北排最右三针。

09-04 09-05
五路 VOn_P 最远曼哈顿 ~120 mm(VO4 要绕排母端头钻手机 2 底下) ≤42 mm,全部同侧
+5V 3 针,Inner1 上要一条 150 mm 的横贯脊 10 针分散取电,段电流 ≤0.6 A ⇒ 顶层走线即可,不占内层
GND 33 针 26 针(0.77 A/针)
Inner1 规划 5 岛 + +5V + +3V3_HUB + 底填 5 块 VOn_P + GND 底填

网络表一个都没变,只是落点变了。五路的「正对面 3 针 GND」(§15.4)一条不少, p5_btb.py 的 plan() 逐行断言。

原理图画法改了一处:GND 也走 netport(不再画地母线)—— 重排后 GND 散成若干段,每段配母线 + 地旗会让旗掉进相邻行的端口里(行距 10 / 旗高 15~20)。

验证:verify_sch 两页 PASS · xpage 0 漏接 · port_audit 下板 544 / 载板 361 可疑 0 · 载板导入变更 65 条全是 J31/J32 的「修改网络」(无元件增删)· PCB 上 80 个焊盘的网络和逐针表逐行比对一致 · 重扇 210 孔 + Inner2 重铺 185 段 · DRC 518 = 同封装内 115(与基线一致)+ 连接性 403 + 其它 0。

✅ 这一轮(2026-09-06 第二轮):布线开工,连接器侧 5 对差分完成

详见设计文档 §25.12。 事实来源 .claude/pcb2_route_dp.py(SITE_J 是路径,同文件里带本地校核)。

# 做了什么 结果
1 抓到并改掉一个摆位错误:U5n 装反 USBLC6-2SC6 是直通器件,朝连接器那一排原来背对着 J7n。BAND['u5'] rot 0→180。SOT-23-6 对称,6 个焊盘落在完全相同的位置,只是引脚编号对调 ⇒ 无新间距冲突
2 跑完整的返工流程 重摆 161 元件 → pcb2_clear 816 焊盘过 → GND 扇出 clean/dump/push 210 孔 → 重铺铜 → inner1_verify + inner1_diffref 全过,各项数字与改之前完全一致
3 判定自动布线器在载板上不可用 配置逐条回读断言(所选网络 22 个 / 移除已有 / 只勾顶底层)之后跑:只画出 J23 自己那两条 A6↔B6/A7↔B7,全板 0 过孔,30 秒"跑完"。pcb2_route_diff.py 连同 --rollback 留着,别再试
4 新工具 pcb2_route_dp.py 路径按站内坐标写一遍映射到五站;本地校核线-焊盘/线-过孔/线-线/过孔-×4,一般 0.20、对内 0.15;--corridors 扫底层横向走廊
5 5 对 USBn_DPJ/DMJ 布完 DP 全程顶层 5.05 mm;DM 两端次序相反必须交叉,两个过孔沉底层穿过去,9.19 mm,偏斜 4.14(容差 5)。60 段导线 + 10 过孔
6 出线宽度按几何算死 BM24 焊盘阵列里 0.11 mm(上限算出来是 0.1197,取 0.12 会差 0.00015 卡线);出阵列后两线还隔 0.35 时 0.20;分开后 0.28。DIFF['strokeWidthMin'] 随之降到 0.10
7 DRC 479 = 同封装内 115(与基线一致)+ 连接性 364 + 间距错误 0

截图 PCB2-dp-site1.png(能看清 DPJ 走顶层、DMJ 沉到底层从它底下穿过去)。

⚠️ 加完线和过孔一定要重铺铜再看 DRC

不重铺时刷出 40 条间距错误(「到过孔」20 +「到钻孔」20)—— 那是铺铜还不知道新过孔、没让开它们。重铺之后 GND-INNER1 环数 217→227、 INNER2 330→340,正好是 10 个新过孔的反焊盘,40 条全消失。

✅ 这一轮(2026-09-06 第三轮):6 对长差分布完,载板差分对收工

详见设计文档 §25.13。 事实来源 .claude/pcb2_dp_long.py(坐标 + 逐条理由)。

决定全局走法的是 x 112~116 那道隘口:Hub 北边的 C751(1206,底层) 加 C815 的 GND 扇出孔,把北高速掐成「底层低窗 0.535 mm(1 对)」+「底层高窗」+ 「顶层窗口 0.58 mm(1 对,C751 在底层所以顶层是通的)」; 南边被 Hub 的 A 圈 0402 封死(只剩 y≈56 一条缝)。 三对东行的只能一对一个窗口,而南北次序被 U32 的引脚次序锁死 —— 正好对得上,不用额外交叉。 pair4 是全板唯一要横穿其它对的一对,一出焊盘就上顶层南下。

新脚本 / 新用法:

/c/Python314/python .claude/pcb2_net_conn.py            # 按实际铜算连通域(不看 DRC 面板)
/c/Python314/python .claude/inner1_diffref.py --routes  # §10.1 沿实际走线逐点查参考面
/c/Python314/python .claude/_refresh_geo.py             # 刷 inner1_* 三个脚本的输入缓存

⚠️ 三个这一轮踩到的坑

  1. drc_read.py 不加 --pcb2 跑的是下板。 第一次读到 649 条全是下板的网络。
  2. DRC 的连接性结果在程序化建线后是陈旧的,location.reload() 才刷新。 刚下发报 403(每个网络还报 2~7 条"断开了连接"),重载后是 338 —— 比布线前少 26,正合预期。 间距错误那一支倒是即时的。
  3. 45° 切角会把线甩进旁边焊盘的净距圈。 chamfer() 的 r 从 0.6 降到 0.35, 焊盘阵列附近还要多插一个共线点把切角压到 0.15~0.2。

下一步

# 做什么 备注
1 收尾 Hub 卫星圈那三段(+3V3_HUB 2 段 / VBUS_UP 1) 要用户拍板:方案 ① R711/R716/R720 从半径 8.0 挪到 9.5(这三颗的 GND 扇出要重做;挪完走 §25.12 那套返工流程:pcb2_place --push → pcb2_route_dp --dump → pcb2_route_sig --push → pcb2_gnd_patch --push → pcb2_pour --push → reload → DRC)。SCL/POGO5_VDN 已通,U34 已挪。见 §25.15 末尾
2 下板 PCB 导入变更 + 接口区重排 删了 15 个连接器 + 53 个 7xx 器件,新增 J24J30 / U33。J24 中心 (32, 62.5)、本体 53.34×5.08 会压到 C2C6,必须挪;C2/C3 顺便重选(罐高 ≤11.5)。J24/J25 的 40 个焊盘按 pcb2-header-pads.json 减 (30,5) 摆,别按引脚号推理
3 让用户截图确认载板布线 已截 PCB2-vo-vias-site1.png(VO1 站的电容打孔)/ PCB2-gnd-j23.png(J23 那两个屏蔽脚)/ PCB2-u34-j31.png(U34 新位置)。⚠️ EasyEDA 窗口必须在前台,在后台时 document.hidden = true、画布不渲染,getCurrentRenderedAreaImage() 只回 88 字节的空 PNG

⚠️ 2026-09-05 从实物照片新看出来的两条(都还没处理)

1. 原厂排线长约 85~90 mm,只给了 3 mm 水平距离 → ✅ 用户 2026-09-05 拍板:不管,装配时折叠。 照片上排线从主板下边缘一路走到尾插小板(板上印 MAIN + 箭头), 按主板宽 67.05 mm 标定,两个公头相距约 87 mm,而载板的 J7n 在 v = −3.0、 离手机板边只有 3 mm ⇒ 有 80 mm 以上的余量。 用户原话「排线长度不用管,我到时折叠一下」 —— 所以 J7n 不为此挪位, 也不为此改 §13.5 的「台阶式下折」。留一条给装配:折叠的那一坨会占手机上方的高度, 定压板高度(h_up)时要把它算进去。

2. J7n 并没有像注释说的那样「正对 J1401」,横向差 3.9 mm。 mi8_contacts.json(卡尺校核过)记的 J1401 中心是 u = 53.58(长轴沿 u,占 47.4659.71), 而 pcb2_place.BAND['j7'] 是 u = 57.5。 不致命(软板本来就有余量、而且照片上原厂排线自己就是歪的), 但不能直接改成 53.58 —— 那会和同一行的 j6(EDL 座,占 u 39.848.2)撞上 0.7 mm, 要连着 j6 / c3 一起重排。和上面第 1 条一起决定。

✅ BM24 的镜像位 2026-09-05 已实测(§24.3):结论是「不用改」。 用户量出「G1 在右端 / 靠板边那一排是奇数排」,照片确认排线从板边直出, 所以载板 +Y 排(朝手机那一排)= 奇数排 ⇒ ODD_ROW_TOP = True 是对的; 顺带第二位(贴片方向)也自洽:VBUS 端落在 −X = 手机左端,和「52=GND / 51=VBUS」对上。 打样前已经没有会导致重打板的未知量了。

⚠️ 但同一批照片带出两条新的机械问题,见下一节。


上一轮:排母选型(2026-09-05,用户授权「你来定选型」)—— 已实施

详见设计文档 §22.3f。 两个占位到此全部消灭。

型号 立创 关键参数
下板 J24/J25 公头 AZ-PZ2.54-V11-2*20P(宝讯) C55020601 塑高 2.5、配合针长 6、端接针长 3;3 A/针、镀金、直插
载板 J31/J32 母座 PC104-2*20(洲日) C5307344 塑高 10.5、行距 2.54、2.5 A/针、直插

对插 = 2.5 + 10.5 = 13.0 mm(塑料对塑料定位),满足 §22.3d 的 ≥13 mm。 把库里「2.54 间距 + 标了塑高」的排母全扫了一遍,10.5 mm 这一档只有 PC/104 一族, 其余全是 8.3~8.5(对插只有 11 mm,够不到),而 2.54 双排排针的塑高全是 2.5,没法从公头补。

改成插装是比高度更重要的收获:80 个通孔脚本身就是过孔, 直接连 Inner1/Inner2 —— §25.8 里被迫跳过的「J31/J32 那 33 个 GND 脚扇不出去」自动消失。

(这一节原来的「三条硬约束」与「编号体系已实测」已经实施完,结论搬到文首「这一轮」和设计文档 §22.3f / §25.9。)

开工前必读(按顺序)

  1. 五路直供电源板设计.md §22.7 —— 两个 Board 各有哪些页、位号怎么分、验证结果。
  2. §22.2(J24/J25 逐针表 + J31/J32 编号对照)、§22.4 末尾(EDL 驱动与载板开关)、 §22.6(风扇 + TACH 分压)、§13.5(USB 最终走方案 D:原厂主板-尾插排线 + 载板上同型号 BM24 座;那里也留着已作废的方案 A / C 记录,和「买一块尾插小板当量具」的测法)。
  3. .claude/skills/easyeda-sch-geometry —— §9j-2 是最新一轮(Inner1)加的: 铺铜的焊盘连接方式 connectMode 藏在 DRC 规则里,默认十字花会让几何校核报假断路; §9j 第 5 条补了「优先级步长是 2 不是 1」。 §9m / §9n / §9o 是换件那一轮加的: 包围盒不能在元件摆好之后重抓 / 通用 clean() 会删掉丝印 / 「从原理图导入变更」怎么读全。 §9j~§9l 是铺铜那三条,§6b / §6c / §6d 是「几何量必须读回、不能推」的血泪条目。

上一轮(2026-09-05):布线前置三件 + 一个必须记住的事实

全部见设计文档 §25.7。 这一轮一根走线都没画(用户拍板停在排母上,见上)。

# 做了什么 结果
1 载板设计规则整套下发 .claude/pcb2_rules.py --check 十一项全过
2 11 对差分对(不是 7 对) 5 长段 + 5 短段 *J + UPSTREAM
3 Inner2 整板地平面 一块 GND-INNER2,重建后 81 块填充,已存盘(.claude/pcb2_pour.py)
4 两个中跨 M3 (76.6, 80.1) / (148.1, 80.1) §25.6 ⑤ 那条待办关闭;pcb2_clear.py 816 焊盘全过
5 查清排母占位件是 SMD、排距 4.80 成了眼下的路障,见上
6 解决了「铺铜优先级设不上」 优先级 = 同层内的创建顺序,先建的赢;对照实验见 skill §9j
7 GND 扇出 .claude/pcb2_fanout.py 210 个过孔落盘(全 GND,Ø0.610/0.305),出线 105 顶 + 21 底
8 查出 BM24 的一个硬约束 五颗 J7n 的 65 个 GND 脚里 51 个打不下过孔(0.35 间距),见 §25.8
9 DRC 基线 全部 551 = 同封装内间距 115(封装自带)+ 连接性 436(没布线)+ 间距错误 0

⚠️ 看 DRC 只看「间距错误」那一支

「同封装内间距错误」那 115 条是 J71~J75(0.170 mm) / U32(0.125) / J23(0.200) 封装自带的引脚间距,安全间距规则 0.2 mm 比这几颗连接器自己的脚距还严,必然报, 三个数都在嘉立创四层工艺下限 0.1 mm 之上、可以打。 别去调低全局安全间距来「修」它们,那是把全板保护一起放掉。成因逐条见 §25.8 末尾。

为什么差分对是 11 对:载板上每一路 D± 被 ESD 器件 U5n 切成两段 —— 长段 USBn_DP/DM(U32 → U5n,走底层)+ 短段 USBn_DPJ/DMJ(U5n → J7n,约 5 mm,走顶层)。 两段都要建对,否则短段会按普通信号布、在 ESD 那头留一截不受控阻抗。

中跨孔为什么放在手机之间的缝下(y≈80)而不是走廊正中(y≈67.5): ① 要支撑的是 y 85.5~130 那一排手机,80.1 离它 5 mm、67.5 差 18 mm; ② 走廊正中被 C811/C831 两个 Ø12.5 电解罐夹着,只剩 6.9 mm 高的缝, 净间距 2.90 mm;缝下那两处是 5.28 mm。 ⚠️ 再往上会被顶针 J48/J52(y 87.96)逼住,y=83 掉到 2.41、y=84 只剩 1.41。

这一轮踩到的四条 EasyEDA 坑(skill §9j~§9l)

  1. 铺铜优先级是「数字小的先铺、占地盘」 —— 对话框底部自己写着。 下板 pcb_pour.py 里 GND=1 / 电源岛=5 是反的(那边铜面从不重叠所以没出事), 抄到载板之前必须翻过来(Inner1 是分割电源面 + GND 底填,全靠优先级互相裁)。
  2. create() 的 pourPriority 参数不生效 —— 优先级 = 同层内的创建顺序。 ⚠️ modify() 补设不生效还会把客户端弄卡死。 ✅ 对照实验已做,规则确认:先建的赢。电源岛先建、GND 底填最后建。 pcb2_pour.py 的 plan() 顺序就是优先级,--push 会回读断言。
  3. 「重建所有」点了没反应 = 客户端卡在后台任务上(窗口底部一条 0% 进度条), location.reload() 后一次就通。⚠️ 重载会丢掉重载前建的铺铜边框,即使 save 过 —— 所以「建边框 → 重建 → 确认 → save」必须一气呵成。
  4. 每个下发脚本必须自己 openDocument:这一轮因为上一条命令开了 PCB1, 把下板的 14 块铜面重铺并存了盘(无损失,但两边返回值都看着正常)。

另记一条正常行为:preserveSilos=false 时区域内没有本网焊盘/过孔,整块会被当孤岛删光 (边框还在、填充是 0)。所以扇出必须排在铺铜之前。

上上轮(2026-09-05 凌晨):载板切 4 层 + 布线前四处返工

全部见设计文档 §25.6。 这一轮没加新东西,是把上一版摆位里四处会咬人的地方修掉 —— 四处全部是「原理图侧看不出来、包围盒检查也过」的那一类。

# 改了什么 为什么
1 载板 2 层 → 4 层 11 对 USB HS 要 90 Ω,两层板 1.5 mm 介质做不出来;20 A 没有内层电源面;两面都是元件面
2 J23 左口袋 → 右口袋 §25.2 当初只核了下游。上游 J23(x5)→Hub(x110) 曼哈顿 140 mm,手册上限 152。挪完上游 104 / 最远下游 113
3 U31 (25,42) → (105,71) §22.7 写了「就近 Hub 供电是 PCB 摆位约束」,上一版没落实,离 90 mm。现在离 U32.pin64 8.62 mm
4 Hub 26 件网格粗排 → 逐脚对位手排 见下
5 M3 孔 (8,127)/(212,127) 挪走 压在 CH1/CH3 手机本体投影里,其中一个和定位孔 P1H2 重叠 0.93 mm

⚠️ 载板的层角色和下板是镜像的:载板 Inner1 = 电源面 / Inner2 = 完整地平面, 因为 U32 在底层,所有长距离 D± 都从底层出发,底层的参考面是 Inner2。 反过来的话底层 D± 就以分割的电源面为参考。嘉立创四层 1.6 mm 叠层上下对称, 所以 W0.28 / S0.18 照搬,不用重算。

Hub 手排真正多做的事是让出三条通道(网格排法做不到):西侧扇出(10 根下游 D±)、 南侧竖井(XTAL + UP_D±,宽 2.16 mm)、晶振窝。实测对位距离: RBIAS 1.43 / VDD18PLL 1.43 / 各 +3V3 1.462.30 / 配置电阻 1.43.2 / 晶振 4.955.76(是通道的代价,不是疏忽,负载电容贴端子 1.51.8 已做到)。

新写的检查:.claude/pcb2_clear.py(焊盘级净间距)

/c/Python314/python .claude/pcb2_clear.py

按焊盘真实形状两两算净间距,分「同封装内」(只报负值)/「跨元件」(判据 0.2 mm)。 M3 孔那个错误只有它抓得到 —— 自由焊盘不属于任何元件, pcb2_place.py 的包围盒检查根本不看它们(成因写进 skill §9i)。

写它踩到两个 API 陷阱,都写进 skill 了:

  • §9g 焊盘归属只能靠 primitiveId 前缀 —— getState_ParentComponentPrimitiveId 在 getAll() 回来的对象上不存在,try/catch 包住会静默地把所有焊盘当自由焊盘。
  • §9h ⚠️ 焊盘的 rotation 是弧度、元件的是角度。按角度判会把所有旋转过的 矩形焊盘长短边认反 —— J23 的 12 个信号焊盘被误报成两两重叠 0.3 mm(真实 +0.2)。

本轮验证结果(全过)

pcb2_place.py 本地四项 + M3 孔断言      ✔ 161 元件(顶 133 / 底 28)
pcb2_clear.py 跨元件净间距 >= 0.2 mm    ✔ 814 个焊盘
pcb2_clear.py 同封装内负间距            ✔ 0 对
铜层数                                  ✔ 2 -> 4(id 1/15/16/2)

截图 PCB2-carrier-v2.png。

上一轮(2026-09-04 晚)做完的两件

  1. ✅ 顶针占位型号换掉了:C9900001338 → YTC1P-2510-01(立创 C5221288), 配 3.0 mm 立柱(原来文档写的 2.0 mm 买不到,见 §22.3e 的整族对照表)。 c4_site.py 已改,C4-SITE1~5 五页重生成,六项验证 + 跨页对账 + port_audit 全过。
  2. ✅ C1-CONN 抓到并修好一处真断线:GND 旗写在 y=362,旗被吸附到 360、导线端点留在 362, 旗压在导线中段 → J31 pin1 的地是断的。verify_sch / DRC / 跨页对账全过, 只有 port_audit.py 抓得到。已在 c1_conn.py / c4_site.py 加「坐标必须是 5 的倍数」断言, 成因写回 skill §8a-2b。⚠️ 上一轮记的「可疑 0」不能当数,每轮都要重跑。

上一轮(2026-09-04 深夜):USB 定为方案 D,载板改造已完成

方案 D(设计文档 §13.5):手机 J1401 ──原厂「主板→尾插」排线── 载板上一颗同型号 BM24-50DS 座 ──> 载板上的 USB 电路。 用户拍板:天线自己另买插主板上,麦克风 / 震动马达不要,「只要能开机正常运行系统就行」。

载板改造做完了,三道验证全绿(verify_sch 每页 PASS / xpage 0 漏接 / port_audit 载板 330 个可疑 0、下板 513 个可疑 0):

页 改了什么 脚本
C5-J1401A / B(新) 五颗 J71~J75 BM24-50DS 座(C424561),A 放 CH13、B 放 CH45 .claude/c5_j1401.py
C5-AUX(新) 每路 U5n USBLC6 ESD + R76n 56k(CC1→VBUS) + R77n 100k(口温→GND) .claude/c5_aux.py
C3-USB(改) 删掉 J17~J21 五个 1.25mm 出线座;D± 不再经过这一页,只留 VBUS 负载开关 .claude/c3_usb.py

USB 链路现在是:C2-HUB 的 U32 → C5-AUX 的 U5n → C5-J1401 的 J7n → 原厂排线 → 手机 J1401。 NC 清单 .claude/c5-nets-nc.json(每颗座子 34 个悬空脚,含必须悬空的 CC2)。 截图:C3-USB.png / C5-J1401A.png / C5-AUX.png,都看过。

调试串口(pin24 TX / pin29 RX)故意没引:1.8V 电平接 ESP32 要加电平转换, 且 pin29 夹在两个地脚中间会把排版挤垮。要用是一次改板。

上一轮(2026-09-04 深夜):Board3 画完又作废 —— USB 方案改回 A(后又改成 D)

先按方案 C 画完了 Board3(J1401 转接小板),当晚用户确认「用排线连接」之后 改回方案 A:装原厂尾插小板 + 一根改端子的 USB-A 转 C 线。 Board3 / phonerail-usbtap / PCB3-USBTAP 已从工程删除, 生成脚本挪到 .claude/obsolete/t1_usbtap.py。理由对照表见设计文档 §13.5。

方案 A 当时是最优的,但用户随后提出「直接用原厂排线连主板和载板」—— 那就是方案 D,比 A 更紧凑、电路全在载板上好改。方案 A 已作废, 它唯一的优势(保留天线馈点)被「用户自己另买天线」抵消了。

作废但仍有效的四条结论留在 §23:立创 BM24 封装编号是错的(同时佐证了 §13.4 的引脚表)、 BM24 只有 10 次插拔寿命、Rp/Rd 分压核算、以及两条 EasyEDA API 经验(skill §8b / §8c)。

上一轮(2026-09-04 深夜):BM24-50DS 封装自建完成

下一步四件里的第 2 件做完了。 器件在个人库:BM24-50DS/2-0.35V(51) HRS-PIN (771dbf002b714bb79cde47cf86aa0fb1,封装 940f093c1578426aa81e91ebf0bfbd76), c5_j1401.py 已改用它,C5-J1401A/B 重生成,三道验证全绿 (verify_sch 两页 PASS / xpage 0 漏接 / port_audit 载板 330 个可疑 0)。 生成脚本 .claude/fp_bm24.py,封装截图 FP-BM24.png。逐项尺寸与来源写在设计文档 §24。

几何不是外推、也不是抄立创:从 Hirose 官方样本(D47680_en)的 PDF 矢量图里 逐段反算的,50 芯的 A/B/C 表直接可用。信号焊盘 0.18 × 0.48 @ 0.35 间距、两排 y = ±1.06、 电源焊盘 0.49 × 0.76 @ x = ±6.03。

⚠️⚠️ 但是多出一件必须做的事:镜像位实测(§24.3)

原来文档里写「两种候选正好差一个 180° 旋转,转贴片方向就能切换」——这句是错的, 候选有 4 个:2(1 脚在哪一端)× 2(奇数脚在哪一排)。 前一位靠旋转能纠,后一位是镜像,错了要重打载板(D+ 会落到 45 脚 SPKR_OUT_P)。

BM24 的 DS 座是完全对称件(Hirose 样本注 2:no polarity,本体两端与上下两排全一样), 所以「哪一排是奇数排」查图纸查不到 —— 它是小米8 那块板的布局事实,只能量。

量法(两个探点,零成本零风险,打样前必做):

  1. 两个电源触点(焊盘大好戳)分别对主板地量电阻,~0 Ω 的是 G1 = 1/2 脚那一端。
  2. 从 G1 端数,两排各自的第 2 个信号触点对地量:通的那排是奇数排(pin3 = GND; 偶数排第 2 个是 pin4 = VREG_L19A_3P0,不通)。

结果若是「奇数排在 −Y」,把 fp_bm24.py 的 ODD_ROW_TOP 改成 False 重跑一遍即可。

上一轮(2026-09-04 深夜):载板 PCB 从零布起

板框 220 × 135(R5 圆角),五块主板 3+2 双排面对面、第二排居中(用户拍板)。 原理图 161 个元件全部导入并摆好,本地四项检查全过(位号齐全 / 不越框 / 分层重叠 / 顶层不压主板投影)。 逐项见设计文档 §25,脚本 .claude/pcb2_place.py,截图 PCB2-carrier-v1.png(全板)/ PCB2-site1.png(CH1 位)/ PCB2-j23.png。

已落盘的东西:板框 1 条闭合多段线、载板自己 4 个 M3、35 个手机定位孔(NPTH Φ2.5)、 5 个手机位丝印外框 + 标签、161 个元件(顶层 133 / 底层 28)。

⚠️ 抓到一个会让侧键永久短路的封装错误(§25.3)

立创给顶针 C5221288 的封装焊盘是 Ø2.5,而小米8 四个侧键触点最近的一对 (J601↔J604)只隔 2.217 mm —— 两个焊盘直接叠在一起,音量+ 会被永久按住。 顶针法兰其实只有 Ø2.0,是立创把焊盘开大了。原理图侧完全看不出来。

已自建 POGO-SMD1P_D1.9(焊盘 Ø1.9,净间距 0.317 mm),c4_site.py 改用新器件, C4-SITE1~5 重生成、三道验证全绿、PCB 重新导入。

三条布局上的硬决定

  1. J31/J32 排母在底层(下板公头朝上,载板母座必须朝下)。 「第 r 行对第 r 行」不靠脑补:--push 会把载板侧 80 个焊盘坐标导出到 .claude/pcb2-header-pads.json,下板照数值摆即可(下板目标 = 导出值 − (30,5))。
  2. USB Hub 也在底层、压在通道正中 (110,67) —— 放左右口袋的话最远那台 D± 要走 200+ mm,超 USB2517I 手册的 152 mm 上限;放中间后最长 92 mm。 ⚠️ 布下板时别让 C2/C3(12 mm 电解)撞上载板底面的元件。
  3. 35 个定位孔做 NPTH,立柱必须用尼龙 —— 金属化 + 金属立柱 = 五台手机的地 在载板上并成一根,4 A 回流会绕开专用 P− 针,违反 §6.4。

载板这一版还欠的(详见 §25.5)

✅ J23 已定稿(rot=270、x=5.134,壳体前端面与板边齐平)· ⬜ Hub 那 26 个件是网格粗排、布线前要手调 · ⬜ 设计规则 / 铺铜 / 布线一根没做 · ⬜ 主板 7 个定位孔的孔径没量

上一轮写的「下一步四件」(第 1/2/5 件已完成,剩下的搬到文首)

# 做什么 备注
1 ✅ 排母选型 + 换件实施全部完成(2026-09-05 夜,见文首)
2 ✅ 自建 BM24-50DS 封装 + 镜像位实测(§24.3)—— 全部完成,结论是当前的 ODD_ROW_TOP = True 就对
3 下板 PCB 导入变更 删了 15 个连接器 + 53 个 7xx 器件,新增 J24~J30 / U33,接口区要重排,最后统一重铺铜。⚠️ 下板的铜面 2026-09-05 被误重铺过一次(无损失,本来就要重做)
4 🟡 载板 PCB —— 摆位 + 规则 + 差分对 + Inner2 地平面 + 6 个 M3 + GND 扇出 + 换件重摆/重扇/重铺 + Inner1 分割电源面都已完成。⬜ 只剩 布线
5 ✅ 载板上半部分的安装点 —— 已加两个中跨 M3 (76.6, 80.1) / (148.1, 80.1),§25.7 ③

改完照例:verify_sch.py(当前页)+ 跨页对账 + port_audit.py (载板加 --board Board2),三个都干净了再让用户看截图。 动 PCB 的话再加一条 pcb2_clear.py(焊盘级净间距,自由焊盘只有它管)。

载板布线的下一步,按顺序

  1. ✅ 换设计规则 —— .claude/pcb2_rules.py --push,--check 十一项全过(§25.7 ①)。
  2. ✅ 建差分对 —— 11 对(5 长 + 5 短 *J + UPSTREAM),短段也建了对。
  3. ✅ Inner2 整板 GND —— 一块 GND-INNER2,81 块填充,已存盘(.claude/pcb2_pour.py --push)。
  4. ✅ GND 扇出 —— .claude/pcb2_fanout.py,换件后已 clean → dump → push 重来一遍, 210 个过孔。J31/J32 那 33 个 GND 脚不再需要扇出(通孔件自己就是过孔, one_net() 的 todo 带 not p['hole'])。 ⬜ 只剩五颗 BM24 的 51 个 GND 脚(过孔塞不进 0.35 间距,只能逃逸布线,§25.8)。
  5. ✅ Inner1 分割电源面 —— 2026-09-06 铺完(五块 VOn_P 岛 + GND 底填, §25.11 / pcb2_pour.py 的 ISLANDS)。三个校核脚本全过: inner1_check.py(下发前预测)/ inner1_verify.py(下发后实测,最窄颈 7.20 mm)/ inner1_diffref.py(§10.1 参考面)。 ⇒ +5V / +3V3_HUB / VOn_P 的扇出现在可以做了(之前扇过去全是悬空过孔)。
  6. 🟡 布线 —— 先 D±(长的先走),再电源,最后杂散信号。 ⚠️ BM24 的逃逸布线单独排一项:前排往南、后排往北,同排横向穿不过去(净距 0.17 mm)。

Inner1 分割电源面的规划(⚠️ 这是 09-05 的规划稿;落地结果与实测见设计文档 §25.11)

层角色:Inner1 = 电源面(分割),Inner2 = 完整地平面(§25.6 ①,和下板是镜像的)。

只做三类岛,其余全部 GND 底填(不是原来写的「+5V / +3V3_HUB / VO15_P / VBUS15」 十二块)——理由是内层是 0.5 oz,按 IPC-2221 内层算 4 A 要 6.97 mm 净宽、 2.8 A 要 4.3 mm,而两排手机之间的走廊(y 49.66~85.5)只有 35.8 mm 高:

岛 网络 为什么要 最小颈宽
5 块 VO1_P~VO5_P 每路 4 A,这是上四层的头号理由 ≥7.0 mm
底填 GND 剩余全部,优先级数字给最大 —

⚠️ 2026-09-05 夜简化:+5V 和 +3V3_HUB 两块岛取消了(§25.10)。 排母逐针分配按几何重排之后,+5V 从 3 针变成 10 针、分散在两颗排母的南北四处, 每个入口只喂本站的 U2n + C70n、段电流 ≤0.6 A ⇒ 顶层 0.5~0.8 mm 走线就够, 内层不用给它留地方。+3V3_HUB(420 mA、局部)同理。 ⇒ Inner1 只剩「五块 VOn_P + GND 底填」,五路 4 A 各走各的,互不挤。

VBUS1~5 不做岛:每口 0.5 A、走线只在本服务带内约 18 mm,PWR1A(0.6 mm) 走线足够。 额外好处:顶层那 5 段 *J 差分(U5n→J7n,5 mm)下方的 Inner1 就是完整的 GND 底填, 参考面比分割的电源面干净得多(§25.7 ① 那条警告因此自动消解)。

走廊里已知的障碍(Inner1 只怕直插焊盘,不怕表贴件): C801/C811/C821 三个 Ø12.5 电解在 y 68.2580.75,C831/C841 在 y 54.4166.91, 两个新中跨 M3 的 Ø6 焊盘在 (76.6, 80.1) / (148.1, 80.1), 外加 J31/J32 的 80 个通孔焊盘(坐标已定稿:J31 x 37.86986.129 / J32 x 133.869182.129, 两行 y = 66.23 / 68.77,见 pcb2-header-pads.json)。 ⚠️ 2.54 间距的通孔在内层会把铜面打成筛子(Ø1.7 焊盘 + 0.2 净距 ⇒ 相邻反焊盘之间只剩 0.44 mm 网), 电源岛的颈宽要绕开这两条带,别指望从排母底下横穿过去。

还欠用户的五件事

  1. BM24 的镜像位实测 → ✅ 2026-09-05 已完成,结论「不用改」(§24.3)。 可选的零成本复核(还没做):奇数排上从 G1 端数,位置 17-18-19 与 22-23-24 各自三连导通 (SPKR_OUT_N 33/35/37 和 SPKR_OUT_P 43/45/47),偶数排没有这种三连。 0b. 量排线两个公头之间的距离 → ✅ 用户拍板不管,装配时折叠(余量约 80 mm,会占手机上方高度)。
  2. h_up:主板正面(朝上那面)最高元件高出板面多少。只影响压板高度与整机层高。
  3. ✅ 排针/排母选型已定(§22.3f):C55020601 + C5307344,对插 13.0 mm。顶针已定稿。 ⬜ 到货要核两件:母座触头镀层(商城只写了「黄铜」,没写镀层)、 对插后实测板间距是否真的 13.0 mm(确认是塑料贴合、不是针先顶死)。 2b. 首件上电前量一次:VBUSn 对 GND 的电阻(确认电源触点 G1/G2 没接反,§13.5)。 负载开关默认关断,所以这一步零风险、零成本。
  4. 顶针首件必验:±1 mm 公差链对 1.2~2.2 mm 的侧键触点是危险的,而且 选定的顶针冠径只有 Ø0.9 球头、又是贴片不可调,所以首件实测是唯一对策(§22.3c)。
  5. 用卡尺重量那 7 个定位孔:±1 mm 里有 ±0.5 来自「孔位是从照片提的」, 量一遍能直接砍掉一半误差 —— 这是眼下性价比最高的一项。

这一轮做了什么(2026-09-04 傍晚,载板画完)

载板 8 页

C1-CONN(排母 J31/J32 + U31 5V→3.3V + U34 LM75BD 温度芯片 0x48)、 C2-HUB、C3-USB、C4-SITE1~5(每台手机一页:P+/P− 焊盘 + §6.3 电容组 + 4 根侧键顶针 + EDL 座/模拟开关/下拉/去耦)。逐页内容与位号见 §22.7。

手机位一台一页,和下板「一路一页」保持同一结构;五页由 .claude/c4_site.py 同一段代码生成,改一处五页一起变。

这一轮自己发现并补上的两个洞

  1. UP_VBUS_DET 在载板上没有源头。 §22.2 给它留了一根排针,但 J23 的 VBUS 是 5V, 排针那头是 3.3V 域的 IO44。在 C2-HUB 上补了 R721 10k / R722 20k 分压(3.33V)。
  2. 载板上原来没有 3.3V。 排针只送 +5V,而 Hub、温度芯片、五颗 EDL 开关都要 3.3V。 U31 从下板搬过来后画在 C1-CONN(+5V 进来的那一页);§9.5 要求的「就近 Hub 供电」 是 PCB 摆位约束,与分页无关,已在文档里说清楚。

两个明知不对、故意先放着的占位型号(⚠️ 顶针那条已于当晚解决,见文首)

占位 差在哪
顶针 C9900001338 ✅已换 自由高 9.0 mm,§22.3e 要的是工作高 2.0 / 自由高 3.03.5 / 冠径 Ø1.52
排母 C9900025309 高度没按 13 mm 净空选;而且编号体系和公头不同(§22.2 有对照表)

放占位是为了让网表先连通、能跑完整验证;两个都在 §22.7 里单列了一节,打样前必换。

⚠ 一个必须记住的编号陷阱

下板公头(DIP 式)第 r 行 = 引脚 r / 41−r;载板母座(IDC 奇偶式)第 r 行 = 引脚 2r−1 / 2r。 只能按行对,不能按引脚号对(VO1_P 在下板是 1/2/3 脚,在载板是 1/3/5 脚)。 原理图这边不会错 —— c1_conn.py 直接 import p5_btb.py 的行表,两边同一个事实来源。 风险在 PCB:焊盘编号 + 摆放镜像决定第 r 行会不会真对上第 r 行,布板时要实打实核一次。

新踩的两条 EDA 坑(已写进 skill §6c / §6d)

  1. 同一颗电解、同样 create(270),在两个图页里 pin1 方向相反。 方向表一张都别信, 极性元件放完必须断言(c4_site.py 里断言「电解 pin1 的 y 必须大于 pin2」)。
  2. 红黑两个测试点的引脚偏移不一样(−20 / −10),按名义值算线会画出斜线, 而 create() 对斜线直接抛 create failed! 并让整批下发中途断掉。 现在下发前统一断言「横平竖直且非零长度」。

状态一览

阶段 状态
方案设计 ✅ 定稿 v1.6(五路直供电源板设计.md)
小米8 侧接口引脚定义 ✅ 已从原理图逐脚追踪(设计文档 §13)
器件选型 ✅ 全部立创有货,UUID 缓存在 .claude/eda-parts.json
P1-INPUT ✅ 补完输入侧 INA226(U13+Rs6+C11,§19),六项验证全过,需重新看一眼
P2-CHANNEL(CH1) ✅ 已删 J2/J7,侧键漏极改挂 POGO1_* 端口,六项全过
P2B-CH2 ~ P2E-CH5 ✅ 同样删了 J3J6 / J8J11,改挂 POGO2~5_*,各自六项全过
P3-CONTROL ✅ 画完,六项验证全过,等用户看截图
P3B-EXPAND ✅ 画完,六项验证全过,等用户看截图
P4-USB ➡️ 已整页搬到 Board2 载板,改名 C3-USB;下板已删
P4B-HUB ➡️ 已整页搬到 Board2 载板,改名 C2-HUB;下板已删。⬜ 还要在载板上补 U31
P1B-5V ✅ 已摘掉 U31 + C736/C737(跟 Hub 一起上载板),现在是纯 12V→5V,9 元件,六项全过
P5-BTB(原 P5-HARNESS) ✅ 整页重画:J24/J25 两条 2×20,47 信号 + 33 地,六项全过(截图 P5-BTB.png)
P6-FAN-EDL ✅ 新建:U33 第三片 595(EDL_EN15)+ 风扇低边驱动 + J26J30,六项全过(截图 P6-FAN-EDL.png)
跨页网络对账 ✅ 下板 11 页 0 漏接 / 0 待补;✅ 载板 62 个网络 0 漏接
port_audit.py ✅ 下板 513 个、载板 260 个,可疑 0 个(载板加 --board Board2)。⚠️ 2026-09-04 晚重跑时载板曾报 1 个(C1-CONN 的 GND 旗),已修 —— 每轮都要重跑,别信上一轮的数
Board2 载板原理图 ✅ 8 页全部画完:C1-CONN / C2-HUB / C3-USB / C4-SITE1~5,六项验证 + 跨页对账 + port_audit 全过
Board2 载板 PCB 🟡 4 层 / 220×135 板框 / 35 个定位孔 / 6 个 M3(含 2 个中跨)/ 161 个元件已摆完(§25、§25.6、§25.7);⬜ 未布线
Board2 载板 GND 扇出 🟡 210 个过孔已落盘(§25.8),脚本 pcb2_fanout.py;⬜ 差 J31/J32 的 33 个(等换件)+ BM24 的 51 个(要逃逸布线)
Board2 载板 设计规则 ✅ 整套已下发,pcb2_rules.py --check 十三项全过(四层能力集 / PWR4A·PWR5V·PWR1A / 11 对差分 / USBPAIR 0.15 / 12 个网络指派 / PLANE-DIRECT 铺铜全连接 + 5 个网络指派)
Board2 载板 铺铜 ✅ 两层内层都铺完:Inner2 整板 GND + Inner1 五块 VOn_P 岛 + GND 底填(§25.11)。脚本 pcb2_pour.py(ISLANDS 是事实来源,--verify 回读)+ 三个校核脚本 inner1_check / inner1_verify / inner1_diffref
BM24-50DS 封装 ✅ 自建完成(§24),个人库器件 771dbf00…,几何来自 Hirose 官方样本。⬜ 镜像位待实测(§24.3)
PCB 转板 ⚠️ 已过期:原理图这一轮删了 15 个连接器 + 53 个 7xx 器件、新增 J24~J30/U33,还没导入变更
PCB 板框 ✅ 160 × 125 mm,四角 R5 圆角 + 4 个 M3 金属化定位孔(接 GND)
PCB 布局 🟡 第五遍:8 A 输入链整条重排(见下),等用户看 PCB-layout-v5.png
设计规则 ✅ 整套已下发,--check 七项全 OK(能力集 / PWR4A / PWR12V / 差分对 / 0.2 mm 间距 / 7 对 / 12 个指派)。上一轮说的「只能手动」是错的,见下
PCB 板框 ⚠️ 改成一条闭合 polyline 了(原来的 4 直线 + 4 圆弧自动布线器不认,见下)
设计规则 ✅ 等长容差 5 mm;新增 USBPAIR 0.15 mm,只挂在每一对自己的两个网络之间
铺铜 ✅ 19 块(新增 +5V / +3V3_HUB / +3V3×2),三条低压轨的 91 个焊盘已扇出
7 对 USB 差分 ✅ 6 / 7 布通(USB1~5 + UPSTREAM)。⬜ 只剩 ESP32 调试口 USB_DP/DM
PCB 布线 🟡 1208 过孔 / 5814 导线已落盘(save 后重开验证过)。⚠️ 铺铜是旧填充、接口区马上要大改 —— 先别继续布
Kelvin / 灌铜网络的漏网焊盘 ⬜ 要手工布:INA226 的 VIN±、五路泄放电阻 R*19、输入侧 U13 —— 5 mΩ 分流器满量程才 20 mV,多 1 mΩ 走线就是 20% 误差
GND 漏网焊盘 ⬜ 20 个(459 个扇出过孔之外的)
丝印整理 ⬜ 位号大面积压字,打样前要单独跑一遍
原理图 SCL 断线 ✅ U13 的 SCL 压在导线中段、根本没接上,已修(设计文档 §19.4)。新增全工程扫描 .claude/port_audit.py
板载 USB Hub 改造 ✅ 原理图 + PCB 布局都做完了
两板架构(2026-09-04 拍板) ✅ 设计文档 §22 定稿;✅ 下板原理图改造已做完(§22.7);⬜ 载板 C1-CONN 未画
上板(载板) ✅ 原理图 8 页画完;⬜ PCB 还是空板;✅ 顶针已定稿 YTC1P-2510-01;⬜ 只剩排母占位(§22.7)
散热 ✅ 定稿 + 电路已画(P6-FAN-EDL:Q6/D2/J26~J30 + TACH 分压)。⬜ 风扇型号未定(若选 4 线风扇,§22.6 那段 MOS+分压可整段删)
小米8 主板机械数据 ✅ 全部到手:44.66×67.09 实测、7 个定位孔 ±0.5、触点 ±0.3(§22.3)。数据在 .claude/mi8_mech.json / mi8_contacts.json
EDL(9008) 控制口 ✅ 两侧都画完:下板 U33(第三片 595)→ EDL_EN1~5 → 排针 → 载板 U41U45(TS5A3157DBVR)+ J61J65(1.25mm 2P)+ R741~R745 下拉

下板原理图 11 页(顺序已排): P1-INPUT / P1B-5V / P2-CHANNEL / P2B-CH2 / P2C-CH3 / P2D-CH4 / P2E-CH5 / P3-CONTROL / P3B-EXPAND / P5-BTB / P6-FAN-EDL。

载板原理图 8 页(顺序已排): C1-CONN / C2-HUB / C3-USB / C4-SITE1~5。

截图:12 页里已截 10 页(缺 P2B/P2C/P2D,它们是 P2 的机械复制)—— P1-INPUT.png、P1B-5V.png、P2-CHANNEL.png、P2E-CH5.png、P3-CONTROL.png、 P3B-EXPAND.png、P4-USB.png、P4B-HUB.png、P5-HARNESS.png,PCB 最新是 PCB-layout-v4.png。 (上一轮那三张空白 PNG 是渲染没出来,不是图元缺失;这一轮重截,都是好的。)

这一轮做了什么(2026-09-04,方案转向)

一、自动布线(转向前完成的部分)

见下面「自动布线终于跑起来了」那节。结果保留着:1208 过孔 / 5814 导线, 6/7 对 USB 差分布通,间距/物理错误 0。但接口区马上要大改,别在这上面继续投入。

三条最贵的教训(每条都卡了很久):

  1. 板框必须是一条闭合 polyline,不能是 4 直线 + 4 圆弧。 自动布线器取板框走 fu(),只扫 polygon 管理器;独立线/弧不在那个表里 → getFilterBoard() 返回空 → 弹「请先绘制板框!」然后静默退出。 正确写法:pcb_MathPolygon.createPolygon(SRC) → pcb_PrimitivePolyline.create('', 11, poly, 5)。 ⚠️ 直接把源数组喂给 create() 会抛「无法创建多边形图元」而且真的没建 —— 跟铺铜那条「抛了也算成功」方向相反。
  2. 布线跑的时候什么都别碰。不要 save()、不要 pcb_Drc.check()、 连 getAll() 这种只读调用都别做。被打断时 routerResult() 里 state!==1 会直接丢弃结果 —— 画布上看得见、getAll() 数得到, 但没进文档模型。判据只有一个:save 后重新 openDocument 再数一次。
  3. 差分对要一次统筹全部 7 对。一对一对布会互相堵死(保留 模式下先布的成了障碍)。 正确姿势:全选 7 对的焊盘 + 「已有导线/过孔 = 移除」+「布线网络 = 所选网络」。

其它:重载客户端会清空自动布线对话框的设置;DRC 面板刷不出来时走 设计 → 清除错误 → 设计 → 检查DRC。

二、方案转向:两块板(用户 2026-09-04 拍板)

用户的原始需求分四次说清楚:

  1. 「做成两块板,五路 usb 移到上板,排针对接,上板加五块主板定位」
  2. 「进入 9008 的控制接口也接入」
  3. 「与主板连接的 usbhub 移到上板,不用排针,排针只需要把下板的电源部分接上来」
  4. 「侧键做顶针,EDL 做 2 针 1.25mm 座子我自己接线,VBUS 通断走排针」
  5. 「不要散热垫,加风扇」→「侧面两侧各 2 个小风扇吸入,顶部 1 个大风扇排出」→「风扇装 3D 打印外壳上」

完整规格写在设计文档 §22,这里不重复。

三、⭐ 小米8 主板机械数据:一直就在我们自己的 PDF 里

旧结论「PDF 是电气原理图、不含机械尺寸、只能实测」是错的。 小米8原理图/Xiaomi_Mi_8_Schematic.pdf 共 46 页, 前 2 页是正反面位号图(含完整板框轮廓 + 全部位号),后 44 页才是原理图。 当初只按「原理图」去读,get_text() 一看全是位号就跳过了,从没渲染出来看一眼。

拿到的数据(方法与精度分档见 §22.3a / §22.3b):

  • 比例尺 17.0 pt/mm(从 464 个 0201 封装反推,卡尺验证 0.7%)
  • 主板 44.66 × 67.09 mm(卡尺实测)
  • 7 个定位孔(从用户标红框的照片提取,±0.5 mm)
  • EDL / 尾插 / 侧键触点(从器件本体图元提取,±0.3 mm,并被两个卡尺读数交叉验证)

四、⚠️ 这一轮我判错了四次,都是同一个毛病

没有实物对照就从矢量图元硬推。 记下来,别再犯:

判错 错因
「主板没有定位孔」 轮廓用「逐行取最外侧墨点」提取,这方法看不见板内的孔和挖空
「定位孔就是图纸上那 7 个圆」 抓到的是测试点和圆焊盘。摆成横向和用户照片一对就露馅
把两个卡尺读数当成「板厚」 用户说的是侧键到板边的距离
把 7.61 读成 1.76 卡尺屏是旋转的,数字看反了

规矩:卡尺读数先跟用户确认再落档;从图纸推的几何结论, 必须有一个实物基准交叉验证才能写进文档。

五、EDL(9008) 电路(原理图 Sheet 17,比维修站的付费短接图可靠)

VREG_S4A_1P8 ──[R1204 1.8kΩ 0201]── TP1204   ← 0.75 mm 测试点
FORCED_USB_BOOT ──┬── TP1205                  ← 0.75 mm 测试点
                  └── CR1209 PESD3V3V1BCSF ── GND

短接两点 = 把 FORCED_USB_BOOT 拉到 1.8V → 进 9008。 串了 1.8 kΩ,短路电流只有 1 mA,一颗模拟开关就够。详见 §22.4。


这一轮:自动布线终于跑起来了(2026-09-04)

⚠️⚠️ 头号发现:板框必须是一条闭合多段线,不能是 4 直线 + 4 圆弧

布线 → 自动布线... → 运行 静默失败:对话框正常关闭、一根导线不画、 console 没有任何异常、也没有网络请求。三次尝试(React onClick / 原生 dispatchEvent / 等 90 秒)现象完全一样。

顺着客户端 bundle 追下去才找到:

pcb_ManufactureData.getAutoRouteJsonFile()
  → ui.js  getAutoRouteJson()  → windowBridge.rpcCall('getNewAutoRouteJson', …)
  → pcb.js vP.autoRouting()    → createPangolinJson()
  → getFilterBoard() → fu() → []        ← 就是这里
  → 弹 "Please draw a board outline first!" 然后 return undefined

fu() 的判据是(pcb.js 反编译):

[Ni.getInstance().getAllItem(), tn…, Yi…]      // 只有 polygon 管理器
  .forEach(t => t.forEach(e => {
     !e.isEmpty && (e.type == "Board Outline" || e.type == "Line" && e.layerId == 11) && o.push(e)
  }))

它只扫多段线(polygon)管理器。我们的板框是 pcb_PrimitiveLine.create() × 4 + pcb_PrimitiveArc.create() × 4, 八个独立图元,压根不在那个表里 —— 于是 fu() 恒返回空。

这个错极难往板框上想,因为除了自动布线,板框在别处全都是好的: 渲染正常、铺铜按它的几何铺好了、DRC/ERC 全过、Gerber 也认。 同一个 fu() 还卡着 getDsnFile() 和 getAutoRouteJsonFile() (两个都返回 undefined,等于 freerouting 那条路也被堵死了)。

正确姿势(两步,缺一不可):

const poly = await eda.pcb_MathPolygon.createPolygon(SRC);   // ⚠️ 必须先过这一步
await eda.pcb_PrimitivePolyline.create('', 11, poly, 5);

⚠️ 把 TPCB_PolygonSourceArray 直接喂给 pcb_PrimitivePolyline.create() 会抛「错误:无法创建多边形图元」,而且是真的没建(getAll() 仍是 0)—— 注意这跟铺铜那条「create() 抛了也算成功」的经验方向正好相反,别混用。

pcb_place.py 的 push_outline() 已经改成这个写法(新增 outline_poly())。

一路排查中间踩到的三件事

  1. pcb_PrimitiveArc.getAll() / getAllPrimitiveId() 在本工程恒返回 0。 排查时我以为四个 R5 圆角不存在,又补建了四段 —— 于是 layer 11 上现在有 8 段重叠圆弧 + 4 条重叠直线(几何完全重合,Gerber 上是同一条路径描两遍)。 它们不在 fu() 的表里,不影响布线;但删不掉(枚举不到就没有 primitiveId), 要清只能在客户端里手删,或者隐藏其它层后框选 layer 11 全删再重建 polyline。
  2. 桥接的 WS 请求 30 秒硬超时,eda.py 的 timeout= 只管 HTTP 那一段。 JS 里 sleep 超过 30 s 一律拿到 Request … timed out after 30000ms。 要等就分多次短调用轮询。
  3. 客户端重连会换 windowId。curl http://127.0.0.1:49620/eda-windows 可以确认 当前只有一个窗口 —— 排查「是不是打到另一个窗口去了」时先看这个,别猜。

抓静默失败的通用手法(这轮就是靠它破的)

页面里挂三层钩子,再触发一次,就能看见吞掉的错误:

// ① console 全接管 ② window.onerror / unhandledrejection ③ fetch / XHR / Worker
// ④ 最关键:postMessage —— 父窗口与编辑器 iframe 之间的 rpc 都走这里
window.addEventListener('message', e => { …记录 topic 与 message… }, true);
document.querySelector('iframe.editframe').contentWindow
        .addEventListener('message', …, true);

回包里 {"topic":"getNewAutoRouteJson_MSG_BUS_RPC_64600","type":"push"} 没有 message 字段 —— 一眼就能断定是 iframe 里的处理函数返回了 undefined, 而不是参数没传对或者按钮没点上。

再配合 console.warn 里打 new Error().stack,能直接定位到 ui.js:1819 的 getAutoRouteJson,然后把 pro-ui/…/js/ui.js(17 MB)和 pro-pcb/…/js/pcb.js(10 MB)下下来 grep —— 这两个包是公开 CDN,随便下。

自动布线的设置(对话框里的状态是持久化的,改一次就记住)

项 取值 为什么
布线网络 所有网络
布线拐角 45°
已有导线/过孔 保留 708 个扇出过孔和平面层不能被冲掉
效果优先级 完成度优先
布线图层 只勾顶层 + 底层 内层1 是完整地平面、内层2 是电源平面,不许被信号线割开
忽略网络 15 个 GND(459 个扇出过孔已经接到地平面了,让它布会刷出一堆顶层地线)+ 7 对差分的 14 个网络(USB1~5_DP/DM、UP_DP/DM、USB_DP/DM,打算之后按对单独布)

对话框是 React 的,忽略网络 那个下拉可以直接从 fiber 上取 onChange 逐个灌:

const p = /* 从 [data-id^="selectOptionKey_"] 往上找带 {list, onChange} 的 fiber */;
p.onChange('GND');            // 每加一个 list 就少一个,要重新取 fiber

⚠️ 自动布线的结果不到最后不落盘 —— 中途被打断就白跑

第二趟(补差分对)跑完,getAll() 报 6196 条导线、几何上逐段查过 USB1_DP 从 U32.2 一路连到 J17.3、并查集只有一个连通块, 但 DRC 咬定这两个焊盘断开。pcb_Document.save() 之后 重载客户端,板子回到了第一趟的 1095 过孔 / 3455 导线 —— 第二趟一根线都没进文档。

原因在 routerResult():

routerResult(t){ if(this.state!==1){ console.error("Auto Routing is stop!", this.state); return; } … }

布线结果是一批一批推回来的,state!==1(被打断)时直接丢掉。 画布上看得见、getAll() 数得到,但那只是 historyGroup 里的预览,没有进文档模型 —— 所以 DRC 是对的,错的是我。

判据只有一个:save 之后重新 openDocument,再数一次导线/过孔。 数字对不上就是没落盘。别信 getAll(),也别信画布。

跑的时候不要碰客户端:不要 save()、不要 pcb_Drc.check()、 连 pcb_PrimitiveVia.getAll() 这种只读调用都先别做 —— 第二趟就是这么被打断的 (第一趟跑满 ~50 分钟、进度条自己消失,结果就落盘了)。 盯进度只读 DOM 文本:document.body.innerText 里那行 自动布线中...(NN%)。

顺带两条

  • 重载客户端会把自动布线对话框的设置清空(忽略网络清零、四个布线图层全勾回来)。 重载之后必须重新配一遍再跑。
  • DRC 面板刷不出来时走菜单:设计 → 清除错误(全部 (0))→ 设计 → 检查DRC。 面板上那个「刷新」按钮不是 DRC 的,pcb_Drc.check() 单独调有时也不刷新。

局部返工:把 7 对 USB 差分抢回通道(2026-09-04,用户拍板)

第一趟自动布线之后,7 对差分只布上 2 对(USB1/USB2),其余 5 对 连「所选网络」单独重跑都一根线不画 —— 板子已经被普通信号线占满了。 用户拍板走「局部返工」而不是整体回滚。四步:

① 先改规则(pcb_rules.py --push),否则布完还是一堆假违规:

改动 从 到 为什么
differentailPairLenTolerMax 0.5 mm 5 mm USB 2.0 HS 对内偏斜预算 ≈100 ps ≈ 15 mm;实测偏差 1.6~1.9 mm ≈ 10 ps。为凑 0.5 去绕蛇形线是拿串扰换数字
新增安全间距条目 USBPAIR — 0.15 mm 只通过 overwriteNetByNetRules 挂在每一对自己的两个网络之间;对其它网络仍是 0.2 mm

USBPAIR 那条不能用全表抬下限的循环扫到(会被抬回 0.2),代码里单独跳过了。

net-by-net 的条目形状文档没写,是从 pcb.js 的 getBetweenNetDataItem() 反出来的:

{net1:{type:'net',name:'USB1_DP'}, net2:{type:'net',name:'USB1_DM'},
 rule:'USBPAIR', copperRuleName:null, planeRuleName:null}

⚠️ overwriteNetByNetRules 是整表覆盖,要先 getNetByNetRules() 读回来再合并。 改完 DRC 172 → 166,4 条对内间距 + 2 条等长违规一起消失。

② 三条低压轨改成铺铜(铜面 15 → 19 块):

铜面 网络 层 范围 盖住
5V-L3 +5V L3 x 11151 / y 5.514 10 / 16
3V3HUB-L3 +3V3_HUB L3 x 59111 / y 1531 21 / 21
3V3-CTRL-L3 +3V3 L3 x 95.5150.5 / y 33.567 合计 45 / 61
3V3-CH-L3 +3V3 L3 x 5155 / y 100.3124 同上

都塞在 12V 那三条带子之间的空地里,互相与 12V 都留 ≥0.5 mm。 3V3-CTRL-L3 的右边界 150.5 是被 ESP32 天线禁铺区(x≥151)逼的。

这带出两个工具改动:

  • pcb_pour.audit() 要按网络做并集。+3V3 是两块铜面,逐块判「焊盘在不在里面」 会让每一块都把另一块盖住的焊盘报成「意外漏掉」。
  • 新增 POUR_PARTIAL = ('+5V', '+3V3'):这两条轨的铜面故意只盖一部分, 漏在外面的焊盘靠走线接,不算错。

③ pcb_fanout.py 只对「落在自己铜面里」的焊盘扇出(新增 _pour_filter())。 在没有铜的地方打一个下到 L3 的过孔 = 一个悬空过孔,DRC 反而多一条断连。 三条轨一共扇出 91 个过孔,5 个焊盘周围太挤放不下,留给走线。

④ 局部返工的选取判据(_snap_before.json 是基准):

删掉「布线器加的」且满足下面任一条的**整个网络**的线与过孔:
  a) 网络已经改成铺铜(+5V / +3V3 / +3V3_HUB)
  b) 该网络在 y < 28 mm 的下带里有走线,且不是 GND/12V/VOn 这类扇出网络、也不是差分对

结果:34 个网络 / 678 条导线 / 68 个过孔,只占布线器产物的 18%。 整网删、不留断头,剩下的 80% 一根没动。

⚠️ 差分对必须一次统筹全部 7 对,一对一对布会互相堵死

下带清空之后,选中 5 对跑「所选网络」,只布上 USB4/USB5;再跑一次, 一根线都不加。原因是 保留 模式下先布好的那两对成了后面的障碍 —— USB3 与上游 UP 要横穿它们,而下带就那么宽。

正确姿势是把 7 对全选中 + 「已有导线/过孔 = 移除」,让布线器把已布的两对 一起拆掉重排。clearRoute(false, undefined, this.selectNets) 只清选中的网络, 不会波及别处(pcb.js 里确认过)。一趟下来 6 / 7 对布通(USB1~5 + UPSTREAM), 只剩 ESP32 那条调试口 USB_DP/DM。

顺带澄清一条之前的误判:「所选网络」模式是好的。早先它「跑完却一根线不画」 不是模式坏了,是那时候板子上真的没路了 —— 日志里 routingRange:1、选中 24 个焊盘 都对,弹的也是「自动布线已完成」而不是报错。

布线跑完之后要做的(按顺序)

  1. DRC —— python .claude/drc_read.py。看「间距错误」和「物理错误」两支, 连接性错误剩多少 = 还有多少没布上。
  2. 七对差分单独布(USB1~5_DP/DM、UP_DP/DM、USB_DP/DM)。 规则里已经建好 7 对、W 0.28 / S 0.18 / 等长容差 0.5 mm。 ⚠️ 顺序上其实应该先布差分再自动布线——这一轮是为了先验证布线器能不能跑才反过来的。 如果跑完发现差分对已经无路可走,就按快照回滚,改成「先差分后自动」重跑。
  3. 重新铺铜。底层(layer 2)上有 VO1~5_P 五块铜面,自动布线在底层加了信号线, 铜面要重建才会让开它们:工具 → 铺铜管理器... → 重建所有 (pcb_pour.py --rebuild 里已经把这一串打好了;rebuildCopperRegion() API 是坏的)。
  4. 丝印整理 —— 位号大面积压字,打样前要单独跑一遍。
  5. Gerber 预览复核板框闭合 + 四个 M3 定位孔。

跑之前先存了个快照(.claude/_snap_before.json)

布线前把 708 个过孔 + 575 条导线的 primitiveId 全存了下来。 万一自动布线的结果不能要,删掉不在这张表里的线和过孔就能精确回滚, 比 清除布线(会连扇出一起清)干净得多。


上一轮:开始布线(2026-09-03)

顺序是「先把面铺好,再拉线」—— 平面定了,信号线的参考面和过孔位置才有依据。

1. 先抓到一个真的断线:U13 的 SCL 没接上 ⚠️

追 12V 母线的网络时发现 PCB 里 U13.2 / U13.5 挂在一个只有两个焊盘的 $1N257 上,而 SCL 网络 11 个焊盘里没有 U13。输入侧检流那片 INA226 是死的。

根因不是漏画线,是 SCL 端口的引脚压在一条导线的中段而不是端点 (端口在 (920,340),导线是 [920,320 → 920,570])。EasyEDA 只在端点认连接。 同一页的 SDA 端口恰好落在端点上,所以 SDA 是好的 —— 两个端口在图上一模一样。

DRC / ERC / verify_sch --nets / 跨页对账四道全过,因为 verify_sch 的判据是「引脚落在某条导线段上」,中段搭接满足这一条。

新增 .claude/port_audit.py:全工程 572 个旗/端口逐个判「是不是某条导线段的端点」。 修之前 1 个不合格(就是它),修完 0 个。写进 easyeda-sch-geometry §8a-2 与设计文档 §19.4。

写这个检查时踩了个自伤的坑:第一版按折线解析导线,572 个里报出 197 个可疑, 其中一大片是工作正常的 GND 旗。getState_Line() 返回的是端点对(skill §5), 按折线解析会凭空造出斜线段。误报到这个程度的检查等于没有检查。

2. 8 A 输入链整条重排

原来这一串是仿射映射甩出来的,J1(x5.8) → F1(x46.6) → Q1(x53) → 体电容(x66/75) → Rs6(x84.6~90.7):8 A 要从 x5.8 走到 x90.7,85 mm,其中 $1N223(J1→F1) 单独一段就 42 mm。8 A 在 0.5 oz 内层要 ΔT=20 °C 得 18 mm 宽铜面 (IPC-2221 内层按外层 50% 取,面积翻倍),band B 一共才 36 mm 高,塞不下四条。

而且这一串在 y 上来回折(F1.2 在 y60.08,Q1 源极在 y62.85,中间还夹着 $1N236 在 y60.56 的一个焊盘),L3 上按 y 切开分块,切线最窄处只剩 0.2 mm。

改成 J1 右手边一条走廊 x 11.546 / y 49.568.5(栅极网络放 J1 上方口袋):

之前 现在
$1N223 J1→F1 42 mm 6.9 mm
8 A 段的横跨 x 5.8 ~ 90.7 x 12 ~ 40
分叉点 x 90.7 x ≈ 40(Rs6 下游)
母线最大截面电流 8 A 全程 顶带 ≤ 6.4 A(往左喂 CH1、往右喂 CH2~5)

顺带把 13.7 × 9.7 的立式泄放电阻 R9 也钉死了 —— 仿射区右缩之后它被推到 x95, 本体框伸到 x102 压进 P3 区。relax() 只夹中心坐标、不看外框,这类件必须手钉。

3. 平面层(.claude/pcb_pour.py)

铜面 层 网络 范围
GND-L2 L2 GND 整板(板框内缩 0.5,挖 ESP32 天线区)
VIN-RAW-L3 L3 $1N223 x 314 / y 45.555.5
VIN-FUSED-L3 L3 $1N227 x 14.619 / y 49.556.5
VIN-SW-L3 L3 $1N236 L 形,x 14.435 / y 4966.5
12V-L3 L3 +12V 下带 x14.695 y3348.3 + 中带 x38.595 + 顶带全板 y67.581.9
VO1~5-L3 L3 VOn 每路 30 mm 宽 / y 82.5~99.5
VO1~5_P-L4 L4 VOn_P 每路 30 mm 宽 / y 110~116.2,带一条下伸臂到分流电阻

分界线是焊盘边定的不是整数:+12V 顶边 81.9 卡在 C102 那排 1210 焊盘(y80.281.7); VOn 底边 82.5 是因为 C107 pin1 是 rot90 的 1210,焊盘跨到 y83.0784.55 —— 按「元件中心在 y85.4」算会把它漏在两块铜面之间的缝里。

U8~U13 的 0.5 mm 间距 Kelvin 脚一律不进铜面(U13.10 是 VIN+、U13.9 是 VIN−, 相距 0.5 mm,任何分界线都塞不进去),走顶层细线,本来也不载流。 pcb_pour.py 里 audit() 会把「本网焊盘在铜面外」的逐条列出来, EXPECT_OUT 是白名单,漏在名单外的就要查。

4. 铺铜 API 的三个坑(都实测过)

  1. rebuildCopperRegion() 是坏的,四种写法都抛 Cannot read properties of undefined (reading 'length')。 唯一能填充的路子是 GUI:工具 → 铺铜管理器... → 重建所有 → 确认, 已用 React 的 onClick 打进 pcb_pour.py。判据是 nPoured 不是 nPour。
  2. 多边形必须显式闭合(末尾把首点再写一遍)。文档说「首尾不重合会自动重合」 对铺铜边框不成立:不闭合就直接不建。board_poly() 一直能用是因为 它最后那段圆弧正好回到起点。
  3. create() 成功了也会抛 错误:无法创建覆铜边框图元。返回值和 try/catch 都不能当判据,只能建完 getAll() 按名字核对 —— 而且名字会被转成大写。

排查时分不清 1 和 2 的办法:不闭合的矩形建完 getAll() 数量不变,闭合的 +1, 两者抛一模一样的异常。

5. GND 扇出:改布局之后必须按这个顺序重跑

python .claude/pcb_fanout.py --clean      # 1. 先清空板上的过孔与走线
python .claude/dump_pads.py               # 2. 再重新抓障碍表
python .claude/pcb_fanout.py --push       # 3. 最后规划 + 下发

--clean --push 写成一条会用旧的 pcb-copper.json:清是清干净了, 规划器看的还是那 459 个已经不存在的过孔,把它们当障碍绕 —— 实测 459 → 292 个过孔、五路 Buck 的 pin2 全扇不出去、27 个散热盘只打进 4 个孔。 反过来先 dump 再 --clean --push 更糟,板上会剩两套(747 个)。

正确顺序的结果(15 个网络一趟跑完):708 个过孔 / 0 个失败, 其中 GND 459、+12V 128、输入链三条 48、五路 VOn 各 17、五路 VOn_P 各 8。

6b. 出线宽度必须按焊盘算,不能按网络算

第一版给功率网络一刀切 1.0 mm(= 线宽规则的最小值),结果 29 个焊盘扇不出去: +12V 上挂着 R104 那种 0402 分压抽头,焊盘只有 0.57 × 0.54 mm, 1.0 mm 的线比焊盘还宽。退回 0.3 mm 硬扇出去,又被判 「线宽 11.8 mil,应在 39.4 ~ 393.7 mil」—— 34 条物理错误。

两处一起改才对:

  1. PWR4A / PWR12V 的最小值 1.0 → 0.3(默认值仍是 1.5 / 2.0)。 这就是 §10.1 那条「载流靠灌铜保证,不靠 DRC」再走一步。
  2. 出线宽度按焊盘短边 × 0.9 取,夹到 [0.3, 1.0];过孔数按焊盘短边 ≥ 0.8 mm 给 3 个、 否则 1 个 —— 0402 抽头本来就不载功率电流,多打过孔只会把平面打成筛子。

6c. 扇出的障碍模型漏掉的两件事(DRC 抓出来的)

现象 根因
10 条「导线到过孔 3.4 mil,应 ≥ 7.9 mil」 板上已有的过孔只按钻孔半径 0.15 登记进 holes,没按焊盘半径 0.3 登记进 grid。差的正好是 0.15 mm
1 条「导线到导线 6.2 mil」 出线之间的间距压根没查过 —— 原来只查了线对焊盘、线对钻孔

两条都补进 pcb_fanout.py 了(trk_grid + seg_seg_dist)。 补完重跑:间距错误 0、物理错误 0。

6. 顺手发现的两条 EasyEDA 路子

  • 设计 → 从原理图导入变更(Alt+I) 能用 DOM 打开并读出变更表, 逐条列 U13:2 $1N257 → SCL。改完原理图必须走这一步,PCB 才会跟着变。
  • 布线 → 扇出布线... / 自动布线... / 差分对布线 / 等长调节 都在菜单里。 信号线那 400 多条不必自己写布线器,下一轮直接驱动它。

最新一轮:设计规则不用手动了(2026-09-03 深夜,收尾)

上一轮结论「规则配置只能在 GUI 里手建」是错的。原因是参数形状给错了, 现在 python .claude/pcb_rules.py --push 一条命令把五项全下发,--check 七项全 OK:

[OK] 四层板能力集已就位(最小线宽 0.102 / 过孔 0.30-0.20,含 0.5oz 内层条目)
[OK] 线宽规则 PWR12V 存在      [OK] 线宽规则 PWR4A 存在
[OK] 差分对几何 W 0.280 / S 0.180 mm
[OK] 安全间距下限 0.2000 mm
[OK] 差分对 7 对: DEBUG UPSTREAM USB1 USB2 USB3 USB4 USB5
[OK] 线宽指派 12 个网络

当初为什么以为不行 —— 两个坑叠在一起

  1. overwriteCurrentRuleConfiguration(x) 要 config 本体,不要 {name, config} 包装。 传包装返回 undefined 且静默无效;传本体返回 true。 (saveRuleConfiguration(cfg, name, allowOverwrite) 确实是三个参数 —— 上一轮只传了一个 —— 但补齐了照样返回 false。它是真的不能用, 好在我们并不需要存命名配置。)

  2. 两种 config 的形状不一样,混用会被默默吃掉。

    来源 线宽 安全间距表
    getRuleConfiguration('<系统能力集>') 平:form.strokeWidthDefault entry.table
    getCurrentRuleConfiguration() 嵌套:form.data['1'].defaultValue entry.tables['1'].content

    正确姿势:拿 getCurrentRuleConfiguration().config 改,原形状送回去。 系统能力集只当数据源用(那张内层 0.5 oz 间距表是从它身上抄的)。

「切到多层板能力集」其实只是五个字段

把两个能力集逐值 diff 过:只差 3 个数 + 2 条默认不启用的条目,其余 300 多个数完全一致。

项 Two Layers Multiple Layers
Track/copperThickness1oz 最小线宽 0.127 0.102
过孔外径最小 0.5 0.3
过孔内径最小 0.3 0.2
Track/copperThickness0.5oz 无 有(min 0.089,isSetDefault=false)
Safe Spacing/copperThicknessInner0.5oz 无 有(isSetDefault=false)

也就是说,能力集之间的差别全是放宽限制,两层板那套只是更保守。 真正必要的是最小线宽 0.102(U32 那个 0.5 mm 间距 QFN-64 的扇出要用)。

⚠️ 下发之后千万别做的一件事

别再去 GUI 的 设计 → 设计规则… 里动“配置”下拉框。 换一下就是整套重置:PWR4A / PWR12V 两条线宽规则会被冲掉, 12 个功率网络的指派立刻变回悬空(DRC 不报错,线宽也就不生效 —— 查不出来的那种坏)。 要改规则就改 pcb_rules.py 再 --push。

配置名下发后会自动变成 “自定义配置”,这是对的 —— 说明它已经从系统能力集上摘下来了。

顺带踩到的一个坑:mm 值会被换算成 mil 再换回来

写进去 0.2,回读是 0.19999959999999997(差 4e-7 mm)。 任何几何值都不能做精确比较,--check 的容差已放到 1e-4。 (第一版容差 1e-9,结果安全间距那项在明明写对的情况下报红。)

DRC 结果读得回来(.claude/drc_read.py),而且真抓到了 9 个错

先说一句:本轮早些时候写的「DRC 结果读不回来」是错的,已推翻。

pcb_Drc.check() 确实只返回一个 boolean,40 多个方法里没有结果 getter, 也没有 DRC 标记层可读。但 DRC 面板在 DOM 里是挂载着的 (跟设计规则对话框不一样,那个窗口不前台就不挂载)。三个手法:

  1. 左边分类树要用 React 的 onClick 点 —— 从元素上找 __reactProps$*, 一路往上找到带 onClick 的那层直接调。el.click() 不管用,不点就不选中, 右边明细区一直是「空列表」。
  2. 「清除错误」那个反过来,它是原生 <button class="i18n">,没有 React props, el.click() 直接生效。同一个面板上两种按钮混着,别一条路走到黑。
  3. 明细表 innerText 尾巴上粘着分页组件的一大段 CSS,碰到 input::-webkit 截断。

跑法:/c/Python314/python .claude/drc_read.py(--no-check 只读不重跑)。

那一片黄叉:是 check() 画的,不是板子坏了

跑完 check(),每个未布线焊盘都盖一个黄叉,没布线时 1200+ 个, 满屏都是,看着像出了大事。分类是:

全部 (1273) = 间距错误 (9)  +  连接性错误 (1264)
                 ↑真错误          ↑还没布线,预期之内

只看「间距错误」那一支。 看完用面板上的「清除错误」清掉。

9 个真错误,两类,都已修掉(1273 → 1264)

① 贴片焊盘到贴片焊盘 ×5 —— 五路的 Cin 挨太近,差 10 µm

C%01 / C%02(Buck 输入去耦,§10.2-1 手调的热回路)两片焊盘只剩 0.1905 mm,安全间距要 0.2 mm,差 10 µm,五路一起报。 这是把全局间距从 0.152 抬到 0.2 mm 的直接后果 —— 老规则下它是过的。

改法:pcb_place.py 里 C%02 的 y 从 14.9 挪到 14.95,缝变 0.2405 mm。 0.05 mm 对约 4 mm 周长的热回路可以忽略。模型和板子都已改(五路各挪一颗)。

② 钻孔到钻孔 ×4 —— 四个 M3 定位孔各有一个重影

四个角上各有两个 MHn 焊盘,焊盘一样大(6.0 mm),钻孔一个 3.2 mm(对的), 另一个 0.32 mm —— 正好差 10 倍,当初建孔时 mm/mil 滑了一位, 改对之后旧的那 4 个没删。两个孔完全同心,距离 0 mil。 已删掉 4 个 0.32 mm 的,MHn 从 8 个回到 4 个,全是 3.2 mm / GND。

⚠️ 这两类本地几何检查一个都抓不到 —— pcb_place.py 的 check() 只看 本体框重叠和越框,不看焊盘间距,更不看钻孔。布局改完要跑一次 drc_read.py。

布线时如果 U32 扇出被 0.2 mm 间距卡住

0.5 mm 间距 QFN 出线,相邻焊盘到走线边缘算下来约 0.233 mm, 比 0.2 mm 只多 33 µm,能过但很紧。真被刷屏了不要去改全局间距 —— pcb_Drc.getRegionRules / overwriteRegionRules 可以在 U32 周围开一块局部规则区。


最新一轮:缩板 + 定位孔 + 设计规则(2026-09-03 深夜)

1. 板框 200×150 → 160×125,四角 R5 + 4 个 M3 定位孔

用户拍板的尺寸。定位孔 M3 金属化接 GND:孔 Φ3.2 / 焊盘 Φ6.0 / 禁布 Φ6.5, 孔心 (5,5)(155,5)(5,120)(155,120),与圆角同心。

⚠️ 四角定位孔和 band A 的连接器是几何上打架的,5 × 32 = 160 正好铺满板宽, CH1 / CH5 的顶角必然被连接器本体压住。解开靠两条(都写在 pcb_place.py 的 CH1 表里):

  1. J7~J11(按键出线)是立式座,本来就不需要板边 —— 挪进通道内部 (25.5, 31), 顶边只留 J2~J6 一个 12 mm 宽的右角式座;
  2. J2 摆在通道中央(local x=15,本体 x 9~21),左右各让出 9 mm。

功率链那一段(local y 3~42)一个件都没动 —— §10.2 的四条关键回路是手调出来的, 缩板不该动它们。按键驱动那 9 个件从「J2 右边一横排」改成右列 x 24/28 的纵排。

check() 里新加了定位孔禁布区这一项。孔不是元件,原来那两项检查都看不见它。

2. 又是本体框:J2 离板边差了 4 mm

FP_BODY 里 Micro-Fit 的本体我上一轮手量成 12 × 13.5 mm,渲染图上重量是 9.8 × 10.1 (对得上 43650 两位右角式的 8.6 × 10.1)。偏大 4 mm 的后果是 check() 以为贴着板边、 实际差 4 mm。已改成 (-4.95, -9.0, 4.95, 1.4) 并把 J2 挪到 local y=49 顶住 y=125。

这是 FP_BODY 第二次出错了(第一次是 U20 压出板框)。手量的本体框都要复核一遍, 偏大是安全方向(连接器缩进去),偏小是危险方向(本体悬空出框)。

3. Hub 挪到板中央 —— D± 最长砍掉一半

原来 U32 在最左边(x=31),到 CH5 要走 112 mm。挪到板中央 x=79 之后:

USB1 USB2 USB3 USB4 USB5 上游 J23
D± 长度 64 32 0 32 64 mm 16 mm

⚠️ U32 正上方就是 CH3 的负载开关块,所以去耦不能像原来那样在正下方排一横排 —— 改成上面一排 4 颗 + 左边一列 3 颗。

4. 设计规则与差分对(.claude/pcb_rules.py)

API 能做的已经做了:

  • 7 对差分对:USB1~5(Hub → 五路出线座)、UPSTREAM(J23 → Hub)、DEBUG(J22 → ESP32)。
  • 12 个功率网络指派线宽规则:VO1VO5 / VO1_PVO5_P → PWR4A;+12V / +5V → PWR12V。

API 做不了的(当时这么写的,2026-09-03 深夜已推翻,见下一节):

saveRuleConfiguration / overwriteCurrentRuleConfiguration 全部返回 false 或 undefined,什么也不做。 —— 是参数形状给错了,不是 API 不行。

5. 外层铜厚:2 oz → 1 oz(用户 2026-09-03 拍板)

理由:U32 是 0.5 mm 间距的 QFN-64(2 oz 外层最小线宽/间距 0.2 mm 起,扇出很吃力, 1 oz 是 0.102 mm),而且 90 Ω 的几何随铜厚变(2 oz 下同样 W/S 只剩 83 Ω)。 设计文档 §10.1 已改写,附了完整的载流表。

这条改动连带否掉了一个数:原来 PWR12V 定的 2.5 mm,在 1 oz 下 8 A 温升 35 °C,不能用。已改成:

规则 值 说明
PWR4A 默认 1.5 / 最小 0.3 / 最大 6.0 mm 4 A 温升 16.7 °C ✓;最小值 2026-09-03 由 1.0 降到 0.3,见设计文档 §10.1 的补注
PWR12V 默认 2.0 / 最小 0.3 / 最大 10.0 mm 只给分支(每路 1.6 A);0402 抽头引不出 1.0 mm 的线

⚠️ 12V 总干那 8 A 不许当走线画 —— 1 oz 下要 3.49 mm 才够 20 °C。 必须走灌铜:Inner2-PWR(0.5 oz)上要 ≥18 mm 宽,或顶层灌铜 ≥3.5 mm。 没把 PWR12V 的最小值设成 3.5 mm,是因为 +12V 上还有一堆进 0402/SOT-23 焊盘的 短支线,卡 3.5 会刷出几百条假 DRC 错误,规则就没人看了。 载流靠灌铜保证,不靠 DRC。

6. 还有一条要用户确认

  • 90 Ω 的 W/S 必须拿板厂叠层复核。0.28/0.18 是按嘉立创四层 1.6 mm 标准叠层 (h=0.2104 mm、εr≈4.3、1 oz 外层)的微带近似算的,叠层一变就得重算。

上一轮(2026-09-03 晚)做了什么

新增了三个脚本(都在 .claude/):

脚本 干什么
refresh_cache.py 重抓 pcb-parts.json / pcb-pads.json / sch-xy.json。改完原理图 + importChanges 之后必跑,否则 pcb_place.py 看到的还是旧元件表
shot_pcb.py 截整块 PCB(shot.py 只能截原理图页,对 PCB 会报错)
shot_pcb_region.py <out> <x0> <y0> <x1> <y1> 截 PCB 的指定 mm 区域。方向错误只有放大了才看得出来,整板图上一个 SOT-23-6 才几个像素

1. PCB 导入终于点下去了:357 → 391 元件

上一轮卡在「importChanges() 弹了确认框但点不了」(屏幕会话拿不到,CopyFromScreen 报 handle invalid)。这轮换了个办法绕过去:桥接执行的 JS 能拿到主窗口的 document, 所以弹窗可以直接用 DOM 点:

[...document.querySelectorAll('button')].filter(b => b.innerText.trim()==='应用修改')[0].click();

弹窗本身在 div[class^=lc_modal_dialog__body] 里,变更表是虚拟滚动的, DOM 里只有可见的那几行(想抓全表得滚动收集,实测容易撞 30 s 桥接超时,没必要)。

结果 357 → 391(新增 39、删掉 5 个 USB-A)。上一轮预估的 386 少算了几个。

做了一次逐位号对账(.claude/refresh_cache.py 顺带的能力): 原理图进 PCB 的 391 个 ↔ PCB 的 391 个,重号 0 / 漏 0 / 多余 0, C801~C806 正确地没进来。

2. 【C】带按板载 Hub 重排(.claude/pcb_place.py)

USB-A 腾出来的 75 mm 板边给了 Hub。现在是:

x  2..28  【C1】12V→5V(U30 + L3 + Cin/Cout)—— 靠左板边,离 J1 的 12V 近
x 30..62  【C2】板载 Hub:J23 USB-C 在下板边 / U32 QFN-64 / Y1 / U31 3V3 LDO
x 64..198 【C3】五路 TPS22918 + J17~J21 出线,节距 27 mm

D± 全程留在这一带里,不纵穿五路 Buck 的电感与 SW 铜箔(§15.3)。 Hub 那 27 个件全部手排:仿射映射会把 strap 电阻甩进 U32 与晶振之间那条 1.8 mm 的缝里,而两个落在同一点的器件在推挤迭代里是对称的、永远分不开 (R719/R720 就卡在那儿,relax() 跑 3000 轮也出不来)。

3. 截图看出来的四个真错误(几何检查全都查不到)

# 错误 为什么本地检查抓不到
1 U30 的 Cin 摆在了封装的另一侧,热回路绕整个封装 检查只管重叠/越框,不管「哪个焊盘挨着哪个」
2 五路负载开关的四个卫星件全部左右镜像(CIN/ON 该在右、COUT/CT 该在左) 同上,而且五路一起错
3 J22 调试口开口朝板内 封装本体外形 API 取不到,box() 用的是对称框
4 U20 ESP32 模组整块压出右板框 2.7 mm 同上 —— 模组原点在焊盘形心,天线端往外还有 7 mm

根因是同一个:rot=0 时 SOT-23-6 的 1/2/3 在右列、4/5/6 在左列, 和画原理图时的直觉相反。现在是照着焊盘坐标读回来写死在注释里的,不是猜的:

U30 / U24~U28 (SOT-23-6, rot=0)
  右列 自下而上  pin1(-0.94) / pin2(0) / pin3(+0.96)
  左列 自下而上  pin6(-0.94) / pin5(0) / pin4(+0.96)

3 和 4 已经在 FP_BODY 里补了两条手量的不对称本体框(USB-C、ESP32 模组), 以后再动这两个器件,check() 就能自己拦住了。

读回焊盘框有个坑:getState_Pad() 给的 w/h 是未旋转的, 所以对 rot≠0 的元件,我那段「px−w/2」算出来的框是错的(J22 的 x/y 跨度和 同封装的 J23 对不上就是这么发现的)。要拿准确的框,得先把元件转回 rot=0 读一遍再转回去。 pcb_place.py 的 FP_PAD 只收 rot==0 的实例,正是为了避开这一条。

4. P4B-HUB 的压字

6 行注释横穿 J23 的符号(几何验证不看文字)。注释挪到 U32 右下的空白带 (sch (460, 210~300))—— 一次挪过头压到标题栏上,又上移了 30。

每次重画 P4B,importChanges 都会报 4 行 J23/R717/R718 修改网络: CC1/CC2 是没有旗/端口的页内两脚网络,w('CC1', …) 那个名字不会落到网表上, 每次重画都重新分配一个 $15Nxxx。是噪声不是错误,已按提示应用。 想根治就给这两条网络各加一对端口。


前四轮做了什么(都在 2026-09-03)

0. P5-HARNESS(新增,原理图 10 页画齐了)

线束页,§20。四个分区:【A】三条线束的板端↔手机端连接图、【B】手机侧电容组(真电路)、 【C】五路对照表、【D】五条红字警告。除【B】外全是非电气图元 (矩形 / 折线 / 富文本),不参与网表。模型在 .claude/p5_harness.py。

手机侧电容组 C801~C806 是离板件:addIntoBom=false / addIntoPcb=false, 读回确认过。只画了 CH1 一套,CH2~CH5 位号见页上的【C】表。

顺手补了一个真缺口:VOn_P(Rs01 之后 → J.1 / Q*02 / INA226 的 VIN−、VBUS) 原来是页内一段没名字的网络,P5 的电容组挂不上去。已在五页各加一个 VOn_P 端口((960,745),dir='OUT',从 J2 那根横线上引 50 长支线), 五页重推 + 六项验证全过。

⚠️ VO2_PVO5_P 全工程只有 1 个实例(P5 只画了 CH1 一套电容组), 这不是漏接。.claude/xpage.py 新增 NAMING_ONLY 集合把它们列为例外, 哪天补画 CH2CH5 的电容组就删掉。


前三轮

1. 五路复制(新增 P2B~P2E 四页)

用 dmt_Schematic.copySchematicPage 从 P2 复制,再由 .claude/p2gen.py(模型)+ .claude/p2push.py(下发)按通道号改写位号 / 网络名 / 地址跳线。

复制时发现母版上有两个真问题,五页一起修了:

  1. K1_VUP 端口其实没连上。 它的引脚坐标和 R122.1 完全重合, 靠一段零长度导线"连着"。几何验证在旧 P2 上不报,是因为那段零长度线 恰好落在引脚上;真按这个网表打样,那一路音量+ 很可能是断的。 三个按键端口 + VDAC1 现在统一改走向上 40 的短支线,末端才放端口。
  2. 两处标签压字:VDAC1 与 K1_PWR 的标签面对面撞在一起(渲染成 VDACK1_PWR); J7 的地旗文字压在 Q105 的位号上(渲染成 G5)5)。

⚠️ 所以 CH1 也重画了一遍(元件没动,只重画了导线 / 旗 / 端口 / 文字)。 用户之前确认过的是旧版 P2,这一页要重新看一眼。

2. INA226 地址按布线难度重排(§8.2)

INA226 的 A1/A0 各能接 GND / VS / SDA / SCL,但只用 GND 和 VS 只能凑出 4 个地址 (0x40/0x41/0x44/0x45),全板要 6 个,必然有两片得把地址脚搭到 I²C 线上。 原来的顺排(0x40~0x44)会让 CH3 接 SDA、CH4 接 SCL,而 A0 脚上下正好夹着 Alert 和 SDA 两根横线,往 SCL 拉必然跨线。

CH1 CH2 CH3 CH4 CH5 U13(输入侧)
A1 GND GND VS VS GND GND
A0 GND VS GND VS SDA SCL
地址 0x40 0x41 0x44 0x45 0x42 0x43

这样五个通道里只有 CH5 需要搭 SDA(0 交叉),剩下那个 SCL 落到 U13 头上—— 它画在 P1 的空白区,布线自由。 代价:通道号不再等于地址低位,固件里要查表,不能算。

3. P1 补输入侧检流(§19)

U13 INA226 + Rs6 5mΩ + C11,量 12V 母线,地址 0x43(A1=GND / A0=SCL)。 体电容在 Rs6 上游,所以量到的是负载电流;满板约 8A → 40mV(满量程 81.92mV)。 U13 转了 90°,VIN+/VIN−/VBUS/GND/VS+ 五个脚朝上正对分流电阻,I²C 朝下走空白区。

又挖出一个同类真错误:P1 上 BLD_REF/BLD_A 两个端口的引脚坐标和 U29(TL431) 的 REF / 阴极完全重合,靠零长度导线"连着"。已挪开 40 并补真导线。 这类错误几何验证抓不到(零长度段会让"引脚落在导线上"通过),只有重画那一页才暴露—— 规律已写进 skill §8a。

4. 之前:P4-USB(§18)

五路 TPS22918 负载开关,一路一列,D±/GND 直通、只切 VBUS,全页 0 处交叉。 ON 下拉 / CT 摆率 100pF / QOD 短接 VOUT / CIN>COUT 四条按 datasheet 定稿。

⚠️ TPS22918 的引脚号:datasheet 引脚图的文字被排版拆开后读起来像 1 VOUT / 2 QOD / …,和 Pin Functions 表相反。按文字坐标还原后确认表是对的: 1 VIN / 2 GND / 3 ON / 4 CT / 5 QOD / 6 VOUT。接反了图上看不出来。

✅ 板载 USB Hub(2026-09-03 做完原理图部分)

外部只留一根 USB 线。用户拍板:USB2517I 单片 7 口 + USB-C 上游口。 硬件配置逐条对 datasheet 复核,写在设计文档 §21;5V 轨与 LDO 见 §9.4 / §9.5。

最终方案

项 结论
Hub U32 = USB2517I-JZX(C478081,QFN-64);口 1~5 给手机,口 6/7 用 10k 上拉禁用
配置 CFG_SEL[2:0]=000 → 内部默认配置 + strap 使能;寄存器 06h 默认 9Bh、bit7=1 = 自供电
上游口 J23 USB-C(UFP),CC1/CC2 各 5.1k 下拉,不做 PD
下行 VBUS 板上 5V 轨(U30 = 第二片 LMR51430,12V→4.98V),经 U24~U28 由 ESP32 通断
Hub 的 3.3V U31 AMS1117-3.3 专供(Hub 满配约 420mA,挂不上 U19 那条 0.5A 控制轨)
Hub 复位 U22.Q7(74HC595 唯一那个空着的输出)→ HUB_RST,R716 10k 上拉
删除 J12~J16 五个 USB-A 母座

三个 datasheet 陷阱(都已避开,细节在 §21)

  1. 别下错手册:Microchip 站上 00001692C.pdf 是 USB251xB 系列(另一世代)。 我们这颗要看 2517.pdf(SMSC USB2517 Rev 2.8)。
  2. 引脚图和引脚表打架(RBIAS 图上读作 64、表里是 63)。 按坐标还原引脚图后确认表是对的 —— 和 §18 的 TPS22918 同一个套路。 库里的符号也与表一致,三方对上了才动笔。
  3. 必须是自供电。声明成总线供电,主机按每口 100mA 做功率预算, 而手机描述符要 500mA → 主机拒绝配置,手机根本枚举不上。 靠 CFG_SEL=000 走内部默认值拿到自供电,不需要 EEPROM。

验证结果

页 结果
P4B-HUB 27 元件 / 112 导线段,六项全过,0 处交叉,32 个脚在 NC 清单
P1B-5V 12 元件 / 44 导线段,六项全过,2 处合法交叉(+12V×SW5V、SW5V×CB5V)
P4-USB 30 元件 / 180 导线段,六项全过,0 交叉
P3B-EXPAND 重推(加 HUB_RST),六项全过
跨页对账 0 个漏接(12 页);+5V/+3V3_HUB/HUB_RST/USB1~5_D± 全部两端对上

顺手修了一个遗留真错误:P3B 的 C611(DAC 的 CAP 旁路 2.2µF)在页面上被挪动过, 位置与 p3b_nets.py 的接线对不上,两个脚都是悬空的。上一轮没重跑 P3B 的验证所以没发现。 已按 skill §8d 用 rot=270 摆回横放(这颗 0805 传 rot=0 反而是竖的)。

这一页为什么满是网络端口

U32 是 65 脚 QFN,33 脚在左、32 脚在右,脚间距只有 10。外围器件直接连脚必然打结。 做法:每个用到的脚只引一小段 stub,末端放网络端口当页内标号, 器件按功能分块摆在四周,块间靠端口名对上。 netport 约 10 单位高,在 10 的脚间距上排一列不重叠(skill §8b-2);netflag 有 20 高,放不下。 J23 的 USB-C 正反脚也用端口重名并联,省掉了 Type-C 固有的一堆交叉(P3 的 J22 当年是靠绕线解的)。

这三件收尾都已经做完(2026-09-03 晚)

  • PCB 导入已应用(357 → 391),逐位号对账通过。
  • 【C】带已按 Hub 重排并下发,391 个全部在框内、无重叠。
  • 三张空白截图已重截,都是好的。

⭐⭐ 第二件事:主板载板(2026-09-03 用户提出,尚未动手)

需求原话:「底板是现在设计的电源板,另外做一块板子,按照主板定位孔设计, 将 5 块主板固定在这块板子上,固定主板的板子直接对插到我们现在设计的这块板子」。

即两块板叠起来:

        ┌─────────────────────────────────────┐
        │ 载板:按小米8 主板定位孔打孔,装 5 块主板 │
        │  每台的 P+/P− 焊盘 + §6.3 电容组       │
        │  侧键触点(顶针?)+ USB              │
        └───────────┬─────────────────────────┘
                    │ 板对板对插(电源 4A×5 + 信号约 30 根)
        ┌───────────┴─────────────────────────┐
        │ 底板:现在这块 200×150 五路电源板      │
        └─────────────────────────────────────┘

这是好事,但先说清楚它推翻了什么

推翻的:

  • §15.4 的线束基本消失 —— 现在图上的 J2J6(Micro-Fit)/ J7J11 / J17~J21 共 15 个连接器,会被板对板连接器取代。
  • P5-HARNESS 那页要重写(它现在画的是线束)。
  • PCB 布局的连接器分边决策作废。我 2026-09-03 排的「上边=电源+按键、 下边=USB 子系统」是为了线束走向和 D± 不穿功率区;改成板对板之后, 接口位置由机械叠层决定,不是由「线从哪出」决定。【A】【C】两带都要重排。

变好的(值得高兴的一条):

  • §6「最容易翻车的地方」大幅缓解。 原方案是 20 cm 线缆 ≈ 300 nH, 射频突发 2 A/µs 时压降 0.6 V。改成板对板后回路电感掉到大约 30~50 nH 量级, 同样 di/dt 下压降约 0.1 V。粗算 L=50nH / C=2200µF: f_res 从 6.2 kHz 升到约 15 kHz,Z0 从 11.7 mΩ 降到约 4.8 mΩ, Q = 4.8/(走线+触点 R + ESR 60mΩ) ≈ 0.07,比原来还过阻尼。 → §6.1 / §6.2 的数要重算;C801 用普通铝电解做阻尼这条依然成立(更保险了)。
  • §6.4「大电容必须在排线端」自动满足 —— 电容组直接放在载板上 P+/P− 焊盘旁边, 离得比原来更近,而且是贴片工艺,不用手焊线束。

✅ 头号阻塞项已解除(2026-09-04):机械数据一直就在我们手里

下面这段旧结论是错的,留着当教训:曾经断定 「Xiaomi_Mi_8_Schematic.pdf 是电气原理图,不含任何机械尺寸,只能实测」。

实际上那份 PDF 共 46 页,前 2 页是正反面位号图(含完整板框轮廓 + 全部位号), 后 44 页才是原理图。当初只按「原理图」去读,get_text() 一看全是位号就跳过了, 从没渲染出来看一眼 —— 一张图看 5 秒就能推翻的结论,靠翻文字翻了一整轮。

已经从中反推出:比例尺 17.0 pt/mm、主板 ≈ 45.0 × 66.8 mm、 J600 / J1401 / TP1204 / TP1205 / J601~J604 的坐标与所在面。方法与数据见设计文档 §22.3。

⚠️ 仍需用户做一件事:用卡尺核一次主板长宽。45 mm 对 74.8 mm 的机身偏窄, 怕比例尺有系统性偏差;后面所有孔位都按这个尺子换算。

六个待定项

  1. 电池那一路怎么接。 §13.5 已经论证过:必须保留原厂 BM3E 排线 (立创只有母座 5050040812,没有对插的公座;而且 BATT_ID 可能是 1-wire 认证 IC,复制不了)。所以载板上做 P+/P− 焊盘, 排线一头焊在载板、另一头插回主板 J600。 ⚠️ §13.5 那条「别焊错边」的坑原样保留 —— 只是从焊线束变成焊到载板焊盘。
  2. 侧键触点怎么接。 J601~J604 是主板上的弹片触点。 载板上用**弹簧顶针(pogo pin)**顶上去最干净,但要求机械对位准 (定位孔正好提供这个基准)。前提是主板装上去时触点那一面朝着载板 —— 要先确认第 2 项。
  3. USB 怎么从尾插小板引到载板。 USB-C 母座在原厂尾插小板上, 小板靠 FPC 插主板 J1401。最省事:一根短 USB 线从小板 USB-C 到载板上的连接器。 要不要把尾插小板也固定在载板上,一起定。
  4. 板对板连接器选型 —— 这条最难。 每台 2 根电源(4A)+ 4 根按键 + 4 根 USB = 10 根, 五台共 50 根,其中电源合计要过 20 A。单个信号 BTB 扛不住。 现实做法是电源与信号分开:电源用大电流连接器或多针并联, 信号用普通 BTB;D± 那 5 对还要满足 §15.3 的 90Ω 差分。
  5. 叠层间距(立柱高度)。 底板上现在有一批高个子: C2/C3 470µF 直插电解(约 1012 mm)、R9 水泥电阻(约 9 mm)、 ~~J12J16 USB-A 母座~~(Hub 改造后已全部删除,板上最高的连接器变成 J23 USB-C,约 3.2 mm)、 U20 ESP32 模组。要么立柱够高,要么把这些挪到不被载板压住的区域。
  6. 和「板载 USB Hub」那件事的关系。 两件事都动 USB 接口,建议一起设计: Hub 改造让外部只剩一根线,载板改造让内部不用线束 —— 合起来才是 「一根 USB + 一根 12V 进来,五台手机插上去就行」。

建议的推进顺序

  1. 先量主板(上表 1~4)。拿不到机械数据,载板一步都走不了。
  2. 量的同时可以并行做 Hub 改造(那件事不依赖机械数据)。
  3. 机械数据到手后:定叠层与板对板连接器 → 画载板外形与孔位 → 反过来改底板的接口区布局。
  4. 底板的功率部分(P1、五路 Buck、控制)不受影响,不用重画。 受影响的只有「接口怎么出板」这一层。

改完要同步的地方

位置 改什么
设计文档 §6.1 / §6.2 线缆电感 300nH → 板对板约 50nH,压降与 Q 重算
设计文档 §6.3 / §6.4 电容组从「装在排线端」改成「装在载板上 P+/P− 焊盘旁」
设计文档 §13.3 侧键从飞线改成顶针(如果定了顶针方案)
设计文档 §15.4 线束表基本作废,改成板对板接口定义表
设计文档 新增一节 载板:机械、孔位、叠层、板对板连接器
原理图 P5-HARNESS 从「线束页」改成「载板接口页」
原理图 P2 系列 / P4 J2J6 / J7J11 / J17~J21 换成板对板连接器的一部分
PCB 接口区重排;载板是第二块 PCB(EasyEDA 里新建一个 Board)

PCB(2026-09-03 转板完成)

PCB1(uuid 7e9415a16a909c51)挂在 Board1 下,关联原理图 c63aa0a04d951314。

项 结果
铜层 pcb_Layer.setTheNumberOfCopperLayers(4) → 顶层 / Inner1-GND / Inner2-PWR / 底层
元件 357 个,与原理图逐个对上,无重号
网络 227 个
日志 致命错误 0 / 错误 0 / 警告 0
P5 的 C801~C806 ✅ 没有进来(addIntoPcb=false 生效)

转板要知道的三件事

  1. pcb_Document.importChanges() 返回 true 只表示「弹窗开了」,不代表导入完成。 它会弹一个「确认导入信息」对话框列出全部变更,要点右下角的 「应用修改」 才真的写进去。 API 里没有确认这一步的接口,canvas 截图也拍不到弹窗(截图只有画布)—— 要看弹窗得截整屏(System.Drawing 的 CopyFromScreen)。
  2. 导入前先 sch_Document.save(),否则 API 改的内容可能还没落盘。
  3. 「元件 $Ixx 唯一 ID 与其他元件重复」那 578 条提示是良性的。 来源是 P2B~P2E 由 copySchematicPage 复制而来、内部唯一 ID 撞车, EasyEDA 生成网表时自动重分配。担心的是「每次重分配结果不同 → 以后改原理图会把布局冲掉」, 已实测排除:第二次 importChanges 日志是「无变更,原理图与 PCB 已经匹配!」, 说明重分配是确定性的,后续是增量更新。

PCB 布局(2026-09-03;第一遍见下,第二遍见开头「本轮」那节)

板框 200 × 150 mm,原点在左下角,Y 向上,API 单位 mil(sys_Unit.getFrontendDataUnit())。 模型在 .claude/pcb_place.py(--push 下发)。 截图:PCB-layout-v1.png(第一遍,357 元件)/ PCB-layout-v2.png(当前,391 元件)。

分区(2026-09-03 用户拍板板框尺寸 + 连接器边)

y=150 ── 上边:每路 J2~J6 电源 Micro-Fit + J7~J11 按键 4P(去手机)
  【A】五路功率级   y 86~150,每路 40 mm 宽,同方向横排(§10.3)
y= 86 ──
  【B】P1 输入(左 x0~96) │ P3/P3B 控制(右 x96~200)   y 42~86
y= 42 ──
  【C】USB 子系统   y 0~42(**已按板载 Hub 重排**,见开头「本轮」第 2 节)
y=  0 ── 下边:唯一的外部 USB 口 J23 在这条边上

左短边 = J1 12V 接线端子;右短边 = J22 调试 USB-C + U20 ESP32(天线朝板边)。

⚠️ 与用户选择的一处偏离(已知,等确认)

用户选的是「手机的三根线束全部同点出板」。但 J17~J21 放上边的话, D± 必须纵穿五路 Buck 的电感与 SW 铜箔,这是 §15.3 明令禁止的 (比线长本身严重得多)。所以实际排的是:

  • 上边 = 每路的电源 + 按键(两根线束同点出板)
  • 下边 = 整个 USB 子系统,J12(上游)与 J17(去手机)相距约 19 mm,D± 全程留在 USB 带里

要恢复成「三根同点」就把 J17~J21 挪到上边,代价是 D± 穿功率区。

怎么排的

  • 五路功率级是显式手排(pcb_place.py 的 CH1 表),自下而上: 反馈/补偿 → Buck(Cin 贴 VIN/GND)→ 电感/输出 → eFuse → 分流+INA226(Kelvin)→ 连接器。 按键驱动放右列,远离 SW。CH1 排好后按 40 mm 节距平移复制五份(§10.3 同方向)。
  • P1 / P3 / P3B 按原理图坐标仿射映射进各自分区,再跑分离迭代消重叠—— 原理图上的功能分组画图时就想清楚了,直接当粗排初值。
  • 本地检查:重叠 0、越框 0。

踩到的三个坑

  1. 封装本体不一定以原点对称。 USB-A(KH-AF90ZJ)本体从原点往 −Y 伸 10.7 mm、 往 +Y 只有 1 mm。按对称尺寸算,第一次放 y=8 时本体伸出板框 2.7 mm, 而本地检查(用对称框)查不出来,是截图看出来的。已改到 y=11.5。
  2. API 取不到封装的本体外形。 pcb_PrimitiveLine.getAll() 只返回板框那 4 条, 元件内部的丝印 / 外形图元不外露(和原理图里 sch_PrimitivePin.getAll() 对元件实例 返回空是同一个套路)。能读回来的只有焊盘: component.getState_Pads() 给的是 {primitiveId, net, padNumber}, 要用 pcb_PrimitiveComponent.getAllPinsByPrimitiveId(id) 才拿得到真实坐标, 焊盘尺寸在 pad.getState_Pad() = ["RECT", w, h, r]。 所以 pcb_place.py 的占位框 = 标称本体框 ∪ 真实焊盘框, 板边连接器另有一张手量的 FP_BODY。「本体伸出板框」只能靠截图人眼看。
  3. zoomToRegion 的参数是 (left, right, top, bottom),不是 (x0,y0,x1,y1)。 传错了不报错,只是没缩放。

下一步(按顺序)

  1. 人眼确认 PCB-layout-v4.png,尤其下面「等确认」那几条。
  2. 设计规则 已全部下发(pcb_rules.py --push,--check 七项全 OK)。 Hub 到五路的 D± 最长约 140 mm(U32 在 x=45、CH5 在 x=185), USB 2.0 HS 走得了,但必须是受控阻抗,且五对要等长成对。
  3. 精调关键回路(§10.2):五路 Buck 的 Cin 热回路 / SW 铜箔 / FB 走线 / Kelvin 取样, 调好再重新复制五份;U30 那一路的 SW 只能走 x 13.9~15.4 那条 1.5 mm 的缝 (VIN 与 GND 夹着 SW 是这个封装的固有形状,绕不开)。
  4. U31(SOT-223,耗散约 0.71 W)与 U32 的散热铺铜。
  5. 灌铜(Inner1-GND 整片不许割断;Inner2-PWR 走 12V + 5V + 五路输出)→ 布线 → DRC。

等用户确认的事(几何验证证明不了)

  • 板边连接器的朝向。都是从渲染图上量出来的推断,要对着连接器图纸核一眼—— 接反了板子做出来插不进去:
    • J2~J6 Molex 43650 右角式,转 180° 让开口朝上板边(未改动)。
    • J23 / J22 USB-C:焊盘在 +Y、壳体往 −Y 伸 5.2 mm,也就是 rot=0 时开口朝 −Y。 J23 rot=0 开口朝下板边;J22 这轮从 270 改成了 90(原来开口朝板内)。
    • J1 KF128:丝印上两个大箭头 = 进线方向,rot=0 时朝 +Y(板内), 这轮改成了 90,进线朝左板边。箭头到底是不是进线方向,请核一眼。
    • U20 ESP32 模组:这轮从 x=186 挪到 183.2,让整块模组(含天线端)不出框。 如果你本来就想让天线伸出板外,那要挪回去并把板框改成带缺口的形状。
  • 丝印位号大面积压字(EasyEDA 自动生成的),布线前要单独跑一遍丝印整理。

打样前的阻塞项(都不需要电源板,可并行做)

  1. 排线旁路验证(设计文档 §13.7)—— 确认焊在 P+/P− 而非 B+/B−。 判定性测试是第 3 步:可调电源升到 4.40V,BTB 座电压必须跟随且不断开。
  2. 侧键触点定位(§13.3)—— 万用表 5 分钟,拍照存档。带电验证那一步是安全闸门。
  3. 环路补偿实测(§9.2)—— 图上按 fco≈18kHz 放了 R106/C110/C111,三个位置都留了焊盘。

已知遗留

  • 上游用的是 USB-A 母座(§15.1 定的),所以板到 Hub 之间是一根 A 公 - A 公线。 这不是标准 USB 用法;好处是物理上插不了充电头(VBUS 一旦被抬到 9V, TPS22918 的绝对最大 6V 会当场炸)。要换 USB-B / Type-C 就趁现在,改 5 个连接器而已。
  • C7(P1 上 U19 的输入去耦)只有 2.2 µF,datasheet 要求高频去耦 ≥4.7 µF。 建议改成 10 µF/50V 0805(C440198,BOM 里已有)。不改也能工作, 但 PCB 上必须把 C7 贴到引脚旁。§9.3
  • 电感 Isat 略低于目标:SPM6530T-4R7M 是 Isat 6.2A,原定 ≥7A。 正常峰值 4.61A、eFuse 限流工况 5.08A 都够;但 Buck 高侧限流 datasheet 是 8/11/14A,打嗝保护触发时电感会短暂深度饱和。首板要确认发热与饱和表现。
  • eFuse 最坏容差下可能到 4.519V 才跳,超过 1S 上限 4.45V 约 60mV。已接受,理由见 §5。
  • PCA9555 的 4 个备用脚直接接地(不串电阻),依赖"上电默认为输入"。 固件严禁把 Port0[7] / Port1[7:5] 配成输出。
  • U29 泄放的 R9 是突发额定(10Ω/5W,13.3V 下 17.7W), 持续故障靠固件监控输入电压兜底:>13.0V 关全部 VBUS,>13.8V 关全部 EFUSE_EN。
  • 交叉点:P2 系列各 6 处、P3 有 1 处(USB-C 的 D± 交叉,Type-C 固有)、P4 是 0 处。 几何验证确认互不相连。
  • 手机侧 C802/C803 用了 MLCC 代替聚合物(§6.3)。立创基础库里没有 100µF/6.3V 聚合物, 改用 100µF/6.3V X5R 1206(C15008)。3.9V 偏压下每颗只剩约 40 µF, 中频段储能从 200 µF 掉到约 80 µF。C801 的 2200 µF 才是主力,估计影响不大, 但这是个待用户拍板的偏离:要严格照原方案就得单独采购聚合物。
  • C801 是选型陷阱:必须买普通铝电解(C49256788,ESR 数十 mΩ)。 同尺寸的聚合物 C53446724(ESR 12 mΩ)会让线缆 LC 的 Q 从 0.19 回到 4.5 而振铃。 这一条已写成 P5 页上的红字。

沉淀下来的工具(.claude/ 下)

文件 用途
eda.py 跑任意 JS 到桥接:python .claude/eda.py x.js。HTTP 500 会带出中文错误原文
place.py / place4.py 批量放置元件;place4.py 支持 mirror(位置表 p4_pos.py)
fetch_pins.py 导出某页的真实引脚坐标成 pins.json
build.py 本地几何检查 + 分批推送(--push),用于 P3/P3B/P4
p2gen.py 五路母版模型:几何来自 p2-ch1.json,按通道号改位号 / 网络名 / 地址跳线
p2push.py python .claude/p2push.py <n> <pageUuid> --temp|--final|--push
p2-ch1.json CH1 页的几何快照(导线 / 旗 / 元件),是五页的唯一事实来源
shot.py 截图某页存 PNG —— 画完先自己看一眼,能省掉大部分返工
xpage.py 跨页网络对账,找"全工程只有一个实例"的网络
drc_read.py 跑 DRC 并把结果读回来(DOM 抓 DRC 面板)。布局一改就跑,本地几何检查抓不到焊盘/钻孔间距
p3_nets.py / p3b_nets.py / p4_nets.py 三页的网表源码(改图改这里,再跑 build.py)
p4b_nets.py + p4b_pos.py P4B-HUB:USB2517I 那一页的位置表与网表
p1b_nets.py P1B-5V:12V→5V + Hub 专用 LDO
p1_add.py P1 的增量:导线/旗/文字整页重画(底稿 p1-base.json),端口只增不重建
p5_harness.py P5 整页模型 + 下发(含文字越界 / 压标题栏的本地自检)
pcb_place.py PCB 布局模型 + 下发(分区表 + CH1 手排 + 仿射映射 + 重叠/越框自检)
pcb-parts.json PCB 侧 357 个元件的位号 / 封装 / 坐标(--push 前要重新读)
pcb-pads.json 每个元件的真实焊盘外框(封装不对称时靠它,见上面的坑 1)
sch-xy.json 每页元件的原理图坐标,仿射映射的输入
eda-parts.json 器件 + 工程/图纸/全部 10 页的 UUID

⚠️ P2 早期的 p2-nets.py / p2-pos.py 等四个文件已和页面对不上(位号还是改号前的 C1/R1,端口位置也是旧的),已挪到 .claude/obsolete/,别 import。 五路的唯一事实来源是 p2-ch1.json + p2gen.py。

位号改名要分两趟

copySchematicPage 会把复制页的位号自动改成不撞车的(C11~C24、U2/U3/U4……), 而这些恰好就是别的通道要用的号。一趟改名会撞号被 EasyEDA 悄悄改掉, 所以 p2push.py 先把所有页都改成 Z<n>T<i> 临时名(--temp), 再统一改成目标名(--final)。

新会话开工检查清单

  1. 当前状态:
    • ✅ 板载 USB Hub 的原理图部分做完了(12 页,跨页 0 漏接)。 还欠:把新器件导进 PCB + 【C】带重排 + 人眼看三张新图的截图。
    • ⭐⭐ 主板载板(5 块主板固定在一块载板上,对插到底板)—— 卡在「没有小米8 主板的机械数据」,只能等用户实测。
  2. 桥接在不在:/c/Python314/python .claude/eda.py 随便跑一段 JS;不通就按下面「环境」起服务。
  3. 页面 UUID 全在 .claude/eda-parts.json 的 pages 里(12 页都有),不用重新枚举。
  4. 动手前读 easyeda-sch-geometry skill,尤其 §8a(零长度导线)、§8b(端口 dir 决定标签朝向)、 §2(旗的 rot 语义,四种组合都实测过了)。
  5. 还欠人眼确认的页:P1-INPUT、P2-CHANNEL、P3-CONTROL、P3B-EXPAND、P5-HARNESS, 以及这轮新改/新增的 P4-USB、P4B-HUB、P1B-5V (P2B~P2E 与 P2-CHANNEL 完全同构,看一页即可)。 ⚠️ 截图 API 有时会抓到另一个窗口(返回的 windowId 会变),拍出来只有元件没有导线。 遇到就换个时机重截,或直接在客户端里看 —— 先用 API 读回图元数量确认数据在不在,再判断。
  6. 改完任何一页,跑 verify_sch.py(当前页)+ xpage.py(全工程),两个都要干净。

环境

  • EasyEDA 桥接:node C:/Users/35138/.claude/skills/easyeda-api/scripts/bridge-server.mjs 后台运行,端口 49620~49629 自动选。客户端里要装 run-api-gateway.eext 扩展。
  • Python:/c/Python314/python(Windows 路径写 C:\Python314\python.exe)。 已装 pymupdf —— datasheet 可以直接下下来用 fitz 抽文字(get_text("words") 带坐标,用来还原被排版拆散的引脚图,P4 就是靠这个确认 TPS22918 引脚号的)。
  • 验证:/c/Python314/python .claude/skills/easyeda-sch-geometry/scripts/verify_sch.py --nets (跑的是当前打开的那一页,所以要先 openDocument)。
  • 清空上下文后,先看 CLAUDE.md,再看这份,然后读 easyeda-sch-geometry skill。