1use serde::{Deserialize, Serialize};
9
10use crate::CalcError;
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct ViaInput {
17 pub hole_diameter_mils: f64,
19 pub pad_diameter_mils: f64,
21 pub antipad_diameter_mils: f64,
23 pub height_mils: f64,
25 pub plating_thickness_mils: f64,
27 pub er: f64,
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct ViaResult {
34 pub capacitance_pf: f64,
36 pub inductance_nh: f64,
38 pub impedance_ohms: f64,
40 pub resonant_freq_mhz: f64,
42}
43
44pub fn calculate(input: &ViaInput) -> Result<ViaResult, CalcError> {
53 if input.hole_diameter_mils <= 0.0 {
54 return Err(CalcError::NegativeDimension {
55 name: "hole_diameter_mils",
56 value: input.hole_diameter_mils,
57 });
58 }
59 if input.pad_diameter_mils <= 0.0 {
60 return Err(CalcError::NegativeDimension {
61 name: "pad_diameter_mils",
62 value: input.pad_diameter_mils,
63 });
64 }
65 if input.antipad_diameter_mils <= 0.0 {
66 return Err(CalcError::NegativeDimension {
67 name: "antipad_diameter_mils",
68 value: input.antipad_diameter_mils,
69 });
70 }
71 if input.height_mils <= 0.0 {
72 return Err(CalcError::NegativeDimension {
73 name: "height_mils",
74 value: input.height_mils,
75 });
76 }
77 if input.plating_thickness_mils <= 0.0 {
78 return Err(CalcError::NegativeDimension {
79 name: "plating_thickness_mils",
80 value: input.plating_thickness_mils,
81 });
82 }
83 if input.er <= 0.0 {
84 return Err(CalcError::OutOfRange {
85 name: "er",
86 value: input.er,
87 expected: "> 0",
88 });
89 }
90 if input.antipad_diameter_mils <= input.pad_diameter_mils {
91 return Err(CalcError::OutOfRange {
92 name: "antipad_diameter_mils",
93 value: input.antipad_diameter_mils,
94 expected: "> pad_diameter_mils",
95 });
96 }
97
98 let d_hole = input.hole_diameter_mils / 1000.0;
100 let d_pad = input.pad_diameter_mils / 1000.0;
101 let d_antipad = input.antipad_diameter_mils / 1000.0;
102 let h = input.height_mils / 1000.0;
103
104 let capacitance_pf =
108 1.41 * input.er * h * d_pad / (d_antipad - d_pad);
109
110 let inductance_nh = 5.08 * h * ((4.0 * h / d_hole).ln() + 1.0);
114
115 let impedance_ohms = (inductance_nh / capacitance_pf).sqrt() * 1000.0_f64.sqrt();
119
120 let lc_product_nh_pf = inductance_nh * capacitance_pf;
128 let resonant_freq_mhz =
130 1.0 / (2.0 * std::f64::consts::PI * (lc_product_nh_pf * 1e-21).sqrt()) / 1e6;
131
132 Ok(ViaResult {
133 capacitance_pf,
134 inductance_nh,
135 impedance_ohms,
136 resonant_freq_mhz,
137 })
138}
139
140#[cfg(test)]
141mod tests {
142 use approx::assert_relative_eq;
143
144 use super::*;
145
146 #[test]
155 fn saturn_page36_via_vector() {
156 let input = ViaInput {
157 hole_diameter_mils: 10.0,
158 pad_diameter_mils: 20.0,
159 antipad_diameter_mils: 40.0,
160 height_mils: 62.0,
161 plating_thickness_mils: 1.0,
162 er: 4.6,
163 };
164 let result = calculate(&input).unwrap();
165 assert_relative_eq!(result.capacitance_pf, 0.4021, epsilon = 1e-3);
166 assert_relative_eq!(result.inductance_nh, 1.3262, epsilon = 1e-3);
167 assert_relative_eq!(result.impedance_ohms, 57.429, epsilon = 1e-2);
168 assert_relative_eq!(result.resonant_freq_mhz, 6891.661, epsilon = 1.0);
169 }
170
171 #[test]
172 fn error_on_zero_hole() {
173 let input = ViaInput {
174 hole_diameter_mils: 0.0,
175 pad_diameter_mils: 20.0,
176 antipad_diameter_mils: 40.0,
177 height_mils: 62.0,
178 plating_thickness_mils: 1.0,
179 er: 4.6,
180 };
181 assert!(calculate(&input).is_err());
182 }
183
184 #[test]
185 fn error_when_antipad_not_larger_than_pad() {
186 let input = ViaInput {
187 hole_diameter_mils: 10.0,
188 pad_diameter_mils: 40.0,
189 antipad_diameter_mils: 40.0,
190 height_mils: 62.0,
191 plating_thickness_mils: 1.0,
192 er: 4.6,
193 };
194 assert!(calculate(&input).is_err());
195 }
196}