Polar SI9000场阻抗计算器算法实现
Polar SI9000安装包下载:Polar SI9000 2025 V25.01 阻抗计算神器安装包分享
做 PCB 阻抗设计时,Polar SI9000 基本绕不开。它的使用方式很直接:选一个截面模型,填介质厚度、介电常数、铜厚、线宽,然后点计算,得到一个阻抗值。
但如果只把它看成一个"公式计算器",其实会低估它。很多结构并不适合只靠简单闭式公式,比如有限铜厚、梯形蚀刻、上下介质不一样、空气盒子影响、有限地平面等等。这类问题更自然的做法是场求解。
这篇文章记录我用纯 Python 写一个简化版"场阻抗计算器"的过程。目标不是复刻 Polar SI9000 的内部实现,也不声称知道它的商业算法细节;目标只是把一个准静态二维场求解流程写清楚,并拿它和 SI9000 做几个数值对比,看这个思路到底靠不靠谱。
代码用到的库很普通:Gmsh 负责建模和网格,scikit-fem 负责有限元装配,SciPy 负责线性方程求解。整个流程不依赖商业电磁仿真器或外部场求解程序。
1. 理论分析
1.1 要算的是一个二维截面
这里讨论的是传输线的横截面问题。也就是说,走线沿着屏幕外的方向无限延伸,我们只在二维平面里看电场怎么分布。
我这里用的是一个双介质层微带线截面。主要参数有:
| 参数 | 含义 |
|---|---|
H1 |
下层介质高度 |
H2 |
上层介质高度 |
T |
铜厚 |
W1 |
走线下边宽度 |
W2 |
走线上边宽度 |
Er1 |
下层介质相对介电常数 |
Er2 |
上层介质相对介电常数 |

