From 7f89baa3ebfcb93db177ee4f510592124495a438 Mon Sep 17 00:00:00 2001 From: Denis Chevalier Date: Mon, 10 Aug 2026 16:03:44 +0200 Subject: formatting, graphs, potentiometer wiper noise and wear, crosstalk --- module-design.typ | 808 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 736 insertions(+), 72 deletions(-) (limited to 'module-design.typ') diff --git a/module-design.typ b/module-design.typ index 5288949..e42f231 100644 --- a/module-design.typ +++ b/module-design.typ @@ -2,6 +2,8 @@ #import "lib/apply_prefix.typ": apply-prefix #import "lib/assert_aeq.typ": assert-aeq #import "@preview/diverential:0.3.0": * +#import "@preview/cetz:0.5.2" +#import "@preview/cetz-plot:0.1.4": plot = Module design @@ -109,7 +111,10 @@ operators. table( columns: 4, table.header([Symbol], [Definition], [Value], [Unit]), - [$k_B$], [Boltzmann constant], [$num("1.381e-23")$], [$unit("joule per kelvin")$], + [$k_B$], + [Boltzmann constant], + [$num("1.381e-23")$], + [$unit("joule per kelvin")$], [$T$], [Absolute temperature], [N/A], [$unit(K)$], [$e$], [Elementary charge], [$num("1.603e-19")$], [$unit(C)$], @@ -130,9 +135,13 @@ operators. [$L$], [Inductance], [$unit(H)$], [$P$], [Power], [$unit(W)$], [$f$], [Frequency], [$unit("Hz")$], - [$omega$], [Angular frequency ($omega = 2 pi f$)], [$unit("radian per second")$], + [$omega$], + [Angular frequency ($omega = 2 pi f$)], + [$unit("radian per second")$], - [$tau$], [Time constant ($tau = upright("RC") "or" upright("L/R")$)], [$unit(s)$], + [$tau$], + [Time constant ($tau = upright("RC") "or" upright("L/R")$)], + [$unit(s)$], [$upright("BW")$], [Bandwidth], [$unit("Hz")$], ), @@ -205,9 +214,13 @@ operators. columns: 3, table.header([Symbol], [Definition], [Typical unit]), [$epsilon$], [Error (general)], [$unit("ppm")$ or $unit("uV")$], - [$epsilon_"max"$], [Maximum allowable error], [$unit("ppm")$ or $unit("uV")$], + [$epsilon_"max"$], + [Maximum allowable error], + [$unit("ppm")$ or $unit("uV")$], - [$epsilon_"total"$], [Total combined error], [$unit("ppm")$ or $unit("uV")$], + [$epsilon_"total"$], + [Total combined error], + [$unit("ppm")$ or $unit("uV")$], [$epsilon_"systematic"$], [Systematic error component], [$unit("ppm")$], [$epsilon_"random"$], [Random error component], [$unit("uVrms")$], @@ -238,9 +251,13 @@ operators. [Current noise spectral density], [$unit("pico ampere per shertz")$ or $unit("femto ampere per shertz")$], - [$e_(n,"white")$], [White noise component], [$unit("nano volt per shertz")$], + [$e_(n,"white")$], + [White noise component], + [$unit("nano volt per shertz")$], - [$e_(n,1/f)$], [$1/f$ noise component], [$unit("nano volt per shertz")$ at $qty(1, "Hz")$], + [$e_(n,1/f)$], + [$1/f$ noise component], + [$unit("nano volt per shertz")$ at $qty(1, "Hz")$], [$f_c$], [Noise corner frequency ($1/f$ to white)], [$unit("Hz")$], @@ -261,7 +278,9 @@ operators. [$f_"carrier"$], [Carrier frequency], [$unit("Hz")$], [$f_0$], [Resonant or center frequency], [$unit("Hz")$], [$phi$], [Phase], [$unit("radian")$ or $unit("degree")$], - [$Delta phi$], [Phase error or shift], [$unit("radian")$ or $unit("degree")$], + [$Delta phi$], + [Phase error or shift], + [$unit("radian")$ or $unit("degree")$], [$t_"settle"$], [Settling time], [$unit("s")$], [$t_h$], [Timing jitter], [$unit("ps")$ or $unit("ns")$], @@ -276,13 +295,21 @@ operators. columns: 3, table.header([Symbol], [Definition], [Typical unit]), [$T$], [Temperature (absolute)], [$unit("K")$], - [$delta upright(T)$], [Temperature difference], [$unit("celsius")$ or $unit("K")$], + [$delta upright(T)$], + [Temperature difference], + [$unit("celsius")$ or $unit("K")$], - [$(Delta T)/(upright(d) y)$], [Vertical temperature gradient], [$unit("celsius per centi meter")$], + [$(Delta T)/(upright(d) y)$], + [Vertical temperature gradient], + [$unit("celsius per centi meter")$], - [$theta_"conv"$], [Convective thermal resistance], [$unit("celsius per watt")$], + [$theta_"conv"$], + [Convective thermal resistance], + [$unit("celsius per watt")$], - [$theta_"cond"$], [Conductive thermal resistance], [$unit("celsius per watt")$], + [$theta_"cond"$], + [Conductive thermal resistance], + [$unit("celsius per watt")$], [$accent(Q, dot)$], [Heat flux], [$unit("W")$], [$accent(m, dot)$], [Mass flow rate], [$unit("kilo gram per second")$], @@ -1063,7 +1090,8 @@ $tau = qty(25, "us")$: it represents one Nyquist interval $T_"sample" = qty(25, "us")$). #let epsilon_noise = ( - apply-prefix(632.455, "micro") / calc.sqrt(2 * 20000 * apply-prefix(25, "micro")) + apply-prefix(632.455, "micro") + / calc.sqrt(2 * 20000 * apply-prefix(25, "micro")) ) #assert-aeq(epsilon_noise, apply-prefix(632.455, "micro")) $ @@ -1083,9 +1111,13 @@ errors. Both must be met, but they are separate budgets: table( columns: 3, table.header([Budget], [Allocation], [Expressed as]), - [Systematic error budget], [$qty(200, "uV")$ ($qty(10, "ppm")$)], [Offset, drift, gain error, nonlinearity], + [Systematic error budget], + [$qty(200, "uV")$ ($qty(10, "ppm")$)], + [Offset, drift, gain error, nonlinearity], - [Random error budget], [$qty(632.455, "uVrms")$ ($qty(90, "dB")$)], [Thermal noise, $1/f$ noise, interference], + [Random error budget], + [$qty(632.455, "uVrms")$ ($qty(90, "dB")$)], + [Thermal noise, $1/f$ noise, interference], ), caption: [Error budgets allocation], ) @@ -1261,9 +1293,15 @@ $ ====== Scaling law -Thermal noise scales with $sqrt(R)$. Lower resistance values reduce noise but -increase power consumption and loading effects. This creates a fundamental -tradeoff. +Thermal noise voltage scales with the square root of resistance ($sqrt(R)$), +absolute temperature ($sqrt(T)$), and bandwidth ($sqrt(upright("BW"))$). At +circuit outputs, referred-to-output thermal noise scales linearly with the +stage's closed-loop noise gain ($G_N = 1 + R_f / R_"in"$). Decreasing +resistance values by a factor of $10$ yelds a $sqrt(10) approx 3.16 times$ +($qty(-10, "dB")$) reduction in thermal noise voltage, but increases power +dissipation ($P = V^2 / R$) and operational amplifier driving current demands by +$10 times$, establishing a fundamental thermodynamic trade-off between noise +floor and power/thermal loading. ====== Compensation strategy @@ -1315,22 +1353,89 @@ servo loop. table( columns: 4, table.header([Op-amp class], [$e_(n,"white")$], [$f_c$], [Notes]), - [General purpose (TL07x)], [$18 unit("nano volt per shertz")$], [$qty(200, "Hz")$], [JFET input], - - [Low noise (OPA211)], [$1.1 unit("nano volt per shertz")$], [$qty(10, "Hz")$], [Bipolar input], - - [Precision low noise (LT1028)], [$0.85 unit("nano volt per shertz")$], [$qty(3.5, "Hz")$], [Bipolar input], - - [Chopper stabilized (LTC2057)], [$7 unit("nano volt per shertz")$], [$lt qty(1, "Hz")$], [No $1/f$ corner], + [General purpose (TL07x)], + [$18 unit("nano volt per shertz")$], + [$qty(200, "Hz")$], + [JFET input], + + [Low noise (OPA211)], + [$1.1 unit("nano volt per shertz")$], + [$qty(10, "Hz")$], + [Bipolar input], + + [Precision low noise (LT1028)], + [$0.85 unit("nano volt per shertz")$], + [$qty(3.5, "Hz")$], + [Bipolar input], + + [Chopper stabilized (LTC2057)], + [$7 unit("nano volt per shertz")$], + [$lt qty(1, "Hz")$], + [No $1/f$ corner], ), caption: [Op-amp voltage noise typical magnitudes], ) +#figure( + cetz.canvas({ + import cetz.draw: * + + plot.plot( + size: (12, 6), + x-mode: "log", + x-label: [Frequency $f$ ($unit("Hz")$)], + y-label: [$e_n(f)$ ($unit("nano volt per shertz")$)], + x-min: 0.1, + x-max: 100000, + y-min: 0.5, + y-max: 50, + x-grid: "minor", + y-grid: "minor", + { + // General purpose (TL072): e_white = 18 nV/sqrt(Hz), fc = 200 Hz + plot.add( + style: (stroke: (dash: "solid")), + label: [TL07x], + domain: (0.1, 100000), + samples: 8000, + f => 18 * calc.sqrt(1 + 200 / f), + ) + // Low noise (OPA211): e_white = 1.1 nV/sqrt(Hz), fc = 10 Hz + plot.add( + style: (stroke: (dash: "dashed")), + label: [OPA211], + domain: (0.1, 100000), + samples: 8000, + f => 1.1 * calc.sqrt(1 + 10 / f), + ) + // Precision low noise (LT1028): e_white = 0.85 nV/sqrt(Hz), fc = 3.5 Hz + plot.add( + style: (stroke: (dash: "dotted")), + label: [LT1028], + domain: (0.1, 100000), + samples: 8000, + f => 0.85 * calc.sqrt(1 + 3.5 / f), + ) + // Chopper stabilized (LTC2057): e_white = 7 nV/sqrt(Hz), fc = 1 Hz + plot.add( + style: (stroke: (dash: "dash-dotted")), + label: [LTC2057], + domain: (0.1, 100000), + samples: 8000, + f => 7 * calc.sqrt(1 + 1 / f), + ) + }, + ) + }), + caption: [Comparison of operational amplifier voltage noise spectral density curves across frequency], +) + For a precision op-amp ($1 unit("nano volt per shertz"), f_c = qty(10, "Hz")$) over $qty(0.001, "Hz")$ to $qty(20, "kHz")$: #let v_n_rms = calc.sqrt( - calc.pow(calc.pow(10, -9), 2) * 20000 + (calc.pow(calc.pow(10, -9), 2) * 10 * calc.ln(20000 / 0.001)), + calc.pow(calc.pow(10, -9), 2) * 20000 + + (calc.pow(calc.pow(10, -9), 2) * 10 * calc.ln(20000 / 0.001)), ) #assert-aeq(v_n_rms, apply-prefix(0.43, "micro")) $ @@ -1348,6 +1453,17 @@ $ V_(n,"out") = e_n (1+R_f/R_"in") $ For a unity-gain buffer ($R_f = 0$), $V_(n,"out") = e_n$. For a gain-of-10 amplifier, output noise is $10 times$ input voltage noise. +====== Scaling law + +Output voltage noise scales linearly with closed-loop stage noise gain +($G_N = 1 + R_f/R_"in"$). At high frequencies ($f gt.double f_c$), total +integrated white noise scales with the square root of bandwidth +($sqrt(f_H - f_L)$). In the low-frequency $1/f$ region ($f lt.double f_c$), +integrated noise scales with the square root of the logarithmic observation +time ratio ($sqrt(ln(f_H / f_L))$), accumulating equal noise power across every +decade of frequency. Internal white noise density $e_(n,"white")$ scales with +$sqrt(T)$ following transistor channel and junction thermal noise dynamics. + ====== Compensation strategy - Select low noise bipolar op-amps ($e_n < 2 unit("nano volt per shertz")$) for @@ -1385,7 +1501,9 @@ $ V_(n,i,"rms") = i_n times Z_"source" times sqrt(upright("BW")) $ table( columns: 3, table.header([Op-amp class], [$i_n$], [Notes]), - [Bipolar (LT1028)], [$1 unit("pico ampere per shertz")$], [Low voltage noise, moderate current noise], + [Bipolar (LT1028)], + [$1 unit("pico ampere per shertz")$], + [Low voltage noise, moderate current noise], [JFET (OPA627)], [$2.5 unit("femto ampere per shertz")$], [Negligible], @@ -1420,6 +1538,16 @@ Current noise flows through all passive networks connected to the op-amp inputs. The total current-noise-induced voltage error is dominated by the highest equivalent impedance node connected to either terminal. +====== Scaling law + +Current-noise-induced voltage error scales linearly with equivalent source +impedance $Z_"source"$ and spectral density $i_n$, and with the square root of +bandwidth $sqrt(upright("BW"))$. Because shot noise density scales as +$i_n prop sqrt(I_B)$, current noise in bipolar inputs scales with the square +root of DC bias current. For JFET and CMOS inputs, where bias current doubles +for every $qty(10, "dC")$ temperature increase, $i_n$ scales by $sqrt(2)$ +($approx plus qty(41, "percent")$) per $qty(10, "dC")$. + ====== Compensation strategy Current noise cannot be cancelled by differential substraction because $i_(n+)$ @@ -1447,13 +1575,13 @@ significantly greater threat to precision than DC supply variations. The output error voltage $V_(n,"supply")$ resulting from supply ripple ($V_("ripple")$) is: -$ V_(n,"supply") = V_("ripple") / (upright("PSRR")(f)) $ +$ V_(n,"supply") = V_"ripple" / (upright("PSRR")(f)) $ Where $upright("PSRR")$ expressed in $unit("dB")$ represents the ratio of AC supply variation to output error variation: $ - upright("PSRR")_(unit("dB")) = 20 log_10((Delta V_("supply"))/(Delta V_("out"))) + upright("PSRR")_unit("dB") = 20 log_10((Delta V_"supply")/(Delta V_"out")) $ ====== Typical magnitude @@ -1461,7 +1589,9 @@ $ #figure( table( columns: 3, - table.header([Op-amp], [$upright("PSRR")$ (DC)], [$upright("PSRR")$ ($qty(10, "kHz")$)]), + table.header( + [Op-amp], [$upright("PSRR")$ (DC)], [$upright("PSRR")$ ($qty(10, "kHz")$)] + ), [TL072], [$qty(100, "dB")$], [$qty(80, "dB")$], [OPA211], [$qty(130, "dB")$], [$qty(90, "dB")$], [LT1028], [$qty(120, "dB")$], [$qty(90, "dB")$], @@ -1469,6 +1599,71 @@ $ caption: [Power supply noise coupling typical magnitudes], ) +#figure( + cetz.canvas({ + import cetz.draw: * + + plot.plot( + size: (12, 6), + x-mode: "log", + y-mode: "linear", + x-label: [Frequency $f$ ($unit("Hz")$)], + y-label: [$upright("PSRR")(f)$ ($unit("dB")$)], + x-min: 1, + x-max: 1000000, + y-min: 0, + y-max: 140, + x-grid: "minor", + y-grid: "minor", + { + // 500 kHz PWAM Carrier vertical reference line + plot.add( + ((500000, 0), (500000, 140)), + style: (stroke: (paint: luma(120), thickness: 0.8pt, dash: "dashed")), + label: none, + ) + // General purpose (TL07x): PSRR_DC = 100 dB, fp1 = 1 kHz, fp2 = 100 kHz + plot.add( + style: (stroke: (dash: "solid")), + label: [TL07x], + domain: (1, 1000000), + samples: 8000, + f => ( + 100 + - 10 * calc.log(1 + calc.pow(f / 1000, 2), base: 10) + - 10 * calc.log(1 + calc.pow(f / 100000, 2), base: 10) + ), + ) + // Low noise (OPA211): PSRR_DC = 130 dB, fp1 = 300 Hz, fp2 = 50 kHz + plot.add( + style: (stroke: (dash: "dotted")), + label: [OPA211], + domain: (1, 1000000), + samples: 8000, + f => ( + 130 + - 10 * calc.log(1 + calc.pow(f / 300, 2), base: 10) + - 10 * calc.log(1 + calc.pow(f / 50000, 2), base: 10) + ), + ) + // Precision low noise (LT1028): PSRR_DC = 120 dB, fp1 = 300 Hz, fp2 = 100 kHz + plot.add( + style: (stroke: (dash: "dashed")), + label: [LT1028], + domain: (1, 1000000), + samples: 8000, + f => ( + 120 + - 10 * calc.log(1 + calc.pow(f / 300, 2), base: 10) + - 10 * calc.log(1 + calc.pow(f / 100000, 2), base: 10) + ), + ) + }, + ) + }), + caption: [Power supply rejection ratio ($upright("PSRR")$) degradation across frequency, highlighting the reduced rejection at the $qty(500, "kHz")$ PWAM carrier frequency], +) + For SAME analog supply cleanliness specification ($qty(500, "uVpp")$ ripple on $plus.minus qty(15, "V")$ rails) with $qty(80, "dB") upright("PSRR")$ at $qty(10, "kHz")$: @@ -1496,6 +1691,14 @@ $ - High-gain stages (where output noise is further amplified by subsequent stages), -PWAM modulation cores susceptible to $qty(500, "kHz")$ carrier rail modulation. +====== Scaling law + +Power supply coupling error scales linearly with rail ripple amplitude +$V_"ripple"$ and stage closed-loop gain. Above the op-amp's dominant +$upright("PSRR")$ corner frequency (typically $qtyrange(10, 100, "Hz")$), +coupling error increases with frequency at $plus qty(20, "decibel per decade")$ +due to the single-pole degradation of internal open-loop gain. + ====== Compensation strategy - Enforce power supply cleanliness specification per @power-distribution, @@ -1525,7 +1728,7 @@ unwanted DC offset voltage that directly corrupts precision calculations. The peak Rf voltage induced in an unshielded conductor of length $l$ exposed to an electric field of strength $E$ is: -$ V_("induced") approx E dot l dot eta $ +$ V_"induced" approx E dot l dot eta $ Where $eta$ is the antenna coupling efficiency ($0.01$ to $0.5$, depending on frequency and geometry). @@ -1533,7 +1736,7 @@ frequency and geometry). The resulting demodulated DC offset error $V_(upright("DC"),"error")$ created by junction rectification efficiency $alpha$ ($0.01$ to $0.1$) is: -$ V_(upright("DC"),"error") = alpha dot V_("induced") $ +$ V_(upright("DC"),"error") = alpha dot V_"induced" $ ====== Typical magnitude @@ -1544,7 +1747,7 @@ specification limit) with coupling efficiency $eta = 0.1$: #let v_induced = 3 * 1 * 0.1 #assert-aeq(v_induced, apply-prefix(300, "milli")) -$ V_("induced") = 3 times 1 times 0.1 = qty(300, "mVpp") $ +$ V_"induced" = 3 times 1 times 0.1 = qty(300, "mVpp") $ Without input filtering, RFI demodulation with $alpha = 0.03$ produces a DC offset error of: @@ -1616,22 +1819,22 @@ with the signal path. Ground loop errors operate under two distinct mechanisms: -/ Intra-chassis return drops: Internal module return currents $I_("return")$ - flowing through shared backplane AGND plane resistance $R_("AGND")$ produce a - systematic offset: $ V_("error","internal") = I_("return") dot R_("AGND") $ +/ Intra-chassis return drops: Internal module return currents $I_"return"$ + flowing through shared backplane AGND plane resistance $R_upright("AGND")$ produce a + systematic offset: $ V_("error","internal") = I_"return" dot R_upright("AGND") $ / Inter-chassis common-mode offset: External ground potential differences $Delta V_G$ coupled via external cables are attenuated by the Common-Mode Rejection Ratio (CMRR) of the Interface Module's input stage: $ - V_("error","external") = (Delta V_G) / upright("CMRR")_("linear") = Delta V_G dot 10^(- upright("CMRR")_(unit("dB")) / 20) + V_("error","external") = (Delta V_G) / upright("CMRR")_"linear" = Delta V_G dot 10^(- upright("CMRR")_unit("dB") / 20) $ ====== Typical magnitude -For intra-chassis module operation, $R_("AGND") lt.eq qty(0.5, "milli ohm")$ +For intra-chassis module operation, $R_upright("AGND") lt.eq qty(0.5, "milli ohm")$ (guaranteed by continuous Layer 2 copper planes and multi-pin DB-25 grounding per @pcb-ground-planes and @backplane-connector). A -$I_("return") = qty(100, "mA")$ analog return current produces: +$I_"return" = qty(100, "mA")$ analog return current produces: #let v_error_internal = apply-prefix(100, "milli") * apply-prefix(0.5, "milli") #assert-aeq(v_error_internal, apply-prefix(50, "micro")) @@ -1666,9 +1869,9 @@ $ ====== Scaling law Intra-chassis ground error scales linearly with module return current -$I_("return")$ and AGND copper resistance $R_(upright("AGND"))$. Inter-chassis +$I_"return"$ and AGND copper resistance $R_upright("AGND")$. Inter-chassis ground error scales inversely with Interface Module -$upright("CMRR")_(unit("dB"))$ at $qty(20, "decibel per decade")$. +$upright("CMRR")_unit("dB")$ at $qty(20, "decibel per decade")$. ====== Compensation strategy @@ -1677,7 +1880,7 @@ includes DC components. Mitigation relies on structural topology: - Enforcing strict separation of AGND and DGND across backplane pins with zero on-module bridging per @grounding-rules, - Mandating continuous $qty(1, "oz")$ copper AGND planes on Layer 2 of all - module PCBs to minimize $R_(upright("AGND"))$ per + module PCBs to minimize $R_upright("AGND")$ per @pcb-ground-planes-agnd-plane, - Mandating high-CMRR ($gt.eq qty(120, "dB")$) differential instrumentation inputs or galvanic isolation on all external-facing interface modules per @@ -1703,7 +1906,7 @@ $upright("DA")$ is modeled as a voltage source in series with the ideal capacitance, decaying as a sum of exponential time constants: $ - V_(upright("DA")) = V_("peak") dot upright("DA")% dot sum_i A_i e^(-t/(tau_i)) + V_upright("DA") = V_"peak" dot upright("DA")% dot sum_i A_i e^(-t/(tau_i)) $ Where $upright("DA")%$ is the dielectric absorption coefficient and $tau_i$ @@ -1725,13 +1928,118 @@ represents the dielectric material relaxation time constants. caption: [Capacitor dielectric absorption coefficients], ) +#figure( + cetz.canvas({ + import cetz.draw: * + + plot.plot( + size: (12, 9), + y-mode: "log", + x-label: [Time $t$ ($unit("s")$)], + y-label: [Residual Voltage $V_upright("DA")$ ($unit("mV")$)], + x-min: 0.001, + x-max: 9, + y-min: 0.01, + y-max: 500, + x-grid: "minor", + y-grid: "minor", + { + // 200 uV (0.2 mV) Metrologic systematic error budget limit + plot.add( + ((0.001, 0.2), (10, 0.2)), + style: ( + stroke: (paint: luma(120), thickness: 0.8pt, dash: "dash-dotted"), + ), + label: [Budget limit ($qty(200, "uV")$)], + ) + // Ceramic X7R (DA = 2.5%, 10 V step -> 250 mV initial soakage peak) + plot.add( + style: (stroke: (dash: "solid")), + label: [Ceramic (X7R / Class 2)], + domain: (0.001, 10), + samples: 10000, + t => ( + 250 + * ( + 0.5 * calc.exp(-t / 0.01) + + 0.3 * calc.exp(-t / 0.1) + + 0.2 * calc.exp(-t / 1.0) + ) + ), + ) + // Polypropylene PP (DA = 0.05%, 10 V step -> 5 mV initial soakage peak) + plot.add( + style: (stroke: (dash: "dotted")), + label: [Polypropylene (PP)], + domain: (0.001, 10), + samples: 10000, + t => ( + 5 + * ( + 0.5 * calc.exp(-t / 0.01) + + 0.3 * calc.exp(-t / 0.1) + + 0.2 * calc.exp(-t / 1.0) + ) + ), + ) + // Polystyrene PP (DA = 0.002, 10 V step -> 2 mV initial soakage peak) + plot.add( + style: (stroke: (dash: "dashed")), + label: [Polystyrene (PS)], + domain: (0.001, 10), + samples: 10000, + t => ( + 2 + * ( + 0.5 * calc.exp(-t / 0.01) + + 0.3 * calc.exp(-t / 0.1) + + 0.2 * calc.exp(-t / 1.0) + ) + ), + ) + // Teflon PTFE (DA = 0.001, 10 V step -> 1 mV initial soakage peak) + plot.add( + style: (stroke: (dash: "dash-dotted")), + label: [Teflon (PTFE)], + domain: (0.001, 10), + samples: 10000, + t => ( + 1 + * ( + 0.5 * calc.exp(-t / 0.01) + + 0.3 * calc.exp(-t / 0.1) + + 0.2 * calc.exp(-t / 1.0) + ) + ), + ) + // Class 1 C0G/NP0 Ceramic (DA = 0.005%, 10 V step -> 0.5 mV initial soakage peak) + plot.add( + style: (stroke: (dash: (6pt, 2pt, 2pt, 2pt))), + label: [Class 1 (C0G/NP0)], + domain: (0.001, 10), + samples: 10000, + t => ( + 0.5 + * ( + 0.5 * calc.exp(-t / 0.01) + + 0.3 * calc.exp(-t / 0.1) + + 0.2 * calc.exp(-t / 1.0) + ) + ), + ) + }, + ) + }), + caption: [Capacitor dielectric absorption residual voltage relaxation ($V_upright("DA")$) over time following a $qty(10, "V")$ step voltage reset, comparing dielectric classes against the $qty(200, "uV")$ systematic error budget], +) + For a standard polypropylene capacitor ($qty(0.05, "percent") upright("DA")$) step-charged at $qty(10, "V")$: #let v_da = 10 * 0.0005 #assert.eq(v_da, apply-prefix(5, "milli")) $ - V_(upright("DA")) = qty(10, "V") times 0.0005 = qty(5, "mV") (qty(250, "ppm")) + V_upright("DA") = qty(10, "V") times 0.0005 = qty(5, "mV") (qty(250, "ppm")) $ This single residual term exceeds our $qty(200, "uV")$ systematic error budget @@ -1747,7 +2055,7 @@ by a factor of $25$. ====== Scaling law Dielectric absorption error scales linearly with peak applied step voltage -$V_("peak")$ and the dielectric material coefficient $upright("DA")$. It decays +$V_"peak"$ and the dielectric material coefficient $upright("DA")$. It decays logarithmically over time, making rapid reset/re-integration cycles particularly vulnerable. @@ -1784,16 +2092,16 @@ through two primary physical mechanisms: Triboelectric induced noise voltage in a flexible cable is modeled as: -$ V_("tribo") = k_("tribo") dv(L, t) $ +$ V_"tribo" = k_"tribo" dv(L, t) $ -Where $k_("tribo")$ is the cable's triboelectric coupling constant in +Where $k_"tribo"$ is the cable's triboelectric coupling constant in $unit("mV") / unit("meter per second")$ and $dv(L, t)$ is the rate of mechanical deformation. Piezoelectric microphonic voltage generated across a capacitor of capacitance -$C$ subjected to vibrational force $F_("vib")$ is: +$C$ subjected to vibrational force $F_"vib"$ is: -$ V_("piezo") = (d_33 dot F_("vib")) / C $ +$ V_"piezo" = (d_33 dot F_"vib") / C $ Where $d_33$ is the longitudinal piezoelectric charge coefficient of the dielectric material. @@ -1802,22 +2110,22 @@ dielectric material. For a standard PVC-insulated patch cable, -$k_("tribo") approx qty(50, "milli volt per meter per second")$ +$k_"tribo" approx qty(50, "milli volt per meter per second")$ experiencing mild flexing ($dv(L, t) = qty(1, "milli meter per second")$): #let v_tribo = apply-prefix(50, "milli") * apply-prefix(1, "milli") #assert-aeq(v_tribo, apply-prefix(50, "micro")) $ - V_("tribo") = num("50e-3") times num ("1e-3") = qty(50, "uV") (qty(2.5, "ppm")) + V_"tribo" = num("50e-3") times num ("1e-3") = qty(50, "uV") (qty(2.5, "ppm")) $ Using a low-noise graphite-coated cable -($k_("tribo") approx qty(1, "milli volt per meter per second")$): +($k_"tribo" approx qty(1, "milli volt per meter per second")$): #let v_tribo = apply-prefix(1, "milli") * apply-prefix(1, "milli") #assert-aeq(v_tribo, apply-prefix(1, "micro")) $ - V_("tribo") = num("1e-3") times num("1e-3") = qty(1, "uV") (qty(0.05, "ppm")) + V_"tribo" = num("1e-3") times num("1e-3") = qty(1, "uV") (qty(0.05, "ppm")) $ For an X7R ceramic capacitor @@ -1828,7 +2136,7 @@ $qty(100, "nF")$ node: #let v_piezo = (apply-prefix(200, "pico") * 0.1) / apply-prefix(100, "nano") #assert-aeq(v_piezo, apply-prefix(200, "micro")) $ - V_("piezo") = (num("200e-12") times 0.1) / num("100e-9") = qty(200, "uV") (qty(10, "ppm")) + V_"piezo" = (num("200e-12") times 0.1) / num("100e-9") = qty(200, "uV") (qty(10, "ppm")) $ ====== Where it enters @@ -1874,46 +2182,46 @@ signal traces into the signal path. ====== Mathematical model -Surface leakage current $I_("leak")$ flowing from a neighboring trace at -potential $V_("trace")$ across surface insulation resistance $R_("leak")$ is: +Surface leakage current $I_"leak"$ flowing from a neighboring trace at +potential $V_"trace"$ across surface insulation resistance $R_"leak"$ is: -$ I_("leak") = V_("trace") / R_("leak") $ +$ I_"leak" = V_"trace" / R_"leak" $ This stray current flows into the high-impedance node's equivalent source -impedance $Z_("source")$, developing a systematic error voltage: +impedance $Z_"source"$, developing a systematic error voltage: $ - V_("error") = I_("leak") times Z_("source") = V_("trace") dot Z_("source")/R_("leak") + V_"error" = I_"leak" times Z_"source" = V_"trace" dot Z_"source"/R_"leak" $ ====== Typical magnitude For a clean, dry FR-4 PCB at $qty(50, "percent") upright("RH")$, surface resistance between adjacent traces is typically -$R_("leak") approx qty(1, "tera ohm")$. Under high ambient humidity +$R_"leak" approx qty(1, "tera ohm")$. Under high ambient humidity ($qty(70, "percent") upright("RH")$) or with uncleaned flux residue, surface -resistance drops sharply to $R_("leak") approx qty(1, "giga ohm")$. +resistance drops sharply to $R_"leak" approx qty(1, "giga ohm")$. -For a $V_("trace") = qty(10, "V")$ potential adjacent to a -$Z_("source") = qty(1, "mega ohm")$ input node (white banana jack boundary): +For a $V_"trace" = qty(10, "V")$ potential adjacent to a +$Z_"source" = qty(1, "mega ohm")$ input node (white banana jack boundary): -- On a clean PCB ($R_("leak") = qty(1, "tera ohm")$): +- On a clean PCB ($R_"leak" = qty(1, "tera ohm")$): #let i_leak = 10 / apply-prefix(1, "tera") #assert-aeq(i_leak, apply-prefix(10, "pico")) #let v_error = i_leak * apply-prefix(1, "mega") #assert-aeq(v_error, apply-prefix(10, "micro")) $ - I_("leak") & = qty(10, "V") / qty(1, "tera ohm") = qty(10, "pA") \ - V_("error") & = qty(10, "pA") times qty(1, "mega ohm") = qty(10, "uV") (qty(0.5, "ppm")) + I_"leak" & = qty(10, "V") / qty(1, "tera ohm") = qty(10, "pA") \ + V_"error" & = qty(10, "pA") times qty(1, "mega ohm") = qty(10, "uV") (qty(0.5, "ppm")) $ -- On a contaminated PCB ($R_("leak") = qty(1, "giga ohm")$): +- On a contaminated PCB ($R_"leak" = qty(1, "giga ohm")$): #let i_leak = 10 / apply-prefix(1, "giga") #assert-aeq(i_leak, apply-prefix(10, "nano")) #let v_error = i_leak * apply-prefix(1, "mega") #assert-aeq(v_error, apply-prefix(10, "milli")) $ - I_("leak") & = qty(10, "V") / qty(1, "giga ohm") = qty(10, "nA") \ - V_("error") & = qty(10, "nA") times qty(1, "mega ohm") = qty(10, "mV") (qty(500, "ppm")) + I_"leak" & = qty(10, "V") / qty(1, "giga ohm") = qty(10, "nA") \ + V_"error" & = qty(10, "nA") times qty(1, "mega ohm") = qty(10, "mV") (qty(500, "ppm")) $ Contamination increases systematic DC error by $500 times$, exceeding the total @@ -1997,7 +2305,13 @@ materials. #figure( table( columns: 5, - table.header([Inductor type], [$upright("DCR")$], [Core loss ($qty(10, "kHz")$)], [Self-resonance], [Notes]), + table.header( + [Inductor type], + [$upright("DCR")$], + [Core loss ($qty(10, "kHz")$)], + [Self-resonance], + [Notes], + ), [Air core], [High ($qtyrange(1, 100, "ohm")$)], [None], @@ -2010,9 +2324,17 @@ materials. [$qtyrange(10, 100, "MHz")$], [Designed for loss (filtering)], - [Ferrite inductor], [$qtyrange(0.1, 10, "ohm")$], [Moderate], [$qtyrange(1, 50, "MHz")$], [General purpose], + [Ferrite inductor], + [$qtyrange(0.1, 10, "ohm")$], + [Moderate], + [$qtyrange(1, 50, "MHz")$], + [General purpose], - [Powdered iron], [$qtyrange(0.5, 5, "ohm")$], [Low], [$qtyrange(1, 10, "MHz")$], [DC bias tolerant], + [Powdered iron], + [$qtyrange(0.5, 5, "ohm")$], + [Low], + [$qtyrange(1, 10, "MHz")$], + [DC bias tolerant], [Laminated steel], [$qtyrange(0.1, 1, "ohm")$], @@ -2064,6 +2386,348 @@ instability. Mitigation relies on topological avoidance: - Isolating magnetic components away from high impedance summing nodes to prevent inductive coupling. +===== Potentiometer wiper noise and wear + + +====== Physical mechanism + + +Potentiometers used in control modules introduce several noise and error +mechanisms: +/ Wiper contact noise: The sliding contact between wiper and resistive elements + creates intermittent micro-disconnections, appearing as broadband noise, +/ Wiper contact resistance: The wiper-to-element contact has finite resistance + (typically $qtyrange(1, 100, "ohm")$) that varies with pressure, + contamination, and position, +/ Resolution (wirewound): Wirewound potentiometers have discrete turn steps, + creating spatial quantization noise, +/ Resistive element noise: Current flow through granular carbon or ceramic + matrix structures generates $1/f$ excess noise beyond Johnson noise, +/ Mechanical wear: Repeated operation wears the resistive track and wiper material, + increasing contact resistance and noise density over time, +/ Contamination: Airbone dust, flux residues, and oxidation on the element + create severe microphonic dropouts and static offset jumps. + +====== Mathematical model + + +Total potentiometer output noise spectral density combines thermal noise and DC +current-dependent excess noise characterized by the Current Noise Index +($upright("CNI")$): + +$ + V_"noise" = sqrt(4 k_B upright("TR") dot upright("BW")) + I dot R dot 10^(upright("CNI")/20) dot 10^-6 +$ + +Where $upright("CNI")$ is typically specified in $unit("dB")$ (typically +$qtyrange(-20, 20, "dB")$, with $qty(0, "dB")$ corresponding to +$qty(1, "micro volt")$ of excess noise per $unit("V")$ of DC drop over a +frequency decade). + +Wiper contact resistance variation during movement is modeled as: + +$ R_"contact" (t) = R_("contact",0) + Delta R_"contact" dot n(t) $ + +Where $n(t)$ is a normalized random noise process bounded by wiper travel velocity. + +====== Typical magnitude + +#figure( + table( + columns: 5, + table.header( + [Potentiometer type], + [Contact resistance], + [$upright("CNI")$], + [Wear life], + [Notes], + ), + [Carbon composition], + [$qtyrange(2, 50, "ohm")$], + [$qtyrange(0, 10, "dB")$], + [$10$k - $100$k cycles], + [Noisy, wear-prone], + + [Cermet], + [$qtyrange(1, 10, "ohm")$], + [$qtyrange(-10, 0, "dB")$], + [$100$k - $1$M cycles], + [Good compromise], + + [Conductive plastic], + [$qtyrange(0.5, 5, "ohm")$], + [$qtyrange(-20, -10, "dB")$], + [$1$M - $10$M cycles], + [Low noise, good wear], + + [Wirewound], + [$qtyrange(0.1, 2, "ohm")$], + [Very low], + [$1$M+ cycles], + [Quantized, best for DC], + + [Multi-turn (10T)], + [$qtyrange(0.5, 5, "ohm")$], + [$qtyrange(-15, -5, "dB")$], + [$100$k - $1$M cycles], + [High resolution], + ), + caption: [Potentiometer wiper noise and wear typical magnitudes], +) + +For a $qty(10, "kilo ohm")$ conductive plastic potentiometer at mid-scale +($qty(5, "kilo ohm")$) carrying $qty(1, "mA")$ DC current: + +#let v_signal = apply-prefix(5, "kilo") * apply-prefix(1, "milli") +#assert.eq(v_signal, 5) +$ V_"signal" = qty(5, "kilo ohm") times qty(1, "mA") = qty(5, "V") $ + +With $upright("CNI") = qty(-15, "dB")$ over $qty(20, "kHz")$ bandwidth: + +#let v_noise_excess = ( + 5 * calc.pow(10, -15 / 20) * calc.sqrt(20000) * calc.pow(10, -6) +) +#assert-aeq(v_noise_excess, apply-prefix(125.74, "micro")) +$ + V_("noise","excess") & approx qty(5, "V") times 10^(-15/20) times sqrt(20000) dot 10^-6 \ + V_("noise","excess") & approx qty(5, "V") times 0.1778 times num("141.4e-6") approx qty(125.74, "uV") +$ + +Excess noise alone consumes $qty(6.3, "ppm")$: $qty(63, "percent")$ of the +$qty(10, "ppm")$ systematic budget if unbuffered. Furthermore, wiper contact +resistance spikes during mechanical adjustment generate dynamic transient spikes +of $qtyrange(0.1, 10, "mV")$. + +====== Where it enters + + +- Front-panel manual controls in Control Modules per + @module-category-control-modules, +- Joysticks, multi-turn dials, and linear fader interfaces, +- External instrumentation level-matching controls on Interface Modules. + +====== Scaling law + +Excess noise voltage scales linearly with applied DC voltage drop +$V_upright("DC") = I dot R$ and exponentially with $upright("CNI")$. Contact +resistance voltage drop scales linearly with wiper current $I_"wiper"$. + +====== Compensation strategy + + +Wiper contact noise and wear cannot be eliminated by software calibration. +Mitigation relies on design rules: +- Prohibiting potentiometers on Compute Modules per + @compute-modules-interface-requirements, +- Mandating conductive plastic elements with $upright("CNI") lt.eq qty(-15, "dB")$ + and $gt.eq 1 upright(M)$ cycle wear rating for Control Modules, +- Buffering wiper outputs immediately with ultra-high input impedance JFET + op-amps ($I_"wiper" arrow 0$) to eliminate $I dot R_"contact"$ voltage drops, +- Applying low-pass RC filtering ($f_c approx qty(10, "Hz")$) directly on + control voltage wiper lines to attenuate mechanical movement contact noise. + +===== Crosstalk + +====== Physical mechanism + +Crosstalk is the unintended electromagnetic or conductive coupling of signals +between channels or adjacent circuits traces. Mechanisms include: +/ Capacitive coupling: Electric field between adjacent conductors induce + displacement current: + + $ I_"coupled" = C_"stray" dot dv(V, t) $ + +/ Inductive coupling: Magnetic fields generated by signal currents induce + unwanted voltages in adjacent signal loops: + + $ V_"induced" = M dot dv(I, t) $ + +/ Common-impedance coupling: Shared conductive return paths (ground planes, + power distribution lines) cause signal currents from one stage to modulate the + ground or supply reference of another: + + $ V_"coupled" = I_"signal" dot Z_"shared" $ + +/ Radiation coupling: High-frequency switching or harmonics radiate + electromagnetic waves between unshielded PCB traces or cables. + +====== Mathematical model + +/ Capacitive crosstalk: Mutual capacitance $C_"mutual"$ between parallel PCB + traces of length $l$, separation $d$, height $h$ above a ground plane is: + + $ + C_"mutual" approx (epsilon.alt_0 epsilon.alt_r l)/(2 pi) dot ln((d + sqrt(d^2 - 4 h^2))/(2 h))^(-1) + $ + + For typical PCB geometries ($qty(5, "mil")$ trace width, $qty(10, "mil")$ + spacing, $qty(10, "mil")$ dielectric height): + + $ C_"mutual" approx qtyrange(1, 3, "pico farad per inch") $ + + The resulting coupled voltage into a victim node of impedance $Z_"victim"$ is: + + $ + V_"coupled" = (Z_"victim"/(Z_"victim" + 1/(j omega C_"mutual"))) dot V_"aggressor" + $ + + At frequencies where $omega lt.double 1/(Z_"victim" dot C_"mutual")$: + + $ V_"coupled" approx j omega C_"mutual" Z_"victim" V_"aggressor" $ + + #figure( + cetz.canvas({ + import cetz.draw: * + + plot.plot( + size: (12, 6), + x-mode: "log", + x-label: [Frequency $f$ ($unit("Hz")$)], + y-label: [Coupled Voltage $V_"coupled"$ ($unit("mV")$)], + x-min: 10, + x-max: 100000, + y-min: 0.001, + y-max: 65, + x-grid: "minor", + y-grid: "minor", + { + // 20 kHz System Bandwidth limit vertical reference line + plot.add( + ((20000, 0.001), (20000, 100)), + style: ( + stroke: (paint: luma(120), thickness: 0.8pt, dash: "dashed"), + ), + label: none, + ) + + // Unshielded PCB trace: C_mutual = 1 pF, Z_victim = 10 kOhm + plot.add( + style: (stroke: (dash: "solid")), + label: [Unshielded trace \ + ($qty(1, "pF"), qty(10, "kilo ohm")$)], + domain: (10, 100000), + samples: 8000, + f => 2 * calc.pi * f * 1e-12 * 10000 * 10 * 1000, // in mV + ) + + // Low-impedance node: C_mutual = 1 pF, Z_victim = 1 kOhm + plot.add( + style: (stroke: (dash: "dashed")), + label: [Low-impedance node \ + ($qty(1, "pF"), qty(1, "kilo ohm")$)], + domain: (10, 100000), + samples: 8000, + f => 2 * calc.pi * f * 1e-12 * 1000 * 10 * 1000, // in mV + ) + + // Guard trace shielding: C_mutual = 0.05 pF, Z_victim = 10 kOhm + plot.add( + style: (stroke: (dash: "dotted")), + label: [Guard trace shielded \ + ($qty(0.05, "pF"), qty(10, "kilo ohm")$)], + domain: (10, 100000), + samples: 8000, + f => 2 * calc.pi * f * 0.05e-12 * 10000 * 10 * 1000, // in mV + ) + }, + ) + }), + caption: [Capacitive crosstalk coupled voltage ($V_"coupled"$) vs. frequency across different PCB layout conditions, demonstrating the $qty(20, "decibel per decade")$ slope up to the $qty(20, "kHz")$ system bandwidth boundary], + ) + +/ Inductive crosstalk: Mutual inductance $M$ between parallel traces is modeled + as: + + $ M approx (mu_0 l)/(2 pi) dot ln(1 + l^2/d^2) $ + + Yielding typical values of: + + $ M approx qtyrange(1, 10, "nano henry per inch") $. + +/ Common impedance coupling (ground bounce): + + $ + V_"coupled" = I_"aggressor" dot Z_"ground" + $ + + For $I_"agressor" = qty(10, "mA")$ transient through a $Z_"ground" = qty(0.1, "ohm")$ ground trace: + + #let v_coupled = apply-prefix(10, "milli") * 0.1 + #assert.eq(v_coupled, apply-prefix(1, "milli")) + $V_"coupled" = qty(10, "mA") times qty(0.1, "ohm") = qty(1, "mV") (qty(50, "ppm"))$ + + +====== Typical magnitude + +#figure( + table( + columns: 3, + table.header( + [Coupling mechanism], [Typical magnitude], [Frequency dependence] + ), + [Capacitive (PCB)], + [$qtyrange(0.1, 1, "percent")$ at $qty(20, "kHz")$], + [Increases with frequency], + + [Inductive (PCB)], + [$qtyrange(0.01, 0.1, "percent")$ at $qty(20, "kHz")$], + [Increases with frequency], + + [Ground bounce], [$qtyrange(1, 10, "mV")$], [Depends on transient speed], + [Supply coupling], [$qtyrange(0.1, 1, "mV")$], [Via PSRR], + [Module-to-module], + [$lt qty(-60, "dB")$], + [Protected by steel cassettes shielding], + ), + caption: [Crosstalk typical magnitudes], +) + +For a unshielded $qty(1, "pF")$ mutual capacitive between channels driving a +$Z_"victim" = qty(10, "kilo ohm")$ victim impedance at $f = qty(20, "kHz")$ with +$V_"aggressor" = qty(10, "V")$: + +#let v_coupled = 2 * calc.pi * 20000 * apply-prefix(1, "pico") * 10000 * 10 +#assert-aeq(v_coupled, apply-prefix(12.566, "milli")) +$ + V_"coupled" = 2 pi times 20000 times qty(1, "pF") times 10000 times qty(10, "V") approx qty(12.566, "mV") (qty(628.3, "ppm")) +$ + +This single unshielded capacitive trace coupling produces a $qty(628.3, "ppm")$ +error, exceeding the Metrologic systematic budget ($qty(10, "ppm")$) by a factor +of $63$. + +====== Where it enters + +- Long parallel signal traces on module PCBs, +- Shared DB-25 backplane connector pins and power/ground harness lines, +- Multichannel compute module ICs (e.g. quad op-amp packages sharing a + substrate), +- Unshielded front-panel $qty(4, "mm")$ banana patch cables bundled closely + together. + +====== Scaling law + +Capacitive and inductive crosstalk scale linearly with signal frequency $f$, +coupling length $l$, and victim impedance $Z_"victim"$, and inversely with +conductor spacing $d$. Common-impedance crosstalk scales linearly with +aggressor current $I_"aggressor"$ and shared trace impedance $Z_"shared"$. + +====== Compensation strategy + +Crosstalk cannot be removed by post-processing because the aggressor signal is +dynamic and independent. Mitigation requires strict structural and layout rule: +- Mandating solid, continuous Layer 2 AGND planes on all modules PCBs to + provide microstrip return shielding and minimize mutual inductance $M$ per + @pcb-ground-planes, +- Enforcing ground guard traces between high-frequency and high-voltage lines on + signal layers, +- Restricting compute module architectures to single or dual op-amp packages to + eliminate intra-IC substrate silicon crosstalk, +- Housing each module in a $qty(1.2, "mm")$ steel cassette to guarantee + $gt qty(60, "dB")$ inter-module isolation per @cassette, +- Requiring star grounding for power return lines to keep $Z_"shared" arrow 0$ + per @grounding-rules. + == Error compensation strategies == Advanced compensation topologies -- cgit