Skip to main content

pas_co2_rs/
regs.rs

1use num_enum::{IntoPrimitive, TryFromPrimitive};
2
3#[derive(IntoPrimitive)]
4#[repr(u8)]
5pub enum Register {
6    ProdId = 0x0,
7    SensorStatus = 0x01,
8    MeasurementRate = 0x02,
9    MeasurementMode = 0x04,
10    Co2Ppm = 0x05,
11    MeasurementStatus = 0x07,
12    InterruptConfig = 0x08,
13    AlarmThreshold = 0x09,
14    PressureReference = 0x0B,
15    CalibrationReference = 0x0D,
16    ScratchPad = 0x0F,
17    SensorReset = 0x10,
18}
19
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21#[derive(Clone, Copy, PartialEq, Debug)]
22pub struct Status {
23    /// Sensor ready bit
24    pub ready: bool,
25    /// PWM_DIS pin status
26    pub pwm_dis: bool,
27    /// Out-of-range temperature error bit
28    pub temperature_error: bool,
29    /// Out-of-range VDD12V/5V error bit
30    pub voltage_error: bool,
31    /// Communication error notification bit
32    pub communication_error: bool,
33}
34
35impl From<u8> for Status {
36    fn from(value: u8) -> Self {
37        Self {
38            ready: (value & 0b1000_0000) != 0,
39            pwm_dis: (value & 0b0100_0000) != 0,
40            temperature_error: (value & 0b0010_0000) != 0,
41            voltage_error: (value & 0b0001_0000) != 0,
42            communication_error: (value & 0b0000_1000) != 0,
43        }
44    }
45}
46
47#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48#[derive(Clone, Copy, PartialEq, Debug)]
49pub struct MeasurementStatus {
50    /// New data available in CO2PPM Register
51    pub data_ready: bool,
52    /// Pin INT has been latched to active state
53    pub int_active: bool,
54    /// Alarm notification (threshold violation occured)
55    pub alarm: bool,
56}
57
58impl From<u8> for MeasurementStatus {
59    fn from(value: u8) -> Self {
60        Self {
61            data_ready: (value & 0b0001_0000) != 0,
62            int_active: (value & 0b0000_1000) != 0,
63            alarm: (value & 0b0000_0100) != 0,
64        }
65    }
66}
67
68#[cfg_attr(feature = "defmt", derive(defmt::Format))]
69#[derive(Clone, Copy)]
70pub struct MeasurementMode {
71    /// PWM output software enable bit
72    pub pwm_out_enable: bool,
73    /// PWM mode configuration
74    pub pwm_mode: PwmMode,
75    /// Baseline offset compensation config
76    pub baseline_offset_comp: BaselineOffsetCompensation,
77    /// Sensor operating mode
78    pub operating_mode: OperatingMode,
79}
80#[cfg_attr(feature = "defmt", derive(defmt::Format))]
81#[derive(Clone, Copy)]
82pub enum PwmMode {
83    SinglePulse = 0,
84    PulseTrain = 1,
85}
86
87#[cfg_attr(feature = "defmt", derive(defmt::Format))]
88#[derive(Clone, Copy)]
89pub enum BaselineOffsetCompensation {
90    Disabled = 0b00,
91    Enabled = 0b01,
92    Forced = 0b10,
93    _Reserved = 0b11,
94}
95
96#[cfg_attr(feature = "defmt", derive(defmt::Format))]
97#[derive(Clone, Copy)]
98pub enum OperatingMode {
99    Idle = 0b00,
100    SingleShot = 0b01,
101    Continuous = 0b10,
102    _Reserved = 0b11,
103}
104
105impl Default for MeasurementMode {
106    fn default() -> Self {
107        Self::from(0x24)
108    }
109}
110
111impl From<MeasurementMode> for u8 {
112    fn from(value: MeasurementMode) -> Self {
113        (value.pwm_out_enable as u8) << 5
114            | (value.pwm_mode as u8) << 4
115            | (value.baseline_offset_comp as u8) << 2
116            | value.operating_mode as u8
117    }
118}
119
120impl From<u8> for MeasurementMode {
121    fn from(value: u8) -> Self {
122        Self {
123            pwm_out_enable: (value & 0b0010_0000) != 0,
124            pwm_mode: match (value & 0b0001_0000) >> 4 {
125                0 => PwmMode::SinglePulse,
126                _ => PwmMode::PulseTrain,
127            },
128            baseline_offset_comp: match (value & 0b0000_1100) >> 2 {
129                0b00 => BaselineOffsetCompensation::Disabled,
130                0b01 => BaselineOffsetCompensation::Enabled,
131                0b10 => BaselineOffsetCompensation::Forced,
132                _ => BaselineOffsetCompensation::_Reserved,
133            },
134            operating_mode: match value & 0b0000_0011 {
135                0b00 => OperatingMode::Idle,
136                0b01 => OperatingMode::SingleShot,
137                0b10 => OperatingMode::Continuous,
138                _ => OperatingMode::_Reserved,
139            },
140        }
141    }
142}
143
144#[cfg_attr(feature = "defmt", derive(defmt::Format))]
145#[derive(Clone, Copy, PartialEq, Debug)]
146pub struct InterruptConfig {
147    /// Pin INT electrical config: false = active low, true = active high
148    pub int_pin_active_high: bool,
149    /// Pin INT function config
150    pub int_function_config: IntFunctionConfig,
151    /// Alarm type: false = crossing down, true = crossing up
152    pub alarm_crossing_up: bool,
153}
154
155#[cfg_attr(feature = "defmt", derive(defmt::Format))]
156#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)]
157#[repr(u8)]
158pub enum IntFunctionConfig {
159    /// Pin INT is inactive
160    Inactive = 0x0,
161    /// Alarm threshold violation notification
162    Alarm = 0x1,
163    /// Data ready notification
164    DataReady = 0x2,
165    /// Busy notification
166    Busy = 0x3,
167    /// Early measurement start notification (only continuous mode)
168    EarlyMeasurementStart = 0x4,
169}
170
171impl Default for InterruptConfig {
172    fn default() -> Self {
173        Self {
174            int_pin_active_high: true,
175            int_function_config: IntFunctionConfig::Inactive,
176            alarm_crossing_up: true,
177        }
178    }
179}
180
181impl From<InterruptConfig> for u8 {
182    fn from(value: InterruptConfig) -> Self {
183        (value.int_pin_active_high as u8) << 4
184            | (value.int_function_config as u8) << 1
185            | value.alarm_crossing_up as u8
186    }
187}
188
189impl TryFrom<u8> for InterruptConfig {
190    type Error = crate::ResponseError;
191    fn try_from(value: u8) -> Result<Self, Self::Error> {
192        Ok(Self {
193            int_pin_active_high: (value & 0b0001_0000) != 0,
194            int_function_config: ((value & 0b0000_1110) >> 1)
195                .try_into()
196                .map_err(|_| Self::Error::InvalidRegisterValue)?,
197            alarm_crossing_up: (value & 0b0000_0001) != 0,
198        })
199    }
200}
201
202#[derive(IntoPrimitive)]
203#[repr(u8)]
204/// Soft reset register
205pub enum SoftReset {
206    /// Trigger a soft reset event
207    SoftReset = 0xA3,
208    /// Reset the ABOC context
209    AbocReset = 0xBC,
210    /// Save the force calibration offset to internal NVM immediately
211    SaveForceCalibNvm = 0xCF,
212    /// Disable the stepwise reactive IIR filter
213    DisableStepwiseReractiveIirFilter = 0xDF,
214    /// Reset the forced calibration correction factor
215    ResetForcedCalibCorrectionFactor = 0xFC,
216    /// Enable the stepwise reactive IIR filter (default enabled)
217    EnableStepwiseReaciveIirFilter = 0xFE,
218}
219
220#[cfg(test)]
221mod test {
222    use super::*;
223
224    #[test]
225    fn test_status_bitmask() {
226        let status = Status {
227            ready: true,
228            pwm_dis: false,
229            temperature_error: false,
230            voltage_error: true,
231            communication_error: true,
232        };
233
234        let bitmask: u8 = 0b1001_1000;
235
236        assert_eq!(status, Status::from(bitmask))
237    }
238
239    #[test]
240    fn test_measurement_status_bitmask() {
241        let status = MeasurementStatus {
242            data_ready: true,
243            int_active: false,
244            alarm: true,
245        };
246
247        let bitmask: u8 = 0b0001_0100;
248
249        assert_eq!(status, MeasurementStatus::from(bitmask));
250
251        // Check that not equal if alarm bit is flipped
252        assert_ne!(status, MeasurementStatus::from(bitmask ^ 0b0000_0100));
253    }
254
255    #[test]
256    fn test_measurement_mode_bitmask() {
257        let mode = MeasurementMode {
258            pwm_out_enable: true,                                     // 0b1
259            pwm_mode: PwmMode::SinglePulse,                           //0b0
260            baseline_offset_comp: BaselineOffsetCompensation::Forced, //0b10
261            operating_mode: OperatingMode::Continuous,                // 0b10
262        };
263
264        let bitmask: u8 = mode.into();
265
266        assert_eq!(bitmask, 0b0010_1010)
267    }
268
269    #[test]
270    fn test_interrupt_config_bitmask() {
271        let config = InterruptConfig {
272            int_pin_active_high: true,
273            int_function_config: IntFunctionConfig::DataReady,
274            alarm_crossing_up: true,
275        };
276
277        let bitmask: u8 = config.into();
278
279        assert_eq!(bitmask, 0b0001_0101);
280
281        let config_from = InterruptConfig::try_from(bitmask).unwrap();
282        assert_eq!(config, config_from);
283    }
284}