MH-Z14a reading below 400ppm while outside after zero point calibration

Hello all,
So, I'm a newb at this stuff and I'm basically just piecing things together for the most part.
I have a project, that among other things measures CO2 levels using this arduino mega 2560 R3 and an MH-Z14a.
I've attempted calibrating the sensor by letting it run outdoors for >24hours and also by sending the zero point calibration command after 30min. Neither seem to work as I still read below 400ppm (between 140 and 200ppm) outdoors.

Here's a "working" slice of the code that I'm having issues with:

#include <Arduino.h>
#include <LiquidCrystal.h>

int SecToPreheatCO2Sensor = 180; //Seconds to PreHeat the CO2 Sensor (docs call for 180 seconds)
int CO2PPM = 0;
long MillisToPreheatCO2Sensor = (SecToPreheatCO2Sensor * 1000L); //Milliseconds to PreHeat the CO2 Sensor
long MillisToCalibrateCO2 = 1800000; //176400000; //49 hours
long CO2WarmingTimer = 0;
long CO2CountdownTimer = 0;
long CO2CalibrationTimer = 0;
long LastCO2Reading = 0;

bool CO2Calibrated = false;


//LCD Config - Parameters: (rs, enable, d4, d5, d6, d7)//
LiquidCrystal lcd(2, 3, 4, 5, 6, 7); 
/////////////////////////////////////////////////////////


//CO2 Config///////////////////////////////////////////////////////////////////////////////////
byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};  // get gas command
byte cmdCal[9] = {0xFF, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78};  // calibrate command
byte mhzCmdABCEnable[9] = {0xFF,0x01,0x79,0xA0,0x00,0x00,0x00,0x00,0xE6};
byte mhzCmdABCDisable[9] = {0xFF,0x01,0x79,0x00,0x00,0x00,0x00,0x00,0x86};
char response[9];  // holds the recieved data
///////////////////////////////////////////////////////////////////////////////////////////////


//ChamberMode Switch Config////////////////////////////////////////////////////////////////////////
#define ChamberModeSwitchPin A0 //for 3-way switch
int ChamberModeSwitchValue = 0; //integer for analog value of pin that mode switch is connected to
String PreviousChamberMode = ""; //for 3-way switch
String ActualChamberMode = ""; //for 3-way switch
////////////////////////////////////////////////////////////////////////////////////////////


void setup() {

//Initialize Serial Busses/////////////////////////////
  Serial.begin(9600);
    while(!Serial);    // Time to get serial running
   
  Serial1.begin(9600); //CO2 runs on Serial 1
    while(!Serial1);    // time to get serial1 running
///////////////////////////////////////////////////////


// LCD Initialization/////////////////////////////////////////////////////////
  lcd.begin(20,4);//Specifies the dimensions (width and height) of the display
//////////////////////////////////////////////////////////////////////////////


//Call ChamberMode Function for Confirmation - reads a POT to determine mode//
ChamberMode();
//////////////////////////////////////////////////////////////////////////////


//Call calibration function (will not run unless in calibrate mode)//
calibrate();
/////////////////////////////////////////////////////////////////////

}



void loop() 
{
  
}


//Chamber Mode Function////////////////////////////////////////////////
void ChamberMode()
      {
        ActualChamberMode = "Calibration";
      }
///////////////////////////////////////////////////////////////////////


//CO2 Sensor Calibration Function/////////////////////////////////////////////////////////////////////////////////
void calibrate()
  {
    if (ActualChamberMode == "Calibration")
      {
        Serial1.write(mhzCmdABCEnable, 9);
        CO2CalibrationTimer = millis();
        LCDDataRefresh();
        WarmCO2();
        while ((millis() - CO2CalibrationTimer < MillisToCalibrateCO2) && (ActualChamberMode == "Calibration"))
          {
            GetCO2Readings();
            ChamberMode();
            LCDDataRefresh();
            delay(15000);
          }
        if ((CO2Calibrated == false) && (millis() - CO2CalibrationTimer > MillisToCalibrateCO2))
          {
            CO2Calibrated = true;
            delay(10000);//added 10 sec delay in case I screw up the code and start calling calibrate in a loop and it messes the sensor up like I read online
            Serial1.write(cmdCal, 9);
            delay(100);
            while (CO2Calibrated == true)
             {
              GetCO2Readings();
              ChamberMode();
              LCDDataRefresh();
              delay(15000);
             }
          }
       }
  }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////


