R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
+129
@@ -0,0 +1,129 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""C2-HUB(R1 载板):补 USB2517 晶振的 **1 MΩ 跨接电阻**(XTAL1–XTAL2)。
|
||||
|
||||
/c/Python314/python .claude/c2_xtal1m.py # 读活页 + 碰撞检查(干跑)
|
||||
/c/Python314/python .claude/c2_xtal1m.py --push # 放件 + 连线 + 端口 + 存盘
|
||||
|
||||
## 依据
|
||||
|
||||
USB2517 Hardware Design Checklist(DS00004211)p9 原文:「A 1 MΩ resistor connected across XTAL1 and XTAL2
|
||||
is **required**. Failure to place this resistor will result in unstable crystal operation.」
|
||||
数据手册 DS00001598C p52 Figure 9-1 典型晶振电路也画着 1Meg。
|
||||
2026-09-10 核实:XTAL1 / XTAL2 上只有 U32 / Y1 / C740 / C741 —— **R1 起就漏了**(R2 底板照搬,同样漏)。
|
||||
用户 2026-09-10 定:底板(`r2/a4_hub.py` 的 R723)和 R1 载板**都补**。
|
||||
|
||||
## ⚠ 用前
|
||||
|
||||
- 要先在客户端里**打开 R1 工程**(五路手机直供电源板):本脚本只 `openDocument` 这一页、不切工程,
|
||||
页名不是 `C2-HUB` 就停手(`c2_cfgsel.open_page()` 的断言)。切工程前先把 R2 那边存盘。
|
||||
- 只动原理图。载板 PCB(PCB2)上的摆位 / 布线另做 —— Y1 在 (95.8, 58.8)、C740 (92.8, 59.65)、
|
||||
C741 (96.9, 55.7)(`pcb2_place.hub_layout()`),导入变更之后要给这颗 0402 找位置并补两段短线。
|
||||
- 做完跑 `verify_sch.py --nets`(先打开这一页)+ `port_audit.py`,再让用户看截图。
|
||||
|
||||
## 画法(同底板 A4-HUB 的 R723)
|
||||
|
||||
Y1 正上方 y=800 那一行空着:电阻横放 rot 0(1 脚 x−20、2 脚 x+20,同页 R708 实测),
|
||||
两端各一段短线接 `XTAL1` / `XTAL2` 同名端口 —— 本页本来就是「一脚一段短线 + 同名端口」的画法。
|
||||
页面说明最上一行在 y=780(11 号字,盒子到 y≈789),端口标签在 y 795~805,不压字;右端标签到 x≈1115 < 边框 1145。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
from c2_cfgsel import call, open_page, dump, _on, FRAME # noqa: E402 (c2_cfgsel 的 main 在 __main__ 保护下)
|
||||
|
||||
DES = 'R723'
|
||||
LIB = '0819f05c4eef4c71ace90d822a990e87'
|
||||
R1M = '512988296f6244ad86a0777e419ee0f1' # C26083 0402WGF1004TCE 1MΩ(`lib_Device.get` 核过)
|
||||
RC = (1000, 800)
|
||||
PINS = sorted([[RC[0] - 20, RC[1]], [RC[0] + 20, RC[1]]])
|
||||
W = [((950, 800), (980, 800), 'XTAL1'), ((1020, 800), (1050, 800), 'XTAL2')]
|
||||
PT = [['BI', 'XTAL1', 950, 800, 180], # BI + rot180 ⇒ 符号在左,占 885~950
|
||||
['BI', 'XTAL2', 1050, 800, 0]] # BI + rot0 ⇒ 符号在右,占 1050~1115
|
||||
BOX = (880, 785, 1120, 815) # 新块(件 + 线 + 端口标签)要占的矩形
|
||||
|
||||
|
||||
def check(geo):
|
||||
errs = []
|
||||
if any(p[0] == DES for p in geo['parts']):
|
||||
errs.append(u'页上已经有 %s(跑过了?)—— 不重复放' % DES)
|
||||
inb = lambda x, y: BOX[0] <= x <= BOX[2] and BOX[1] <= y <= BOX[3] # noqa: E731
|
||||
for des, _x, _y, _r, pins in geo['parts']:
|
||||
for nm, px, py in pins:
|
||||
if inb(px, py):
|
||||
errs.append(u'引脚 %s.%s@(%d,%d) 落在新块里' % (des, nm, px, py))
|
||||
for net, x, y, _r in geo['flags']:
|
||||
if BOX[0] - 20 <= x <= BOX[2] + 20 and BOX[1] - 25 <= y <= BOX[3] + 10:
|
||||
errs.append(u'旗 %s@(%d,%d) 挨着新块' % (net, x, y))
|
||||
for net, x, y, _r, _nm in geo['ports']:
|
||||
if BOX[0] - 60 <= x <= BOX[2] + 20 and BOX[1] - 10 <= y <= BOX[3] + 10:
|
||||
errs.append(u'端口 %s@(%d,%d) 挨着新块' % (net, x, y))
|
||||
for w in geo['wires']:
|
||||
for i in range(0, len(w) - 3, 4):
|
||||
a, b = (w[i], w[i + 1]), (w[i + 2], w[i + 3])
|
||||
if inb(*a) or inb(*b):
|
||||
errs.append(u'已有导线 %s-%s 的端点在新块里' % (a, b))
|
||||
elif a[0] == b[0] and BOX[0] <= a[0] <= BOX[2] and min(a[1], b[1]) < BOX[3] and max(a[1], b[1]) > BOX[1]:
|
||||
errs.append(u'已有竖线 x=%d 穿过新块' % a[0])
|
||||
elif a[1] == b[1] and BOX[1] <= a[1] <= BOX[3] and min(a[0], b[0]) < BOX[2] and max(a[0], b[0]) > BOX[0]:
|
||||
errs.append(u'已有横线 y=%d 穿过新块' % a[1])
|
||||
for (a, b, net) in W:
|
||||
assert (a[0] == b[0]) != (a[1] == b[1]) and all(v % 5 == 0 for v in a + b), net
|
||||
for it in PT:
|
||||
assert it[2] % 5 == 0 and it[3] % 5 == 0
|
||||
if BOX[2] > FRAME[2] or BOX[3] > FRAME[3]:
|
||||
errs.append(u'新块越框 %s' % (BOX,))
|
||||
# XTAL1 / XTAL2 这两个网在页上必须已经存在(同名端口才连得上)
|
||||
have = set(p[0] for p in geo['ports'])
|
||||
for net in ('XTAL1', 'XTAL2'):
|
||||
if net not in have:
|
||||
errs.append(u'页上没有 %s 端口 —— 同名端口连不上' % net)
|
||||
return errs
|
||||
|
||||
|
||||
def main():
|
||||
push = '--push' in sys.argv
|
||||
geo = dump()
|
||||
print(u'当前页:元件 %d / 旗 %d / 端口 %d / 导线图元 %d'
|
||||
% (len(geo['parts']), len(geo['flags']), len(geo['ports']), len(geo['wires'])))
|
||||
errs = check(geo)
|
||||
if errs:
|
||||
print(u'检查 %d 条:' % len(errs))
|
||||
for e in errs:
|
||||
print(u' ' + e)
|
||||
raise SystemExit(u'未通过,不下发')
|
||||
print(u'碰撞检查通过:%s 放在 %s,XTAL1/XTAL2 两个端口' % (DES, RC))
|
||||
if not push:
|
||||
print(u'加 --push 执行')
|
||||
return
|
||||
|
||||
open_page()
|
||||
call("""
|
||||
await eda.sch_PrimitiveComponent.create({libraryUuid:'%s', uuid:'%s'}, %d, %d, undefined, 0, false, true, true);
|
||||
for (const c of await eda.sch_PrimitiveComponent.getAll()){
|
||||
if (c.getState_ComponentType()!=='part') continue;
|
||||
if (Math.round(c.getState_X())!==%d || Math.round(c.getState_Y())!==%d) continue;
|
||||
await c.toAsync().setState_Designator('%s').done();
|
||||
}
|
||||
return 1;""" % (LIB, R1M, RC[0], RC[1], RC[0], RC[1], DES))
|
||||
got = call("""
|
||||
for (const c of await eda.sch_PrimitiveComponent.getAll()){
|
||||
if (c.getState_ComponentType()!=='part' || c.getState_Designator()!=='%s') continue;
|
||||
return (await c.getAllPins()).map(p=>[Math.round(p.getState_X()),Math.round(p.getState_Y())]).sort();
|
||||
}
|
||||
return null;""" % DES)
|
||||
if sorted(got or []) != PINS:
|
||||
raise SystemExit(u'%s 引脚 %s,应为 %s —— 不连线,先查' % (DES, got, PINS))
|
||||
print(u'%s 引脚坐标核对通过 %s' % (DES, got))
|
||||
call("for (const s of %s) await eda.sch_PrimitiveWire.create(s); return 1;"
|
||||
% json.dumps([[a[0], a[1], b[0], b[1]] for a, b, _n in W]))
|
||||
call("for (const p of %s) await eda.sch_PrimitiveComponent.createNetPort(p[0],p[1],p[2],p[3],p[4],false); return 1;"
|
||||
% json.dumps(PT))
|
||||
call("return await eda.sch_Document.save();")
|
||||
print(u'推送完成并存盘 —— 接着打开这一页跑 verify_sch.py --nets / port_audit.py,再截图给用户看')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户