# -*- coding: utf-8 -*- u"""载板 C5-J1401A / C5-J1401B:五颗对插手机尾插排线的 BM24-50DS 座(方案 D,§13.5)。 手机主板 J1401 ──原厂「主板→尾插」排线── 本页的 J71~J75 ──> 载板上的 USB 电路。 一页三个位(A:CH1~3)、一页两个位(B:CH4~5)—— 一个 52 脚符号带 12 个地 再加信号引出,一个位要 350 单位宽,一页塞不下五个。 ⚠️⚠️ 本页按 **§13.4 的 Hirose 编号**连线(D+=46 / D−=48 / CC1=30 / 口温=38 / 地=3,9,13,21,27,28,31,36,40,44,49,50 / 两个电源触点 51,52)。 排线两端是不是 1:1 直通**还没实测**,但那只影响封装不影响本页: 这颗座子本来就必须自建封装(立创那个是 DIP 绕圈编号,和 Hirose 对不上,见 §13.4 末尾), 自建时把「焊盘号 = 经排线看过去的 Hirose 脚号」定死即可。 ⚠️ `PWR` 里「哪个电源触点是地」同样是待实测项 —— 接反就是 5V 对地短路。 用法: /c/Python314/python .claude/c5_j1401.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 LIB = '0819f05c4eef4c71ace90d822a990e87' # ⚠ 2026-09-04 改用**自建封装版**器件(个人库),不再用立创原件: # 立创封装 CONN-SMD_BBM24-50DS-2-0.35V51 是 DIP 绕圈编号,和 Hirose 奇偶行对不上(§13.4)。 # 自建封装 BM24-50DS_HIROSE-PIN 几何来自 Hirose 官方样本,见 .claude/fp_bm24.py。 LIB_BM24 = '543725e8f0454017a5691e7e570aeef6' # 个人库 BM24 = '771dbf002b714bb79cde47cf86aa0fb1' # BM24-50DS/2-0.35V(51) HRS-PIN BM24_LCSC = 'daa43b90a3484a8ca91a9b599d4254d8' # 作废:C424561 原件(DIP 绕圈封装) TPRED = '17b5ed105c5b4fe180eee3ffb4702963' # C9900007423 红色测试点(引脚在 cx-20) PAGES = [('C5-J1401A', '967543b084075b4b', [1, 2, 3]), ('C5-J1401B', '63413775cfc3aafc', [4, 5])] CX = [270, 620, 970] # 一页里第 1/2/3 个位的中心 x CY = 560 # 座子中心 y # ---- J1401 引脚用途(§13.4 的 Hirose 编号)---- J1401_GND = [3, 9, 13, 21, 27, 31, 49, 28, 36, 40, 44, 50] J1401_SIG = [(30, 'CC1_%d'), (38, 'THERM_%d'), (46, 'USB%d_DPJ'), (48, 'USB%d_DMJ')] # CC2(32) 必须悬空;调试串口 24/29 故意不引(1.8V,接 ESP32 要电平转换) def map_pin(n): u"""J1401 的 Hirose 脚号 -> 载板这颗座子上的脚号(2026-09-04 实测 + 推导,§13.5)。 实测:原厂排线在摊平的平面里是**恒等映射** —— 上方内侧↔下方内侧、上方外侧↔下方外侧, 从左到右一一导通(用户量的)。照片又确认两个公头在**同一面**、相差 180°。 于是在各自的本体坐标里:公头A(某排, 第 m 个) ↔ 公头B(同排, 第 26-m 个) —— 排不变、序号反过来。母座与插上去的公头引脚一一重合,这条关系原样传给两个母座: J1401(排, m) ↔ 载板座子(同排, 26-m) 再套 §13.4 已证明的奇偶排编号(奇数一排 / 偶数一排): 奇数 N -> 50-N 偶数 N -> 52-N 校验:1->49, 2->50, 49->1, 50->2,奇偶不变(排没换)✔ **这条推导不需要知道「哪一排是奇数排」**,那个未知量在两边抵消掉了。 """ return (50 - n) if n % 2 else (52 - n) GND_PINS = sorted(map_pin(n) for n in J1401_GND) SIG_PINS = [(map_pin(n), tpl) for n, tpl in J1401_SIG] GND_L = [k for k in GND_PINS if k <= 25] GND_R = [k for k in GND_PINS if k > 25] SIG_L = [(k, t) for k, t in SIG_PINS if k <= 25] SIG_R = [(k, t) for k, t in SIG_PINS if k > 25] # 两个电源触点。约定(自建封装时按这个编): # 符号 pin 51 = Hirose「pin1/pin2 那一端」的电源触点 # 符号 pin 52 = Hirose「pin49/pin50 那一端」的电源触点 # J1401 的 G1(地) 在它的 pin1/2 端;经过上面那个「序号反过来」的映射, # 落到我们这颗的 pin49/50 端 -> **52 = GND,51 = VBUS**。 # ⚠ 仍需首件实测确认:接反就是 5V 对地短路。 PWR = {'GND': 52, 'VBUS': 51} USED = set(GND_PINS + [k for k, _ in SIG_PINS]) NC_PINS = [str(k) for k in range(1, 51) if k not in USED] # 相对 cx 的三档引出长度。左侧现在既有 8 个地又有 4 个信号: # **地母线必须在信号走廊的外侧**(信号短、地长),否则信号线会横穿地母线 = 短路。 # 端口标签比想象中宽(8 字名字约占 90 单位),所以地母线要留够 —— 拉到 200。 X_GNDL, X_SIG, X_GNDR = -200, 70, 90 Y_VBUS = 730 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 {'j': 'J%d' % (70 + n)} def placement(sites): out = [] for i, n in enumerate(sites): d, cx = desig(n), CX[i] out.append([d['j'], BM24, cx, CY, 0, LIB_BM24]) return out def wiring(sites, 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 i, n in enumerate(sites): d, cx = desig(n), CX[i] pm = pins[d['j']] # ---- 左列的地:短线到母线(母线放在信号走廊外侧,两者不会相交)---- ys = sorted(pm[str(k)][1] for k in GND_L) for k in GND_L: px, py = pm[str(k)] w(px, py, cx + X_GNDL, py) w(cx + X_GNDL, ys[0], cx + X_GNDL, ys[-1] + 40) F.append(['Ground', 'GND', cx + X_GNDL, ys[-1] + 40, 180]) # ---- 左列信号 -> 端口(走廊比地母线短)---- for k, tpl in SIG_L: px, py = pm[str(k)] w(px, py, cx - X_SIG, py) PT.append(['BI', tpl % n, cx - X_SIG, py, 180]) # ---- 右列的地:同上,母线放得更外,给信号让出走廊 ---- ysr = sorted(pm[str(k)][1] for k in GND_R) for k in GND_R: px, py = pm[str(k)] w(px, py, cx + X_GNDR, py) w(cx + X_GNDR, ysr[0], cx + X_GNDR, ysr[-1] + 20) F.append(['Ground', 'GND', cx + X_GNDR, ysr[-1] + 20, 180]) # ---- 右列信号 -> 端口(走廊比地母线短,两者不会相交)---- for k, tpl in SIG_R: px, py = pm[str(k)] w(px, py, cx + X_SIG, py) PT.append(['BI', tpl % n, cx + X_SIG, py, 0]) # ---- 两个电源触点 ---- vx, vy = pm[str(PWR['VBUS'])] w(vx, vy, vx, Y_VBUS, cx + X_SIG, Y_VBUS) PT.append(['BI', 'VBUS%d' % n, cx + X_SIG, Y_VBUS, 0]) gx, gy = pm[str(PWR['GND'])] w(gx, gy, gx, gy - 30) F.append(['Ground', 'GND', gx, gy - 30, 0]) TX.append((cx - 140, CY + 175, u'CH%d 手机位' % n, 12)) return W, F, PT, TX def page_text(name, sites): return [ (45, 806, u'【载板 %s】手机尾插 J1401 对插座 —— CH%s(方案 D,§13.5)' % (name, '/'.join(str(x) for x in sites)), 14), (45, 786, u'手机主板 J1401 ──原厂「主板→尾插」排线── 本页的 J7x 座 ──> C3-USB 上的 ESD / Rp / 口温 / VBUS 开关。', 10), (45, 350, u'引脚映射(2026-09-04 实测 + 推导,§13.5):原厂排线在摊平的平面里是**恒等映射**', 12), (45, 332, u'(上方内侧↔下方内侧、上方外侧↔下方外侧,从左到右一一导通,用户实测);', 10), (45, 316, u'两个公头在同一面、相差 180°。于是 J1401(排, m) ↔ 本座子(同排, 26-m):', 10), (45, 296, u' 奇数脚 N → 50-N 偶数脚 N → 52-N', 12), (45, 276, u'即 D+ 46→6、D- 48→4、CC1 30→22、口温 38→14;地 12 个 → 1,2,8,12,16,19,23,24,29,37,41,47。', 10), (45, 260, u'(这条推导不需要知道「哪一排是奇数排」—— 两边都用 Hirose 脚号表达,那个未知量抵消了。)', 10), (45, 232, u'⚠ 电源触点约定:符号 pin51 = Hirose pin1/2 那一端,pin52 = pin49/50 那一端。', 10), (45, 216, u' J1401 的 G1(地) 在它的 pin1/2 端,经上面的反序落到我们这颗的 pin49/50 端', 10), (45, 200, u' → **52 = GND,51 = VBUS**。⚠ 首件仍要实测,接反就是 5V 对地短路。', 10), (620, 350, u'⚠ 机械:两个公头同一面 → 两个母座都得朝上、大致共面。', 10), (620, 334, u' 而 J1401 朝天、本座子也朝天、载板在手机下面,所以排线必须走', 10), (620, 318, u' **台阶式下折**(下 90° 再上 90°,净旋转为零,公头仍朝下),', 10), (620, 302, u' **不能走 U 形回折**(会把面翻过来,插不进去)。', 10), (620, 286, u' → 本座子要放在手机轮廓之外、J1401 出线的那一侧,PCB 摆位时定死。', 10), (620, 258, u'⚠ BM24 只有 10 次插拔寿命,而一根排线上有两个插拔点 —— 插上就别反复拔。', 10), (620, 234, u'⚠ 没画的引脚(音频 / 射频控制 / 各路 VREG / CC2)全部悬空。', 10), (620, 218, u' CC2 必须悬空 —— 只拉一边 CC,手机才判得出插入方向。', 10), (620, 202, u'⚠ 调试串口(pin24 TX / pin29 RX)故意不引:1.8V 电平,接 ESP32 要电平转换。', 10), ] def build(name, page, sites, push): if not 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!=='%s') 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, name)) place = placement(sites) for i in range(0, len(place), 3): call(""" for (const [d,uuid,x,y,rot,lib] of %s){ await eda.sch_PrimitiveComponent.create({libraryUuid:lib, 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 + 3])) # 座子的默认名 'BM24-50DS/2-0.35V(51)' 21 个字,压在符号下面很难看 call(""" for (const c of await eda.sch_PrimitiveComponent.getAll()){ if (/^J7[1-5]$/.test(c.getState_Designator())) await c.toAsync().setState_Name('BM24-50DS').done(); } return 1; """) 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()} for n in sites: d = desig(n) if len(pins[d['j']]) != 52: raise SystemExit(u'%s 引脚数 %d,期望 52(50 信号 + 2 个电源触点)' % (d['j'], len(pins[d['j']]))) W, F, PT, TX = wiring(sites, pins) TX = TX + page_text(name, sites) # 下发前的几何断言(skill §6c / §8a-2b) for a, b, c, e in W: assert (a == c) != (b == e), u'%s 有一段导线不是横平竖直或零长度: %s' % (name, (a, b, c, e)) for v in (a, b, c, e): assert v % 5 == 0, u'%s 导线坐标 %d 不是 5 的倍数: %s' % (name, 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 的 %s 坐标 (%d,%d) 不是 5 的倍数' % (name, it[1], it[2], it[3]) 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'%s: 元件 %d 导线 %d 旗 %d 端口 %d 文字 %d' % (name, len(place), len(W), len(F), len(PT), len(TX))) def main(): push = '--push' in sys.argv for name, page, sites in PAGES: if not push: print(u'%s: %d 个位,元件 %d 个' % (name, len(sites), len(placement(sites)))) continue build(name, page, sites, True) if not push: print(u'(未推送,加 --push)') return ncf = os.path.join(HERE, 'c5-nets-nc.json') json.dump({desig(n)['j']: NC_PINS for n in (1, 2, 3, 4, 5)}, open(ncf, 'w', encoding='utf-8'), ensure_ascii=False, indent=1) print(u'NC 清单已写入 %s(每颗座子 %d 个悬空脚)' % (ncf, len(NC_PINS))) if __name__ == '__main__': main()