Skip to content

Commit

Permalink
Merge pull request #1 from CDFER/1.1.0
Browse files Browse the repository at this point in the history
1.1.0
  • Loading branch information
CDFER authored Apr 24, 2023
2 parents d28976d + 0788972 commit 42cef6d
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 66 deletions.
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"cSpell.words": [
"datasheet",
"EEPROM",
"LOGI",
"Sensirion"
]
}
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

This is a library to interface with the Sensirion SCD4x true CO2 sensors in Arduino using the I2C protocol.

## 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.

```c++
#include "scd4x.h"

Wire.begin();
co2.begin(Wire);
co2.setCalibrationMode(false);
co2.saveSettings();
```

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.

## Features
- use multiple I2C Busses -> scd4x.begin(Wire1);
- no extra dependencies
Expand All @@ -12,18 +26,16 @@ This is a library to interface with the Sensirion SCD4x true CO2 sensors in Ardu
- not all functions are implemented
- not compatible with other SCD4x Arduino libraries
- only tested with the esp32
- under active development

### Setup
```c++
#include "scd4x.h"
SCD4X co2;
double CO2 = 0, temperature = 0, humidity = 0;


Wire.begin();
scd4x.begin(Wire);
scd4x.startPeriodicMeasurement();
co2.begin(Wire);
co2.startPeriodicMeasurement();
```
### Loop
```c++
Expand Down
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "scd4x-CO2",
"version": "1.0.0",
"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": {
Expand Down
107 changes: 58 additions & 49 deletions scd4x.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Sensirion AG
* Copyright (c) 2023, Chris Dirks
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -35,34 +36,32 @@ uint8_t SCD4X::begin(TwoWire& port, uint8_t addr) {
_i2cPort = &port;
_address = addr;
_i2cPort->beginTransmission(_address);
return _i2cPort->endTransmission();
_error = _i2cPort->endTransmission();
return _error;
}

bool SCD4X::isConnected(TwoWire& port, Stream* stream, uint8_t addr) {
_i2cPort = &port;
_address = addr;
const int bytesRequested = 3;

_debug_output_stream = stream;

_i2cPort->beginTransmission(_address);
_error = _i2cPort->endTransmission(true);
SCD4X::begin(port, addr);

SCD4X::stopPeriodicMeasurement();
vTaskDelay(500 / portTICK_PERIOD_MS); // wait for SCD4x to stop as per datasheet

char addrCheck[32];
if (_error != 0) {
_debug_output_stream->printf("SCD4x returned endTransmission error %i\r\n", _error);
return false;
}

_i2cPort->beginTransmission(_address);
_i2cPort->write(0x36);
_i2cPort->write(0x39);
_i2cPort->endTransmission(true);

vTaskDelay(10000 / portTICK_PERIOD_MS);
_commandSequence(0x3639);

uint8_t temp[3];
if (_i2cPort->requestFrom(_address, (uint8_t)3)) {
Wire.readBytes(temp, 3);
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);
}

if (temp[0] != 0 || temp[1] != 0 || temp[2] != 0x81) {
Expand All @@ -74,30 +73,34 @@ bool SCD4X::isConnected(TwoWire& port, Stream* stream, uint8_t addr) {
return true;
}

uint8_t SCD4X::stopPeriodicMeasurement() {
_commandSequence(0x3f86);
return _error;
}

uint8_t SCD4X::startPeriodicMeasurement() {
_i2cPort->beginTransmission(_address);
_i2cPort->write(0x21);
_i2cPort->write(0xb1);
return _i2cPort->endTransmission();
_commandSequence(0x21b1);
return _error;
}

uint8_t SCD4X::readMeasurement(double& co2, double& temperature, double& humidity) {
const int bytesRequested = 12;
const int bytesRequested = 9;

_i2cPort->beginTransmission(_address);
_i2cPort->write(0xec);
_i2cPort->write(0x05);
_error = _i2cPort->endTransmission(false); // no stop bit

if (_i2cPort->endTransmission(true) == 0) {
if (_error == 0) {
// read measurement data: 2 bytes co2, 1 byte CRC,
// 2 bytes T, 1 byte CRC, 2 bytes RH, 1 byte CRC,
// 2 bytes sensor status, 1 byte CRC
// stop reading after bytesRequested (12 bytes)

uint8_t bytesReceived = Wire.requestFrom(_address, bytesRequested);
uint8_t bytesReceived = _i2cPort->requestFrom(_address, bytesRequested);
if (bytesReceived == bytesRequested) { // If received requested amount of bytes
uint8_t data[bytesReceived];
Wire.readBytes(data, bytesReceived);
_i2cPort->readBytes(data, bytesReceived);

// floating point conversion
co2 = (double)((uint16_t)data[0] << 8 | data[1]);
Expand All @@ -108,44 +111,50 @@ uint8_t SCD4X::readMeasurement(double& co2, double& temperature, double& humidit

if (inRange(co2, 40000, 0) && inRange(temperature, 60, -10) &&
inRange(humidity, 100, 0)) {
return false;
return 0;
} else {
ESP_LOGE("measurement", "out of range");
return true;
Serial.printf("%4.0f,%2.1f,%1.0f\n", co2, temperature, humidity);
_error = 7;
}

} else {
//ESP_LOGE("Wire.requestFrom", "bytesReceived(%i) != bytesRequested(%i)", bytesReceived, bytesRequested);
return true;
// ESP_LOGE("Wire.requestFrom", "bytesReceived(%i) != bytesRequested(%i)", bytesReceived, bytesRequested);
_error = 6;
}
} else {
ESP_LOGE("Wire.endTransmission", "Returned ERROR");
return true;
}
return _error;
}

bool SCD4X::isDataReady() {
const int bytesRequested = 3;
if ((_readSequence(0xe4b8) & 0x07ff) == 0x0000) { // lower 11 bits == 0 -> data not ready
return false;
} else {
return true;
}
}

_i2cPort->beginTransmission(_address);
_i2cPort->write(0xe4);
_i2cPort->write(0xb8);
bool SCD4X::getCalibrationMode() {
return (bool)_readSequence(0x2313);
}

if (_i2cPort->endTransmission(true) == 0) {
uint8_t bytesReceived = Wire.requestFrom(_address, bytesRequested);
if (bytesReceived == bytesRequested) { // If received requested amount of bytes
uint8_t data[bytesReceived];
Wire.readBytes(data, bytesReceived);
return data[1] != (0x00); // check if last 8 bits are not 0
uint8_t SCD4X::setCalibrationMode(bool enableSelfCalibration) {
SCD4X::stopPeriodicMeasurement();
vTaskDelay(500 / portTICK_PERIOD_MS); // wait for SCD4x to stop as per datasheet

} else {
ESP_LOGE("Wire.requestFrom",
"bytesReceived(%i) != bytesRequested(%i)", bytesReceived,
bytesRequested);
return false;
}
if (enableSelfCalibration) {
SCD4X::_writeSequence(0x2416, 0x0001, 0xB0);

} else {
ESP_LOGE("Wire.endTransmission", "Returned ERROR");
return false;
SCD4X::_writeSequence(0x2416, 0x0000, 0x81);
}
}

return _error;
}

uint8_t SCD4X::saveSettings() {
_commandSequence(0x3615);
ESP_LOGI("Settings Saved to EEPROM", "");
vTaskDelay(800 / portTICK_PERIOD_MS); // wait for SCD4x to saveSettings as per datasheet
return _error;
}
Loading

0 comments on commit 42cef6d

Please sign in to comment.