文件
phonerail/.claude/r2/a1b_pd.py
T

362 行
18 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- 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()