//CO2 Reading Function///////////////////////////////////////////////////////////////////////////////////////
void GetCO2Readings()
{
  while (Serial1.available())  // this clears out any garbage in the RX buffer
  {
    int garbage = Serial1.read();
  }
  Serial1.write(cmd, 9);  // Sent out read command to the sensor
  Serial1.flush();  // this pauses the sketch and waits for the TX buffer to send all its data to the sensor

  while (!Serial1.available())  // this pauses the sketch and waits for the sensors responce
  {
    delay(0);
  }

  Serial1.readBytes(response, 9);  // once data is avilable, it reads it to a variable
  int responseHigh = (int)response[2];
  int responseLow = (int)response[3];
  CO2PPM = (256 * responseHigh) + responseLow;

  LastCO2Reading = millis();
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////


//Warmup CO2 Sensor Funtion//////////////////////////////////////////////////////////////////////////////////////////////////////////////
void WarmCO2()
  {
    CO2WarmingTimer = millis(); 
    Serial.println("CO2 Warmup Countdown Starting");
    while (millis() - CO2WarmingTimer < MillisToPreheatCO2Sensor)
    {
      if (millis() - CO2CountdownTimer > 1000)
        {
          Serial.println((MillisToPreheatCO2Sensor - (millis() - CO2WarmingTimer)) / 1000);  // counts down till the sensor is ready
          lcd.setCursor(0,3);
          lcd.print("CO2WarmupTimer:");
          lcd.setCursor(15,3);
          lcd.print("     ");
          lcd.setCursor(15,3);
          lcd.print((MillisToPreheatCO2Sensor - (millis() - CO2WarmingTimer)) / 1000);
          CO2CountdownTimer = millis();
        }

    }
      GetCO2Readings();
      Serial.print("CO2 Read in Setup: ");
      Serial.println(CO2PPM);
      LastCO2Reading = millis();
  }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


//LCD Data Refresh Function////////////////////////////////////////////////////////////////////
void LCDDataRefresh()
{
  lcd.clear(); // Clears the display

  if (ActualChamberMode == "Calibration")
    {
        lcd.print("Mode:");
        lcd.setCursor(6,0);
        lcd.print(ActualChamberMode);
          lcd.setCursor(0,1);
          lcd.print("T-Minus:");
          lcd.setCursor(9,1);
          lcd.print(((MillisToCalibrateCO2 - (millis() - CO2CalibrationTimer)) / 1000) /60);
          lcd.setCursor(0,2);
          lcd.print("Calibrated?");
          lcd.setCursor(12,2);
          lcd.print(CO2Calibrated);
            lcd.setCursor(0,3);
            lcd.print("CO2PPM Reading:");
            lcd.setCursor(15,3);
            lcd.print("     ");
            lcd.setCursor(15,3);
            lcd.print(CO2PPM);
    }
}
//////////////////////////////////////////////////////////////////////////////////////////////

Sorry if the code organization or comment lines are weird, it just really helps me read the code more easily.
Here's the wiring diagram: (I've also attached it in case the google link doesn't work)

Basically I just have it wired directly to the Mega's 5v, gnd and serial1 at D18 and D19.
The Mega is being powered via 5v usb.

Not sure if I'm missing anything, hopefully someone out there can give me a couple pointers.

Did you calibrate the sensor with test gas with a known Co2 PPM? If not your readings are meaningless, I would expect outdoor air to have less Co2 than indoor, where there are living beings exhaling Co2.

instructions say to calibrate in an outdoor environment where the CO2 concentration is more or less 400PPM.
Sending the Zero point commend will set the current CO2 concentration to 400PPM so if the sensor is outdoors you will get it calibrated within about 15ppm or so.
Allowing it to run for 24+ hours with ABC (Automatic Baseline Calibration) enabled will take the lowest concentration and set that as 400 so again, being outdoors it will get the calibration to within 15ppm.

Yes, there will always be higher CO2 levels indoors if there are people or animals inside.

conradcliff:
There's a lot of code so I figured I would just add the portions that are relevant...

Translated: "don't bother to read this as it's incomplete anyway".What you should do is create a minimal example that shows the problem. So code that does no more than read that one sensor and print the result to the Serial monitor or your LCD display. Then post that code.
The moment you send the calibrate command, I expect the subsequent readings to go to 400 ppm, as you just calibrated it to that point. If that doesn't happen, maybe your calibrate command has an issue and the sensor does not actually calibrate itself.

wvmarle:
Translated: "don't bother to read this as it's incomplete anyway".What you should do is create a minimal example that shows the problem. So code that does no more than read that one sensor and print the result to the Serial monitor or your LCD display. Then post that code.
The moment you send the calibrate command, I expect the subsequent readings to go to 400 ppm, as you just calibrated it to that point. If that doesn't happen, maybe your calibrate command has an issue and the sensor does not actually calibrate itself.

What I've tried to do here is create a minimal example that shows the problem with the code that I'm using.
The program only executes the code I've posted above while it is in calibrate mode, so that's the portion I posted .
I will see if this code will run on it's own and edit the post so that no one else will translate it in the way you describe.
And yes, that would be the expected behavior.

Yes, perhaps my calibrate command does have an issue...that is why I'm posting, to see if anyone can spot the issue.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.