# -*- coding: utf-8 -*- u"""P6-FAN-EDL 的连线:风扇低边驱动 + TACH 分压 + 第三片 74HC595(EDL_EN1~5)。 用法: /c/Python314/python .claude/p6_nets.py [--push] ⚠ D2(SS34) 的极性是读回来的,不是按 §6 的表推的,而且踩到一条新坑: create() 的 rotation 参数与 setState_Rotation() 差 180 度。 同样传 270:create 得到 pin1(K) 在下,setState_Rotation 得到 pin1(K) 在上。 本脚本用 setState_Rotation(270),K 朝上接 +12V。 非极性的 R/C 一律按坐标(上面那脚 / 下面那脚)连线,不认引脚号。 """ import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run PAGE = 'fd00f13e59336392' # 重新摆位(元件可以直接移动,旗和端口不行 —— §3b) MOVE = [ ('D2', 790, 430, 270), ('C738', 1090, 740, 270), ('R730', 970, 745, 270), ('R731', 920, 725, 0), ('R732', 840, 690, 270), ('C739', 520, 600, 270), ] DROP = ['R735', 'R736', 'R737', 'R738', 'R739'] # EDL_EN 下拉改放载板(贴着模拟开关) # 期望的引脚坐标,推送前逐个核对 EXPECT = { 'D2': {'1': (790, 450), '2': (790, 410)}, # 1=K 朝上 -> +12V(靠 setState_Rotation(270)) 'Q6': {'1': (680, 330), '2': (710, 310), '3': (710, 350)}, # G / S / D 'R730': {'1': (970, 765), '2': (970, 725)}, 'R731': {'1': (900, 725), '2': (940, 725)}, 'R732': {'1': (840, 710), '2': (840, 670)}, 'R733': {'1': (580, 330), '2': (620, 330)}, 'R734': {'1': (660, 250), '2': (660, 290)}, 'C738': {'1': (1090, 755), '2': (1090, 725)}, 'C739': {'1': (520, 620), '2': (520, 580)}, } FANS = [('J26', 700), ('J27', 620), ('J28', 540), ('J29', 460), ('J30', 380)] X_PIN = 1065 # 风扇座引脚所在 x X_12V, X_RTN, X_TAC = 1030, 1000, 970 # 三条竖母线 # 74HC595:1=QB 2=QC 3=QD 4=QE 5=QF 6=QG 7=QH 8=GND # 9=QH' 10=SRCLR# 11=SRCLK 12=RCLK 13=OE# 14=SER 15=QA 16=VCC U33L = 210 # 左列 x U33R = 290 # 右列 x NC = {'U33': ['5', '6', '7', '9']} # QF/QG/QH 备用、QH' 链尾 def call(js, timeout=180): r = run(js, timeout=timeout) if not r.get('success'): raise RuntimeError(json.dumps(r, ensure_ascii=False)[:800]) return r['result'] def build(): W, F, PT, TX = [], [], [], [] def w(*pts): for i in range(0, len(pts) - 2, 2): W.append([pts[i], pts[i + 1], pts[i + 2], pts[i + 3]]) # ---------- 【A】EDL:第三片 74HC595 ---------- # 左列输出 QB~QE -> EDL_EN2~5 for pin, y, net in [('1', 635, 'EDL_EN2'), ('2', 625, 'EDL_EN3'), ('3', 615, 'EDL_EN4'), ('4', 605, 'EDL_EN5')]: w(U33L, y, U33L - 40, y) PT.append(['BI', net, U33L - 40, y, 180]) # 右列 w(U33R, 625, U33R + 40, 625) PT.append(['BI', 'EDL_EN1', U33R + 40, 625, 0]) # 15 = QA for pin, y, net in [('11', 585, 'KEY_SRCLK'), ('12', 595, 'KEY_RCLK'), ('13', 605, 'KEY_OE#'), ('14', 615, 'SR_CAS2')]: w(U33R, y, U33R + 40, y) PT.append(['BI', net, U33R + 40, y, 0]) # 电源与地 w(U33R, 635, 400, 635, 400, 665) # 16 VCC F.append(['Power', '+3V3', 400, 665, 0]) w(U33R, 575, 400, 575, 400, 540) # 10 SRCLR# 拉高 F.append(['Power', '+3V3', 400, 540, 180]) w(U33L, 565, 170, 565, 170, 535) # 8 GND F.append(['Ground', 'GND', 170, 535, 0]) w(520, 620, 520, 650) F.append(['Power', '+3V3', 520, 650, 0]) w(520, 580, 520, 550) F.append(['Ground', 'GND', 520, 550, 0]) # ---------- 【B】风扇:栅极驱动 ---------- w(580, 330, 540, 330) PT.append(['BI', 'FAN_PWM', 540, 330, 180]) w(620, 330, 680, 330) # R733 -> Q6.G w(660, 330, 660, 290) # -> R734.2 下拉 w(660, 250, 660, 225) F.append(['Ground', 'GND', 660, 225, 0]) w(710, 310, 710, 280) # Q6.S -> GND F.append(['Ground', 'GND', 710, 280, 0]) # FAN_RTN:Q6 漏极 -> 续流二极管阳极 -> 风扇座 pin2 母线 w(710, 350, 1000, 350) w(790, 410, 790, 350) # D2.A 落在上面那条线上 w(X_RTN, 350, X_RTN, 700) # 续流二极管阴极 -> +12V w(790, 450, 790, 480) F.append(['Power', '+12V', 790, 480, 0]) # ---------- 【B】风扇:三条母线 + 五个座子 ---------- for d, y in FANS: w(X_PIN, y + 10, X_12V, y + 10) # pin1 +12V w(X_PIN, y, X_RTN, y) # pin2 FAN_RTN w(X_PIN, y - 10, X_TAC, y - 10) # pin3 TACH w(X_12V, 390, X_12V, 785) F.append(['Power', '+12V', X_12V, 785, 0]) w(X_TAC, 370, X_TAC, 725) # TACH_RAW 母线,上端接 R730.1 # 12V 本地储能 w(1090, 755, X_12V, 755) w(1090, 725, 1090, 700) F.append(['Ground', 'GND', 1090, 700, 0]) # TACH 上拉到 12V + 10k/3.3k 分压,IO48 最高 12*3.3/13.3 = 2.98V w(970, 765, X_12V, 765) w(970, 725, 940, 725) w(900, 725, 800, 725) w(840, 725, 840, 710) w(840, 670, 840, 645) F.append(['Ground', 'GND', 840, 645, 0]) PT.append(['BI', 'FAN_TACH', 800, 725, 180]) TX += [ (460, 806, u'【A】第三片 74HC595(U33)—— 5 路 EDL_EN', 12), (460, 786, u"级联在 U22 之后:U22.QH'(9) -> U33.SER(14),网络名 SR_CAS2。", 10), (460, 770, u'整条链变成 24 位,先移入的字节落到 U33。SRCLK / RCLK / OE# 与前两片共用。', 10), (460, 754, u'QA~QE = EDL_EN1~5;QF/QG/QH 备用不接。', 10), (45, 340, u'【B】风扇:低边 N-MOS + PWM,5 个风扇并一路(§22.6)', 12), (45, 318, u'Q6 AO3400A 切风扇回流,D2 SS34 做续流 —— 风扇是感性负载,', 10), (45, 302, u'少了它 MOS 会被反峰打穿。FAN_PWM = IO47,FAN_TACH = IO48。', 10), (45, 286, u'5 个 TACH 并联做线或,停一个频率掉约 1/5(分不出是哪一个)。', 10), (45, 262, u'TACH 必须分压:低边 PWM 关断时风扇地被抬到 12V,TACH 是集电极', 10), (45, 246, u'开路,也跟着到 12V —— 直连 IO48 会打穿 ESP32。', 10), (45, 230, u'10k 上拉到 12V + 10k/3.3k 分压 -> IO48 最高 2.98V。关断期间读到', 10), (45, 214, u'稳定高电平(测不到转速),导通期间才有脉冲 —— 这是低边调速的', 10), (45, 198, u'固有限制,不是接线错。', 10), (45, 174, u'EDL_EN 的 100k 下拉不放这一页 —— 跟按键 MOS 栅极一样,放在载板', 10), (45, 158, u'上紧挨模拟开关(§8.1 复位安全)。', 10), (45, 134, u'风扇电流 4x0.10 + 1x0.30 = 0.70A @12V;12V 预算 7.6 -> 8.3A,10A 适配器仍有余量。', 10), (45, 118, u'风扇座 KF2510-3A:pin1 = +12V,pin2 = 风扇回流(MOS 漏极),pin3 = TACH。', 10), ] return W, F, PT, TX def main(): push = '--push' in sys.argv W, F, PT, TX = build() print(u'导线 %d 旗 %d 端口 %d 文字 %d' % (len(W), len(F), len(PT), len(TX))) if not push: return call(""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,900)); const n=(await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name; if (n!=='P6-FAN-EDL') throw new Error('page='+n); const drop=%s, move=%s; for (const c of await eda.sch_PrimitiveComponent.getAll()){ const t=c.getState_ComponentType(); if (t==='netflag'||t==='netport'){ await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); continue; } if (t!=='part') continue; const d=c.getState_Designator(); if (drop.includes(d)){ await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); continue; } const m = move.find(v=>v[0]===d); if (m) await c.toAsync().setState_X(m[1]).setState_Y(m[2]).setState_Rotation(m[3]).done(); } 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, json.dumps(DROP), json.dumps(MOVE))) print(u'已清空导线/旗/端口/文字,已挪位、已删 %s' % ','.join(DROP)) pins = 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(),Math.round(p.getState_X()),Math.round(p.getState_Y())]); } return out; """) bad = [] for d, want in EXPECT.items(): got = {p: (x, y) for p, x, y in pins.get(d, [])} for pin, xy in want.items(): if got.get(pin) != xy: bad.append(u'%s.%s 期望 %s 实测 %s' % (d, pin, xy, got.get(pin))) for d, y in FANS: got = {p: (x, y2) for p, x, y2 in pins.get(d, [])} for pin, yy in (('1', y + 10), ('2', y), ('3', y - 10)): if got.get(pin) != (X_PIN, yy): bad.append(u'%s.%s 期望 %s 实测 %s' % (d, pin, (X_PIN, yy), got.get(pin))) for pin, y in [('1', 635), ('2', 625), ('3', 615), ('4', 605), ('8', 565)]: got = {p: (x, y2) for p, x, y2 in pins.get('U33', [])} if got.get(pin) != (U33L, y): bad.append(u'U33.%s 期望 %s 实测 %s' % (pin, (U33L, y), got.get(pin))) if bad: raise SystemExit(u'引脚坐标对不上:\n ' + '\n '.join(bad)) print(u'引脚坐标全部核对通过') 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(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(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([list(t) for t in TX], 15, "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;") json.dump(NC, open(os.path.join(HERE, 'p6-nets-nc.json'), 'w', encoding='utf-8'), ensure_ascii=False, indent=1) print(u'推送完成') main()