Skip to main content

stationxml_rs/
conversion.rs

1//! ADC conversion helpers.
2//!
3//! Convert between raw ADC counts, voltage, and physical units
4//! using sensor sensitivity and digitizer parameters.
5//!
6//! # Formulas
7//!
8//! ```text
9//! voltage = (count / max_count) * full_scale_voltage / (pga_gain * adc_gain)
10//! physical = voltage / sensor_sensitivity
11//! overall_sensitivity = max_count * pga_gain * adc_gain * sensor_sensitivity / full_scale_voltage
12//! ```
13//!
14//! See `docs/guide/03-instrument-response.md` for background.
15
16/// Parameters for ADC count / voltage conversion.
17///
18/// Describes the digitizer characteristics needed to convert between
19/// raw ADC counts and voltage (or physical units with a sensor sensitivity).
20#[derive(Debug, Clone, PartialEq)]
21pub struct AdcConversion {
22    /// Full-scale range in Volts (e.g. 5.0 for a +/-2.5V ADC)
23    pub full_scale_voltage: f64,
24    /// Maximum count value: 2^(bits-1) - 1 (e.g. 8388607 for 24-bit)
25    pub max_count: f64,
26    /// External PGA (Programmable Gain Amplifier) gain (e.g. 1.0)
27    pub pga_gain: f64,
28    /// Internal digital gain in ADC (e.g. 1.0)
29    pub adc_gain: f64,
30}
31
32impl AdcConversion {
33    /// Create from ADC bit depth and gains.
34    ///
35    /// `max_count` is computed as 2^(bits-1) - 1.
36    pub fn new(full_scale_voltage: f64, bits: u32, pga_gain: f64, adc_gain: f64) -> Self {
37        Self {
38            full_scale_voltage,
39            max_count: (1_i64 << (bits - 1)) as f64 - 1.0,
40            pga_gain,
41            adc_gain,
42        }
43    }
44
45    /// Convert raw ADC count to input voltage (before PGA).
46    pub fn count_to_voltage(&self, count: f64) -> f64 {
47        (count / self.max_count) * self.full_scale_voltage / (self.pga_gain * self.adc_gain)
48    }
49
50    /// Convert input voltage to raw ADC count.
51    pub fn voltage_to_count(&self, voltage: f64) -> f64 {
52        voltage * self.max_count * self.pga_gain * self.adc_gain / self.full_scale_voltage
53    }
54
55    /// Convert raw ADC count to physical unit using sensor sensitivity.
56    ///
57    /// `sensitivity` is in V/(m/s) for velocity sensors.
58    pub fn count_to_physical(&self, count: f64, sensitivity: f64) -> f64 {
59        self.count_to_voltage(count) / sensitivity
60    }
61
62    /// Compute overall sensitivity in counts per physical unit.
63    ///
64    /// This is the value that goes into `<InstrumentSensitivity><Value>`.
65    pub fn overall_sensitivity(&self, sensor_sensitivity: f64) -> f64 {
66        self.max_count * self.pga_gain * self.adc_gain * sensor_sensitivity
67            / self.full_scale_voltage
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    fn cs5532_24bit() -> AdcConversion {
76        // CS5532 24-bit ADC, 5V full-scale, no PGA, no digital gain
77        AdcConversion::new(5.0, 24, 1.0, 1.0)
78    }
79
80    #[test]
81    fn max_count_24bit() {
82        let adc = cs5532_24bit();
83        assert_eq!(adc.max_count, 8388607.0);
84    }
85
86    #[test]
87    fn count_to_voltage_full_scale() {
88        let adc = cs5532_24bit();
89        let v = adc.count_to_voltage(adc.max_count);
90        assert!((v - 5.0).abs() < 1e-6);
91    }
92
93    #[test]
94    fn count_to_voltage_zero() {
95        let adc = cs5532_24bit();
96        assert_eq!(adc.count_to_voltage(0.0), 0.0);
97    }
98
99    #[test]
100    fn voltage_to_count_roundtrip() {
101        let adc = cs5532_24bit();
102        let voltage = 2.5;
103        let count = adc.voltage_to_count(voltage);
104        let v_back = adc.count_to_voltage(count);
105        assert!((v_back - voltage).abs() < 1e-6);
106    }
107
108    #[test]
109    fn overall_sensitivity_gs11d() {
110        let adc = cs5532_24bit();
111        // GS-11D sensitivity: 32.0 V/(m/s)
112        let overall = adc.overall_sensitivity(32.0);
113        // Expected: 8388607 * 1 * 1 * 32.0 / 5.0 = 53687084.8
114        assert!((overall - 53687084.8).abs() < 0.1);
115    }
116
117    #[test]
118    fn count_to_physical() {
119        let adc = cs5532_24bit();
120        let sensitivity = 32.0; // V/(m/s)
121        // 1000 counts -> voltage -> physical
122        let physical = adc.count_to_physical(1000.0, sensitivity);
123        let expected_voltage = 1000.0 / 8388607.0 * 5.0;
124        let expected_physical = expected_voltage / 32.0;
125        assert!((physical - expected_physical).abs() < 1e-12);
126    }
127
128    #[test]
129    fn with_pga_gain() {
130        let adc = AdcConversion::new(5.0, 24, 2.0, 1.0);
131        // With PGA=2, effective voltage is halved for same count
132        let v_no_pga = cs5532_24bit().count_to_voltage(1000.0);
133        let v_with_pga = adc.count_to_voltage(1000.0);
134        assert!((v_with_pga - v_no_pga / 2.0).abs() < 1e-10);
135    }
136}