初始导入: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
这个提交包含在:
zhou yang
2026-09-06 18:18:14 +08:00
共同撰写人 Claude Opus 5
当前提交 2bfe308b30
共修改 526 个文件,包含 80633 行新增和 0 行删除
+245
查看文件
@@ -0,0 +1,245 @@
# -*- coding: utf-8 -*-
u"""六对**长差分**(USB1~5 的 U32↔U5n + UPSTREAM 的 U32↔J23)+ Hub 西缘被 D± 夹住的两条 +3V3 车道。
**v5 版(2026-09-06,载板全部底层、接口边朝板边)。** v3 版存档在 `obsolete/pcb2_dp_long_v3_2026-09-06.py`。
被 `pcb2_route_dp.py` import;校核 / 下发都用那个脚本,这里只放坐标。
## 出发点:U32 西缘的 12 条车道
U32(QFN-64,**底层**,中心 (100, 68))的 D± 全在**西缘 x 95.55**,0.5 脚距、南→北:
1DM 64.25 | 1DP 64.75 | 2DM 65.25 | 2DP 65.75 | pin5 +3V3 66.25 |
3DM 66.75 | 3DP 67.25 | 4DM 67.75 | 4DP 68.25 | pin10 +3V3 68.75 |
5DM 69.25 | 5DP 69.75 | pin13 GND 70.25 | 14~16 NC
目的地却是四面八方:1→西北远处 (U51 22/24, 87)、2→正北 (U52 94/96, 87)、3→东北 (U53 165/167, 87)、
4→西南 (U54 88.6/90.5, 48)、5→东南 (U55 160/162, 48)、UP→正东 (J23 212, 61~62)。
### 定下来的走法(理由都在代码注释里)
| 车道 | 层 | 走法 |
|---|---|---|
| pair 1 | 底 | 直着西行 y 64.25/64.75 到 x 22.34/22.82,北上进 U51 |
| pair 2 | 底 | 西行到 89.27/89.75 北上,y 80 处 45° 斜到 93.84/95.74 进 U52 |
| pin 5 | 底→顶 | 西行到 90.4,跟着 pair 3 北上到 70.5 打孔翻顶层,西去 (88.2, 70.5) 再下来进 C747 |
| pair 3 | 底 | 西行到 91.05/91.53 北上,y 76.50/76.98 东行,过了 pair 5 之后压到 75.40/75.88 从 MH 焊盘下面钻过去,x 165 北上 |
| pair 4 | **顶** | 西行到 x 92.2 打孔上顶层(DM 67.95 / DP 68.85),沿 x 91.5/91.98 南下到 y 52,斜到 (89.6/90.5, 50.3) 下来进 U54 |
| pin 10 | 底 | 西行到 92.95,跟着 pair 5 北上,斜到 (92.5, 70.0) 进 C748;(92.5, 70.0) 上有一个孔接顶层 +3V3 干线 |
| pair 5 | 底 | 西行到 93.47/93.95 北上,y 75.50/75.98 东行到 110.30/110.78 南下,**y 59.2~61.6 翻顶层跨过 UP**,y 52.5/52.98 东行,x 160.1/162 南下进 U55 |
| UP | 底 | 南缘 pin58/59 从 C743/C745 之间的竖井下到 60.12/60.60,一路东行,x 205 抬到 61.25/61.75 进 J23 B6/A7 |
| +3V3 干线 | 顶 | U31 pin2 旁 (90.3, 61.65) 打孔上顶层,北上到 (90.4, 70.5)(与 pin5 的孔汇合),东去 (92.5, 70.0) 下来进 C748 |
### 三条关键约束
1. **0.5 脚距的车道之间打不了孔**:孔要离两边车道各 0.645,所以只有当邻居车道都拐走之后才能打。
北边的先拐(pair 5 → pin 10 → pair 4 打孔 → pair 3 → pin 5 → pair 2),像剥洋葱。
pair 4 的两个孔卡在 DP3 的横车道(y 67.25,要求孔心 ≥ 67.895)和 pin10 北上车道(x 92.95,要求孔心 x ≤ 92.305)之间,
所以 DP4 得先在 x 93.1→92.5 抬到 68.85 再打孔,两孔上下错 0.9。
2. **顶层只走 Inner1 是 GND 的地方**:Hub 周围 x ≥ 87(VO4 岛东界已从 89 收到 87)、y < 75.5(VO2 的 Hub 北横带以南)是一整片 GND;
pair 4 全程、pair 5 跨 UP 的那 2.4 mm、+3V3 干线都在这片里。pair 5 的跨越点 x 110~111.5 也在 VO4/VO5 之间的 GND 窗口里。
3. **平行 45° 斜段要保持 0.48 间距**:两根线各自的拐点不能同 x/同 y,第一拐**内侧**那根要早拐 0.2
(0.48·(√2−1)),否则斜段上的垂直间距只剩 0.34。文件里所有成对斜段都按这个写。
### 每个站点最后一段
U5n 朝 Hub 那排的 DP/DM 相距 1.9 mm,车道并排 0.48 到最后 1.4 mm 再用 45° 分开,
分开的那根一律是**转弯时的外侧**,所以到站不用交叉 —— 五对都核过了
(转弯规则:向右拐时左边那根变成外侧,连拐两次回到原样)。
"""
import math
L_TOP, L_BOT = 1, 2
W = 0.28
def chamfer(pts, r=0.35):
u"""把折线的拐点切成 45°。换层点(相邻两点层不同)不切。
⚠ 点的 layer/width 语义是「从这一点到下一点」,所以在拐点 b 处插入的
b1 用**前一段**的层/宽(它承担斜切段),b2 用 b 自己的。
"""
if len(pts) < 3:
return list(pts)
out = [pts[0]]
for i in range(1, len(pts) - 1):
a, b, c = pts[i - 1], pts[i], pts[i + 1]
if a[2] != b[2] or b[2] != c[2]: # 拐点两侧不同层 -> 这是过孔点
out.append(b)
continue
ax, ay = b[0] - a[0], b[1] - a[1]
cx, cy = c[0] - b[0], c[1] - b[1]
la, lc = math.hypot(ax, ay), math.hypot(cx, cy)
if la < 1e-9 or lc < 1e-9:
out.append(b)
continue
# 共线就不用切;**已经是 45° 的拐(正 + 斜)也不切** —— 再切一刀会变成两个 22.5°,
# 而且切出来的那一小段会向内侧挪 0.1 mm,实测把 DP4 的抬升段切进了 pin10 车道的净距圈(0.125)。
dot = max(-1.0, min(1.0, (ax * cx + ay * cy) / (la * lc)))
if math.degrees(math.acos(dot)) <= 46.0:
out.append(b)
continue
d = min(r, la / 2.0, lc / 2.0)
out.append((round(b[0] - ax / la * d, 4), round(b[1] - ay / la * d, 4), a[2], a[3]))
out.append((round(b[0] + cx / lc * d, 4), round(b[1] + cy / lc * d, 4), b[2], b[3]))
out.append(pts[-1])
return out
def P(*pts):
u"""(x, y[, layer[, width]]) -> 规范化四元组;层/宽省略时沿用上一点。"""
out, lay, w = [], L_BOT, W
for t in pts:
x, y = t[0], t[1]
lay = t[2] if len(t) > 2 else lay
w = t[3] if len(t) > 3 else w
out.append((round(x, 4), round(y, 4), lay, w))
return out
PAD_X = 95.55 # U32 西缘焊盘中心(焊盘 x 95.125~95.975,hh 0.15)
# 0.28 的线直着从 0.5 脚距的焊盘出来,两边净距 0.21,不用让位。
# ══════════ pair 1 → U51(西北远处)══════════
# 直着西行:y 64.25/64.75 从 x 95.55 到 x 22~23 一路无障碍(U31 pin1 顶 63.35、J31 底排焊盘底 65.38、
# R721/R722 顶 61.77、U34 顶 ~62)。西行时 DM 在南 = 左侧,右拐北上后左侧变西 ⇒ DM 走西车道 22.34(正对 U51.6),
# DP 在 22.82,到 y 84.3 再 45° 分到 24.24 进 U51.4。
SITE1 = {
'USB1_DM': [(PAD_X, 64.25), (22.34, 64.25), (22.34, 86.475)],
'USB1_DP': [(PAD_X, 64.75), (22.82, 64.75), (22.82, 84.30), (24.24, 85.72), (24.24, 86.475)],
}
# ══════════ pair 2 → U52(正北)══════════
# 西行到 x 89.27/89.75 右拐北上(DM 西)。北上车道左边是 J31 最东一列焊盘(x ≤ 86.98),右边是 pin5 的北上车道 90.4。
# 一直到 y 80 才 45° 斜向东北 —— 必须过了 pair 3 / pair 5 东行车道(y ≤ 76.97)之后才能往东挪,
# 而且要绕开 site2 的定位孔 H1 (89.39, 85.36)。斜段直接斜到各自焊盘正下方:DM 到 93.84、DP 一口气到 95.74。
# 内侧(DP,东)早拐 0.2 保持斜段 0.48 间距。
SITE2 = {
'USB2_DM': [(PAD_X, 65.25), (89.27, 65.25), (89.27, 80.00), (93.84, 84.57), (93.84, 86.475)],
'USB2_DP': [(PAD_X, 65.75), (89.75, 65.75), (89.75, 79.80), (95.74, 85.79), (95.74, 86.475)],
}
# ══════════ pair 3 → U53(东北)══════════
# 西行到 91.05/91.53 右拐北上(DM 西),再右拐东行(西车道变北车道 ⇒ DM 76.98 / DP 76.50),
# 这一段在 pair 5 东行车道(75.50/75.98)的北边;pair 5 在 x 110.78 南下之后,pair 3 在 x 112 压到 75.40/75.88 ——
# 因为 MH (148.1, 79.5) 的 Ø6 焊盘底在 76.5,北车道上沿必须 ≤ 76.16。
# 到 x 165 左拐北上:北车道(DM)是内侧变西 ⇒ DM 165.34 正对 U53.6,DP 在 165.82 到 y 84.6 再分到 167.24。
# 下压的斜段:第一拐向右(东→东南),内侧是南车道 DP,早拐 0.2。
SITE3 = {
'USB3_DM': [(PAD_X, 66.75), (91.05, 66.75), (91.05, 76.98), (112.00, 76.98), (113.10, 75.88),
(165.34, 75.88), (165.34, 86.475)],
'USB3_DP': [(PAD_X, 67.25), (91.53, 67.25), (91.53, 76.50), (111.80, 76.50), (112.90, 75.40),
(165.82, 75.40), (165.82, 84.60), (167.24, 86.02), (167.24, 86.475)],
}
# ══════════ pair 4 → U54(西南)—— 全程顶层 ══════════
# 它北边是 pin10(要跟 pair 5 北上)、南边是 pair 3(北上)、pair 2、pair 1 全都横在西边 —— 底层无路,
# 一出焊盘就打孔上顶层。两个孔:DM4 (92.2, 67.95)(离 DP3 横车道 67.39 有 0.255)、DP4 (92.2, 68.85)(上下错 0.9),
# DP4 先在 x 93.1→92.5 斜抬到 68.85 再西行到孔,避开 DM4 的孔(孔顶 68.255 vs 车道底 68.11 才 0.055)。
# 顶层:DP 西车道 91.5 / DM 91.98 南下到 y 52,45° 斜到 (89.6, 50.3) / (90.5, 50.3) 打孔回底层,
# 进 U54.4 (88.6, 48.149) / U54.6 (90.5, 48.149)。DM 的孔正对焊盘、直下;DP 的孔再斜 1 mm 到 88.6。
# 顶层这一段底下 Inner1:x ≥ 87 全是 GND(VO4 岛东界 87、site4 岛 x ≤ 84.75、VO2 从 y 75.5 起)。
SITE4 = {
'USB4_DM': [(PAD_X, 67.75), (92.60, 67.75), (92.40, 67.95), (92.20, 67.95),
(92.20, 67.95, L_TOP), (91.98, 67.73), (91.98, 52.00), (90.50, 50.52), (90.50, 50.30),
(90.50, 50.30, L_BOT), (90.50, 48.685)],
'USB4_DP': [(PAD_X, 68.25), (93.10, 68.25), (92.50, 68.85), (92.20, 68.85),
(92.20, 68.85, L_TOP), (91.50, 68.85), (91.50, 52.50), (89.60, 50.60), (89.60, 50.30),
(89.60, 50.30, L_BOT), (88.60, 49.30), (88.60, 48.685)],
}
# ══════════ pair 5 → U55(东南)—— 绕 Hub 北边走 ══════════
# 最北的一对,出来就右拐北上(DM 西 93.47 / DP 东 93.95;DP 的东沿离 C751 焊盘 94.295 只剩 0.205,
# 再东一点 pin13 的 GND 就打不了孔了 —— 给它留的孔位是 (94.6, 71.4))。
# 北上到 75.50/75.98(C751 顶 74.974,DP 拐角的 45° 切角离它 0.24)右拐东行(DM 北),到 x 110.30/110.78 右拐南下(南车道 DP 是内侧变西 ⇒ DP 110.30)。
# 南下时 x 110.3 离 R711/R716/R720 的东沿 109.62 有 0.54、离 J64 焊盘 107.43 有 2.7。
# **y 59.2~61.6 翻到顶层跨过 UP 的两根横线**(60.12/60.60):DP 的孔在 110.30,DM 的孔错到 111.3(两孔 1.0)。
# 到 y 52.5/52.98 左拐东行(东车道 DM 是内侧变北 ⇒ DM 52.98),DP 在 x 160.10 南下进 U55.4,DM 到 162.00 进 U55.6。
SITE5 = {
'USB5_DM': [(PAD_X, 69.25), (93.47, 69.25), (93.47, 75.98), (110.78, 75.98),
(110.78, 62.60), (111.30, 62.08), (111.30, 61.60),
(111.30, 61.60, L_TOP), (111.30, 59.20),
(111.30, 59.20, L_BOT), (111.30, 58.72), (110.78, 58.20),
(110.78, 52.98), (162.00, 52.98), (162.00, 48.685)],
'USB5_DP': [(PAD_X, 69.75), (93.95, 69.75), (93.95, 75.50), (110.30, 75.50),
(110.30, 61.60),
(110.30, 61.60, L_TOP), (110.30, 59.20),
(110.30, 59.20, L_BOT), (110.30, 52.50), (160.10, 52.50), (160.10, 48.685)],
}
# ══════════ UPSTREAM → J23(U32 南缘 → 板子正东)══════════
# pin59 UP_DP (98.75, 63.55) / pin58 UP_DM (99.25, 63.55) 直着南下,从 C743(x ≤ 97.62)与 C745(x ≥ 99.78)之间的
# 竖井下到 y 60.12/60.60(那排 0402 的焊盘底 60.98),一路东行到 x 205 —— 中间 y 60~61 从 x 99 到 208 全空。
# J23(底层 rot 270):B6 UP_DP 61.25 / A7 UP_DM 61.75 / A6 UP_DP 62.25 / B7 UP_DM 62.75,B5 CC2 在 60.25、A8 空脚 60.75。
# 车道压在 60.17 会把 B5 的 CC2 走线堵死(R718 在 59.5),所以 x 205 就抬到 61.25/61.75 平着进 B6/A7。
# 抬升是左拐再右拐,第一拐内侧是北车道 DM ⇒ DM 早拐 0.2。
UP = {
'UP_DP': [(98.75, 63.55), (98.75, 60.12), (205.00, 60.12), (206.13, 61.25), (212.392, 61.25)],
'UP_DM': [(99.25, 63.55), (99.25, 60.60), (204.80, 60.60), (205.95, 61.75), (212.392, 61.75)],
}
# ══════════ J23 的 A6↔B6 / A7↔B7 短接(USB-C 正反插的一对重复脚)══════════
# 几何照抄 v3(那一版过了 DRC),只是 J23 翻到底层之后网络对调:61.25↔62.25 这一对现在是 UP_DP(B6/A6),
# 61.75↔62.75 是 UP_DM(A7/B7);出线在底层,跨越段翻到**顶层**。
# ⚠ 表贴焊盘只到 x=213.04,过孔要离它 0.505 ⇒ 最西只能放到 213.55;出线前 0.65 mm 必须平着走(阵列上方起坡会蹭隔壁脚)。
# 另一根的 61.75 横线离 (213.85, 62.25) 那个孔只有 0.165 —— 这是同一对的两根(UP_DP vs UP_DM),按对内 0.15 判。
J23_TIE = {
'UP_DP#2': [(212.392, 62.25, L_BOT, W), (213.85, 62.25), (213.85, 62.25, L_TOP),
(213.85, 60.70), (213.85, 60.70, L_BOT), (213.50, 61.25),
(213.20, 61.25), (212.392, 61.25)],
'UP_DM#2': [(212.392, 61.75, L_BOT, W), (213.20, 61.75), (213.50, 61.75),
(214.25, 61.30), (214.75, 61.30), (214.75, 61.30, L_TOP),
(214.75, 63.20), (214.75, 63.20, L_BOT), (214.25, 63.20),
(213.50, 62.75), (213.20, 62.75), (212.392, 62.75)],
}
# ══════════ Hub 西缘 pin5 / pin10 的 +3V3(被 D± 夹住,只能在这里一起画)══════════
# 后面的信号布线器(pcb2_route_sig.py)把这些线当**已有障碍**,别删。
# pin5 (66.25) 夹在 DP2 (65.75) 与 DM3 (66.75) 之间:西行到 90.4(pair 3 拐走之后)跟着北上,到 (90.4, 70.5) 打孔翻顶层
# —— 孔离 DP2 车道 89.75 / DM3 车道 91.05 各 0.65。顶层西去 (88.2, 70.5) 再下来进 C747(底层 rot 90,x 88.2,
# 在 J31 最东列 86.98 与 DM2 车道 89.27 之间)。(88.2, 70.5) 的孔离 VO2 岛东界 87.3 有 0.9,反焊盘不碰岛。
# pin10 (68.75) 夹在 DP4 (68.25) 与 DM5 (69.25) 之间:西行到 92.95 跟着 pair 5 北上(离 DM5 车道 0.24、离 pair 4 的孔 0.305),
# y 69.55 起 45° 斜到 (92.5, 70.0),直进 C748(底层 rot 90,x 92.5,夹在 DP3 车道 91.53 与 DM5 车道 93.47 中间)。
# +3V3 干线:U31 pin2(+3V3 输出,(90.47, 60.5) 焊盘 y ≤ 61.05)旁 (90.3, 61.65) 打孔上顶层,沿 x 90.3 北上
# (pair 4 的顶层车道在 91.5 以东),到 (90.4, 70.5) 与 pin5 的孔汇合,再东去 (92.5, 70.0) 打孔下来进 C748。
# 顶层这三段底下 Inner1 都是 GND(x 87.3~100.5、y 65.15~75.5 之间没有任何岛)。
# 电容的哪个焊盘是 +3V3 由 build() 从障碍表里查,不写死。
V_P5, V_P5W = (90.40, 70.50), (88.20, 70.50)
V_P10 = (92.50, 70.00)
V_TRUNK = (90.30, 61.65)
def _pad(pads, ref, net):
for p in pads:
if p['ref'] == ref and p['net'] == net:
return (p['x'], p['y'])
raise SystemExit(u'障碍表里找不到 %s 的 %s 焊盘(先 --dump)' % (ref, net))
def power_lanes(pads):
c747 = _pad(pads, 'C747', '+3V3_HUB')
c748 = _pad(pads, 'C748', '+3V3_HUB')
for p in pads: # 干线起点必须真的落在 U31 pin2(+3V3 输出)里
if p['ref'] == 'U31' and p['num'] == '2':
assert abs(p['x'] - 90.47) < 0.05 and abs(p['y'] - 60.5) < 0.05, u'U31 pin2 挪了,改 V_TRUNK'
return {
'+3V3_HUB#p5': [(PAD_X, 66.25), (90.40, 66.25), (V_P5[0], V_P5[1]),
(V_P5[0], V_P5[1], L_TOP), (V_P5W[0], V_P5W[1]),
(V_P5W[0], V_P5W[1], L_BOT), (c747[0], c747[1])],
'+3V3_HUB#p10': [(PAD_X, 68.75), (92.95, 68.75), (92.95, 69.55), (V_P10[0], V_P10[1]),
(c748[0], c748[1])],
'+3V3_HUB#trunk': [(90.30, 61.00), (V_TRUNK[0], V_TRUNK[1]),
(V_TRUNK[0], V_TRUNK[1], L_TOP), (90.30, 70.40), (V_P5[0], V_P5[1]),
(V_P10[0], V_P10[1]), (V_P10[0], V_P10[1], L_BOT), (c748[0], c748[1])],
}
def build(pads=None):
out = {}
for grp in (SITE1, SITE2, SITE3, SITE4, SITE5, UP, J23_TIE):
for net, pts in grp.items():
out[net] = chamfer(P(*pts))
if pads is not None:
for net, pts in power_lanes(pads).items():
out[net] = chamfer(P(*pts))
return out