Skip to main content

pcb_toolkit/impedance/
common.rs

1//! Shared impedance computation helpers.
2//!
3//! Effective dielectric constant, conductor thickness correction,
4//! Kirschning-Jansen frequency dispersion — used by all topology modules.
5
6use crate::constants;
7
8/// Static effective dielectric constant (Hammerstad-Jensen).
9///
10/// `u` = W/H ratio, `er` = substrate relative permittivity.
11pub fn er_eff_static(u: f64, er: f64) -> f64 {
12    let f = if u <= 1.0 {
13        (1.0 + 12.0 / u).powf(-0.5) + 0.04 * (1.0 - u).powi(2)
14    } else {
15        (1.0 + 12.0 / u).powf(-0.5)
16    };
17    (er + 1.0) / 2.0 + (er - 1.0) / 2.0 * f
18}
19
20/// Conductor thickness correction — effective width increase due to finite thickness.
21///
22/// `w` = conductor width (mils), `h` = dielectric height (mils), `t` = conductor thickness (mils).
23/// Returns the effective width We (mils).
24pub fn effective_width(w: f64, h: f64, t: f64) -> f64 {
25    if t <= 0.0 {
26        return w;
27    }
28    let u = w / h;
29    let dw = if u >= std::f64::consts::FRAC_PI_2 {
30        (t / std::f64::consts::PI) * (1.0 + (2.0 * h / t).ln())
31    } else {
32        (t / std::f64::consts::PI) * (1.0 + (4.0 * std::f64::consts::PI * w / t).ln())
33    };
34    w + dw
35}
36
37/// Propagation delay from Er_eff (ps/in).
38pub fn propagation_delay(er_eff: f64) -> f64 {
39    // Tpd = sqrt(Er_eff) / c, where c = 11.803 in/ns = 11803 in/µs
40    // Result in ps/in: (sqrt(Er_eff) / 11.803) * 1000
41    er_eff.sqrt() / constants::SPEED_OF_LIGHT_IN_NS * 1000.0
42}
43
44/// Inductance per unit length from Zo and Tpd (nH/in).
45pub fn inductance_per_length(zo: f64, tpd_ps_per_in: f64) -> f64 {
46    // Lo = Zo × Tpd, with Tpd in ns/in → Lo in nH/in
47    zo * tpd_ps_per_in / 1000.0
48}
49
50/// Capacitance per unit length from Zo and Tpd (pF/in).
51pub fn capacitance_per_length(zo: f64, tpd_ps_per_in: f64) -> f64 {
52    // Co = Tpd / Zo, with Tpd in ns/in → Co in nF/in → ×1000 for pF/in
53    (tpd_ps_per_in / 1000.0) / zo * 1000.0
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn er_eff_fr4_wide_trace() {
62        // W/H = 2.0, Er = 4.6 → Er_eff should be roughly 3.5-3.8
63        let er_eff = er_eff_static(2.0, 4.6);
64        assert!(er_eff > 3.0 && er_eff < 4.6, "er_eff = {er_eff}");
65    }
66
67    #[test]
68    fn er_eff_narrow_trace() {
69        // W/H = 0.5, Er = 4.6 → Er_eff should be lower (more field in air)
70        let narrow = er_eff_static(0.5, 4.6);
71        let wide = er_eff_static(2.0, 4.6);
72        assert!(narrow < wide, "narrow {narrow} should be < wide {wide}");
73    }
74
75    #[test]
76    fn thickness_correction_increases_width() {
77        let w = 5.0; // mils
78        let h = 4.0;
79        let t = 1.4; // 1oz copper
80        let we = effective_width(w, h, t);
81        assert!(we > w, "effective width {we} should be > original {w}");
82    }
83}