Skip to main content

pcb_toolkit/impedance/
microstrip.rs

1//! Microstrip impedance calculator.
2//!
3//! Reference: Hammerstad & Jensen, "Accurate Models for Microstrip Computer-Aided
4//! Design", IEEE MTT-S International Microwave Symposium Digest, 1980.
5//!
6//! Decompiled from Saturn PCB Toolkit FUN_00440e34 (Mode 0).
7
8use crate::CalcError;
9use crate::impedance::{common, types::ImpedanceResult};
10
11/// Inputs for microstrip impedance calculation. All dimensions in mils.
12pub struct MicrostripInput {
13    /// Conductor width (mils).
14    pub width: f64,
15    /// Dielectric height — distance from trace to ground plane (mils).
16    pub height: f64,
17    /// Conductor thickness (mils). Usually from copper weight.
18    pub thickness: f64,
19    /// Substrate relative permittivity (e.g., 4.6 for FR-4).
20    pub er: f64,
21    /// Frequency (Hz). Used for Kirschning-Jansen dispersion correction.
22    pub frequency: f64,
23}
24
25/// Compute microstrip characteristic impedance and derived quantities.
26pub fn calculate(input: &MicrostripInput) -> Result<ImpedanceResult, CalcError> {
27    let MicrostripInput { width, height, thickness, er, .. } = *input;
28
29    if width <= 0.0 {
30        return Err(CalcError::NegativeDimension { name: "width", value: width });
31    }
32    if height <= 0.0 {
33        return Err(CalcError::NegativeDimension { name: "height", value: height });
34    }
35    if er < 1.0 {
36        return Err(CalcError::OutOfRange {
37            name: "er",
38            value: er,
39            expected: ">= 1.0",
40        });
41    }
42
43    // Apply thickness correction to get effective width
44    let we = common::effective_width(width, height, thickness);
45    let u = we / height;
46
47    // Static effective dielectric constant
48    let er_eff = common::er_eff_static(u, er);
49
50    // Characteristic impedance (Hammerstad-Jensen)
51    let zo = if u <= 1.0 {
52        // Narrow trace
53        (60.0 / er_eff.sqrt()) * (8.0 * height / we + we / (4.0 * height)).ln()
54    } else {
55        // Wide trace
56        (120.0 * std::f64::consts::PI / er_eff.sqrt())
57            / (u + 1.393 + 0.667 * (u + 1.444).ln())
58    };
59
60    // Derived quantities
61    let tpd = common::propagation_delay(er_eff);
62    let lo = common::inductance_per_length(zo, tpd);
63    let co = common::capacitance_per_length(zo, tpd);
64
65    Ok(ImpedanceResult {
66        zo,
67        er_eff,
68        tpd_ps_per_in: tpd,
69        lo_nh_per_in: lo,
70        co_pf_per_in: co,
71    })
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn basic_microstrip() {
80        let result = calculate(&MicrostripInput {
81            width: 10.0,
82            height: 5.0,
83            thickness: 1.4,
84            er: 4.6,
85            frequency: 0.0,
86        })
87        .unwrap();
88
89        // Zo should be in a reasonable range for this geometry
90        assert!(result.zo > 20.0 && result.zo < 80.0, "Zo = {}", result.zo);
91        assert!(result.er_eff > 1.0 && result.er_eff < 4.6, "Er_eff = {}", result.er_eff);
92        assert!(result.tpd_ps_per_in > 0.0);
93        assert!(result.lo_nh_per_in > 0.0);
94        assert!(result.co_pf_per_in > 0.0);
95    }
96
97    #[test]
98    fn narrow_trace_higher_impedance() {
99        let narrow = calculate(&MicrostripInput {
100            width: 3.0,
101            height: 5.0,
102            thickness: 1.4,
103            er: 4.6,
104            frequency: 0.0,
105        })
106        .unwrap();
107
108        let wide = calculate(&MicrostripInput {
109            width: 20.0,
110            height: 5.0,
111            thickness: 1.4,
112            er: 4.6,
113            frequency: 0.0,
114        })
115        .unwrap();
116
117        assert!(
118            narrow.zo > wide.zo,
119            "narrow Zo {} should be > wide Zo {}",
120            narrow.zo,
121            wide.zo
122        );
123    }
124
125    #[test]
126    fn rejects_negative_width() {
127        let result = calculate(&MicrostripInput {
128            width: -1.0,
129            height: 5.0,
130            thickness: 1.4,
131            er: 4.6,
132            frequency: 0.0,
133        });
134        assert!(result.is_err());
135    }
136}