R2 底板改 4 层(D15):规则 / 几何 / 背面摆位 / 4 层布线与铺铜,已下发
- 规则:铜层 2→4,差分 0.28/0.18,间距 0.15,过孔 0.3/0.2(rules_base) - 几何:98×98 板框 + 4×M3,槽阵居中,L1 12V 大铜 + 指头(pcb_base) - 摆位:五插槽留 L1,其余 133 件翻到 L4;背面镜像规则实测;Hub 放走廊 2、 东移 2.5 mm;扇出圈 / 小件间距 1.5(去耦例外)/ 9 A 过孔阵禁区(place_base) - 叠层按用户决定改为 L1 槽面 / L2 整层 +3V3 / L3 整层 GND / L4 器件面 - 布线:D± 七对按构造布通(USB3 掉头、USB1/2 北去掉头、USB4/5 东绕、 J23 桥),+3V3 / GND / +12V 平面网每脚一孔,0.125 网格 + PathFinder 开关(route_base、remote_route.ps1);剩 37 个网待手工补 - 铺铜:4 层重写,入口链自南向北切段 + 断面校核(pour_base); 已下发布线 / 缝合孔 / 铜面轮廓,填铜未成功(待查) - 工具:plot_base / plot_routes / shot_base,board_check 通孔两层都算 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
这个提交包含在:
@@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""底板 ESP32(`U20`)的 EFUSE_EN / PRSNT# 引脚**重新分配**(M9,2026-09-09 用户提出)。
|
||||
|
||||
/c/Python314/python .claude/r2/pinmap_base.py # 只打计划 + 读回现状
|
||||
/c/Python314/python .claude/r2/pinmap_base.py --push # 真改(删端口 → 重建)
|
||||
|
||||
## 为什么要换
|
||||
|
||||
槽阵转 90° 之后,五个槽沿 y 从南到北是 **1→5**,而 `U20` 上这两组脚的顺序**正好是反的**:
|
||||
|
||||
U20 东沿(南→北):EFUSE_EN5 EN4 EN3 EN2 EN1
|
||||
U20 南沿(西→东):PRSNT4# PRSNT3# PRSNT2# PRSNT1#
|
||||
|
||||
五根线因此互相全交叉,两层板上每个交叉都要打过孔换层 —— 而**这不是布线器的问题,
|
||||
是引脚分配的问题**,布线器只会报「出不来 / 没布通」。
|
||||
|
||||
## 为什么可以随便换(组内互换,零电气风险)
|
||||
|
||||
`EFUSE_EN*` 是普通 GPIO 输出、`PRSNT*#` 是普通 GPIO 输入,全部经 ESP32-S3 的
|
||||
GPIO matrix 出去,不挑脚。**组内互换**不碰 strapping(IO0/45/46)、不碰 ADC1
|
||||
(NTC 要的 IO1~IO10)、不碰 USB PHY(IO19/20)—— 换的只是固件里的编号。
|
||||
|
||||
⚠️⚠️ **`setState_Net` 对 netport 是坏的**(R1 `pinmap.py` 2026-09-06 实测):
|
||||
调用之后网名变空字符串、标签也没了,而**几何验证抓不到**。⇒ 只能删掉重建。
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, HERE)
|
||||
from eda import run # noqa: E402
|
||||
|
||||
PAGE = '38327f806bc13133' # A3-CTRL(r2-uuids.json)
|
||||
# 只动 `U20` 那一侧的端口:它们在 x=340 / 470;上拉那一侧在 x=145,不许动。
|
||||
U20_SIDE_X = (340, 470)
|
||||
SWAP = {
|
||||
'EFUSE_EN1': 'EFUSE_EN5', 'EFUSE_EN5': 'EFUSE_EN1',
|
||||
'EFUSE_EN2': 'EFUSE_EN4', 'EFUSE_EN4': 'EFUSE_EN2',
|
||||
'PRSNT1#': 'PRSNT4#', 'PRSNT4#': 'PRSNT1#',
|
||||
'PRSNT2#': 'PRSNT3#', 'PRSNT3#': 'PRSNT2#',
|
||||
}
|
||||
|
||||
JS_READ = u"""
|
||||
await eda.dmt_EditorControl.openDocument('%s');
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
const out = [];
|
||||
for (const c of await eda.sch_PrimitiveComponent.getAll()) {
|
||||
if (c.getState_ComponentType() !== 'netport') continue;
|
||||
let comp = ''; try { comp = (c.getState_Component() || {}).name || ''; } catch (e) {}
|
||||
out.push([c.getState_PrimitiveId(), String(c.getState_Net() || ''),
|
||||
c.getState_X(), c.getState_Y(), c.getState_Rotation
|
||||
? c.getState_Rotation() : (c.rotation || 0), comp]);
|
||||
}
|
||||
return out;
|
||||
"""
|
||||
|
||||
JS_APPLY = u"""
|
||||
await eda.dmt_EditorControl.openDocument('%s');
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
const DEL = %s, MK = %s;
|
||||
if (DEL.length) await eda.sch_PrimitiveComponent.delete(DEL);
|
||||
for (const p of MK) await eda.sch_PrimitiveComponent.createNetPort(p[0], p[1], p[2], p[3], p[4], false);
|
||||
await eda.sch_Document.save();
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
return {del: DEL.length, mk: MK.length};
|
||||
"""
|
||||
|
||||
|
||||
def read():
|
||||
r = run(JS_READ % PAGE, timeout=180)
|
||||
assert r.get('success'), json.dumps(r, ensure_ascii=False)[:400]
|
||||
return r['result']
|
||||
|
||||
|
||||
def plan(ports):
|
||||
out = []
|
||||
for pid, net, x, y, rot, comp in ports:
|
||||
if net not in SWAP:
|
||||
continue
|
||||
if round(x) not in U20_SIDE_X:
|
||||
continue # 上拉那一侧不动
|
||||
out.append((pid, net, SWAP[net], x, y, rot, comp))
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
ports = read()
|
||||
pl = plan(ports)
|
||||
print(u'A3-CTRL 上端口 %d 个;要改 %d 个(只动 U20 侧 x=%s)'
|
||||
% (len(ports), len(pl), '/'.join(str(v) for v in U20_SIDE_X)))
|
||||
for pid, old, new, x, y, rot, comp in sorted(pl, key=lambda t: t[1]):
|
||||
print(u' (%4d,%4d) %-11s → %-11s [%s]' % (x, y, old, new, comp or '?'))
|
||||
if '--push' not in sys.argv:
|
||||
print(u'(没加 --push,没有下发)')
|
||||
return
|
||||
dele = [t[0] for t in pl]
|
||||
mk = [['BI' if (t[6] or '').endswith('BI') else ('IN' if (t[6] or '').endswith('IN') else 'OUT'),
|
||||
t[2], t[3], t[4], t[5]] for t in pl]
|
||||
r = run(JS_APPLY % (PAGE, json.dumps(dele), json.dumps(mk, ensure_ascii=False)), timeout=600)
|
||||
assert r.get('success'), json.dumps(r, ensure_ascii=False)[:500]
|
||||
print(u' 删端口 %d / 重建 %d' % (r['result']['del'], r['result']['mk']))
|
||||
# 读回校验
|
||||
now = {(round(x), round(y)): net for _p, net, x, y, _r, _c in read()}
|
||||
bad = []
|
||||
for _pid, old, new, x, y, _rot, _c in pl:
|
||||
got = now.get((round(x), round(y)))
|
||||
if got != new:
|
||||
bad.append(u'(%d,%d) 应为 %s,读回 %s' % (x, y, new, got))
|
||||
print(u'[%s] 读回校验%s' % ('OK' if not bad else 'X',
|
||||
u'逐个对上' if not bad else u' %d 条不过: %s' % (len(bad), bad[:4])))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户