原理图/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
304 行
13 KiB
Python
304 行
13 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""P5-HARNESS 整页模型 + 下发。
|
||
|
||
这是一页**文档页**:只有【B】手机侧电容组是真电路(C801~C806,离板,
|
||
addIntoBom=false / addIntoPcb=false),其余全是非电气图元
|
||
(sch_PrimitiveRectangle / sch_PrimitivePolygon / sch_PrimitiveText)。
|
||
|
||
用法: /c/Python314/python .claude/p5_harness.py [--push]
|
||
不带 --push 只跑本地几何自检。元件已经放好,本脚本不动元件。
|
||
"""
|
||
import json, os, sys
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, HERE)
|
||
PAGE = '58bacf3ddd60ed4e'
|
||
|
||
# 元件引脚(getAllPins 读回,勿手改)
|
||
PINS = {
|
||
'C801': {'1': (100, 350), '2': (100, 310)}, # 2200uF/10V 铝电解,pin1=+
|
||
'C802': {'1': (172, 345), '2': (172, 315)},
|
||
'C803': {'1': (244, 345), '2': (244, 315)},
|
||
'C804': {'1': (316, 345), '2': (316, 315)},
|
||
'C805': {'1': (388, 345), '2': (388, 315)},
|
||
'C806': {'1': (460, 350), '2': (460, 310)},
|
||
}
|
||
|
||
WIRES, FLAGS, PORTS, TEXTS, POLYS, RECTS = [], [], [], [], [], []
|
||
|
||
|
||
def w(a, b):
|
||
WIRES.append((a, b))
|
||
|
||
|
||
def gnd(x, y, rot=0):
|
||
FLAGS.append(('Ground', 'GND', x, y, rot))
|
||
|
||
|
||
def port(net, x, y, rot, d='IN'):
|
||
PORTS.append((d, net, x, y, rot))
|
||
|
||
|
||
def T(x, y, s, size=10, bold=False, color=None):
|
||
TEXTS.append((x, y, s, size, bold, color))
|
||
|
||
|
||
def PL(pts, color=None, lw=2, lt=0):
|
||
POLYS.append((pts, color, lw, lt))
|
||
|
||
|
||
def R(x, y, wd, ht, color='#999999'):
|
||
RECTS.append((x, y, wd, ht, color))
|
||
|
||
|
||
BLUE, RED, GRAY = '#0055AA', '#CC0000', '#666666'
|
||
ORANGE, GREEN, DK = '#CC6600', '#009944', '#333333'
|
||
|
||
# ============================ 标题 ============================
|
||
T(30, 806, u'【P5-HARNESS】手机侧线束与接点 —— 每台手机一套,五台完全相同',
|
||
13, True, '#000000')
|
||
T(560, 806, u'本页器件全部离板:不进 BOM、不上 PCB(addIntoBom / addIntoPcb 均为 false)',
|
||
10, False, RED)
|
||
|
||
# =============== 【A】三条线束(板端 → 手机端)================
|
||
BOARD_X, PHONE_X, CABLE_L, CABLE_R = 40, 700, 270, 700
|
||
|
||
|
||
def band(top, height, bhead, brows, phead, prows, cnotes, colors):
|
||
u"""一条线束:左框(板端) + 右框(手机端) + 中间缆线 + 缆线标注。"""
|
||
R(BOARD_X, top, 230, height)
|
||
R(PHONE_X, top, 440, height)
|
||
T(BOARD_X + 10, top - 13, bhead, 11, True, BLUE)
|
||
T(PHONE_X + 12, top - 13, phead, 11, True, BLUE)
|
||
for x, y, s, sz, c in cnotes:
|
||
T(x, y, s, sz, False, c)
|
||
y = top - 35
|
||
for i, (b, p) in enumerate(zip(brows, prows)):
|
||
T(BOARD_X + 12, y, b, 10)
|
||
T(PHONE_X + 12, y, p, 10)
|
||
PL([CABLE_L, y, CABLE_R, y], colors[i], 3)
|
||
y -= 20
|
||
|
||
|
||
# 电源线束
|
||
band(790, 68,
|
||
u'【电源】J2 Micro-Fit 3.0 2P',
|
||
[u'1 VO1_P', u'2 GND'],
|
||
u'电池排线(原厂 BM3E,保留 NTC / ID)',
|
||
[u'P+ 焊点 -> J600 pin 7/8 与 G3/G4 (VBATT)',
|
||
u'P- 焊点 -> J600 pin 1/2 与 G1/G2 (GND)'],
|
||
[(300, 777, u'18 AWG 双绞 · 不超过 20 cm · 红=正 黑=负', 10, None),
|
||
(510, 777, u'手机端并接【B】电容组', 10, ORANGE)],
|
||
[RED, DK])
|
||
|
||
# 侧键线束
|
||
band(712, 112,
|
||
u'【侧键】J7 1.25 mm 4P',
|
||
[u'1 K1_PWR', u'2 K1_VUP', u'3 K1_VDN', u'4 GND'],
|
||
u'侧键触点(主板已串 1 kΩ,对地短接即可)',
|
||
[u'J603 电源键 PHONE_ON_N',
|
||
u'J601 音量 + VOL_UP_N',
|
||
u'J602 音量 − PM_RESIN_N',
|
||
u'J604 公共地 GND(三键共用)'],
|
||
[(300, 699, u'AWG 26~28 细线 · 远离电源线', 10, None),
|
||
(500, 699, u'触点需先用万用表定位(§13.3)', 10, None)],
|
||
['#0066CC', '#0066CC', '#0066CC', DK])
|
||
|
||
# USB 线束
|
||
band(592, 112,
|
||
u'【USB】J17 1.25 mm 4P',
|
||
[u'1 VBUS1', u'2 USB1_DM', u'3 USB1_DP', u'4 GND'],
|
||
u'原厂尾插小板 USB-C(CC / ESD / 口温 NTC 都在其上)',
|
||
[u'USB-C VBUS <- 板上 U24 通断,默认断开',
|
||
u'USB-C D−',
|
||
u'USB-C D+',
|
||
u'USB-C GND'],
|
||
[(300, 579, u'USB 线改端子 · D± 保持双绞', 10, None),
|
||
(500, 579, u'上游只接电脑 / Hub,恒 5V', 10, None)],
|
||
[ORANGE, GREEN, GREEN, DK])
|
||
|
||
# ========== 【B】手机侧电容组(本页唯一的真电路)==========
|
||
RAIL_T, RAIL_B, RAIL_L, RAIL_R = 390, 265, 60, 490
|
||
T(30, 448, u'【B】手机侧电容组 —— 装在排线 P+ / P− 焊点处', 12, True, BLUE)
|
||
T(200, 400, u'上母线 = 排线 P+ 焊点(VO1_P) · 下母线 = 排线 P− 焊点(GND)', 9, False, GRAY)
|
||
T(150, 425, u'图中为 CH1;CH2~CH5 各一套相同、未画出,位号见【C】表', 9, False, GRAY)
|
||
|
||
w((RAIL_L, RAIL_T), (RAIL_R, RAIL_T)) # 上母线 = VO1_P(手机端 P+)
|
||
w((RAIL_L, RAIL_B), (RAIL_R, RAIL_B)) # 下母线 = GND (手机端 P−)
|
||
w((RAIL_L, RAIL_T), (RAIL_L, 420))
|
||
port('VO1_P', RAIL_L, 420, 180, 'IN')
|
||
w((RAIL_R, RAIL_B), (RAIL_R, 245)) # 地旗放右端:放左端时 GND 标签会压到【D】标题
|
||
gnd(RAIL_R, 245, 0)
|
||
for _d in ('C801', 'C802', 'C803', 'C804', 'C805', 'C806'):
|
||
_px, _p1y = PINS[_d]['1']
|
||
_p2y = PINS[_d]['2'][1]
|
||
w((_px, _p1y), (_px, RAIL_T))
|
||
w((_px, _p2y), (_px, RAIL_B))
|
||
|
||
for _x, _s in ((72, u'主储能 / 阻尼'), (158, u'中频'), (230, u'中频'),
|
||
(302, u'高频'), (374, u'高频'), (440, u'甚高频')):
|
||
T(_x, 248, _s, 9, False, GRAY)
|
||
T(30, 232, u'C801 2200µF/10V 铝电解 C49256788(普通铝电解,ESR 数十 mΩ = 阻尼源)', 9)
|
||
T(30, 216, u'C802/803 100µF/6.3V 1206 C15008 · C804/805 22µF/25V 1206 C12891'
|
||
u' · C806 100nF 0402 C1525', 9)
|
||
|
||
# ================== 【C】五路对照表 ==================
|
||
CX = [555, 620, 690, 760, 830, 905, 975]
|
||
T(550, 448, u'【C】五路对照表', 12, True, BLUE)
|
||
for _x, _s in zip(CX, [u'CH', u'电源座', u'按键座', u'USB 座',
|
||
u'输出网络', u'INA226', u'电容组位号']):
|
||
T(_x, 425, _s, 10, True)
|
||
PL([550, 413, 1135, 413], '#888888', 1)
|
||
ROWS = [
|
||
(u'CH1', 'J2', 'J7', 'J17', 'VO1_P', '0x40', 'C801~C806'),
|
||
(u'CH2', 'J3', 'J8', 'J18', 'VO2_P', '0x41', 'C811~C816'),
|
||
(u'CH3', 'J4', 'J9', 'J19', 'VO3_P', '0x44', 'C821~C826'),
|
||
(u'CH4', 'J5', 'J10', 'J20', 'VO4_P', '0x45', 'C831~C836'),
|
||
(u'CH5', 'J6', 'J11', 'J21', 'VO5_P', '0x42', 'C841~C846'),
|
||
]
|
||
_yy = 393
|
||
for _row in ROWS:
|
||
for _x, _cell in zip(CX, _row):
|
||
T(_x, _yy, _cell, 10)
|
||
_yy -= 21
|
||
PL([550, 285, 1135, 285], '#888888', 1)
|
||
T(550, 268, u'⚠ J600 的 G3 / G4 固定脚实际是 VBATT,不是地;1/2 + G1/G2 才是 GND。',
|
||
10, False, RED)
|
||
T(550, 250, u'BATT_ID (pin 3) / BATT_THERM (pin 4) 由原厂排线提供,缺任一条可能拒绝开机。', 10)
|
||
T(550, 232, u'五路的地在手机端各自独立,不要并成一根 —— 会把射频突发窜到其他四台。', 10)
|
||
T(550, 214, u'INA226 地址不等于通道号,固件必须查表(§8.2)。', 10)
|
||
|
||
# ================== 【D】动手前必读 ==================
|
||
D = [
|
||
(u'【D】动手前必读', 11, True, BLUE),
|
||
(u'⚠ 电源线焊在排线的 P+ / P−(保护板输出侧),不是 B+ / B−。', 10, False, RED),
|
||
(u' 焊错常温下完全正常,只有过压时才暴露(保护板抢先动作)。', 10, False, None),
|
||
(u' 判别:BTB pin 7/8 对候选正极 ≈0Ω 者为 P+;pin 1/2 对候选负极者为 P−。',
|
||
10, False, None),
|
||
(u' 升到 4.40V 时 BTB 座电压必须跟随且不断开 —— 唯一的判定性测试(§13.7)。',
|
||
10, False, None),
|
||
(u'⚠ 大电容必须装在排线端。装在电源板上隔着 300 nH 线缆电感,基本无效。',
|
||
10, False, RED),
|
||
(u'⚠ C801 必须用普通铝电解(ESR 40~100 mΩ)做阻尼。', 10, False, RED),
|
||
(u' 换成同尺寸的聚合物版(如 C53446724,ESR 12 mΩ),Q 会从 0.19 回到 4.5,',
|
||
10, False, None),
|
||
(u' 线缆 LC 在 6.2 kHz 上振铃。(§6.2)', 10, False, None),
|
||
(u'⚠ 三条线束的地在手机端不要并成一根,各自回本路的板端地。(§15.4)',
|
||
10, False, RED),
|
||
(u'C801~C806 不进 BOM、不上 PCB;线束与电容组每台一套,五台共 5 套。', 10, False, None),
|
||
]
|
||
_yy = 193
|
||
for _s, _sz, _bold, _col in D:
|
||
T(30, _yy, _s, _sz, _bold, _col)
|
||
_yy -= 17
|
||
|
||
|
||
# ======================== 本地自检 ========================
|
||
def _on(p, a, b, tol=0.6):
|
||
(px, py), (ax, ay), (bx, by) = p, a, b
|
||
if abs(ax - bx) < tol:
|
||
return abs(px - ax) < tol and min(ay, by) - tol <= py <= max(ay, by) + tol
|
||
if abs(ay - by) < tol:
|
||
return abs(py - ay) < tol and min(ax, bx) - tol <= px <= max(ax, bx) + tol
|
||
return False
|
||
|
||
|
||
def selfcheck():
|
||
errs = []
|
||
for a, b in WIRES:
|
||
if a == b:
|
||
errs.append(u'零长度导线 %s' % (a,))
|
||
marks = [(x, y) for _, _, x, y, _ in FLAGS] + [(x, y) for _, _, x, y, _ in PORTS]
|
||
for x, y in marks:
|
||
if x % 5 or y % 5:
|
||
errs.append(u'旗/端口坐标未对齐到 5 的倍数: (%s,%s)' % (x, y))
|
||
pts = [(d + '.' + p, xy) for d, ps in PINS.items() for p, xy in ps.items()]
|
||
pts += [(u'旗/端口', m) for m in marks]
|
||
for name, p in pts:
|
||
if not any(_on(p, a, b) for a, b in WIRES):
|
||
errs.append(u'悬空: %s @ %s' % (name, p))
|
||
# 图元不越界(可用区 x 20~1145 / y 10~812;标题栏 x>455 且 y<192)
|
||
for x, y, s, sz, _b, _c in TEXTS:
|
||
wd = sum(11 if ord(ch) > 0x2000 else 5.6 for ch in s) * sz / 11.0
|
||
if x < 20 or x + wd > 1145 or y < 10 or y > 812:
|
||
errs.append(u'文字越界 (%d,%d,+%d): %s' % (x, y, wd, s[:26]))
|
||
if x + wd > 455 and y < 195:
|
||
errs.append(u'压标题栏 (%d,%d,+%d): %s' % (x, y, wd, s[:26]))
|
||
return errs
|
||
|
||
|
||
if __name__ == '__main__':
|
||
_errs = selfcheck()
|
||
print(u'导线 %d 旗 %d 端口 %d 文字 %d 折线 %d 矩形 %d'
|
||
% (len(WIRES), len(FLAGS), len(PORTS), len(TEXTS), len(POLYS), len(RECTS)))
|
||
if _errs:
|
||
print(u'\n!! %d 个问题:' % len(_errs))
|
||
for _e in _errs:
|
||
print(' ', _e)
|
||
sys.exit(1)
|
||
print(u'本地自检通过')
|
||
if '--push' not in sys.argv:
|
||
sys.exit(0)
|
||
|
||
from eda import run
|
||
OPEN = "await eda.dmt_EditorControl.openDocument('%s');" % PAGE
|
||
|
||
def call(body, timeout=180):
|
||
r = run(OPEN + body, timeout=timeout)
|
||
if not r.get('success'):
|
||
print(u'推送失败:', json.dumps(r, ensure_ascii=False)[:500])
|
||
sys.exit(1)
|
||
return r['result']
|
||
|
||
print(u'\n清空导线/旗/端口/文字/折线/矩形(元件保留)...')
|
||
print(call("""
|
||
for (const x of await eda.sch_PrimitiveWire.getAll()) await eda.sch_PrimitiveWire.delete(x.getState_PrimitiveId());
|
||
for (const x of await eda.sch_PrimitiveText.getAll()) await eda.sch_PrimitiveText.delete(x.getState_PrimitiveId());
|
||
for (const x of await eda.sch_PrimitivePolygon.getAll()) await eda.sch_PrimitivePolygon.delete(x.getState_PrimitiveId());
|
||
for (const x of await eda.sch_PrimitiveRectangle.getAll()) await eda.sch_PrimitiveRectangle.delete(x.getState_PrimitiveId());
|
||
for (const c of await eda.sch_PrimitiveComponent.getAll())
|
||
if (['netflag','netport'].includes(c.getState_ComponentType()))
|
||
await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId());
|
||
return (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
|
||
"""))
|
||
|
||
def chunk(items, n):
|
||
for k in range(0, len(items), n):
|
||
yield items[k:k + n]
|
||
|
||
_n = 0
|
||
for _part in chunk([[a[0], a[1], b[0], b[1]] for a, b in WIRES], 40):
|
||
_n += call("for (const s of %s) await eda.sch_PrimitiveWire.create([s[0],s[1],s[2],s[3]]); return %d;"
|
||
% (json.dumps(_part), len(_part)))
|
||
print(u'导线 %d 段' % _n)
|
||
|
||
_n = 0
|
||
for _part in chunk([list(f) for f in FLAGS], 25):
|
||
_n += call("for (const f of %s) await eda.sch_PrimitiveComponent.createNetFlag(f[0],f[1],f[2],f[3],f[4],false); return %d;"
|
||
% (json.dumps(_part), len(_part)))
|
||
print(u'旗 %d 个' % _n)
|
||
|
||
_n = 0
|
||
for _part in chunk([list(p) for p in PORTS], 25):
|
||
_n += call("for (const p of %s) await eda.sch_PrimitiveComponent.createNetPort(p[0],p[1],p[2],p[3],p[4],false); return %d;"
|
||
% (json.dumps(_part), len(_part)))
|
||
print(u'端口 %d 个' % _n)
|
||
|
||
_n = 0
|
||
for _part in chunk([list(r) for r in RECTS], 20):
|
||
_n += call("for (const r of %s) await eda.sch_PrimitiveRectangle.create(r[0],r[1],r[2],r[3],0,0,r[4],null,1,0,0); return %d;"
|
||
% (json.dumps(_part), len(_part)))
|
||
print(u'矩形 %d 个' % _n)
|
||
|
||
_n = 0
|
||
for _part in chunk([list(p) for p in POLYS], 20):
|
||
_n += call("for (const p of %s) await eda.sch_PrimitivePolygon.create(p[0],p[1],null,p[2],p[3]); return %d;"
|
||
% (json.dumps(_part), len(_part)))
|
||
print(u'折线 %d 条' % _n)
|
||
|
||
_n = 0
|
||
for _part in chunk([list(t) for t in TEXTS], 18):
|
||
_n += call("for (const t of %s) await eda.sch_PrimitiveText.create(t[0],t[1],t[2],0,t[5],null,t[3],t[4]); return %d;"
|
||
% (json.dumps(_part), len(_part)))
|
||
print(u'文字 %d 条' % _n)
|