use serde::{Deserialize, Serialize};
use crate::CalcError;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HzToPpmResult {
pub variation_hz: f64,
pub ppm: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PpmToHzResult {
pub variation_hz: f64,
pub max_hz: f64,
pub min_hz: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct XtalLoadResult {
pub c_load_calc_f: f64,
pub c_load_rule_of_thumb_f: f64,
}
pub fn hz_to_ppm(center_hz: f64, max_hz: f64) -> Result<HzToPpmResult, CalcError> {
if center_hz <= 0.0 {
return Err(CalcError::OutOfRange {
name: "center_hz",
value: center_hz,
expected: "> 0",
});
}
if max_hz <= center_hz {
return Err(CalcError::OutOfRange {
name: "max_hz",
value: max_hz,
expected: "> center_hz",
});
}
let variation_hz = max_hz - center_hz;
let ppm = (variation_hz / center_hz) * 1_000_000.0;
Ok(HzToPpmResult { variation_hz, ppm })
}
pub fn ppm_to_hz(center_hz: f64, ppm: f64) -> Result<PpmToHzResult, CalcError> {
if center_hz <= 0.0 {
return Err(CalcError::OutOfRange {
name: "center_hz",
value: center_hz,
expected: "> 0",
});
}
if ppm <= 0.0 {
return Err(CalcError::OutOfRange {
name: "ppm",
value: ppm,
expected: "> 0",
});
}
let variation_hz = center_hz * ppm / 1_000_000.0;
Ok(PpmToHzResult {
variation_hz,
max_hz: center_hz + variation_hz,
min_hz: center_hz - variation_hz,
})
}
pub fn xtal_load(
c_stray_f: f64,
c1_f: f64,
c2_f: f64,
) -> Result<XtalLoadResult, CalcError> {
if c_stray_f < 0.0 {
return Err(CalcError::OutOfRange {
name: "c_stray_f",
value: c_stray_f,
expected: ">= 0",
});
}
if c1_f <= 0.0 {
return Err(CalcError::OutOfRange {
name: "c1_f",
value: c1_f,
expected: "> 0",
});
}
if c2_f <= 0.0 {
return Err(CalcError::OutOfRange {
name: "c2_f",
value: c2_f,
expected: "> 0",
});
}
let c_series = (c1_f * c2_f) / (c1_f + c2_f);
let c_load_calc_f = c_series + c_stray_f;
let c_load_rule_of_thumb_f = (c1_f + c2_f) / 2.0;
Ok(XtalLoadResult {
c_load_calc_f,
c_load_rule_of_thumb_f,
})
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn saturn_hz_to_ppm() {
let result = hz_to_ppm(32000.0, 32001.0).unwrap();
assert_relative_eq!(result.variation_hz, 1.0, epsilon = 1e-10);
assert_relative_eq!(result.ppm, 31.25, epsilon = 1e-6);
}
#[test]
fn saturn_ppm_to_hz() {
let result = ppm_to_hz(50e6, 25.0).unwrap();
assert_relative_eq!(result.variation_hz, 1250.0, epsilon = 1e-6);
assert_relative_eq!(result.max_hz, 50_001_250.0, epsilon = 1e-4);
assert_relative_eq!(result.min_hz, 49_998_750.0, epsilon = 1e-4);
}
#[test]
fn saturn_xtal_load() {
let result = xtal_load(3e-12, 14e-12, 14e-12).unwrap();
assert_relative_eq!(result.c_load_calc_f, 10e-12, epsilon = 1e-14);
assert_relative_eq!(result.c_load_rule_of_thumb_f, 14e-12, epsilon = 1e-14);
}
#[test]
fn error_on_zero_center_freq() {
assert!(hz_to_ppm(0.0, 100.0).is_err());
assert!(ppm_to_hz(0.0, 10.0).is_err());
}
#[test]
fn error_on_max_not_greater_than_center() {
assert!(hz_to_ppm(1000.0, 999.0).is_err());
assert!(hz_to_ppm(1000.0, 1000.0).is_err());
}
}