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

196 行
10 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""⛔⛔ **这个脚本已经过期,不要再 `--push`**(2026-09-06 起)。
它是「整页重画导线 / 旗 / 文字」的写法,几何底稿是 `p1-base.json` ——
那份快照停在 2026-09-04,**不知道后来的两轮改动**:
* `p1_r9f1.py`:R9 拆成 R9A~R9E(5 × 2Ω 2512 串联),F1 换成 12 A 那颗;
* `p1_text_fix.py`:说明文字去重 + Rs6/U13 那一组挪到 R9 链下方。
再推一次的后果是**静默回退**:BASE 里那条 `(780,300)-(780,270)` 和 (780,270) 的
GND 旗会被重新画出来,而 R9A~R9E 之间那 6 段新导线一条都不在 BASE 里 ⇒
五颗电阻串在半空中。`verify_sch.py` 会报「单引脚网络」,但要跑了才知道,
而 PCB 那边已经按新网表扇完出、铺完铜了。
真要再改这一页,就照 `p1_r9f1.py` 的写法做**增量**(先 create 后 delete、
旗删掉重建、坐标全 5 的倍数),别再整页重画。
────────────────────────────────────────────────────────────
原说明(留作记录):P1-INPUT 加输入侧 INA226(U13 + Rs6 + C11)。
几何底稿是 `p1-base.json`(加 U13 之前的整页回读快照)。
导线 / 旗 / 文字整页重画,**端口不动**——当时以为 P1 那 14 个端口的 dir(IN/OUT/BI)
API 读不回来,重建会把标签方向全弄错,所以只增量加 3 个新端口。
⚠️ **2026-09-06 更正:dir 读得回来** —— `getState_Component().name` 就是
`Netport-BI` / `Netport-IN` / `Netport-OUT`(skill §8b 末尾)。这一条限制可以解除了;
`.claude/pinmap.py` 就是靠它整批删掉重建端口的。
拓扑:Q1 漏极 → 体电容 C2~C6 / D1 → **Rs6(5mΩ)** → +12V(送五路 + 辅助 Buck + 泄放)
体电容在 Rs6 **上游**,Rs6 量到的是负载电流;开关纹波由各路自己的输入 MLCC 就近提供。
PCB 上 Rs6 要放在体电容与五路分叉点之间,Kelvin 四线焊盘。
排版上被两件事挤着,坐标是照着它们定的:
· U13 的型号文字 `INA226AIDGSR` 画在符号右边约 30~90 个单位处,宽 60,
所有旗都得躲开 x 933~993 / y 632~645 这一块;
· FB3V3 端口的标签一直伸到 x≈870(y 458~472),左边那几条竖线只能走 x≥845。
用法: python .claude/p1_add.py [--push]
"""
import io, json, os, sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from eda import run
PAGE = '9a6096762e036e23'
BASE = json.load(io.open(os.path.join(HERE, 'p1-base.json'), encoding='utf-8'))
DROP_WIRE = [((780, 770), (900, 770))] # 12V 母线右段,被 Rs6 一分为二
# U29(TL431) 的 REF / 阴极原来和 BLD_REF / BLD_A 两个端口**引脚坐标完全重合**,
# 靠零长度导线"连着"——和 P2 上 K1_VUP 是同一个毛病(见 §16.5)。
# 端口往左挪 40,补一段真导线。删掉旧的那两个端口,坐标见 DROP_PORT_XY。
DROP_PORT_XY = [(240, 200), (240, 190)]
DROP_FLAG = ['+12V', 900, 770, 0] # +12V 旗挪到 Rs6 下游
ADD_WIRE = [
((780, 770), (880, 770)), # 母线 -> Rs6.1(上游)
((920, 770), (960, 770)), # Rs6.2(下游)-> +12V 旗
((880, 670), (880, 770)), # VIN+ -> Rs6 上游焊盘
((890, 670), (890, 720)), ((890, 720), (920, 720)), ((920, 720), (920, 770)), # VIN-
((900, 670), (900, 720)), # VBUS 搭到 VIN- 上(测母线电压)
((910, 670), (910, 700)), ((910, 700), (1120, 700)), ((1120, 700), (1120, 675)), # GND
((920, 670), (920, 680)), ((920, 680), (1080, 680)), ((1080, 680), (1080, 660)), # VS+ -> C11.1
((1030, 680), (1030, 665)), # +3V3 旗(Power rot180,挂在导线下面)
((1080, 620), (1080, 605)), # C11.2 -> GND
((880, 570), (880, 545)), ((880, 545), (855, 545)), ((855, 545), (855, 500)), # A1 -> GND
((890, 570), (890, 440)), ((890, 440), (850, 440)),
((850, 440), (850, 320)), ((850, 320), (920, 320)), # A0 -> SCL
((900, 570), (900, 420)), # Alert
((910, 570), (910, 380)), # SDA
((920, 570), (920, 320)), # SCL(A0 搭在它下端)
((200, 200), (240, 200)), # BLD_REF -> U29.REF(原来是零长度导线)
((200, 190), (240, 190)), # BLD_A -> U29 阴极(同上)
]
ADD_FLAG = [
('+12V', 960, 770, 0),
('GND', 1120, 675, 0),
('GND', 1080, 605, 0),
('+3V3', 1030, 665, 180), # rot180 = 引脚在上、符号挂在下面
('GND', 855, 500, 0),
]
ADD_PORT = [
('INA_ALERT', 900, 420, 0, 'OUT'),
('SDA', 910, 380, 0, 'BI'),
('SCL', 920, 340, 0, 'BI'),
# BLD_REF / BLD_A 全部重建(清空那一步是按**网络名**删端口的,会把同名的都删掉)。
# dir 按截图上标签的朝向反推:五个都是标签背着导线走,等价于 BI。
('BLD_REF', 200, 200, 180, 'BI'), # 原来在 (240,200),和 U29.REF 引脚重合
('BLD_A', 200, 190, 180, 'BI'), # 原来在 (240,190),和 U29 阴极重合
('BLD_REF', 80, 235, 0, 'BI'),
('BLD_A', 385, 225, 0, 'BI'),
('BLD_A', 545, 280, 180, 'BI'),
]
ADD_TEXT = [
('【D】输入侧电流 / 电压监测', 850, 800, 12),
('Rs6 5mΩ: 满板约 8A -> 40mV(满量程 81.92mV)', 850, 300, 9),
('U13: A1=GND / A0=SCL -> 0x43(地址表见 §8.2)', 850, 287, 9),
('体电容在 Rs6 上游;PCB 上 Rs6 放在体电容与', 850, 274, 9),
('五路分叉点之间,Kelvin 四线焊盘', 850, 261, 9),
]
# 元件位置(place 见 __main__;坐标一改这里也要改)
PARTS = [
('U13', 'c19abc37bc174e89b3cbd0176ec08490', 900, 620, 90),
('Rs6', '845ad0fe0ddd489189487bf9c35bcea3', 900, 770, 0),
('C11', '87bb635d0b2f489a9f60e7cd225beb3c', 1080, 640, 270),
]
OPEN = "await eda.dmt_EditorControl.openDocument('%s');" % PAGE
def call(body, timeout=180):
r = run(OPEN + body, timeout=timeout)
if not r.get('success'):
print('失败:', json.dumps(r, ensure_ascii=False)[:400]); sys.exit(1)
return r['result']
def build():
drop_w = {tuple(sorted(w)) for w in DROP_WIRE}
wires = [tuple(map(tuple, w)) for w in BASE['wires'] if tuple(sorted(map(tuple, w))) not in drop_w]
assert len(wires) == len(BASE['wires']) - 1, '要删的母线段没找到'
wires += ADD_WIRE
flags = [tuple(f) for f in BASE['flags'] if list(f) != DROP_FLAG]
assert len(flags) == len(BASE['flags']) - 1, '要删的 +12V 旗没找到'
flags += ADD_FLAG
texts = [tuple(t) for t in BASE['texts']] + ADD_TEXT
return wires, flags, texts
def chunk(xs, k):
for i in range(0, len(xs), k):
yield xs[i:i + k]
if __name__ == '__main__':
if '--push' in sys.argv and '--i-know-it-is-stale' not in sys.argv:
sys.exit(u'⛔ p1_add.py 已过期,重推会把 R9A~R9E 的连线静默回退。'
u'理由见文件头;确实要跑就加 --i-know-it-is-stale。')
wires, flags, texts = build()
print('导线 %d 旗 %d 文字 %d 新增端口 %d' % (len(wires), len(flags), len(texts), len(ADD_PORT)))
if '--push' not in sys.argv:
sys.exit(0)
print('当前页:', call("return (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;"))
# ① 元件(已存在就只挪位置)
print('元件:', call("""
const LIB='0819f05c4eef4c71ace90d822a990e87';
const spec=%s, have={};
for (const c of await eda.sch_PrimitiveComponent.getAll())
if (c.getState_ComponentType()==='part') have[c.getState_Designator()]=c;
const out=[];
for (const [des,uuid,x,y,rot] of spec){
if (have[des]) { await have[des].toAsync().setState_X(x).setState_Y(y).setState_Rotation(rot).done(); out.push(des+' moved'); }
else { const c = await eda.sch_PrimitiveComponent.create({libraryUuid:LIB,uuid}, x, y, undefined, rot, false);
await c.toAsync().setState_Designator(des).done(); out.push(des+' created'); }
}
return out;""" % json.dumps([list(p) for p in PARTS])))
# ② 清掉导线 / 旗 / 文字,以及**我加过的那 3 个端口**(原有 14 个端口不动)
print('清空:', call("""
let w=0,f=0,t=0,p=0;
const mine = %s, mineXY = %s;
for (const x of await eda.sch_PrimitiveWire.getAll()){ await eda.sch_PrimitiveWire.delete(x.getState_PrimitiveId()); w++; }
for (const c of await eda.sch_PrimitiveComponent.getAll()){
const ty = c.getState_ComponentType();
if (ty==='netflag'){ await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); f++; }
else if (ty==='netport'){
const k = Math.round(c.getState_X())+','+Math.round(c.getState_Y());
if (mine.includes(c.getState_Net()) || mineXY.includes(k)){ await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); p++; }
}
}
for (const x of await eda.sch_PrimitiveText.getAll()){ await eda.sch_PrimitiveText.delete(x.getState_PrimitiveId()); t++; }
return {wires:w, flags:f, ports:p, texts:t};""" % (json.dumps(sorted({n for n, *_ in ADD_PORT})),
json.dumps(['%d,%d' % xy for xy in DROP_PORT_XY]))))
n = 0
for part in chunk([[a[0], a[1], b[0], b[1]] for a, b in wires], 40):
n += call("for (const s of %s) await eda.sch_PrimitiveWire.create([s[0],s[1],s[2],s[3]]); return %d;"
% (json.dumps(part), len(part)))
print('导线 %d 段' % n)
n = 0
for part in chunk([['Ground' if net == 'GND' else 'Power', net, x, y, r] for net, x, y, r in flags], 25):
n += call("for (const f of %s) await eda.sch_PrimitiveComponent.createNetFlag(f[0],f[1],f[2],f[3],f[4],false); return %d;"
% (json.dumps(part), len(part)))
print('旗 %d 个' % n)
n = call("for (const p of %s) await eda.sch_PrimitiveComponent.createNetPort(p[0],p[1],p[2],p[3],p[4],false); return %d;"
% (json.dumps([[d, net, x, y, r] for net, x, y, r, d in ADD_PORT]), len(ADD_PORT)))
print('端口 %d 个(新增)' % n)
n = 0
for part in chunk([[s, x, y, sz] for s, x, y, sz in texts], 20):
n += call("for (const t of %s) await eda.sch_PrimitiveText.create(t[1],t[2],t[0],0,null,null,t[3]); return %d;"
% (json.dumps(part), len(part)))
print('文字 %d 条' % n)