pcb_toolkit/differential/
edge_coupled_embedded.rs1use crate::CalcError;
9use crate::impedance::embedded::{self, EmbeddedMicrostripInput};
10use super::types::{DifferentialResult, kb_terminated};
11
12pub struct EdgeCoupledEmbeddedInput {
14 pub width: f64,
16 pub spacing: f64,
18 pub height: f64,
20 pub thickness: f64,
22 pub er: f64,
24 pub cover_height: f64,
27}
28
29pub fn calculate(input: &EdgeCoupledEmbeddedInput) -> Result<DifferentialResult, CalcError> {
31 let EdgeCoupledEmbeddedInput { width, spacing, height, thickness, er, cover_height } = *input;
32
33 if width <= 0.0 {
34 return Err(CalcError::NegativeDimension { name: "width", value: width });
35 }
36 if spacing <= 0.0 {
37 return Err(CalcError::NegativeDimension { name: "spacing", value: spacing });
38 }
39 if height <= 0.0 {
40 return Err(CalcError::NegativeDimension { name: "height", value: height });
41 }
42 if thickness <= 0.0 {
43 return Err(CalcError::NegativeDimension { name: "thickness", value: thickness });
44 }
45 if cover_height < 0.0 {
46 return Err(CalcError::NegativeDimension { name: "cover_height", value: cover_height });
47 }
48 if er < 1.0 {
49 return Err(CalcError::OutOfRange {
50 name: "er",
51 value: er,
52 expected: ">= 1.0",
53 });
54 }
55
56 let base = embedded::calculate(&EmbeddedMicrostripInput {
57 width,
58 height,
59 thickness,
60 er,
61 cover_height,
62 frequency: 0.0,
63 })?;
64
65 let z0 = base.zo;
66
67 let zodd = z0 * (1.0 - 0.48 * (-0.96 * spacing / height).exp());
68 let zeven = z0 * z0 / zodd;
69 let zdiff = 2.0 * zodd;
70 let kb = (zeven - zodd) / (zeven + zodd);
71 let kb_db = 20.0 * kb.log10();
72 let kb_term = kb_terminated(kb);
73 let kb_term_db = 20.0 * kb_term.log10();
74
75 Ok(DifferentialResult {
76 zdiff,
77 zo: z0,
78 zodd,
79 zeven,
80 kb,
81 kb_db,
82 kb_term,
83 kb_term_db,
84 })
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use approx::assert_relative_eq;
91
92 fn input(
93 width: f64,
94 spacing: f64,
95 height: f64,
96 thickness: f64,
97 er: f64,
98 cover_height: f64,
99 ) -> EdgeCoupledEmbeddedInput {
100 EdgeCoupledEmbeddedInput { width, spacing, height, thickness, er, cover_height }
101 }
102
103 #[test]
108 fn zero_cover_matches_external() {
109 let result = calculate(&input(10.0, 5.0, 15.0, 2.10, 4.6, 0.0)).unwrap();
110 assert_relative_eq!(result.zdiff, 95.76, max_relative = 0.005);
111 }
112
113 #[test]
115 fn deeper_burial_reduces_z0() {
116 let surface = calculate(&input(10.0, 5.0, 15.0, 2.10, 4.6, 0.0)).unwrap();
117 let buried = calculate(&input(10.0, 5.0, 15.0, 2.10, 4.6, 5.0)).unwrap();
118
119 assert!(
120 buried.zo < surface.zo,
121 "buried Zo {:.3} should be less than surface Zo {:.3}",
122 buried.zo,
123 surface.zo
124 );
125 }
126
127 #[test]
129 fn wider_spacing_reduces_coupling() {
130 let close = calculate(&input(10.0, 5.0, 15.0, 2.10, 4.6, 3.0)).unwrap();
131 let far = calculate(&input(10.0, 20.0, 15.0, 2.10, 4.6, 3.0)).unwrap();
132
133 assert!(
134 far.kb.abs() < close.kb.abs(),
135 "wider spacing Kb {:.4} should be smaller than {:.4}",
136 far.kb,
137 close.kb
138 );
139 }
140
141 #[test]
142 fn rejects_negative_width() {
143 let result = calculate(&input(-1.0, 5.0, 15.0, 2.10, 4.6, 0.0));
144 assert!(result.is_err());
145 }
146
147 #[test]
148 fn rejects_negative_cover_height() {
149 let result = calculate(&input(10.0, 5.0, 15.0, 2.10, 4.6, -1.0));
150 assert!(result.is_err());
151 }
152}