pcb_toolkit/differential/
edge_coupled_external.rs1use crate::CalcError;
7use super::types::{DifferentialResult, kb_terminated};
8
9pub struct EdgeCoupledExternalInput {
11 pub width: f64,
13 pub spacing: f64,
15 pub height: f64,
17 pub thickness: f64,
19 pub er: f64,
21}
22
23pub fn calculate(input: &EdgeCoupledExternalInput) -> Result<DifferentialResult, CalcError> {
25 let EdgeCoupledExternalInput { width, spacing, height, thickness, er } = *input;
26
27 if width <= 0.0 {
28 return Err(CalcError::NegativeDimension { name: "width", value: width });
29 }
30 if spacing <= 0.0 {
31 return Err(CalcError::NegativeDimension { name: "spacing", value: spacing });
32 }
33 if height <= 0.0 {
34 return Err(CalcError::NegativeDimension { name: "height", value: height });
35 }
36 if thickness <= 0.0 {
37 return Err(CalcError::NegativeDimension { name: "thickness", value: thickness });
38 }
39 if er < 1.0 {
40 return Err(CalcError::OutOfRange {
41 name: "er",
42 value: er,
43 expected: ">= 1.0",
44 });
45 }
46
47 let z0 = (87.0 / (er + 1.41_f64).sqrt())
48 * (5.98 * height / (0.8 * width + thickness)).ln();
49
50 let zodd = z0 * (1.0 - 0.48 * (-0.96 * spacing / height).exp());
51 let zeven = z0 * z0 / zodd;
52 let zdiff = 2.0 * zodd;
53 let kb = (zeven - zodd) / (zeven + zodd);
54 let kb_db = 20.0 * kb.log10();
55 let kb_term = kb_terminated(kb);
56 let kb_term_db = 20.0 * kb_term.log10();
57
58 Ok(DifferentialResult {
59 zdiff,
60 zo: z0,
61 zodd,
62 zeven,
63 kb,
64 kb_db,
65 kb_term,
66 kb_term_db,
67 })
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use approx::assert_relative_eq;
74
75 fn input(width: f64, spacing: f64, height: f64, thickness: f64, er: f64) -> EdgeCoupledExternalInput {
76 EdgeCoupledExternalInput { width, spacing, height, thickness, er }
77 }
78
79 #[test]
81 fn saturn_pdf_page11() {
82 let result = calculate(&input(10.0, 5.0, 15.0, 2.10, 4.6)).unwrap();
83
84 assert_relative_eq!(result.zo, 77.504, max_relative = 0.002);
85 assert_relative_eq!(result.zodd, 50.490, max_relative = 0.002);
86 assert_relative_eq!(result.zeven, 118.971, max_relative = 0.002);
87 assert_relative_eq!(result.zdiff, 100.979, max_relative = 0.002);
88 assert_relative_eq!(result.kb, 0.4041, max_relative = 0.002);
89 assert_relative_eq!(result.kb_db, -7.870, max_relative = 0.002);
90 assert_relative_eq!(result.kb_term, 0.2111, max_relative = 0.005);
91 assert_relative_eq!(result.kb_term_db, -13.512, max_relative = 0.005);
92 }
93
94 #[test]
96 fn wider_spacing_reduces_coupling() {
97 let close = calculate(&input(10.0, 5.0, 15.0, 2.10, 4.6)).unwrap();
98 let far = calculate(&input(10.0, 20.0, 15.0, 2.10, 4.6)).unwrap();
99
100 assert!(
101 far.kb.abs() < close.kb.abs(),
102 "wider spacing Kb {:.4} should be smaller than {:.4}",
103 far.kb,
104 close.kb
105 );
106 }
107
108 #[test]
110 fn higher_er_gives_lower_z0() {
111 let low_er = calculate(&input(10.0, 5.0, 15.0, 2.10, 3.0)).unwrap();
112 let high_er = calculate(&input(10.0, 5.0, 15.0, 2.10, 4.6)).unwrap();
113
114 assert!(
115 high_er.zo < low_er.zo,
116 "higher Er Zo {:.3} should be less than {:.3}",
117 high_er.zo,
118 low_er.zo
119 );
120 }
121
122 #[test]
123 fn rejects_negative_width() {
124 let result = calculate(&input(-1.0, 5.0, 15.0, 2.10, 4.6));
125 assert!(result.is_err());
126 }
127
128 #[test]
129 fn rejects_er_below_one() {
130 let result = calculate(&input(10.0, 5.0, 15.0, 2.10, 0.5));
131 assert!(result.is_err());
132 }
133}