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

176 行
8.7 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""载板 C3-USB:五路 USB 的 VBUS 负载开关(TPS22918)。
2026-09-04 方案 D 改造:**删掉 J17~J21 那五个 1.25mm 出线座**。
现在 USB 是「C2-HUB → C5-AUX 的 ESD → C5-J1401 的 BM24 座 → 原厂排线 → 手机」,
本页只负责切 VBUS,D± 根本不经过这一页。
本页原来由 `p4_nets.py` + `build.py` 生成(那时还叫 P4-USB,在下板上)。
这个脚本是自带布线的重写版:**元件位置一律沿用页面上现成的**(只删 J1x),
所以不会动到已经验证过的开关部分排版。
用法: /c/Python314/python .claude/c3_usb.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 = 'f924c6608ffd1bc1' # C3-USB
XS = [130, 345, 560, 775, 990]
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 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, X in enumerate(XS, 1):
u = pins['U%d' % (23 + n)]
c70, c71, c72 = pins['C70%d' % n], pins['C71%d' % n], pins['C72%d' % n]
r70 = pins['R70%d' % n]
# 元件位置沿用页面上现成的 —— 这里把每一个用到的坐标都断言一遍
exp = [(u['1'], (X + 75, 600), 'U.VIN'), (u['2'], (X + 85, 600), 'U.GND'),
(u['3'], (X + 95, 600), 'U.ON'), (u['4'], (X + 95, 480), 'U.CT'),
(u['5'], (X + 85, 480), 'U.QOD'), (u['6'], (X + 75, 480), 'U.VOUT'),
(c70['2'], (X + 35, 595), 'C70.2'), (c70['1'], (X + 35, 555), 'C70.1'),
(c71['1'], (X + 55, 420), 'C71.1'), (c71['2'], (X + 55, 380), 'C71.2'),
(c72['1'], (X + 95, 450), 'C72.1'), (c72['2'], (X + 95, 420), 'C72.2'),
(r70['1'], (X + 125, 660), 'R70.1'), (r70['2'], (X + 125, 620), 'R70.2')]
for got, want, nm in exp:
assert tuple(got) == want, u'CH%d 的 %s 实测 %s 期望 %s' % (n, nm, got, want)
# ---- 板上 5V 轨 -> 开关 VIN,带 2.2µF 输入电容 C70n ----
# +5V 端口原来放在 y=660,正好和 (X+65,640) 那个朝上的 GND 旗叠在一起(截图上看出来的),
# 往上挪到 700 就干净了 —— VBUS_EN 的横线在 y=685,只跨 X+25~X+95,不碰 X+15。
w(X + 15, 700, X + 15, 610, X + 75, 610)
w(X + 75, 610, X + 75, 600)
PT.append(['BI', '+5V', X + 15, 700, 0])
w(X + 35, 610, X + 35, 595)
w(X + 35, 555, X + 35, 540)
F.append(['Ground', 'GND', X + 35, 540, 0])
# ---- 开关自己的地 ----
w(X + 85, 600, X + 85, 620, X + 65, 620)
w(X + 65, 620, X + 65, 640)
F.append(['Ground', 'GND', X + 65, 640, 180])
# ---- ON:VBUS_ENn 端口 + 100k 下拉 R70n ----
w(X + 95, 600, X + 95, 685, X + 25, 685)
w(X + 25, 685, X + 25, 675)
PT.append(['BI', 'VBUS_EN%d' % n, X + 25, 675, 0])
w(X + 95, 660, X + 125, 660)
w(X + 125, 620, X + 125, 605)
F.append(['Ground', 'GND', X + 125, 605, 0])
# ---- 输出 VBUSn:QOD 短接 VOUT,100nF 输出电容 C71n,再送去 C5-AUX / C5-J1401 ----
w(X + 75, 480, X + 75, 330)
w(X + 75, 330, X + 15, 330)
PT.append(['BI', 'VBUS%d' % n, X + 15, 330, 180])
w(X + 85, 480, X + 85, 455, X + 75, 455)
w(X + 75, 420, X + 55, 420)
w(X + 55, 380, X + 55, 365)
F.append(['Ground', 'GND', X + 55, 365, 0])
# ---- CT 摆率电容 C72n ----
w(X + 95, 480, X + 95, 450)
w(X + 95, 420, X + 95, 405)
F.append(['Ground', 'GND', X + 95, 405, 0])
TX.append((X + 45, 705, u'CH%d' % n, 13))
TX += [
(45, 796, u'【载板 C3-USB】五路 VBUS 负载开关(TPS22918)—— 本页只切电,D± 不经过这一页', 14),
(45, 776, u'USB 链路:C2-HUB 的 U32 → C5-AUX 的 ESD → C5-J1401 的 BM24 座 → 原厂排线 → 手机 J1401。', 10),
(45, 185, u'TPS22918: 1~5.5V / 2A / Ron 52mΩ,ON 脚不得悬空', 11),
(45, 168, u'R701~705 100k 下拉 → ESP32 复位期间 VBUS 全断', 11),
(45, 151, u'CT 100pF C0G: tR≈425µs,涌流≈94mA(10µF 负载)', 11),
(45, 134, u'QOD 短接 VOUT: 内部 RPD≈24Ω,关断约 200µs 归零', 11),
(45, 117, u'CIN 2.2µF > COUT 100nF;VBUS 来自板上 5V 轨 U30,不吃上游线电流(§9.4)', 11),
(45, 100, u'2026-09-04 方案 D:J17~J21 五个 1.25mm 出线座已删 —— USB 改走原厂尾插排线(§13.5)', 11),
]
# 下发前的几何断言(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
if not push:
print(u'C3-USB:删 J17~J21,重画布线(元件位置沿用现成的)。加 --push 执行')
return
# 只删 J17~J21 这五个连接器 + 全部旗/端口/导线/文字;其余元件原地不动
n = call("""
await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,1400));
const nm=(await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
if (nm!=='C3-USB') throw new Error('page='+nm);
let conn=0, flag=0;
for (const c of await eda.sch_PrimitiveComponent.getAll()){
const t=c.getState_ComponentType();
if (t==='sheet') continue;
if (t==='netflag'||t==='netport'){ await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); flag++; continue; }
if (t==='part' && /^J(1[7-9]|2[01])$/.test(c.getState_Designator())){
await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); conn++; }
}
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());
const left=(await eda.sch_PrimitiveComponent.getAll()).filter(c=>c.getState_ComponentType()==='part').length;
return {conn, flag, left};
""" % PAGE)
print(u'删掉连接器 %d 个、旗/端口 %d 个,剩余元件 %d 个' % (n['conn'], n['flag'], n['left']))
# 第一次跑删掉 5 个;之后重跑时它们已经不在了,conn=0 也是正常的。
# ⚠ 这个守卫原来写成 `!=5 就退出`,第二次跑就会在「导线已经删光」之后才退出,
# 把页面留在半清空状态(2026-09-04 踩过一次)。守卫要么放在删之前,要么就别拦。
if n['conn'] not in (0, 5):
raise SystemExit(u'期望删掉 5 个或 0 个连接器,实际 %d 个 —— 先查清楚再跑' % n['conn'])
if n['left'] != 25:
raise SystemExit(u'剩余元件 %d 个,期望 25(5 路 x 5 件)—— 先查清楚再跑' % n['left'])
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(), Math.round(p.getState_X()), Math.round(p.getState_Y())]);
}
return out;
""")
pins = {k: {p: (x, y) for p, x, y in v} for k, v in raw.items()}
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'C3-USB: 导线 %d 旗 %d 端口 %d 文字 %d' % (len(W), len(F), len(PT), len(TX)))
if __name__ == '__main__':
main()