- Today got to know why my sdc41 sensor was showing bad data earlier it used to show 500 in day and 600-800ppm in night when door was closed. #sdc41 #esp32
- It was paired with bmp280 and it was using pressure to Compensate the co2. but bmp 280 was showing bad value randomly about -149 hpa
- As it was using BMP280 value it was started showing double and after few days it started showing 0 ppm
- Now i ran simple code to just get value of sdc41 it was showing 23000 which more than current limit of sdc41 which is 5000
- Ran frc 420 to self calibrate the sensor with current value to 420 reference
- Ran auto calibration , everything looks good now.
#include <Arduino.h>
#include <Wire.h>
#include <SparkFun_SCD4x_Arduino_Library.h>
// NodeMCU/ESP8266 I2C pins: D2 = SDA, D1 = SCL
static const uint8_t SDA_PIN = D2;
static const uint8_t SCL_PIN = D1;
static const uint16_t MAX_VALID_CO2_PPM = 5000;
static const uint16_t DEFAULT_FRC_REFERENCE_PPM = 420; // Typical fresh-air reference
static const uint32_t FRC_STABILIZE_MS = 180000; // >= 3 minutes per datasheet
static const float DEFAULT_AMBIENT_PRESSURE_HPA = 1006.0f;
SCD4x scd4x(SCD4x_SENSOR_SCD41);
bool applyAmbientPressureCompensation(float pressureHpa, bool persist = true) {
if (pressureHpa < 700.0f || pressureHpa > 1200.0f) {
Serial.println("ERROR: pressure out of expected range (700..1200 hPa).");
return false;
}
const float pressurePa = pressureHpa * 100.0f;
scd4x.stopPeriodicMeasurement();
delay(500);
if (!scd4x.setAmbientPressure(pressurePa)) {
Serial.println("ERROR: Failed to set ambient pressure compensation.");
scd4x.startPeriodicMeasurement();
return false;
}
if (persist && !scd4x.persistSettings()) {
Serial.println("WARNING: Ambient pressure set but persistSettings failed.");
}
if (!scd4x.startPeriodicMeasurement()) {
Serial.println("ERROR: Failed to restart periodic measurement after pressure update.");
return false;
}
Serial.print("Ambient pressure compensation set to ");
Serial.print(pressureHpa, 1);
Serial.println(" hPa");
return true;
}
bool configureAutoSelfCalibration(bool enabled) {
scd4x.stopPeriodicMeasurement();
delay(500);
if (!scd4x.setAutomaticSelfCalibrationEnabled(enabled)) {
Serial.println("ERROR: Failed to set ASC state.");
return false;
}
if (!scd4x.persistSettings()) {
Serial.println("WARNING: ASC updated but persistSettings failed.");
}
if (!scd4x.startPeriodicMeasurement()) {
Serial.println("ERROR: Failed to restart periodic measurement after ASC config.");
return false;
}
Serial.print("ASC is now ");
Serial.println(enabled ? "ENABLED" : "DISABLED");
return true;
}
bool runForcedRecalibration(uint16_t referencePpm) {
if (referencePpm < 350 || referencePpm > 2000) {
Serial.println("ERROR: Reference ppm out of expected FRC range (350..2000).");
return false;
}
Serial.println();
Serial.println("Starting Forced Recalibration (FRC)...");
Serial.println("Keep sensor in stable, known CO2 environment.");
Serial.print("Reference CO2 (ppm): ");
Serial.println(referencePpm);
scd4x.stopPeriodicMeasurement();
delay(500);
if (!scd4x.startPeriodicMeasurement()) {
Serial.println("ERROR: Could not start periodic measurement for FRC preconditioning.");
return false;
}
// Datasheet requirement: run in normal mode for at least 3 minutes before FRC.
const uint32_t startMs = millis();
while (millis() - startMs < FRC_STABILIZE_MS) {
const uint32_t remaining = (FRC_STABILIZE_MS - (millis() - startMs)) / 1000;
Serial.print("FRC preconditioning... ");
Serial.print(remaining);
Serial.println(" s remaining");
if (scd4x.readMeasurement()) {
Serial.print(" sample CO2(ppm): ");
Serial.println(scd4x.getCO2());
}
delay(5000);
}
scd4x.stopPeriodicMeasurement();
delay(500);
float correction = 0.0f;
if (!scd4x.performForcedRecalibration(referencePpm, &correction)) {
Serial.println("ERROR: FRC failed.");
scd4x.startPeriodicMeasurement();
return false;
}
Serial.print("FRC correction applied (ppm): ");
Serial.println(correction, 1);
if (!scd4x.persistSettings()) {
Serial.println("WARNING: FRC succeeded but persistSettings failed.");
}
if (!scd4x.reInit()) {
Serial.println("WARNING: reInit failed after FRC.");
}
if (!scd4x.startPeriodicMeasurement()) {
Serial.println("ERROR: Could not restart periodic measurement after FRC.");
return false;
}
Serial.println("FRC complete. Sensor restarted in periodic mode.");
return true;
}
void handleSerialCommands() {
if (!Serial.available()) {
return;
}
String cmd = Serial.readStringUntil('\n');
cmd.trim();
cmd.toLowerCase();
if (cmd == "help") {
Serial.println("Commands:");
Serial.println(" help -> show commands");
Serial.println(" asc on -> enable automatic self-calibration");
Serial.println(" asc off -> disable automatic self-calibration");
Serial.println(" frc -> run forced recalibration with 420 ppm reference");
Serial.println(" frc <ppm> -> run forced recalibration with custom reference");
Serial.println(" pressure -> set pressure compensation to 1006 hPa");
return;
}
if (cmd == "asc on") {
configureAutoSelfCalibration(true);
return;
}
if (cmd == "asc off") {
configureAutoSelfCalibration(false);
return;
}
if (cmd == "frc") {
runForcedRecalibration(DEFAULT_FRC_REFERENCE_PPM);
return;
}
if (cmd.startsWith("frc ")) {
int ppm = cmd.substring(4).toInt();
if (ppm <= 0) {
Serial.println("ERROR: Invalid ppm. Example: frc 420");
return;
}
runForcedRecalibration((uint16_t)ppm);
return;
}
if (cmd == "pressure") {
applyAmbientPressureCompensation(DEFAULT_AMBIENT_PRESSURE_HPA, true);
return;
}
Serial.print("Unknown command: ");
Serial.println(cmd);
Serial.println("Type 'help' for available commands.");
}
void setup() {
Serial.begin(115200);
delay(200);
Serial.println();
Serial.println("SCD41 minimal CO2 test starting...");
Wire.begin(SDA_PIN, SCL_PIN);
Wire.setClock(100000);
if (!scd4x.begin(true, true, false)) {
Serial.println("ERROR: SCD41 init failed. Check wiring, power, and I2C address.");
while (true) {
delay(1000);
}
}
// Ensure periodic mode starts from a clean state.
scd4x.stopPeriodicMeasurement();
delay(500);
if (!scd4x.startPeriodicMeasurement()) {
Serial.println("ERROR: Could not start low-power periodic measurement.");
while (true) {
delay(1000);
}
}
// Recommended for SCD41 long-term stability in real deployments.
configureAutoSelfCalibration(true);
// Overwrite any previously saved bad pressure setting (e.g. from a faulty upstream sensor)
// and persist the correct local pressure compensation.
applyAmbientPressureCompensation(DEFAULT_AMBIENT_PRESSURE_HPA, true);
Serial.println("SCD41 initialized. First reading can take ~30s.");
Serial.println("Type 'help' in Serial Monitor for calibration commands.");
}
void loop() {
handleSerialCommands();
if (!scd4x.readMeasurement()) {
Serial.println("Measurement not ready yet...");
delay(5000);
return;
}
const uint16_t co2 = scd4x.getCO2();
const float temperature = scd4x.getTemperature();
const float humidity = scd4x.getHumidity();
Serial.print("CO2(ppm): ");
Serial.print(co2);
Serial.print(" Temp(C): ");
Serial.print(temperature, 2);
Serial.print(" RH(%): ");
Serial.println(humidity, 2);
if (co2 == 0) {
Serial.println("WARNING: CO2 is 0 ppm (invalid on SCD41). Sensor may still be stabilizing or faulty.");
} else if (co2 > MAX_VALID_CO2_PPM) {
Serial.println("WARNING: CO2 is above SCD41 range (5000 ppm). Reading likely invalid; run 'frc 420' in fresh air.");
}
delay(5000);
}
FRC preconditioning... 24 s remaining
sample CO2(ppm): 23076
FRC preconditioning... 19 s remaining
sample CO2(ppm): 23076
FRC preconditioning... 14 s remaining
sample CO2(ppm): 23077
FRC preconditioning... 9 s remaining
sample CO2(ppm): 23075
FRC preconditioning... 4 s remaining
sample CO2(ppm): 23078
FRC correction applied (ppm): -22518.0
FRC complete. Sensor restarted in periodic mode.
Measurement not ready yet...
CO2(ppm): 471 Temp(C): 30.83 RH(%): 73.06
CO2(ppm): 472 Temp(C): 30.39 RH(%): 74.56
CO2(ppm): 488 Temp(C): 30.14 RH(%): 75.75
CO2(ppm): 486 Temp(C): 29.92 RH(%): 76.85
CO2(ppm): 469 Temp(C): 29.68 RH(%): 77.88