diff --git a/.vscode/settings.json b/.vscode/settings.json index bb63124..b018689 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "datasheet", "EEPROM", "LOGI", + "selftest", "Sensirion" ] } \ No newline at end of file diff --git a/README.md b/README.md index 3ad9e9e..fe9f463 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,46 @@ # SCD4x Library -This is a library to interface with the Sensirion SCD4x true CO2 sensors in Arduino using the I2C protocol. +The SCD4x Library provides an interface to interact with Sensirion SCD4x true CO2 sensors using the I2C protocol in Arduino. -## Warning -These sensors by default have an auto-calibrate mode that takes the lowest CO2 level from the last week and assumes it is 400ppm. This can cause the sensor to read hundreds of ppm off if it is in a room that doesn't get to 400ppm of CO2 in a week. It's important to be aware of this issue, as some scientific papers are even using this sensor and haven't noticed the problem. Here is the code that you need to permanently set them to not auto calibrate. To avoid unnecessary wear of the EEPROM, the saveSettings command should only be used sparingly. The EEPROM is guaranteed to endure at least 2000 write cycles before failure. +## Warning: Auto-Calibration Issue and Recommended Actions -```c++ +The SCD4x CO2 sensors come with a default auto-calibrate mode that assumes the lowest CO2 level from the past week as 400ppm. However, it's crucial to be aware that this auto-calibration can result in inaccurate readings if the sensor is placed in an environment that doesn't reach 400ppm CO2 within a week. This issue is particularly important to note when using the sensor for scientific purposes. + +To address this issue, it is recommended to permanently disable the auto-calibration mode by using the provided code snippet: + +```cpp #include "scd4x.h" -Wire.begin(); -co2.begin(Wire); -co2.setCalibrationMode(false); -co2.saveSettings(); +SCD4X co2; + +void setup() { + // Initialize the SCD4x library + co2.begin(); + + // Disable auto-calibration + co2.setCalibrationMode(false); + + // Save the settings to EEPROM + co2.saveSettings(); +} + +void loop() { + // Your code here +} ``` -Despite this issue with the auto-calibration mode, I still believe that the Sensirion SCD4x CO2 Sensors are a great choice for measuring indoor air quality. In my experience, they have proven to be much more accurate than other popular sensors such as eCO2 sensors. It's important to be aware of this particular limitation and take the necessary steps to disable the auto-calibration mode, but overall, these sensors are a reliable and effective tool for monitoring CO2 levels. +Please exercise caution when using the `saveSettings()` command as it writes to the EEPROM, which has a limited lifespan of approximately 2000 write cycles before potential failure. + +Despite the auto-calibration issue, the Sensirion SCD4x CO2 Sensors remain an excellent choice for monitoring indoor air quality, as they have proven to be more accurate than other popular eCO2 sensors. + +## Factory Calibration with Auto-Calibration Off +![Co Location Calibration](/images/cal.png) ## Features -- use multiple I2C Busses -> scd4x.begin(Wire1); -- no extra dependencies -- only implements necessary functions -- uses doubles (64bit floating point numbers) for proper accurate data calculations +* Supports multiple I2C busses: scd4x.begin(Wire1); +* No extra dependencies required +* Implements only necessary functions +* Uses doubles (64-bit floating-point numbers) for accurate data calculations ## Warnings - not all functions are implemented @@ -51,14 +71,28 @@ vTaskDelay(4750 / portTICK_PERIOD_MS); //new data available after approx 5 secon ## 🖼️ Schematic ![Schematic](/images/schematic.png) -- the scd4x sensor can draw up to 205ma peaks at 3.3V (only 18ma average) so make sure you have a robust power source (the above schematic has been tested to work) -- you only need to solder the 6 pins shown and the thermal pad on the underside to get it working -- unfortunately, I have not found a way to easily solder these sensors with a soldering iron, a oven or hotplate seems to be the only way -- look out for a temperature offset if you place the sensor in a case of sorts +* The SCD4x sensor can draw up to 205mA peaks at 3.3V (only 18mA average), so ensure you have a robust power source (the above schematic has been tested to work). +* You only need to solder the 6 shown pins and the thermal pad on the underside to get it working. +* Unfortunately, soldering these sensors with a soldering iron is not easy; an oven or hotplate seems to be the only way. +* Watch out for a temperature offset if you place the sensor in a case or enclosure. -## Based on the awesome work of Raphael Nestler and everyone at Sensirion AG -Origin created by Raphael Nestler in 2021 -https://github.com/Sensirion/arduino-i2c-scd4x +## Function Refreence +| Function | Description | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `begin()` | Initializes the library. Must be called before any other library functions. | +| `isConnected()` | Checks if the device is correctly connected by verifying the response, manufacturer ID, and part ID. | +| `startPeriodicMeasurement()` | Starts periodic measurement, with new data available in approximately 5 seconds. | +| `stopPeriodicMeasurement()` | Stops periodic measurement. Wait at least 500ms before sending further commands. | +| `readMeasurement()` | Reads the sensor output, including CO₂ concentration in ppm, temperature in °C, and relative humidity in %RH. The measurement data can only be read out once per signal update interval as the buffer is emptied upon read-out. | +| `isDataReady()` | Checks whether new measurement data is available for read-out. | +| `setCalibrationMode()` | Sets the calibration mode and stores it in the EEPROM of the SCD4x. The automatic self-calibration algorithm assumes that the sensor is exposed to the atmospheric CO2 concentration of 400 ppm at least once per week. Use this function sparingly to avoid unnecessary wear of the EEPROM. | +| `getCalibrationMode()` | Gets the calibration mode. Returns `true` if auto calibration is enabled, `false` otherwise. | +| `saveSettings()` | Stores settings in the EEPROM of the SCD4x. Wait at least 800ms before sending further commands. EEPROM is guaranteed to endure at least 2000 write cycles before failure. | +| `getErrorText()` | Converts an error code into descriptive text. Returns a pointer to a constant character array containing the descriptive text of the error. If the error code is not recognized, "Unknown error" is returned. | +## Credits +Based on the work of Raphael Nestler and everyone at Sensirion AG. +Originally created by Raphael Nestler in 2021. +https://github.com/Sensirion/arduino-i2c-scd4x -To help support my work check out my store: https://keastudios.co.nz/ +To help support my work, check out my store: https://keastudios.co.nz/ \ No newline at end of file diff --git a/examples/async_scd4x_measurement.ino b/examples/async_scd4x_measurement.ino new file mode 100644 index 0000000..196d44a --- /dev/null +++ b/examples/async_scd4x_measurement.ino @@ -0,0 +1,54 @@ +/* + SCD4X Measurement Task Example + + This example demonstrates the usage of the SCD4X CO2 sensor library with FreeRTOS on an Arduino board, + specifically designed for the ESP32's version of Arduino with FreeRTOS. + + Instructions: + - Install the SCD4X library in your Arduino IDE or add it to platformio.ini + - Connect the SCD4X CO2 sensor to the ESP32: + - SDA: GPIO 21 + - SCL: GPIO 22 + - VCC: 3V3 (3.3V) + - Optionally, you can add external 4.7k pull-up resistors on the SDA and SCL lines. + - Flash code + - Open the Serial Monitor in the Arduino IDE to view the measurement data. + + Note: This example is specifically designed for the ESP32's version of Arduino with FreeRTOS. +*/ + +#include + +SCD4X co2; +double co2Value = 0, temperature = 0, humidity = 0; +TaskHandle_t measurementTask; + +void measurementTaskFunction(void* parameter) { + Wire.begin(); // SDA: GPIO 21, SCL: GPIO 22 by default on the ESP32 + co2.begin(Wire); + co2.startPeriodicMeasurement(); + + while (true) { + if (co2.isDataReady()) { + if (co2.readMeasurement(co2Value, temperature, humidity) == 0) { + Serial.printf("CO2: %.0f ppm, Temperature: %.1f °C, Humidity: %.0f %%RH\n", co2Value, temperature, humidity); + vTaskDelay(pdMS_TO_TICKS(4750)); // New data available after approximately 5 seconds + } + } + vTaskDelay(pdMS_TO_TICKS(50)); // Check every 50ms + } +} + +void setup() { + Serial.begin(115200); + while (!Serial) { + // Wait for serial port to connect + delay(100); + } + + xTaskCreatePinnedToCore(measurementTaskFunction, "MeasurementTask", 2048, NULL, 1, &measurementTask, 0); +} + +void loop() { + // Your other code here +} diff --git a/examples/basic_data_logger_SD_card.ino b/examples/basic_data_logger_SD_card.ino new file mode 100644 index 0000000..c232eaa --- /dev/null +++ b/examples/basic_data_logger_SD_card.ino @@ -0,0 +1,96 @@ +#include +#include +#include + +SCD4X co2; + +const int chipSelectPin = 10; // Chip select pin for SD card module +const String logFileName = "co2_log.txt"; // Name of the log file + +void setup() { + Serial.begin(115200); + while (!Serial) { + // Wait for serial port to connect + delay(100); + } + + // Initialize SD card + if (!SD.begin(chipSelectPin)) { + Serial.println("SD card initialization failed."); + return; + } + + // Initialize the I2C communication + Wire.begin(); + + // Initialize the SCD4x library + if (!co2.begin()) { + Serial.println("Failed to initialize SCD4X sensor."); + return; + } + + // Check if the sensor is connected + if (!co2.isConnected()) { + Serial.println("Sensor not connected. Please check the wiring."); + return; + } + + // Check if auto-calibration is enabled + if (co2.getCalibrationMode()) { + // Disable auto-calibration + co2.setCalibrationMode(false); + + // Save the settings to EEPROM + co2.saveSettings(); + } + + // Start periodic measurement after updating settings + co2.startPeriodicMeasurement(); + + // Create or append to the log file + File logFile = SD.open(logFileName, FILE_WRITE); + if (logFile) { + logFile.println("CO2 (ppm), Temperature (°C), Humidity (%RH)"); + logFile.close(); + Serial.println("Data logging started. Press reset to start a new log."); + } else { + Serial.println("Failed to open log file."); + return; + } +} + +void loop() { + double co2Value, temperature, humidity; + + // Read measurement data from the sensor + uint8_t errorCode = co2.readMeasurement(co2Value, temperature, humidity); + + // Check for errors + if (errorCode == 0) { + // Format the measurements into a string + char measurementString[50]; + snprintf(measurementString, sizeof(measurementString), "%.0f, %.1f, %.0f", co2Value, temperature, humidity); + + // Open the log file in append mode + File logFile = SD.open(logFileName, FILE_WRITE | FILE_APPEND); + if (logFile) { + // Write the measurement data to the log file + logFile.println(measurementString); + logFile.close(); + Serial.println("Data logged: " + String(measurementString)); + } else { + Serial.println("Failed to open log file."); + } + + } else { + // Convert the error code to text + const char* errorText = co2.getErrorText(errorCode); + + // Print the error message + Serial.print("Error reading measurement: "); + Serial.println(errorText); + } + + // Delay for new measurement to be taken + delay(5000); +} \ No newline at end of file diff --git a/examples/basic_scd4x.ino b/examples/basic_scd4x.ino new file mode 100644 index 0000000..f2b603d --- /dev/null +++ b/examples/basic_scd4x.ino @@ -0,0 +1,94 @@ +/* + SCD4x CO2 Sensor Example + + This example demonstrates the usage of the SCD4x CO2 sensor library on an Arduino board. + + Instructions: + - Install the SCD4x library in your Arduino IDE. + - Connect the SCD4X CO2 sensor to the following pins on your Arduino board: + - SDA: Default SDA pin + - SCL: Default SCL pin + - VCC: 3.3V or 5V (make sure you match this with your Arduino VCC voltage or otherwise you can damage it) + - Optionally, you can add external 4.7k pull-up resistors on the SDA and SCL lines. + - Flash code + - Open the Serial Monitor in the Arduino IDE to view the measurement data and any error messages. + + Default I2C pins for some common Arduino boards: + - Arduino Uno, Nano: SDA - A4, SCL - A5 (VCC: 5V) + - Arduino Mega: SDA - 20, SCL - 21 (VCC: 5V) + - Arduino Leonardo: SDA - 2, SCL - 3 (VCC: 5V) + - Arduino Due: SDA - 20, SCL - 21 (VCC: 3.3V) + - ESP32: SDA - GPIO 21, SCL - GPIO 22 (VCC: 3.3V) + - ESP8266: SDA - GPIO 4, SCL - GPIO 5 (VCC: 3.3V) + - RP2040: SDA - GP5 (pin 4), SCL - GP4 (pin 3) (VCC: 3.3V) + + Note: The default SDA and SCL pins may vary depending on the specific board or variant. Please refer to the board documentation for the correct pin mappings. + + 4.7k Pull-up Resistors: + - I2C communication requires pull-up resistors on the SDA and SCL lines to ensure proper signal levels. + - Some Arduino boards already have built-in pull-up resistors for the I2C pins, so external resistors may not be necessary. + - However, if you encounter communication issues or have long wire lengths, adding external 4.7k pull-up resistors between the SDA/SCL lines and VCC can help improve signal stability. +*/ + +#include +#include + +SCD4X co2; + +void setup() { + // Initialize the I2C communication + Wire.begin(); + + // Initialize the SCD4x library + co2.begin(); + + // Check if the sensor is connected + if (!co2.isConnected()) { + while (true) { + Serial.println("Sensor not connected. Please check the wiring."); + delay(1000); + } + } + + // Check if auto-calibration is enabled + if (co2.getCalibrationMode()) { + // Disable auto-calibration + co2.setCalibrationMode(false); + + // Save the settings to EEPROM + co2.saveSettings(); + } + + // Start periodic measurement after updating settings + co2.startPeriodicMeasurement(); + + // Wait for the sensor to warm up and take the first reading + delay(5000); +} + +void loop() { + double co2Value, temperature, humidity; + + // Read measurement data from the sensor + uint8_t errorCode = co2.readMeasurement(co2Value, temperature, humidity); + + // Check for errors + if (errorCode == 0) { + // Format the measurements into a string + char measurementString[100]; + sprintf(measurementString, "CO2: %.0f ppm, Temperature: %.1f °C, Humidity: %.0f %%RH", co2Value, temperature, humidity); + + // Print the measurements + Serial.println(measurementString); + } else { + // Convert the error code to text + const char* errorText = co2.getErrorText(errorCode); + + // Print the error message + Serial.print("Error reading measurement: "); + Serial.println(errorText); + } + + // Delay for new measurement to be taken + delay(5000); +} diff --git a/images/cal.png b/images/cal.png new file mode 100644 index 0000000..58bde4f Binary files /dev/null and b/images/cal.png differ diff --git a/library.json b/library.json index 0960059..399e0b6 100644 --- a/library.json +++ b/library.json @@ -1,21 +1,25 @@ { -"name": "scd4x-CO2", -"version": "1.1.0", -"description": "A library to interface esp chips with the SCD4x CO2 sensors in the Arduino (c++) Framework.", -"keywords": "co2, scd40, scd41, scd4x, i2c", -"repository": { - "type": "git", - "url": "https://github.com/CDFER/scd4x-CO2.git" -}, -"authors": [ - { + "name": "scd4x-CO2", + "version": "1.2.0", + "description": "An Arduino library for interfacing ESP chips with SCD4x CO2 sensors using the I2C protocol.", + "keywords": [ + "co2", + "scd40", + "scd41", + "scd4x", + "i2c" + ], + "repository": { + "type": "git", + "url": "https://github.com/CDFER/scd4x-CO2.git" + }, + "author": { "name": "Chris Dirks", "url": "https://keastudios.co.nz/about.htm", "maintainer": true - } -], -"license": "MIT", -"homepage": "https://github.com/CDFER/scd4x-CO2", -"frameworks": "arduino", -"platforms": "*" + }, + "license": "Other", + "homepage": "https://github.com/CDFER/scd4x-CO2", + "frameworks": "arduino", + "platforms": "*" } \ No newline at end of file diff --git a/scd4x.cpp b/scd4x.cpp index a027e57..9931d69 100644 --- a/scd4x.cpp +++ b/scd4x.cpp @@ -35,8 +35,13 @@ uint8_t SCD4X::begin(TwoWire& port, uint8_t addr) { _i2cPort = &port; _address = addr; + + // Begin I2C transmission with the sensor's address _i2cPort->beginTransmission(_address); + + // End I2C transmission and retrieve the error code _error = _i2cPort->endTransmission(); + return _error; } @@ -58,7 +63,7 @@ bool SCD4X::isConnected(TwoWire& port, Stream* stream, uint8_t addr) { _commandSequence(0x3639); vTaskDelay(10000 / portTICK_PERIOD_MS); // wait for SCD4x to do a self test as per datasheet - + uint8_t temp[bytesRequested]; if (_i2cPort->requestFrom(_address, bytesRequested)) { _i2cPort->readBytes(temp, bytesRequested); @@ -102,13 +107,14 @@ uint8_t SCD4X::readMeasurement(double& co2, double& temperature, double& humidit uint8_t data[bytesReceived]; _i2cPort->readBytes(data, bytesReceived); - // floating point conversion + // converter co2 ppm to floating point co2 = (double)((uint16_t)data[0] << 8 | data[1]); - // convert T in degC + // convert to temperature in degC temperature = (double)-45 + (double)175 * (double)((uint16_t)data[3] << 8 | data[4]) / (double)65536; - // convert RH in % + // convert to relative humidity to % humidity = (double)100 * (double)((uint16_t)data[6] << 8 | data[7]) / (double)65536; + // Check if measurements are within range if (inRange(co2, 40000, 0) && inRange(temperature, 60, -10) && inRange(humidity, 100, 0)) { return 0; @@ -144,7 +150,6 @@ uint8_t SCD4X::setCalibrationMode(bool enableSelfCalibration) { if (enableSelfCalibration) { SCD4X::_writeSequence(0x2416, 0x0001, 0xB0); - } else { SCD4X::_writeSequence(0x2416, 0x0000, 0x81); } @@ -158,3 +163,26 @@ uint8_t SCD4X::saveSettings() { vTaskDelay(800 / portTICK_PERIOD_MS); // wait for SCD4x to saveSettings as per datasheet return _error; } + +const char* SCD4X::getErrorText(uint8_t errorCode) { + switch (errorCode) { + case 0: + return "Success"; + case 1: + return "I2C data too long to fit in transmit buffer"; + case 2: + return "I2C received NACK on transmit of address"; + case 3: + return "I2C received NACK on transmit of data"; + case 4: + return "I2C other error"; + case 5: + return "I2C timeout"; + case 6: + return "bytesReceived(%i) != bytesRequested(%i)"; + case 7: + return "Measurement out of range"; + default: + return "Unknown error"; + } +} diff --git a/scd4x.h b/scd4x.h index e1fede3..cfbc981 100644 --- a/scd4x.h +++ b/scd4x.h @@ -33,141 +33,157 @@ #include #include -#define SCD4X_I2C_ADDRESS 0x62 // 7-bit I2C Address +#define SCD4X_I2C_ADDRESS 0x62 // I2C Address class SCD4X { public: /** - * begin() - Initializes this library. Must be called before any other library functions - * @note this function does not start the i2c bus, you must do that before calling this command + * Initializes this library. Must be called before any other library functions. + * + * @note This function does not start the I2C bus; you must do that before calling this command. * * @param i2cBus Arduino stream object to use for communication. * - * @retval 0 success - * @retval 1 i2c data too long to fit in transmit buffer - * @retval 2 i2c received NACK on transmit of address - * @retval 3 i2c received NACK on transmit of data - * @retval 4 i2c other error - * @retval 5 i2c timeout + * @retval 0 Success + * @retval 1 I2C data too long to fit in transmit buffer + * @retval 2 I2C received NACK on transmit of address + * @retval 3 I2C received NACK on transmit of data + * @retval 4 I2C other error + * @retval 5 I2C timeout */ uint8_t begin(TwoWire& port = Wire, uint8_t addr = SCD4X_I2C_ADDRESS); /** - * checks for correct response, manufacturer id and part id. + * Checks for correct response, manufacturer ID, part ID, and does a quick selftest. + * + * @param port Wire instance (e.g., Wire or Wire1) + * @param stream Debug output pointer (e.g., &Serial) + * @param addr I2C address of the sensor (0x62 by default) * - * @param port Wire instance (e.g Wire or Wire1) - * @param stream debug output pointer (e.g. &Serial) - * @param addr i2c address of sensor (0x62 by default) - * @returns true if device correctly connected, otherwise false + * @returns true if the device is correctly connected, otherwise false */ bool isConnected(TwoWire& port = Wire, Stream* stream = &Serial, uint8_t addr = SCD4X_I2C_ADDRESS); /** - * startPeriodicMeasurement() - start periodic measurement, new data available - * in ~5 seconds. - * - * @retval 0 success - * @retval 1 i2c data too long to fit in transmit buffer - * @retval 2 i2c received NACK on transmit of address - * @retval 3 i2c received NACK on transmit of data - * @retval 4 i2c other error - * @retval 5 i2c timeout + * Starts periodic measurement, with new data available in approximately 5 seconds. + * + * @retval 0 Success + * @retval 1 I2C data too long to fit in transmit buffer + * @retval 2 I2C received NACK on transmit of address + * @retval 3 I2C received NACK on transmit of data + * @retval 4 I2C other error + * @retval 5 I2C timeout */ uint8_t startPeriodicMeasurement(); /** - * stopPeriodicMeasurement() - stop periodic measurement - * @note wait atleast 500ms before sending further commands - * - * @retval 0 success - * @retval 1 i2c data too long to fit in transmit buffer - * @retval 2 i2c received NACK on transmit of address - * @retval 3 i2c received NACK on transmit of data - * @retval 4 i2c other error - * @retval 5 i2c timeout + * Stops periodic measurement. + * + * @note Wait at least 500ms before sending further commands. + * + * @retval 0 Success + * @retval 1 I2C data too long to fit in transmit buffer + * @retval 2 I2C received NACK on transmit of address + * @retval 3 I2C received NACK on transmit of data + * @retval 4 I2C other error + * @retval 5 I2C timeout */ uint8_t stopPeriodicMeasurement(); /** - * readMeasurement() - read 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. + * Reads 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 isDataReady() function can be used to check the 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. * - * @note This command is only available in measurement mode. The firmware - * updates the measurement values depending on the measurement mode. + * @note This command is only available in measurement mode. The firmware updates the measurement values depending on the measurement mode. * * @param co2 CO₂ concentration in ppm - * * @param temperature Temperature in °C - * * @param humidity Relative humidity in %RH * - * @retval 0 success - * @retval 1 i2c data too long to fit in transmit buffer - * @retval 2 i2c received NACK on transmit of address - * @retval 3 i2c received NACK on transmit of data - * @retval 4 i2c other error - * @retval 5 i2c timeout + * @retval 0 Success + * @retval 1 I2C data too long to fit in transmit buffer + * @retval 2 I2C received NACK on transmit of address + * @retval 3 I2C received NACK on transmit of data + * @retval 4 I2C other error + * @retval 5 I2C timeout * @retval 6 bytesReceived(%i) != bytesRequested(%i) - * @retval 7 measurement out of range + * @retval 7 Measurement out of range */ - uint8_t readMeasurement(double& co2, double& temperature, - double& humidity); + uint8_t readMeasurement(double& co2, double& temperature, double& humidity); /** - * getDataReadyFlag() - Check whether new measurement data is available - * for read-out. + * Checks whether new measurement data is available for read-out. * - * @param dataReadyFlag True if valid data is available, false otherwise. + * @param dataReadyFlag true if valid data is available, false otherwise. * - * @return 0 on success, an i2c error code otherwise + * @return true on success, false if there was an I2C error */ bool isDataReady(); /** - * setSelfCalibrationMode() - a blocking call to set the calibration mode and store it in the EEPROM of the SCD4x + * Sets the calibration mode and stores it in the EEPROM of the SCD4x. * - * @note The automatic self calibration algorithm assumes that the sensor is exposed to the atmospheric CO2 - * concentration of 400 ppm at least once per week. + * @note The automatic self-calibration algorithm assumes that the sensor is exposed to the atmospheric CO2 concentration of 400 ppm at least once per week. * * @note To avoid unnecessary wear of the EEPROM, the setSelfCalibrationMode command should only be used sparingly. * - * @param turn on or off self calibration + * @param enableAutoCalibration Turn on or off self calibration * - * @retval 0 success - * @retval 1 i2c data too long to fit in transmit buffer - * @retval 2 i2c received NACK on transmit of address - * @retval 3 i2c received NACK on transmit of data - * @retval 4 i2c other error - * @retval 5 i2c timeout + * @retval 0 Success + * @retval 1 I2C data too long to fit in transmit buffer + * @retval 2 I2C received NACK on transmit of address + * @retval 3 I2C received NACK on transmit of data + * @retval 4 I2C other error + * @retval 5 I2C timeout */ uint8_t setCalibrationMode(bool enableAutoCalibration); /** - * getCalibrationMode() + * Gets the calibration mode. * - * @return is auto calibration enabled + * @return true if auto calibration is enabled, false otherwise */ bool getCalibrationMode(); /** - * saveSettings() - store settings in the EEPROM of the SCD4x, wait atleast 800ms before sending further commands - * @note To avoid unnecessary wear of the EEPROM, the saveSettings command should only be used sparingly. + * Stores settings in the EEPROM of the SCD4x. + * + * @note Wait at least 800ms before sending further commands. * EEPROM is guaranteed to endure at least 2000 write cycles before failure. * - * @retval 0 success - * @retval 1 i2c data too long to fit in transmit buffer - * @retval 2 i2c received NACK on transmit of address - * @retval 3 i2c received NACK on transmit of data - * @retval 4 i2c other error - * @retval 5 i2c timeout + * @retval 0 Success + * @retval 1 I2C data too long to fit in transmit buffer + * @retval 2 I2C received NACK on transmit of address + * @retval 3 I2C received NACK on transmit of data + * @retval 4 I2C other error + * @retval 5 I2C timeout */ uint8_t saveSettings(); + /** + * @brief Converts an error code into descriptive text. + * + * This function takes an error code and returns a corresponding descriptive text. It can be used to convert + * error codes returned by the SCD4X sensor into human-readable error messages. + * + * @param errorCode The error code to convert. + * @return A pointer to a constant character array containing the descriptive text of the error. + * If the error code is not recognized, "Unknown error" is returned. + * + * @note The error codes and their meanings are as follows: + * - 0: Success + * - 1: I2C data too long to fit in transmit buffer + * - 2: I2C received NACK on transmit of address + * - 3: I2C received NACK on transmit of data + * - 4: I2C other error + * - 5: I2C timeout + * - 6: bytesReceived(%i) != bytesRequested(%i) + * - 7: Measurement out of range + */ + const char* getErrorText(uint8_t errorCode); + private: uint8_t _error = 0; uint8_t _isValid = false; @@ -180,28 +196,38 @@ class SCD4X { return !(value <= min) && (value <= max); } + /** + * Sends a command sequence over the I2C port. + * + * @param registerAddress The register address to write to. + */ void _commandSequence(uint16_t registerAddress) { _i2cPort->beginTransmission(_address); _i2cPort->write(highByte(registerAddress)); _i2cPort->write(lowByte(registerAddress)); - _error = _i2cPort->endTransmission(true); // send stop bit + _error = _i2cPort->endTransmission(true); // Send stop bit } + /** + * Reads a sequence of data from the specified register address over the I2C port. + * + * @param registerAddress The register address to read from. + * @return The received data as a 16-bit unsigned integer. + */ uint16_t _readSequence(uint16_t registerAddress) { - const int bytesRequested = 3; // check bit at end + const int bytesRequested = 3; // Check bit at the end _i2cPort->beginTransmission(_address); _i2cPort->write(highByte(registerAddress)); _i2cPort->write(lowByte(registerAddress)); - _error = _i2cPort->endTransmission(false); // no stop bit + _error = _i2cPort->endTransmission(false); // No stop bit if (_error == 0) { uint8_t bytesReceived = _i2cPort->requestFrom(_address, bytesRequested); - if (bytesReceived == bytesRequested) { // If received requested amount of bytes + if (bytesReceived == bytesRequested) { // If received the requested amount of bytes uint8_t data[bytesReceived]; _i2cPort->readBytes(data, bytesReceived); return ((uint16_t)data[0] << 8 | data[1]); - } else { ESP_LOGE("Wire.requestFrom", "bytesReceived(%i) != bytesRequested(%i)", bytesReceived, @@ -214,6 +240,13 @@ class SCD4X { } } + /** + * Writes a sequence of data to the specified register address over the I2C port. + * + * @param registerAddress The register address to write to. + * @param value The value to write as a 16-bit unsigned integer. + * @param checkSum The checksum value to write. + */ void _writeSequence(uint16_t registerAddress, uint16_t value, uint8_t checkSum) { _i2cPort->beginTransmission(_address); _i2cPort->write(highByte(registerAddress)); @@ -221,9 +254,8 @@ class SCD4X { _i2cPort->write(highByte(value)); _i2cPort->write(lowByte(value)); - _i2cPort->write(checkSum); // Checksum + _i2cPort->write(checkSum); // Checksum - _error = _i2cPort->endTransmission(true); // stop bit + _error = _i2cPort->endTransmission(true); // Stop bit } - };