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