初始导入: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
这个提交包含在:
zhou yang
2026-09-06 18:18:14 +08:00
共同撰写人 Claude Opus 5
当前提交 2bfe308b30
共修改 526 个文件,包含 80633 行新增和 0 行删除
+148
查看文件
@@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
"""P2 五路母版生成器。
几何来自 `p2-ch1.json`(CH1 页的回读快照),本文件负责按通道号改写:
1. 位号百位 R/C/Q/L/Rs 1xx -> nxx;U1->Un、U8->U(7+n)、U14->U(13+n);J2->J(1+n)、J7->J(6+n)
2. 网络名 VOn / VDACn / EFUSE_ENn / EFUSE_FLTn / EFUSE_PGn / Kn_PWR|VUP|VDN
(GND / +12V / +3V3 / SDA / SCL / BUCK_PG / INA_ALERT 五路共用,不加后缀)
3. INA226 地址跳线(A1/A0 的去向)
⚠️ 端口的 dir(IN/OUT/BI)**读不回来**(API 里没有对应的 getState_),
而 dir 决定标签画在引脚左边还是右边(见 easyeda-sch-geometry §8b),
所以 PORTS 是显式声明的,不是从页面回读的。
"""
import io, json, os
HERE = os.path.dirname(os.path.abspath(__file__))
SNAP = json.load(io.open(os.path.join(HERE, 'p2-ch1.json'), encoding='utf-8'))
# ── 端口:显式声明 (net, x, y, rotation, dir) ──────────────────────────
# 标签朝向 = 远离导线来的方向。BI/OUT: rot0→右, rot180→左;IN 正好相反。
PORTS = [
('BUCK_PG', 370, 760, 0, 'OUT'), # 标签在右
('VO1', 465, 705, 0, 'BI'), # 标签在右
('VO1', 180, 385, 0, 'IN'), # 标签在左(页面左边缘)
('VDAC1', 420, 340, 180, 'IN'), # 标签在右;挪到向上的支线末端,避开 K1_PWR
('EFUSE_EN1', 530, 600, 0, 'IN'), # 标签在左
('EFUSE_FLT1', 600, 375, 0, 'OUT'), # 标签在右
('EFUSE_PG1', 760, 530, 180, 'OUT'), # 标签在左(右边是 R115)
('INA_ALERT', 700, 460, 180, 'OUT'), # 标签在左
('SDA', 700, 400, 180, 'BI'), # 标签在左
('SCL', 750, 395, 0, 'BI'), # 标签在右
('EFUSE_EN1', 1055, 445, 0, 'IN'), # 标签在左
('K1_PWR', 510, 340, 180, 'IN'), # 标签在右
('K1_VUP', 700, 340, 180, 'IN'), # 标签在右
('K1_VDN', 860, 340, 180, 'IN'), # 标签在右
('VO1_P', 960, 745, 0, 'OUT'), # 标签在右;给 P5-HARNESS 的手机侧电容组用
]
# VO1_P 原来只是页内的一段无名网络(Rs101 -> J2 / Q102 / U8.VIN-)。
# P5-HARNESS 上的手机侧电容组要挂在这个节点上,跨页就必须有名字,
# 所以从 y=695 那根横线上引一段 50 长的支线,末端放 VO1_P 端口(§8a:不能直接压在引脚上)。
VOP_STUB = [((960, 695), (960, 745))]
# 三个按键端口改走「向上 40 的短支线」(原来直接压在栅极电阻的引脚上)。
# 原因有两个:① K1_VUP 的端口引脚和 R122.1 是同一个坐标,靠一段**零长度导线**"连着",
# 几何验证会报悬空——真按这个网表打样,那一路音量+ 可能根本没连上;
# ② 三个标签原来朝左,正好压在前一级 MOS 的符号上。
# 支线末端 y=340 这一行 x=625/660/820/980 有竖线穿过,所以四个标签都朝右,
# 各自落在 420-500 / 510-590 / 700-780 / 860-940 四段空隙里。
KEY_STUB_DROP = [((560, 300), (580, 300)), ((900, 300), (905, 300))]
KEY_STUB_ADD = [((510, 300), (580, 300)), ((510, 300), (510, 340)),
((700, 300), (740, 300)), ((700, 300), (700, 340)),
((860, 300), (900, 300)), ((860, 300), (860, 340))]
# J7 那个地旗原来在 (1010,330),它的 "GND" 文字正好压在 Q105 的位号 + 型号上
# (渲染出来是 "G5)5" 这种看不懂的东西)。往下再引 40,落到空行里。
# 往上 / 往左都走不通:x=980 那根竖线(VDN 漏极去 J7)和 y=360/370/380 三根按键线把路堵死了。
J7_GND_DROP = ('GND', 1010, 330, 0)
J7_GND_ADD_WIRE = ((1010, 330), (1010, 290))
J7_GND_ADD_FLAG = ('GND', 1010, 290, 0)
# ── INA226 地址跳线 ───────────────────────────────────────────────────
# A1 支线终点 (700,495),A0 支线终点 (660,490)。两条支线都从下方接进旗。
# 地址表:A1=GND 时 A0=GND/VS/SDA/SCL -> 0x40/0x41/0x42/0x43
# A1=VS 时 -> 0x44/0x45/0x46/0x47
# 只有 CH5 需要 A0 接 SDA(A0 走 (690,470)->(690,450)->(700,450) 搭到 SDA 线上,0 交叉)。
ADDR = { # n: (A1 去向, A0 去向, 地址, 说明)
1: ('GND', 'GND', 0x40, 'A1=A0=GND -> 0x40'),
2: ('GND', '+3V3', 0x41, 'A1=GND A0=VS -> 0x41'),
3: ('+3V3', 'GND', 0x44, 'A1=VS A0=GND -> 0x44'),
4: ('+3V3', '+3V3', 0x45, 'A1=A0=VS -> 0x45'),
5: ('GND', 'SDA', 0x42, 'A1=GND A0=SDA -> 0x42'),
}
PAGE_SUFFIX = {1: '', 2: 'B', 3: 'C', 4: 'D', 5: 'E'}
def des_map(n):
"""CH1 位号 -> CHn 位号。"""
m = {'U1': 'U%d' % n, 'U8': 'U%d' % (7 + n), 'U14': 'U%d' % (13 + n),
'J2': 'J%d' % (1 + n), 'J7': 'J%d' % (6 + n)}
for d, _mfr, _sub, _x, _y, _r, _mir in SNAP['parts']:
if d in m:
continue
pre = 'Rs' if d.startswith('Rs') else d[0]
num = d[len(pre):]
assert num.isdigit() and len(num) == 3 and num[0] == '1', d
m[d] = '%s%d%s' % (pre, n, num[1:])
return m
def net_map(n):
m = {}
for base in ('VO1', 'VO1_F', 'VO1_P', 'VDAC1', 'EFUSE_EN1', 'EFUSE_FLT1', 'EFUSE_PG1'):
m[base] = base[:-1] + str(n) if base[-1] == '1' else base.replace('1_', '%d_' % n)
m['VO1_F'] = 'VO%d_F' % n
m['VO1_P'] = 'VO%d_P' % n
for k in ('PWR', 'VUP', 'VDN'):
m['K1_' + k] = 'K%d_%s' % (n, k)
return m
def channel(n):
"""返回 CHn 的完整图页内容。"""
nm = net_map(n)
a1, a0, addr, addr_txt = ADDR[n]
drop = {tuple(sorted(w)) for w in KEY_STUB_DROP}
wires = [tuple(map(tuple, w)) for w in SNAP['wires'] if tuple(sorted(map(tuple, w))) not in drop]
assert len(wires) == len(SNAP['wires']) - len(drop), '按键支线的旧导线没删干净'
wires += KEY_STUB_ADD + [J7_GND_ADD_WIRE] + VOP_STUB
flags = [tuple(f) for f in SNAP['flags'] if tuple(f) != J7_GND_DROP]
assert len(flags) == len(SNAP['flags']) - 1, 'J7 地旗没找到'
flags.append(J7_GND_ADD_FLAG)
# 地址跳线:A1 一律往上引到 (700,495)
wires += [((770, 480), (700, 480)), ((700, 480), (700, 495))]
flags.append((a1, 700, 495, 180 if a1 == 'GND' else 0))
if a0 == 'SDA': # 直接搭到 SDA 线上,不用旗
wires += [((770, 470), (690, 470)), ((690, 470), (690, 450)), ((690, 450), (700, 450))]
else:
wires += [((770, 470), (660, 470)), ((660, 470), (660, 490))]
flags.append((a0, 660, 490, 180 if a0 == 'GND' else 0))
ports = [(nm.get(net, net), x, y, rot, d) for net, x, y, rot, d in PORTS]
title = ('P2 CH1 单路通道(五路母版:复制后只改位号 / 网络名 / INA226 地址)' if n == 1 else
'P2%s CH%d 单路通道(与 CH1 完全相同,仅位号 / 网络名 / INA226 地址不同)'
% (PAGE_SUFFIX[n], n))
texts = [
(title, 185, 785, 16),
('TPS54622: Vref 0.6V, fsw 480kHz(R%d03=100k), Vout = 4.222 - 0.2813*Vd' % n, 60, 185, 11),
('TPS25940A: OVP 4.36V(R%d09/R%d10), ILIM 4.45A(R%d11), dVdT 33nF -> 0.36V/ms' % (n, n, n), 60, 165, 11),
('缓启动 11.5ms / 涌流 0.8A ; INA226 CH%d: %s' % (n, addr_txt), 60, 145, 11),
('放电互锁: EN 低 -> Q%d01 截止 -> Q%d02 导通 -> R%d19 10R 泄放手机侧 2200uF' % (n, n, n), 60, 125, 11),
]
return {'wires': wires, 'flags': flags, 'ports': ports, 'texts': texts,
'des': des_map(n), 'addr': addr,
'page': 'P2-CHANNEL' if n == 1 else 'P2%s-CH%d' % (PAGE_SUFFIX[n], n)}
if __name__ == '__main__':
for n in range(1, 6):
c = channel(n)
print('%-10s 导线 %3d 旗 %2d 端口 %2d 文字 %d INA226 0x%02X'
% (c['page'], len(c['wires']), len(c['flags']), len(c['ports']), len(c['texts']), c['addr']))
m = des_map(3)
print('CH3 位号样例:', {k: m[k] for k in ('U1', 'U8', 'U14', 'J2', 'J7', 'R101', 'Rs101', 'C114', 'Q105', 'L101')})
print('CH3 网络样例:', net_map(3))