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