208 行
9.8 KiB
Python
208 行
9.8 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""受控阻抗 + IPC-2221 载流线宽计算器(纯数学,不连桥接)。
|
||
|
||
/c/Python314/python .claude/pcb_calc.py # 回验本项目的定稿几何
|
||
/c/Python314/python .claude/pcb_calc.py --zdiff 90 # 求 90Ω 差分的线宽
|
||
/c/Python314/python .claude/pcb_calc.py --current 4 # 求 4A 的最小线宽
|
||
/c/Python314/python .claude/pcb_calc.py --sweep # 叠层/铜厚变了扫一遍
|
||
|
||
移植自 hyl64/jlcmcp 的 `src/calculators.ts`(MIT),改成 **mm 为主单位**
|
||
(原版是 mil),并补了本项目的默认叠层。公式与 `五路直供电源板设计.md` §15.3
|
||
写的是同两条,所以这支脚本可以直接回验文档里的 Z0 / Zdiff。
|
||
|
||
⚠️ §15.3 已经写了:**打样前用板厂的阻抗计算器复核,别信这里的近似公式。**
|
||
这支脚本的用途是「叠层/铜厚/线宽一改,先看会不会跑出 ±10% 窗口」,不是定稿依据。
|
||
"""
|
||
from __future__ import print_function
|
||
|
||
import argparse
|
||
import math
|
||
|
||
MIL = 0.0254 # 1 mil = 0.0254 mm
|
||
|
||
# ---- 本项目定稿(五路直供电源板设计.md §10.1 / §15.3)------------------------
|
||
ER_FR4 = 4.3 # 嘉立创四层板 FR4
|
||
H_L1L2_MM = 0.2104 # L1-L2 介质厚度,1.6mm 标准叠层
|
||
T_1OZ_MM = 1.37 * MIL # 1 oz = 1.37 mil(§10.1 用的就是这个数,不是 1.4)
|
||
T_2OZ_MM = 2.74 * MIL
|
||
T_HALFOZ_MM = 0.685 * MIL
|
||
W_USB_MM = 0.28 # §15.3 定稿线宽
|
||
S_USB_MM = 0.18 # §15.3 定稿间距
|
||
ZDIFF_TARGET = 90.0 # USB 2.0 HS,±10%
|
||
DT_DEFAULT = 20.0 # °C 允许温升 —— 设计文档 §10.1 表头与 §4694 全篇用 20,不是 10
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 阻抗
|
||
# ---------------------------------------------------------------------------
|
||
def z0_microstrip(w_mm, h_mm, t_mm, er=ER_FR4):
|
||
u"""表层微带线特征阻抗。Z0 = 87/√(εr+1.41) · ln(5.98h / (0.8W + t))"""
|
||
return (87.0 / math.sqrt(er + 1.41)) * math.log(5.98 * h_mm / (0.8 * w_mm + t_mm))
|
||
|
||
|
||
def z0_stripline(w_mm, h_mm, t_mm, er=ER_FR4):
|
||
u"""内层带状线。Z0 = 60/√εr · ln(4h / (π(W + t)))"""
|
||
return (60.0 / math.sqrt(er)) * math.log((4.0 * h_mm) / (math.pi * (w_mm + t_mm)))
|
||
|
||
|
||
def zdiff_microstrip(w_mm, s_mm, h_mm, t_mm, er=ER_FR4):
|
||
u"""差分微带。Zdiff = 2·Z0 · (1 − 0.48·e^(−0.96·S/h))"""
|
||
z0 = z0_microstrip(w_mm, h_mm, t_mm, er)
|
||
return 2.0 * z0 * (1.0 - 0.48 * math.exp(-0.96 * s_mm / h_mm))
|
||
|
||
|
||
def zdiff_stripline(w_mm, s_mm, h_mm, t_mm, er=ER_FR4):
|
||
u"""差分带状线。Zdiff = 120/√εr · ln(2h / (π(W + t + S)))"""
|
||
return (120.0 / math.sqrt(er)) * math.log((2.0 * h_mm) / (math.pi * (w_mm + t_mm + s_mm)))
|
||
|
||
|
||
def width_for_z(target, fn, h_mm, t_mm, er=ER_FR4, s_mm=None,
|
||
lo=0.02, hi=5.0, tol=1e-4):
|
||
u"""二分反解线宽。阻抗对线宽单调递减,所以 z > target 时要加宽。
|
||
|
||
lo/hi 单位 mm。返回 (w_mm, z_actual)。
|
||
"""
|
||
def z_of(w):
|
||
return fn(w, s_mm, h_mm, t_mm, er) if s_mm is not None else fn(w, h_mm, t_mm, er)
|
||
|
||
if z_of(lo) < target or z_of(hi) > target:
|
||
return (float('nan'), float('nan')) # 目标落在可解区间外
|
||
for _ in range(200):
|
||
mid = (lo + hi) / 2.0
|
||
z = z_of(mid)
|
||
if abs(z - target) < tol:
|
||
return (mid, z)
|
||
if z > target:
|
||
lo = mid
|
||
else:
|
||
hi = mid
|
||
mid = (lo + hi) / 2.0
|
||
return (mid, z_of(mid))
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# IPC-2221 载流
|
||
# ---------------------------------------------------------------------------
|
||
def ipc_width_mm(current_a, t_mm=T_1OZ_MM, dt_c=DT_DEFAULT, external=True):
|
||
u"""IPC-2221:I = k·ΔT^0.44·A^0.725,反解线宽。
|
||
|
||
k = 0.048(外层)/ 0.024(内层)。A 的单位是 mil²,所以内部换算。
|
||
返回 (线宽 mm, 截面积 mil²)。
|
||
"""
|
||
k = 0.048 if external else 0.024
|
||
a_mil2 = (current_a / (k * dt_c ** 0.44)) ** (1.0 / 0.725)
|
||
t_mil = t_mm / MIL
|
||
return (a_mil2 / t_mil * MIL, a_mil2)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
def verify():
|
||
print(u'=' * 74)
|
||
print(u'回验 §15.3 定稿几何(W=%.2f / S=%.2f mm, h=%.4f mm, εr=%.1f, 1oz)'
|
||
% (W_USB_MM, S_USB_MM, H_L1L2_MM, ER_FR4))
|
||
print(u'=' * 74)
|
||
z0 = z0_microstrip(W_USB_MM, H_L1L2_MM, T_1OZ_MM)
|
||
zd = zdiff_microstrip(W_USB_MM, S_USB_MM, H_L1L2_MM, T_1OZ_MM)
|
||
print(u' Z0 = %.1f Ω (文档写 ≈57.5) %s'
|
||
% (z0, u'✓ 对上' if abs(z0 - 57.5) < 1.0 else u'✗ 对不上'))
|
||
print(u' Zdiff = %.1f Ω (文档写 ≈90.5) %s'
|
||
% (zd, u'✓ 对上' if abs(zd - 90.5) < 1.0 else u'✗ 对不上'))
|
||
lo, hi = ZDIFF_TARGET * 0.9, ZDIFF_TARGET * 1.1
|
||
print(u' 90 Ω ±10%% 窗口 = %.1f ~ %.1f Ω → %s'
|
||
% (lo, hi, u'在窗口内' if lo <= zd <= hi else u'**出窗口**'))
|
||
|
||
print(u'\n 文档那句「回到 2 oz 同样几何只剩 83 Ω,W 要收到约 0.24 mm」:')
|
||
zd2 = zdiff_microstrip(W_USB_MM, S_USB_MM, H_L1L2_MM, T_2OZ_MM)
|
||
w2, z2 = width_for_z(ZDIFF_TARGET, zdiff_microstrip, H_L1L2_MM, T_2OZ_MM,
|
||
s_mm=S_USB_MM)
|
||
print(u' 2oz 同几何 Zdiff = %.1f Ω (文档写 83) %s'
|
||
% (zd2, u'✓ 对上' if abs(zd2 - 83) < 1.5 else u'✗ 对不上'))
|
||
print(u' 2oz 下解 90 Ω 的线宽 = %.3f mm (文档写 ≈0.24) %s'
|
||
% (w2, u'✓ 对上' if abs(w2 - 0.24) < 0.015 else u'✗ 对不上'))
|
||
|
||
print(u'\n 反解自检(1oz,固定 S=%.2f):' % S_USB_MM)
|
||
w1, z1 = width_for_z(ZDIFF_TARGET, zdiff_microstrip, H_L1L2_MM, T_1OZ_MM,
|
||
s_mm=S_USB_MM)
|
||
print(u' 要正好 90.0 Ω 需 W = %.3f mm(现用 %.2f,差 %+.0f µm)'
|
||
% (w1, W_USB_MM, (W_USB_MM - w1) * 1000))
|
||
|
||
print(u'\n' + u'=' * 74)
|
||
print(u'IPC-2221 载流(回验 §10.1 的「4A 外层 1oz 要 1.34 mm」)')
|
||
print(u'=' * 74)
|
||
print(u' %-8s %-10s %-8s %-12s %s' % (u'电流', u'层', u'ΔT', u'截面积', u'最小线宽'))
|
||
for i, ext, dt in ((4.0, True, 20), (4.0, True, 10), (4.0, False, 20),
|
||
(8.0, False, 20), (1.0, True, 20)):
|
||
t = T_1OZ_MM if ext else T_HALFOZ_MM
|
||
w, a = ipc_width_mm(i, t, dt, ext)
|
||
print(u' %-8s %-10s %-8s %-12s %.2f mm'
|
||
% (u'%.0f A' % i, u'外层 1oz' if ext else u'内层 0.5oz',
|
||
u'%d °C' % dt, u'%.0f mil²' % a, w))
|
||
w4, _ = ipc_width_mm(4.0, T_1OZ_MM, 20, True)
|
||
print(u'\n 4A / 外层 1oz / ΔT=20°C → %.2f mm (文档写 1.34) %s'
|
||
% (w4, u'✓ 对上' if abs(w4 - 1.34) < 0.03 else u'✗ 对不上'))
|
||
|
||
|
||
def sweep():
|
||
print(u'\n' + u'=' * 74)
|
||
print(u'叠层敏感度:Zdiff 随介质厚度 h 与铜厚变化(W=%.2f / S=%.2f 固定)'
|
||
% (W_USB_MM, S_USB_MM))
|
||
print(u'=' * 74)
|
||
print(u' %-12s %-12s %-12s %s' % (u'h (mm)', u'1oz', u'2oz', u'判定(1oz)'))
|
||
for h in (0.15, 0.18, 0.2104, 0.25, 0.30):
|
||
a = zdiff_microstrip(W_USB_MM, S_USB_MM, h, T_1OZ_MM)
|
||
b = zdiff_microstrip(W_USB_MM, S_USB_MM, h, T_2OZ_MM)
|
||
ok = u'✓' if 81 <= a <= 99 else u'✗ 出窗口'
|
||
mark = u' ← 定稿' if abs(h - H_L1L2_MM) < 1e-6 else u''
|
||
print(u' %-12s %-12s %-12s %s%s'
|
||
% (u'%.4f' % h, u'%.1f Ω' % a, u'%.1f Ω' % b, ok, mark))
|
||
print(u'\n ⇒ h 从 0.2104 掉到 0.15(板厂换 prepreg)就掉出 ±10% 窗口。')
|
||
print(u' §15.3 那句「叠层一变就得重算」是有量的:h 每变 0.01 mm,Zdiff 约变 %.1f Ω。'
|
||
% abs(zdiff_microstrip(W_USB_MM, S_USB_MM, H_L1L2_MM + 0.01, T_1OZ_MM)
|
||
- zdiff_microstrip(W_USB_MM, S_USB_MM, H_L1L2_MM, T_1OZ_MM)))
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument('--zdiff', type=float, help=u'目标差分阻抗 Ω,反解线宽')
|
||
ap.add_argument('--z0', type=float, help=u'目标单端阻抗 Ω,反解线宽')
|
||
ap.add_argument('--spacing', type=float, default=S_USB_MM, help=u'差分间距 mm')
|
||
ap.add_argument('--height', type=float, default=H_L1L2_MM, help=u'介质厚度 mm')
|
||
ap.add_argument('--er', type=float, default=ER_FR4)
|
||
ap.add_argument('--copper', choices=('0.5oz', '1oz', '2oz'), default='1oz')
|
||
ap.add_argument('--stripline', action='store_true', help=u'内层带状线(默认表层微带)')
|
||
ap.add_argument('--current', type=float, help=u'载流 A,求最小线宽')
|
||
ap.add_argument('--dt', type=float, default=DT_DEFAULT,
|
||
help=u'允许温升 °C(默认 20,跟设计文档 §10.1 一致)')
|
||
ap.add_argument('--internal', action='store_true', help=u'载流按内层算')
|
||
ap.add_argument('--sweep', action='store_true')
|
||
a = ap.parse_args()
|
||
|
||
t = {'0.5oz': T_HALFOZ_MM, '1oz': T_1OZ_MM, '2oz': T_2OZ_MM}[a.copper]
|
||
|
||
if a.zdiff:
|
||
fn = zdiff_stripline if a.stripline else zdiff_microstrip
|
||
w, z = width_for_z(a.zdiff, fn, a.height, t, a.er, s_mm=a.spacing)
|
||
print(u'目标 %.1f Ω 差分%s,S=%.2f h=%.4f εr=%.1f %s'
|
||
% (a.zdiff, u'带状线' if a.stripline else u'微带', a.spacing,
|
||
a.height, a.er, a.copper))
|
||
print(u' → W = %.3f mm (实得 %.2f Ω)' % (w, z))
|
||
return
|
||
if a.z0:
|
||
fn = z0_stripline if a.stripline else z0_microstrip
|
||
w, z = width_for_z(a.z0, fn, a.height, t, a.er)
|
||
print(u'目标 %.1f Ω 单端 → W = %.3f mm (实得 %.2f Ω)' % (a.z0, w, z))
|
||
return
|
||
if a.current:
|
||
w, area = ipc_width_mm(a.current, t, a.dt, not a.internal)
|
||
print(u'%.2f A / %s / %s / ΔT=%.0f°C → 最小线宽 %.2f mm(截面 %.0f mil²)'
|
||
% (a.current, u'内层' if a.internal else u'外层', a.copper, a.dt, w, area))
|
||
return
|
||
|
||
verify()
|
||
if a.sweep:
|
||
sweep()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|