embedded_devices/devices/sensirion/scd4x/
commands.rs

1use crate::devices::sensirion::commands::define_sensirion_commands;
2use embedded_interfaces::codegen::interface_objects;
3use uom::si::{
4    f64::{Length, Pressure, Ratio, ThermodynamicTemperature},
5    length::meter,
6    pressure::hectopascal,
7    ratio::{part_per_million, percent},
8    thermodynamic_temperature::degree_celsius,
9};
10
11interface_objects! {
12    /// The variant of the sensor
13    enum SensorVariant: u8{3} {
14        0b000 SCD40,
15        0b001 SCD41,
16        0b101 SCD43,
17        _ Invalid,
18    }
19
20    /// Whether data is ready to be read out
21    enum DataReadyStatus: u8{2} {
22        0 NotReady,
23        _ Ready,
24    }
25
26    /// Whether a malfunction was detected
27    enum MalfunctionStatus: u16 {
28        0 Ok,
29        _ Malfunction,
30    }
31
32    struct Measurement(size=6) {
33        /// CO2 concentration, LSB = 1 ppm
34        raw_co2_concentration: u16 = 0 => {
35            quantity: Ratio,
36            unit: part_per_million,
37            lsb: 1f64 / 1f64,
38        },
39        /// Ambient temperature, T(°C) = -45 + 175 / (2^16 - 1) * value
40        raw_temperature: u16 = 0 => {
41            quantity: ThermodynamicTemperature,
42            unit: degree_celsius,
43            from_raw: |x| -45.0 + (175.0 * x as f64) / ((1 << 16) - 1) as f64,
44            into_raw: |x| (((x + 45.0) * ((1 << 16) - 1) as f64) / 175.0) as u16,
45        },
46        /// Ambient relative humidity, RH(%) = 100 / (2^16 - 1) * value
47        raw_relative_humidity: u16 = 0 => {
48            quantity: Ratio,
49            unit: percent,
50            from_raw: |x| (100.0 * x as f64) / ((1 << 16) - 1) as f64,
51            into_raw: |x| ((x * ((1 << 16) - 1) as f64) / 100.0) as u16,
52        },
53    }
54
55    struct TemperatureOffset(size=2) {
56        /// Offset temperature, value = T_offset[°C] * (2^16 - 1) / 175
57        temperature: u16,
58    }
59
60    struct SensorAltitude(size=2) {
61        /// Sensor altitude, between 0 and 3000m. LSB = 1 meter.
62        raw_altitude: u16 = 0 => {
63            quantity: Length,
64            unit: meter,
65            lsb: 1f64 / 1f64,
66        },
67    }
68
69    struct AmbientPressure(size=2) {
70        /// Ambient pressure in hPa to be used for pressure compensation. LSB = 1 hPa.
71        raw_pressure: u16 = 1013 => {
72            quantity: Pressure,
73            unit: hectopascal,
74            lsb: 1f64 / 1f64,
75        },
76    }
77
78    struct TargetCo2Concentration(size=2) {
79        /// Target co2 concentration of the test setup. LSB = 1 ppm.
80        raw_target_co2_concentration: u16 = 400 => {
81            quantity: Ratio,
82            unit: part_per_million,
83            lsb: 1f64 / 1f64,
84        },
85    }
86
87    struct Co2Correction(size=2) {
88        /// Correction value as received from the SCD in ppm. To get the FRC correction in ppm,
89        /// subtract `0x8000` from this value.
90        ///
91        /// If the recalibration has failed this value is 0xFFFF.
92        raw_correction: u16 = 0 => {
93            quantity: Ratio,
94            unit: part_per_million,
95            from_raw: |x| (x - 0x8000) as f64,
96            into_raw: |x| x as u16 + 0x8000,
97        },
98    }
99
100    struct AutomaticSelfCalibrationConfig(size=2) {
101        _: u16{15},
102        /// Set to true to enable or false to disable the automatic CO2 measurement
103        /// self-calibration feature.
104        enable: bool,
105    }
106
107    struct AutomaticSelfCalibrationTarget(size=2) {
108        /// ASC target co2 concentration. LSB = 1 ppm.
109        raw_target_co2_concentration: u16 = 400 => {
110            quantity: Ratio,
111            unit: part_per_million,
112            lsb: 1f64 / 1f64,
113        },
114    }
115
116    struct DataReady(size=2) {
117        _: u16{14},
118        /// When no measurement is running, [`DataReadyStatus::NotReady`] will be returned.
119        data_ready: DataReadyStatus = DataReadyStatus::NotReady,
120    }
121
122    struct SerialNumber(size=6) {
123        /// 6-byte serial number
124        serial_number: [u8; 6],
125    }
126
127    struct SelfTestResult(size=2) {
128        /// Whether a malfunction was detected.
129        malfunction_status: MalfunctionStatus = MalfunctionStatus::Malfunction,
130    }
131
132    struct SensorVariantResult(size=2) {
133        /// The sensor variant.
134        variant: SensorVariant = SensorVariant::Invalid,
135        _: u16{13},
136    }
137
138    struct AutomaticSelfCalibrationInitialPeriod(size=2) {
139        /// ASC initial period in hours.
140        period_hours: u16,
141    }
142
143    struct AutomaticSelfCalibrationStandardPeriod(size=2) {
144        /// ASC standard period in hours.
145        period_hours: u16,
146    }
147}
148
149define_sensirion_commands! {
150    id_len 2;
151    marker [
152        ("sensirion-scd40", crate::devices::sensirion::scd40::SCD40Command),
153        ("sensirion-scd41", crate::devices::sensirion::scd41::SCD41Command),
154        ("sensirion-scd43", crate::devices::sensirion::scd43::SCD43Command),
155    ];
156
157    /// Starts the periodic measurement mode. The signal update interval is 5 seconds.
158    send 0x21b1 time_ms=0 StartPeriodicMeasurement();
159
160    /// Reads the sensor output. The measurement data can only be read out once per signal update
161    /// interval as the buffer is emptied upon read-out. If no data is available in the buffer, the
162    /// sensor returns a NACK. To avoid a NACK response, [`GetDataReady`] can be issued to check
163    /// data status. The I2C master can abort the read transfer with a NACK followed by a STOP
164    /// condition after any data byte if the user is not interested in subsequent data.
165    ///
166    /// May be used while measuring.
167    read 0xec05 time_ms=1 ReadMeasurement() -> Measurement;
168
169    /// Command returns a sensor running in periodic measurement mode or low power periodic measurement
170    /// mode back to the idle state, e.g. to then allow changing the sensor configuration or to save
171    /// power.
172    ///
173    /// May be used while measuring.
174    send 0x3f86 time_ms=500 StopPeriodicMeasurement();
175
176    /// Setting the temperature offset of the SCD4x inside the customer device allows the user to
177    /// optimize the RH and T output signal. The temperature offset can depend on several factors such
178    /// as the SCD4x measurement mode, self-heating of close components, the ambient temperature and
179    /// air flow. Thus, the SCD4x temperature offset should be determined after integration into the
180    /// final device and under its typical operating conditions (including the operation mode to be
181    /// used in the application) in thermal equilibrium. By default, the temperature offset is set to 4
182    /// °C. To save the setting to the EEPROM, the [`PersistSettings`] command may be issued.
183    write 0x241d time_ms=1 SetTemperatureOffset(TemperatureOffset);
184
185    /// See [`SetTemperatureOffset`] for details.
186    read 0x2318 time_ms=1 GetTemperatureOffset() -> TemperatureOffset;
187
188    /// Reading and writing the sensor altitude must be done while the SCD4x is in idle mode.
189    /// Typically, the sensor altitude is set once after device installation. To save the setting to
190    /// the EEPROM, the [`PersistSettings`] command must be issued. The default sensor altitude value
191    /// is set to 0 meters above sea level. Valid input values are between 0 - 3000 m.
192    write 0x2427 time_ms=1 SetSensorAltitude(SensorAltitude);
193
194    /// See [`SetSensorAltitude`] for details.
195    read 0x2322 time_ms=1 GetSensorAltitude() -> SensorAltitude;
196
197    /// Sets the ambient pressure value. The ambient pressure can be used for pressure
198    /// compensation in the CO2 sensor. Setting an ambient pressure overrides any pressure compensation
199    /// based on a previously set sensor altitude. Use of this command is recommended for applications
200    /// experiencing significant ambient pressure changes to ensure CO2 sensor accuracy. Valid input
201    /// values are between 700 to 1200 hPa. The default value is 1013 hPa.
202    ///
203    /// May be used while measuring.
204    write 0xe000 time_ms=1 SetAmbientPressure(AmbientPressure);
205
206    /// See [`SetAmbientPressure`] for details.
207    ///
208    /// May be used while measuring.
209    read 0xe000 time_ms=1 GetAmbientPressure() -> AmbientPressure;
210
211    /// Perform forced recalibration (FRC) of the CO2 signal. Refer to the datasheet for additional
212    /// information on how this is to be used.
213    write_read 0x362f time_ms=400 PerformForcedRecalibration(TargetCo2Concentration) -> Co2Correction;
214
215    /// Sets the status of the CO2 sensor automatic self-calibration (ASC). The CO2 sensor supports
216    /// automatic self-calibration (ASC) for long-term stability of the CO2 output. This feature can be
217    /// enabled or disabled. By default, it is enabled.
218    write 0x2416 time_ms=1 SetAutomaticSelfCalibrationEnabled(AutomaticSelfCalibrationConfig);
219
220    /// See [`SetAutomaticSelfCalibrationEnabled`] for details.
221    read 0x2313 time_ms=1 GetAutomaticSelfCalibrationEnabled() -> AutomaticSelfCalibrationConfig;
222
223    /// Sets the value of the ASC baseline target, i.e. the CO2 concentration in ppm which the ASC
224    /// algorithm will assume as lower-bound background to which the SCD4x is exposed to regularly
225    /// within one ASC period of operation. To save the setting to the EEPROM, the [`PersistSettings`]
226    /// command must be issued subsequently.
227    write 0x243a time_ms=1 SetAutomaticSelfCalibrationTarget(AutomaticSelfCalibrationTarget);
228
229    /// Gets the value of the ASC baseline target. See [`SetAutomaticSelfCalibrationTarget`] for
230    /// details.
231    read 0x233f time_ms=1 GetAutomaticSelfCalibrationTarget() -> AutomaticSelfCalibrationTarget;
232
233    /// To enable use-cases with a constrained power budget, the SCD4x features a low power periodic
234    /// measurement mode with a signal update interval of approximately 30 seconds.
235    send 0x21ac time_ms=0 StartLowPowerPeriodicMeasurement();
236
237    /// Polls the sensor for whether data from a periodic or single shot measurement is ready to be
238    /// read out.
239    ///
240    /// May be used while measuring.
241    read 0xe4b8 time_ms=1 GetDataReady() -> DataReady;
242
243    /// Configuration settings such as the temperature offset, sensor altitude and the ASC
244    /// enabled/disabled parameters are by default stored in the volatile memory (RAM) only. This
245    /// command stores the current configuration in the EEPROM of the SCD4x, ensuring the current
246    /// settings persist after power-cycling. To avoid unnecessary wear of the EEPROM, this command
247    /// should only be sent following configuration changes whose persistence is required. The EEPROM
248    /// is guaranteed to withstand at least 2000 write cycles. Note that field calibration history
249    /// (i.e. FRC and ASC) is automatically stored in a separate EEPROM dimensioned for the specified
250    /// sensor lifetime when operated continuously in either periodic measurement mode, low power
251    /// periodic measurement mode or single shot mode with 5 minute measurement interval (SCD41 and
252    /// SCD43 only).
253    send 0x3615 time_ms=800 PersistSettings();
254
255    /// Reading out the serial number can be used to identify the chip and to verify the presence of
256    /// the sensor.
257    read 0x3682 time_ms=1 GetSerialNumber() -> SerialNumber;
258
259    /// This command can be used as an end-of-line test to check the sensor functionality.
260    read 0x3615 time_ms=10_000 PerformSelfTest() -> SelfTestResult;
261
262    /// Resets all configuration settings stored in the EEPROM and erases the FRC and ASC algorithm
263    /// history.
264    send 0x3632 time_ms=1_200 FactoryReset();
265
266    /// Reinitializes the sensor by reloading user settings from EEPROM. The sensor must be in the idle
267    /// state before sending the reinit command. If the reinit command does not trigger the desired
268    /// re-initialization, a power-cycle should be applied to the SCD4x.
269    send 0x3646 time_ms=30 Reinit();
270
271    /// Reads out the SCD4x sensor variant (e.g. SCD40, SCD41 or SCD43).
272    read 0x202f time_ms=1 GetSensorVariant() -> SensorVariantResult;
273}
274
275define_sensirion_commands! {
276    id_len 2;
277    marker [
278        ("sensirion-scd41", crate::devices::sensirion::scd41::SCD41Command),
279        ("sensirion-scd43", crate::devices::sensirion::scd43::SCD43Command),
280    ];
281
282    /// On-demand measurement of CO2 concentration, relative humidity and temperature. The sensor
283    /// output is read out by using the [`ReadMeasurement`] command.
284    send 0x219d time_ms=1 MeasureSingleShot(); // NOTE: Adjusted time to 1ms; datasheet specifies 5_000ms but we want to allow polling manually using [`GetDataReady`].
285
286    /// On-demand measurement of relative humidity and temperature only, significantly reduces power
287    /// consumption. The sensor output is read out by using the read_measurement command. CO2 output is
288    /// returned as 0 ppm.
289    send 0x2196 time_ms=1 MeasureSingleShotRhtOnly(); // NOTE: Adjusted time to 1ms; datasheet specifies 50ms but we want to allow polling manually using [`GetDataReady`].
290
291    /// Put the sensor from idle to sleep to reduce current consumption. Can be used to power down when
292    /// operating the sensor in power-cycled single shot mode.
293    send 0x36e0 time_ms=1 PowerDown();
294
295    /// Wake up the sensor from sleep mode into idle mode. Note that the SCD4x does not acknowledge the
296    /// wake_up command. The sensor's idle state after wake up can be verified by reading out the
297    /// serial number.
298    send 0x36f6 time_ms=30 WakeUp();
299
300    /// Sets the duration of the initial period for ASC correction (in hours). By default, the initial
301    /// period for ASC correction is 44 hours. Allowed values are integer multiples of 4 hours. A value
302    /// of 0 results in an immediate correction. To save the setting to the EEPROM, the
303    /// [`PersistSettings`] command must be issued.
304    ///
305    /// Note: For single shot operation, this parameter always assumes a measurement interval of 5
306    /// minutes, counting the number of single shots to calculate elapsed time. If single shot
307    /// measurements are taken more / less frequently than once every 5 minutes, this parameter must be
308    /// scaled accordingly to achieve the intended period in hours (e.g. for a 10-minute measurement
309    /// interval, the scaled parameter value is obtained by multiplying the intended period in hours by
310    /// 0.5).
311    write 0x2445 time_ms=1 SetAutomaticSelfCalibrationInitialPeriod(AutomaticSelfCalibrationInitialPeriod);
312
313    /// See [`SetAutomaticSelfCalibrationInitialPeriod`] for details.
314    read 0x2340 time_ms=1 GetAutomaticSelfCalibrationInitialPeriod() -> AutomaticSelfCalibrationInitialPeriod;
315
316    /// Sets the standard period for ASC correction (in hours). By default, the standard period for ASC
317    /// correction is 156 hours. Allowed values are integer multiples of 4 hours. A value of 0 results
318    /// in an immediate correction. To save the setting to the EEPROM, the [`PersistSettings`] command
319    /// must be issued.
320    ///
321    /// Note: For single shot operation, this parameter always assumes a measurement interval of 5
322    /// minutes, counting the number of single shots to calculate elapsed time. If single shot
323    /// measurements are taken more / less frequently than once every 5 minutes, this parameter must be
324    /// scaled accordingly to achieve the intended period in hours (e.g. for a 10-minute measurement
325    /// interval, the scaled parameter value is obtained by multiplying the intended period in hours by
326    /// 0.5).
327    write 0x244e time_ms=1 SetAutomaticSelfCalibrationStandardPeriod(AutomaticSelfCalibrationStandardPeriod);
328
329    /// See [`SetAutomaticSelfCalibrationStandardPeriod`] for details.
330    read 0x234b time_ms=1 GetAutomaticSelfCalibrationStandardPeriod() -> AutomaticSelfCalibrationStandardPeriod;
331}