文件
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

2806 行
191 KiB
Markdown
原始文件 Blame 文件历史

此文件含有不可见的 Unicode 字符
此文件含有人类无法区分的不可见的 Unicode 字符,但可以由计算机进行不同的处理。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 进度与交接
更新: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.5~9 那条 GND 底填(**1312.8 mm²**)
在 Inner2 上是孤立的,一个落脚点都没有 —— 它原本靠 x 115~116 那条 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.5~5.3(3.8 mm → 8.4 A),
U13 南边变成一整片 x 5.5~14 / 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.33~0.39 mm,**一点余量都没有**。
唯一的空地在 Rs101 顶到通道北界 y 59 之间的 **1.27 mm**
⇒ 把 `U8 + Rs101` 整体北移 **0.7~0.8 mm**(母版改一处 = 五路一起改)。
⚠️ 这会连带作废 **扇出 + 铺铜 + 五路功率级布线**(`U*.9 → Rs*01.1` 的 Kelvin 线也在母版里)。
**C 组的解**:给那两三个 GND 扇出孔在 `pcb_fanout._CH_FORCE_LOCAL` 里钉一个偏 0.1~0.2 mm 的位置。
#### ⚠️ 另外 95 条是**拥挤**,要拆线重布才能再往前
失败集中在**控制块(U21~U23,x 75~107 / 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.26~0.30 mm(U1~U5 / 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 接下来的顺序
0. **让用户人眼确认摆位 v9 + 功率级布线**(截图已出,见上面「一句话现状」)。
不算硬阻塞,但这一轮动过五路的摆位,值得看一眼。
1. ~~设计规则~~ ✅ §26.7 2. ~~定铺铜几何~~ ✅ §26.8
3. ~~扇出~~ ✅ **§26.9 / §26.13** 4. ~~铺铜~~ ✅ **§26.10,已下发并回读复核**
5. ~~8 A 输入链顶层宽铜~~ ✅ **§26.11,已下发并回读复核**
6. ~~五路功率级精调~~ ✅ **§26.13,已下发并回读复核**
7. ~~写下板信号布线器~~ ✅ **`pcb1_route_sig.py` 已建好并跑通 75%(第二十轮)**,
`pcb1_route_pwr.py` 那 210 段一根没动(这个布线器只加不拆,删除只按坐标删自己画过的)。
8. ~~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`。
9. ⬅️ **下一步:给 `pcb1_route_sig.py` 加拆线重布**(rip-up & reroute),收掉剩下的 91 条。
⚠️ 直接再跑一遍 `pcb1_route_sig.py --refresh --push` 也会有进展(它读板上的铜、
只补还缺的),但一遍过的策略已经到顶了 —— 真要清零得能拆别人的线。
⚠️ **北左角的顶层现在被 8 A 宽铜占了大半**(x 1.5~28.9 / y 68.6~93)。
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.5~72, 9~34) + 中桥 (61.5~72, 34~88),⌐ 形 |
| Inner2 | `+12V` | `12V-SE+RC` | 南东块 (116~159.5, 9~34) + 右通道 (153.5~159.5, 34~88),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 58~118 / y 85~125 有一块约 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 个。
<details><summary>当初为什么非挪不可(留作记录)</summary>
| 方向 | 挡住的东西 | 余量 |
|---|---|---|
| 南 | `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 外),中间横着这两条竖段。
</details>
### (已作废)等用户拍板: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 个**,
`C8n2`~`C8n6` 仍是各 1 个 —— 全按 3 个来会往五块岛里再钻 50 个反焊盘,
最窄断面(下限 7.0)扛不住。这五颗只承担高频纹波,单孔 1~1.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 45~65 / v 1.4~4.4(J1401 正下方);`TAIL_LANE` u 44.6~62.6 / v 4.4~38.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 器件,新增 J24~J30 / U33。J24 中心 (32, 62.5)、本体 53.34×5.08 会压到 `C2`~`C6`,必须挪;`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.46~59.71),
而 `pcb2_place.BAND['j7']` 是 **u = 57.5**。
不致命(软板本来就有余量、而且照片上原厂排线自己就是歪的),
但**不能直接改成 53.58** —— 那会和同一行的 `j6`(EDL 座,占 u 39.8~48.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.46~2.30 / 配置电阻 1.4~3.2 /
晶振 4.95~5.76(**是通道的代价,不是疏忽**,负载电容贴端子 1.5~1.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 放 CH1~3、B 放 CH4~5 | `.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 / VO1~5_P / VBUS1~5」
十二块)——理由是内层是 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.25~80.75,`C831/C841` 在 y 54.41~66.91,
两个新中跨 M3 的 Ø6 焊盘在 (76.6, 80.1) / (148.1, 80.1),
外加 J31/J32 的 **80 个通孔焊盘**(坐标已定稿:J31 x 37.869~86.129 / J32 x 133.869~182.129,
两行 y = 66.23 / 68.77,见 `pcb2-header-pads.json`)。
⚠️ 2.54 间距的通孔在内层会把铜面打成筛子(Ø1.7 焊盘 + 0.2 净距 ⇒ 相邻反焊盘之间只剩 0.44 mm 网),
电源岛的颈宽要绕开这两条带,别指望从排母底下横穿过去。
### 还欠用户的五件事
0. ~~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,会占手机上方高度)。
1. **h_up**:主板**正面**(朝上那面)最高元件高出板面多少。只影响压板高度与整机层高。
2. ✅ **排针/排母选型已定**(§22.3f):`C55020601` + `C5307344`,对插 13.0 mm。顶针已定稿。
⬜ **到货要核两件**:母座触头镀层(商城只写了「黄铜」,没写镀层)、
对插后实测板间距是否真的 13.0 mm(确认是塑料贴合、不是针先顶死)。
2b. **首件上电前量一次**:`VBUSn` 对 GND 的电阻(确认电源触点 G1/G2 没接反,§13.5)。
负载开关默认关断,所以这一步零风险、零成本。
3. **顶针首件必验**:±1 mm 公差链对 1.2~2.2 mm 的侧键触点是危险的,而且
**选定的顶针冠径只有 Ø0.9 球头、又是贴片不可调**,所以首件实测是**唯一**对策(§22.3c)。
4. **用卡尺重量那 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.0~3.5 / 冠径 Ø1.5~2 |
| 排母 `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** | ✅ **同样删了 J3~J6 / J8~J11,改挂 `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_EN1~5)+ 风扇低边驱动 + J26~J30,六项全过(截图 `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` → 排针 → 载板 U41~U45(TS5A3157DBVR)+ J61~J65(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 反编译):
```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 那条路也被堵死了)。
**正确姿势(两步,缺一不可)**:
```js
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` 可以确认
当前只有一个窗口 —— 排查「是不是打到另一个窗口去了」时先看这个,别猜。
### 抓静默失败的通用手法(这轮就是靠它破的)
页面里挂三层钩子,再触发一次,就能看见吞掉的错误:
```js
// ① 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` 逐个灌:
```js
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()`:
```js
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()` 反出来的:
```js
{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 11~151 / y 5.5~14 | 10 / 16 |
| 3V3HUB-L3 | `+3V3_HUB` | L3 | x 59~111 / y 15~31 | **21 / 21** |
| 3V3-CTRL-L3 | `+3V3` | L3 | x 95.5~150.5 / y 33.5~67 | 合计 45 / 61 |
| 3V3-CH-L3 | `+3V3` | L3 | x 5~155 / y 100.3~124 | 同上 |
都塞在 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.5~46 / y 49.5~68.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 3~14 / y 45.5~55.5 |
| VIN-FUSED-L3 | L3 | `$1N227` | x 14.6~19 / y 49.5~56.5 |
| VIN-SW-L3 | L3 | `$1N236` | L 形,x 14.4~35 / y 49~66.5 |
| 12V-L3 | L3 | `+12V` | 下带 x14.6~95 y33~48.3 + 中带 x38.5~95 + 顶带全板 y67.5~81.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.2~81.7);
VOn 底边 82.5 是因为 C107 pin1 是 rot90 的 1210,焊盘跨到 y83.07~84.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 扇出:改布局之后必须按这个顺序重跑
```bash
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 个功率网络指派线宽规则**:VO1~VO5 / VO1_P~VO5_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 点:
```js
[...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`(Rs*01 之后 → J*.1 / Q*02 / INA226 的 VIN−、VBUS)
原来是**页内一段没名字的网络**,P5 的电容组挂不上去。已在五页各加一个
`VOn_P` 端口(`(960,745)`,`dir='OUT'`,从 J2 那根横线上引 50 长支线),
五页重推 + 六项验证全过。
> ⚠️ `VO2_P`~`VO5_P` 全工程只有 1 个实例(P5 只画了 CH1 一套电容组),
> 这不是漏接。`.claude/xpage.py` 新增 `NAMING_ONLY` 集合把它们列为例外,
> 哪天补画 CH2~CH5 的电容组就删掉。
---
## 前三轮
### 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` 是 USB251x**B** 系列(另一世代)。
我们这颗要看 **`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 的线束基本消失** —— 现在图上的 J2~J6(Micro-Fit)/ J7~J11 / 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 直插电解(约 10~12 mm)、R9 水泥电阻(约 9 mm)、
~~J12~J16 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** | J2~J6 / J7~J11 / 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`)。
## 新会话开工检查清单
0. **当前状态**:
- ✅ **板载 USB Hub 的原理图部分做完了**(12 页,跨页 0 漏接)。
还欠:**把新器件导进 PCB** + 【C】带重排 + **人眼看三张新图的截图**。
- ⭐⭐ **主板载板**(5 块主板固定在一块载板上,对插到底板)——
**卡在「没有小米8 主板的机械数据」**,只能等用户实测。
1. 桥接在不在:`/c/Python314/python .claude/eda.py` 随便跑一段 JS;不通就按下面「环境」起服务。
2. 页面 UUID 全在 `.claude/eda-parts.json` 的 `pages` 里(**12 页**都有),不用重新枚举。
3. **动手前读 `easyeda-sch-geometry` skill**,尤其 §8a(零长度导线)、§8b(端口 dir 决定标签朝向)、
§2(旗的 rot 语义,四种组合都实测过了)。
4. 还欠人眼确认的页:**P1-INPUT、P2-CHANNEL、P3-CONTROL、P3B-EXPAND、P5-HARNESS**,
以及这轮新改/新增的 **P4-USB、P4B-HUB、P1B-5V**
(P2B~P2E 与 P2-CHANNEL 完全同构,看一页即可)。
⚠️ 截图 API 有时会抓到**另一个窗口**(返回的 `windowId` 会变),拍出来只有元件没有导线。
遇到就换个时机重截,或直接在客户端里看 —— 先用 API 读回图元数量确认数据在不在,再判断。
5. 改完任何一页,跑 `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。