文件
zhou yang和Claude Opus 5 2bfe308b30 初始导入:PhoneRail 五路手机直供电源板
原理图/PCB 已在 EasyEDA 里做到 12 页跨页 0 漏接,固件刚完成骨架包 A1。
这次是把已有的一切一次性纳入版本控制,没有改动任何设计内容。

纳入:
- 设计与需求文档、PROGRESS.md、小米8 参考原理图 PDF 与追脚脚本
- .claude/ 下的 EasyEDA 桥接工具链与几何/网表缓存(eda-parts.json、
  pcb-parts.json、pcb-pads.json、p2-ch1.json 等,是重画各页的唯一事实来源)
- 117 张验证截图(本项目所有方向问题都是靠人眼看截图发现的,属于证据不是垃圾)
- firmware/ 的 A1 骨架与 site/ 文档站源码

.gitignore 排除:构建产物(firmware/build、host/build、sdkconfig)、
site/node_modules、__pycache__、桥接日志 _bridge.log、脚本自标的 *.stale。

.gitattributes 默认 `* -text`:现有 .py/.json 大多是 CRLF,原样存,
不让 git 重写行尾。唯一例外是 *.sh 强制 LF —— 带 \r 的 shell 脚本 bash 跑不了。

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
2026-09-06 18:18:14 +08:00

231 行
11 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- 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()