初始导入: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 行删除
@@ -0,0 +1,291 @@
# -*- coding: utf-8 -*-
u"""六对**长差分**的路径(USB1~5 的 U32↔U5n + UPSTREAM 的 U32↔J23)。
被 `pcb2_route_dp.py` import;校核 / 下发都用那个脚本,这里只放坐标。
## 拓扑(2026-09-06 第三轮定,理由见设计文档 §25.13)
U32 是 **QFN-64、贴在底层**,十根下游 D± 全部从**西边缘**出来(0.5 mm 脚距,
南→北 = port1..5)。目的地却是四面八方:
| 对 | 去哪 | 走法 |
|---|---|---|
| 1 | site1 (61.5, 76.4) 西北 | 底层西行 → x=98.3/98.8 爬升 → 西行车道 74.70/75.55 |
| 2 | site2 (133, 76.4) 东北 | 底层西行 → 爬升 → **北高速「高窗」** 78.29/78.77 → 下降 |
| 3 | site3 (204.5, 76.4) 东北 | 底层西行 → 爬升 → **北高速「低窗」** 76.20/76.68 |
| 4 | site4 (49.4, 58.8) 西南 | **换顶层**南下(唯一要穿过其它对的一对)→ 底层西行 60.5/61.0 |
| 5 | site5 (120.9, 58.8) 东南 | 底层爬升 → **顶层**过隘口 75.69/76.12 → 底层南下 |
| UP | J23 (212.4, 61~63) 正东 | U32 南边「竖井」下到 y≈57 → 东行 → 北抬到 60.5/61.2 |
### 为什么这么绕:x 112~116 那道隘口
C751(10 µF 1206,底层,(113.0,74.0))+ C815 的 GND 扇出孔 (113.047,77.5)
把北高速掐成两段。底层在 x=113 只剩两个窗口:
* **低窗** y 76.045~76.855(线心 76.185~76.72,**只塞得下一对**)
* **高窗** y 78.145~81.66(宽,但东边 (117.067,78.392) 那个孔又把 x=117 的天花板压到 77.75,
所以出了隘口必须马上降下来)
顶层同一段是通的(C751 在底层),y 75.44~76.3 还有一条**顶层窗口**——
而且 x 80~116 / y 67.6~84 底下正好是 Inner1 的 `VO2_P` 整岛,
**一整块实心参考面、不跨分割**(§10.1 的约束按「一块铜」判,不是按「必须是地」)。
所以第三对(pair 5)从这里过,两端的过孔都落在岛内(x ≤ 115.8)。
### 排序规则(写路径时逐条对照,避免同层交叉)
一束平行线拐弯时**外侧的先拐**:西行右转向北 → 最东的先拐;
北行右转向东 → 最西的在最高的车道。由此从 U32 西边缘(南→北 = 1,2,3,4,5)
出发,爬升后 x 由西到东是 1,2,3,5,转东后车道由北到南是 **2,3,5** ——
正好满足「pair2 要在 x=133 北拐、pair3 继续东行、pair5 在 x=121 南下」。
### 每个站点最后 2 mm 都是同一个花样
U5n 的 DP/DM 焊盘在**顶层**、相距 1.9 mm,而车道上的南北次序和焊盘的东西次序
永远差一次交叉。解法统一:**外侧那根先打过孔上顶层**,从另一根的**底层**车道
上方跨过去,另一根再在自己焊盘正下方上来。两根 1.9 mm 分开,过孔互不干涉。
"""
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 自己的。
⚠ r 别调大:0.6 时 `USB1_DM` 那个让位拐角会被削进 C747 的 GND 扇出孔
(103.28, 62.868) 的净距圈里(0.178 < 0.20)。0.35 才留得住 0.209。
"""
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
# 共线就不用切
if abs(ax / la - cx / lc) < 1e-9 and abs(ay / la - cy / lc) < 1e-9:
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
# ══════════ U32 西边缘的逃逸 ══════════
# 焊盘 x=105.55,西端 105.125;脚距 0.5 mm,0.28 线正好留 0.22 净距(>0.20)。
# 让位在 x 104.9→104.4 这一小段做完(x ≤ 104.785 之后就离所有焊盘 ≥0.34)。
#
# ⚠ `USB1_DM` 的原生 y=63.25 会撞上 C747 的 GND 扇出孔 (103.28, 62.868)
# —— 净距只有 0.382,要 0.645。抬到 63.55 之后是 0.682。
# 抬了它,上面四根就得跟着让(对内 0.43 / 对间 0.48),一直让到 pair3。
# ⚠ 同一对的两根让位量必须**相等**(斜段平行),否则 0.50 的行距按垂直方向
# 折算会掉到 0.43 以下。pair1/2 统一 +0.30、pair3 统一 −0.20。
# ⚠ `USB5_DP` 的原生 68.75 离 U32 pin13 的 GND 扇出孔 (104.599, 69.25) 只有 0.50,
# 要 0.645 ⇒ 压到 68.60;DM5 跟着到 68.15。这一压正好给 pair4 的过孔留出
# [DP3+0.645, DM5−0.645] = [66.695, 67.505] 这 0.81 mm 的窗口(两个孔要 0.76)。
ESC = { # net: (焊盘 y, 西行车道 y, 爬升 x)
'USB1_DM': (63.25, 63.55, 98.30),
'USB1_DP': (63.75, 64.05, 98.80),
'USB2_DM': (64.25, 64.55, 99.30),
'USB2_DP': (64.75, 65.05, 99.80),
'USB3_DM': (65.75, 65.55, 100.30),
'USB3_DP': (66.25, 66.05, 100.80),
'USB5_DM': (68.25, 68.05, 101.30),
'USB5_DP': (68.75, 68.55, 101.80),
}
PAD_X = 105.55
# ⚠ 让位的斜段**不能太早起步**:x=105.19 处斜上去时离隔壁焊盘只剩 0.183(要 0.20)。
# 所以先直着走到 105.00,再走到拐点 104.60 —— 这样 45° 切角被 |ab|/2 限成 0.20,
# 斜段从 x=104.80 才开始(离隔壁焊盘 0.242)。
# ⚠ 让位段还**不能太陡**:相邻两根各让 0.28 / 0.26,斜段近似平行,
# 0.50 的行距按垂直方向折算成 0.50·Δx/|d|。Δx=0.5 时只剩 0.436(净距 0.156,要 0.20),
# Δx=1.2 时是 0.487(净距 0.207)。所以让位横跨 104.60 → 103.40。
JOG_X0, JOG_X1, JOG_X2 = 105.00, 104.60, 103.40
def esc(net):
u"""焊盘 → 让位 → 西行到爬升点。
⚠ pair5 是特例,三条约束把它挤成一条 **0.18 mm 的窄段**:
· `USB5_DP` 出焊盘后要压到 (104.599, 69.25) 那个 GND 扇出孔的净距圈外;
· 但它又不能压太低 —— 隔壁 `USB5_DM` 的焊盘上沿在 68.40,对内净距要 0.15;
· `USB5_DM` 自己也不能往下让 —— 下面 0.5 mm 就是 U32 pin10 的焊盘。
0.28 线宽下这三条无解;收到 **0.18** 之后 DP 走 68.62、DM 保持 68.25
(净距 0.19),过了焊盘区(x ≤ 103.4)再放回 0.28 并拉开到 0.50。
"""
pad_y, lane, cx = ESC[net]
if net == 'USB5_DP':
return [(PAD_X, 68.75, L_BOT, 0.18), (105.13, 68.75), (104.70, 68.62),
(103.40, 68.62), (102.80, lane, L_BOT, W), (cx, lane)]
if net == 'USB5_DM':
return [(PAD_X, 68.25, L_BOT, 0.18), (103.40, 68.25),
(102.80, lane, L_BOT, W), (cx, lane)]
return [(PAD_X, pad_y), (JOG_X0, pad_y), (JOG_X1, pad_y), (JOG_X2, lane), (cx, lane)]
# ══════════ pair 1 → site1(西北)══════════
# 西行车道 74.70(DM1, 南) / 75.55(DP1, 北)。
# U51 焊盘:pin4 DP x=61.55(西)、pin6 DM x=63.45(东),y=76.351,从南边进。
# 西行时先到的是**东**边的 DM1,它必须先北拐 —— 而 DP1 的车道在它北边,
# 同层必交叉 ⇒ **DM1 在 (63.45, 74.70) 打孔上顶层**,从 DP1 的底层车道上方跨过去。
SITE1 = {
'USB1_DM': esc('USB1_DM') + [(98.30, 74.70), (63.45, 74.70),
(63.45, 74.70, L_TOP), (63.45, 76.351)],
'USB1_DP': esc('USB1_DP') + [(98.80, 75.55), (61.55, 75.55),
(61.55, 75.55, L_TOP), (61.55, 76.351)],
}
def build():
out = {}
for grp in (SITE1, SITE2, SITE3, SITE4, SITE5, UP, J23_TIE):
for net, pts in grp.items():
out[net] = chamfer(P(*pts))
return out
# ══════════ pair 3 → site3(东北,走「低窗」)══════════
# 隘口 x 112~114 的底层低窗:线心只能落在 76.185~76.72 —— 只塞得下这一对。
# 取 76.20 / 76.68(间距 0.48,斜段上垂直净距 0.45 > 0.43)。
# 出了隘口先在 x 120→126 降到 73.90/74.38(要给 pair2 让出北边的车道,
# 它得从北高速一路降到 75.05/75.70 才能从南边进 U52),
# x 140→146 再抬回 75.00/75.70(避开 (187.065,73.863) 那个孔,也对齐 site3 的进站高度)。
SITE3 = {
'USB3_DM': esc('USB3_DM') + [(100.30, 76.63), (118.00, 76.63), (120.00, 76.63), (126.00, 75.20),
(132.00, 74.35), (140.00, 74.35), (146.00, 75.70), (206.45, 75.70),
(206.45, 75.70, L_TOP), (206.45, 76.351)],
'USB3_DP': esc('USB3_DP') + [(100.80, 76.20), (118.00, 76.16), (120.00, 76.16), (126.00, 74.73),
(132.00, 73.85), (140.00, 73.85), (146.00, 75.00), (204.55, 75.00),
(204.55, 75.00, L_TOP), (204.55, 76.351)],
}
# ══════════ pair 2 → site2(东北,走「高窗」)══════════
# 高窗 78.145~81.66 宽敞,但东边 (117.067, 78.392) 那个孔把 x=117 的天花板压到 77.75,
# 所以过了隘口(x=114)就得斜下来:78.77/78.29 → 77.61/77.13(21°,垂直净距 0.448)。
# 之后一路缓降到 75.70/75.05,从南边进 U52 —— 全程都在 pair3 的北边。
SITE2 = {
'USB2_DM': esc('USB2_DM') + [(99.30, 78.83), (114.00, 78.83), (116.80, 77.70),
(120.00, 77.70), (126.00, 76.30), (132.00, 75.70), (134.95, 75.70),
(134.95, 75.70, L_TOP), (134.95, 76.351)],
'USB2_DP': esc('USB2_DP') + [(99.80, 78.29), (114.00, 78.29), (116.80, 77.16),
(120.00, 77.16), (126.00, 75.76), (132.00, 75.05), (133.05, 75.05),
(133.05, 75.05, L_TOP), (133.05, 76.351)],
}
# ══════════ pair 4 → site4(西南,全板唯一要「翻层穿越」的一对)══════════
# pair4 夹在 pair3(往北)和 pair5(往北)中间却要往南 —— 同层必然横切这两对,
# 所以一出焊盘就**上顶层**南下,把交叉全部让给层间。
#
# 过孔位置被四样东西同时夹住:
# · 西边界 x ≥ 102.445(离 pair5 的爬升柱 x=101.80 要 0.645)
# · DM4 的孔 ≥ DP3 车道 66.05 + 0.645 = 66.695
# · DP4 的孔 ≤ DM5 车道 68.15 − 0.645 = 67.505
# · 两孔互相 ≥ 0.76 ⇒ 只能**错开 x** 放:(102.50, 66.75) 与 (103.60, 67.48)
# 顶层的两根竖线还得躲开 (103.28, 62.868) 那个孔(x ≤ 102.635),
# 而且**不能压到对方的过孔上** ⇒ DP4 竖在 100.70、DM4 竖在 101.30,
# DP4 从东边高处横过来时正好从 DM4 竖线的上方(y=67.48)越过。
# 顶层这一段(x 100.4~103.6, y 61~67.5)底下 Inner1 全是 GND 底填
# (VO2 岛从 y=67.6 起、VO4 岛到 x=100 止),**没有跨分割**。
SITE4 = {
'USB4_DM': [(105.55, 66.75), (104.60, 66.75), (102.50, 66.75),
(102.50, 66.75, L_TOP), (101.30, 66.75), (101.30, 60.30),
(101.30, 60.30, L_BOT), (49.39, 60.30),
(49.39, 60.30, L_TOP), (49.39, 58.809)],
'USB4_DP': [(105.55, 67.25), (104.60, 67.25), (103.60, 67.40),
(103.60, 67.40, L_TOP), (100.70, 67.40), (100.70, 61.00),
(100.40, 61.00), (100.40, 61.00, L_BOT), (51.29, 61.00),
(51.29, 61.00, L_TOP), (51.29, 58.809)],
}
# ══════════ pair 5 → site5(东南,过隘口走**顶层**)══════════
# 隘口的底层两个窗口分别被 pair3(低窗)和 pair2(高窗)占了,
# 第三对只能走顶层 y 75.44~76.3 那条缝。那一段底下是 Inner1 的 `VO2_P` 整岛,
# 一整块实心参考面 —— **为此把 VO2 的东边界从 x=116 放到 119**(§25.13),
# 好让下沉的两个过孔 (118.0) 还落在岛内,顶层全程不跨分割。
# ⚠ 下坡不能早于 x=115.2:(114.476, 75.0) 那个 GND 孔要求 DP5 ≥ 75.645。
SITE5 = {
'USB5_DM': esc('USB5_DM') + [(101.30, 75.35), (103.00, 75.35),
(103.00, 75.35, L_TOP), (108.00, 76.14), (115.20, 76.14),
(118.00, 75.50), (118.00, 75.50, L_BOT),
(120.89, 75.50), (120.89, 60.40),
(120.89, 60.40, L_TOP), (120.89, 58.809)],
'USB5_DP': esc('USB5_DP') + [(101.80, 74.50), (103.00, 74.50),
(103.00, 74.50, L_TOP), (108.00, 75.68), (115.20, 75.68),
(118.00, 74.70), (118.00, 74.70, L_BOT),
(120.00, 74.70), (120.00, 59.50),
(122.79, 59.50), (122.79, 59.50, L_TOP),
(122.79, 58.809)],
}
# ══════════ UPSTREAM → J23(U32 南边缘 → 板子正东)══════════
# ⚠ hub_layout 里写的「南侧竖井」(C743 与 C745 之间 2.16 mm)**在底层已经被
# C743/C745 自己的 GND 扇出孔堵死了**:(108.219,60.28) 与 (109.182,60.28)
# 中间只剩 0.96 mm,一根 0.28 的线净距只有 0.036。
# ⇒ **UP 一出焊盘就上顶层**(南边那排 0402 都在底层,顶层 y 61.0~61.8
# 从 x=109 一路通到 130)。到 x=128 才沉回底层,
# 这样五号站的 D± 在 x=120~123 竖下去时是**从 UP 底下穿过**,两不相干。
# 顶层这一段底下 Inner1 是 GND 底填(VO4 到 x=100、VO5 从 x=139.5 起、VO2 从 y=67.6 起),
# 不跨分割。
# ⚠ J23 前面 R717 的 GND 扇出孔 (209.933, 61.642) 占住 y 61.0~62.3,
# 所以东行车道压到 60.00/60.75,两根都在它**东边**(x ≥ 210.6)才抬头进焊盘。
# J23 侧 A6=UP_DP 在 61.75、A7=UP_DM 在 62.25,南北次序与车道一致,不用交叉。
UP = {
'UP_DP': [(108.75, 62.55), (108.75, 62.20), (108.75, 61.90),
(108.30, 61.45), (108.30, 61.10),
(108.30, 61.10, L_TOP), (110.20, 61.10), (128.00, 61.10),
(128.00, 61.10, L_BOT), (128.00, 60.00), (211.25, 60.00),
(211.25, 61.65), (211.25, 61.65, L_TOP), (211.60, 61.75),
(212.392, 61.75)],
'UP_DM': [(109.25, 62.55), (109.25, 61.70), (109.25, 61.70, L_TOP),
(110.80, 61.70), (128.60, 61.70), (128.60, 61.70, L_BOT),
(128.60, 60.75), (210.65, 60.75), (210.65, 62.25),
(210.65, 62.25, L_TOP), (212.392, 62.25)],
}
# ══════════ J23 的 A6↔B6 / A7↔B7 短接(USB-C 正反插的一对重复脚)══════════
# ⚠ 这两条**原来是自动布线器画的**,被 `pcb2_route_dp.py --push` 的「先删本网所有
# 1/2 层导线」连坐删掉了,必须自己补回来 —— 少了它们,反插方向就没有 D±。
# 四个焊盘在同一列上、间距 0.5:B7(61.25,DM) A6(61.75,DP) A7(62.25,DM) B6(62.75,DP),
# 两两要短接的中间都隔着对方的脚,所以只能**绕到连接器东侧沉底层**跨过去
# (x > 213.55 那一带底层是空的,J23 自己的两个固定脚在 y 57.7 / 66.3)。
# ⚠ 上表贴焊盘只到 x=213.042,过孔要离它 0.505 ⇒ 最西只能放到 213.55。
# 两根出线在 y 上只差 0.5,一根的过孔必然贴着另一根的出线 ——
# 所以出线要**边往东边分开**:DP 走到 214.40 并压到 61.55/62.95,
# DM 停在 213.60 保持 62.25/60.95,四个孔两两 ≥0.76、线-孔 ≥0.62。
J23_TIE = {
# ⚠ 出焊盘的那 0.65 mm 必须**平着走**:焊盘阵列 x 到 213.042 为止,
# 在阵列上方就起坡的话会蹭到隔壁脚(实测差 0.04~0.14)。都在 x=213.30 之后再拐。
'UP_DM#2': [(212.392, 62.25, L_TOP, W), (213.85, 62.25), (213.85, 62.25, L_BOT),
(213.85, 60.70), (213.85, 60.70, L_TOP), (213.50, 61.25),
(213.20, 61.25), (212.392, 61.25)],
'UP_DP#2': [(212.392, 61.75, L_TOP, W), (213.20, 61.75), (213.50, 61.75),
(214.25, 61.30), (214.75, 61.30), (214.75, 61.30, L_BOT),
(214.75, 63.20), (214.75, 63.20, L_TOP), (214.25, 63.20),
(213.50, 62.75), (213.20, 62.75), (212.392, 62.75)],
}