# -*- coding: utf-8 -*- u"""载板 C1-CONN:板对板排母 J31/J32 + I2C 温度芯片 U34。 用法: /c/Python314/python .claude/c1_conn.py [--push] ## 编号(2026-09-05 换件后统一,见设计文档 §22.3f) 下板公头 `C55020601` 与载板母座 `C5307344` **是同一套编号**: 第 r 行 = 引脚 2r−1(左列)/ 2r(右列),第 1 行在元件中心上方 95 个单位。 (换件前公头是 DIP 式 `r / 41−r`,两套编号对不上,那个陷阱已经消失。) 本脚本直接复用 p5_btb.py 里那两张按行组织的表,逐行生成,不做任何手工转写。 ⚠️ 但 PCB 上那条还在:符号编号一致 ≠ 第 r 行真对上第 r 行 —— 公头在下板顶层、母座在载板底层,底层件在顶视图里是镜像的。 以 `pcb2-header-pads.json` 导出的 80 个坐标为准。 """ import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run from p5_btb import J24, J25 # 逐行的 (左列网络, 右列网络),唯一事实来源 PAGE = '1cc8af8cba7ec7b2' # C1-CONN LIB = '0819f05c4eef4c71ace90d822a990e87' P = { 'HDRF': '786346fbe2e94eaf9cd217eca540dc4b', # C5307344 PC104-2*20(塑高 10.5,直插) 'LM75': '16898f8cfee9460099b731d841af8f30', # C34565 LM75BD,118 'C100N': '87bb635d0b2f489a9f60e7cd225beb3c', # C1525 100nF 0402 'LDO': '9f9c6cb41c7449fd8acf96aceed2661a', # C6186 AMS1117-3.3 (U31) 'C10U': '6e5726223dd84f70bc3b626fc7d1f72c', # C440198 10uF/50V 0805 'C22U': '0de9d28bbcd34ca3bf2a4d78faaa50bb', # C12891 22uF/25V 1206 } # 载板侧的两条排母,和下板 J24 / J25 一一对插 CONNS = [('J31', 'J24', 300, 500, J24), ('J32', 'J25', 800, 500, J25)] STUB = 40 ROW0 = 95 # 第 1 行相对元件中心的 y 偏移(实测;换件后公母两颗都是 95) DY = 10 U34 = (600, 300) # LM75BD 中心 # U31 从下板 P1B-5V 搬过来:载板上唯一的 3.3V 来源(§9.5)。 # 放在 C1-CONN 而不是 C2-HUB,是因为 +5V 从排针进来就在这一页; # §9.5 要求的「就近 Hub 供电」是 PCB 摆位约束,和原理图分页无关。 U31 = (250, 330) C736, C737 = (165, 300), (400, 315) C760 = (760, 295) # 0402 引脚跨距是 ±20(实测),放 295 才让上脚正好落在 315 NC = {'U34': ['3']} # OS 过温输出不用 def call(js, timeout=180): r = run(js, timeout=timeout) if not r.get('success'): raise RuntimeError(json.dumps(r, ensure_ascii=False)[:800]) return r['result'] def plan(): 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]]) # ⚠️ 2026-09-05 起 GND 也走 netport,不再画地母线 —— 逐针表按几何重排之后 # GND 散在两列的若干段,每段配母线 + 地旗的话旗会掉进相邻行的端口里 # (行距 10,旗高 15~20,skill §7)。成因与新分配见 p5_btb.py 的表头 / §25.10。 for desig, mate, cx, cy, rows in CONNS: assert len(rows) == 20, u'%s 必须正好 20 行' % desig lx, rx = cx - 30, cx + 30 lp, rp = lx - STUB, rx + STUB for i, (nnet, snet) in enumerate(rows): y = cy + ROW0 - DY * i w(lx, y, lp, y) PT.append(['BI', nnet, lp, y, 180]) w(rx, y, rp, y) PT.append(['BI', snet, rp, y, 0]) TX.append((cx - 60, cy + ROW0 + 45, u'%s(对插下板 %s)北排(奇)↑ / 南排(偶)↓' % (desig, mate), 11)) # ---- U34 LM75BD:1=SDA 2=SCL 3=OS 4=GND 5=A2 6=A1 7=A0 8=VCC ---- ux, uy = U34 w(ux - 40, uy + 15, ux - 80, uy + 15) PT.append(['BI', 'SDA', ux - 80, uy + 15, 180]) w(ux - 40, uy + 5, ux - 80, uy + 5) PT.append(['BI', 'SCL', ux - 80, uy + 5, 180]) w(ux - 40, uy - 15, ux - 80, uy - 15, ux - 80, uy - 45) # 4 GND F.append(['Ground', 'GND', ux - 80, uy - 45, 0]) # A0/A1/A2 全部接地 -> 0x48 for dy in (5, -5, -15): w(ux + 40, uy + dy, ux + 80, uy + dy) w(ux + 80, uy + 5, ux + 80, uy - 45) F.append(['Ground', 'GND', ux + 80, uy - 45, 0]) # 8 VCC -> +3V3_HUB,顺路接 C760 w(ux + 40, uy + 15, C760[0], uy + 15) w(ux + 100, uy + 15, ux + 100, uy + 45) F.append(['Power', '+3V3_HUB', ux + 100, uy + 45, 0]) w(C760[0], C760[1] - 20, C760[0], uy - 45) F.append(['Ground', 'GND', C760[0], uy - 45, 0]) # ---- U31 AMS1117-3.3:1=GND 2=VOUT 3=VIN 4=VOUT(散热片) ---- w(205, 320, 120, 320) # 3 VIN PT.append(['BI', '+5V', 120, 320, 180]) w(165, 280, 165, 255) # C736 下极 -> GND(上极搭在 VIN 那条线上) F.append(['Ground', 'GND', 165, 255, 0]) # ⚠ 这里原来写的是 362:旗的位置被吸附到 360,导线端点却留在 362, # 旗的引脚就落在导线**中段** —— 渲染正常、verify_sch 也过,但 J31 pin1 的地 # 实际是断的(port_audit 抓到的)。坐标必须落在 5 的倍数上,见下面的断言。 w(205, 340, 175, 340, 175, 360) # 1 GND(旗压得低一点,别顶到 J31 的 HUB_RST 标签) F.append(['Ground', 'GND', 175, 360, 180]) w(205, 330, 145, 330, 145, 375) # 2 VOUT PT.append(['BI', '+3V3_HUB', 145, 375, 180]) w(295, 330, 430, 330, 430, 355, 460, 355) # 4 VOUT(散热片),C737 搭在中途 PT.append(['BI', '+3V3_HUB', 460, 355, 0]) w(400, 300, 400, 275) # C737 下极 -> GND F.append(['Ground', 'GND', 400, 275, 0]) TX += [ (45, 806, u'【载板 C1-CONN】板对板排母 + 5V→3.3V + I2C 温度芯片', 14), (45, 784, u'J31 / J32 是 2×20 排母,分别对插下板的 J24 / J25。逐针定义见设计文档 §22.2。', 10), (45, 764, u'编号:第 r 行 = 引脚 2r−1(北排,左)/ 2r(南排,右)—— 公头 C55020601 与母座 C5307344 同一套(§22.3f 实测)。', 10), (45, 748, u'分配按载板几何排(§25.10):北排喂北边的站、南排喂南边的站;J31 正对 site1/site4,J32 正对 site3/site5,site2 取 J31 北排最右三针。', 10), (45, 732, u'母座 C5307344(PC104-2*20):塑高 10.5、直插、行距 2.54、2.5A 每针;', 10), (45, 716, u'配下板公头(塑高 2.5 / 配合针长 6)对插 = 13.0mm 板间距,由塑料对塑料定位。', 10), (45, 694, u'⚠ PCB 上仍要实打实核一次「第 r 行对第 r 行」:母座在载板底层,顶视图里是镜像的。', 10), (45, 678, u'⚠ 到货核两件:触头镀层(商城只写「黄铜」)、对插后板间距是否真的 13.0mm。', 10), (45, 205, u'U34 LM75BD:I2C 温度传感器,A0/A1/A2 全接地 → 地址 0x48(§8.2 里 0x48 是空的)。', 10), (45, 189, u'它测的是载板温度(手机底下),下板那两路 NTC 测的是功率级 —— 风扇反馈要用这一颗。', 10), (45, 173, u'供电取 +3V3_HUB(U31 就在本页右下)。排针上没有 3.3V,载板的 3.3V 只有这一个来源。', 10), (45, 157, u'OS(过温输出,pin3)不接。', 10), (800, 350, u'U31 AMS1117-3.3(从下板 P1B-5V 搬过来):', 10), (800, 334, u'载板上唯一的 3.3V 来源 —— U32 Hub、U34、', 10), (800, 318, u'五路 EDL 模拟开关都吃它。Hub 满配约 420mA,', 10), (800, 302, u'耗散 0.42A×1.7V≈0.71W:SOT-223 要铺铜散热,', 10), (800, 286, u'且必须摆在 U32 旁边(§9.5 / §10 就近供电)。', 10), ] # ---- 下发前的几何断言(skill §6c:几何量必须读回,且坐标吸附到 5 的倍数)---- # 旗/端口的坐标会被吸附到 5 的倍数,导线端点不会。两边对不齐 2 个单位, # 旗就落在导线中段 = 真断线,而 verify_sch 和 DRC 都看不出来。 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 kind, lst in (('flag', F), ('port', PT)): for it in lst: x, y = it[2], it[3] assert x % 5 == 0 and y % 5 == 0, u'%s %s 的坐标 (%d,%d) 不是 5 的倍数' % (kind, it[1], x, y) return W, F, PT, TX def main(): push = '--push' in sys.argv W, F, PT, TX = plan() print(u'导线 %d 旗 %d 端口 %d 文字 %d' % (len(W), len(F), len(PT), len(TX))) if not push: return call(""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,900)); const n=(await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name; if (n!=='C1-CONN') 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) print(u'C1-CONN 已清空') place = [[d, P['HDRF'], cx, cy, 0] for d, _, cx, cy, _ in CONNS] place.append(['U34', P['LM75'], U34[0], U34[1], 0]) place.append(['C760', P['C100N'], C760[0], C760[1], 270]) place.append(['U31', P['LDO'], U31[0], U31[1], 0]) place.append(['C736', P['C10U'], C736[0], C736[1], 270]) place.append(['C737', P['C22U'], C737[0], C737[1], 270]) for i in range(0, len(place), 4): 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 + 4]), LIB)) print(u'已放置 %d 个元件' % len(place)) pins = 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; """) bad = [] for desig, _, cx, cy, _ in CONNS: pm = {p: (x, y) for p, _n, x, y in pins[desig]} for r in range(20): y = cy + ROW0 - DY * r if pm.get(str(2 * r + 1)) != (cx - 30, y): bad.append(u'%s 第%d行左 = pin%d,期望 %s 实测 %s' % (desig, r + 1, 2 * r + 1, (cx - 30, y), pm.get(str(2 * r + 1)))) if pm.get(str(2 * r + 2)) != (cx + 30, y): bad.append(u'%s 第%d行右 = pin%d,期望 %s 实测 %s' % (desig, r + 1, 2 * r + 2, (cx + 30, y), pm.get(str(2 * r + 2)))) want34 = {'1': (U34[0] - 40, U34[1] + 15), '2': (U34[0] - 40, U34[1] + 5), '4': (U34[0] - 40, U34[1] - 15), '8': (U34[0] + 40, U34[1] + 15), '7': (U34[0] + 40, U34[1] + 5), '6': (U34[0] + 40, U34[1] - 5), '5': (U34[0] + 40, U34[1] - 15)} pm = {p: (x, y) for p, _n, x, y in pins['U34']} nm = {p: _n for p, _n, x, y in pins['U34']} for p, xy in want34.items(): if pm.get(p) != xy: bad.append(u'U34.%s 期望 %s 实测 %s' % (p, xy, pm.get(p))) print(u'U34 引脚名: %s' % ' '.join('%s=%s' % (k, nm[k]) for k in sorted(nm, key=int))) pmc = {p: (x, y) for p, _n, x, y in pins['C760']} want_c = sorted([(C760[0], C760[1] - 20), (C760[0], C760[1] + 20)]) if sorted(pmc.values()) != want_c: bad.append(u'C760 引脚期望 %s 实测 %s' % (want_c, sorted(pmc.values()))) if (C760[0], U34[1] + 15) not in pmc.values(): bad.append(u'C760 没有引脚落在 +3V3 那条横线 y=%d 上' % (U34[1] + 15)) want31 = {'1': (205, 340), '2': (205, 330), '3': (205, 320), '4': (295, 330)} pm31 = {p: (x, y) for p, _n, x, y in pins['U31']} for p, xy in want31.items(): if pm31.get(p) != xy: bad.append(u'U31.%s 期望 %s 实测 %s' % (p, xy, pm31.get(p))) # C736 的上极必须正好落在 VIN 那条横线(y=320)上,下极是接地那头 v736 = sorted(v[1] for v in {p: (x, y) for p, _n, x, y in pins['C736']}.values()) if v736 != [280, 320]: bad.append(u'C736 引脚 y 期望 [280, 320] 实测 %s' % v736) v737 = sorted(v[1] for v in {p: (x, y) for p, _n, x, y in pins['C737']}.values()) if v737 != [300, 330]: bad.append(u'C737 引脚 y 期望 [300, 330] 实测 %s' % v737) if bad: raise SystemExit(u'引脚坐标对不上:\n ' + '\n '.join(bad)) print(u'引脚坐标全部核对通过') 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], 15, "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;") json.dump(NC, open(os.path.join(HERE, 'c1-nets-nc.json'), 'w', encoding='utf-8'), ensure_ascii=False, indent=1) print(u'推送完成') if __name__ == '__main__': main()