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