Dear Forum,
I have a STC31 CO2 sensor conected to an Arduino Uno R4 Wifi via the Qwiic cable (~3 in long). Using the example sketch for temperature and relative humidity correction via the include STHC3, I am getting fairly good CO2 readings. Ambient CO is averaging a believable 0.3 - 0.4%. Pure CO2 is reading as 100 +/- 0.3%.
I have noticed two unexpected behaviours, and am wondering if they should actually be expected?:
- When I place the sensor in a cell culture incubator (>90% RH) that has been calibrated to 5% CO2 with a Fyrite kit, the STC31 initially reads very close to 5%. But over the next few minutes, as the temperature on the board climbs, the CO2 reading also climbs to ~5.3%, which seems a bit high. I am curious if this slow climb from ~4.9% to 5.3% over ~10 minutes is to be expected. The RH and temp correction is being performed before each measurement. (as per the code below)
- When I try to measure to % CO2 in a mixture of 5% CO2 / 95% O2, the STC31 reads negative values of CO2. Is this an expected issue when exposing the unit to high O2 with the “balance air” setting?
FWIW, have have an ExplorIR sensor that handles the 5% CO2 / 95% O2 mix, but it’s much bigger and more expensive, so I am hoping to be able to shift to the STC31 for this project.
Here is the code used, not including the header file where I define some structures to aid in calculating average readings:
/*
Reading CO2 concentration from the STC3x
By: Paul Clark
Based on earlier code by: Nathan Seidle
SparkFun Electronics
Date: June 11th, 2021
License: MIT. See license file for more information but you can
basically do whatever you want with this code.
Feel like supporting open source hardware?
Buy a board from SparkFun! CO₂ Sensor - STC31 (Qwiic)
This examples shows how to compensate for pressure, temperature and humidity
Temperature and humidity are provided by the SHTC3 on the STC31 breakout
Hardware Connections:
Attach RedBoard to computer using a USB cable.
Connect STC31 to RedBoard using Qwiic cable.
Open Serial Monitor at 115200 baud.
*/
#include <Wire.h>
#include “SparkFun_STC3x_Arduino_Library.h” //Click here to get the library: http://librarymanager/All#SparkFun_STC3x
STC3x mySensor;
#include “SparkFun_SHTC3.h” //Click here to get the library: http://librarymanager/All#SparkFun_SHTC3
SHTC3 mySHTC3;
#include “structures.h”;
serialMsg serialMain(23); //hold buffer and variables related to serial monitor messages
float SHTC3temp = 0;
generalSensor CO2(10,“CO2”);
float RH = 0;
void setup()
{
Serial.begin(115200);
Serial.println(“Press any key to continue…”);
while (Serial.available() == 0) {
// Wait here until data is received from the Serial Monitor
}
Serial.println(F(“STC3x Example”));
Wire1.begin();
//mySensor.enableDebugging(); // Uncomment this line to get helpful debug messages on Serial
if (mySensor.begin(0x29, Wire1) == false)
{
Serial.println(F(“STC3x not detected. Please check wiring. Freezing…”));
while (1)
;
}
if (mySHTC3.begin(Wire1) != SHTC3_Status_Nominal)
{
Serial.println(F(“SHTC3 not detected. Please check wiring. Freezing…”));
while (1)
;
}
//We need to tell the STC3x what binary gas and full range we are using
//Possible values are:
// STC3X_BINARY_GAS_CO2_N2_100 : Set binary gas to CO2 in N2. Range: 0 to 100 vol%
// STC3X_BINARY_GAS_CO2_AIR_100 : Set binary gas to CO2 in Air. Range: 0 to 100 vol%
// STC3X_BINARY_GAS_CO2_N2_25 : Set binary gas to CO2 in N2. Range: 0 to 25 vol%
// STC3X_BINARY_GAS_CO2_AIR_25 : Set binary gas to CO2 in Air. Range: 0 to 25 vol%
if (mySensor.setBinaryGas(STC3X_BINARY_GAS_CO2_AIR_100) == false)
{
Serial.println(F(“Could not set the binary gas! Freezing…”));
while (1)
;
}
//We can compensate for temperature and relative humidity using the readings from the SHTC3
if (mySHTC3.update() != SHTC3_Status_Nominal) // Request a measurement
{
Serial.println(F(“Could not read the RH and T from the SHTC3! Freezing…”));
while (1)
;
}
//In case the ‘Set temperature command’ has been used prior to the measurement command,
//the temperature value given out by the STC31 will be that one of the ‘Set temperature command’.
//When the ‘Set temperature command’ has not been used, the internal temperature value can be read out.
float temperature = mySHTC3.toDegC(); // “toDegC” returns the temperature as a floating point number in deg C
Serial.print(F("Setting STC3x temperature to "));
Serial.print(temperature, 2);
Serial.print(F("C was "));
if (mySensor.setTemperature(temperature) == false)
Serial.print(F("not "));
Serial.println(F(“successful”));
RH = mySHTC3.toPercent(); // “toPercent” returns the percent humidity as a floating point number
Serial.print(F("Setting STC3x RH to “));
Serial.print(RH, 2);
Serial.print(F(”% was "));
if (mySensor.setRelativeHumidity(RH) == false)
Serial.print(F("not "));
Serial.println(F(“successful”));
//If we have a pressure sensor available, we can compensate for ambient pressure too.
//As an example, let’s set the pressure to 840 mbar (== SF Headquarters)
uint16_t pressure = 960;
Serial.print(F("Setting STC3x pressure to "));
Serial.print(pressure);
Serial.print(F("mbar was "));
if (mySensor.setPressure(pressure) == false)
Serial.print(F("not "));
Serial.println(F(“successful”));
Serial.print(F("CO2(%):"));
//Serial.print(mySensor.getCO2(), 2);
Serial.print(F("\tCO2.value:"));
//Serial.print(CO2.value, 2);
Serial.print(F("\tCO2.average:"));
//Serial.print(CO2.average, 2);
Serial.print(F("\tCO2 expected:"));
//Serial.print(CO2.setpoint, 2);
Serial.print(F("\tTemp(C):"));
//Serial.print(mySensor.getTemperature(), 2);
mySHTC3.update();
Serial.print(F("\tSHTC3_Temp(C):"));
//Serial.print(SHTC3temp, 2);
Serial.print(F("\tRH(%):"));
//Serial.print(RH, 2);
Serial.println();
}
void loop()
{
if (Serial.available() > 0) {
// Read serialMain.incoming until end character is reached or maxMessageLength characters are read.
serialMain.length = Serial.readBytesUntil(serialMain.end, serialMain.incoming, serialMain.size);
if (serialMain.length <= serialMain.size) {
if (serialMain.incoming[0] == ‘c’) {
// indicate the known CO2 % in the gas on the STC31 as nnn/10.
char co2char[3] = {serialMain.incoming[1],serialMain.incoming[2],serialMain.incoming[3]};
CO2.setpoint = atof(co2char)/10;
//Serial.print("CO2 expected now ");
//Serial.println(CO2.setpoint,1);
}
}
}
if (mySensor.measureGasConcentration()) {// measureGasConcentration will return true when fresh data is available
SHTC3temp = mySHTC3.toDegC();
RH = mySHTC3.toPercent();
mySensor.setTemperature(SHTC3temp);
mySensor.setRelativeHumidity(RH);
CO2.value = mySensor.getCO2();
sensorUpdate(CO2);
//Serial.print(F("CO2(%):"));
Serial.print(F("\t"));
Serial.print(mySensor.getCO2(), 2);
//Serial.print(F("\tCO2.value:"));
Serial.print(F("\t"));
Serial.print(CO2.value, 2);
//Serial.print(F("\tCO2.average:"));
Serial.print(F("\t"));
Serial.print(CO2.average, 2);
//Serial.print(F("\tCO2 expected:"));
Serial.print(F("\t"));
Serial.print(CO2.setpoint, 2);
//Serial.print(F("\tTemp(C):"));
Serial.print(F("\t"));
Serial.print(mySensor.getTemperature(), 2);
mySHTC3.update();
//Serial.print(F("\tSHTC3_Temp(C):"));
Serial.print(F("\t"));
Serial.print(SHTC3temp, 2);
//Serial.print(F("\tRH(%):"));
Serial.print(F("\t"));
Serial.print(RH, 2);
Serial.println();
} else {
//Serial.print(F(“.”));
}
delay(1000);
}
void sensorUpdate(generalSensor& sensor) {
//add the value to the array and note the time that value is added to the history in millis
sensor.history[sensor.index] = sensor.value;
sensor.deviation = sensor.value - sensor.setpoint;
sensor.time[sensor.index] = millis();
if (sensor.value < sensor.minimum) sensor.minimum = sensor.value;
if (sensor.value > sensor.maximum) sensor.maximum = sensor.value;
//determine slope and append to slope history
if (sensor.historyFilled) {
//determine slope
sensor.slope[sensor.index] = (sensor.value - sensor.history[(sensor.index - sensor.slopeInterval) % sensor.historySize]) / (sensor.slopeUnits * (sensor.time[sensor.index] - sensor.time[(sensor.index - sensor.slopeInterval) % sensor.historySize]));
//calculate average over the history
float thisAvg = 0;
for (int i = 0; i < sensor.historySize; i++) {
thisAvg += sensor.history[i];
}
sensor.average = thisAvg / sensor.historySize;
} else {
sensor.slope[sensor.index] = 0;
sensor.average = sensor.value;
}
sensor.index++;
//this is essentially used like a circular buffer
if (sensor.index == (sensor.historySize)) {
sensor.historyFilled = true;
sensor.index = 0; // reset
}
}
Thanks,
Damon