use serde::{Deserialize, Serialize};
use crate::CalcError;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ViaInput {
pub hole_diameter_mils: f64,
pub pad_diameter_mils: f64,
pub antipad_diameter_mils: f64,
pub height_mils: f64,
pub plating_thickness_mils: f64,
pub er: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ViaResult {
pub capacitance_pf: f64,
pub inductance_nh: f64,
pub impedance_ohms: f64,
pub resonant_freq_mhz: f64,
}
pub fn calculate(input: &ViaInput) -> Result<ViaResult, CalcError> {
if input.hole_diameter_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "hole_diameter_mils",
value: input.hole_diameter_mils,
});
}
if input.pad_diameter_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "pad_diameter_mils",
value: input.pad_diameter_mils,
});
}
if input.antipad_diameter_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "antipad_diameter_mils",
value: input.antipad_diameter_mils,
});
}
if input.height_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "height_mils",
value: input.height_mils,
});
}
if input.plating_thickness_mils <= 0.0 {
return Err(CalcError::NegativeDimension {
name: "plating_thickness_mils",
value: input.plating_thickness_mils,
});
}
if input.er <= 0.0 {
return Err(CalcError::OutOfRange {
name: "er",
value: input.er,
expected: "> 0",
});
}
if input.antipad_diameter_mils <= input.pad_diameter_mils {
return Err(CalcError::OutOfRange {
name: "antipad_diameter_mils",
value: input.antipad_diameter_mils,
expected: "> pad_diameter_mils",
});
}
let d_hole = input.hole_diameter_mils / 1000.0;
let d_pad = input.pad_diameter_mils / 1000.0;
let d_antipad = input.antipad_diameter_mils / 1000.0;
let h = input.height_mils / 1000.0;
let capacitance_pf =
1.41 * input.er * h * d_pad / (d_antipad - d_pad);
let inductance_nh = 5.08 * h * ((4.0 * h / d_hole).ln() + 1.0);
let impedance_ohms = (inductance_nh / capacitance_pf).sqrt() * 1000.0_f64.sqrt();
let lc_product_nh_pf = inductance_nh * capacitance_pf;
let resonant_freq_mhz =
1.0 / (2.0 * std::f64::consts::PI * (lc_product_nh_pf * 1e-21).sqrt()) / 1e6;
Ok(ViaResult {
capacitance_pf,
inductance_nh,
impedance_ohms,
resonant_freq_mhz,
})
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn saturn_page36_via_vector() {
let input = ViaInput {
hole_diameter_mils: 10.0,
pad_diameter_mils: 20.0,
antipad_diameter_mils: 40.0,
height_mils: 62.0,
plating_thickness_mils: 1.0,
er: 4.6,
};
let result = calculate(&input).unwrap();
assert_relative_eq!(result.capacitance_pf, 0.4021, epsilon = 1e-3);
assert_relative_eq!(result.inductance_nh, 1.3262, epsilon = 1e-3);
assert_relative_eq!(result.impedance_ohms, 57.429, epsilon = 1e-2);
assert_relative_eq!(result.resonant_freq_mhz, 6891.661, epsilon = 1.0);
}
#[test]
fn error_on_zero_hole() {
let input = ViaInput {
hole_diameter_mils: 0.0,
pad_diameter_mils: 20.0,
antipad_diameter_mils: 40.0,
height_mils: 62.0,
plating_thickness_mils: 1.0,
er: 4.6,
};
assert!(calculate(&input).is_err());
}
#[test]
fn error_when_antipad_not_larger_than_pad() {
let input = ViaInput {
hole_diameter_mils: 10.0,
pad_diameter_mils: 40.0,
antipad_diameter_mils: 40.0,
height_mils: 62.0,
plating_thickness_mils: 1.0,
er: 4.6,
};
assert!(calculate(&input).is_err());
}
}