R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-10-04 19:24:45 +08:00
共同撰写人 Claude Opus 5.5
父节点 cd01640c40
当前提交 cf7cc9cf90
共修改 759 个文件,包含 76962 行新增和 5299 行删除
+242
查看文件
@@ -0,0 +1,242 @@
# -*- 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()