初始导入:PhoneRail 五路手机直供电源板
原理图/PCB 已在 EasyEDA 里做到 12 页跨页 0 漏接,固件刚完成骨架包 A1。 这次是把已有的一切一次性纳入版本控制,没有改动任何设计内容。 纳入: - 设计与需求文档、PROGRESS.md、小米8 参考原理图 PDF 与追脚脚本 - .claude/ 下的 EasyEDA 桥接工具链与几何/网表缓存(eda-parts.json、 pcb-parts.json、pcb-pads.json、p2-ch1.json 等,是重画各页的唯一事实来源) - 117 张验证截图(本项目所有方向问题都是靠人眼看截图发现的,属于证据不是垃圾) - firmware/ 的 A1 骨架与 site/ 文档站源码 .gitignore 排除:构建产物(firmware/build、host/build、sdkconfig)、 site/node_modules、__pycache__、桥接日志 _bridge.log、脚本自标的 *.stale。 .gitattributes 默认 `* -text`:现有 .py/.json 大多是 CRLF,原样存, 不让 git 重写行尾。唯一例外是 *.sh 强制 LF —— 带 \r 的 shell 脚本 bash 跑不了。 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
这个提交包含在:
@@ -0,0 +1,12 @@
|
||||
# 作废的文件(2026-09-03 移到这里)
|
||||
|
||||
`p2-nets.py` / `p2-pos.py` / `p2_nets.py` / `p2_pos.py` —— P2 早期的坐标表与网表源码。
|
||||
|
||||
**已经和页面对不上了,不要用:**
|
||||
|
||||
- 位号还是改号前的 `C1` / `R1`(页面上现在是 `C101` / `R101`,见设计文档 §16.0);
|
||||
- 端口位置和数量都变过(`VDAC1` 和三个按键端口改走短支线了,见 §16.5);
|
||||
- 缺 INA226 地址跳线的改动。
|
||||
|
||||
五路的唯一事实来源是 **`../p2-ch1.json`(几何快照)+ `../p2gen.py`(按通道改写)**。
|
||||
留在这里只是备查早期取值,别 import。
|
||||
@@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""P2-CHANNEL 单路完整连线定义。跑 `python nets.py` 做本地几何校验。"""
|
||||
import sys
|
||||
from router import w, gnd, pwr, port, txt, check, crossings, SEGS, FLAGS, PORTS
|
||||
|
||||
MULTI = {'GND', '+3V3', '+12V', 'VO1', 'EFUSE_EN1', 'VO1_P'}
|
||||
|
||||
# ===== Block 1 Buck =====
|
||||
pwr('+12V', 90, 795)
|
||||
w('+12V', (90, 795), (90, 645))
|
||||
w('+12V', (90, 775), (225, 775))
|
||||
for x in (120, 155, 190, 225):
|
||||
w('+12V', (x, 775), (x, 760))
|
||||
w('+12V', (90, 645), (230, 645)); w('+12V', (230, 645), (240, 645))
|
||||
w('+12V', (230, 645), (230, 665))
|
||||
w('+12V', (230, 655), (240, 655))
|
||||
w('+12V', (230, 665), (240, 665))
|
||||
for x in (120, 155, 190, 225):
|
||||
w('GND', (x, 720), (x, 705))
|
||||
w('GND', (100, 705), (225, 705))
|
||||
w('GND', (100, 695), (100, 705))
|
||||
w('GND', (240, 685), (210, 685)); w('GND', (240, 675), (210, 675))
|
||||
w('GND', (210, 675), (210, 705))
|
||||
w('GND', (170, 705), (170, 690)); gnd(170, 690)
|
||||
w('RT1', (240, 695), (140, 695))
|
||||
|
||||
w('EN12V', (360, 655), (430, 655)); w('EN12V', (430, 655), (430, 550))
|
||||
w('EN12V', (430, 550), (445, 550))
|
||||
w('EN12V', (445, 570), (445, 530))
|
||||
w('+12V', (445, 610), (445, 630)); pwr('+12V', 445, 630)
|
||||
w('GND', (445, 490), (445, 470)); gnd(445, 470)
|
||||
w('SS1', (360, 645), (400, 645)); w('SS1', (400, 645), (400, 615))
|
||||
w('GND', (400, 585), (400, 565)); gnd(400, 565)
|
||||
w('COMP1', (360, 635), (385, 635)); w('COMP1', (385, 635), (385, 565))
|
||||
w('COMP1', (385, 565), (340, 565))
|
||||
w('COMP1', (295, 525), (295, 590)); w('COMP1', (295, 590), (385, 590))
|
||||
w('COMPZ', (340, 525), (340, 505))
|
||||
w('GND', (340, 470), (340, 450)); w('GND', (295, 495), (295, 450))
|
||||
w('GND', (295, 450), (340, 450)); w('GND', (315, 450), (315, 435)); gnd(315, 435)
|
||||
w('GND', (360, 625), (375, 625)); w('GND', (375, 625), (375, 610)); gnd(375, 610)
|
||||
|
||||
w('BOOT1', (360, 685), (380, 685)); w('BOOT1', (380, 685), (380, 730))
|
||||
w('SW1', (360, 675), (420, 675))
|
||||
w('SW1', (360, 665), (400, 665)); w('SW1', (400, 665), (400, 675))
|
||||
w('SW1', (420, 730), (420, 700)); w('SW1', (420, 700), (410, 700))
|
||||
w('SW1', (410, 700), (410, 675))
|
||||
w('PWRGD', (360, 695), (370, 695)); w('PWRGD', (370, 695), (370, 760))
|
||||
port('BUCK_PG', 370, 760, 0, 'OUT')
|
||||
|
||||
# ===== VO1 母线 =====
|
||||
w('VO1', (460, 675), (630, 675))
|
||||
w('VO1', (480, 655), (480, 675)); w('VO1', (515, 655), (515, 675))
|
||||
w('VO1', (550, 660), (550, 675))
|
||||
w('GND', (480, 625), (480, 615)); w('GND', (515, 625), (515, 615))
|
||||
w('GND', (550, 620), (550, 615))
|
||||
w('GND', (480, 615), (550, 615)); w('GND', (480, 615), (480, 600)); gnd(480, 600)
|
||||
w('VO1', (465, 675), (465, 705)); port('VO1', 465, 705, 0, 'OUT')
|
||||
|
||||
# ===== Block 2 反馈 + DAC 注入 =====
|
||||
w('VO1', (180, 360), (180, 385)); port('VO1', 180, 385, 0, 'IN')
|
||||
w('FB1', (180, 320), (180, 280))
|
||||
w('FB1', (240, 300), (180, 300))
|
||||
w('FB1', (215, 300), (215, 635)); w('FB1', (215, 635), (240, 635))
|
||||
w('GND', (180, 240), (180, 220)); gnd(180, 220)
|
||||
w('VDAC_F', (280, 300), (350, 300))
|
||||
w('VDAC_F', (325, 300), (325, 270))
|
||||
w('GND', (325, 240), (325, 220)); gnd(325, 220)
|
||||
w('VDAC1', (390, 300), (420, 300)); port('VDAC1', 420, 300, 0, 'IN')
|
||||
|
||||
# ===== Block 3 eFuse =====
|
||||
w('VO1', (610, 675), (610, 655))
|
||||
w('VO1', (610, 665), (630, 665)); w('VO1', (610, 655), (630, 655))
|
||||
w('VO1', (600, 675), (600, 720)); w('VO1', (600, 720), (770, 720))
|
||||
w('VO1', (770, 720), (770, 660))
|
||||
w('VO1', (770, 675), (730, 675)); w('VO1', (770, 665), (730, 665))
|
||||
|
||||
w('EFUSE_EN1', (630, 645), (565, 645)); w('EFUSE_EN1', (565, 645), (565, 570))
|
||||
w('EFUSE_EN1', (565, 570), (490, 570))
|
||||
w('EFUSE_EN1', (530, 570), (530, 600)); port('EFUSE_EN1', 530, 600, 0, 'IN')
|
||||
w('GND', (490, 530), (490, 510)); gnd(490, 510)
|
||||
|
||||
w('OVP1', (630, 635), (575, 635)); w('OVP1', (575, 635), (575, 525))
|
||||
w('OVP1', (575, 525), (465, 525)); w('OVP1', (465, 525), (465, 500))
|
||||
w('VO1', (465, 565), (465, 675))
|
||||
w('GND', (465, 460), (465, 440)); gnd(465, 440)
|
||||
|
||||
w('GND', (630, 625), (585, 625)); w('GND', (585, 625), (585, 510))
|
||||
w('GND', (585, 510), (545, 510)); gnd(545, 510)
|
||||
|
||||
w('ILIM1', (630, 615), (595, 615)); w('ILIM1', (595, 615), (595, 500))
|
||||
w('ILIM1', (595, 500), (565, 500))
|
||||
w('GND', (565, 460), (540, 460)); w('GND', (540, 460), (540, 445)); gnd(540, 445)
|
||||
|
||||
w('DVDT1', (630, 605), (605, 605)); w('DVDT1', (605, 605), (605, 440))
|
||||
w('DVDT1', (605, 440), (595, 440))
|
||||
w('GND', (595, 405), (595, 385)); w('GND', (595, 385), (575, 385)); gnd(575, 385)
|
||||
|
||||
w('IMON1', (630, 595), (610, 595)); w('IMON1', (610, 595), (610, 400))
|
||||
w('IMON1', (610, 400), (520, 400))
|
||||
w('GND', (480, 400), (460, 400)); w('GND', (460, 400), (460, 385)); gnd(460, 385)
|
||||
|
||||
w('FLT1', (630, 585), (620, 585)); w('FLT1', (620, 585), (620, 355))
|
||||
w('FLT1', (620, 355), (545, 355))
|
||||
w('+3V3', (545, 395), (545, 415)); pwr('+3V3', 545, 415)
|
||||
w('FLT1', (600, 355), (600, 375)); port('EFUSE_FLT1', 600, 375, 0, 'OUT')
|
||||
|
||||
w('GND', (630, 575), (625, 575)); w('GND', (625, 575), (625, 340))
|
||||
w('GND', (625, 340), (615, 340)); gnd(615, 340)
|
||||
|
||||
w('GND', (730, 585), (755, 585)); w('GND', (755, 585), (755, 565)); gnd(755, 565)
|
||||
w('EFUSE_PG1', (730, 595), (790, 595)); w('EFUSE_PG1', (790, 595), (790, 565))
|
||||
w('+3V3', (790, 525), (790, 505)); pwr('+3V3', 790, 505)
|
||||
w('EFUSE_PG1', (760, 595), (760, 530)); port('EFUSE_PG1', 760, 530, 180, 'OUT')
|
||||
w('PGTH1', (730, 605), (810, 605)); w('PGTH1', (810, 605), (810, 585))
|
||||
w('PGTH1', (810, 585), (830, 585)); w('PGTH1', (830, 595), (830, 575))
|
||||
w('GND', (830, 535), (830, 515)); gnd(830, 515)
|
||||
|
||||
for y in (615, 625, 635, 645, 655):
|
||||
w('VO1_F', (730, y), (790, y))
|
||||
w('VO1_F', (790, 655), (790, 615))
|
||||
w('VO1_F', (790, 635), (900, 635))
|
||||
|
||||
w('VO1_P', (900, 675), (900, 695)); w('VO1_P', (900, 695), (1130, 695))
|
||||
w('VO1_P', (1060, 695), (1060, 765)); w('VO1_P', (1060, 765), (1100, 765))
|
||||
w('GND', (1100, 755), (1075, 755)); gnd(1075, 755)
|
||||
|
||||
# ===== INA226 =====
|
||||
w('VO1_F', (870, 480), (880, 480)); w('VO1_F', (880, 480), (880, 635))
|
||||
w('VO1_P', (870, 470), (930, 470)); w('VO1_P', (930, 470), (930, 695))
|
||||
w('VO1_P', (870, 460), (890, 460)); w('VO1_P', (890, 460), (890, 470))
|
||||
w('GND', (870, 450), (920, 450)); w('GND', (920, 450), (920, 425)); gnd(920, 425)
|
||||
w('+3V3', (870, 440), (960, 440)); w('+3V3', (960, 440), (960, 520))
|
||||
w('+3V3', (960, 520), (975, 520)); w('+3V3', (960, 545), (960, 520)); pwr('+3V3', 960, 545)
|
||||
w('GND', (1015, 520), (1035, 520)); w('GND', (1035, 520), (1035, 500)); gnd(1035, 500)
|
||||
w('GND', (770, 480), (740, 480)); w('GND', (770, 470), (740, 470))
|
||||
w('GND', (740, 470), (740, 480)); w('GND', (740, 480), (685, 480))
|
||||
w('GND', (685, 480), (685, 440)); gnd(685, 440)
|
||||
w('INA_ALERT', (770, 460), (700, 460)); port('INA_ALERT', 700, 460, 180, 'OUT')
|
||||
w('SDA', (770, 450), (700, 450)); w('SDA', (700, 450), (700, 400))
|
||||
port('SDA', 700, 400, 180, 'BI')
|
||||
w('SCL', (770, 440), (750, 440)); w('SCL', (750, 440), (750, 395))
|
||||
port('SCL', 750, 395, 0, 'BI')
|
||||
|
||||
# ===== 放电联锁 =====
|
||||
w('+3V3', (1060, 565), (1060, 585)); pwr('+3V3', 1060, 585)
|
||||
w('DIS1_G', (1060, 525), (1060, 505)); w('DIS1_G', (1060, 505), (1090, 505))
|
||||
w('DIS1_G', (1090, 505), (1090, 490))
|
||||
w('DIS1_G', (1075, 505), (1075, 560)); w('DIS1_G', (1075, 560), (1100, 560))
|
||||
w('GND', (1090, 450), (1090, 430)); gnd(1090, 430)
|
||||
w('EFUSE_EN1', (1060, 470), (1060, 430)); w('EFUSE_EN1', (1060, 430), (1000, 430))
|
||||
port('EFUSE_EN1', 1000, 430, 180, 'IN')
|
||||
w('VO1_P', (1130, 650), (1130, 695))
|
||||
w('DIS1_D', (1130, 610), (1130, 580))
|
||||
w('GND', (1130, 540), (1130, 520)); gnd(1130, 520)
|
||||
|
||||
# ===== Block 4 按键驱动 + J7 =====
|
||||
KEYS = [
|
||||
('K1_PWR', 600, 380),
|
||||
('K1_VUP', 760, 370),
|
||||
('K1_VDN', 920, 360),
|
||||
]
|
||||
for name, rx, jy in KEYS:
|
||||
w(name, (rx - 20, 300), (rx - 40, 300)); w(name, (rx - 40, 300), (rx - 40, 340))
|
||||
port(name, rx - 40, 340, 0, 'IN')
|
||||
w(name + '_G', (rx + 20, 300), (rx + 30, 300))
|
||||
w(name + '_G', (rx + 25, 300), (rx + 25, 285))
|
||||
w('GND', (rx + 25, 245), (rx + 25, 230)); gnd(rx + 25, 230)
|
||||
w('GND', (rx + 60, 280), (rx + 60, 255)); gnd(rx + 60, 255)
|
||||
w(name + '_D', (rx + 60, 320), (rx + 60, jy)); w(name + '_D', (rx + 60, jy), (1025, jy))
|
||||
w('GND', (1025, 350), (1010, 350)); w('GND', (1010, 350), (1010, 330)); gnd(1010, 330)
|
||||
w('GND', (1025, 330), (1010, 330))
|
||||
w('GND', (1025, 400), (1025, 420)); w('GND', (1025, 420), (960, 420)); gnd(960, 420)
|
||||
|
||||
# ===== 文字 =====
|
||||
txt('P2 CH1 单路通道(5 路完全相同:复制后改位号 / INA226 地址 / 网络名后缀)', 60, 805, 16)
|
||||
txt('TPS54622: Vref 0.6V, fsw 480kHz(R3=100k), Vout = 4.222 - 0.2813*Vd', 60, 185, 11)
|
||||
txt('TPS25940A: OVP 4.36V(R9/R10), ILIM 4.45A(R11), dVdT 33nF -> 0.36V/ms', 60, 165, 11)
|
||||
txt('缓启动 11.5ms / 涌流 0.8A ; INA226 CH1: A1=A0=GND -> 0x40', 60, 145, 11)
|
||||
txt('放电互锁: EN 低 -> Q1 截止 -> Q2 导通 -> R19 10R 泄放手机侧 2200uF', 60, 125, 11)
|
||||
|
||||
if __name__ == '__main__':
|
||||
errs = [e for e in check()
|
||||
if not any(e.startswith('网络 %s ' % m) for m in MULTI)]
|
||||
for e in errs:
|
||||
print('ERR', e)
|
||||
print('--- segs %d flags %d ports %d ---' % (len(SEGS), len(FLAGS), len(PORTS)))
|
||||
cr = crossings()
|
||||
print('crossings %d:' % len(cr))
|
||||
for c in cr:
|
||||
print(' ', c)
|
||||
sys.exit(1 if errs else 0)
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
POS = {
|
||||
'U1':(300,660,0),
|
||||
'C1':(120,740,270),'C2':(155,740,270),'C3':(190,740,0),'C4':(225,740,270),
|
||||
'R3':(120,695,0),'C5':(400,730,0),'L1':(440,675,0),
|
||||
'R4':(445,590,270),'R5':(445,510,270),'C9':(400,600,270),
|
||||
'R6':(340,545,270),'C10':(340,485,270),'C11':(295,510,270),
|
||||
'C6':(480,640,270),'C7':(515,640,270),'C8':(550,640,270),
|
||||
'R1':(180,340,270),'R2':(180,260,270),
|
||||
'R7':(260,300,0),'R8':(370,300,0),'C12':(325,255,270),
|
||||
'U14':(680,625,180),
|
||||
'R14':(490,550,270),'R9':(465,545,270),'R10':(465,480,270),
|
||||
'R11':(565,480,270),'C14':(595,420,270),'R12':(500,400,0),'R13':(545,375,270),
|
||||
'R15':(790,545,270),'R16':(830,615,270),'R17':(830,555,270),
|
||||
'Rs1':(900,655,270),'U8':(820,460,0),'C13':(995,520,0),
|
||||
'J2':(1120,760,0),
|
||||
'Q1':(1080,470,0),'R18':(1060,545,270),
|
||||
'Q2':(1120,560,0),'R19':(1130,630,270),
|
||||
'R20':(600,300,0),'Q3':(650,300,0),'R21':(625,265,270),
|
||||
'R22':(760,300,0),'Q4':(810,300,0),'R23':(785,265,270),
|
||||
'R24':(920,300,0),'Q5':(970,300,0),'R25':(945,265,270),
|
||||
'J7':(1050,365,0),
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""P2-CHANNEL 单路完整连线定义。跑 `python nets.py` 做本地几何校验。"""
|
||||
import sys, os
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
sys.path.insert(0, os.path.join(HERE, 'skills', 'easyeda-sch-geometry', 'scripts'))
|
||||
import local_netcheck as N
|
||||
w, gnd, pwr, port, txt = N.w, N.gnd, N.pwr, N.port, N.txt
|
||||
check, crossings, SEGS, FLAGS, PORTS = N.check, N.crossings, N.SEGS, N.FLAGS, N.PORTS
|
||||
|
||||
MULTI = {'GND', '+3V3', '+12V', 'VO1', 'EFUSE_EN1', 'VO1_P'}
|
||||
|
||||
# ===== Block 1 Buck =====
|
||||
pwr('+12V', 90, 795)
|
||||
w('+12V', (90, 795), (90, 645))
|
||||
w('+12V', (90, 775), (225, 775))
|
||||
for x in (120, 155, 190, 225):
|
||||
w('+12V', (x, 775), (x, 760))
|
||||
w('+12V', (90, 645), (230, 645)); w('+12V', (230, 645), (240, 645))
|
||||
w('+12V', (230, 645), (230, 665))
|
||||
w('+12V', (230, 655), (240, 655))
|
||||
w('+12V', (230, 665), (240, 665))
|
||||
for x in (120, 155, 190, 225):
|
||||
w('GND', (x, 720), (x, 705))
|
||||
w('GND', (100, 705), (225, 705))
|
||||
w('GND', (100, 695), (100, 705))
|
||||
w('GND', (240, 685), (210, 685)); w('GND', (240, 675), (210, 675))
|
||||
w('GND', (210, 675), (210, 705))
|
||||
w('GND', (170, 705), (170, 690)); gnd(170, 690)
|
||||
w('RT1', (240, 695), (140, 695))
|
||||
|
||||
w('EN12V', (360, 655), (430, 655)); w('EN12V', (430, 655), (430, 550))
|
||||
w('EN12V', (430, 550), (445, 550))
|
||||
w('EN12V', (445, 570), (445, 530))
|
||||
w('+12V', (445, 610), (445, 630)); pwr('+12V', 445, 630)
|
||||
w('GND', (445, 490), (445, 470)); gnd(445, 470)
|
||||
w('SS1', (360, 645), (400, 645)); w('SS1', (400, 645), (400, 615))
|
||||
w('GND', (400, 585), (400, 565)); gnd(400, 565)
|
||||
w('COMP1', (360, 635), (385, 635)); w('COMP1', (385, 635), (385, 565))
|
||||
w('COMP1', (385, 565), (340, 565))
|
||||
w('COMP1', (295, 525), (295, 590)); w('COMP1', (295, 590), (385, 590))
|
||||
w('COMPZ', (340, 525), (340, 505))
|
||||
w('GND', (340, 470), (340, 450)); w('GND', (295, 495), (295, 450))
|
||||
w('GND', (295, 450), (340, 450)); w('GND', (315, 450), (315, 435)); gnd(315, 435)
|
||||
w('GND', (360, 625), (375, 625)); w('GND', (375, 625), (375, 610)); gnd(375, 610)
|
||||
|
||||
w('BOOT1', (360, 685), (380, 685)); w('BOOT1', (380, 685), (380, 730))
|
||||
w('SW1', (360, 675), (420, 675))
|
||||
w('SW1', (360, 665), (400, 665)); w('SW1', (400, 665), (400, 675))
|
||||
w('SW1', (420, 730), (420, 700)); w('SW1', (420, 700), (410, 700))
|
||||
w('SW1', (410, 700), (410, 675))
|
||||
w('PWRGD', (360, 695), (370, 695)); w('PWRGD', (370, 695), (370, 760))
|
||||
port('BUCK_PG', 370, 760, 0, 'OUT')
|
||||
|
||||
# ===== VO1 母线 =====
|
||||
w('VO1', (460, 675), (630, 675))
|
||||
w('VO1', (480, 655), (480, 675)); w('VO1', (515, 655), (515, 675))
|
||||
w('VO1', (550, 660), (550, 675))
|
||||
w('GND', (480, 625), (480, 615)); w('GND', (515, 625), (515, 615))
|
||||
w('GND', (550, 620), (550, 615))
|
||||
w('GND', (480, 615), (550, 615)); w('GND', (480, 615), (480, 600)); gnd(480, 600)
|
||||
w('VO1', (465, 675), (465, 705)); port('VO1', 465, 705, 0, 'OUT')
|
||||
|
||||
# ===== Block 2 反馈 + DAC 注入 =====
|
||||
w('VO1', (180, 360), (180, 385)); port('VO1', 180, 385, 0, 'IN')
|
||||
w('FB1', (180, 320), (180, 280))
|
||||
w('FB1', (240, 300), (180, 300))
|
||||
w('FB1', (215, 300), (215, 635)); w('FB1', (215, 635), (240, 635))
|
||||
w('GND', (180, 240), (180, 220)); gnd(180, 220)
|
||||
w('VDAC_F', (280, 300), (350, 300))
|
||||
w('VDAC_F', (325, 300), (325, 270))
|
||||
w('GND', (325, 240), (325, 220)); gnd(325, 220)
|
||||
w('VDAC1', (390, 300), (420, 300)); port('VDAC1', 420, 300, 0, 'IN')
|
||||
|
||||
# ===== Block 3 eFuse =====
|
||||
w('VO1', (610, 675), (610, 655))
|
||||
w('VO1', (610, 665), (630, 665)); w('VO1', (610, 655), (630, 655))
|
||||
w('VO1', (600, 675), (600, 720)); w('VO1', (600, 720), (770, 720))
|
||||
w('VO1', (770, 720), (770, 660))
|
||||
w('VO1', (770, 675), (730, 675)); w('VO1', (770, 665), (730, 665))
|
||||
|
||||
w('EFUSE_EN1', (630, 645), (565, 645)); w('EFUSE_EN1', (565, 645), (565, 570))
|
||||
w('EFUSE_EN1', (565, 570), (490, 570))
|
||||
w('EFUSE_EN1', (530, 570), (530, 600)); port('EFUSE_EN1', 530, 600, 0, 'IN')
|
||||
w('GND', (490, 530), (490, 510)); gnd(490, 510)
|
||||
|
||||
w('OVP1', (630, 635), (575, 635)); w('OVP1', (575, 635), (575, 525))
|
||||
w('OVP1', (575, 525), (465, 525)); w('OVP1', (465, 525), (465, 500))
|
||||
w('VO1', (465, 565), (465, 675))
|
||||
w('GND', (465, 460), (465, 440)); gnd(465, 440)
|
||||
|
||||
w('GND', (630, 625), (585, 625)); w('GND', (585, 625), (585, 510))
|
||||
w('GND', (585, 510), (545, 510)); gnd(545, 510)
|
||||
|
||||
w('ILIM1', (630, 615), (595, 615)); w('ILIM1', (595, 615), (595, 500))
|
||||
w('ILIM1', (595, 500), (565, 500))
|
||||
w('GND', (565, 460), (540, 460)); w('GND', (540, 460), (540, 445)); gnd(540, 445)
|
||||
|
||||
w('DVDT1', (630, 605), (605, 605)); w('DVDT1', (605, 605), (605, 440))
|
||||
w('DVDT1', (605, 440), (595, 440))
|
||||
w('GND', (595, 405), (595, 385)); w('GND', (595, 385), (575, 385)); gnd(575, 385)
|
||||
|
||||
w('IMON1', (630, 595), (610, 595)); w('IMON1', (610, 595), (610, 400))
|
||||
w('IMON1', (610, 400), (520, 400))
|
||||
w('GND', (480, 400), (460, 400)); w('GND', (460, 400), (460, 385)); gnd(460, 385)
|
||||
|
||||
w('FLT1', (630, 585), (620, 585)); w('FLT1', (620, 585), (620, 355))
|
||||
w('FLT1', (620, 355), (545, 355))
|
||||
w('+3V3', (545, 395), (545, 415)); pwr('+3V3', 545, 415)
|
||||
w('FLT1', (600, 355), (600, 375)); port('EFUSE_FLT1', 600, 375, 0, 'OUT')
|
||||
|
||||
w('GND', (630, 575), (625, 575)); w('GND', (625, 575), (625, 340))
|
||||
w('GND', (625, 340), (615, 340)); gnd(615, 340)
|
||||
|
||||
w('GND', (730, 585), (755, 585)); w('GND', (755, 585), (755, 565)); gnd(755, 565)
|
||||
w('EFUSE_PG1', (730, 595), (790, 595)); w('EFUSE_PG1', (790, 595), (790, 565))
|
||||
w('+3V3', (790, 525), (790, 505)); pwr('+3V3', 790, 505)
|
||||
w('EFUSE_PG1', (760, 595), (760, 530)); port('EFUSE_PG1', 760, 530, 180, 'OUT')
|
||||
w('PGTH1', (730, 605), (810, 605)); w('PGTH1', (810, 605), (810, 585))
|
||||
w('PGTH1', (810, 585), (830, 585)); w('PGTH1', (830, 595), (830, 575))
|
||||
w('GND', (830, 535), (830, 515)); gnd(830, 515)
|
||||
|
||||
for y in (615, 625, 635, 645, 655):
|
||||
w('VO1_F', (730, y), (790, y))
|
||||
w('VO1_F', (790, 655), (790, 615))
|
||||
w('VO1_F', (790, 635), (900, 635))
|
||||
|
||||
w('VO1_P', (900, 675), (900, 695)); w('VO1_P', (900, 695), (1130, 695))
|
||||
w('VO1_P', (1060, 695), (1060, 765)); w('VO1_P', (1060, 765), (1100, 765))
|
||||
w('GND', (1100, 755), (1075, 755)); gnd(1075, 755)
|
||||
|
||||
# ===== INA226 =====
|
||||
w('VO1_F', (870, 480), (880, 480)); w('VO1_F', (880, 480), (880, 635))
|
||||
w('VO1_P', (870, 470), (930, 470)); w('VO1_P', (930, 470), (930, 695))
|
||||
w('VO1_P', (870, 460), (890, 460)); w('VO1_P', (890, 460), (890, 470))
|
||||
w('GND', (870, 450), (920, 450)); w('GND', (920, 450), (920, 425)); gnd(920, 425)
|
||||
w('+3V3', (870, 440), (960, 440)); w('+3V3', (960, 440), (960, 520))
|
||||
w('+3V3', (960, 520), (975, 520)); w('+3V3', (960, 545), (960, 520)); pwr('+3V3', 960, 545)
|
||||
w('GND', (1015, 520), (1035, 520)); w('GND', (1035, 520), (1035, 500)); gnd(1035, 500)
|
||||
w('GND', (770, 480), (740, 480)); w('GND', (770, 470), (740, 470))
|
||||
w('GND', (740, 470), (740, 480)); w('GND', (740, 480), (685, 480))
|
||||
w('GND', (685, 480), (685, 440)); gnd(685, 440)
|
||||
w('INA_ALERT', (770, 460), (700, 460)); port('INA_ALERT', 700, 460, 180, 'OUT')
|
||||
w('SDA', (770, 450), (700, 450)); w('SDA', (700, 450), (700, 400))
|
||||
port('SDA', 700, 400, 180, 'BI')
|
||||
w('SCL', (770, 440), (750, 440)); w('SCL', (750, 440), (750, 395))
|
||||
port('SCL', 750, 395, 0, 'BI')
|
||||
|
||||
# ===== 放电联锁 =====
|
||||
w('+3V3', (1060, 565), (1060, 585)); pwr('+3V3', 1060, 585)
|
||||
w('DIS1_G', (1060, 525), (1060, 505)); w('DIS1_G', (1060, 505), (1090, 505))
|
||||
w('DIS1_G', (1090, 505), (1090, 490))
|
||||
w('DIS1_G', (1075, 505), (1075, 560)); w('DIS1_G', (1075, 560), (1100, 560))
|
||||
w('GND', (1090, 450), (1090, 430)); gnd(1090, 430)
|
||||
w('EFUSE_EN1', (1060, 470), (1060, 430)); w('EFUSE_EN1', (1060, 430), (1000, 430))
|
||||
port('EFUSE_EN1', 1000, 430, 180, 'IN')
|
||||
w('VO1_P', (1130, 650), (1130, 695))
|
||||
w('DIS1_D', (1130, 610), (1130, 580))
|
||||
w('GND', (1130, 540), (1130, 520)); gnd(1130, 520)
|
||||
|
||||
# ===== Block 4 按键驱动 + J7 =====
|
||||
KEYS = [
|
||||
('K1_PWR', 600, 380),
|
||||
('K1_VUP', 760, 370),
|
||||
('K1_VDN', 920, 360),
|
||||
]
|
||||
for name, rx, jy in KEYS:
|
||||
w(name, (rx - 20, 300), (rx - 40, 300)); w(name, (rx - 40, 300), (rx - 40, 340))
|
||||
port(name, rx - 40, 340, 0, 'IN')
|
||||
w(name + '_G', (rx + 20, 300), (rx + 30, 300))
|
||||
w(name + '_G', (rx + 25, 300), (rx + 25, 285))
|
||||
w('GND', (rx + 25, 245), (rx + 25, 230)); gnd(rx + 25, 230)
|
||||
w('GND', (rx + 60, 280), (rx + 60, 255)); gnd(rx + 60, 255)
|
||||
w(name + '_D', (rx + 60, 320), (rx + 60, jy)); w(name + '_D', (rx + 60, jy), (1025, jy))
|
||||
w('GND', (1025, 350), (1010, 350)); w('GND', (1010, 350), (1010, 330)); gnd(1010, 330)
|
||||
w('GND', (1025, 330), (1010, 330))
|
||||
w('GND', (1025, 400), (1025, 420)); w('GND', (1025, 420), (960, 420)); gnd(960, 420)
|
||||
|
||||
# ===== 文字 =====
|
||||
txt('P2 CH1 单路通道(5 路完全相同:复制后改位号 / INA226 地址 / 网络名后缀)', 60, 805, 16)
|
||||
txt('TPS54622: Vref 0.6V, fsw 480kHz(R3=100k), Vout = 4.222 - 0.2813*Vd', 60, 185, 11)
|
||||
txt('TPS25940A: OVP 4.36V(R9/R10), ILIM 4.45A(R11), dVdT 33nF -> 0.36V/ms', 60, 165, 11)
|
||||
txt('缓启动 11.5ms / 涌流 0.8A ; INA226 CH1: A1=A0=GND -> 0x40', 60, 145, 11)
|
||||
txt('放电互锁: EN 低 -> Q1 截止 -> Q2 导通 -> R19 10R 泄放手机侧 2200uF', 60, 125, 11)
|
||||
|
||||
if __name__ == '__main__':
|
||||
errs = [e for e in check()
|
||||
if not any(e.startswith('网络 %s ' % m) for m in MULTI)]
|
||||
for e in errs:
|
||||
print('ERR', e)
|
||||
print('--- segs %d flags %d ports %d ---' % (len(SEGS), len(FLAGS), len(PORTS)))
|
||||
cr = crossings()
|
||||
print('crossings %d:' % len(cr))
|
||||
for c in cr:
|
||||
print(' ', c)
|
||||
sys.exit(1 if errs else 0)
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
POS = {
|
||||
'U1':(300,660,0),
|
||||
'C1':(120,740,270),'C2':(155,740,270),'C3':(190,740,0),'C4':(225,740,270),
|
||||
'R3':(120,695,0),'C5':(400,730,0),'L1':(440,675,0),
|
||||
'R4':(445,590,270),'R5':(445,510,270),'C9':(400,600,270),
|
||||
'R6':(340,545,270),'C10':(340,485,270),'C11':(295,510,270),
|
||||
'C6':(480,640,270),'C7':(515,640,270),'C8':(550,640,270),
|
||||
'R1':(180,340,270),'R2':(180,260,270),
|
||||
'R7':(260,300,0),'R8':(370,300,0),'C12':(325,255,270),
|
||||
'U14':(680,625,180),
|
||||
'R14':(490,550,270),'R9':(465,545,270),'R10':(465,480,270),
|
||||
'R11':(565,480,270),'C14':(595,420,270),'R12':(500,400,0),'R13':(545,375,270),
|
||||
'R15':(790,545,270),'R16':(830,615,270),'R17':(830,555,270),
|
||||
'Rs1':(900,655,270),'U8':(820,460,0),'C13':(995,520,0),
|
||||
'J2':(1120,760,0),
|
||||
'Q1':(1080,470,0),'R18':(1060,545,270),
|
||||
'Q2':(1120,560,0),'R19':(1130,630,270),
|
||||
'R20':(600,300,0),'Q3':(650,300,0),'R21':(625,265,270),
|
||||
'R22':(760,300,0),'Q4':(810,300,0),'R23':(785,265,270),
|
||||
'R24':(920,300,0),'Q5':(970,300,0),'R25':(945,265,270),
|
||||
'J7':(1050,365,0),
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""六对**长差分**的路径(USB1~5 的 U32↔U5n + UPSTREAM 的 U32↔J23)。
|
||||
|
||||
被 `pcb2_route_dp.py` import;校核 / 下发都用那个脚本,这里只放坐标。
|
||||
|
||||
## 拓扑(2026-09-06 第三轮定,理由见设计文档 §25.13)
|
||||
|
||||
U32 是 **QFN-64、贴在底层**,十根下游 D± 全部从**西边缘**出来(0.5 mm 脚距,
|
||||
南→北 = port1..5)。目的地却是四面八方:
|
||||
|
||||
| 对 | 去哪 | 走法 |
|
||||
|---|---|---|
|
||||
| 1 | site1 (61.5, 76.4) 西北 | 底层西行 → x=98.3/98.8 爬升 → 西行车道 74.70/75.55 |
|
||||
| 2 | site2 (133, 76.4) 东北 | 底层西行 → 爬升 → **北高速「高窗」** 78.29/78.77 → 下降 |
|
||||
| 3 | site3 (204.5, 76.4) 东北 | 底层西行 → 爬升 → **北高速「低窗」** 76.20/76.68 |
|
||||
| 4 | site4 (49.4, 58.8) 西南 | **换顶层**南下(唯一要穿过其它对的一对)→ 底层西行 60.5/61.0 |
|
||||
| 5 | site5 (120.9, 58.8) 东南 | 底层爬升 → **顶层**过隘口 75.69/76.12 → 底层南下 |
|
||||
| UP | J23 (212.4, 61~63) 正东 | U32 南边「竖井」下到 y≈57 → 东行 → 北抬到 60.5/61.2 |
|
||||
|
||||
### 为什么这么绕:x 112~116 那道隘口
|
||||
|
||||
C751(10 µF 1206,底层,(113.0,74.0))+ C815 的 GND 扇出孔 (113.047,77.5)
|
||||
把北高速掐成两段。底层在 x=113 只剩两个窗口:
|
||||
|
||||
* **低窗** y 76.045~76.855(线心 76.185~76.72,**只塞得下一对**)
|
||||
* **高窗** y 78.145~81.66(宽,但东边 (117.067,78.392) 那个孔又把 x=117 的天花板压到 77.75,
|
||||
所以出了隘口必须马上降下来)
|
||||
|
||||
顶层同一段是通的(C751 在底层),y 75.44~76.3 还有一条**顶层窗口**——
|
||||
而且 x 80~116 / y 67.6~84 底下正好是 Inner1 的 `VO2_P` 整岛,
|
||||
**一整块实心参考面、不跨分割**(§10.1 的约束按「一块铜」判,不是按「必须是地」)。
|
||||
所以第三对(pair 5)从这里过,两端的过孔都落在岛内(x ≤ 115.8)。
|
||||
|
||||
### 排序规则(写路径时逐条对照,避免同层交叉)
|
||||
|
||||
一束平行线拐弯时**外侧的先拐**:西行右转向北 → 最东的先拐;
|
||||
北行右转向东 → 最西的在最高的车道。由此从 U32 西边缘(南→北 = 1,2,3,4,5)
|
||||
出发,爬升后 x 由西到东是 1,2,3,5,转东后车道由北到南是 **2,3,5** ——
|
||||
正好满足「pair2 要在 x=133 北拐、pair3 继续东行、pair5 在 x=121 南下」。
|
||||
|
||||
### 每个站点最后 2 mm 都是同一个花样
|
||||
|
||||
U5n 的 DP/DM 焊盘在**顶层**、相距 1.9 mm,而车道上的南北次序和焊盘的东西次序
|
||||
永远差一次交叉。解法统一:**外侧那根先打过孔上顶层**,从另一根的**底层**车道
|
||||
上方跨过去,另一根再在自己焊盘正下方上来。两根 1.9 mm 分开,过孔互不干涉。
|
||||
"""
|
||||
import math
|
||||
|
||||
L_TOP, L_BOT = 1, 2
|
||||
W = 0.28
|
||||
|
||||
|
||||
def chamfer(pts, r=0.35):
|
||||
u"""把折线的拐点切成 45°。换层点(相邻两点层不同)不切。
|
||||
|
||||
⚠ 点的 layer/width 语义是「从这一点到下一点」,所以在拐点 b 处插入的
|
||||
b1 用**前一段**的层/宽(它承担斜切段),b2 用 b 自己的。
|
||||
⚠ r 别调大:0.6 时 `USB1_DM` 那个让位拐角会被削进 C747 的 GND 扇出孔
|
||||
(103.28, 62.868) 的净距圈里(0.178 < 0.20)。0.35 才留得住 0.209。
|
||||
"""
|
||||
if len(pts) < 3:
|
||||
return list(pts)
|
||||
out = [pts[0]]
|
||||
for i in range(1, len(pts) - 1):
|
||||
a, b, c = pts[i - 1], pts[i], pts[i + 1]
|
||||
if a[2] != b[2] or b[2] != c[2]: # 拐点两侧不同层 -> 这是过孔点
|
||||
out.append(b)
|
||||
continue
|
||||
ax, ay = b[0] - a[0], b[1] - a[1]
|
||||
cx, cy = c[0] - b[0], c[1] - b[1]
|
||||
la, lc = math.hypot(ax, ay), math.hypot(cx, cy)
|
||||
if la < 1e-9 or lc < 1e-9:
|
||||
out.append(b)
|
||||
continue
|
||||
# 共线就不用切
|
||||
if abs(ax / la - cx / lc) < 1e-9 and abs(ay / la - cy / lc) < 1e-9:
|
||||
out.append(b)
|
||||
continue
|
||||
d = min(r, la / 2.0, lc / 2.0)
|
||||
out.append((round(b[0] - ax / la * d, 4), round(b[1] - ay / la * d, 4), a[2], a[3]))
|
||||
out.append((round(b[0] + cx / lc * d, 4), round(b[1] + cy / lc * d, 4), b[2], b[3]))
|
||||
out.append(pts[-1])
|
||||
return out
|
||||
|
||||
|
||||
def P(*pts):
|
||||
u"""(x, y[, layer[, width]]) -> 规范化四元组;层/宽省略时沿用上一点。"""
|
||||
out, lay, w = [], L_BOT, W
|
||||
for t in pts:
|
||||
x, y = t[0], t[1]
|
||||
lay = t[2] if len(t) > 2 else lay
|
||||
w = t[3] if len(t) > 3 else w
|
||||
out.append((round(x, 4), round(y, 4), lay, w))
|
||||
return out
|
||||
|
||||
|
||||
# ══════════ U32 西边缘的逃逸 ══════════
|
||||
# 焊盘 x=105.55,西端 105.125;脚距 0.5 mm,0.28 线正好留 0.22 净距(>0.20)。
|
||||
# 让位在 x 104.9→104.4 这一小段做完(x ≤ 104.785 之后就离所有焊盘 ≥0.34)。
|
||||
#
|
||||
# ⚠ `USB1_DM` 的原生 y=63.25 会撞上 C747 的 GND 扇出孔 (103.28, 62.868)
|
||||
# —— 净距只有 0.382,要 0.645。抬到 63.55 之后是 0.682。
|
||||
# 抬了它,上面四根就得跟着让(对内 0.43 / 对间 0.48),一直让到 pair3。
|
||||
# ⚠ 同一对的两根让位量必须**相等**(斜段平行),否则 0.50 的行距按垂直方向
|
||||
# 折算会掉到 0.43 以下。pair1/2 统一 +0.30、pair3 统一 −0.20。
|
||||
# ⚠ `USB5_DP` 的原生 68.75 离 U32 pin13 的 GND 扇出孔 (104.599, 69.25) 只有 0.50,
|
||||
# 要 0.645 ⇒ 压到 68.60;DM5 跟着到 68.15。这一压正好给 pair4 的过孔留出
|
||||
# [DP3+0.645, DM5−0.645] = [66.695, 67.505] 这 0.81 mm 的窗口(两个孔要 0.76)。
|
||||
ESC = { # net: (焊盘 y, 西行车道 y, 爬升 x)
|
||||
'USB1_DM': (63.25, 63.55, 98.30),
|
||||
'USB1_DP': (63.75, 64.05, 98.80),
|
||||
'USB2_DM': (64.25, 64.55, 99.30),
|
||||
'USB2_DP': (64.75, 65.05, 99.80),
|
||||
'USB3_DM': (65.75, 65.55, 100.30),
|
||||
'USB3_DP': (66.25, 66.05, 100.80),
|
||||
'USB5_DM': (68.25, 68.05, 101.30),
|
||||
'USB5_DP': (68.75, 68.55, 101.80),
|
||||
}
|
||||
PAD_X = 105.55
|
||||
# ⚠ 让位的斜段**不能太早起步**:x=105.19 处斜上去时离隔壁焊盘只剩 0.183(要 0.20)。
|
||||
# 所以先直着走到 105.00,再走到拐点 104.60 —— 这样 45° 切角被 |ab|/2 限成 0.20,
|
||||
# 斜段从 x=104.80 才开始(离隔壁焊盘 0.242)。
|
||||
# ⚠ 让位段还**不能太陡**:相邻两根各让 0.28 / 0.26,斜段近似平行,
|
||||
# 0.50 的行距按垂直方向折算成 0.50·Δx/|d|。Δx=0.5 时只剩 0.436(净距 0.156,要 0.20),
|
||||
# Δx=1.2 时是 0.487(净距 0.207)。所以让位横跨 104.60 → 103.40。
|
||||
JOG_X0, JOG_X1, JOG_X2 = 105.00, 104.60, 103.40
|
||||
|
||||
|
||||
def esc(net):
|
||||
u"""焊盘 → 让位 → 西行到爬升点。
|
||||
|
||||
⚠ pair5 是特例,三条约束把它挤成一条 **0.18 mm 的窄段**:
|
||||
· `USB5_DP` 出焊盘后要压到 (104.599, 69.25) 那个 GND 扇出孔的净距圈外;
|
||||
· 但它又不能压太低 —— 隔壁 `USB5_DM` 的焊盘上沿在 68.40,对内净距要 0.15;
|
||||
· `USB5_DM` 自己也不能往下让 —— 下面 0.5 mm 就是 U32 pin10 的焊盘。
|
||||
0.28 线宽下这三条无解;收到 **0.18** 之后 DP 走 68.62、DM 保持 68.25
|
||||
(净距 0.19),过了焊盘区(x ≤ 103.4)再放回 0.28 并拉开到 0.50。
|
||||
"""
|
||||
pad_y, lane, cx = ESC[net]
|
||||
if net == 'USB5_DP':
|
||||
return [(PAD_X, 68.75, L_BOT, 0.18), (105.13, 68.75), (104.70, 68.62),
|
||||
(103.40, 68.62), (102.80, lane, L_BOT, W), (cx, lane)]
|
||||
if net == 'USB5_DM':
|
||||
return [(PAD_X, 68.25, L_BOT, 0.18), (103.40, 68.25),
|
||||
(102.80, lane, L_BOT, W), (cx, lane)]
|
||||
return [(PAD_X, pad_y), (JOG_X0, pad_y), (JOG_X1, pad_y), (JOG_X2, lane), (cx, lane)]
|
||||
|
||||
|
||||
# ══════════ pair 1 → site1(西北)══════════
|
||||
# 西行车道 74.70(DM1, 南) / 75.55(DP1, 北)。
|
||||
# U51 焊盘:pin4 DP x=61.55(西)、pin6 DM x=63.45(东),y=76.351,从南边进。
|
||||
# 西行时先到的是**东**边的 DM1,它必须先北拐 —— 而 DP1 的车道在它北边,
|
||||
# 同层必交叉 ⇒ **DM1 在 (63.45, 74.70) 打孔上顶层**,从 DP1 的底层车道上方跨过去。
|
||||
SITE1 = {
|
||||
'USB1_DM': esc('USB1_DM') + [(98.30, 74.70), (63.45, 74.70),
|
||||
(63.45, 74.70, L_TOP), (63.45, 76.351)],
|
||||
'USB1_DP': esc('USB1_DP') + [(98.80, 75.55), (61.55, 75.55),
|
||||
(61.55, 75.55, L_TOP), (61.55, 76.351)],
|
||||
}
|
||||
|
||||
|
||||
def build():
|
||||
out = {}
|
||||
for grp in (SITE1, SITE2, SITE3, SITE4, SITE5, UP, J23_TIE):
|
||||
for net, pts in grp.items():
|
||||
out[net] = chamfer(P(*pts))
|
||||
return out
|
||||
|
||||
|
||||
# ══════════ pair 3 → site3(东北,走「低窗」)══════════
|
||||
# 隘口 x 112~114 的底层低窗:线心只能落在 76.185~76.72 —— 只塞得下这一对。
|
||||
# 取 76.20 / 76.68(间距 0.48,斜段上垂直净距 0.45 > 0.43)。
|
||||
# 出了隘口先在 x 120→126 降到 73.90/74.38(要给 pair2 让出北边的车道,
|
||||
# 它得从北高速一路降到 75.05/75.70 才能从南边进 U52),
|
||||
# x 140→146 再抬回 75.00/75.70(避开 (187.065,73.863) 那个孔,也对齐 site3 的进站高度)。
|
||||
SITE3 = {
|
||||
'USB3_DM': esc('USB3_DM') + [(100.30, 76.63), (118.00, 76.63), (120.00, 76.63), (126.00, 75.20),
|
||||
(132.00, 74.35), (140.00, 74.35), (146.00, 75.70), (206.45, 75.70),
|
||||
(206.45, 75.70, L_TOP), (206.45, 76.351)],
|
||||
'USB3_DP': esc('USB3_DP') + [(100.80, 76.20), (118.00, 76.16), (120.00, 76.16), (126.00, 74.73),
|
||||
(132.00, 73.85), (140.00, 73.85), (146.00, 75.00), (204.55, 75.00),
|
||||
(204.55, 75.00, L_TOP), (204.55, 76.351)],
|
||||
}
|
||||
|
||||
# ══════════ pair 2 → site2(东北,走「高窗」)══════════
|
||||
# 高窗 78.145~81.66 宽敞,但东边 (117.067, 78.392) 那个孔把 x=117 的天花板压到 77.75,
|
||||
# 所以过了隘口(x=114)就得斜下来:78.77/78.29 → 77.61/77.13(21°,垂直净距 0.448)。
|
||||
# 之后一路缓降到 75.70/75.05,从南边进 U52 —— 全程都在 pair3 的北边。
|
||||
SITE2 = {
|
||||
'USB2_DM': esc('USB2_DM') + [(99.30, 78.83), (114.00, 78.83), (116.80, 77.70),
|
||||
(120.00, 77.70), (126.00, 76.30), (132.00, 75.70), (134.95, 75.70),
|
||||
(134.95, 75.70, L_TOP), (134.95, 76.351)],
|
||||
'USB2_DP': esc('USB2_DP') + [(99.80, 78.29), (114.00, 78.29), (116.80, 77.16),
|
||||
(120.00, 77.16), (126.00, 75.76), (132.00, 75.05), (133.05, 75.05),
|
||||
(133.05, 75.05, L_TOP), (133.05, 76.351)],
|
||||
}
|
||||
|
||||
|
||||
# ══════════ pair 4 → site4(西南,全板唯一要「翻层穿越」的一对)══════════
|
||||
# pair4 夹在 pair3(往北)和 pair5(往北)中间却要往南 —— 同层必然横切这两对,
|
||||
# 所以一出焊盘就**上顶层**南下,把交叉全部让给层间。
|
||||
#
|
||||
# 过孔位置被四样东西同时夹住:
|
||||
# · 西边界 x ≥ 102.445(离 pair5 的爬升柱 x=101.80 要 0.645)
|
||||
# · DM4 的孔 ≥ DP3 车道 66.05 + 0.645 = 66.695
|
||||
# · DP4 的孔 ≤ DM5 车道 68.15 − 0.645 = 67.505
|
||||
# · 两孔互相 ≥ 0.76 ⇒ 只能**错开 x** 放:(102.50, 66.75) 与 (103.60, 67.48)
|
||||
# 顶层的两根竖线还得躲开 (103.28, 62.868) 那个孔(x ≤ 102.635),
|
||||
# 而且**不能压到对方的过孔上** ⇒ DP4 竖在 100.70、DM4 竖在 101.30,
|
||||
# DP4 从东边高处横过来时正好从 DM4 竖线的上方(y=67.48)越过。
|
||||
# 顶层这一段(x 100.4~103.6, y 61~67.5)底下 Inner1 全是 GND 底填
|
||||
# (VO2 岛从 y=67.6 起、VO4 岛到 x=100 止),**没有跨分割**。
|
||||
SITE4 = {
|
||||
'USB4_DM': [(105.55, 66.75), (104.60, 66.75), (102.50, 66.75),
|
||||
(102.50, 66.75, L_TOP), (101.30, 66.75), (101.30, 60.30),
|
||||
(101.30, 60.30, L_BOT), (49.39, 60.30),
|
||||
(49.39, 60.30, L_TOP), (49.39, 58.809)],
|
||||
'USB4_DP': [(105.55, 67.25), (104.60, 67.25), (103.60, 67.40),
|
||||
(103.60, 67.40, L_TOP), (100.70, 67.40), (100.70, 61.00),
|
||||
(100.40, 61.00), (100.40, 61.00, L_BOT), (51.29, 61.00),
|
||||
(51.29, 61.00, L_TOP), (51.29, 58.809)],
|
||||
}
|
||||
|
||||
# ══════════ pair 5 → site5(东南,过隘口走**顶层**)══════════
|
||||
# 隘口的底层两个窗口分别被 pair3(低窗)和 pair2(高窗)占了,
|
||||
# 第三对只能走顶层 y 75.44~76.3 那条缝。那一段底下是 Inner1 的 `VO2_P` 整岛,
|
||||
# 一整块实心参考面 —— **为此把 VO2 的东边界从 x=116 放到 119**(§25.13),
|
||||
# 好让下沉的两个过孔 (118.0) 还落在岛内,顶层全程不跨分割。
|
||||
# ⚠ 下坡不能早于 x=115.2:(114.476, 75.0) 那个 GND 孔要求 DP5 ≥ 75.645。
|
||||
SITE5 = {
|
||||
'USB5_DM': esc('USB5_DM') + [(101.30, 75.35), (103.00, 75.35),
|
||||
(103.00, 75.35, L_TOP), (108.00, 76.14), (115.20, 76.14),
|
||||
(118.00, 75.50), (118.00, 75.50, L_BOT),
|
||||
(120.89, 75.50), (120.89, 60.40),
|
||||
(120.89, 60.40, L_TOP), (120.89, 58.809)],
|
||||
'USB5_DP': esc('USB5_DP') + [(101.80, 74.50), (103.00, 74.50),
|
||||
(103.00, 74.50, L_TOP), (108.00, 75.68), (115.20, 75.68),
|
||||
(118.00, 74.70), (118.00, 74.70, L_BOT),
|
||||
(120.00, 74.70), (120.00, 59.50),
|
||||
(122.79, 59.50), (122.79, 59.50, L_TOP),
|
||||
(122.79, 58.809)],
|
||||
}
|
||||
|
||||
|
||||
# ══════════ UPSTREAM → J23(U32 南边缘 → 板子正东)══════════
|
||||
# ⚠ hub_layout 里写的「南侧竖井」(C743 与 C745 之间 2.16 mm)**在底层已经被
|
||||
# C743/C745 自己的 GND 扇出孔堵死了**:(108.219,60.28) 与 (109.182,60.28)
|
||||
# 中间只剩 0.96 mm,一根 0.28 的线净距只有 0.036。
|
||||
# ⇒ **UP 一出焊盘就上顶层**(南边那排 0402 都在底层,顶层 y 61.0~61.8
|
||||
# 从 x=109 一路通到 130)。到 x=128 才沉回底层,
|
||||
# 这样五号站的 D± 在 x=120~123 竖下去时是**从 UP 底下穿过**,两不相干。
|
||||
# 顶层这一段底下 Inner1 是 GND 底填(VO4 到 x=100、VO5 从 x=139.5 起、VO2 从 y=67.6 起),
|
||||
# 不跨分割。
|
||||
# ⚠ J23 前面 R717 的 GND 扇出孔 (209.933, 61.642) 占住 y 61.0~62.3,
|
||||
# 所以东行车道压到 60.00/60.75,两根都在它**东边**(x ≥ 210.6)才抬头进焊盘。
|
||||
# J23 侧 A6=UP_DP 在 61.75、A7=UP_DM 在 62.25,南北次序与车道一致,不用交叉。
|
||||
UP = {
|
||||
'UP_DP': [(108.75, 62.55), (108.75, 62.20), (108.75, 61.90),
|
||||
(108.30, 61.45), (108.30, 61.10),
|
||||
(108.30, 61.10, L_TOP), (110.20, 61.10), (128.00, 61.10),
|
||||
(128.00, 61.10, L_BOT), (128.00, 60.00), (211.25, 60.00),
|
||||
(211.25, 61.65), (211.25, 61.65, L_TOP), (211.60, 61.75),
|
||||
(212.392, 61.75)],
|
||||
'UP_DM': [(109.25, 62.55), (109.25, 61.70), (109.25, 61.70, L_TOP),
|
||||
(110.80, 61.70), (128.60, 61.70), (128.60, 61.70, L_BOT),
|
||||
(128.60, 60.75), (210.65, 60.75), (210.65, 62.25),
|
||||
(210.65, 62.25, L_TOP), (212.392, 62.25)],
|
||||
}
|
||||
|
||||
|
||||
# ══════════ J23 的 A6↔B6 / A7↔B7 短接(USB-C 正反插的一对重复脚)══════════
|
||||
# ⚠ 这两条**原来是自动布线器画的**,被 `pcb2_route_dp.py --push` 的「先删本网所有
|
||||
# 1/2 层导线」连坐删掉了,必须自己补回来 —— 少了它们,反插方向就没有 D±。
|
||||
# 四个焊盘在同一列上、间距 0.5:B7(61.25,DM) A6(61.75,DP) A7(62.25,DM) B6(62.75,DP),
|
||||
# 两两要短接的中间都隔着对方的脚,所以只能**绕到连接器东侧沉底层**跨过去
|
||||
# (x > 213.55 那一带底层是空的,J23 自己的两个固定脚在 y 57.7 / 66.3)。
|
||||
# ⚠ 上表贴焊盘只到 x=213.042,过孔要离它 0.505 ⇒ 最西只能放到 213.55。
|
||||
# 两根出线在 y 上只差 0.5,一根的过孔必然贴着另一根的出线 ——
|
||||
# 所以出线要**边往东边分开**:DP 走到 214.40 并压到 61.55/62.95,
|
||||
# DM 停在 213.60 保持 62.25/60.95,四个孔两两 ≥0.76、线-孔 ≥0.62。
|
||||
J23_TIE = {
|
||||
# ⚠ 出焊盘的那 0.65 mm 必须**平着走**:焊盘阵列 x 到 213.042 为止,
|
||||
# 在阵列上方就起坡的话会蹭到隔壁脚(实测差 0.04~0.14)。都在 x=213.30 之后再拐。
|
||||
'UP_DM#2': [(212.392, 62.25, L_TOP, W), (213.85, 62.25), (213.85, 62.25, L_BOT),
|
||||
(213.85, 60.70), (213.85, 60.70, L_TOP), (213.50, 61.25),
|
||||
(213.20, 61.25), (212.392, 61.25)],
|
||||
'UP_DP#2': [(212.392, 61.75, L_TOP, W), (213.20, 61.75), (213.50, 61.75),
|
||||
(214.25, 61.30), (214.75, 61.30), (214.75, 61.30, L_BOT),
|
||||
(214.75, 63.20), (214.75, 63.20, L_TOP), (214.25, 63.20),
|
||||
(213.50, 62.75), (213.20, 62.75), (212.392, 62.75)],
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""载板 PCB2-CARRIER 布局(2026-09-04 从零开始)。
|
||||
|
||||
板框 **220 × 135 mm**,四角 R5。5 块小米8 主板 **3+2 双排面对面**,第二排居中:
|
||||
|
||||
┌──────────────────────────────────────────────┐ 135
|
||||
│ ┌───CH1───┐ ┌───CH2───┐ ┌───CH3───┐ │
|
||||
│ │ 主板正面朝天,反面贴 3.0 立柱 │ │ y 85.5~130.16
|
||||
│ └────▼────┘ └────▼────┘ └────▼────┘ │
|
||||
│ ▓服务带 A(y 67.5~85.5)▓▓▓▓▓▓▓▓▓▓▓▓ │
|
||||
│ ▓服务带 B(y 49.66~67.5)▓▓▓▓▓▓ │
|
||||
│ ┌───CH4───┐ ┌───CH5───┐ │
|
||||
│ 口袋L │ │ │ │ 口袋R │ y 5~49.66
|
||||
│ └─────────┘ └─────────┘ │
|
||||
└──────────────────────────────────────────────┘ 220
|
||||
|
||||
**为什么这么排**(2026-09-04 用户拍板):小米8 主板的**所有**接口都挤在同一条边上
|
||||
(侧键 x 2~5.5 / J600 x≈23.8 / EDL x 42.6,45.4 / J1401 x 47.5~59.7,y 全 < 8),
|
||||
所以每台手机只需要**一条 18 mm 的服务带**贴着那条边。两排面对面 → 共用一条
|
||||
36 mm 的中央通道,J31/J32 排母正好落在下板 160×125 的投影里。
|
||||
|
||||
## 三条布局上的硬决定
|
||||
|
||||
1. **J31/J32 在底层(layer 2)。** 下板装公头朝上,载板必须装母座朝下。
|
||||
⚠️ 底层元件在顶视图里是镜像的 —— 下板 PCB 放 J24/J25 时必须实打实核一次
|
||||
「第 r 行对第 r 行」(§22.7 那条编号陷阱)。本文件的 `header_pads()` 会把
|
||||
载板侧 80 个焊盘的绝对坐标打出来,下板照着对。
|
||||
2. **USB Hub(U32 + Y1 + strap + 去耦)也在底层,压在通道正中 (110, 67)。**
|
||||
顶层通道被两条服务带占满了,而 Hub 放到左右口袋里的话,到最远那台的 D±
|
||||
要走 200+ mm —— USB2517I 手册要求 D± < 6 inch(152 mm),超了。
|
||||
放中间后最长约 92 mm。底层本来就要贴 J31/J32,不额外增加工序。
|
||||
⚠️ 底层元件在两板之间那 13 mm 缝里,**布下板时别让 C2/C3(12 mm 电解)撞上**。
|
||||
3. **35 个定位孔做 NPTH(不金属化)。** 主板 7 个孔是金属化的、连着手机地;
|
||||
做成金属化 + 金属立柱 = 五台手机的地在载板上并成一根,4 A 回流会从
|
||||
专用的 P− 针里跑掉一部分。§6.4 明确要求每路独立回流 → **立柱必须用尼龙**。
|
||||
|
||||
用法:
|
||||
/c/Python314/python .claude/pcb2_place.py # 只算,本地查重叠/越框
|
||||
/c/Python314/python .claude/pcb2_place.py --outline # 画板框 + 载板自己的 M3 孔
|
||||
/c/Python314/python .claude/pcb2_place.py --mech # 35 定位孔 + 5 个手机位丝印
|
||||
/c/Python314/python .claude/pcb2_place.py --push # 摆元件
|
||||
/c/Python314/python .claude/pcb2_place.py --layers # 切 4 层(只需跑一次)
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
from eda import run
|
||||
|
||||
PCB = 'f126ffb29c92f9b2' # PCB2-CARRIER
|
||||
MIL = 39.3700787401575
|
||||
|
||||
BW, BH = 220.0, 135.0 # 板框 mm
|
||||
CORNER_R = 5.0
|
||||
# 载板自己的 M3 安装孔。
|
||||
# ⚠️ 上面两个原来在 (8,127) / (212,127) —— **压在 CH1 / CH3 的手机本体投影里**,
|
||||
# 其中 (8,127) 的 Ø6 焊盘和 CH1 的定位孔 P1H2 直接重叠 0.93 mm(pcb2_clear.py 抓到)。
|
||||
# 四角布孔是在五个手机位拍板**之前**放的,之后没复核。
|
||||
# 板顶那条 y 130.16~135 的 4.84 mm 缝里塞不下 Ø6 焊盘,三个手机横排也已经把
|
||||
# x 方向占满(5+67.09+4.41+67.09+4.5+67.09+4.91 = 220.09),
|
||||
# 所以上面两个孔只能退进中央通道 / 右口袋。
|
||||
# 2026-09-05 用户拍板补了**两个中跨孔**,解决上半部分 220 mm 跨距无支撑的问题
|
||||
# (§25.6 ⑤ 那条待办到此关闭)。孔位不是拍脑袋定的,是拿真实焊盘 + 五个 Ø12.5
|
||||
# 电解罐体做网格搜索找出来的(`_tmp_mh.py`),三个候选窗口的实测净间距:
|
||||
#
|
||||
# CH1|CH2 缝下 (76.6, 80.1) 净间距 **5.28 mm** ← 选它
|
||||
# CH2|CH3 缝下 (148.1, 80.1) 净间距 **5.28 mm** ← 选它
|
||||
# 走廊正中 (127.9, 67.5) 净间距 2.90 mm ← 窄一半,且离要支撑的地方远 18 mm
|
||||
#
|
||||
# 选 y≈80 而不是走廊正中 y≈67.5 的两条理由:
|
||||
# ① 要支撑的是 y 85.5~130 那一排手机,80.1 离它只有 5 mm,67.5 差了 18 mm;
|
||||
# ② 走廊正中被 C811 / C831 两个 Ø12.5 电解罐夹着(罐体分别占 y68.25~80.75 和
|
||||
# y54.41~66.91),只剩一条 6.9 mm 高的缝;手机之间那 4.41 mm 的 x 向空档
|
||||
# 一路通到 y 84,宽敞得多。
|
||||
# ⚠️ y 再往上就被**顶针** J48/J52(y 87.96,CH2/CH3 的侧键触点)逼住:
|
||||
# y=83 时净间距掉到 2.41,y=84 只剩 1.41。80.1 是这个窗口的最宽处。
|
||||
MH = [(8.0, 8.0), (212.0, 8.0), (7.0, 60.0), (213.0, 45.0),
|
||||
(76.6, 80.1), (148.1, 80.1)]
|
||||
MH_HOLE, MH_PAD = 3.2, 6.0
|
||||
|
||||
# ---- 小米8 主板(顶视,板左下角原点,mm)----
|
||||
PW, PH = 67.09, 44.66
|
||||
HOLES = {'H1': (54.2, 42.3), 'H2': (2.6, 38.2), 'H3': (61.9, 33.7), 'H4': (62.1, 29.2),
|
||||
'H5': (2.6, 27.0), 'H6': (4.0, 10.8), 'H7': (42.6, 3.7)}
|
||||
HOLE_D = 2.5 # 载板上的定位孔,NPTH
|
||||
|
||||
# 侧键触点(§22.3b,已折算成顶视投影)。顶针顺序 = c4_site.py 的 pogo[0..3]
|
||||
# pogo[0] POGOn_PWR -> J603 电源键
|
||||
# pogo[1] POGOn_VUP -> J601 音量+
|
||||
# pogo[2] POGOn_VDN -> J602 音量−
|
||||
# pogo[3] (公共地) -> J604
|
||||
POGO_XY = [(3.11, 6.91), (2.70, 4.60), (5.41, 6.52), (2.12, 2.46)]
|
||||
POGO_TAG = ['J603 PWR', 'J601 VUP', 'J602 VDN', 'J604 GND']
|
||||
|
||||
# ---- 五个手机位:本体左下角 + 旋转(0 = 接口边朝下,180 = 接口边朝上)----
|
||||
SITES = {
|
||||
1: (5.0, 85.5, 0),
|
||||
2: (76.5, 85.5, 0),
|
||||
3: (148.0, 85.5, 0),
|
||||
4: (40.75, 5.0, 180),
|
||||
5: (112.25, 5.0, 180),
|
||||
}
|
||||
|
||||
# 上游 USB-C 的中心 X:壳体前端面与**右**板边齐平(中心离前端面 5.134 mm,§25.5 #1)
|
||||
J23_X = BW - 5.134
|
||||
|
||||
# ---- 服务带:站在手机本体坐标里,v<0 = 接口边外侧 ----
|
||||
# 行 1(v=-3.0)贴着手机边:要接线/插排线的都在这一行
|
||||
# 行 2(v=-7.5)小有源器件
|
||||
# 行 3(v=-12.5)大电解 + VBUS 负载开关
|
||||
BAND = {
|
||||
# 行 1(v=-3.2)贴着手机边:要接排线 / 焊飞线的都在这一行
|
||||
'tp_p': (21.0, -3.2, 0), # TP8n1 P+ Ø2.7 通孔焊盘(电池排线焊这里)
|
||||
'c6': (23.5, -3.2, 0), # C8n6 100nF 0402,紧夹在两个焊盘中间
|
||||
'tp_n': (26.0, -3.2, 0), # TP8n2 P−
|
||||
'c2': (31.0, -3.2, 0), # C8n2 100µF 1206
|
||||
'c3': (36.5, -3.2, 0), # C8n3
|
||||
'j6': (44.0, -3.2, 0), # J6n EDL 1.25mm 2P(两根线焊到主板 TP1204/1205)
|
||||
# ⚠️ 2026-09-05:这不是「正对 J1401」—— `mi8_contacts.json`(卡尺校核)记的
|
||||
# J1401 中心是 u = 53.58(长轴沿 u,占 47.46~59.71),这里是 57.5,横向差 3.9 mm。
|
||||
# 不致命(原厂排线本身就是歪的、余量极大),但**不能直接改成 53.58**:
|
||||
# 那会和同一行的 'j6'(占 u 39.8~48.2)撞 0.7 mm,要连 j6/c3 一起重排。
|
||||
'j7': (57.5, -3.0, 0), # J7n BM24-50DS(插原厂尾插排线)
|
||||
# 行 2(v=-8.0)小有源器件
|
||||
'c4': (31.0, -8.0, 0), # C8n4 22µF 1206
|
||||
'c5': (36.5, -8.0, 0), # C8n5
|
||||
'r74': (41.0, -8.0, 0), # R74n 100k EDL_EN 下拉
|
||||
'u4': (45.0, -8.0, 0), # U4n TS5A3157 模拟开关
|
||||
'c76': (49.5, -8.0, 0), # C76n 100nF 去耦
|
||||
# ⚠️ 2026-09-06 rot 0 → **180**(布 D± 时才发现的摆位错误)。
|
||||
# USBLC6-2SC6 是**直通**器件:pin1/6 是 I/O1、pin3/4 是 I/O2,一排朝连接器、一排朝主机。
|
||||
# rot 0 时南排(y=76.35)是 pin1/2/3 = 连接器侧(`*_DPJ/DMJ`)、北排(y=78.65)是 pin6/5/4 = 主机侧,
|
||||
# 而 **J7n 在北边(y=81.44)、Hub 在南边** —— 正好接反,两条网络都得绕过封装走
|
||||
# (实测 DM ~8 mm、DP ~12 mm,几乎没有耦合,偏斜 3.6 mm),
|
||||
# 长差分对还被逼到 U5n 与 BM24 焊盘之间那条 2.0 mm 的窄缝里换层。
|
||||
# SOT-23-6 转 180° 之后 **6 个焊盘落在完全相同的 6 个位置**(封装对称),
|
||||
# 只是引脚编号对调 ⇒ 没有任何新的间距冲突,`VBUS1`(pin5) 顺带也离 U2n 更近。
|
||||
'u5': (57.5, -8.0, 180), # U5n USBLC6 ESD,紧贴 BM24
|
||||
# ⚠️ 2026-09-06 rot 0 → **180**(布线时才发现的方向错误)。
|
||||
# pin1=CC1 要朝**东**(CC1 从 BM24 中央通道的车道出口 u=62.3 过来),
|
||||
# pin2=VBUS 要朝**西**(VBUS 从 U5n 的 u=57.5 过来)。rot 0 时两根线
|
||||
# 各自绕过整颗电阻、当场交叉,五个站的 VBUSn 全部布不通。
|
||||
'r76': (61.5, -8.0, 180), # R76n 56k CC1→VBUS
|
||||
'r77': (64.0, -8.0, 0), # R77n 100k 口温→GND
|
||||
# 行 3:大电解摆最左(那一段没有别的东西),VBUS 负载开关摆 BM24 下面
|
||||
# ⚠ 2026-09-05:'c1' 的 v 从 −11.0 收到 **−8.0**(和行 2 齐平)。
|
||||
# 换成直插排母之后 J31/J32 变成**通孔件、顶层也占位**,而 Ø12.5 的罐体
|
||||
# keep-out 正好探进中央走廊:上排三站的罐底 y=68.25、下排两站的罐顶 y=66.95,
|
||||
# 中间只剩 1.30 mm,而 J31/J32 的焊盘区高 4.24 mm —— 怎么摆都塞不进去。
|
||||
# 罐体本身在 u 5.75~18.25 这一段是**独占**的(行 1/2/3 都从 u=21 起),
|
||||
# 所以 v 可以自由收到「不进手机投影」为止(v ≤ −6.25)。取 −8.0:
|
||||
# · 罐体边到手机板边 3.00 mm(keep-out 边 1.75)
|
||||
# · keep-out 边到 J31/J32 焊盘 1.63(上排)/ 1.43(下排)mm
|
||||
# 实测 v ∈ [−9.0, −6.75] 本地四项检查全过,−8.0 取在中间偏保守处。
|
||||
'c1': (12.0, -8.0, 0), # C8n1 2200µF Ø10 直插 —— 本带最大件
|
||||
'c70': (39.5, -13.0, 0), # C70n 2.2µF 输入
|
||||
'u2': (45.0, -13.0, 0), # U24~28 TPS22918 VBUS 负载开关
|
||||
'c71': (49.5, -13.0, 0), # C71n 100nF 输出
|
||||
'c72': (52.5, -13.0, 0), # C72n 100pF CT
|
||||
'r70': (55.5, -13.0, 0), # R70n 100k 下拉
|
||||
}
|
||||
C801_BODY_R = 5.0 + 1.25 # Ø10 罐 + 单边 1.25 余量(留到 Ø12.5,见 §24 的 C801 决定)
|
||||
|
||||
|
||||
def desig(n):
|
||||
u"""第 n 个手机位用到的全部位号。"""
|
||||
return {
|
||||
'pogo': ['J%d' % (41 + (n - 1) * 4 + i) for i in range(4)],
|
||||
'tp_p': 'TP8%d1' % (n - 1), 'tp_n': 'TP8%d2' % (n - 1),
|
||||
'c1': 'C8%d1' % (n - 1), 'c2': 'C8%d2' % (n - 1), 'c3': 'C8%d3' % (n - 1),
|
||||
'c4': 'C8%d4' % (n - 1), 'c5': 'C8%d5' % (n - 1), 'c6': 'C8%d6' % (n - 1),
|
||||
'j6': 'J6%d' % n, 'u4': 'U4%d' % n, 'r74': 'R74%d' % n, 'c76': 'C76%d' % n,
|
||||
'j7': 'J7%d' % n, 'u5': 'U5%d' % n, 'r76': 'R76%d' % n, 'r77': 'R77%d' % n,
|
||||
'u2': 'U%d' % (23 + n), 'c70': 'C70%d' % n, 'c71': 'C71%d' % n,
|
||||
'c72': 'C72%d' % n, 'r70': 'R70%d' % n,
|
||||
}
|
||||
|
||||
|
||||
def to_board(n, u, v, rot=0):
|
||||
u"""手机本体坐标 (u,v) -> 板坐标。rot=180 时整位绕本体中心转半圈。"""
|
||||
x0, y0, rho = SITES[n]
|
||||
if rho == 0:
|
||||
return (x0 + u, y0 + v, rot)
|
||||
return (x0 + PW - u, y0 + PH - v, (rot + 180) % 360)
|
||||
|
||||
|
||||
# ═══════════════ 全局块 ═══════════════
|
||||
# 左口袋 x 5~40.75、右口袋 x 179.34~215(第二排居中之后左右各空出 35.75 × 44.66)
|
||||
#
|
||||
# 2026-09-04 深夜第二轮,用户拍板改了三处(原因见 §25.6):
|
||||
# ① J23 从**左**口袋挪到**右**口袋。上游 D± 原来要从 x=5 跑到 Hub 的 x=110,
|
||||
# 曼哈顿 140 mm,而 USB2517I 的上限是 152 mm —— §25.2 当初只核了下游。
|
||||
# 挪到右边之后上游 ~104 mm、最远下游 ~107 mm,两头都宽裕。
|
||||
# ② R717/R718(CC1/CC2 的 5.1k 下拉)原来被 hub_layout() 的网格捞走了,
|
||||
# 摆在**底层**、离 J23 105 mm。CC 脚挂这么长的残桩没有意义,
|
||||
# 必须和 J23 同层、贴着 A5/B5 焊盘。
|
||||
# ③ U31(3V3 LDO)原来在左口袋 (25,42),离 Hub 90 mm ——
|
||||
# §22.7 白纸黑字写着「§9.5 要求的就近 Hub 供电是 PCB 摆位约束」,
|
||||
# 这一版没落实。挪到 (105,71),离 Hub 的 +3V3 脚 6 mm。
|
||||
GLOBAL = {
|
||||
# 上游 USB-C。方向判据两条(2026-09-04 用户两次指正后定的,见 §25.5 #1):
|
||||
# ① 12 个信号焊盘在**连接器背面**、4 个屏蔽固定孔靠近口,所以口必须朝板外;
|
||||
# ② 壳体前端面比最外侧铜还往外 2 mm,焊盘框算不出来,是从渲染图量的:
|
||||
# 用板框紫线 + 信号焊盘列左沿两点标定,得出「中心离前端面 5.134 mm」。
|
||||
# 挪到右板边之后就是把这条几何镜像过去:rot 270 -> **90**(口朝 +X),
|
||||
# 中心 x = 220 − 5.134 = 214.866,壳体前端面仍与板边齐平。
|
||||
# y 取 62.0,让信号焊盘列(x≈212.4)正对 Hub 的上游脚行(y≈62.55)。
|
||||
'J23': (J23_X, 62.0, 90),
|
||||
# CC1/CC2 各 5.1k 下拉,贴着 J23 的 A5/B5 焊盘(焊盘在 x≈212.4,往板内 2 mm)
|
||||
'R717': (J23_X - 4.5, 60.75, 180), # pin1 = CC1 -> J23.A5
|
||||
'R718': (J23_X - 4.5, 63.75, 180), # pin1 = CC2 -> J23.B5
|
||||
# AMS1117-3.3:+5V 从 J31 进来,3V3 就近喂 Hub(§9.5)。
|
||||
# 顶层 y 67.5~72.5 这条横带是空的(J31/J32 在底层,服务带 A 从 y 72.5 起)。
|
||||
'U31': (105.0, 71.0, 0),
|
||||
'C736': (98.0, 71.0, 0), # +5V 输入
|
||||
'C737': (112.5, 68.8, 0), # +3V3 输出,朝 Hub 那一侧
|
||||
# ~~'U34': (36.0, 12.0, 0)~~ / ~~'C760': (36.0, 6.5, 0)~~ —— 2026-09-06 用户拍板
|
||||
# 挪到 J31 旁边(底层,见 BOT_GLOBAL)。原来在左口袋贴着 CH4 量进风侧空气,
|
||||
# SCL/SDA 要从 J31.21/23 跑 70 mm 过去、和别的线抢左下角唯一那条通道(§25.14 末尾)。
|
||||
}
|
||||
# 底层的零散件(不属于 Hub 簇,但必须在底层)
|
||||
# R721/R722 = 上游 VBUS 分压 -> UP_VBUS_DET -> J31 pin36 (81.049, 65.100)。
|
||||
# 分压**跟着高阻端走**:UP_VBUS_DET 是高阻节点,留在 J31 脚边只有 3 mm;
|
||||
# 低阻的 VBUS_UP 从 J23 长跑过来无所谓。原来它俩在左口袋陪着老 J23,是反的。
|
||||
BOT_GLOBAL = {
|
||||
'R721': (83.0, 62.2, 180), # pin1 = VBUS_UP(朝东,迎着从 J23 过来的那根)
|
||||
'R722': (81.0, 62.2, 180), # pin1 = UP_VBUS_DET, pin2 = GND
|
||||
# LM75BD 测温 + 100nF:2026-09-06 从左口袋挪到 J31 北排正上方(底层)。
|
||||
# SCL/SDA 在 J31.21/23 (63.3/65.8, 68.77),这里离它们 ~4 mm。
|
||||
# 顶层这一带塞不下(J31 焊盘顶 69.62 到 U24 底 71.25 只有 1.6 mm),底层是空的。
|
||||
# ⚠️ 只能**横放(rot 90)**:USB1_DP/DM 那对长差分在底层 y≈75.1~76.0 横穿
|
||||
# (`pcb2_dp_long.py` 的手写几何,不能动),竖放时北排焊盘正压在上面(实测 −0.14)。
|
||||
# 这条带子只有 69.62(J31 焊盘顶)~ 74.56(USB1_DM 线下沿)= 4.94 mm,
|
||||
# 横放的 U34 焊盘占 4.38 ⇒ y 只能在 72.01~72.17 之间,取 72.1(上下各留 ~0.28)。
|
||||
# x 取 68.0:再往左 U34.4 会撞到 USB1_DM 在 (63.45, 74.70) 的换层过孔(实测 0.12)。
|
||||
# ⚠️ 挪过来之后量的是**排母走廊**的温度,不再是进风侧空气 —— 风扇闭环的阈值要按新位置定。
|
||||
'U34': (68.0, 72.1, 90),
|
||||
'C760': (72.6, 72.1, 90),
|
||||
}
|
||||
# 底层:两条排母 + 整个 Hub
|
||||
HDR = {'J31': (62.0, 67.5, 0), 'J32': (158.0, 67.5, 0)}
|
||||
HUB_CENTER = (110.0, 67.0)
|
||||
HUB_PARTS = (['U32', 'Y1'] + ['C7%d' % i for i in range(40, 52)]
|
||||
+ ['R7%02d' % i for i in (8, 9, 10, 11, 12, 14, 15, 16, 19, 20)])
|
||||
# ⚠ 17/18 已从这里拿掉 —— R717/R718 是 J23 的 CC 下拉,跟着连接器走,见 GLOBAL。
|
||||
|
||||
|
||||
def th_parts():
|
||||
u"""直插件:引脚穿板,**顶层和底层都占位**,两层的重叠检查都要算它。
|
||||
|
||||
载板上有四类:C8n1(Ø10 电解)、TP8n1/TP8n2(Ø2.7 焊盘)、
|
||||
**J31/J32(2026-09-05 换成直插排母 C5307344 之后也是通孔件)**。
|
||||
⚠ 顶层要按**罐体 Ø12.5** 算,底层只有两根引脚(按焊盘包围盒算)——
|
||||
用同一个框会把 J31 误判成撞上 C811。
|
||||
"""
|
||||
out = set(HDR)
|
||||
for n in SITES:
|
||||
d = desig(n)
|
||||
out |= {d['c1'], d['tp_p'], d['tp_n']}
|
||||
return out
|
||||
|
||||
|
||||
def hub_layout():
|
||||
u"""Hub 的 24 个卫星件,**逐脚对位**手排(2026-09-04 深夜,替换掉原来的 3 mm 网格粗排)。
|
||||
|
||||
U32 是 QFN-64,焊盘中心到 ±4.45、包围盒到 ±4.877,脚距 0.5 mm。
|
||||
0402 件本体 0.54 mm 宽 —— **一个脚一个件地排是排不下的**(0.5 < 0.54),
|
||||
所以每条边分两圈:A 圈 5.9~6.3 mm、B 圈 8.0 mm,件按 1.2 mm 间距错开放。
|
||||
|
||||
## 让出来的三条通道(这是这版手排的主要工作,网格排法做不到)
|
||||
|
||||
| 通道 | 位置(相对 Hub 中心)| 给谁 |
|
||||
|---|---|---|
|
||||
| 西侧扇出 | x < −4.45, y −3.8~+1.8 | 五对下游 D±(10 根),整条左边不许放件 |
|
||||
| 南侧竖井 | x −2.4~−0.2, y < −4.45 | XTAL1/2 下到 Y1 + UP_DP/DM 南下再西行 |
|
||||
| 晶振窝 | (−4.2, −9.2) 一带 | Y1 与两颗负载电容 |
|
||||
|
||||
A 圈 y=−6.3 上 `C743`(x−2.65) 与 `C745`(x+0.05) 之间那 2.16 mm 就是南侧竖井,
|
||||
四根线(XTAL 用 0.2、UP 用 0.28)刚好过得去。
|
||||
|
||||
## 逐件的对位依据
|
||||
|
||||
- `R708` **RBIAS**:12k 精密偏置,离 pin63 只有 0.76 mm —— 全簇最近的一件。
|
||||
- `C742/C743` VDD18 / VDD18PLL:内部 LDO 的储能,各自贴 pin25 / pin62。
|
||||
- `C744~C750`:七颗 +3V3 脚(5/10/24/46/52/57/64)一脚一颗。
|
||||
⚠ pin5 / pin10 夹在下游 D± 中间,件只能放到扇出区之外(3.3~3.8 mm)——
|
||||
靠 Inner2 的 +3V3 灌铜就近打孔补偿,不是靠这段走线。
|
||||
- `C751` 10 µF 1206 是总储能,放北边不抢位置。
|
||||
- `R709/R710` VBUS 分压留在**东侧 pin44 边上**:高阻分压节点要短,
|
||||
低阻的 VBUS_UP 从 J23 长跑过来无所谓。
|
||||
- `R711/R716/R720` 是纯配置下拉/上拉,放 B 圈(离脚 3.1 mm)完全够。
|
||||
"""
|
||||
hx, hy = HUB_CENTER
|
||||
# ⚠️ RB 2026-09-06 用户拍板 8.0 → **9.5**:A/B 两圈之间原来只有 0.268 mm
|
||||
# (A 圈外脚 117.016 到 B 圈内脚 117.284),`+3V3_HUB` 进 `R716.2` / `VBUS_UP` 进
|
||||
# `R709.1` 三段怎么都布不通(§25.15:换出线方向、改顺序、单独布都试过)。
|
||||
# ⚠️ 9.5 **不行**:B 圈外脚正压在 `USB5_DP` 那条竖走的长差分上(x=120.0,左沿 119.86,
|
||||
# `pcb2_dp_long.py` 的手写几何),而本地检查只看焊盘不看走线、根本拦不住。
|
||||
# 外脚边到 DP 左沿留 0.2 ⇒ RB ≤ 8.944,取 **8.9**(留 0.244)。
|
||||
# A/B 缝 = (118.9−0.433−0.283) − (116.3+0.433+0.283) = 1.17 mm,
|
||||
# 一根 0.35 的线带两边 0.2 净距要 0.75,够。
|
||||
RA, RB = 6.3, 8.9
|
||||
out = {'U32': (hx, hy, 0)}
|
||||
|
||||
# 南边 A 圈(rot 270 = pin1 朝上、朝芯片)
|
||||
for dsg, dx in (('C744', -4.45), # pin64 +3V3
|
||||
('R708', -3.45), # pin63 RBIAS ★ 0.76 mm
|
||||
('C743', -2.65), # pin62 VDD18PLL
|
||||
# ── 竖井 −2.4 ~ −0.2:XTAL1/2 + UP_DP/DM 从这里下去 ──
|
||||
('C745', 0.05), # pin57 +3V3
|
||||
('R715', 1.25), # pin56 PRTDIS7
|
||||
('R714', 2.45), # pin54 PRTDIS6
|
||||
('C746', 3.65)): # pin52 +3V3
|
||||
out[dsg] = (hx + dx, hy - RA, 270)
|
||||
|
||||
# 晶振窝:Y1 摆西南,两颗负载电容各贴一个端子
|
||||
out['Y1'] = (hx - 4.2, hy - 9.2, 0) # pin1=XTAL1 在左上, pin3=XTAL2 在右下
|
||||
out['C740'] = (hx - 7.2, hy - 8.35, 180) # pin1 朝右 -> Y1 pin1
|
||||
out['C741'] = (hx - 3.1, hy - 12.3, 270) # pin1 朝上 -> Y1 pin3
|
||||
|
||||
# 北边 A 圈(rot 90 = pin1 朝下、朝芯片)
|
||||
out['C749'] = (hx - 0.75, hy + RA, 90) # pin24 +3V3
|
||||
out['C742'] = (hx + 0.75, hy + RA, 90) # pin25 VDD18
|
||||
out['C751'] = (hx + 3.0, hy + 7.0, 90) # 10 µF 总储能(1206,本簇最大件)
|
||||
|
||||
# 西边:整条留给下游 D± 扇出,只在扇出区上下各塞一颗 +3V3 电容
|
||||
out['C747'] = (hx - RA, hy - 5.0, 180) # 服务 pin5 (+3V3, 夹在 D± 中间)
|
||||
out['C748'] = (hx - RA, hy + 3.5, 180) # 服务 pin10 (+3V3, 同上)
|
||||
|
||||
# 东边 A 圈(rot 0 = pin1 朝左、朝芯片;R709/R710 反过来放)
|
||||
out['R719'] = (hx + RA, hy + 0.25, 0) # pin40 NONREM1 -> GND
|
||||
out['R712'] = (hx + RA, hy - 0.95, 0) # pin42 CFGSEL1 -> GND
|
||||
out['R709'] = (hx + RA, hy - 2.15, 180) # pin1=VBUS_UP 朝外, pin2=VBUS_DET 朝 pin44
|
||||
out['R710'] = (hx + RA, hy - 3.35, 180) # pin1=VBUS_DET, pin2=GND
|
||||
out['C750'] = (hx + RA, hy - 4.55, 0) # pin46 +3V3
|
||||
# 东边 B 圈:三颗配置电阻,离脚 4.0 mm(原 3.1),纯配置下拉/上拉,够
|
||||
out['R711'] = (hx + RB, hy - 0.35, 0) # pin41 CFGSEL0 -> GND
|
||||
out['R716'] = (hx + RB, hy - 1.55, 0) # pin43 HUB_RST -> +3V3
|
||||
out['R720'] = (hx + RB, hy - 2.75, 0) # pin45 NONREM0 -> GND
|
||||
|
||||
missing = set(HUB_PARTS) - set(out)
|
||||
extra = set(out) - set(HUB_PARTS)
|
||||
if missing or extra:
|
||||
raise SystemExit(u'Hub 手排对不上 HUB_PARTS:漏 %s / 多 %s' % (sorted(missing), sorted(extra)))
|
||||
return out
|
||||
|
||||
|
||||
BOTTOM = set(list(HDR) + list(BOT_GLOBAL) + HUB_PARTS) # 这些件在 layer 2
|
||||
|
||||
|
||||
def plan():
|
||||
u"""汇总所有元件 -> {位号: (x_mm, y_mm, rot, layer)}"""
|
||||
pos = {}
|
||||
for n in SITES:
|
||||
d = desig(n)
|
||||
for i, (u, v) in enumerate(POGO_XY):
|
||||
x, y, r = to_board(n, u, v, 0)
|
||||
pos[d['pogo'][i]] = (x, y, r, 1)
|
||||
for key, (u, v, r0) in BAND.items():
|
||||
x, y, r = to_board(n, u, v, r0)
|
||||
pos[d[key]] = (x, y, r, 1)
|
||||
for dsg, (x, y, r) in GLOBAL.items():
|
||||
pos[dsg] = (x, y, r, 1)
|
||||
for dsg, (x, y, r) in list(HDR.items()) + list(BOT_GLOBAL.items()):
|
||||
pos[dsg] = (x, y, r, 2)
|
||||
for dsg, (x, y, r) in hub_layout().items():
|
||||
pos[dsg] = (x, y, r, 2)
|
||||
return pos
|
||||
|
||||
|
||||
# ═══════════════ 本地检查 ═══════════════
|
||||
def load_bb():
|
||||
u"""从 pcb2-parts.json 读每个元件的焊盘包围盒(相对元件原点,mm)。"""
|
||||
raw = json.load(open(os.path.join(HERE, 'pcb2-parts.json'), encoding='utf-8'))
|
||||
bb = {}
|
||||
for c in raw:
|
||||
if not c['bb']:
|
||||
bb[c['d']] = (-0.5, -0.5, 0.5, 0.5)
|
||||
continue
|
||||
x, y = c['x'], c['y']
|
||||
b = c['bb']
|
||||
bb[c['d']] = ((b[0] - x) / MIL, (b[1] - y) / MIL, (b[2] - x) / MIL, (b[3] - y) / MIL)
|
||||
return bb
|
||||
|
||||
|
||||
CAN = None # 延迟算:五个 Ø10 电解的位号
|
||||
|
||||
|
||||
def box(d, rot, bb, layer=1):
|
||||
global CAN
|
||||
if CAN is None:
|
||||
CAN = {desig(n)['c1'] for n in SITES}
|
||||
if d in CAN and layer == 1: # 顶层按罐体 Ø12.5 留;底层只有两根引脚
|
||||
return (-C801_BODY_R, -C801_BODY_R, C801_BODY_R, C801_BODY_R)
|
||||
x0, y0, x1, y1 = bb[d]
|
||||
if rot % 180 == 90:
|
||||
x0, y0, x1, y1 = y0, x0, y1, x1
|
||||
x0, x1 = min(x0, x1), max(x0, x1)
|
||||
y0, y1 = min(y0, y1), max(y0, y1)
|
||||
return (x0, y0, x1, y1)
|
||||
|
||||
|
||||
def check(pos):
|
||||
bb = load_bb()
|
||||
errs = []
|
||||
known = set(bb)
|
||||
for d in pos:
|
||||
if d not in known:
|
||||
errs.append(u'PCB 上没有这个位号: %s' % d)
|
||||
missing = known - set(pos)
|
||||
if missing:
|
||||
errs.append(u'没安排位置的元件 %d 个: %s' % (len(missing), sorted(missing)[:12]))
|
||||
|
||||
TH = th_parts()
|
||||
|
||||
def layers(d, lay):
|
||||
return (1, 2) if d in TH else (lay,)
|
||||
|
||||
# 越框
|
||||
for d, (x, y, r, lay) in pos.items():
|
||||
if d not in known:
|
||||
continue
|
||||
b = box(d, r, bb, lay)
|
||||
if x + b[0] < 1.0 or x + b[2] > BW - 1.0 or y + b[1] < 1.0 or y + b[3] > BH - 1.0:
|
||||
errs.append(u'越框: %s @ (%.1f, %.1f)' % (d, x, y))
|
||||
|
||||
# 同层重叠
|
||||
items = [d for d in pos if d in known]
|
||||
for i in range(len(items)):
|
||||
for j in range(i + 1, len(items)):
|
||||
a, b_ = items[i], items[j]
|
||||
ax, ay, ar, al = pos[a]
|
||||
bx, by, br, bl = pos[b_]
|
||||
shared = set(layers(a, al)) & set(layers(b_, bl))
|
||||
for L in sorted(shared):
|
||||
pa, pb = box(a, ar, bb, L), box(b_, br, bb, L)
|
||||
if (ax + pa[0] < bx + pb[2] - 0.02 and bx + pb[0] < ax + pa[2] - 0.02 and
|
||||
ay + pa[1] < by + pb[3] - 0.02 and by + pb[1] < ay + pa[3] - 0.02):
|
||||
errs.append(u'重叠(L%d): %s(%.1f,%.1f) x %s(%.1f,%.1f)' % (L, a, ax, ay, b_, bx, by))
|
||||
|
||||
# 载板自己的 M3 孔不许落进手机本体投影(螺钉头会顶到手机板),
|
||||
# 也不许和 35 个手机定位孔挨太近(立柱和螺钉会打架)。
|
||||
# 这一条是 2026-09-04 补的 —— 原来的四角布孔就是这么错的。
|
||||
for i, (mx, my) in enumerate(MH, 1):
|
||||
for n, (x0, y0, rho) in SITES.items():
|
||||
if x0 - MH_PAD / 2 < mx < x0 + PW + MH_PAD / 2 and y0 - MH_PAD / 2 < my < y0 + PH + MH_PAD / 2:
|
||||
errs.append(u'载板 M3 孔 MH%d (%.1f,%.1f) 落在 CH%d 主板投影里' % (i, mx, my, n))
|
||||
for n in SITES:
|
||||
for hn, (u_, v_) in HOLES.items():
|
||||
hx, hy, _ = to_board(n, u_, v_)
|
||||
d = ((mx - hx) ** 2 + (my - hy) ** 2) ** 0.5
|
||||
need = MH_PAD / 2 + HOLE_D / 2 + 0.2
|
||||
if d < need:
|
||||
errs.append(u'载板 M3 孔 MH%d 离定位孔 P%d%s 只有 %.2f mm(要 >= %.2f)'
|
||||
% (i, n, hn, d, need))
|
||||
|
||||
# 顶层元件不能压到手机本体投影里(顶针和定位孔除外 —— 它们本来就在下面)
|
||||
allow = set()
|
||||
for n in SITES:
|
||||
allow |= set(desig(n)['pogo'])
|
||||
for d, (x, y, r, lay) in pos.items():
|
||||
if lay != 1 or d in allow or d not in known:
|
||||
continue
|
||||
b = box(d, r, bb, 1)
|
||||
for n, (x0, y0, rho) in SITES.items():
|
||||
if (x + b[0] < x0 + PW and x0 < x + b[2] and
|
||||
y + b[1] < y0 + PH and y0 < y + b[3]):
|
||||
errs.append(u'%s 压在 CH%d 主板投影里 @ (%.1f, %.1f)' % (d, n, x, y))
|
||||
return errs
|
||||
|
||||
|
||||
# ═══════════════ 下发 ═══════════════
|
||||
def call(body, timeout=300):
|
||||
r = run("await eda.dmt_EditorControl.openDocument('%s');"
|
||||
"await new Promise(r=>setTimeout(r,1500));" % PCB + body, timeout=timeout)
|
||||
if not r.get('success'):
|
||||
print(u'下发失败:', json.dumps(r, ensure_ascii=False)[:600])
|
||||
sys.exit(1)
|
||||
return r['result']
|
||||
|
||||
|
||||
def push_layers():
|
||||
u"""把载板从 2 层切到 **4 层**(2026-09-04 用户拍板,§25.6)。
|
||||
|
||||
转板过来默认是两层。下板 §10.1 已经定了四层,而**载板非上四层不可**:
|
||||
· 11 对 USB HS 差分要 90 Ω —— W0.28/S0.18 是按四层 1.6 mm 叠层
|
||||
(L1-L2 介质 h=0.2104)算的;两层板的介质是整个 1.5 mm,
|
||||
同样阻抗线宽要 2 mm 以上,根本排不下;
|
||||
· 五路 4 A(合计 20 A)没有内层电源面就只能靠外层大面积铜,
|
||||
而两面都摆满了元件;
|
||||
· 顶/底两层都是元件面,地回流会被割得很碎。
|
||||
|
||||
叠层沿用下板:L1 顶层信号 / L2(id 15) 完整地平面 / L3(id 16) 电源 / L4(id 2) 底层信号。
|
||||
"""
|
||||
r = call("""
|
||||
const before = await eda.pcb_Layer.getTheNumberOfCopperLayers();
|
||||
await eda.pcb_Layer.setTheNumberOfCopperLayers(4);
|
||||
const after = await eda.pcb_Layer.getTheNumberOfCopperLayers();
|
||||
const all = await eda.pcb_Layer.getAllLayers();
|
||||
await eda.pcb_Document.save();
|
||||
return {before, after, copper: all.filter(l => l && l.type === 'SIGNAL').map(l => [l.id, l.name])};
|
||||
""")
|
||||
print(u'铜层数 %s -> %s' % (r['before'], r['after']))
|
||||
for lid, name in r['copper']:
|
||||
print(u' id %-3s %s' % (lid, name))
|
||||
if r['after'] != 4:
|
||||
sys.exit(u'!! 切四层失败')
|
||||
|
||||
|
||||
def outline_poly():
|
||||
u"""板框:一条闭合多段线(4 直边 + 4 个 R5 圆角)。
|
||||
|
||||
⚠️ 必须是**一条 polyline**,拆成独立线弧的话自动布线器认不出板框
|
||||
(pcb_place.py 的 push_outline 里有完整成因)。
|
||||
"""
|
||||
R = CORNER_R
|
||||
pts = []
|
||||
L = lambda x, y: pts.append(('L', x, y))
|
||||
A = lambda x, y: pts.append(('ARC', 90.0, x, y))
|
||||
L(BW - R, 0.0); A(BW, R)
|
||||
L(BW, BH - R); A(BW - R, BH)
|
||||
L(R, BH); A(0.0, BH - R)
|
||||
L(0.0, R); A(R, 0.0)
|
||||
src, mode = [round(R * MIL, 4), 0.0], None
|
||||
for p in pts:
|
||||
if p[0] == 'L':
|
||||
if mode != 'L':
|
||||
src.append('L'); mode = 'L'
|
||||
src += [round(p[1] * MIL, 4), round(p[2] * MIL, 4)]
|
||||
else:
|
||||
src.append('ARC'); mode = 'ARC'
|
||||
src += [p[1], round(p[2] * MIL, 4), round(p[3] * MIL, 4)]
|
||||
return src
|
||||
|
||||
|
||||
def push_outline():
|
||||
n = call("""
|
||||
let del = 0;
|
||||
for (const l of await eda.pcb_PrimitiveLine.getAll())
|
||||
if (l.getState_Layer() === 11) { await eda.pcb_PrimitiveLine.delete(l.getState_PrimitiveId()); del++; }
|
||||
for (const p of await eda.pcb_PrimitivePolyline.getAll())
|
||||
if (p.getState_Layer() === 11) { await eda.pcb_PrimitivePolyline.delete(p.getState_PrimitiveId()); del++; }
|
||||
const poly = await eda.pcb_MathPolygon.createPolygon(%s);
|
||||
const r = await eda.pcb_PrimitivePolyline.create('', 11, poly, 5);
|
||||
const left = (await eda.pcb_PrimitivePolyline.getAll()).filter(p => p.layer === 11).length;
|
||||
return {del, made: r ? r.primitiveId : null, left};
|
||||
""" % json.dumps(outline_poly()))
|
||||
if n['left'] != 1:
|
||||
print(u'!! layer 11 上有 %d 条多段线,应为 1' % n['left'])
|
||||
sys.exit(1)
|
||||
print(u'板框: %.0f × %.0f mm,一条闭合多段线(R%.0f 圆角)' % (BW, BH, CORNER_R))
|
||||
k = call("""
|
||||
const MH = %s;
|
||||
for (const p of await eda.pcb_PrimitivePad.getAll()) {
|
||||
let parent = null;
|
||||
try { parent = p.getState_ParentComponentPrimitiveId(); } catch(e) {}
|
||||
if (parent) continue;
|
||||
const num = p.getState_PadNumber();
|
||||
if (typeof num === 'string' && num.indexOf('MH') === 0) await eda.pcb_PrimitivePad.delete(p.getState_PrimitiveId());
|
||||
}
|
||||
let k = 0;
|
||||
for (let i = 0; i < MH.length; i++) {
|
||||
await eda.pcb_PrimitivePad.create(12, 'MH' + (i + 1), MH[i][0], MH[i][1], 0,
|
||||
['ELLIPSE', %.2f, %.2f], 'GND', ['ROUND', %.4f], 0, 0, 0, true);
|
||||
k++;
|
||||
}
|
||||
return k;
|
||||
""" % (json.dumps([[round(x * MIL, 2), round(y * MIL, 2)] for x, y in MH]),
|
||||
MH_PAD * MIL, MH_PAD * MIL, MH_HOLE * MIL))
|
||||
print(u'载板自己的 M3 定位孔: %d 个(Φ%.1f / 焊盘 Φ%.1f / 接 GND)' % (k, MH_HOLE, MH_PAD))
|
||||
call("await eda.pcb_Document.save(); return 1;")
|
||||
|
||||
|
||||
def mech_items():
|
||||
u"""35 个手机定位孔(NPTH)+ 5 个手机位的丝印外框与标签。"""
|
||||
holes, lines, texts = [], [], []
|
||||
for n in SITES:
|
||||
x0, y0, rho = SITES[n]
|
||||
for hn, (u, v) in HOLES.items():
|
||||
x, y, _ = to_board(n, u, v)
|
||||
holes.append(['P%d%s' % (n, hn), round(x * MIL, 2), round(y * MIL, 2)])
|
||||
# 主板轮廓(丝印,装配对位用)
|
||||
c = [(x0, y0), (x0 + PW, y0), (x0 + PW, y0 + PH), (x0, y0 + PH)]
|
||||
for i in range(4):
|
||||
a, b = c[i], c[(i + 1) % 4]
|
||||
lines.append([round(a[0] * MIL, 2), round(a[1] * MIL, 2),
|
||||
round(b[0] * MIL, 2), round(b[1] * MIL, 2)])
|
||||
# 接口边加粗一道,提醒装配朝向
|
||||
ey = y0 if rho == 0 else y0 + PH
|
||||
lines.append([round((x0 + 2) * MIL, 2), round((ey + (1.5 if rho == 0 else -1.5)) * MIL, 2),
|
||||
round((x0 + PW - 2) * MIL, 2), round((ey + (1.5 if rho == 0 else -1.5)) * MIL, 2)])
|
||||
texts.append([round((x0 + PW / 2 - 6) * MIL, 2), round((y0 + PH / 2) * MIL, 2),
|
||||
u'CH%d 主板' % n])
|
||||
return holes, lines, texts
|
||||
|
||||
|
||||
def push_mech():
|
||||
holes, lines, texts = mech_items()
|
||||
n = call("""
|
||||
const H = %s;
|
||||
for (const p of await eda.pcb_PrimitivePad.getAll()) {
|
||||
let parent = null;
|
||||
try { parent = p.getState_ParentComponentPrimitiveId(); } catch(e) {}
|
||||
if (parent) continue;
|
||||
const num = p.getState_PadNumber();
|
||||
if (typeof num === 'string' && /^P[1-5]H[1-7]$/.test(num)) await eda.pcb_PrimitivePad.delete(p.getState_PrimitiveId());
|
||||
}
|
||||
let k = 0;
|
||||
for (const [name, x, y] of H) {
|
||||
await eda.pcb_PrimitivePad.create(12, name, x, y, 0,
|
||||
['ELLIPSE', %.4f, %.4f], '', ['ROUND', %.4f], 0, 0, 0, false);
|
||||
k++;
|
||||
}
|
||||
return k;
|
||||
""" % (json.dumps(holes), HOLE_D * MIL, HOLE_D * MIL, HOLE_D * MIL))
|
||||
print(u'手机定位孔: %d 个 NPTH Φ%.1f(⚠ 必须配尼龙立柱,见文件头第 3 条)' % (n, HOLE_D))
|
||||
|
||||
m = call("""
|
||||
const L = %s, T = %s;
|
||||
let del = 0;
|
||||
for (const l of await eda.pcb_PrimitiveLine.getAll())
|
||||
if (l.getState_Layer() === 3) { await eda.pcb_PrimitiveLine.delete(l.getState_PrimitiveId()); del++; }
|
||||
for (const s of await eda.pcb_PrimitiveString.getAll())
|
||||
if (s.getState_Layer() === 3) { await eda.pcb_PrimitiveString.delete(s.getState_PrimitiveId()); del++; }
|
||||
let k = 0;
|
||||
for (const [x1,y1,x2,y2] of L) { await eda.pcb_PrimitiveLine.create('', 3, x1, y1, x2, y2, 6); k++; }
|
||||
// create(layer, x, y, text, fontFamily, fontSize, lineWidth, alignMode, rotation, reverse, expansion, mirror, lock)
|
||||
for (const [x,y,t] of T) { await eda.pcb_PrimitiveString.create(3, x, y, t, 'NotoSansSC-Regular', 60, 6, 0, 0, false, 0, false, false); }
|
||||
return {del, k, t: T.length};
|
||||
""" % (json.dumps(lines), json.dumps(texts, ensure_ascii=False)))
|
||||
print(u'手机位丝印: %d 条线 + %d 个标签(顺带删掉 %d 个旧图元)' % (m['k'], m['t'], m['del']))
|
||||
call("await eda.pcb_Document.save(); return 1;")
|
||||
|
||||
|
||||
def push_parts(pos):
|
||||
plan_list = [[d, round(x * MIL, 2), round(y * MIL, 2), r, lay]
|
||||
for d, (x, y, r, lay) in sorted(pos.items())]
|
||||
total = 0
|
||||
for k in range(0, len(plan_list), 50):
|
||||
total += call("""
|
||||
const P = %s;
|
||||
const map = {};
|
||||
for (const c of await eda.pcb_PrimitiveComponent.getAll()) map[c.getState_Designator()] = c;
|
||||
let n = 0;
|
||||
for (const [d, x, y, rot, lay] of P) {
|
||||
const c = map[d]; if (!c) continue;
|
||||
await eda.pcb_PrimitiveComponent.modify(c.getState_PrimitiveId(), {x, y, rotation: rot, layer: lay});
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
""" % json.dumps(plan_list[k:k + 50]))
|
||||
print(u' 已摆 %d / %d' % (total, len(plan_list)))
|
||||
call("await eda.pcb_Document.save(); return 1;")
|
||||
print(u'布局已下发并保存:%d 个元件' % total)
|
||||
|
||||
|
||||
def header_pads():
|
||||
u"""把载板侧 J31/J32 的 80 个焊盘绝对坐标读出来存盘,给下板 PCB 对位用。
|
||||
|
||||
⚠️ 这是**唯一靠得住**的对位方式。J31/J32 在载板底层、J24/J25 在下板顶层,
|
||||
「第 r 行会不会真对上第 r 行」取决于焊盘编号 + 贴装镜像,
|
||||
光靠脑补两边的引脚表必错(§22.7 已经把这条列为 PCB 阶段的风险)。
|
||||
做法:本函数把载板侧每个焊盘的 (位号, 焊盘号, x, y) 落成 json,
|
||||
下板放 J24/J25 时**按同一个装配坐标系把 pin k 摆到同一个 (x, y)**,
|
||||
数值对上就一定插得上,不用推理。
|
||||
|
||||
装配坐标系 = 载板坐标系;下板 160×125 居中放在载板 220×135 下面,
|
||||
即下板原点 = 载板 (30, 5)。下板侧的目标坐标 = 这里的坐标 − (30, 5)。
|
||||
"""
|
||||
r = call("""
|
||||
const out = [];
|
||||
for (const c of await eda.pcb_PrimitiveComponent.getAll()) {
|
||||
const d = c.getState_Designator();
|
||||
if (d !== 'J31' && d !== 'J32') continue;
|
||||
const pins = await eda.pcb_PrimitiveComponent.getAllPinsByPrimitiveId(c.getState_PrimitiveId());
|
||||
for (const p of pins) out.push([d, p.padNumber, p.x, p.y, c.getState_Layer()]);
|
||||
}
|
||||
return out;
|
||||
""")
|
||||
MILf = MIL
|
||||
data = {'frame': u'载板坐标系,mm;下板原点 = 载板 (30, 5),下板侧目标 = 本坐标 − (30,5)',
|
||||
'pads': [{'ref': a, 'pad': b, 'x': round(x / MILf, 3), 'y': round(y / MILf, 3),
|
||||
'layer': lay} for a, b, x, y, lay in r]}
|
||||
out = os.path.join(HERE, 'pcb2-header-pads.json')
|
||||
json.dump(data, open(out, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
|
||||
print(u'排母焊盘坐标已导出: %s(%d 个)' % (out, len(r)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pos = plan()
|
||||
errs = check(pos)
|
||||
print(u'安排了 %d 个元件(顶层 %d / 底层 %d)'
|
||||
% (len(pos), sum(1 for v in pos.values() if v[3] == 1),
|
||||
sum(1 for v in pos.values() if v[3] == 2)))
|
||||
if errs:
|
||||
print(u'\n!! %d 个问题:' % len(errs))
|
||||
for e in errs[:30]:
|
||||
print(' ', e)
|
||||
if len(errs) > 30:
|
||||
print(u' ...还有 %d 条' % (len(errs) - 30))
|
||||
sys.exit(1)
|
||||
print(u'本地检查通过:无重叠、无越框、顶层没有元件压进手机投影')
|
||||
if '--layers' in sys.argv:
|
||||
push_layers()
|
||||
if '--outline' in sys.argv:
|
||||
push_outline()
|
||||
if '--mech' in sys.argv:
|
||||
push_mech()
|
||||
if '--push' in sys.argv:
|
||||
push_parts(pos)
|
||||
header_pads()
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"J91": [
|
||||
"1",
|
||||
"2",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"10",
|
||||
"11",
|
||||
"12",
|
||||
"14",
|
||||
"15",
|
||||
"16",
|
||||
"17",
|
||||
"18",
|
||||
"19",
|
||||
"20",
|
||||
"22",
|
||||
"23",
|
||||
"25",
|
||||
"26",
|
||||
"32",
|
||||
"33",
|
||||
"34",
|
||||
"35",
|
||||
"37",
|
||||
"39",
|
||||
"41",
|
||||
"42",
|
||||
"43",
|
||||
"45",
|
||||
"47"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
# -*- 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()
|
||||
在新工单中引用
屏蔽一个用户