文件
phonerail/.claude/pcb_calc.py

208 行
9.8 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- 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()