文件
phonerail/.claude/c5_aux.py
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

226 行
11 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""载板 C5-AUX:五路 USB 的 ESD + CC 上拉 Rp + 口温电阻(方案 D,§13.5)。
原厂尾插小板上本来就有的三样东西,方案 D 不装那块小板,所以在载板上补回来:
U51~U55 USBLC6-2SC6 D± 的 ESD(顺带钳 VBUS)
R761~765 56 kΩ CC1 -> VBUS 的 Rp(Type-C Default USB 档,§23.3 有分压核算)
R771~775 100 kΩ 口温 pin38 -> GND
⚠ 100k 这个值是**从主板图纸反推**的(2026-09-04,§13.5):
主板上 USB_CONN_THERM 是 SMB1355 的 BIAS(1) --[R640 20k 1%]-- TSKIN(7) 这个分压网络,
下臂就是小板上的 NTC。分压灵敏度在 R_ntc = 20k 时最高:
10k NTC(B=3435) 在 8°C 才到 20k -> 不可能(过温传感器不会把灵敏区放在 8 度)
100k NTC(B=4250) 在 63°C 到 20k -> 正好是接口过温的跳变区 ✔
而且 100k/B=4250(104EF/104FB)本来就是高通的标准热敏件。
我们放**固定**电阻:100k -> 手机恒读约 25°C;放 10k 会读成 60°C 以上,可能触发热限制。
为什么单独一页:C5-J1401A/B 上五颗 52 脚座子已经把版面占满,
C3-USB 的五列只有 215 单位宽、下方又是标题栏,都塞不下。
⚠ U51~U55 的 1/6 是同一个 I/O1、3/4 是同一个 I/O2(芯片内部短接),
而 EasyEDA 会在引脚处截断导线,所以 D± 在网表里天然是两段:
USBn_DP / USBn_DM = Hub 侧(C2-HUB 过来)
USBn_DPJ / USBn_DMJ = 座子侧(C5-J1401 过去)
PCB 上必须让走线从一个焊盘进、另一个焊盘出,不能只搭一个脚。
用法: /c/Python314/python .claude/c5_aux.py [--push]
"""
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from eda import run
PAGE = 'b1ffbaef65ef82c9' # C5-AUX
LIB = '0819f05c4eef4c71ace90d822a990e87'
P = {
'ESD': '1e5e214711544da8ba71fb2137ae703f', # C2687116 USBLC6-2SC6
'R56K': '0d85e42011ec4910b1749e936b037dd7', # C25796 56k 0402
'R100K': 'acc294b6661948399f609b41c77ed19f', # C25741 100k 0402
}
# 五个位排成 2+2+1 三排。一开始排成上三下二(间距 350),结果**相邻两位的端口标签叠在一起**
# —— 端口图元的文字比想象中宽('USB1_DMJ' 这种 8 字名字实际占 90 单位左右,不是 60)。
# 拉到 500 的横向间距就干净了。最下面一排的 y 不能再低 —— 图纸右下角是标题栏(实测占 x>=464 / y<=185,
# 不是原先估的 161;截图上文字压到标题栏就是这么发现的)。
SITES = [(1, 300, 720), (2, 800, 720), (3, 300, 520), (4, 800, 520), (5, 300, 320)]
def call(js, timeout=180):
r = run(js, timeout=timeout)
if not r.get('success'):
raise RuntimeError(json.dumps(r, ensure_ascii=False)[:900])
return r['result']
def desig(n):
return {'u': 'U5%d' % n, 'rcc': 'R76%d' % n, 'rth': 'R77%d' % n}
def placement():
out = []
for n, bx, by in SITES:
d = desig(n)
out.append([d['u'], P['ESD'], bx, by, 0])
out.append([d['rcc'], P['R56K'], bx, by - 80, 0])
out.append([d['rth'], P['R100K'], bx, by - 130, 0])
return out
def wiring(pins):
W, F, PT, TX = [], [], [], []
def w(*p):
for i in range(0, len(p) - 2, 2):
W.append([p[i], p[i + 1], p[i + 2], p[i + 3]])
for n, bx, by in SITES:
d = desig(n)
u = pins[d['u']]
io1a, io1b = u['1'], u['6'] # I/O1 -> DM
io2a, io2b = u['3'], u['4'] # I/O2 -> DP
ug, uv = u['2'], u['5']
assert io1a[1] == io1b[1] == by + 10, u'CH%d 的 I/O1 不在 y=%d' % (n, by + 10)
assert io2a[1] == io2b[1] == by - 10, u'CH%d 的 I/O2 不在 y=%d' % (n, by - 10)
# ---- D± 穿过 ESD:左边是座子侧(_J),右边是 Hub 侧 ----
w(bx - 105, by + 10, io1a[0], by + 10)
PT.append(['BI', 'USB%d_DMJ' % n, bx - 105, by + 10, 180])
w(bx - 105, by - 10, io2a[0], by - 10)
PT.append(['BI', 'USB%d_DPJ' % n, bx - 105, by - 10, 180])
w(io1b[0], by + 10, bx + 105, by + 10)
PT.append(['BI', 'USB%d_DM' % n, bx + 105, by + 10, 0])
w(io2b[0], by - 10, bx + 105, by - 10)
PT.append(['BI', 'USB%d_DP' % n, bx + 105, by - 10, 0])
# GND(2) 和 VBUS(5) 都夹在两个 I/O 中间,往哪边走都要跨过一条 D 线;
# 全页就这五处交叉,都是纯交叉(无结点)= 不相连,原理图的常规画法。
w(ug[0], ug[1], bx - 75, ug[1], bx - 75, by - 45)
F.append(['Ground', 'GND', bx - 75, by - 45, 0])
# VBUS(5) 不单独出端口 —— 往下搭到 56k 那一端的 VBUSn 节点上,
# 否则它的端口图元会和右边 D± 的两个端口挤成一团。
w(uv[0], uv[1], bx + 75, uv[1], bx + 75, uv[1] - 80)
# ---- 56k:CC1 -> VBUS ----
r = pins[d['rcc']]
ra = min(r.values(), key=lambda v: v[0])
rb = max(r.values(), key=lambda v: v[0])
w(bx - 80, ra[1], ra[0], ra[1])
PT.append(['BI', 'CC1_%d' % n, bx - 80, ra[1], 180])
w(rb[0], rb[1], bx + 80, rb[1])
PT.append(['BI', 'VBUS%d' % n, bx + 80, rb[1], 0])
# ---- 10k:口温 -> GND ----
r2 = pins[d['rth']]
r2a = min(r2.values(), key=lambda v: v[0])
r2b = max(r2.values(), key=lambda v: v[0])
w(bx - 80, r2a[1], r2a[0], r2a[1])
PT.append(['BI', 'THERM_%d' % n, bx - 80, r2a[1], 180])
w(r2b[0], r2b[1], bx + 60, r2b[1])
F.append(['Ground', 'GND', bx + 60, r2b[1], 0])
TX.append((bx - 140, by + 45, u'CH%d' % n, 12))
TX += [
(45, 806, u'【载板 C5-AUX】五路 USB 的 ESD + CC 上拉 + 口温电阻 —— 原厂尾插小板上本来就有的三样,方案 D 补回来', 14),
(45, 786, u'D± 从 C2-HUB 过来(USBn_DP/DM)→ 穿过 U5n → 去 C5-J1401 的座子(USBn_DPJ/DMJ)。VBUSn 来自 C3-USB 的负载开关。', 10),
(640, 340, u'R761~765 56k:CC1 → VBUS 的 Rp。整条链路里已经没有 USB-C 连接器了,', 10),
(640, 324, u'没人提供 Rp,手机看不到 Rp 就只充电不枚举。56k = Default USB 档;', 10),
(640, 308, u'配手机侧 Rd 5.1k 分压 0.32~0.52V,落在 UFP 的 vRd-USB 窗口', 10),
(640, 292, u'0.25~0.61V 内,两端各有 ≥70mV 余量(§23.3)。', 10),
(640, 268, u'⚠ R771~775 100k:从主板图纸反推(BIAS-[R640 20k]-TSKIN 分压,下臂是 NTC)。', 10),
(640, 244, u'⚠ U5n 的 1/6 是同一个 I/O1、3/4 是同一个 I/O2(芯片内部短接),而 EasyEDA', 10),
(640, 228, u' 会在引脚处截断导线,所以 D± 在网表里天然是两段(_P Hub 侧 / _J 座子侧)。', 10),
(640, 212, u' PCB 上走线必须从一个焊盘进、另一个焊盘出(串在线中),不能只搭一个脚;', 10),
(640, 196, u' 这颗不允许不贴,不贴就断线。', 10),
]
# 下发前的几何断言(skill §6c / §8a-2b)
for a, b, c, e in W:
assert (a == c) != (b == e), u'有一段导线不是横平竖直或零长度: %s' % ((a, b, c, e),)
for v in (a, b, c, e):
assert v % 5 == 0, u'导线坐标 %d 不是 5 的倍数: %s' % (v, (a, b, c, e))
for lst in (F, PT):
for it in lst:
assert it[2] % 5 == 0 and it[3] % 5 == 0, \
u'%s 坐标 (%d,%d) 不是 5 的倍数' % (it[1], it[2], it[3])
return W, F, PT, TX
def main():
push = '--push' in sys.argv
place = placement()
print(u'C5-AUX: %d 个位,元件 %d 个' % (len(SITES), len(place)))
if not push:
print(u'(未推送,加 --push)')
return
call("""
await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,1200));
const n=(await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
if (n!=='C5-AUX') throw new Error('page='+n);
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()==='sheet') continue;
await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId());
}
for (const w of await eda.sch_PrimitiveWire.getAll()) await eda.sch_PrimitiveWire.delete(w.getState_PrimitiveId());
for (const t of await eda.sch_PrimitiveText.getAll()) await eda.sch_PrimitiveText.delete(t.getState_PrimitiveId());
return 1;
""" % PAGE)
for i in range(0, len(place), 5):
call("""
for (const [d,uuid,x,y,rot] of %s){
await eda.sch_PrimitiveComponent.create({libraryUuid:'%s', uuid}, x, y, undefined, rot, false, true, true);
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()!=='part') continue;
if (Math.round(c.getState_X())!==x || Math.round(c.getState_Y())!==y) continue;
await c.toAsync().setState_Designator(d).done();
}
}
return 1;
""" % (json.dumps(place[i:i + 5]), LIB))
raw = call("""
const out={};
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()!=='part') continue;
out[c.getState_Designator()] = (await c.getAllPins()).map(
p=>[p.getState_PinNumber(), (p.getState_PinName?p.getState_PinName():''),
Math.round(p.getState_X()), Math.round(p.getState_Y())]);
}
return out;
""")
pins = {k: {p: (x, y) for p, _n, x, y in v} for k, v in raw.items()}
pnames = {k: {p: _n for p, _n, x, y in v} for k, v in raw.items()}
want = {'1': 'I/O1', '2': 'GND', '3': 'I/O2', '4': 'I/O2', '5': 'VBUS', '6': 'I/O1'}
bad = []
for n, _bx, _by in SITES:
got = pnames[desig(n)['u']]
for k, v in want.items():
if got.get(k, '').upper() != v.upper():
bad.append(u'%s.%s 期望 %s 实测 %s' % (desig(n)['u'], k, v, got.get(k)))
if bad:
raise SystemExit(u'引脚核对失败:\n ' + '\n '.join(bad))
W, F, PT, TX = wiring(pins)
def batch(items, size, js):
for i in range(0, len(items), size):
call(js % json.dumps(items[i:i + size], ensure_ascii=False))
batch(W, 40, "for (const s of %s) await eda.sch_PrimitiveWire.create(s); return 1;")
batch(PT, 25, "for (const p of %s) await eda.sch_PrimitiveComponent.createNetPort(p[0],p[1],p[2],p[3],p[4],false); return 1;")
batch(F, 25, "for (const f of %s) await eda.sch_PrimitiveComponent.createNetFlag(f[0],f[1],f[2],f[3],f[4],false); return 1;")
batch([list(t) for t in TX], 12,
"for (const t of %s) await eda.sch_PrimitiveText.create(t[0],t[1],t[2],0,null,null,t[3],false,false,false,0); return 1;")
print(u'C5-AUX: 导线 %d 旗 %d 端口 %d 文字 %d' % (len(W), len(F), len(PT), len(TX)))
if __name__ == '__main__':
main()