原理图/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
231 行
11 KiB
Python
231 行
11 KiB
Python
# -*- 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()
|