文件
phonerail/.claude/emc_budget.py

194 行
10 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""PhoneRail 的 EMC / PDN 设计预算 —— 用 emc_formulas.py 把设计文档里的
参数换算成**布局判据**(最大热环路面积、PDN 目标阻抗、4A 线宽压降)。
/c/Python314/python .claude/emc_budget.py
数据来源:`五路直供电源板设计.md` §3(12V→3.5~4.2V / 4A / 480kHz / 4.7uH)、
§8.4(五路**不错相**)、§10.1(4 层,外层 1oz / 内层 0.5oz)。
emc_formulas.py 摘自 aklofas/kicad-happy(MIT, Copyright (c) 2025 Andrew Klofas),
已去掉对 KiCad 侧的依赖,可独立运行。
⚠️ 这里算的是**预算**,不是对现有布局的审计 —— 它给出"热环路不许超过多少 mm²",
拿到实际摆位之后再跟三角形面积(输入电容 → Buck → 电感)对一次才算闭环。
"""
from __future__ import print_function
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import emc_formulas as F
# ---------------------------------------------------------------------------
# 设计常数(改这里,不要改下面的计算)
# ---------------------------------------------------------------------------
VIN = 12.0 # V 输入
VOUT_MAX = 4.2 # V 输出上限(§4.3 第一层限幅)
IOUT = 4.0 # A 每路满载
DELTA_IL = 1.16 # A 电感纹波峰峰(§3.2)
FSW = 480e3 # Hz 开关频率(§3,五路统一)
N_CH = 5 # 路数
PHASE_STAGGER = False # §8.4 已否决错相 —— 五路同相,谐波电流相干叠加
# SW 节点 10-90% 上升时间。TPS54622 手册没给,实测前这是最大的不确定源,
# 所以下面按 2 / 5 / 10 ns 三档都算一遍。
TR_CANDIDATES = (2e-9, 5e-9, 10e-9)
TR_NOMINAL = 5e-9
DIST_M = 3.0 # m FCC Part 15 Class B 辐射测试距离
STANDARD = 'fcc-class-b'
MARGIN_DB = 6.0 # dB 设计裕量
CU_OUTER_MM = 0.035 # 1 oz 外层铜厚(§10.1)
CU_INNER_MM = 0.0175 # 0.5 oz 内层
# 手机自身的接收频段 —— 手机农场特有的关注点:本板的开关谐波落进手机自己的
# 接收带,测不出 EMC 不合规,但会抬高手机底噪、掉信号格。
PHONE_BANDS = [
(u'GSM900 RX', 925e6, 960e6),
(u'GPS L1', 1574.0e6, 1577.0e6),
(u'GSM1800 RX',1805e6, 1880e6),
(u'LTE B1 RX', 2110e6, 2170e6),
(u'WiFi 2.4G', 2400e6, 2483.5e6),
]
def hr(title):
print(u'\n' + u'=' * 72)
print(title)
print(u'=' * 72)
def main():
duty = VOUT_MAX / VIN
ipk = IOUT + DELTA_IL / 2.0 # 输入回路方波的峰值电流
n_coherent = 1 if PHASE_STAGGER else N_CH
hr(u'0. 工况')
print(u' Vin=%.1fV Vout=%.2fV Iout=%.1fA/路 D=%.3f fsw=%.0f kHz'
% (VIN, VOUT_MAX, IOUT, duty, FSW / 1e3))
print(u' 热环路方波峰值电流 Ipk = Iout + ΔIL/2 = %.2f A' % ipk)
print(u' 五路%s → 谐波电流按 %d 路相干叠加'
% (u'错相' if PHASE_STAGGER else u'**同相**(§8.4 已否决错相)', n_coherent))
# -----------------------------------------------------------------
hr(u'1. 谐波包络的两个拐点(上升时间是主导不确定源)')
print(u' %-10s %-14s %-14s' % (u'tr', u'f1=1/(πτ)', u'f2=1/(π·tr)'))
for tr in TR_CANDIDATES:
f1, f2 = F.trapezoidal_corner_frequencies(duty, tr, FSW)
mark = u' ← 标称' if tr == TR_NOMINAL else u''
print(u' %-10s %-14s %-14s%s'
% (u'%.0f ns' % (tr * 1e9), u'%.2f MHz' % (f1 / 1e6),
u'%.1f MHz' % (f2 / 1e6), mark))
print(u'\n f2 以上按 -40 dB/dec 滚降。tr 每减半,f2 翻倍,'
u'高频谐波幅度约 ×4 —— 所以 SW 节点的上升沿是 EMI 的总开关。')
# -----------------------------------------------------------------
hr(u'2. 热环路面积预算(输入电容 → Buck → 电感 的三角形)')
print(u' 判据:在 30 MHz ~ 1 GHz 每个谐波上,差模环路辐射要低于 '
u'%s @ %.0fm,留 %.0f dB 裕量。' % (STANDARD, DIST_M, MARGIN_DB))
print(u' 取所有谐波里**最紧的那一个**作为预算。\n')
for tr in TR_CANDIDATES:
worst_area = None
worst_f = None
n = 1
while True:
f = n * FSW
if f > 1e9:
break
if f >= 30e6:
lim = F.get_emission_limit(f, STANDARD)
if lim:
i_n = F.trapezoidal_harmonic_amplitude(n, ipk, duty, tr, FSW)
i_n_total = i_n * n_coherent
if i_n_total > 1e-9:
a = F.dm_max_loop_area_m2(f, i_n_total, lim[0], DIST_M,
margin_db=MARGIN_DB,
ground_plane=True)
if worst_area is None or a < worst_area:
worst_area, worst_f, worst_i = a, f, i_n_total
worst_lim = lim[0]
n += 1
# ⚠️ n_coherent 已经体现在 i_n_total 里:传进去 5I 算出来的就是
# **单路**面积预算(总辐射 ∝ 5·A·I)。不能再除一次 5。
chk = F.dm_radiation_dbuv_m(worst_f, worst_area * n_coherent,
worst_i / n_coherent, DIST_M, ground_plane=True)
assert abs(chk - (worst_lim - MARGIN_DB)) < 0.01, u'回代不上:%.2f vs %.2f' % (chk, worst_lim - MARGIN_DB)
mark = u' ← 标称' if tr == TR_NOMINAL else u''
print(u' tr=%-5s 最紧谐波 %7.1f MHz (5路合计 I_n=%.1f mA, '
u'限值 %.0f dBμV/m) → 每路热环路 ≤ **%.1f mm²**%s'
% (u'%.0fns' % (tr * 1e9), worst_f / 1e6, worst_i * 1e3, worst_lim,
worst_area * 1e6, mark))
print(u'\n 参考:kicad-happy 的 SW-003 经验阈值是 <25 mm² 良好、>100 mm² 判 HIGH。')
print(u' ⚠️ 五路同相 = 谐波电流相干叠加 ×%d → 单路面积预算比单路板紧 %d 倍。'
% (n_coherent, n_coherent))
print(u' 把 PHASE_STAGGER 改 True 可以看错相能拿回多少(§8.4 当时是为别的理由否决的)。')
# -----------------------------------------------------------------
hr(u'3. 开关谐波落进手机自己的接收带')
print(u' (不是 EMC 合规问题 —— 是手机农场自己的信号质量问题)')
print(u' 假设单路热环路 25 mm²、%d 路同相、%.0f m 处:\n' % (n_coherent, DIST_M))
area = 25e-6
print(u' %-12s %-11s %-8s %-13s %-13s %s'
% (u'频段', u'带内谐波数', u'代表 n', u'谐波电流', u'辐射@3m', u'FCC 余量'))
for name, lo, hi in PHONE_BANDS:
ns = F.switching_harmonics_in_band(FSW, lo, hi)
if not ns:
print(u' %-12s 无' % name)
continue
n_mid = ns[len(ns) // 2]
f = n_mid * FSW
i_n = F.trapezoidal_harmonic_amplitude(n_mid, ipk, duty, TR_NOMINAL, FSW) * n_coherent
# 总辐射 ∝ (5·A)·I,i_n 里已含 5 倍,所以面积用单路值
e = F.dm_radiation_dbuv_m(f, area, i_n, DIST_M, ground_plane=True)
lim = F.get_emission_limit(f, STANDARD)
margin = (u'%+.0f dB' % (lim[0] - e)) if lim else u'超出表范围'
print(u' %-12s %-11d %-8d %-13s %-13s %s'
% (name, len(ns), n_mid, u'%.2f mA' % (i_n * 1e3),
u'%.0f dBμV/m' % e, margin))
print(u'\n 带内谐波数 = 带宽 / 480 kHz —— 每个手机接收带里都有上百根谐波线,')
print(u' 所以「错开频率躲开某个频段」在 480 kHz 上不成立,只能靠**压幅度**:')
print(u' 缩小热环路、放慢 SW 上升沿、输入端 π 型滤波。')
# -----------------------------------------------------------------
hr(u'4. PDN 目标阻抗(输出轨 3.5~4.2 V)')
print(u' %-12s %-14s %-14s' % (u'允许纹波', u'负载阶跃', u'Z_target'))
for ripple_mv, istep in ((10, 1.0), (10, 2.0), (50, 2.0), (50, 4.0)):
ripple_pct = ripple_mv / 1000.0 / VOUT_MAX * 100.0
z = F.pdn_target_impedance(VOUT_MAX, ripple_pct, istep)
print(u' %-12s %-14s %-14s'
% (u'%d mV' % ripple_mv, u'%.1f A' % istep, u'%.1f mΩ' % (z * 1000)))
print(u'\n §3.2 定的输出纹波是 10 mV。手机的瞬态(射频发射突发)按 1~2 A 估,')
print(u' 对应目标阻抗落在 **5~10 mΩ** —— 这是输出电容组 + 走线 + 排线的**总和**,')
print(u' 排线和连接器接触电阻要算进去(见 §13)。')
# -----------------------------------------------------------------
hr(u'5. 4 A 走线的直流压降(决定能不能顶到 4.15 V)')
print(u' %-10s %-11s %-12s %-12s %-12s'
% (u'线宽', u'铜厚', u'10mm 电阻', u'10mm 压降', u'50mm 压降'))
for w_mm, cu, cu_name in ((1.0, CU_OUTER_MM, u'1oz 外'),
(1.34, CU_OUTER_MM, u'1oz 外'),
(2.0, CU_OUTER_MM, u'1oz 外'),
(3.0, CU_OUTER_MM, u'1oz 外'),
(7.0, CU_INNER_MM, u'0.5oz 内')):
r10 = F.trace_resistance_ohm(10.0, w_mm, cu)
r50 = F.trace_resistance_ohm(50.0, w_mm, cu)
print(u' %-10s %-11s %-12s %-12s %-12s'
% (u'%.2f mm' % w_mm, cu_name, u'%.2f mΩ' % (r10 * 1000),
u'%.1f mV' % (r10 * IOUT * 1000), u'%.1f mV' % (r50 * IOUT * 1000)))
print(u'\n §10.1 定的 PWR4A 最小线宽是 1.0 mm,IPC-2221 对 4A@1oz 外层要 1.34 mm。')
print(u' ⚠️ 压降直接吃掉输出上限余量:§4.3 把上限压到 4.120 V 与 OVP 让开 86 mV,')
print(u' 板上再掉几十 mV,手机端看到的就更低了 —— 反馈取样点的位置很关键。')
hr(u'下一步')
print(u' 1. 实测 SW 上升沿(tekscope 的 scope_measure),把 TR_NOMINAL 换成实测值;')
print(u' 2. 从摆位数据算出每路 输入电容→Buck→电感 三角形面积,跟 §2 的预算对;')
print(u' 3. 反馈取样点确认在负载侧(§5 第二层的容差窗口经不起额外压降)。')
if __name__ == '__main__':
main()