# -*- coding: utf-8 -*- u"""R2 底板 **A1B-PD** 页(D16,2026-09-11):USB-C PD 取电 20V → LM61495 → 12V 母线。 /c/Python314/python .claude/r2/a1b_pd.py # 只算 + 本地自检 /c/Python314/python .claude/r2/a1b_pd.py --png out # 另存一张本地预览图(不碰 EDA) /c/Python314/python .claude/r2/a1b_pd.py --create # 在 r2-base 里建页(幂等),uuid 写进 r2-uuids.json /c/Python314/python .claude/r2/a1b_pd.py --push # 清页 + 重画(幂等) ## 为什么单开一页 原 A1-INPUT 的入口是「DC-005 → F1 → Q1 反接 → 母线」,4 件。换成 USB-C PD 之后是 C 座 + 保险 + TVS + PD 取电芯片 + 10 A 降压 + 十几颗外围,塞进 A1 会和保留的母线电容 / U13 检流 / 泄放电路挤成一团。A1 只删旧入口,新入口整页放这里,12V 靠 `VIN_SW` 旗接回去。 ## 取值(依据见方案文档 §36) | 件 | 值 | 为什么 | |---|---|---| | `U36` CH224A | 单电阻模式 `R7` 120k ⇒ **只要 20V** | 各家手机充电器 PD 都有 20V;12V 不是每家都有;28V EPR 手机充电器不给 | | `D3` | SMBJ22A | VRWM 22 V > PD 20V 档上限 21 V;**固件不许让 CH224 请求 28V** | | `U37` LM61495 | `R8` 22.1k ⇒ 720 kHz | `L > Vout/(fsw·0.6·10A)` = 2.8 µH;4.7 µH −20% 仍够。400 kHz 要 ≥5 µH | | `L4` SRP1265A-4R7M | Isat 28 A / Irms 13.5 A / 8.4 mΩ | 纹波 (20−12)·0.6/(4.7µ·720k) = 1.42 A(14%)| | FB | `R10` 110k / `R11` 10k | 1.0 V × 12 = **12.0 V** | | EN | `R12` 100k / `R13` 10k | 1.263 V × 11 = **13.9 V** 才启动(12.8~15.0)⇒ 协商到 20V 之前不开,母线大电容不会在 5V 下拽充电器 | | SPSP / SYNC / BIAS | VCC / GND / GND | 扩频开;轻载自动 PFM;BIAS 按手册「输出不低于 12V 就接地」| | CBOOT / RBOOT | `C18` 100n,RBOOT 直连 CBOOT | RBOOT 相当于 0 Ω | | 输入 | `C21~C24` 10µ/50V + `C19/C20` 100n/50V | 手册:≥2×10 µF,且每对 VIN/PGND 贴一颗小封装 | | 输出 | `C14~C17` 22µ/25V | A1 那边还有 C2/C3 470 µF + C4/C5 10 µF | CH224A 的 CFG2/CFG3 在单电阻模式下自动变成 I²C(0x22/0x23,内置 10k 上拉),**直接挂主 I²C 总线**(`SDA` / `SCL`)。 ⚠ D16 第一版接的是 TCA9548A 通道 5(SDA6/SCL6);PCB 上 U35.15/16 被 J36 一带原有布线围死、与南带 U36 不连通, 2026-09-12 用户选改挂主总线:0x22/0x23 与主总线上 U13 0x43 / U6 0x4A / U7 0x4B / U35 0x70 都不冲突。 寄存器 `0x50` = 当前档位最大电流(×50 mA),固件据此定整板功率上限(60 / 65 / 100 W)。DP/DM、PG 不用。 ## 几何 U37 的两侧都是 10 间距的脚,旗挂不下(skill §7),所以功能块之间一律用**端口**连: `SW12` / `BST12` / `VCC12` / `RT12` / `EN12` / `FB12`;电源轨用 Power 旗(`VIN_RAW` / `VIN_FUSED` / `VIN_SW`)。 器件相对坐标都是 2026-09-11 在 A1-INPUT 空白处 `create(rot 0)` 读回的(读完即删); J1 与 A4-HUB 的 J23 同料,rot 180 偏移照抄 `_c2hub_live.json`。放件后 `Sheet.place` 仍逐脚回读断言。 """ 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, call, two_pin, by_rel, pins_of # noqa: E402 SCH = '372e2399adb7a6db' # r2-base 原理图 NAME = 'A1B-PD' R2_PROJECT = '05a65c22f14846b8b95360ef8d3c1e2e' UUIDS = os.path.join(HERE, 'r2-uuids.json') K_TYPEC, K_FUSE, K_TVS = 'C165948', 'C310990', 'C151920' K_PD, K_BUCK, K_IND = 'C42459160', 'C3188410', 'C780205' K_10U50, K_100N50, K_1U50 = 'C440198', 'C307331', 'C28323' K_22U, K_1U, K_100N = 'C12891', 'C52923', 'C1525' K_110K, K_120K, K_100K, K_10K, K_22K1 = 'C25745', 'C25750', 'C25741', 'C25744', 'C43473' # ── 引脚相对坐标(rot 0,实测)────────────────────────────────────────── PD_REL0 = {'1': (-60, 15), '2': (-60, 5), '3': (-60, -5), '4': (-60, -15), '5': (-60, -25), '6': (60, -25), '7': (60, -15), '8': (60, -5), '9': (60, 5), '10': (60, 15), '11': (10, 35)} # 1 VHV / 2 CFG2·SCL / 3 CFG3·SDA / 4 DP / 5 DM / 6 CC2 / 7 CC1 / 8 VBUS / 9 CFG1 / 10 PG / 11 GND(EP) BUCK_REL0 = {'3': (-65, 65), '10': (-65, 55), '9': (-65, 45), '11': (-65, 15), '16': (-65, 5), '12': (-65, -5), '5': (-65, -35), '4': (-65, -45), '13': (-65, -55), '7': (-65, -65), '1': (65, -65), '15': (65, -55), '8': (65, -45), '2': (65, 25), '14': (65, 35), '6': (65, 65)} # 左:3 RBOOT / 10 RESET# / 9 RT / 11 SPSP / 16 SW / 12 SYNC / 5 BIAS / 4 CBOOT / 13 EN / 7 FB # 右:1 PGND2 / 15 PGND1 / 8 AGND / 2 VIN2 / 14 VIN1 / 6 VCC TYPEC_REL180 = {'A1B12': (35, -55), 'A4B9': (35, -45), 'B8': (35, -35), 'A5': (35, -25), 'B7': (35, -15), 'A6': (35, -5), 'A7': (35, 5), 'B6': (35, 15), 'A8': (35, 25), 'B5': (35, 35), 'B4A9': (35, 45), 'B1A12': (35, 55), '4': (-35, 25), '3': (-35, 35), '2': (-35, 45), '1': (-35, 55)} def _rot180(rel): return dict((k, (-x, -y)) for k, (x, y) in rel.items()) def hz(x, y, half=20): u"""横放两脚件:只比坐标集合。""" return {'?': set([(x - half, y), (x + half, y)])} J1_C, U36_C, U37_C = (110, 690), (920, 700), (330, 520) BUS_FUSED_Y, RAIL_IN_Y = 735, 660 # 第一行:VIN_FUSED 母线 / 输入电容的地轨 OUT_Y, RAIL_OUT_Y = 530, 460 # 第二行:VIN_SW 输出母线 / 输出电容的地轨 NC = {'J1': ['B8', 'A8', 'A6', 'B6', 'A7', 'B7'], # SBU1/2、D+/D−:只取电,不走 USB 数据 'U36': ['4', '5', '10'], # DP / DM / PG 'U37': ['10']} # RESET# ALLOW_SPLIT = set(['GND', 'VIN_RAW', 'VIN_FUSED', 'VIN_SW', 'PD_CC1', 'PD_CC2', 'SW12', 'BST12', 'VCC12', 'RT12', 'EN12', 'FB12']) TEXTS = [ (45, 805, u'【A1B-PD】底板电源入口(D16):USB-C PD 取电 20V → LM61495 降压 → 12V 母线(VIN_SW,接 A1-INPUT)', 14), # ⚠ 右下这一块第一版从 y 405 起,第一行横穿 x 900,压在 R11 那面地旗的「GND」字样上 # (字画在旗下方约 y 397~410;text_clash 只比旗的锚点,查不出来,是截图看出来的)⇒ 整块下移 20 (600, 385, u'【A】取电 U36 CH224A:单电阻 R7=120k ⇒ 只请求 20V;VBUS 与 VHV 同接 VIN_FUSED', 10), (600, 368, u'CFG2/CFG3 = I²C,直接挂主总线 SCL/SDA(地址 0x22/0x23);0x50 = 当前档最大电流 ×50mA', 10), (600, 351, u'固件按 0x50 定整板功率上限(60 / 65 / 100W);⚠ 不许写 0x0A=5(28V):D3 在 24.4V 开始导通', 10), (600, 324, u'【B】降压 U37 LM61495(10A):R8=22.1k ⇒ 720kHz;L4 4.7µH(Isat 28A)', 10), (600, 307, u'FB 110k/10k ⇒ 12.0V;EN 100k/10k ⇒ 输入升到约 13.9V 才启动(协商到 20V 之前不开)', 10), (600, 290, u'SPSP→VCC 扩频开;SYNC→GND 轻载 PFM;BIAS→GND;RBOOT 直连 CBOOT;RESET# 不用', 10), (600, 263, u'【C】J1 与 J23(Hub 上行)同一颗 C 座 —— 丝印必须标「PWR」。插错不会坏:', 10), (600, 246, u' PC 口只给 5V,EN 门限 13.9V 挡住,降压不启动;充电器插进 J23 也只给 5V', 10), (600, 219, u'节点:VIN_RAW(J1→F1)/ VIN_FUSED(F1 后 20V,≤5A)/ VIN_SW(降压输出 12V → A1 的 Rs6)', 10), (600, 202, u'已删(原 A1):DC-005 圆座 / Q1 反接保护 / DZ1·R1·C1 缓启动 —— USB-C 不会插反', 10), ] def build(): s = Sheet(None, NAME) place, exp = [], {} def put(d, k, x, y, rot, want): place.append([d, k, x, y, rot]) exp[d] = want # ── 第一行:J1 → F1 → VIN_FUSED 母线(输入电容 + EN 分压)───────────────── jx, jy = J1_C put('J1', K_TYPEC, jx, jy, 180, by_rel(jx, jy, TYPEC_REL180)) J = exp['J1'] assert J['B4A9'] == (145, 735) and J['A4B9'] == (145, 645) and J['A5'] == (145, 665) # 上面那只 VBUS 直走 F1;T 点挂 VIN_RAW 旗给这一组起名 s.w('VIN_RAW', J['B4A9'], (285, BUS_FUSED_Y)) s.w('VIN_RAW', (225, BUS_FUSED_Y), (225, 760)) s.pwr('VIN_RAW', 225, 760, 0) # 下面那只 VBUS 挂旗(直连会横穿 CC 线) s.w('VIN_RAW', J['A4B9'], (195, 645), (195, 625)) s.pwr('VIN_RAW', 195, 625, 180) # 两只 GND s.w('GND', J['B1A12'], (165, 745), (165, 765)) s.gnd(165, 765, 180) s.w('GND', J['A1B12'], (165, 635), (165, 615)) s.gnd(165, 615, 0) # CC1 / CC2 → 端口(对端在 U36 左侧) s.w('PD_CC1', J['A5'], (175, 665)) s.port('BI', 'PD_CC1', 175, 665, 0) s.w('PD_CC2', J['B5'], (175, 725)) s.port('BI', 'PD_CC2', 175, 725, 0) # 外壳 4 脚:各自横出到 x 55 的竖线上(T 接),竖线下端挂地 for k in ('4', '3', '2', '1'): s.w('GND', J[k], (55, J[k][1])) s.w('GND', (55, 745), (55, 695)) s.gnd(55, 695, 0) put('F1', K_FUSE, 305, BUS_FUSED_Y, 0, hz(305, BUS_FUSED_Y)) s.w('VIN_FUSED', (325, BUS_FUSED_Y), (720, BUS_FUSED_Y)) s.pwr('VIN_FUSED', 720, BUS_FUSED_Y, 0) # 母线下面挂一排:D3 TVS(K 在上)/ 4×10µ / 2×100n / C12(U36 的 VHV 1µ) row = [('D3', K_TVS, 360, 20), ('C21', K_10U50, 400, 20), ('C22', K_10U50, 440, 20), ('C23', K_10U50, 480, 20), ('C24', K_10U50, 520, 20), ('C19', K_100N50, 560, 20), ('C20', K_100N50, 600, 20), ('C12', K_1U50, 640, 15)] for d, k, x, half in row: cy = 700 if d == 'D3': put(d, k, x, cy, 270, {'1': (x, cy + half), '2': (x, cy - half)}) # 1 = K 在上 else: put(d, k, x, cy, 90, two_pin(x, cy, half)) s.w('VIN_FUSED', (x, cy + half), (x, BUS_FUSED_Y)) s.w('GND', (x, cy - half), (x, RAIL_IN_Y)) s.w('GND', (360, RAIL_IN_Y), (640, RAIL_IN_Y)) s.gnd(640, RAIL_IN_Y, 0) # EN 欠压门限:母线 → R12 100k → EN12 → R13 10k → 地 s.w('VIN_FUSED', (690, BUS_FUSED_Y), (690, 725)) put('R12', K_100K, 690, 705, 90, two_pin(690, 705)) s.w('EN12', (690, 685), (690, 665)) s.w('EN12', (690, 675), (710, 675)) s.port('BI', 'EN12', 710, 675, 0) put('R13', K_10K, 690, 645, 90, two_pin(690, 645)) s.w('GND', (690, 625), (690, 605)) s.gnd(690, 605, 0) # ── U36 CH224A(rot 180:CC/VBUS/CFG1/PG 在左、VHV/I²C/DP/DM 在右、EP 在下)── px, py = U36_C put('U36', K_PD, px, py, 180, by_rel(px, py, _rot180(PD_REL0))) P = exp['U36'] assert P['6'] == (860, 725) and P['1'] == (980, 685) and P['11'] == (910, 665) s.w('PD_CC2', P['6'], (830, 725)) s.port('BI', 'PD_CC2', 830, 725, 180) s.w('PD_CC1', P['7'], (830, 715)) s.port('BI', 'PD_CC1', 830, 715, 180) s.w('VIN_FUSED', P['8'], (800, 705), (800, 655)) # VBUS 与 VHV 同接(手册 6.1.1) s.pwr('VIN_FUSED', 800, 655, 180) s.w('#CFG1', P['9'], (840, 695), (840, 675)) # CFG1 → R7 120k → 地 ⇒ 20V put('R7', K_120K, 840, 655, 90, two_pin(840, 655)) s.w('GND', (840, 635), (840, 615)) s.gnd(840, 615, 0) s.w('GND', P['11'], (910, 645)) s.gnd(910, 645, 0) s.w('VIN_FUSED', P['1'], (1000, 685), (1000, 665)) s.pwr('VIN_FUSED', 1000, 665, 180) s.w('SCL', P['2'], (1040, 695)) # 主 I²C(2026-09-12 起;原 TCA9548A 通道 5) s.port('BI', 'SCL', 1040, 695, 0) s.w('SDA', P['3'], (1040, 705)) s.port('BI', 'SDA', 1040, 705, 0) # ── 第二行:U37 LM61495(rot 0)───────────────────────────────────────── bx, by = U37_C put('U37', K_BUCK, bx, by, 0, by_rel(bx, by, BUCK_REL0)) B = exp['U37'] assert B['16'] == (265, 525) and B['2'] == (395, 545) and B['6'] == (395, 585) for pin, net in (('3', 'BST12'), ('9', 'RT12'), ('11', 'VCC12'), ('16', 'SW12'), ('4', 'BST12'), ('13', 'EN12'), ('7', 'FB12')): s.w(net, B[pin], (235, B[pin][1])) s.port('BI', net, 235, B[pin][1], 180) # SYNC / BIAS 接地:短竖线并起来,中点往左绕开端口标签再挂地 s.w('GND', B['12'], (250, 515)) s.w('GND', B['5'], (250, 485)) s.w('GND', (250, 485), (250, 515)) s.w('GND', (250, 500), (140, 500), (140, 490)) s.gnd(140, 490, 0) # 右侧:三只地脚并到 x 415 竖线;两只 VIN 并到另一段 x 415 竖线;VCC 出端口 for pin in ('1', '15', '8'): s.w('GND', B[pin], (415, B[pin][1])) s.w('GND', (415, 475), (415, 435)) s.gnd(415, 435, 0) for pin in ('2', '14'): s.w('VIN_FUSED', B[pin], (415, B[pin][1])) s.w('VIN_FUSED', (415, 555), (415, 525)) s.pwr('VIN_FUSED', 415, 525, 180) s.w('VCC12', B['6'], (455, 585)) s.port('BI', 'VCC12', 455, 585, 0) # L4 + 输出电容 + VIN_SW put('L4', K_IND, 600, OUT_Y, 0, hz(600, OUT_Y)) s.w('SW12', (580, OUT_Y), (560, OUT_Y)) s.port('BI', 'SW12', 560, OUT_Y, 180) s.w('VIN_SW', (620, OUT_Y), (840, OUT_Y)) s.pwr('VIN_SW', 840, OUT_Y, 0) for i, x in enumerate((660, 700, 740, 780)): put('C%d' % (14 + i), K_22U, x, 495, 90, two_pin(x, 495, 15)) s.w('VIN_SW', (x, 510), (x, OUT_Y)) s.w('GND', (x, 480), (x, RAIL_OUT_Y)) s.w('GND', (660, RAIL_OUT_Y), (780, RAIL_OUT_Y)) s.gnd(780, RAIL_OUT_Y, 0) # FB 分压:VIN_SW → R10 110k → FB12 → R11 10k → 地 s.pwr('VIN_SW', 900, 565, 0) s.w('VIN_SW', (900, 565), (900, 545)) put('R10', K_110K, 900, 525, 90, two_pin(900, 525)) s.w('FB12', (900, 505), (900, 485)) s.w('FB12', (900, 495), (930, 495)) s.port('BI', 'FB12', 930, 495, 0) put('R11', K_10K, 900, 465, 90, two_pin(900, 465)) s.w('GND', (900, 445), (900, 425)) s.gnd(900, 425, 0) # ── 第三行:VCC 1µ / CBOOT 100n / RT 22.1k ───────────────────────────── s.port('BI', 'VCC12', 120, 420, 0) s.w('VCC12', (120, 420), (120, 400)) put('C13', K_1U, 120, 380, 90, two_pin(120, 380)) s.w('GND', (120, 360), (120, 340)) s.gnd(120, 340, 0) s.port('BI', 'BST12', 260, 420, 0) s.w('BST12', (260, 420), (260, 400)) put('C18', K_100N, 260, 380, 90, two_pin(260, 380)) s.w('SW12', (260, 360), (260, 340)) s.port('BI', 'SW12', 260, 340, 0) s.port('BI', 'RT12', 520, 420, 0) s.w('RT12', (520, 420), (520, 400)) put('R8', K_22K1, 520, 380, 90, two_pin(520, 380)) s.w('GND', (520, 360), (520, 340)) s.gnd(520, 340, 0) for t in TEXTS: s.txt(*t) assert len(set(d for d, _k, _x, _y, _r in place)) == len(place), u'位号撞车' return s, place, exp def preview(s, exp, path): u"""本地预览:导线 / 引脚 / 旗 / 端口标签框 / 文字框。只为人眼先扫一遍排版,不是判据。""" import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(16, 11.5)) ax.add_patch(plt.Rectangle((20, 10), 1125, 802, fill=False, color='#c00')) ax.add_patch(plt.Rectangle((455, 10), 690, 182, fill=False, color='#c00', ls='--')) for a, b, n in s.W: ax.plot([a[0], b[0]], [a[1], b[1]], color='#080' if n != 'GND' else '#555', lw=1) for d, want in exp.items(): pts = sorted(want['?']) if '?' in want else list(want.values()) xs, ys = [p[0] for p in pts], [p[1] for p in pts] ax.add_patch(plt.Rectangle((min(xs) - 3, min(ys) - 3), max(xs) - min(xs) + 6, max(ys) - min(ys) + 6, fill=False, color='#00a', lw=0.8)) ax.text(max(xs) + 4, max(ys) + 2, d, fontsize=7, color='#00a') ax.plot(xs, ys, 'o', ms=2, color='#00a') for kind, name, x, y, rot in s.F: dy = (20 if rot == 0 else -20) if kind == 'Power' else (-20 if rot == 0 else 20) ax.plot([x, x], [y, y + dy], color='#a0a', lw=3) ax.text(x + 3, y + dy, name, fontsize=6, color='#a0a') for d, name, x, y, rot in s.PT: w = 40 + 6.7 * len(name) x0 = x if rot == 0 else x - w ax.add_patch(plt.Rectangle((x0, y - 5), w, 10, fill=False, color='#e60')) ax.text(x0 + 2, y - 3, name, fontsize=6, color='#e60') for x, y, t, size in s.TX: cjk = sum(1 for ch in t if ord(ch) > 0x2E7F) w = size * (cjk + 0.5 * (len(t) - cjk)) ax.add_patch(plt.Rectangle((x, y - size * 0.8), w, size * 1.6, fill=False, color='#999', lw=0.6)) ax.set_xlim(0, 1170) ax.set_ylim(0, 825) ax.set_aspect('equal') fig.savefig(path, dpi=110, bbox_inches='tight') print(u'预览: %s' % path) def create_page(): uid = call(u""" const info = await eda.dmt_Schematic.getSchematicInfo('%s'); for (const p of (info.page||[])) if (String(p.name).toUpperCase()==='%s') return p.uuid; const u = await eda.dmt_Schematic.createSchematicPage('%s'); await eda.dmt_Schematic.modifySchematicPageName(u,'%s'); return u; """ % (SCH, NAME, SCH, NAME)) raw = io.open(UUIDS, encoding='utf-8').read() u = json.loads(raw) pages = u['boards']['BASE']['pages'] if pages.get(NAME) != uid: pages[NAME] = uid io.open(UUIDS, 'w', encoding='utf-8').write(json.dumps(u, ensure_ascii=False, indent=1) + '\n') print(u'页 %s = %s(已记入 r2-uuids.json)' % (NAME, uid)) return uid def main(): s, place, exp = build() s.assert_grid() import a1_input # 只借两个排版检查,它有 __main__ 保护 errs = s.netcheck(pins_of(exp), NC, ALLOW_SPLIT) errs += a1_input.text_clash(s, exp) + a1_input.flag_clash(s) s.report(len(place), errs) if '--png' in sys.argv: preview(s, exp, sys.argv[sys.argv.index('--png') + 1]) if '--create' in sys.argv: create_page() if '--push' not in sys.argv: return page = json.load(io.open(UUIDS, encoding='utf-8'))['boards']['BASE']['pages'].get(NAME) assert page, u'先跑 --create' s.page = page call("await eda.dmt_Project.openProject('%s'); await new Promise(r=>setTimeout(r,2500)); return 1;" % R2_PROJECT) s.clear() s.place(place, exp) s.push_geometry() call(u"return await eda.sch_Document.save();") # 存盘才分到 uniqueId(a1_input.py 同款) print(u'接着跑 verify_sch.py --nets,再 uid_sync_base.py') if __name__ == '__main__': main()