diff --git a/.github/workflows/arduino_quality_check.yml b/.github/workflows/arduino_quality_check.yml
index 4df8067..7ba7568 100644
--- a/.github/workflows/arduino_quality_check.yml
+++ b/.github/workflows/arduino_quality_check.yml
@@ -1,16 +1,18 @@
name: Quality check
on:
- pull_request:
- branches:
- - master
push:
+ pull_request:
branches:
- - master
+ - main
jobs:
arduino-quality:
uses: sensirion/.github/.github/workflows/driver.arduino.check.yml@main
with:
expect-arduino-examples: true
- lint-lib-manager-check: update
+ # change to "update" once you published the driver on Arduino library registry
+ lint-lib-manager-check: submit
+
+ code-generation-check:
+ uses: sensirion/.github/.github/workflows/driver.generated.metadata_check.yml@main
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8890155..6a916ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,60 +1,22 @@
-# Changelog
-
-All notable changes to this project will be documented in this file.
+# CHANGELOG
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## Unreleased
-
-## [0.4.0] - 2023-03-06
-
-### Added
-- Methods for ASC (Automatic Self Calibration)
-
-### Breaking changes
-- Renamed getDataReadyStatus to getDataReadyFlag
-
-### Fixed
-- RH/T conversion now uses correct constant: `2^16 - 1`
-- Fixed a few comments
-- Initializing the output variables to zero
-
-## [0.3.1] - 2021-04-30
-
-### Changed
+## [Unreleased]
-* Increase timing for single shot from 1350ms to 5000ms
-* Increase timing for self test from 5500ms to 10000ms
-
-
-## [0.3.0] - 2021-03-01
+## [1.0.0] - 2025-1-30
### Added
-- Convenience interfaces taking care of unit conversion to and from ticks.
-
-### Fixed
-- wake-up interface handles missing ACK from sensor on wake up.
-
-## [0.2.0] - 2021-02-10
-
-### Changed
-
-* Updated Sensirion Core library version from 0.4.0 to 0.4.2. This includes the
- renaming of the library header file from `SensirionCoreArduinoLibrary.h` to
- `SensirionCore.h`.
-* Define `SCD4X_I2C_ADDRESS` as hex instead of dec (unchanged value).
+- All commands according to data sheet
+## [0.1.0] - 2021-2-1
### Added
-* Added warning about limited EEPROM write cycles.
-
-## [0.1.0] - 2021-02-05
-
-Initial release
+- Initial version
+- Check latest 0.x.x version for changelog prior to version 1.0.0
-[0.3.1]: https://github.com/Sensirion/arduino-i2c-scd4x/compare/0.3.0...0.3.1
-[0.3.0]: https://github.com/Sensirion/arduino-i2c-scd4x/compare/0.2.0...0.3.0
-[0.2.0]: https://github.com/Sensirion/arduino-i2c-scd4x/compare/0.1.0...0.2.0
-[0.1.0]: https://github.com/Sensirion/arduino-i2c-scd4x/releases/tag/0.1.0
+[Unreleased]: https://github.com/Sensirion/arduino-i2c-scd4x/compare/1.0.0...HEAD
+[1.0.0]: https://github.com/Sensirion/arduino-i2c-scd4x/compare/0.1.0...1.0.0
+[0.1.0]: https://github.com/Sensirion/arduino-i2c-scd4x/releases/tag/0.1.0
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index ff20c41..2062766 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
BSD 3-Clause License
-Copyright (c) 2021, Sensirion AG
+Copyright (c) 2025, Sensirion AG
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/README.md b/README.md
index df18891..f1b9cab 100644
--- a/README.md
+++ b/README.md
@@ -1,63 +1,195 @@
-# Sensirion I2C SCD4x Arduino Library
+# Sensirion I²C SCD4X Arduino Library
-This is the Sensirion SCD4x library for Arduino using the I2C interface.
+This is the Sensirion SCD4X library for Arduino allowing you to
+communicate with a sensor of the SCD4X family over I²C.
-[
](https://sensirion.com/my-scd-ek)
+
-Click [here](https://sensirion.com/my-scd-ek) to learn more about the SCD4x
-sensor and the SCD41 Evaluation Kit Board.
+Click [here](https://sensirion.com/products/catalog/SEK-SCD41) to learn more about the Sensirion SCD4X sensor family.
-# Installation
-To install, download the latest release as .zip file and add it to your
-[Arduino IDE](http://www.arduino.cc/en/main/software) via
+Not all sensors of this driver family support all measurements.
+In case a measurement is not supported by all sensors, the products that
+support it are listed in the API description.
- Sketch => Include Library => Add .ZIP Library...
-Don't forget to **install the dependencies** listed below the same way via `Add
-.ZIP Library`
-Note: Installation via the Arduino Library Manager is coming soon.
+## Supported sensor types
-# Dependencies
+| Sensor name | I²C Addresses |
+| ------------- | -------------- |
+|[SCD40](https://sensirion.com/products/catalog/SCD40)| **0x62**|
+|[SCD41](https://sensirion.com/products/catalog/SCD41)| **0x62**|
+The following instructions and examples use a *SCD41*.
+
+
+
+## Installation of the library
+
+This library can be installed using the Arduino Library manager:
+Start the [Arduino IDE](http://www.arduino.cc/en/main/software) and open
+the Library Manager via
+
+`Sketch` ➔ `Include Library` ➔ `Manage Libraries...`
+
+Search for the `Sensirion I2C SCD4X` library in the `Filter
+your search...` field and install it by clicking the `install` button.
+
+If you cannot find it in the library manager, download the latest release as .zip file
+and add it to your [Arduino IDE](http://www.arduino.cc/en/main/software) via
+
+`Sketch` ➔ `Include Library` ➔ `Add .ZIP Library...`
+
+Don't forget to **install the dependencies** listed below the same way via library
+manager or `Add .ZIP Library`
+
+#### Dependencies
* [Sensirion Core](https://github.com/Sensirion/arduino-core)
+## Connect the sensor
+
+Use the following pin description to connect your SCD4X to the standard I²C bus of your Arduino board:
+
+
+
+| *Pin* | *Cable Color* | *Name* | *Description* | *Comments* |
+|-------|---------------|:------:|----------------|------------|
+| 1 | yellow | SCL | I2C: Serial clock input |
+| 2 | black | GND | Ground |
+| 3 | red | VDD | Supply Voltage | 2.4V to 5.5V
+| 4 | green | SDA | I2C: Serial data input / output |
+
+
+
+
+The recommended voltage is 3.3V.
+
+### Board specific wiring
+You will find pinout schematics for recommended board models below:
+
+
+
+Arduino Uno
+
+
+| *SCD4X* | *SCD4X Pin* | *Cable Color* | *Board Pin* |
+| :---: | --- | --- | --- |
+| SCL | 1 | yellow | D19/SCL |
+| GND | 2 | black | GND |
+| VDD | 3 | red | 3.3V |
+| SDA | 4 | green | D18/SDA |
+
+
+
+
+
+
+
+
+
+
+Arduino Nano
+
+
+| *SCD4X* | *SCD4X Pin* | *Cable Color* | *Board Pin* |
+| :---: | --- | --- | --- |
+| SCL | 1 | yellow | A5 |
+| GND | 2 | black | GND |
+| VDD | 3 | red | 3.3V |
+| SDA | 4 | green | A4 |
+
-# Quick Start
-1. Connect the SCD4x sensor to your Arduino board's standard I2C bus. Check
- the pinout of your Arduino board to find the correct pins. The pinout of the
- SCD4x sensor board can be found [here](https://sensirion.com/my-scd-ek)
+
+
+
- * **VDD** of the SEK-SCD41 to the **3.3V** of your Arduino board (5V is also possible)
- * **GND** of the SEK-SCD41 to the **GND** of your Arduino board
- * **SCL** of the SEK-SCD41 to the **SCL** of your Arduino board
- * **SDA** of the SEK-SCD41 to the **SDA** of your Arduino board
-2. Open the `exampleUsage` sample project within the Arduino IDE
- File => Examples => Sensirion I2C Scd4x => exampleUsage
-3. Click the `Upload` button in the Arduino IDE or
+Arduino Micro
+
- Sketch => Upload
+| *SCD4X* | *SCD4X Pin* | *Cable Color* | *Board Pin* |
+| :---: | --- | --- | --- |
+| SCL | 1 | yellow | ~D3/SCL |
+| GND | 2 | black | GND |
+| VDD | 3 | red | 3.3V |
+| SDA | 4 | green | D2/SDA |
+
+
+
+
+
+
+
+
+
+
+Arduino Mega 2560
+
+
+| *SCD4X* | *SCD4X Pin* | *Cable Color* | *Board Pin* |
+| :---: | --- | --- | --- |
+| SCL | 1 | yellow | D21/SCL |
+| GND | 2 | black | GND |
+| VDD | 3 | red | 3.3V |
+| SDA | 4 | green | D20/SDA |
+
+
+
+
+
+
+
+
+
+
+ESP32 DevKitC
+
+
+| *SCD4X* | *SCD4X Pin* | *Cable Color* | *Board Pin* |
+| :---: | --- | --- | --- |
+| SCL | 1 | yellow | GPIO 22 |
+| GND | 2 | black | GND |
+| VDD | 3 | red | 3V3 |
+| SDA | 4 | green | GPIO 21 |
+
+
+
+
+
+
+
+
+
+## Quick Start
+
+1. Install the libraries and dependencies according to [Installation of the library](#installation-of-the-library)
+
+2. Connect the SCD4X sensor to your Arduino as explained in [Connect the sensor](#connect-the-sensor)
+
+3. Open the `exampleUsage` sample project within the Arduino IDE:
+
+ `File` ➔ `Examples` ➔ `Sensirion I2C SCD4X` ➔ `exampleUsage`
+
+
+ The provided example is working with a SCD41, I²C address 0x62.
+ In order to use the code with another product or I²C address you need to change it in the code of `examples/exampleUsage`.
+ You find the list with pre-defined addresses in `src/SensirionI2CSCD4X.h`.
+
+
+5. Click the `Upload` button in the Arduino IDE or `Sketch` ➔ `Upload`
4. When the upload process has finished, open the `Serial Monitor` or `Serial
- Plotter` via the `Tools` menu to observe the measured CO2, Temperature and
- Humidity values. Note that the `Baud Rate` in the corresponding window has
- to be set to `115200 baud`.
+ Plotter` via the `Tools` menu to observe the measurement values. Note that
+ the `Baud Rate` in the used tool has to be set to `115200 baud`.
-# Contributing
+## Contributing
**Contributions are welcome!**
-We develop and test this driver using our company internal tools (version
-control, continuous integration, code review etc.) and automatically
-synchronize the master branch with GitHub. But this doesn't mean that we don't
-respond to issues or don't accept pull requests on GitHub. In fact, you're very
-welcome to open issues or create pull requests :)
-
This Sensirion library uses
[`clang-format`](https://releases.llvm.org/download.html) to standardize the
formatting of all our `.cpp` and `.h` files. Make sure your contributions are
@@ -66,12 +198,13 @@ formatted accordingly:
The `-i` flag will apply the format changes to the files listed.
```bash
-clang-format -i *.cpp *.h
+clang-format -i src/*.cpp src/*.h
```
Note that differences from this formatting will result in a failed build until
they are fixed.
+:
-# License
+## License
-See [LICENSE](LICENSE).
+See [LICENSE](LICENSE).
\ No newline at end of file
diff --git a/examples/exampleScd41SingleShot/exampleScd41SingleShot.ino b/examples/exampleScd41SingleShot/exampleScd41SingleShot.ino
new file mode 100644
index 0000000..e10c8d5
--- /dev/null
+++ b/examples/exampleScd41SingleShot/exampleScd41SingleShot.ino
@@ -0,0 +1,163 @@
+/*
+ * THIS FILE IS AUTOMATICALLY GENERATED
+ *
+ * Generator: sensirion-driver-generator 1.1.2
+ * Product: scd4x
+ * Model-Version: 2.0
+ */
+/*
+ * Copyright (c) 2025, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Sensirion AG nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include
+#include
+#include
+
+// macro definitions
+// make sure that we use the proper definition of NO_ERROR
+#ifdef NO_ERROR
+#undef NO_ERROR
+#endif
+#define NO_ERROR 0
+
+SensirionI2cScd4x sensor;
+
+static char errorMessage[64];
+static int16_t error;
+
+void print_ushort_array(uint16_t* array, uint16_t len) {
+ uint16_t i = 0;
+ Serial.print("0x");
+ for (; i < len; i++) {
+ Serial.print(array[i], HEX);
+ }
+}
+
+void setup() {
+
+ Serial.begin(115200);
+ while (!Serial) {
+ delay(100);
+ }
+ Wire.begin();
+ sensor.begin(Wire, SCD41_I2C_ADDR_62);
+
+ uint16_t serialNumber[3] = {0};
+ delay(30);
+ // Ensure sensor is in clean state
+ error = sensor.wakeUp();
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute wakeUp(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ error = sensor.stopPeriodicMeasurement();
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute stopPeriodicMeasurement(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ error = sensor.reinit();
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute reinit(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ // Read out information about the sensor
+ error = sensor.getSerialNumber(serialNumber, 3);
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute getSerialNumber(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ Serial.print("serial number: ");
+ print_ushort_array(serialNumber, 3);
+ Serial.println();
+ //
+ // If temperature offset and/or sensor altitude compensation
+ // is required, you should call the respective functions here.
+ // Check out the header file for the function definitions.
+}
+
+void loop() {
+
+ uint16_t co2Concentration = 0;
+ float temperature = 0.0;
+ float relativeHumidity = 0.0;
+ //
+ // Wake the sensor up from sleep mode.
+ //
+ error = sensor.wakeUp();
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute wakeUp(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ //
+ // Ignore first measurement after wake up.
+ //
+ error = sensor.measureSingleShot();
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute measureSingleShot(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ //
+ // Perform single shot measurement and read data.
+ //
+ error = sensor.measureAndReadSingleShot(co2Concentration, temperature,
+ relativeHumidity);
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute measureAndReadSingleShot(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ //
+ // Print results in physical units.
+ //
+ Serial.print("CO2 concentration [ppm]: ");
+ Serial.print(co2Concentration);
+ Serial.println();
+ Serial.print("Temperature [°C]: ");
+ Serial.print(temperature);
+ Serial.println();
+ Serial.print("Relative Humidity [RH]: ");
+ Serial.print(relativeHumidity);
+ Serial.println();
+ Serial.print("sleep for 5 minutes until next measurement is due");
+ Serial.println();
+ delay(300000);
+}
diff --git a/examples/exampleUsage/exampleUsage.ino b/examples/exampleUsage/exampleUsage.ino
index a35a06f..db9bcb9 100644
--- a/examples/exampleUsage/exampleUsage.ino
+++ b/examples/exampleUsage/exampleUsage.ino
@@ -1,5 +1,12 @@
/*
- * Copyright (c) 2021, Sensirion AG
+ * THIS FILE IS AUTOMATICALLY GENERATED
+ *
+ * Generator: sensirion-driver-generator 1.1.2
+ * Product: scd4x
+ * Model-Version: 2.0
+ */
+/*
+ * Copyright (c) 2025, Sensirion AG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -28,26 +35,28 @@
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
-
#include
-#include
+#include
#include
-SensirionI2CScd4x scd4x;
+// macro definitions
+// make sure that we use the proper definition of NO_ERROR
+#ifdef NO_ERROR
+#undef NO_ERROR
+#endif
+#define NO_ERROR 0
-void printUint16Hex(uint16_t value) {
- Serial.print(value < 4096 ? "0" : "");
- Serial.print(value < 256 ? "0" : "");
- Serial.print(value < 16 ? "0" : "");
- Serial.print(value, HEX);
-}
+SensirionI2cScd4x sensor;
-void printSerialNumber(uint16_t serial0, uint16_t serial1, uint16_t serial2) {
- Serial.print("Serial: 0x");
- printUint16Hex(serial0);
- printUint16Hex(serial1);
- printUint16Hex(serial2);
- Serial.println();
+static char errorMessage[64];
+static int16_t error;
+
+void print_ushort_array(uint16_t* array, uint16_t len) {
+ uint16_t i = 0;
+ Serial.print("0x");
+ for (; i < len; i++) {
+ Serial.print(array[i], HEX);
+ }
}
void setup() {
@@ -56,81 +65,112 @@ void setup() {
while (!Serial) {
delay(100);
}
-
Wire.begin();
-
- uint16_t error;
- char errorMessage[256];
-
- scd4x.begin(Wire);
-
- // stop potentially previously started measurement
- error = scd4x.stopPeriodicMeasurement();
- if (error) {
+ sensor.begin(Wire, SCD41_I2C_ADDR_62);
+
+ uint16_t serialNumber[3] = {0};
+ delay(30);
+ // Ensure sensor is in clean state
+ error = sensor.wakeUp();
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute wakeUp(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ error = sensor.stopPeriodicMeasurement();
+ if (error != NO_ERROR) {
Serial.print("Error trying to execute stopPeriodicMeasurement(): ");
- errorToString(error, errorMessage, 256);
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
+ error = sensor.reinit();
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute reinit(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
+ return;
}
-
- uint16_t serial0;
- uint16_t serial1;
- uint16_t serial2;
- error = scd4x.getSerialNumber(serial0, serial1, serial2);
- if (error) {
+ // Read out information about the sensor
+ error = sensor.getSerialNumber(serialNumber, 3);
+ if (error != NO_ERROR) {
Serial.print("Error trying to execute getSerialNumber(): ");
- errorToString(error, errorMessage, 256);
+ errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
- } else {
- printSerialNumber(serial0, serial1, serial2);
+ return;
}
-
- // Start Measurement
- error = scd4x.startPeriodicMeasurement();
- if (error) {
+ Serial.print("serial number: ");
+ print_ushort_array(serialNumber, 3);
+ Serial.println();
+ //
+ // If temperature offset and/or sensor altitude compensation
+ // is required, you should call the respective functions here.
+ // Check out the header file for the function definitions.
+ // Start periodic measurements (5sec interval)
+ error = sensor.startPeriodicMeasurement();
+ if (error != NO_ERROR) {
Serial.print("Error trying to execute startPeriodicMeasurement(): ");
- errorToString(error, errorMessage, 256);
+ errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
+ return;
}
-
- Serial.println("Waiting for first measurement... (5 sec)");
+ //
+ // If low-power mode is required, switch to the low power
+ // measurement function instead of the standard measurement
+ // function above. Check out the header file for the definition.
+ // For SCD41, you can also check out the single shot measurement example.
+ //
}
void loop() {
- uint16_t error;
- char errorMessage[256];
-
- delay(100);
- // Read Measurement
- uint16_t co2 = 0;
- float temperature = 0.0f;
- float humidity = 0.0f;
- bool isDataReady = false;
- error = scd4x.getDataReadyFlag(isDataReady);
- if (error) {
- Serial.print("Error trying to execute getDataReadyFlag(): ");
- errorToString(error, errorMessage, 256);
+ bool dataReady = false;
+ uint16_t co2Concentration = 0;
+ float temperature = 0.0;
+ float relativeHumidity = 0.0;
+ //
+ // Slow down the sampling to 0.2Hz.
+ //
+ delay(5000);
+ error = sensor.getDataReadyStatus(dataReady);
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute getDataReadyStatus(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
- if (!isDataReady) {
- return;
+ while (!dataReady) {
+ delay(100);
+ error = sensor.getDataReadyStatus(dataReady);
+ if (error != NO_ERROR) {
+ Serial.print("Error trying to execute getDataReadyStatus(): ");
+ errorToString(error, errorMessage, sizeof errorMessage);
+ Serial.println(errorMessage);
+ return;
+ }
}
- error = scd4x.readMeasurement(co2, temperature, humidity);
- if (error) {
+ //
+ // If ambient pressure compenstation during measurement
+ // is required, you should call the respective functions here.
+ // Check out the header file for the function definition.
+ error =
+ sensor.readMeasurement(co2Concentration, temperature, relativeHumidity);
+ if (error != NO_ERROR) {
Serial.print("Error trying to execute readMeasurement(): ");
- errorToString(error, errorMessage, 256);
+ errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
- } else if (co2 == 0) {
- Serial.println("Invalid sample detected, skipping.");
- } else {
- Serial.print("Co2:");
- Serial.print(co2);
- Serial.print("\t");
- Serial.print("Temperature:");
- Serial.print(temperature);
- Serial.print("\t");
- Serial.print("Humidity:");
- Serial.println(humidity);
+ return;
}
+ //
+ // Print results in physical units.
+ Serial.print("CO2 concentration [ppm]: ");
+ Serial.print(co2Concentration);
+ Serial.println();
+ Serial.print("Temperature [°C]: ");
+ Serial.print(temperature);
+ Serial.println();
+ Serial.print("Relative Humidity [RH]: ");
+ Serial.print(relativeHumidity);
+ Serial.println();
}
diff --git a/images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V.png b/images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V.png
new file mode 100644
index 0000000..942173d
Binary files /dev/null and b/images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V.png differ
diff --git a/images/Arduino-Micro-i2c-pinout-3.3V.png b/images/Arduino-Micro-i2c-pinout-3.3V.png
new file mode 100644
index 0000000..ca9ba6c
Binary files /dev/null and b/images/Arduino-Micro-i2c-pinout-3.3V.png differ
diff --git a/images/Arduino-Nano-i2c-pinout-3.3V.png b/images/Arduino-Nano-i2c-pinout-3.3V.png
new file mode 100644
index 0000000..55fd2ff
Binary files /dev/null and b/images/Arduino-Nano-i2c-pinout-3.3V.png differ
diff --git a/images/Arduino-Uno-Rev3-i2c-pinout-3.3V.png b/images/Arduino-Uno-Rev3-i2c-pinout-3.3V.png
new file mode 100644
index 0000000..a6daad7
Binary files /dev/null and b/images/Arduino-Uno-Rev3-i2c-pinout-3.3V.png differ
diff --git a/images/SCD41.png b/images/SCD41.png
new file mode 100644
index 0000000..d6f6c9c
Binary files /dev/null and b/images/SCD41.png differ
diff --git a/images/SCD41_pinout.png b/images/SCD41_pinout.png
new file mode 100644
index 0000000..bc33679
Binary files /dev/null and b/images/SCD41_pinout.png differ
diff --git a/images/esp32-devkitc-i2c-pinout-3.3V.png b/images/esp32-devkitc-i2c-pinout-3.3V.png
new file mode 100644
index 0000000..319b021
Binary files /dev/null and b/images/esp32-devkitc-i2c-pinout-3.3V.png differ
diff --git a/keywords.txt b/keywords.txt
index c6c7c9c..93f77ff 100644
--- a/keywords.txt
+++ b/keywords.txt
@@ -6,39 +6,62 @@
# Datatypes (KEYWORD1)
#######################################
-SensirionI2CScd4x KEYWORD1
+SensirionI2cScd4x KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
-startPeriodicMeasurement KEYWORD2
+
readMeasurement KEYWORD2
-stopPeriodicMeasurement KEYWORD2
-getTemperatureOffset KEYWORD2
setTemperatureOffset KEYWORD2
-getSensorAltitude KEYWORD2
-setSensorAltitude KEYWORD2
+getTemperatureOffset KEYWORD2
setAmbientPressure KEYWORD2
+getAmbientPressure KEYWORD2
+getDataReadyStatus KEYWORD2
+getSensorVariant KEYWORD2
+measureAndReadSingleShot KEYWORD2
+startPeriodicMeasurement KEYWORD2
+readMeasurementRaw KEYWORD2
+stopPeriodicMeasurement KEYWORD2
+setTemperatureOffsetRaw KEYWORD2
+getTemperatureOffsetRaw KEYWORD2
+setSensorAltitude KEYWORD2
+getSensorAltitude KEYWORD2
+setAmbientPressureRaw KEYWORD2
+getAmbientPressureRaw KEYWORD2
performForcedRecalibration KEYWORD2
-getAutomaticSelfCalibration KEYWORD2
-setAutomaticSelfCalibration KEYWORD2
+setAutomaticSelfCalibrationEnabled KEYWORD2
+getAutomaticSelfCalibrationEnabled KEYWORD2
+setAutomaticSelfCalibrationTarget KEYWORD2
+getAutomaticSelfCalibrationTarget KEYWORD2
startLowPowerPeriodicMeasurement KEYWORD2
-getDataReadyStatus KEYWORD2
+getDataReadyStatusRaw KEYWORD2
persistSettings KEYWORD2
getSerialNumber KEYWORD2
performSelfTest KEYWORD2
performFactoryReset KEYWORD2
reinit KEYWORD2
+getSensorVariantRaw KEYWORD2
measureSingleShot KEYWORD2
measureSingleShotRhtOnly KEYWORD2
powerDown KEYWORD2
wakeUp KEYWORD2
+setAutomaticSelfCalibrationInitialPeriod KEYWORD2
+getAutomaticSelfCalibrationInitialPeriod KEYWORD2
+setAutomaticSelfCalibrationStandardPeriod KEYWORD2
+getAutomaticSelfCalibrationStandardPeriod KEYWORD2
+signalTemperature KEYWORD2
+signalRelativeHumidity KEYWORD2
+signalCo2Concentration KEYWORD2
+signalTemperatureOffset KEYWORD2
+signalAmbientPressure KEYWORD2
+
#######################################
# Instances (KEYWORD2)
#######################################
-scd4x KEYWORD2
+sensor KEYWORD2
#######################################
# Constants (LITERAL1)
-#######################################
+#######################################
\ No newline at end of file
diff --git a/library.properties b/library.properties
index 50f3128..97b068d 100644
--- a/library.properties
+++ b/library.properties
@@ -1,10 +1,11 @@
name=Sensirion I2C SCD4x
-version=0.4.0
+version=1.0.0
author=Sensirion
maintainer=Sensirion
sentence=Library for the SCD4X sensor family by Sensirion
-paragraph=Enables you to use the SCD40 and SCD41 sensors via I2C.
+paragraph=Enables you to use the SCD4X sensor family via I2C.
url=https://github.com/Sensirion/arduino-i2c-scd4x
category=Sensors
+architectures=*
depends=Sensirion Core
-includes=SensirionI2CScd4x.h
+includes=SensirionI2cScd4x.h
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..036e3c4
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,7 @@
+# driver generation metadata
+generator_version: 1.1.2
+model_version: '2.0'
+dg_status: released
+is_manually_modified: false
+first_generated: '2021-01-21 10:54'
+last_generated: '2025-01-29 12:04'
diff --git a/src/SensirionI2cScd4x.cpp b/src/SensirionI2cScd4x.cpp
new file mode 100644
index 0000000..773eb28
--- /dev/null
+++ b/src/SensirionI2cScd4x.cpp
@@ -0,0 +1,771 @@
+/*
+ * THIS FILE IS AUTOMATICALLY GENERATED
+ *
+ * Generator: sensirion-driver-generator 1.1.2
+ * Product: scd4x
+ * Model-Version: 2.0
+ */
+/*
+ * Copyright (c) 2025, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Sensirion AG nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "SensirionI2cScd4x.h"
+#include
+
+// make sure that we use the proper definition of NO_ERROR
+#ifdef NO_ERROR
+#undef NO_ERROR
+#endif
+#define NO_ERROR 0
+
+#define ROUND(x) ((int32_t)((x) + 0.5))
+
+static uint8_t communication_buffer[9] = {0};
+
+SensirionI2cScd4x::SensirionI2cScd4x() {
+}
+
+float SensirionI2cScd4x::signalTemperature(uint16_t rawTemperature) {
+ float temperature = 0.0;
+ temperature = -45.0 + ((175.0 * rawTemperature) / 65535.0);
+ return temperature;
+}
+
+float SensirionI2cScd4x::signalRelativeHumidity(uint16_t rawRelativeHumidity) {
+ float relativeHumidity = 0.0;
+ relativeHumidity = (100.0 * rawRelativeHumidity) / 65535.0;
+ return relativeHumidity;
+}
+
+uint16_t
+SensirionI2cScd4x::signalCo2Concentration(uint16_t rawCo2Concentration) {
+ uint16_t co2Concentration = 0;
+ co2Concentration = rawCo2Concentration;
+ return co2Concentration;
+}
+
+float SensirionI2cScd4x::signalTemperatureOffset(
+ uint16_t rawTemperatureOffset) {
+ float temperatureOffset = 0.0;
+ temperatureOffset = (175 * rawTemperatureOffset) / 65535.0;
+ return temperatureOffset;
+}
+
+uint32_t SensirionI2cScd4x::signalAmbientPressure(uint16_t rawAmbientPressure) {
+ uint32_t ambientPressure = 0;
+ ambientPressure = (uint32_t)rawAmbientPressure * 100;
+ return ambientPressure;
+}
+
+int16_t SensirionI2cScd4x::readMeasurement(uint16_t& aCo2Concentration,
+ float& aTemperature,
+ float& aRelativeHumidity) {
+ uint16_t rawCo2Concentration = 0;
+ uint16_t rawTemperature = 0;
+ uint16_t rawRelativeHumidity = 0;
+ int16_t localError = 0;
+ localError = readMeasurementRaw(rawCo2Concentration, rawTemperature,
+ rawRelativeHumidity);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ aCo2Concentration =
+ SensirionI2cScd4x::signalCo2Concentration(rawCo2Concentration);
+ aTemperature = SensirionI2cScd4x::signalTemperature(rawTemperature);
+ aRelativeHumidity =
+ SensirionI2cScd4x::signalRelativeHumidity(rawRelativeHumidity);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::setTemperatureOffset(float temperatureOffset) {
+ int16_t localError = 0;
+ uint16_t rawTemperatureOffset =
+ (uint16_t)ROUND((temperatureOffset * 65535.0) / 175.0);
+ localError = setTemperatureOffsetRaw(rawTemperatureOffset);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getTemperatureOffset(float& aTemperatureOffset) {
+ uint16_t rawTemperatureOffset = 0;
+ int16_t localError = 0;
+ localError = getTemperatureOffsetRaw(rawTemperatureOffset);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ aTemperatureOffset =
+ SensirionI2cScd4x::signalTemperatureOffset(rawTemperatureOffset);
+
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::setAmbientPressure(uint32_t ambientPressure) {
+ int16_t localError = 0;
+ uint16_t rawAmbientPressure = (uint16_t)ROUND(ambientPressure / 100.0);
+ localError = setAmbientPressureRaw(rawAmbientPressure);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getAmbientPressure(uint32_t& aAmbientPressure) {
+ uint16_t rawAmbientPressure = 0;
+ int16_t localError = 0;
+ localError = getAmbientPressureRaw(rawAmbientPressure);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ aAmbientPressure =
+ SensirionI2cScd4x::signalAmbientPressure(rawAmbientPressure);
+
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getDataReadyStatus(bool& arg0) {
+ uint16_t dataReadyStatus = 0;
+ int16_t localError = 0;
+ localError = getDataReadyStatusRaw(dataReadyStatus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ arg0 = (dataReadyStatus & 2047) != 0;
+ ;
+ return localError;
+}
+
+int16_t
+SensirionI2cScd4x::getSensorVariant(SCD4xSensorVariant& aSensorVariant) {
+ uint16_t rawSensorVariant = 0;
+ int16_t localError = 0;
+ localError = getSensorVariantRaw(rawSensorVariant);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ uint16_t variant = (uint16_t)(rawSensorVariant & 4);
+ if (variant == 0) {
+ aSensorVariant = SCD4X_SENSOR_VARIANT_SCD40;
+ ;
+ return localError;
+ } else if (variant == 1) {
+ aSensorVariant = SCD4X_SENSOR_VARIANT_SCD41;
+ ;
+ return localError;
+ }
+ aSensorVariant = SCD4X_SENSOR_VARIANT_UNKNOWN;
+ ;
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::measureAndReadSingleShot(uint16_t& aCo2Concentration,
+ float& aTemperature,
+ float& aRelativeHumidity) {
+ bool dataReady = false;
+ int16_t localError = 0;
+ localError = measureSingleShot();
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError = getDataReadyStatus(dataReady);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ while (!dataReady) {
+ delay(100);
+ localError = getDataReadyStatus(dataReady);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ }
+ localError =
+ readMeasurement(aCo2Concentration, aTemperature, aRelativeHumidity);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::startPeriodicMeasurement() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x21b1, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::readMeasurementRaw(uint16_t& co2Concentration,
+ uint16_t& temperature,
+ uint16_t& relativeHumidity) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0xec05, buffer_ptr, 9);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 9);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 9,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(co2Concentration);
+ localError |= rxFrame.getUInt16(temperature);
+ localError |= rxFrame.getUInt16(relativeHumidity);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::stopPeriodicMeasurement() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x3f86, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(500);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::setTemperatureOffsetRaw(uint16_t offsetTemperature) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x241d, buffer_ptr, 5);
+ localError |= txFrame.addUInt16(offsetTemperature);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ return localError;
+}
+
+int16_t
+SensirionI2cScd4x::getTemperatureOffsetRaw(uint16_t& offsetTemperature) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x2318, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(offsetTemperature);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::setSensorAltitude(uint16_t sensorAltitude) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x2427, buffer_ptr, 5);
+ localError |= txFrame.addUInt16(sensorAltitude);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getSensorAltitude(uint16_t& sensorAltitude) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x2322, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(sensorAltitude);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::setAmbientPressureRaw(uint16_t ambientPressure) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0xe000, buffer_ptr, 5);
+ localError |= txFrame.addUInt16(ambientPressure);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getAmbientPressureRaw(uint16_t& ambientPressure) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0xe000, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(ambientPressure);
+ return localError;
+}
+
+int16_t
+SensirionI2cScd4x::performForcedRecalibration(uint16_t targetCO2Concentration,
+ uint16_t& frcCorrection) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x362f, buffer_ptr, 5);
+ localError |= txFrame.addUInt16(targetCO2Concentration);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(400);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 5);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(frcCorrection);
+ return localError;
+}
+
+int16_t
+SensirionI2cScd4x::setAutomaticSelfCalibrationEnabled(uint16_t ascEnabled) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x2416, buffer_ptr, 5);
+ localError |= txFrame.addUInt16(ascEnabled);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ return localError;
+}
+
+int16_t
+SensirionI2cScd4x::getAutomaticSelfCalibrationEnabled(uint16_t& ascEnabled) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x2313, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(ascEnabled);
+ return localError;
+}
+
+int16_t
+SensirionI2cScd4x::setAutomaticSelfCalibrationTarget(uint16_t ascTarget) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x243a, buffer_ptr, 5);
+ localError |= txFrame.addUInt16(ascTarget);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ return localError;
+}
+
+int16_t
+SensirionI2cScd4x::getAutomaticSelfCalibrationTarget(uint16_t& ascTarget) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x233f, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(ascTarget);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::startLowPowerPeriodicMeasurement() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x21ac, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getDataReadyStatusRaw(uint16_t& dataReadyStatus) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0xe4b8, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(dataReadyStatus);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::persistSettings() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x3615, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(800);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getSerialNumber(uint16_t serialNumber[],
+ uint16_t serialNumberSize) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x3682, buffer_ptr, 9);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 9);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 9,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |=
+ rxFrame.getBytes((uint8_t*)serialNumber, (serialNumberSize * 2));
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::performSelfTest(uint16_t& sensorStatus) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x3639, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(10000);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(sensorStatus);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::performFactoryReset() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x3632, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1200);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::reinit() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x3646, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(30);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getSensorVariantRaw(uint16_t& sensorVariant) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x202f, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(sensorVariant);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::measureSingleShot() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x219d, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(5000);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::measureSingleShotRhtOnly() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x2196, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(50);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::powerDown() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x36e0, buffer_ptr, 2);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::wakeUp() {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x36f6, buffer_ptr, 2);
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ delay(30);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::setAutomaticSelfCalibrationInitialPeriod(
+ uint16_t ascInitialPeriod) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x2445, buffer_ptr, 5);
+ localError |= txFrame.addUInt16(ascInitialPeriod);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getAutomaticSelfCalibrationInitialPeriod(
+ uint16_t& ascInitialPeriod) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x2340, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(ascInitialPeriod);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::setAutomaticSelfCalibrationStandardPeriod(
+ uint16_t ascStandardPeriod) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x244e, buffer_ptr, 5);
+ localError |= txFrame.addUInt16(ascStandardPeriod);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ return localError;
+}
+
+int16_t SensirionI2cScd4x::getAutomaticSelfCalibrationStandardPeriod(
+ uint16_t& ascStandardPeriod) {
+ int16_t localError = NO_ERROR;
+ uint8_t* buffer_ptr = communication_buffer;
+ SensirionI2CTxFrame txFrame =
+ SensirionI2CTxFrame::createWithUInt16Command(0x234b, buffer_ptr, 3);
+ localError =
+ SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ delay(1);
+ SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
+ localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
+ rxFrame, *_i2cBus);
+ if (localError != NO_ERROR) {
+ return localError;
+ }
+ localError |= rxFrame.getUInt16(ascStandardPeriod);
+ return localError;
+}
+
+void SensirionI2cScd4x::begin(TwoWire& i2cBus, uint8_t i2cAddress) {
+ _i2cBus = &i2cBus;
+ _i2cAddress = i2cAddress;
+}
diff --git a/src/SensirionI2cScd4x.h b/src/SensirionI2cScd4x.h
new file mode 100644
index 0000000..147d261
--- /dev/null
+++ b/src/SensirionI2cScd4x.h
@@ -0,0 +1,868 @@
+/*
+ * THIS FILE IS AUTOMATICALLY GENERATED
+ *
+ * Generator: sensirion-driver-generator 1.1.2
+ * Product: scd4x
+ * Model-Version: 2.0
+ */
+/*
+ * Copyright (c) 2025, Sensirion AG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Sensirion AG nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SENSIRIONI2CSCD4X_H
+#define SENSIRIONI2CSCD4X_H
+
+#include
+#include
+
+#define SCD40_I2C_ADDR_62 0x62
+#define SCD41_I2C_ADDR_62 0x62
+
+typedef enum {
+ SCD4X_START_PERIODIC_MEASUREMENT_CMD_ID = 0x21b1,
+ SCD4X_READ_MEASUREMENT_RAW_CMD_ID = 0xec05,
+ SCD4X_STOP_PERIODIC_MEASUREMENT_CMD_ID = 0x3f86,
+ SCD4X_SET_TEMPERATURE_OFFSET_RAW_CMD_ID = 0x241d,
+ SCD4X_GET_TEMPERATURE_OFFSET_RAW_CMD_ID = 0x2318,
+ SCD4X_SET_SENSOR_ALTITUDE_CMD_ID = 0x2427,
+ SCD4X_GET_SENSOR_ALTITUDE_CMD_ID = 0x2322,
+ SCD4X_SET_AMBIENT_PRESSURE_RAW_CMD_ID = 0xe000,
+ SCD4X_GET_AMBIENT_PRESSURE_RAW_CMD_ID = 0xe000,
+ SCD4X_PERFORM_FORCED_RECALIBRATION_CMD_ID = 0x362f,
+ SCD4X_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED_CMD_ID = 0x2416,
+ SCD4X_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED_CMD_ID = 0x2313,
+ SCD4X_SET_AUTOMATIC_SELF_CALIBRATION_TARGET_CMD_ID = 0x243a,
+ SCD4X_GET_AUTOMATIC_SELF_CALIBRATION_TARGET_CMD_ID = 0x233f,
+ SCD4X_START_LOW_POWER_PERIODIC_MEASUREMENT_CMD_ID = 0x21ac,
+ SCD4X_GET_DATA_READY_STATUS_RAW_CMD_ID = 0xe4b8,
+ SCD4X_PERSIST_SETTINGS_CMD_ID = 0x3615,
+ SCD4X_GET_SERIAL_NUMBER_CMD_ID = 0x3682,
+ SCD4X_PERFORM_SELF_TEST_CMD_ID = 0x3639,
+ SCD4X_PERFORM_FACTORY_RESET_CMD_ID = 0x3632,
+ SCD4X_REINIT_CMD_ID = 0x3646,
+ SCD4X_GET_SENSOR_VARIANT_RAW_CMD_ID = 0x202f,
+ SCD4X_MEASURE_SINGLE_SHOT_CMD_ID = 0x219d,
+ SCD4X_MEASURE_SINGLE_SHOT_RHT_ONLY_CMD_ID = 0x2196,
+ SCD4X_POWER_DOWN_CMD_ID = 0x36e0,
+ SCD4X_WAKE_UP_CMD_ID = 0x36f6,
+ SCD4X_SET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD_CMD_ID = 0x2445,
+ SCD4X_GET_AUTOMATIC_SELF_CALIBRATION_INITIAL_PERIOD_CMD_ID = 0x2340,
+ SCD4X_SET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD_CMD_ID = 0x244e,
+ SCD4X_GET_AUTOMATIC_SELF_CALIBRATION_STANDARD_PERIOD_CMD_ID = 0x234b,
+} SCD4xCmdId;
+
+typedef enum {
+ SCD4X_SENSOR_VARIANT_UNKNOWN = 0,
+ SCD4X_SENSOR_VARIANT_SCD40 = 1,
+ SCD4X_SENSOR_VARIANT_SCD41 = 2,
+} SCD4xSensorVariant;
+
+class SensirionI2cScd4x {
+ public:
+ SensirionI2cScd4x();
+ /**
+ * @brief Initializes the SCD4x class.
+ *
+ * @param i2cBus Arduino stream object to be used for communication.
+ */
+ void begin(TwoWire& i2cBus, uint8_t i2cAddress);
+
+ /**
+ * @brief Read CO₂, temperature, and humidity measurements in physical
+ * units.
+ *
+ * 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, the get_data_ready_status 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.
+ *
+ * @param[out] aCo2Concentration CO₂ concentration in ppm
+ * @param[out] aTemperature Temperature in °C
+ * @param[out] aRelativeHumidity Relative humidity in %RH
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t readMeasurement(uint16_t& aCo2Concentration, float& aTemperature,
+ float& aRelativeHumidity);
+
+ /**
+ * @brief Set the temperature compensation offset.
+ *
+ * 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 persist_settings command may be issued.
+ * Equation (1) details how the characteristic temperature offset can be
+ * calculated using the current temperature output of the sensor (TSCD4x), a
+ * reference temperature value (TReference), and the previous temperature
+ * offset (Toffset_pervious) obtained using the get_temperature_offset_raw
+ * command:
+ *
+ * Toffset_actual = TSCD4x - TReference + Toffset_pervious.
+ *
+ * Recommended temperature offset values are between 0 °C and 20 °C. The
+ * temperature offset does not impact the accuracy of the CO2 output.
+ *
+ * @param[in] temperatureOffset Temperature offset value in °C
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t setTemperatureOffset(float temperatureOffset);
+
+ /**
+ * @brief Get the temperature compensation offset used by the sensor in °C.
+ *
+ * @param[out] aTemperatureOffset Temperature in °C
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getTemperatureOffset(float& aTemperatureOffset);
+
+ /**
+ * @brief Set the ambient pressure around the sensor.
+ *
+ * The set_ambient_pressure command can be sent during periodic measurements
+ * to enable continuous pressure compensation. Note that setting an ambient
+ * pressure overrides any pressure compensation based on a previously set
+ * sensor altitude. Use of this command is highly recommended for
+ * applications experiencing significant ambient pressure changes to ensure
+ * sensor accuracy. Valid input values are between 70000 - 120000 Pa. The
+ * default value is 101300 Pa.
+ *
+ * @param[in] ambientPressure Ambient pressure around the sensor in Pa
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t setAmbientPressure(uint32_t ambientPressure);
+
+ /**
+ * @brief Get the ambient pressure around the sensor.
+ *
+ * @param[out] aAmbientPressure Pressure in Pa
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getAmbientPressure(uint32_t& aAmbientPressure);
+
+ /**
+ * @brief Read if data is ready.
+ *
+ * Polls the sensor for whether data from a periodic or single shot
+ * measurement is ready to be read out.
+ *
+ * @param[out] arg0
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getDataReadyStatus(bool& arg0);
+
+ /**
+ * @brief Reads out the SCD4x sensor variant.
+ *
+ * @param[out] aSensorVariant
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getSensorVariant(SCD4xSensorVariant& aSensorVariant);
+
+ /**
+ * @brief Start a single shot measurement and read out the data when ready
+ *
+ * @param[out] aCo2Concentration CO₂ concentration in ppm
+ * @param[out] aTemperature Temperature in °C
+ * @param[out] aRelativeHumidity Relative humidity in %RH
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t measureAndReadSingleShot(uint16_t& aCo2Concentration,
+ float& aTemperature,
+ float& aRelativeHumidity);
+
+ /**
+ * @brief Start periodic measurement mode.
+ *
+ * Starts the periodic measurement mode. The signal update interval is 5
+ * seconds.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t startPeriodicMeasurement();
+
+ /**
+ * @brief Read CO₂, temperature, and humidity measurements raw values.
+ *
+ * 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, the get_data_ready_status 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.
+ *
+ * @param[out] co2Concentration CO₂ concentration in ppm
+ * @param[out] temperature Convert to degrees celsius by (175 * value /
+ * 65535) - 45
+ * @param[out] relativeHumidity Convert to relative humidity in % by (100 *
+ * value / 65535)
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t readMeasurementRaw(uint16_t& co2Concentration,
+ uint16_t& temperature,
+ uint16_t& relativeHumidity);
+
+ /**
+ * @brief Stop periodic measurement to change the sensor configuration or to
+ * save power.
+ *
+ * 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.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t stopPeriodicMeasurement();
+
+ /**
+ * @brief Set the temperature compensation offset (raw value).
+ *
+ * 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 persist_settings command may be issued.
+ * Equation (1) details how the characteristic temperature offset can be
+ * calculated using the current temperature output of the sensor (TSCD4x), a
+ * reference temperature value (TReference), and the previous temperature
+ * offset (Toffset_pervious) obtained using the get_temperature_offset_raw
+ * command:
+ *
+ * Toffset_actual = TSCD4x - TReference + Toffset_pervious.
+ *
+ * Recommended temperature offset values are between 0 °C and 20 °C. The
+ * temperature offset does not impact the accuracy of the CO2 output.
+ *
+ * @param[in] offsetTemperature Temperature offset. Convert Toffset in °C to
+ * value by: (Toffset * 65535 / 175)
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ *
+ * Example:
+ * --------
+ *
+ * @code{.cpp}
+ *
+ * int16_t localError = 0;
+ * localError = sensor.setTemperatureOffsetRaw(1498);
+ * if (localError != NO_ERROR) {
+ * return;
+ * }
+ *
+ * @endcode
+ *
+ */
+ int16_t setTemperatureOffsetRaw(uint16_t offsetTemperature);
+
+ /**
+ * @brief Get the raw temperature compensation offset used by the sensor.
+ *
+ * @param[out] offsetTemperature Convert to °C by (175 * value / 65535)
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getTemperatureOffsetRaw(uint16_t& offsetTemperature);
+
+ /**
+ * @brief Set the altitude of the sensor (in meters above sea level).
+ *
+ * Typically, the sensor altitude is set once after device installation. To
+ * save the setting to the EEPROM, the persist_settings command must be
+ * issued. The default sensor altitude value is set to 0 meters above sea
+ * level. Note that setting a sensor altitude to the sensor overrides any
+ * pressure compensation based on a previously set ambient pressure.
+ *
+ * @param[in] sensorAltitude Sensor altitude in meters above sea level.
+ * Valid input values are between 0 - 3000 m.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ *
+ * Example:
+ * --------
+ *
+ * @code{.cpp}
+ *
+ * int16_t localError = 0;
+ * localError = sensor.setSensorAltitude(0);
+ * if (localError != NO_ERROR) {
+ * return;
+ * }
+ *
+ * @endcode
+ *
+ */
+ int16_t setSensorAltitude(uint16_t sensorAltitude);
+
+ /**
+ * @brief Get the sensor altitude used by the sensor.
+ *
+ * @param[out] sensorAltitude Sensor altitude used by the sensor in meters
+ * above sea level.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getSensorAltitude(uint16_t& sensorAltitude);
+
+ /**
+ * @brief Set the raw ambient pressure value.
+ *
+ * The set_ambient_pressure command can be sent during periodic measurements
+ * to enable continuous pressure compensation. Note that setting an ambient
+ * pressure overrides any pressure compensation based on a previously set
+ * sensor altitude. Use of this command is highly recommended for
+ * applications experiencing significant ambient pressure changes to ensure
+ * sensor accuracy. Valid input values are between 70000 - 120000 Pa. The
+ * default value is 101300 Pa.
+ *
+ * @param[in] ambientPressure Convert ambient_pressure in hPa to Pa by
+ * ambient_pressure / 100.
+ *
+ * @note Available during measurements.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ *
+ * Example:
+ * --------
+ *
+ * @code{.cpp}
+ *
+ * int16_t localError = 0;
+ * localError = sensor.setAmbientPressureRaw(1013);
+ * if (localError != NO_ERROR) {
+ * return;
+ * }
+ *
+ * @endcode
+ *
+ */
+ int16_t setAmbientPressureRaw(uint16_t ambientPressure);
+
+ /**
+ * @brief Get the ambient pressure around the sensor.
+ *
+ * @param[out] ambientPressure Convert to Pa by value = ambient_pressure *
+ * 100.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getAmbientPressureRaw(uint16_t& ambientPressure);
+
+ /**
+ * @brief Perform a forced recalibration (FRC) of the CO₂ concentration.
+ *
+ * To successfully conduct an accurate FRC, the following steps need to be
+ * carried out:
+ *
+ * 1. Operate the SCD4x in the operation mode later used for normal sensor
+ * operation (e.g. periodic measurement) for at least 3 minutes in an
+ * environment with a homogenous and constant CO2 concentration. The sensor
+ * must be operated at the voltage desired for the application when
+ * performing the FRC sequence. 2. Issue the stop_periodic_measurement
+ * command. 3. Issue the perform_forced_recalibration command.
+ *
+ * A return value of 0xffff indicates that the FRC has failed because the
+ * sensor was not operated before sending the command.
+ *
+ * @param[in] targetCO2Concentration Target CO₂ concentration in ppm CO₂.
+ * @param[out] frcCorrection Convert to FRC correction in ppm CO₂ by
+ * frc_correction - 0x8000. A return value of 0xFFFF indicates that the FRC
+ * has failed because the sensor was not operated before sending the
+ * command.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t performForcedRecalibration(uint16_t targetCO2Concentration,
+ uint16_t& frcCorrection);
+
+ /**
+ * @brief Enable or disable automatic self calibration (ASC).
+ *
+ * Sets the current state (enabled / disabled) of the ASC. By default, ASC
+ * is enabled. To save the setting to the EEPROM, the persist_settings
+ * command must be issued. The ASC enables excellent long-term stability of
+ * SCD4x without the need for regular user intervention. The algorithm
+ * leverages the sensor's measurement history and the assumption of exposure
+ * of the sensor to a known minimum background CO₂ concentration at least
+ * once over a period of cumulative operation. By default, the ASC algorithm
+ * assumes that the sensor is exposed to outdoor fresh air at 400 ppm CO₂
+ * concentration at least once per week of accumulated operation using one
+ * of the following measurement modes for at least 4 hours without
+ * interruption at a time: periodic measurement mode, low power periodic
+ * measurement mode or single shot mode with a measurement interval of 5
+ * minutes (SCD41 only).
+ *
+ * @param[in] ascEnabled 1 enables ASC, 0 disables ASC.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ *
+ * Example:
+ * --------
+ *
+ * @code{.cpp}
+ *
+ * int16_t localError = 0;
+ * localError = sensor.setAutomaticSelfCalibrationEnabled(1);
+ * if (localError != NO_ERROR) {
+ * return;
+ * }
+ *
+ * @endcode
+ *
+ */
+ int16_t setAutomaticSelfCalibrationEnabled(uint16_t ascEnabled);
+
+ /**
+ * @brief Check if automatic self calibration (ASC) is enabled.
+ *
+ * @param[out] ascEnabled 1 if ASC is enabled, 0 if ASC is disabled.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getAutomaticSelfCalibrationEnabled(uint16_t& ascEnabled);
+
+ /**
+ * @brief Set the value of ASC baseline target in ppm.
+ *
+ * Sets the value of the ASC baseline target, i.e. the CO₂ 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 persist_settings
+ * command must be issued subsequently. The factory default value is 400
+ * ppm.
+ *
+ * @param[in] ascTarget ASC baseline value in ppm CO₂
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ *
+ * Example:
+ * --------
+ *
+ * @code{.cpp}
+ *
+ * int16_t localError = 0;
+ * localError = sensor.setAutomaticSelfCalibrationTarget(400);
+ * if (localError != NO_ERROR) {
+ * return;
+ * }
+ *
+ * @endcode
+ *
+ */
+ int16_t setAutomaticSelfCalibrationTarget(uint16_t ascTarget);
+
+ /**
+ * @brief Reads out the ASC baseline target concentration parameter.
+ *
+ * @param[out] ascTarget ASC baseline target concentration parameter in ppm
+ * CO₂.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getAutomaticSelfCalibrationTarget(uint16_t& ascTarget);
+
+ /**
+ * @brief Start a low-power periodic measurement (interval 30 s).
+ *
+ * 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. The low power periodic measurement mode is
+ * initiated using the start_low_power_periodic_measurement command and
+ * read-out in a similar manner as the periodic measurement mode using the
+ * read_measurement command. To periodically check whether a new measurement
+ * result is available for read out, the get_data_ready_status command can
+ * be used to synchronize to the sensor's internal measurement interval as
+ * an alternative to relying on the ACK/NACK status of the
+ * read_measurement_command.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t startLowPowerPeriodicMeasurement();
+
+ /**
+ * @brief Read if data is ready.
+ *
+ * Polls the sensor for whether data from a periodic or single shot
+ * measurement is ready to be read out.
+ *
+ * @param[out] dataReadyStatus If one or more of the 11 least significant
+ * bits are 1, then the data is ready.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getDataReadyStatusRaw(uint16_t& dataReadyStatus);
+
+ /**
+ * @brief Store volatile sensor settings in the EEPROM.
+ *
+ * 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. The persist_settings 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, the persist_settings 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 only).
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t persistSettings();
+
+ /**
+ * @brief Read the sensor's unique serial number.
+ *
+ * Reading out the serial number can be used to identify the chip and to
+ * verify the presence of the sensor. The get_serial_number command returns
+ * 3 words, and every word is followed by an 8-bit CRC checksum. Together,
+ * the 3 words constitute a unique serial number with a length of 48 bits
+ * (in big endian format).
+ *
+ * @param[out] serialNumber 48-bit unique serial number of the sensor.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getSerialNumber(uint16_t serialNumber[], uint16_t serialNumberSize);
+
+ /**
+ * @brief Perform self test to assess sensor functionality and power supply.
+ *
+ * Can be used as an end-of-line test to check the sensor functionality.
+ *
+ * @param[out] sensorStatus If sensor status is equal to 0, no malfunction
+ * has been detected.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t performSelfTest(uint16_t& sensorStatus);
+
+ /**
+ * @brief Perform factory reset to erase the settings stored in the EEPROM.
+ *
+ * The perform_factory_reset command resets all configuration settings
+ * stored in the EEPROM and erases the FRC and ASC algorithm history.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t performFactoryReset();
+
+ /**
+ * @brief Reinitialize the sensor by reloading the settings from the EEPROM.
+ *
+ * The reinit command reinitialize 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.
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t reinit();
+
+ /**
+ * @brief Reads out the SCD4x sensor variant.
+ *
+ * @param[out] sensorVariant Bits[15…12] = 0000 → SCD40 Bits[15…12] = 0001 →
+ * SCD41
+ *
+ * @note This command is only available in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t getSensorVariantRaw(uint16_t& sensorVariant);
+
+ /**
+ * @brief On-demand measurement of the CO₂ concentration, temperature, and
+ * humidity.
+ *
+ * The sensor output is read out by using the read_measurement command. The
+ * fastest possible sampling interval for single shot measurements is 5
+ * seconds. The ASC is enabled by default in single shot operation and
+ * optimized for single shot measurements performed every 5 minutes. For
+ * more details about single shot measurements and optimization of power
+ * consumption please refer to the datasheet.
+ *
+ * @note This command is only available for SCD41.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t measureSingleShot();
+
+ /**
+ * @brief On-demand measurement of the temperature and humidity only.
+ *
+ * For more details about single shot measurements and optimization of power
+ * consumption please refer to the datasheet.
+ *
+ * @note This command is only available for SCD41.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t measureSingleShotRhtOnly();
+
+ /**
+ * @brief Put the sensor into sleep mode from idle mode.
+ *
+ * Put the sensor from idle to sleep to reduce power consumption. Can be
+ * used to power down when operating the sensor in power-cycled single shot
+ * mode.
+ *
+ * @note This command is only available in idle mode. Only for SCD41.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t powerDown();
+
+ /**
+ * @brief Wake up sensor from sleep mode to idle mode.
+ *
+ * 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.
+ *
+ * @note This command is only available for SCD41.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t wakeUp();
+
+ /**
+ * @brief Sets the initial period for ASC correction
+ *
+ * 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
+ * persist_settings command must be issued.
+ *
+ * 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).
+ *
+ * @param[in] ascInitialPeriod ASC initial period in hours
+ *
+ * @note This command is available for SCD41 and only in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ *
+ * Example:
+ * --------
+ *
+ * @code{.cpp}
+ *
+ * int16_t localError = 0;
+ * localError = sensor.setAutomaticSelfCalibrationInitialPeriod(44);
+ * if (localError != NO_ERROR) {
+ * return;
+ * }
+ *
+ * @endcode
+ *
+ */
+ int16_t setAutomaticSelfCalibrationInitialPeriod(uint16_t ascInitialPeriod);
+
+ /**
+ * @brief Read out the initial period for ASC correction
+ *
+ * @param[out] ascInitialPeriod ASC initial period in hours
+ *
+ * @note This command is only available for SCD41 and only in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t
+ getAutomaticSelfCalibrationInitialPeriod(uint16_t& ascInitialPeriod);
+
+ /**
+ * @brief Sets the standard period for ASC correction.
+ *
+ * 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. Note: a value of 0 results in an immediate
+ * correction. To save the setting to the EEPROM, the persist_settings (see
+ * Section 3.10.1) command must be issued.
+ *
+ * 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).
+ *
+ * @param[in] ascStandardPeriod ASC standard period in hours
+ *
+ * @note This command is only available for SCD41 and only in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ *
+ * Example:
+ * --------
+ *
+ * @code{.cpp}
+ *
+ * int16_t localError = 0;
+ * localError = sensor.setAutomaticSelfCalibrationStandardPeriod(156);
+ * if (localError != NO_ERROR) {
+ * return;
+ * }
+ *
+ * @endcode
+ *
+ */
+ int16_t
+ setAutomaticSelfCalibrationStandardPeriod(uint16_t ascStandardPeriod);
+
+ /**
+ * @brief Get the standard period for ASC correction.
+ *
+ * @param[out] ascStandardPeriod ASC standard period in hours
+ *
+ * @note This command is only available for SCD41 and only in idle mode.
+ *
+ * @return error_code 0 on success, an error code otherwise.
+ */
+ int16_t
+ getAutomaticSelfCalibrationStandardPeriod(uint16_t& ascStandardPeriod);
+
+ /**
+ * @brief signalTemperature
+ *
+ * @param[in] rawTemperature
+ *
+ * @return Temperature in °C
+ */
+ static float signalTemperature(uint16_t rawTemperature);
+
+ /**
+ * @brief signalRelativeHumidity
+ *
+ * @param[in] rawRelativeHumidity
+ *
+ * @return Relative humidity in %RH
+ */
+ static float signalRelativeHumidity(uint16_t rawRelativeHumidity);
+
+ /**
+ * @brief signalCo2Concentration
+ *
+ * @param[in] rawCo2Concentration
+ *
+ * @return CO₂ concentration in ppm
+ */
+ static uint16_t signalCo2Concentration(uint16_t rawCo2Concentration);
+
+ /**
+ * @brief signalTemperatureOffset
+ *
+ * @param[in] rawTemperatureOffset
+ *
+ * @return Temperature in °C
+ */
+ static float signalTemperatureOffset(uint16_t rawTemperatureOffset);
+
+ /**
+ * @brief signalAmbientPressure
+ *
+ * @param[in] rawAmbientPressure
+ *
+ * @return Pressure in Pa
+ */
+ static uint32_t signalAmbientPressure(uint16_t rawAmbientPressure);
+
+ private:
+ TwoWire* _i2cBus = nullptr;
+ uint8_t _i2cAddress = 0;
+};
+
+#endif // SENSIRIONI2CSCD4X_H
\ No newline at end of file