# -*- coding: utf-8 -*- u"""Board3 / T1-USBTAP:小米8 尾插 J1401 转接小板(第三块板,每台手机一块)。 作用:从手机主板正面的 J1401(Hirose BM24-50DS/2-0.35V)取出 USB, 用 1.25mm 4P 线接到载板的 J17~J21。§13.5 方案 C。 板上内容: J91 BM24-50DP/2-0.35V(51) 对插 J1401 J92 A1251WV-S-4P 出线到载板(1=VBUS 2=D- 3=D+ 4=GND,和 C3-USB 的 J17 一致) R901 56k CC1(pin30) -> VBUS 的 Rp(Default USB 档),§13.5 必补项 1 R902 10k pin38 USB_CONN_THERM -> GND,§13.5 必补项 2(口温占位,首台机实测确认) U91 USBLC6-2SC6 D± + VBUS ESD(原厂尾插小板上的 ESD 没了,补回来) C901 100nF VBUS 去耦 TP901/902/903 DEBUG_UART TX / RX / GND 测试点(1.8V 电平,默认不接) CC2(pin32) 悬空 §13.5 必补项 3 用法: /c/Python314/python .claude/t1_usbtap.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 SCH = 'fe54ec63db9be605' # phonerail-usbtap (Board3) PAGE = '56ed81d66f1b0b83' # T1-USBTAP LIB = '0819f05c4eef4c71ace90d822a990e87' P = { 'BM24': '7e3457a7ea314e6e8f2e4c5034e6b8a5', # C424560 BM24-50DP/2-0.35V(51) 'WV4': 'a442828947794d278f7b5933443e57dd', # C225106 A1251WV-S-4P 'R56K': '0d85e42011ec4910b1749e936b037dd7', # C25796 56k 0402 'R10K': 'c3b9baa5ef2e4070a4c0f9e9cd04fe6e', # C25744 10k 0402 'C100N': '87bb635d0b2f489a9f60e7cd225beb3c', # C1525 100nF 0402 'ESD': '1e5e214711544da8ba71fb2137ae703f', # C2687116 USBLC6-2SC6 'TPRED': '17b5ed105c5b4fe180eee3ffb4702963', # C9900007423 红色测试点(引脚在 cx-20) 'TPBLK': 'cf8b789614c04ee491fd17b89f81a8ab', # C9900007426 黑色测试点(引脚在 cx-10) } # ---- J1401 引脚用途(§13.4 的 Hirose 编号)---- GND_L = [3, 9, 13, 21] # 左列(符号 1..25)里的地 GND_R = [27, 28, 31, 36, 40, 44, 49, 50] # 右列(符号 26..50)里的地 SIG_R = [(29, 'UART_RX'), (30, 'CC1_P'), (38, 'THERM_P'), (46, 'USB_DP_P'), (48, 'USB_DM_P')] # 右列的信号 SIG_L = [(24, 'UART_TX')] # 左列的信号 PWR_VBUS = [51, 52] # 一端的电源触点(两个焊盘同一个触点)-> USB_VBUS PWR_GND = [53, 54] # 另一端的电源触点 -> GND USED = set(GND_L + GND_R + [k for k, _ in SIG_R] + [k for k, _ in SIG_L]) NC_PINS = [str(k) for k in range(1, 51) if k not in USED] # 含 32(CC2),故意悬空 # ---- 版面常量 ---- J91 = (300, 470) # 左列引脚 x=260(y = 590 - 10*(k-1)),右列 x=340(y = 350 + 10*(k-26)) GNDBUS_L, GNDBUS_R = 200, 500 PORT_R = 400 # 右列信号端口的 x U91 = (760, 550) # USBLC6:1/6=I/O1@cy+10 2=GND@cy-0 3/4=I/O2@cy-10 J92 = (960, 470) # A1251WV-S-4P,引脚朝上(y = cy+25) R901, R902 = (700, 760), (700, 380) C901 = (680, 660) TP901, TP902, TP903 = (250, 260), (250, 220), (250, 180) # 图纸右下角是标题栏,别往那边放 Y_VBUS = 700 # VBUS 母线 Y_DP, Y_DM = 540, 560 # DP 接 I/O2(低),DM 接 I/O1(高)—— 这样 J92 扇出零交叉 Y_GNDJ = 440 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 placement(): return [ ['J91', P['BM24'], J91[0], J91[1], 0], ['J92', P['WV4'], J92[0], J92[1], 0], ['U91', P['ESD'], U91[0], U91[1], 0], ['R901', P['R56K'], R901[0], R901[1], 0], ['R902', P['R10K'], R902[0], R902[1], 0], ['C901', P['C100N'], C901[0], C901[1], 270], ['TP901', P['TPRED'], TP901[0], TP901[1], 0], ['TP902', P['TPRED'], TP902[0], TP902[1], 0], ['TP903', P['TPBLK'], TP903[0], TP903[1], 0], ] def wiring(pins): u"""pins: {位号: {引脚号: (x, y)}},全部读回来的真实坐标。""" 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]]) j91 = pins['J91'] # ---------- J91 左列 ---------- ys = sorted(j91[str(k)][1] for k in GND_L) for k in GND_L: px, py = j91[str(k)] w(px, py, GNDBUS_L, py) w(GNDBUS_L, ys[0], GNDBUS_L, ys[-1] + 40) F.append(['Ground', 'GND', GNDBUS_L, ys[-1] + 40, 180]) for k, net in SIG_L: px, py = j91[str(k)] w(px, py, 160, py) PT.append(['BI', net, 160, py, 180]) # ---------- J91 右列 ---------- ysr = sorted(j91[str(k)][1] for k in GND_R) for k in GND_R: px, py = j91[str(k)] w(px, py, GNDBUS_R, py) w(GNDBUS_R, ysr[0], GNDBUS_R, ysr[-1] + 30) F.append(['Ground', 'GND', GNDBUS_R, ysr[-1] + 30, 180]) for k, net in SIG_R: px, py = j91[str(k)] w(px, py, PORT_R, py) PT.append(['BI', net, PORT_R, py, 0]) # ---------- J91 两个电源触点 ---------- # 51/52 是同一个电源触点的两个焊盘,53/54 是另一端那个。往上/往下各引一条。 xs = sorted(j91[str(k)][0] for k in PWR_VBUS) yv = j91[str(PWR_VBUS[0])][1] for k in PWR_VBUS: px, py = j91[str(k)] w(px, py, px, yv + 50) w(xs[0], yv + 50, PORT_R, yv + 50) PT.append(['BI', 'VBUS_P', PORT_R, yv + 50, 0]) xs2 = sorted(j91[str(k)][0] for k in PWR_GND) yg = j91[str(PWR_GND[0])][1] for k in PWR_GND: px, py = j91[str(k)] w(px, py, px, yg - 40) w(xs2[0], yg - 40, 360, yg - 40) F.append(['Ground', 'GND', 360, yg - 40, 0]) # ---------- U91 USBLC6:D± 直通 + VBUS 钳位 ---------- u = pins['U91'] io1a, io1b = u['1'], u['6'] # I/O1 -> DM io2a, io2b = u['3'], u['4'] # I/O2 -> DP ugnd, uvbus = u['2'], u['5'] assert io1a[1] == io1b[1] == Y_DM, u'U91 的 I/O1 不在 y=%d:%s' % (Y_DM, (io1a, io1b)) assert io2a[1] == io2b[1] == Y_DP, u'U91 的 I/O2 不在 y=%d:%s' % (Y_DP, (io2a, io2b)) # ⚠ USBLC6 的 1/6 是同一个 I/O1、3/4 是同一个 I/O2(芯片内部短接), # 但 EasyEDA 画线时会在元件引脚处把导线截断 —— 想画一条「穿过芯片」的直通线是做不到的 # (实测:create([620,540,860,540]) 回来变成 [620,540→715,540] 和 [860,540→805,540], # 中间穿过芯片本体那一段直接没了)。所以 D± 天然被切成两段,各自起名: # USB_DP_P / USB_DM_P = 手机那一侧(J91 -> U91) # USB_DP_C / USB_DM_C = 载板那一侧(U91 -> J92) # 这正好逼着 PCB 把走线串在 U91 的焊盘中间,而不是只搭一个脚 —— 是想要的结果。 for nm, yy, pn in ((u'DP', Y_DP, io2a), (u'DP', Y_DP, io2b), (u'DM', Y_DM, io1a), (u'DM', Y_DM, io1b)): assert pn[1] == yy, u'U91 的 %s 引脚不在 y=%d:%s' % (nm, yy, pn) w(620, Y_DP, io2a[0], Y_DP) PT.append(['BI', 'USB_DP_P', 620, Y_DP, 180]) w(620, Y_DM, io1a[0], Y_DM) PT.append(['BI', 'USB_DM_P', 620, Y_DM, 180]) # 这两个名字放在拐点上(不是 U91 边上)—— 贴着芯片会把符号盖住 PT.append(['BI', 'USB_DP_C', 860, 465, 180]) PT.append(['BI', 'USB_DM_C', 890, 475, 180]) # U91 的 GND(2) 和 VBUS(5) 都夹在两个 I/O 中间,往哪边走都必然跨过一条 D 线。 # 全页只有这两处交叉,都是纯交叉(没有结点)= 不相连,这是原理图的常规画法。 w(ugnd[0], ugnd[1], 670, ugnd[1], 670, Y_GNDJ) F.append(['Ground', 'GND', 670, Y_GNDJ, 0]) # ---------- J92 出线座(引脚朝左,竖排一列)---------- j = pins['J92'] p1, p2, p3, p4 = j['1'], j['2'], j['3'], j['4'] # 1=VBUS 2=D- 3=D+ 4=GND p5, p6 = j['5'], j['6'] # 固定脚 assert p1[0] == p2[0] == p3[0] == p4[0], u'J92 四个信号脚不在同一列:%s' % [p1, p2, p3, p4] assert p1[1] > p2[1] > p3[1] > p4[1], u'J92 引脚 y 序不是 1>2>3>4:%s' % [p1, p2, p3, p4] JX = p1[0] # D+ 走 I/O2(低的那条),D- 走 I/O1(高的那条);D+ 的拐点 x 必须比 D- 的小, # 否则两条 D 线会互相交叉(J92 上 D+ 也在 D- 下面,顺序一致就零交叉)。 w(io2b[0], Y_DP, 860, Y_DP, 860, p3[1]) w(860, p3[1], JX, p3[1]) w(io1b[0], Y_DM, 890, Y_DM, 890, p2[1]) w(890, p2[1], JX, p2[1]) # VBUS 母线:J91 的电源触点 -> J92 pin1,U91.5 在中途搭上去(和 D- 交叉一次) w(620, Y_VBUS, 920, Y_VBUS, 920, p1[1]) w(920, p1[1], JX, p1[1]) PT.append(['BI', 'VBUS_P', 620, Y_VBUS, 180]) w(uvbus[0], uvbus[1], 835, uvbus[1], 835, Y_VBUS) # pin4 和固定脚 6 并到一条地;固定脚 5 单独一个旗(往上走,避开 VBUS 那条竖线) w(p4[0], p4[1], 900, p4[1], 900, 420) w(p6[0], p6[1], 900, p6[1]) F.append(['Ground', 'GND', 900, 420, 0]) w(p5[0], p5[1], p5[0], p5[1] + 25) F.append(['Ground', 'GND', p5[0], p5[1] + 25, 180]) # ---------- C901 VBUS 去耦 ---------- c = pins['C901'] ctop = max(c.values(), key=lambda v: v[1]) cbot = min(c.values(), key=lambda v: v[1]) w(ctop[0], ctop[1], ctop[0], Y_VBUS) w(cbot[0], cbot[1], cbot[0], cbot[1] - 40) F.append(['Ground', 'GND', cbot[0], cbot[1] - 40, 0]) # ---------- R901:CC1 -> 56k -> VBUS(§13.5 必补项 1)---------- r = pins['R901'] ra = min(r.values(), key=lambda v: v[0]) rb = max(r.values(), key=lambda v: v[0]) w(620, ra[1], ra[0], ra[1]) PT.append(['BI', 'CC1_P', 620, ra[1], 180]) w(rb[0], rb[1], 780, rb[1], 780, Y_VBUS) # ---------- R902:pin38 口温 -> 10k -> GND(§13.5 必补项 2)---------- r2 = pins['R902'] r2a = min(r2.values(), key=lambda v: v[0]) r2b = max(r2.values(), key=lambda v: v[0]) w(620, r2a[1], r2a[0], r2a[1]) PT.append(['BI', 'THERM_P', 620, r2a[1], 180]) w(r2b[0], r2b[1], 800, r2b[1]) F.append(['Ground', 'GND', 800, r2b[1], 0]) # ---------- 调试串口测试点 ---------- for des, net in (('TP901', 'UART_TX'), ('TP902', 'UART_RX')): px, py = pins[des]['1'] w(170, py, px, py) PT.append(['BI', net, 170, py, 180]) px, py = pins['TP903']['1'] w(px, py, 190, py) F.append(['Ground', 'GND', 190, py, 0]) TX += [ (45, 806, u'【Board3 / T1-USBTAP】小米8 尾插 J1401 转接小板 —— 每台手机一块,共 5 块', 14), (45, 784, u'J91 插进主板正面的 J1401(Hirose BM24-50DS/2-0.35V),把 USB 引到 J92,' u'再用 1.25mm 4P 线接载板的 J17~J21。§13.5 方案 C。', 10), (45, 120, u'⚠⚠ 打样前必须先解决:立创这颗 BM24 的封装焊盘编号和 Hirose 的真实编号不一致', 12), (45, 102, u' 立创封装 CONN-SMD_BM24-50DP-2-0.35V51 是 DIP 式:1..25 排一行、26..50 反着排另一行。', 10), (45, 86, u' 而小米8 图纸上的 J1401 是奇偶式:1,3,5..49 一行,2,4,6..50 另一行。两条硬证据:', 10), (45, 70, u' ① 44/46/48/50 = GND/D+/D-/GND 在同一行相邻(差分对被地包夹);', 10), (45, 54, u' ② 奇数行全是音频、偶数行全是数字/电源,33/35/37 与 43/45/47 各是三针并联的喇叭。', 10), (45, 38, u' 本页按 Hirose 编号连线(和 §13.4 的表一致)。转 PCB 前要自建封装,或先实测确认。', 10), (600, 300, u'【§13.5 方案 C 的三条必补项】', 11), (600, 282, u'1. R901 56k:CC1(30) 上拉到 VBUS。整条链路里已经没有 USB-C 连接器了,', 10), (600, 266, u' 没人提供 Rp,手机看不到 Rp 就只充电不枚举。56k = Default USB 档。', 10), (600, 250, u'2. R902 10k:pin38 口温。⚠ 阻值是占位,首台机上实测确认(也可能要 100k NTC);', 10), (600, 234, u' 我们本来就用 input_suspend 关掉了充电,口温读数不准也不影响枚举。', 10), (600, 218, u'3. CC2(32) 必须悬空 —— 只拉一边 CC,手机才判得出插入方向。', 10), (45, 754, u'U91 USBLC6-2SC6:原厂尾插小板上的 ESD 随着方案 C 一起没了,在这里补回来。', 10), (45, 738, u'⚠ 引脚 1/6 是同一个 I/O1、3/4 是同一个 I/O2(芯片内部短接),而 EasyEDA 会在引脚处截断', 10), (45, 722, u' 导线,所以 D± 在网表里天然是两段:USB_D?_P 手机侧 / USB_D?_C 载板侧。', 10), (45, 706, u' PCB 上走线必须从一个焊盘进、另一个焊盘出(串在线中),不能只搭一个脚;这颗不许不贴。', 10), (45, 690, u' 线电容 3.5pF,比 §15.3 想要的 <1pF 大,但这是机箱内 20cm 的短链路,USB2.0 HS 够用。', 10), (960, 806, u'J92 引脚必须和载板 C3-USB 的 J17~J21 一一对上:', 10), (960, 790, u'1=VBUS 2=D- 3=D+ 4=GND,5/6 固定脚接地。', 10), (960, 774, u'TP901/902 是高通调试串口(pin24 TX / pin29 RX),', 10), (960, 758, u'⚠ 1.8V 电平,不能直接接 ESP32;默认不焊线,', 10), (960, 742, u'内核 panic 时这是唯一能看到原因的通道(§13.5)。', 10), (960, 726, u'J91 上没画的引脚(音频/射频/电源轨)全部悬空。', 10), ] # 下发前的几何断言(skill §6c / §8a-2b) for a, b, c2, e in W: assert (a == c2) != (b == e), u'有一段导线不是横平竖直或零长度: %s' % ((a, b, c2, e),) for v in (a, b, c2, e): assert v % 5 == 0, u'导线坐标 %d 不是 5 的倍数: %s' % (v, (a, b, c2, e)) for kind, lst in (('flag', F), ('port', PT)): for it in lst: assert it[2] % 5 == 0 and it[3] % 5 == 0, \ u'%s %s 坐标 (%d,%d) 不是 5 的倍数' % (kind, it[1], it[2], it[3]) return W, F, PT, TX def main(): push = '--push' in sys.argv place = placement() print(u'元件 %d 个' % len(place)) if not push: print(u'(未推送,加 --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!=='T1-USBTAP') 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'页面已清空') 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)) # BM24 的默认名 'BM24-50DP/2-0.35V(51)' 有 21 个字,压在符号下面很难看,改短名。 # supplierId / manufacturerId 都不动,BOM 照旧。 call(""" for (const c of await eda.sch_PrimitiveComponent.getAll()){ if (c.getState_Designator()==='J91') await c.toAsync().setState_Name('BM24-50DP').done(); } return 1; """) print(u'已放置 %d 个元件' % len(place)) 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(), (p.getState_PinName?p.getState_PinName():''), Math.round(p.getState_X()), Math.round(p.getState_Y())]); } return out; """) pins = {k: {p: (x, y) for p, _n, x, y in v} for k, v in raw.items()} pnames = {k: {p: _n for p, _n, x, y in v} for k, v in raw.items()} bad = [] if len(pins['J91']) != 54: bad.append(u'J91 引脚数 %d,期望 54(50 信号 + 4 个电源触点焊盘)' % len(pins['J91'])) want = {'1': 'I/O1', '2': 'GND', '3': 'I/O2', '4': 'I/O2', '5': 'VBUS', '6': 'I/O1'} for k, v in want.items(): got = pnames['U91'].get(k, '') if got.upper() != v.upper(): bad.append(u'U91.%s 引脚名期望 %s 实测 %s' % (k, v, got)) if bad: raise SystemExit(u'引脚核对失败:\n ' + '\n '.join(bad)) 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'导线 %d 旗 %d 端口 %d 文字 %d' % (len(W), len(F), len(PT), len(TX))) ncf = os.path.join(HERE, 't1-nets-nc.json') json.dump({'J91': NC_PINS}, open(ncf, 'w', encoding='utf-8'), ensure_ascii=False, indent=1) print(u'NC 清单已写入 %s(%d 个悬空脚)' % (ncf, len(NC_PINS))) if __name__ == '__main__': main()