304 行
14 KiB
Python
304 行
14 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""R2 刀片 **B3B-USB** 页:尾插座 + D± ESD + Rp/口温 + VBUS 负载开关。
|
||
|
||
/c/Python314/python .claude/r2/b3b_usb.py # 只算 + 本地自检
|
||
/c/Python314/python .claude/r2/b3b_usb.py --push # 清页 + 重画(幂等)
|
||
|
||
M2c 的第二页(拆页理由见 `b3_phone.py` 的表头)。R1 把这些铺在
|
||
`C5-J1401A`(尾插座)、`C5-AUX`(ESD + Rp + 口温)、`C3-USB`(VBUS 开关)三页上。
|
||
|
||
| 位号 | 器件 | 出处 |
|
||
|---|---|---|
|
||
| `J4` | BM24-50DS/2-0.35V,个人库自建封装(**Hirose 奇偶行编号**,立创那个是 DIP 绕圈,错的)| R1 `J71` |
|
||
| `U7` | USBLC6-2SC6,D± ESD | R1 `U51` |
|
||
| `R64` | 56k `CC1 → VBUS` 的 Rp(Default USB 档)| R1 `R761`,见 §23.3 |
|
||
| `R65` | 100k 口温 → GND | R1 `R771` |
|
||
| `U5` `C66~C68` `R62` | TPS22918 + 2.2µF CIN + 100nF COUT + 100pF CT + 100k ON 下拉 | R1 `U24/C701/C711/C721/R701`,§18.2 |
|
||
|
||
## ⚠️ `J4` 的脚号 → 网络**照抄 R1**,不要自己按 §13.4 推
|
||
|
||
R1 §24 那一轮把「手机 J1401 的 Hirose 脚号」经原厂排线映射到座子焊盘号,
|
||
中间隔着一次实测(§24「已实测确认的三件事」)。本页的 `PIN_NET` 表是从
|
||
R1 `C5-J1401A` 的 `J71` **原样读回来的**(`_c5j1401a_live.json`),脚本启动时
|
||
拿它和源页比对,对不上直接终止 —— 按 §13.4 的表重推一遍必然错位。
|
||
|
||
## 三条与 R1 相同、这里再记一次的
|
||
|
||
1. **`U7` 是「1/6 内部同一个 I/O1、3/4 同一个 I/O2」的直通器件**,
|
||
原理图上天然是两段(skill §8b):手机侧 `USB_D*J`、上游侧 `USB_D*`。
|
||
⇒ PCB 上走线必然串在两个焊盘中间,不会偷偷从旁边绕过去只搭一个脚。
|
||
⛔ **2026-09-11 起上游侧接 1/3 脚、手机侧接 6/4 脚**(与 R1 相反,理由见 build() 第 2 节)。
|
||
2. **`R64` 这颗 Rp 不能省**:手机的 PMI8998 靠 CC 上看到 Rp 才判定「有 DFP 插入」。
|
||
R1 §23.2 的坑是「Rp 藏在 USB-A 转 C 线的 C 头里」,而这里没有那根线。
|
||
3. **`R62` 100k 是 `VBUS_EN` 的下拉**(datasheet 明令 ON 脚不得悬空):
|
||
PCA9555 复位时高阻,全靠它保证 VBUS 默认断开(D9 复位安全的一半)。
|
||
"""
|
||
import io
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, HERE)
|
||
from schcheck import Sheet, two_pin, by_rel, pins_of # noqa: E402
|
||
|
||
PAGE, NAME = '4ce5bfe35e90e560', 'B3B-USB'
|
||
|
||
BM24, ESD, LSW = 'BM24-HRS-PIN', 'C2687116', 'C131941'
|
||
R56K, R100K, C2U2, C100N, C100P = 'C25796', 'C25741', 'C377773', 'C1525', 'C1546'
|
||
|
||
# ── J4 的脚号 -> 网络:从 R1 C5-J1401A 的 J71 原样搬(去掉通道后缀)──────
|
||
PIN_NET = {'51': 'VBUS', '4': 'USB_DMJ', '6': 'USB_DPJ',
|
||
'14': 'USB_THERM', '22': 'CC1'}
|
||
GND_PINS = ['1', '2', '8', '12', '16', '19', '23', '24', '29', '37', '41', '47', '52']
|
||
SRC = os.path.join(HERE, '_c5j1401a_live.json')
|
||
SRC_MAP = {'VBUS1': 'VBUS', 'USB1_DMJ': 'USB_DMJ', 'USB1_DPJ': 'USB_DPJ',
|
||
'THERM_1': 'USB_THERM', 'CC1_1': 'CC1'}
|
||
|
||
J4 = (940, 580)
|
||
U7 = (330, 420)
|
||
U5 = (700, 420)
|
||
|
||
ESD_R270 = {'1': (10, 45), '2': (0, 45), '3': (-10, 45),
|
||
'4': (-10, -45), '5': (0, -45), '6': (10, -45)}
|
||
# ⚠ skill §6c 又一例:同一个料号(TPS22918 SOT-23-6),R1 的 C3-USB 上
|
||
# create(270) 得到「1/2/3 在上排」,本页实测是**在下排**。三种角度都量过:
|
||
# rot 0 1(-60,+10) … 6(+60,+10) rot 90 1(+10,+60) …
|
||
# rot 270 1(-10,-60) 2(0,-60) 3(+10,-60) / 4(+10,+60) 5(0,+60) 6(-10,+60)
|
||
# 所以下面这张表是**本页实测**,不是从 R1 抄的;放完仍然逐脚断言。
|
||
LSW_R270 = {'1': (-10, -60), '2': (0, -60), '3': (10, -60),
|
||
'4': (10, 60), '5': (0, 60), '6': (-10, 60)}
|
||
|
||
|
||
def bm24_rel(n):
|
||
u"""BM24 符号:1~25 左列自上而下,26~50 右列自下而上,51/52 在正中上下。"""
|
||
if n == 51:
|
||
return (0, 140)
|
||
if n == 52:
|
||
return (0, -140)
|
||
if n <= 25:
|
||
return (-30, 120 - 10 * (n - 1))
|
||
return (30, -120 + 10 * (n - 26))
|
||
|
||
|
||
BM24_REL = dict((str(n), bm24_rel(n)) for n in range(1, 53))
|
||
USED = set(PIN_NET) | set(GND_PINS)
|
||
NC = {'J4': [str(n) for n in range(1, 53) if str(n) not in USED]}
|
||
ALLOW_SPLIT = set(['GND', 'VBUS', 'CC1', 'USB_THERM',
|
||
'USB_DPJ', 'USB_DMJ', 'USB_DP', 'USB_DM'])
|
||
|
||
LBUS, PORTX, RBUS = J4[0] - 200, J4[0] - 70, J4[0] + 90
|
||
|
||
|
||
def check_source():
|
||
u"""和 R1 源页逐脚对账:脚号→网络必须一模一样,对不上就别画。"""
|
||
if not os.path.exists(SRC):
|
||
print(u'⚠ 找不到 %s,跳过与 R1 的对账(重新 dump_page.py 一次更稳)' % SRC)
|
||
return
|
||
d = json.load(io.open(SRC, encoding='utf-8'))
|
||
pts = d['wires']
|
||
segs = [((int(round(pts[i][0])), int(round(pts[i][1]))),
|
||
(int(round(pts[i + 1][0])), int(round(pts[i + 1][1]))))
|
||
for i in range(0, len(pts), 2)]
|
||
segs = [s for s in segs if s[0] != s[1]]
|
||
|
||
par = {}
|
||
|
||
def find(a):
|
||
par.setdefault(a, a)
|
||
while par[a] != a:
|
||
par[a] = par[par[a]]
|
||
a = par[a]
|
||
return a
|
||
|
||
def uni(a, b):
|
||
ra, rb = find(a), find(b)
|
||
if ra != rb:
|
||
par[ra] = rb
|
||
|
||
def on(p, s):
|
||
(x1, y1), (x2, y2) = s
|
||
if x1 == x2:
|
||
return p[0] == x1 and min(y1, y2) <= p[1] <= max(y1, y2)
|
||
return p[1] == y1 and min(x1, x2) <= p[0] <= max(x1, x2)
|
||
|
||
for s in segs:
|
||
uni(s[0], s[1])
|
||
for s in segs:
|
||
for t in segs:
|
||
if on(s[0], t):
|
||
uni(s[0], t[0])
|
||
if on(s[1], t):
|
||
uni(s[1], t[0])
|
||
name = {}
|
||
for kind, net, x, y, _rot in d['flags'] + d['ports']:
|
||
p = (int(round(x)), int(round(y)))
|
||
for s in segs:
|
||
if on(p, s):
|
||
name.setdefault(find(s[0]), set()).add(
|
||
'GND' if kind.startswith('Ground') else net)
|
||
break
|
||
src = None
|
||
for p in d['parts']:
|
||
if p[0] == 'J71':
|
||
src = p
|
||
assert src, u'源页里找不到 J71'
|
||
got = {}
|
||
for q in src[9]:
|
||
pn, x, y = str(q[0]), int(round(q[2])), int(round(q[3]))
|
||
for s in segs:
|
||
if on((x, y), s):
|
||
nm = sorted(name.get(find(s[0]), []))
|
||
if nm:
|
||
got[pn] = SRC_MAP.get(nm[0], nm[0])
|
||
break
|
||
want = dict(PIN_NET)
|
||
for pn in GND_PINS:
|
||
want[pn] = 'GND'
|
||
if got != want:
|
||
diff = [(k, want.get(k), got.get(k))
|
||
for k in sorted(set(want) | set(got), key=int)
|
||
if want.get(k) != got.get(k)]
|
||
raise SystemExit(u'J4 脚号→网络与 R1 的 J71 对不上:%s' % diff)
|
||
print(u'J4 脚号→网络与 R1 的 J71 逐脚一致(%d 个脚)' % len(want))
|
||
|
||
|
||
def placement():
|
||
return [['J4', BM24, J4[0], J4[1], 0],
|
||
['U7', ESD, U7[0], U7[1], 270],
|
||
['U5', LSW, U5[0], U5[1], 270],
|
||
['R62', R100K, 760, 295, 270],
|
||
['R64', R56K, 920, 300, 270],
|
||
['R65', R100K, 1030, 300, 270],
|
||
['C66', C2U2, 600, 280, 0], # 2.2µF/0805:rot 0 才是竖的(§8d)
|
||
['C67', C100N, 640, 550, 270],
|
||
['C68', C100P, 560, 640, 270]]
|
||
|
||
|
||
def expect():
|
||
return {'J4': by_rel(J4[0], J4[1], BM24_REL),
|
||
'U7': by_rel(U7[0], U7[1], ESD_R270),
|
||
'U5': by_rel(U5[0], U5[1], LSW_R270),
|
||
'R62': two_pin(760, 295), 'R64': two_pin(920, 300),
|
||
'R65': two_pin(1030, 300), 'C66': two_pin(600, 280),
|
||
'C67': two_pin(640, 550), 'C68': two_pin(560, 640, 15)}
|
||
|
||
|
||
def build():
|
||
s = Sheet(PAGE, NAME)
|
||
jx, jy = J4
|
||
|
||
# ── 1. J4 BM24-50DS(版图照搬 R1 C5-J1401A:地母线甩到最左/最右,
|
||
# 信号端口排在母线与引脚之间,两者永不交叉)──────────────────
|
||
left = [n for n in GND_PINS if int(n) <= 25 or n == '52']
|
||
for n in [x for x in GND_PINS if int(x) <= 25]:
|
||
y = jy + bm24_rel(int(n))[1]
|
||
s.w('GND', (jx - 30, y), (LBUS, y))
|
||
s.w('GND', (LBUS, jy - 110), (LBUS, jy + 160))
|
||
s.gnd(LBUS, jy + 160, 180)
|
||
for n in [x for x in GND_PINS if 26 <= int(x) <= 50]:
|
||
y = jy + bm24_rel(int(n))[1]
|
||
s.w('GND', (jx + 30, y), (RBUS, y))
|
||
s.w('GND', (RBUS, jy - 90), (RBUS, jy + 110))
|
||
s.gnd(RBUS, jy + 110, 180)
|
||
s.w('GND', (jx, jy - 140), (jx, jy - 170)) # 52 电源触点 G1
|
||
s.gnd(jx, jy - 170, 0)
|
||
s.w('VBUS', (jx, jy + 140), (jx, jy + 170), (jx + 70, jy + 170)) # 51 = G2
|
||
s.port('BI', 'VBUS', jx + 70, jy + 170, 0)
|
||
for n, net in sorted(PIN_NET.items(), key=lambda kv: -int(kv[0])):
|
||
if n == '51':
|
||
continue
|
||
y = jy + bm24_rel(int(n))[1]
|
||
s.w(net, (jx - 30, y), (PORTX, y))
|
||
s.port('BI', net, PORTX, y, 180)
|
||
assert len(left) == len([x for x in GND_PINS if int(x) <= 25]) + 1
|
||
|
||
# ── 2. U7 USBLC6:中间那个 GND 直着走,两边的信号朝相反方向拐开 ────
|
||
# ⛔ 2026-09-11 两侧对调:**1/3 脚(上排)= 金手指侧 USB_DM/DP,6/4 脚(下排)= 手机侧**。
|
||
# U7 在 B 面,从 4/6 那排流向 1/3 那排时 DP 永远在前进方向右手边(旋转不改手性),
|
||
# 而金手指(DP 在西、向北出线)和 J4(DPJ 在东、从北边进)两头都要 DP 在左手边 ——
|
||
# 旧接法前后各要交叉一次(旧版 DP/DM 各 6~7 个孔)。1↔6、3↔4 内部直通,电气等价。
|
||
ux, uy = U7
|
||
s.w('USB_DM', (ux + 10, uy + 45), (ux + 10, 505), (420, 505))
|
||
s.port('BI', 'USB_DM', 420, 505, 0)
|
||
s.w('USB_DP', (ux - 10, uy + 45), (ux - 10, 505), (240, 505))
|
||
s.port('BI', 'USB_DP', 240, 505, 180)
|
||
s.w('GND', (ux, uy + 45), (ux, 545))
|
||
s.gnd(ux, 545, 180)
|
||
s.w('USB_DMJ', (ux + 10, uy - 45), (ux + 10, 335), (420, 335))
|
||
s.port('BI', 'USB_DMJ', 420, 335, 0)
|
||
s.w('USB_DPJ', (ux - 10, uy - 45), (ux - 10, 335), (240, 335))
|
||
s.port('BI', 'USB_DPJ', 240, 335, 180)
|
||
s.w('VBUS', (ux, uy - 45), (ux, 295))
|
||
s.port('BI', 'VBUS', ux, 295, 0)
|
||
|
||
# ── 3. U5 TPS22918(rot270 实测:VIN/GND/ON 在下排,VOUT/QOD/CT 在上排)──
|
||
vx, vy = U5
|
||
s.w('+5V', (vx - 10, vy - 60), (vx - 10, 320), (560, 320)) # 1 VIN
|
||
s.port('IN', '+5V', 560, 320, 180)
|
||
s.w('+5V', (600, 320), (600, 300)) # -> C66 上脚
|
||
s.w('GND', (600, 260), (600, 235))
|
||
s.gnd(600, 235, 0)
|
||
s.w('GND', (vx, vy - 60), (vx, 300)) # 2 GND 直着下来
|
||
s.gnd(vx, 300, 0)
|
||
s.w('VBUS_EN', (vx + 10, vy - 60), (vx + 10, 330), (800, 330)) # 3 ON
|
||
s.port('IN', 'VBUS_EN', 800, 330, 180)
|
||
s.w('VBUS_EN', (760, 330), (760, 315)) # -> R62 上脚
|
||
s.w('GND', (760, 275), (760, 250))
|
||
s.gnd(760, 250, 0)
|
||
s.w('VBUS', (vx, vy + 60), (vx, 560)) # 5 QOD
|
||
s.port('BI', 'VBUS', vx, 560, 0)
|
||
s.w('VBUS', (vx - 10, vy + 60), (vx - 10, 520), (vx, 520)) # 6 VOUT -> QOD
|
||
s.w('VBUS', (vx, 530), (640, 530)) # -> C67 下脚
|
||
s.w('GND', (640, 570), (640, 595))
|
||
s.gnd(640, 595, 180)
|
||
s.w('CT', (vx + 10, vy + 60), (vx + 10, 620), (560, 620)) # 4 CT
|
||
s.w('CT', (560, 620), (560, 625))
|
||
s.w('GND', (560, 655), (560, 680))
|
||
s.gnd(560, 680, 180)
|
||
|
||
# ── 4. Rp 与口温 ─────────────────────────────────────────────────
|
||
s.w('VBUS', (920, 320), (920, 345))
|
||
s.port('BI', 'VBUS', 920, 345, 0)
|
||
s.w('CC1', (920, 280), (920, 255))
|
||
s.port('BI', 'CC1', 920, 255, 0)
|
||
s.w('USB_THERM', (1030, 320), (1030, 345))
|
||
s.port('BI', 'USB_THERM', 1030, 345, 0)
|
||
s.w('GND', (1030, 280), (1030, 255))
|
||
s.gnd(1030, 255, 0)
|
||
|
||
# ── 5. 文字 ─────────────────────────────────────────────────────
|
||
s.txt(45, 795, u'【B3B-USB】尾插座 BM24-50DS + D± ESD + Rp/口温 + VBUS 负载开关', 14)
|
||
s.txt(60, 265, u'J4 的脚号→网络照抄 R1 的 J71(隔着一次排线映射实测,§24),', 10)
|
||
s.txt(60, 247, u' 脚本每次都和 C5-J1401A 逐脚对账,对不上直接终止。', 10)
|
||
s.txt(60, 229, u'⚠ 51/52 是两个大电源触点:51 = G2 = USB_VBUS,52 = G1 = 真地。', 10)
|
||
s.txt(60, 211, u' 接反就是 5V 对地短路 —— 打样前用表笔在排线自由端确认一次。', 10)
|
||
s.txt(60, 193, u'⚠ R64 56k 是 CC1 上拉到 VBUS 的 Rp:手机靠它判定「有主机插入」。', 10)
|
||
s.txt(60, 175, u' R1 里这颗藏在 USB-A 转 C 线的 C 头中,本方案没有那根线,必须自己放。', 10)
|
||
s.txt(60, 157, u'⚠ R62 100k:ON 脚不得悬空(datasheet),PCA9555 复位时高阻 ⇒ VBUS 默认断。', 10)
|
||
s.txt(60, 139, u'U7 的 1/6、3/4 内部短接:1/3 = 上游 *_DP/DM,6/4 = 手机 *_DPJ/DMJ。', 10)
|
||
return s
|
||
|
||
|
||
def main():
|
||
push = '--push' in sys.argv
|
||
check_source()
|
||
place = placement()
|
||
exp = expect()
|
||
s = build()
|
||
s.assert_grid()
|
||
errs = s.netcheck(pins_of(exp), NC, ALLOW_SPLIT)
|
||
s.report(len(place), errs)
|
||
if not push:
|
||
return
|
||
s.clear()
|
||
s.place(place, exp)
|
||
s.push_geometry()
|
||
json.dump(NC, io.open(os.path.join(HERE, 'b3b-nc.json'), 'w', encoding='utf-8'),
|
||
ensure_ascii=False, indent=1)
|
||
print(u'接着跑 verify_sch.py --nets --nc .claude/r2/b3b-nc.json')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|