# -*- 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()