W1 和 W2 是为了处理梯形铜箔。实际 PCB 蚀刻后,铜线侧壁不一定垂直,所以只用一个线宽会有点粗糙。令 W1 = W2 时就是矩形铜线;二者不相等时,就是梯形铜线。
另外,开域问题总要截断成一个有限区域。所以模型外面加了空气盒子。空气盒子太小会把电场硬挤回去,电容会偏大,阻抗也会跟着偏。因此这类模型里,空气域和基板宽度都要取得比较宽,至少要让边界离主要电场区域足够远。
1.2 为什么静电场可以算阻抗
PCB 上传输线的横截面尺寸通常远小于波长,低频或准静态条件下可以近似成准 TEM 模式。这个时候传输线可以用每单位长度电容 C 和每单位长度电感 L 描述:
v = 1 L C v = \frac{1}{\sqrt{LC}} v=LC 1
Z 0 = L C Z_0 = \sqrt{\frac{L}{C}} Z0=CL
所以问题变成:如何得到 C 和 L。
电容比较好办。给信号线加 1 V,地平面加 0 V,求二维静电场:
∇ ⋅ ( ϵ ∇ ϕ ) = 0 \nabla \cdot \left(\epsilon \nabla \phi \right) = 0 ∇⋅(ϵ∇ϕ)=0
电场是:
E = − ∇ ϕ \mathbf{E} = -\nabla \phi E=−∇ϕ
单位长度上的静电能量为:
U ′ = 1 2 ∫ Ω ϵ ∣ ∇ ϕ ∣ 2 d A U' = \frac{1}{2}\int_{\Omega}\epsilon |\nabla \phi|^2 dA U′=21∫Ωϵ∣∇ϕ∣2dA
而电容储能关系是:
U ′ = 1 2 C V 2 U' = \frac{1}{2}CV^2 U′=21CV2
这里电压差取 1 V,于是:
C = ∫ Ω ϵ ∣ ∇ ϕ ∣ 2 d A C = \int_{\Omega}\epsilon |\nabla \phi|^2 dA C=∫Ωϵ∣∇ϕ∣2dA
这就是后处理里从电场能量得到电容的来源。
1.3 真空参考电容很关键
静电场能直接给出电容,却不能直接给出电感。这里常用一个技巧:同一个导体几何,把所有介质都换成真空,再算一次电容 C0。
同一导体结构在真空中有关系:
L C 0 = 1 c 0 2 LC_0 = \frac{1}{c_0^2} LC0=c021
因此:
L = 1 c 0 2 C 0 L = \frac{1}{c_0^2 C_0} L=c02C01
代回阻抗公式:
Z 0 = 1 c 0 C C 0 Z_0 = \frac{1}{c_0\sqrt{CC_0}} Z0=c0CC0 1
有效介电常数也顺手得到:
ϵ e f f = C C 0 \epsilon_\mathrm{eff} = \frac{C}{C_0} ϵeff=C0C
这就是整个准静态算法的核心:同一张网格、同一个导体几何,算两次静电场。第一次用真实介质,得到 C;第二次全部改成真空,得到 C0;最后用上面的公式算阻抗。
1.4 有限元形式
静电方程写成弱形式后,其实很朴素。取测试函数 v,分部积分:
∫ Ω ϵ ∇ ϕ ⋅ ∇ v d A = ∫ ∂ Ω v ϵ ∇ ϕ ⋅ n d S \int_{\Omega}\epsilon \nabla \phi \cdot \nabla v\ dA = \int_{\partial\Omega} v \epsilon \nabla\phi\cdot \mathbf{n}\ dS ∫Ωϵ∇ϕ⋅∇v dA=∫∂Ωvϵ∇ϕ⋅n dS
空气盒子外边界采用自然边界条件,右端项为零,于是剩下:
∫ Ω ϵ ∇ ϕ ⋅ ∇ v d A = 0 \int_{\Omega}\epsilon \nabla \phi \cdot \nabla v\ dA = 0 ∫Ωϵ∇ϕ⋅∇v dA=0
离散后就是一个刚度矩阵:
K i j = ∫ Ω ϵ ∇ N i ⋅ ∇ N j d A K_{ij} = \int_{\Omega}\epsilon \nabla N_i \cdot \nabla N_j\ dA Kij=∫Ωϵ∇Ni⋅∇Nj dA
信号线和地平面是 Dirichlet 边界条件。信号线固定为 1 V,地固定为 0 V。求解完电势向量 Phi 后,电容可以直接写成:
C = Φ T K Φ C = \Phi^T K \Phi C=ΦTKΦ
这里有一个细节值得注意:如果用二阶三角形单元 P2,边中点也是自由度。所以边界条件不能只靠网格顶点判断,必须按自由度坐标把落在金属边界上的点都找出来。这个小地方如果处理错,结果会有明显偏差。
2. 代码实现
2.1 整体思路
程序流程没有绕很多弯:
- 读入几何参数和材料参数。
- 用 Gmsh 画出空气盒子、两层介质、走线和地。
- 对几何做 fragment,让不同区域共用边界。
- 给每个三角形单元标记介电常数。
- 找到信号线边界和地边界。
- 装配静电问题。
- 求真实介质下的电容
C。 - 把所有区域介电常数改成
1,再求一次C0。 - 后处理得到
Z0。
最后把几何、网格规模、电容、有效介电常数和阻抗都写到 JSON。这样做的好处是每一步都能检查,不会只剩一个孤零零的阻抗结果。
2.2 参数和单位
代码里用一个 MicrostripGeometry 保存输入参数:
python
@dataclass(frozen=True)
class MicrostripGeometry:
h1: float = 2.97
h2: float = 3.53
trace_thickness: float = 1.80
w1: float = 5.60
w2: float = 5.60
er1: float = 4.20
er2: float = 4.20
输入长度单位是 mil,但有限元计算必须用 SI 单位。坐标进入有限元前统一乘:
python
MIL_TO_M = 25.4e-6
这样算出来的电容自然就是 F/m,后面不需要再做额外长度归一化。
网格尺寸没有直接写死,而是从面积估计出来。走线附近的最小网格尺寸用铜截面积控制:
A t r a c e = W 1 + W 2 2 T A_\mathrm{trace} = \frac{W_1 + W_2}{2}T Atrace=2W1+W2T
h t r a c e = A t r a c e N t r a c e h_\mathrm{trace} = \sqrt{\frac{A_\mathrm{trace}}{N_\mathrm{trace}}} htrace=NtraceAtrace
默认 N_trace = 50。远场网格尺寸用总仿真区域面积控制:
h f a r = A s i m N f a r h_\mathrm{far} = \sqrt{\frac{A_\mathrm{sim}}{N_\mathrm{far}}} hfar=NfarAsim
默认 N_far = 400。这不是最严格的网格自适应,但对于参数扫描很方便:线宽和铜厚变化时,局部网格会跟着变化,不至于在某些案例里过粗或过密。
2.3 几何建模
走线是用四个点画出来的:
python
p1 = gmsh.model.occ.addPoint(-0.5 * p.w1, trace_bottom, 0.0)
p2 = gmsh.model.occ.addPoint(0.5 * p.w1, trace_bottom, 0.0)
p3 = gmsh.model.occ.addPoint(0.5 * p.w2, trace_top, 0.0)
p4 = gmsh.model.occ.addPoint(-0.5 * p.w2, trace_top, 0.0)
这样 W1 是底边,W2 是顶边。W1 和 W2 相等时自然是矩形,不相等时就是梯形。
金属本身不作为介质区域参与求解。它只是一个等势边界。建模时仍然需要画出金属面,因为它可以帮助 Gmsh 把周围介质切开,也方便后面识别信号线边界。真正组装有限元区域时,金属内部单元会被排除掉。
2.4 区域和边界识别
这个 demo 没有引入复杂的 CAD 属性系统,而是用几何位置来判断区域:
| 区域 | 含义 |
|---|---|
air |
空气盒子 |
lower |
下层介质 |
upper |
上层介质 |
trace |
金属走线区域,后续排除 |
边界条件也很直接:
| 边界 | 条件 |
|---|---|
| 信号线外表面 | phi = 1 V |
| 地平面 | phi = 0 V |
| 空气盒外边界 | 自然边界条件 |
对于梯形铜线,侧边不是垂直线。程序按高度线性插值当前半宽:
w h a l f ( y ) = W 1 2 + y − H 1 T ( W 2 2 − W 1 2 ) w_\mathrm{half}(y)=\frac{W_1}{2}+\frac{y-H_1}{T}\left(\frac{W_2}{2}-\frac{W_1}{2}\right) whalf(y)=2W1+Ty−H1(2W2−2W1)
如果某个自由度满足 |x| = w_half(y),并且高度落在铜厚范围内,就认为它在铜线侧壁上。这个处理主要是为了保证二阶单元边界自由度也被正确固定。
2.5 装配和求解
scikit-fem 里静电双线性形式写起来很接近数学公式:
python
@BilinearForm
def electrostatic(u, v, w):
return epsilon_0 * w.er * dot(grad(u), grad(v))
这对应:
a ( u , v ) = ∫ Ω ϵ 0 ϵ r ∇ u ⋅ ∇ v d A a(u,v)=\int_\Omega \epsilon_0\epsilon_r \nabla u\cdot\nabla v\ dA a(u,v)=∫Ωϵ0ϵr∇u⋅∇v dA
实际介质求解时,下层、上层和空气分别使用自己的 epsilon_r。真空参考求解时,所有单元都设为 1。
求解结束后,用矩阵能量形式算电容:
python
capacitance_per_length = float(potential @ (stiffness @ potential))
然后:
python
epsilon_eff = capacitance / vacuum_capacitance
inductance = 1.0 / (c**2 * vacuum_capacitance)
phase_velocity = c / np.sqrt(epsilon_eff)
impedance = 1.0 / (c * np.sqrt(capacitance * vacuum_capacitance))
这里的 impedance 就是最终的准静态特征阻抗。
2.6 输出
程序输出一个 result.json,里面包含几何、网格规模和电气结果。比如:
json
{
"capacitance_F_per_m": "...",
"vacuum_capacitance_F_per_m": "...",
"effective_permittivity": "...",
"characteristic_impedance_ohm": "..."
}
实际使用时,我会优先看三个东西:C、C0 和 Z0。如果 Z0 看起来异常,先不要急着改公式,通常应该先检查几何、边界和网格。
3. Polar SI9000对比
为了验证这个实现,我拿几个 SI9000 的计算结果做了对比。下面表格里的长度单位都是 mil,阻抗单位都是 Ohm。
| H1 | Er1 | H2 | Er2 | W1 | W2 | T | Python FEM Z0 | Polar SI9000 Z0 | Difference | Relative difference |
|---|---|---|---|---|---|---|---|---|---|---|
| 10 | 4.2 | 3.53 | 4.2 | 10 | 5.6 | 1.8 | 64.14 | 63.98 | +0.16 | 0.25% |
| 10 | 9.6 | 20 | 2.2 | 20 | 10 | 5 | 31.44 | 31.49 | -0.05 | 0.17% |
| 20 | 3.66 | 20 | 2.2 | 50 | 30 | 4 | 43.29 | 43.28 | +0.01 | 0.02% |
| 5 | 3.66 | 5 | 4.2 | 20 | 50 | 1 | 16.38 | 16.26 | +0.12 | 0.71% |
| 5 | 3.66 | 5 | 4.2 | 1 | 1 | 1 | 91.81 | 91.73 | +0.08 | 0.09% |
误差按下面的方式计算:
Δ Z 0 = Z 0 , P y t h o n − Z 0 , S I 9000 \Delta Z_0 = Z_{0,\mathrm{Python}} - Z_{0,\mathrm{SI9000}} ΔZ0=Z0,Python−Z0,SI9000
R e l a t i v e d i f f e r e n c e = ∣ Z 0 , P y t h o n − Z 0 , S I 9000 ∣ Z 0 , S I 9000 × 100 % \mathrm{Relative\ difference} = \frac{|Z_{0,\mathrm{Python}} - Z_{0,\mathrm{SI9000}}|}{Z_{0,\mathrm{SI9000}}}\times 100\% Relative difference=Z0,SI9000∣Z0,Python−Z0,SI9000∣×100%
这几个案例的差异都不大,最大相对差异大约是 0.72%。对于一个简单的二维准静态有限元实现来说,这个结果已经说明基本方向是对的。
当然,这不代表它和 SI9000 完全等价。商业工具可能有自己的几何修正、经验模型、边界处理和收敛策略。这里的意义更多是:如果我们把几何、材料、边界条件和后处理都写清楚,用一个开源的场求解流程,也可以得到非常接近的阻抗结果。
小结
这个实现的核心其实就一句话:真实介质算一次电容 C,同几何真空环境再算一次电容 C0,然后用准 TEM 关系算阻抗。
我觉得这个小程序最有价值的地方,不是它能替代 SI9000,而是它把阻抗计算器背后的东西摊开了:几何怎么建,边界怎么设,电容从哪里来,为什么要算真空参考,最后阻抗怎么后处理。理解了这些,再去看商业工具给出的结果,就不会只是在盲目相信一个数字。