# -*- coding: utf-8 -*- u"""载板 C6-KEYS:三片 74HC595(U21 / U22 / U33,24 位链)—— 15 路侧键 + 5 路 EDL_EN + HUB_RST。 2026-09-06 从下板 P3B-EXPAND / P6-FAN-EDL 整窝搬过来(设计文档 §26.23)。 位号原样保留(U21 / U22 / U33 / C604 / C605 / C739 / R609),BOM 好对账。 输出位分配沿用 §26.18 那张表(固件 `sr595_key_bit()` / `sr595_edl_bit()` 不用改): 影子位 QA(15) QB(1) QC(2) QD(3) QE(4) QF(5) QG(6) QH(7) U21 EDL_EN4 K1_PWR K1_VUP K1_VDN 空 EDL_EN5 EDL_EN3 EDL_EN2 U22 空 K4_PWR K4_VUP K4_VDN 空 K5_VDN K5_VUP K5_PWR U33 EDL_EN1 K2_VUP K2_VDN K2_PWR K3_VDN K3_VUP K3_PWR HUB_RST 供电是载板的 **+3V3_HUB**(U31 AMS1117),不再是下板的 +3V3; 四根控制线 KEY_SER / KEY_SRCLK / KEY_RCLK / KEY_OE# 从排母 J32 北排 3~6 行进来(C1-CONN)。 用法: /c/Python314/python .claude/c6_keys.py [--push] """ import json import os import sys import time HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run # noqa: E402 SCH = '302e115180aaa8fc' # phonerail-carrier LIB = '0819f05c4eef4c71ace90d822a990e87' PNAME = 'C6-KEYS' P = { 'HC595': '94fc47f3127c49c1b8e18e741938eebc', # C5947 74HC595D,118 'C100N': '87bb635d0b2f489a9f60e7cd225beb3c', # C1525 100nF 0402 'R100K': 'acc294b6661948399f609b41c77ed19f', # C25741 100k 0402 } # 74HC595 符号:左列 x=cx-40:1=QB(cy+35) 2=QC 3=QD 4=QE 5=QF 6=QG 7=QH 8=GND(cy-35) # 右列 x=cx+40:9=QH'(cy-35) 10=SRCLR# 11=SRCLK 12=RCLK 13=OE# 14=SER 15=QA 16=VCC(cy+35) CX = 250 # (位号, cy, 去耦位号, [QB..QH 七个网络], QA 网络, SER 网络, QH' 网络) CHIPS = [ ('U21', 660, 'C604', ['K1_PWR', 'K1_VUP', 'K1_VDN', None, 'EDL_EN5', 'EDL_EN3', 'EDL_EN2'], 'EDL_EN4', 'KEY_SER', 'SR_CAS'), ('U22', 490, 'C605', ['K4_PWR', 'K4_VUP', 'K4_VDN', None, 'K5_VDN', 'K5_VUP', 'K5_PWR'], None, 'SR_CAS', 'SR_CAS2'), ('U33', 320, 'C739', ['K2_VUP', 'K2_VDN', 'K2_PWR', 'K3_VDN', 'K3_VUP', 'K3_PWR', 'HUB_RST'], 'EDL_EN1', 'SR_CAS2', None), ] CAP_X = 500 R609 = (600, 500) # KEY_OE# 上拉 100k(竖放) PWR = '+3V3_HUB' NC = {'U21': ['4'], 'U22': ['4', '15'], 'U33': ['9']} # 空输出 + 链尾 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 placement(): out = [] for d, cy, cap, *_ in CHIPS: out.append([d, P['HC595'], CX, cy, 0]) out.append([cap, P['C100N'], CAP_X, cy + 20, 270]) out.append(['R609', P['R100K'], R609[0], R609[1], 270]) return out def wiring(pins): 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]]) for d, cy, cap, qs, qa, ser, q7s in CHIPS: pm = pins[d] L, R = CX - 40, CX + 40 for i, net in enumerate(qs): # QB..QH 左列,标签朝左 y = cy + 35 - i * 10 assert pm[str(i + 1)] == (L, y), (d, i + 1, pm[str(i + 1)]) if net is None: continue w(L, y, L - 15, y) PT.append(['BI', net, L - 15, y, 180]) assert pm['8'] == (L, cy - 35) and pm['16'] == (R, cy + 35) w(L, cy - 35, L - 35, cy - 35, L - 35, cy - 60) # 8 GND F.append(['Ground', 'GND', L - 35, cy - 60, 0]) w(R, cy + 35, R + 35, cy + 35, R + 35, cy + 65) # 16 VCC F.append(['Power', PWR, R + 35, cy + 65, 0]) if qa: # 15 QA w(R, cy + 25, R + 50, cy + 25) PT.append(['BI', qa, R + 50, cy + 25, 0]) w(R, cy + 15, R + 50, cy + 15) # 14 SER PT.append(['BI', ser, R + 50, cy + 15, 0]) w(R, cy + 5, R + 50, cy + 5) # 13 OE# PT.append(['BI', 'KEY_OE#', R + 50, cy + 5, 0]) w(R, cy - 5, R + 50, cy - 5) # 12 RCLK PT.append(['BI', 'KEY_RCLK', R + 50, cy - 5, 0]) w(R, cy - 15, R + 50, cy - 15) # 11 SRCLK PT.append(['BI', 'KEY_SRCLK', R + 50, cy - 15, 0]) w(R, cy - 25, R + 150, cy - 25, R + 150, cy - 45) # 10 SRCLR# 拉高 F.append(['Power', PWR, R + 150, cy - 45, 180]) if q7s: # 9 QH' 级联 w(R, cy - 35, R + 50, cy - 35) PT.append(['BI', q7s, R + 50, cy - 35, 0]) # 去耦:上脚 -> 电源旗,下脚 -> 地旗(按坐标认上下,不认引脚号) cp = pins[cap] top = max(cp.values(), key=lambda v: v[1]) bot = min(cp.values(), key=lambda v: v[1]) assert top[0] == bot[0] == CAP_X, (cap, cp) w(top[0], top[1], top[0], top[1] + 25) F.append(['Power', PWR, top[0], top[1] + 25, 0]) w(bot[0], bot[1], bot[0], bot[1] - 30) F.append(['Ground', 'GND', bot[0], bot[1] - 30, 0]) # KEY_OE# 上拉 100k:ESP32 复位期间 / 排针断开时 24 路输出全部高阻 rp = pins['R609'] top = max(rp.values(), key=lambda v: v[1]) bot = min(rp.values(), key=lambda v: v[1]) w(top[0], top[1], top[0], top[1] + 30) F.append(['Power', PWR, top[0], top[1] + 30, 0]) w(bot[0], bot[1], bot[0], bot[1] - 25) PT.append(['BI', 'KEY_OE#', bot[0], bot[1] - 25, 0]) TX += [ (45, 806, u'【C6-KEYS】三片 74HC595 —— 15 路侧键 + 5 路 EDL_EN + HUB_RST(2026-09-06 从下板搬来,§26.23)', 13), (45, 790, u'链序 U21 → U22 → U33,整条 24 位;SRCLK / RCLK / OE# 三片共用,四根控制线从 J32 北排 3~6 行进来(C1-CONN)。', 10), (45, 776, u'先移入的字节落在 U33;固件发送顺序 U33 → U22 → U21,每字节 QH 先发。输出位表见设计文档 §17.2(固件 sr595_key_bit / sr595_edl_bit)。', 10), (45, 762, u'供电 +3V3_HUB(U31)。OE# 上拉 100k(R609):ESP32 复位期间 / 排针没插时 24 路输出全高阻,', 10), (45, 748, u'配合每个 MOS 栅极 / EDL_EN 的 100k 下拉(C4-SITEn),15 个按键不会误按、五台手机不会误进 9008。', 10), (720, 640, u'HUB_RST 是低有效(USB2517I RESET_N,R716 上拉):', 10), (720, 624, u'影子位 = 1 才是「Hub 正常跑」,上电安全影子是 1<<23 不是 0。', 10), (720, 600, u'K*_PWR / K*_VUP / K*_VDN 去 C4-SITE1~5 的按键 MOS 栅极;', 10), (720, 584, u'EDL_EN1~5 去 C4-SITE1~5 的 TS5A3157。全是载板内部网络,不再过排针。', 10), (720, 560, u'空脚:U21.QE、U22.QA、U22.QE;U33.QH\' 是链尾。', 10), ] for a, b, c, e in W: assert (a == c) != (b == e), u'有一段导线不是横平竖直或零长度: %s' % ((a, b, c, e),) for v in (a, b, c, e): assert v % 5 == 0, u'导线坐标 %d 不是 5 的倍数: %s' % (v, (a, b, c, 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 坐标不是 5 的倍数' % (kind, it[1]) return W, F, PT, TX def main(): push = '--push' in sys.argv place = placement() if not push: print(u'元件 %d 个(未推送,加 --push)' % len(place)) return page = call(""" const info = await eda.dmt_Schematic.getSchematicInfo('%s'); for (const p of (info.page||[])) if (p.name==='%s') return p.uuid; const u = await eda.dmt_Schematic.createSchematicPage('%s'); await eda.dmt_Schematic.modifySchematicPageName(u,'%s'); return u; """ % (SCH, PNAME.lower(), SCH, PNAME)) print(u'页面 %s' % page) call(""" await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,900)); const n=(await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name; if (n!=='%s') 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, PNAME)) call(""" globalThis.__pl = {done: 0, err: null, n: %d}; (async () => { for (const [d, uuid, x, y, rot] of %s) { try { await eda.sch_PrimitiveComponent.create({libraryUuid: '%s', uuid}, x, y, undefined, rot, false, true, true); } catch (e) { globalThis.__pl.err = d + ': ' + String(e).slice(0, 150); } globalThis.__pl.done++; } })(); return 'fired'; """ % (len(place), json.dumps(place), LIB)) for _ in range(90): st = call("return globalThis.__pl;") if st['err']: raise SystemExit(u'放置失败: %s' % st['err']) if st['done'] >= st['n']: break time.sleep(2) else: raise SystemExit(u'放置超时 %d/%d' % (st['done'], st['n'])) named = call(""" const P = %s; let k = 0; const cs = (await eda.sch_PrimitiveComponent.getAll()).filter(c => c.getState_ComponentType() === 'part'); for (const [d, uuid, x, y] of P) for (const c of cs) { if (Math.round(c.getState_X()) !== x || Math.round(c.getState_Y()) !== y) continue; await c.toAsync().setState_Designator(d).done(); k++; break; } return k; """ % json.dumps([[d, u, x, y] for d, u, x, y, r in place])) if named != len(place): raise SystemExit(u'只认领到 %d/%d 个位号' % (named, 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(), Math.round(p.getState_X()), Math.round(p.getState_Y())]); } return out; """) pins = {k: {p: (x, y) for p, x, y in v} for k, v in raw.items()} 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], 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;") call("await eda.sch_Document.save(); return 1;") json.dump(NC, open(os.path.join(HERE, 'c6-nets-nc.json'), 'w', encoding='utf-8'), ensure_ascii=False, indent=1) print(u'%s: 元件 %d 导线 %d 旗 %d 端口 %d 文字 %d (%s)' % (PNAME, len(place), len(W), len(F), len(PT), len(TX), page)) if __name__ == '__main__': main()