app
Adom Fields
Public Made by Adomby adom
Adom Fields: where the current and the heat go on your board.
main
John Lauer
adom-fields 0.9.0: cable CM emissions, cross-section impedance solver, limit sets, board checks
af6e56e
//! Radiated-emission limit lines, all expressed at 3 m in dBµV/m so one estimate compares with any of them.
//! Built in (well-established values; quasi-peak below 1 GHz):
//! - FCC Part 15.109 class B (3 m) and class A (10 m, +10.5 dB to 3 m);
//! - CISPR 32 / EN 55032 class B and class A (10 m below 1 GHz, +10.5 dB to 3 m; above 1 GHz the 3 m average limits).
//! Others (CISPR 25, MIL-STD-461 RE102, a customer's own) come from spec `emcLimits`: points in MHz and dBµV/m at
//! their own distance, applied as steps and scaled to 3 m by 20·log10(d/3).
use serde::Deserialize;
#[derive(Deserialize, Clone, Debug)]
#[allow(non_snake_case)]
pub struct Custom { pub name: String, #[serde(default = "d3")] pub distanceM: f64, /** [MHz, dBµV/m] steps, ascending */ pub points: Vec<[f64; 2]> }
fn d3() -> f64 { 3.0 }
const D10: f64 = 10.457574905606752; // 20·log10(10/3)
/// the limit at f (Hz) for a named set, at 3 m; None above the set's range
pub fn at(set: &str, custom: &[Custom], f: f64) -> Option<f64> {
let m = f / 1e6;
match set {
"fcc-b" => Some(if m < 88.0 { 40.0 } else if m < 216.0 { 43.5 } else if m < 960.0 { 46.0 } else { 54.0 }),
"fcc-a" => Some(D10 + if m < 88.0 { 39.0 } else if m < 216.0 { 43.5 } else if m < 960.0 { 46.4 } else { 49.5 }),
"cispr32-b" => Some(if m < 230.0 { 30.0 + D10 } else if m < 1000.0 { 37.0 + D10 } else if m < 3000.0 { 50.0 } else { 54.0 }),
"cispr32-a" => Some(if m < 230.0 { 40.0 + D10 } else if m < 1000.0 { 47.0 + D10 } else if m < 3000.0 { 56.0 } else { 60.0 }),
name => {
let c = custom.iter().find(|c| c.name == name)?;
let p = c.points.iter().filter(|p| p[0] <= m).last().or_else(|| c.points.first())?;
Some(p[1] + 20.0 * (c.distanceM / 3.0).log10())
}
}
}
pub fn label(set: &str) -> String {
match set { "fcc-b" => "FCC 15.109 class B".into(), "fcc-a" => "FCC 15.109 class A".into(), "cispr32-b" => "CISPR 32 class B".into(), "cispr32-a" => "CISPR 32 class A".into(), x => x.to_string() }
}
/// every set the board is screened against: the chosen one first, then the other built-ins, then the custom ones
pub fn sets(chosen: &str, custom: &[Custom]) -> Vec<String> {
let mut v = vec![chosen.to_string()];
for s in ["fcc-b", "fcc-a", "cispr32-b", "cispr32-a"] { if s != chosen { v.push(s.into()); } }
for c in custom { if c.name != chosen { v.push(c.name.clone()); } }
v
}
#[cfg(test)] mod tests {
use super::*;
#[test] fn known_points() {
assert_eq!(at("fcc-b", &[], 100e6), Some(43.5));
assert!((at("cispr32-b", &[], 100e6).unwrap() - 40.46).abs() < 0.01); // 30 dBµV/m at 10 m
assert!((at("fcc-a", &[], 100e6).unwrap() - 53.96).abs() < 0.01);
let c = vec![Custom { name: "re102".into(), distanceM: 1.0, points: vec![[2.0, 24.0], [100.0, 30.0]] }];
assert!((at("re102", &c, 150e6).unwrap() - (30.0 - 9.54)).abs() < 0.01);
}
}