Skip to main content

veloxity_core/
battery.rs

1//! Battery-monitor ADC conversion shared by hardware board adapters.
2
3/// Full-scale count produced by a 16-bit ADC conversion.
4pub const ADC_16_BIT_FULL_SCALE: f32 = u16::MAX as f32;
5
6/// Scale and offset for one analog battery-monitor channel.
7///
8/// This follows ROSflight C's conversion order exactly:
9/// `(pin_voltage - offset) * scale_factor`.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct AnalogChannelCalibration {
12    pub scale_factor: f32,
13    pub offset: f32,
14}
15
16impl AnalogChannelCalibration {
17    pub const fn new(scale_factor: f32, offset: f32) -> Self {
18        Self {
19            scale_factor,
20            offset,
21        }
22    }
23
24    /// Apply ROSflight's multiplier semantics. Zero means "leave the current
25    /// board scale unchanged"; every nonzero value replaces it.
26    pub fn apply_multiplier(&mut self, multiplier: f32) {
27        if multiplier != 0.0 {
28            self.scale_factor = multiplier;
29        }
30    }
31
32    pub fn convert_16_bit_sample(self, adc_count: u16, vdda: f32) -> f32 {
33        let pin_voltage = f32::from(adc_count) / ADC_16_BIT_FULL_SCALE * vdda;
34        (pin_voltage - self.offset) * self.scale_factor
35    }
36}
37
38/// Calibration for the two channels exposed by a standard power monitor.
39#[derive(Debug, Clone, Copy, PartialEq)]
40pub struct BatteryMonitorCalibration {
41    pub voltage: AnalogChannelCalibration,
42    pub current: AnalogChannelCalibration,
43}
44
45impl BatteryMonitorCalibration {
46    pub const fn new(
47        voltage_scale: f32,
48        voltage_offset: f32,
49        current_scale: f32,
50        current_offset: f32,
51    ) -> Self {
52        Self {
53            voltage: AnalogChannelCalibration::new(voltage_scale, voltage_offset),
54            current: AnalogChannelCalibration::new(current_scale, current_offset),
55        }
56    }
57
58    pub fn apply_multipliers(&mut self, voltage_multiplier: f32, current_multiplier: f32) {
59        self.voltage.apply_multiplier(voltage_multiplier);
60        self.current.apply_multiplier(current_multiplier);
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn conversion_matches_rosflight_order_of_operations() {
70        let calibration = AnalogChannelCalibration::new(60.5, 0.0747);
71        let value = calibration.convert_16_bit_sample(32_768, 3.3);
72        let expected = (f32::from(32_768_u16) / 65_535.0 * 3.3 - 0.0747) * 60.5;
73        assert!((value - expected).abs() < 1.0e-5);
74    }
75
76    #[test]
77    fn nonzero_multiplier_replaces_board_scale() {
78        let mut calibration = BatteryMonitorCalibration::new(12.62, 0.0, 60.5, 0.0747);
79        calibration.apply_multipliers(7.675, 42.0);
80        assert_eq!(calibration.voltage.scale_factor, 7.675);
81        assert_eq!(calibration.current.scale_factor, 42.0);
82    }
83
84    #[test]
85    fn zero_multiplier_leaves_current_scale_unchanged() {
86        let mut calibration = BatteryMonitorCalibration::new(12.62, 0.0, 60.5, 0.0747);
87        calibration.apply_multipliers(7.675, 0.0);
88        calibration.apply_multipliers(0.0, 0.0);
89        assert_eq!(calibration.voltage.scale_factor, 7.675);
90        assert_eq!(calibration.current.scale_factor, 60.5);
91    }
92}