194 行
10 KiB
Python
194 行
10 KiB
Python
# -*- 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()
|