Arduino + MQ-7 CO gas sensor

The MQ-7 gas sensor measures concentrations of carbon monoxide (CO).  

Background

See link at http://www.savvymicrocontrollersolutions.com/index.php?sensor=mq-7-gas-sensors for information on calculations and equations within the Arduino sketch file.

See link at http://www.savvymicrocontrollersolutions.com/index.php?sensor=mq-gas-sensors for general information about the MQ line of gas sensors and how to make connections to them.

The Code

/*

  MQ-7 CO (carbon monoxide) Gas Sensor
  
  This sketch includes the timer function and the sensor 
  reading functions.
 
  Sensor must be one time 'burned-in' for 24 hours prior to 
  general use.  Burn-in consists of 5.0V for 60 sec followed
  by 1.4V for 90 sec.  You can run this sketch for 24 hours
  (ignoring the readings / alarms), to burn-in the sensor.
  
  After burn-in, regular use of the sensor consists of 
  calibration in clean air.  Therafter, the sensor needs 
  to be subjected to a heating cycle of 5.0V for 60 sec, 
  followed by 1.4V for 90 sec.  After two heating cycles (5.0 min
  total), a reading may be taken at the end of the high 5.0V
  heating cycle, just before transitioning to the low 1.4V
  heating cycle.  
  
  This sketch checks for the presense of CO every 20 minutes.  
  It performs a 2 cycle (5.0 min) warm up (blinking the 
  green LED), then it takes a sensor reading (blinking the red LED).  
  If the COG threshold of 50 ppm is exceeded, an alarm condition is
  set and it persists until the device is reset.  
  
  
  In the United States, OSHA limits long-term workplace exposure 
  levels above 50 ppm.  The average level in homes is 0.5-5ppm. 
  The level near properly adjusted gas stoves in homes and from 
  modern vehicle exhaust emissions is 5-15ppm. The exhaust from 
  automobiles in Mexico City central area is 100-200ppm. 
  The amount of CO that can be created from the exhaust from 
  a home wood fire is 5000ppm. Concentrations as low as 667ppm 
  may cause up to 50% of the body's hemoglobin to convert to 
  carboxyhemoglobin (a level that may result in seizure, 
  coma, and death). 

  Startup & calibration:
    Green LED = fast blink. Heating up MQ-7 sensor.
    Red LED = fast blink.  Taking MQ-7 sensor reading.
    Red LED = on 5 sec + buzzer chirp.  DHT11 sensor error.
    Red LED = steady on + buzzer = CO concentration exceeded !
  
  Normal operation:
    Green LED = steady on.  Power on
    Red LED = on ~3 sec.  Taking MQ-7 sensor reading.
    Red LED = on 5 sec + buzzer chirp.  DHT11 sensor error.
    Red LED = steady on + buzzer = CO concentration exceeded !
  
  Resources:
    A0  gas sensor signal
    DIO2  green LED.  
    DIO3  buzzer  
    DIO4  red LED.  
    DIO5  DHT11 temperature / humidity sensor.
    DIO7  NPN transistor for 12VDC relay
 
 Written by:  Mark Kiehl
 
 */

byte pinGreenLED = 2;
byte pinRedLED = 4;

byte pinBuzzer = 3;

boolean heaterHigh = true;
byte heat_cycles = 0;

//  60 sec high heat (5.0V)
unsigned long timerA = 60000;  
unsigned long timerAlap = millis();  // timer

//  90 sec low heat (1.4V)
unsigned long timerB = 90000;  
unsigned long timerBlap = millis();  // timer
//  The difference between timerB and timerRead is 
//  how long a measurement will be made for MQ-7.
unsigned long timerRead = 60000;

byte pinNPN = 7;
byte pinMQ = A0;
boolean alarmCO = false;
//  The USA OSHA exposure limit for CO is 50 ppm.
//  The average level in a home is 0.5 to 5 ppm.
//  The level in a home with a proper adj gas stove is 5 to 15 ppm.
//  A CO of 667 ppm may result in seizure, coma, and death.
const unsigned int CO_threshold = 50;
// Adjust vRef to be the true supply voltage in mV.
float vRef = 5000.0;
float RL = 10.0;  //  load resistor value in k ohms
float Ro = 10.0;  //  default value 10 k ohms.  Revised during calibration.
const float Ro_clean_air_factor = 10.0;

float mV = 0.0;
unsigned long samples = 0;

////////////////////////////////////////////////////////////////////////
// DHT11 humidity/temperature sensor
#include "DHT.h"
const unsigned int pinDHT = 5;
boolean errDHT11 = false;
// Uncomment whatever type you're using!
//#define DHTTYPE DHT11     // DHT 11 
#define DHTTYPE DHT22   // DHT 22  (AM2302)
//#define DHTTYPE DHT21   // DHT 21 (AM2301)

DHT dht(pinDHT, DHTTYPE);
////////////////////////////////////////////////////////////////////////
// With sensor holes facing you:
// Connect pin 1 (on the left) of the sensor to +5V
// Connect pin 2 of the sensor to whatever your DHTPIN is
// Connect pin 4 (on the right) of the sensor to GROUND
// Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor


void setup() {
  pinMode(pinGreenLED, OUTPUT);
  delay(1);
  pinMode(pinRedLED, OUTPUT);
  delay(1);
  pinMode(pinMQ, INPUT); 
  delay(1);
  pinMode(pinBuzzer, OUTPUT);
  delay(1);
  pinMode(pinNPN, OUTPUT);
  delay(1);

  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect
  }
  //  Delay to give the Arduino Micro time to connect the Serial Monitor.
  delay(35000);

  Serial.print("DHT");
  Serial.print(DHTTYPE);
  Serial.println(" setup");  
  dht.begin();
  //  Get the ambient conditions (deg C & relative humidity) from DHT11
  float ambRH = dht.readHumidity();
  float ambTemp = dht.readTemperature();
  Serial.println("  ");
  // check if returns are valid, if they are NaN (not a number) then something went wrong!
  if (isnan(ambTemp) || isnan(ambRH)) {
    Serial.println("Failed to read from DHT");
    Serial.println("  ");
    errDHT11 = true;
    digitalWrite(pinRedLED, HIGH);
    digitalWrite(pinBuzzer, HIGH);
    delay(100);
    digitalWrite(pinBuzzer, LOW);
    delay(5000);
    digitalWrite(pinRedLED, LOW);
  } else {
    //  DHT11 ok, .. proceed.
    Serial.print("ambTemp = ");
    Serial.print(ambTemp);
    Serial.println(" deg C");
    Serial.print("ambRH = ");
    Serial.print(ambRH);
    Serial.println("% ");
  }
  Serial.println("  ");

  Serial.println("Calibrating MQ-7 CO sensor in clean air..");
  Serial.println("  60 sec high heat cycle..");
  digitalWrite(pinNPN, HIGH);
  // set = 200
  for(int i = 200; i > 0; i--){
    blinkLED(pinGreenLED);
  }
  digitalWrite(pinNPN, LOW);
  Serial.println("  60 sec warmup complete");
  
  Serial.println("  90 sec heat cycle..");
  // set = 300
  for(int i = 300; i>0; i--){
    blinkLED(pinGreenLED);
  }
  Serial.println("  90 sec warmup complete.  Reading MQ-7..");
  
  // If mV > 3000, then repeat warm-up..
  
  //  take a reading..
  // set = 300
  for(int i = 300; i>0; i--){
    blinkLED(pinGreenLED);
    mV += Get_mVfromADC(pinMQ);
    samples += 1;
  }
  mV = mV / (float) samples;
  Serial.print("  avg A");
  Serial.print(pinMQ);
  Serial.print(" for ");
  Serial.print(samples);
  Serial.print(" samples = ");
  Serial.print(mV);
  Serial.println(" mV");
  Serial.print("  Rs = ");
  Serial.println(CalcRsFromVo(mV));
  //  Conv output to Ro
  //  Ro = calibration factor for measurement in clean air.
  //  Ro = ((vRef - mV) * RL) / (mV * Ro_clean_air_factor);
  //  Hereafter, measure the sensor output, convert to Rs, and
  //  then calculate Rs/Ro using: Rs = ((Vc-Vo)*RL) / Vo
  Ro = CalcRsFromVo(mV) / Ro_clean_air_factor;
  Serial.print("  Ro = ");
  Serial.println(Ro);
  //  Values in clean air are:
  //    Rs = 6.99
  //    Ro = 0.70
  Serial.println("Sensor calibration in clean air complete");
  Serial.println("Setup complete.  Monitoring for CO..");
  Serial.println("  ");
  digitalWrite(pinNPN, LOW);
  mV = 0.0;
  samples = 0;

  //  Start with heater on high
  heaterHigh = true;
  timerAlap = millis(); // reset the timer
  
  blinkLED(pinGreenLED);
  blinkLED(pinRedLED);
  //  Chirp the buzzer
  digitalWrite(pinBuzzer, HIGH);
  delay(1);
  digitalWrite(pinBuzzer, LOW);
  delay(1);
  //digitalWrite(pinGreenLED, HIGH);
  //delay(1);
}

void loop() {
  
  // if millis() or timer wraps around, we'll just reset it
  if (timerAlap > millis())  timerAlap = millis();
  if (timerBlap > millis())  timerBlap = millis();

  if (heaterHigh == false && heat_cycles == 2 && (millis() - timerBlap > timerRead)) {
    //  take reading of MQ sensor..
    digitalWrite(pinGreenLED, HIGH);
    mV += Get_mVfromADC(pinMQ);
    samples += 1;
  } else {
    digitalWrite(pinGreenLED, LOW);
  }

  
  if (heaterHigh == true) {
    //  High heat applied for 60 sec
    digitalWrite(pinNPN, HIGH);
    //  Timer A
    if (millis() - timerAlap > timerA) { 
      timerAlap = millis(); // reset the timer
      timerBlap = millis(); // reset the timer
      heaterHigh = false;
    }
  } else {
    //  heaterHigh = false
    //  Low heat applied for 90 sec
    digitalWrite(pinNPN, LOW);
    //  Timer B
    if (millis() - timerBlap > timerB) { 
      timerAlap = millis(); // reset the timer
      timerBlap = millis(); // reset the timer
      heaterHigh = true;
      heat_cycles += 1;
      Serial.print("end of heat_cycle = ");
      Serial.println(heat_cycles);
      //  Report on MQ-7 measurement at end of 
      //  the low phase of the 3rd heat cycle.
      if (heat_cycles == 3) {
        mV = mV / float (samples);
        Serial.print("samples = ");      
        Serial.println(samples);
        Serial.print("A");
        Serial.print(pinMQ);
        Serial.print("  = ");
        Serial.print(mV);
        Serial.println(" mV");
        Serial.print("Rs = ");
        Serial.println(CalcRsFromVo(mV));
        Serial.println("  ");
        mV = 0.0;
        samples = 0;
      }
    }
  }
  
  if (heat_cycles >= 3) {
    heat_cycles = 0;
  }
  
}

float RsRoAtAmbientTo20C65RH(float RsRo_atAmb, float ambTemp, float ambRH) {
  //  Using the datasheet for MQ-7 sensor, derive Rs/Ro values 
  //  from - 10 to 50 C and 33, 65, and 85 % relative humidity.
  //  For the measured Rs/Ro, use linear interpolation to calculate the
  //  standard Rs/Ro values for the measured ambient temperature and RH.
  //  Next, calculate a correction factor from the standard Rs/Ro at ambient
  //  temp and RH relative to standard Rs/Ro at 20C and 65 RH.  
  //  Apply this correction factor to the measured Rs/Ro value and return the
  //  corrected value.  This corrected value may then be used against the Rs/Ro
  //  Rs/Ro vs CO concentration (ppm) chart to estimate the concentration of CO.
  
  //  Calc RsRo values at ambTemp & 33% RH, 65% and 85% RH
  float RsRo_at_ambTemp_33RH = -0.00000593 * pow(ambTemp, 3) + 0.000533 * pow(ambTemp, 2) - 0.0182 * ambTemp + 1.20;
  float RsRo_at_ambTemp_85RH = -0.0000000741 * pow(ambTemp, 3) + 0.000114 * pow(ambTemp, 2) - 0.0114 * ambTemp + 1.03;
   //float RsRo_at_65RH = ((65.0-33.0)/(85.0-65.0));
  float RsRo_at_ambTemp_65RH = ((65.0-33.0)/(85.0-33.0)*(RsRo_at_ambTemp_85RH-RsRo_at_ambTemp_33RH)+RsRo_at_ambTemp_33RH)*1.102;
  //  Linear interpolate to get the RsRo at the ambient RH value (ambRH).
  float RsRo_at_ambTemp_ambRH;
  if (ambRH < 65.0) {
    RsRo_at_ambTemp_ambRH = (ambRH - 33.0)/(65.0 - 33.0)*(RsRo_at_ambTemp_65RH - RsRo_at_ambTemp_33RH) + RsRo_at_ambTemp_33RH;  
  } else {
    // ambRH > 65.0
    RsRo_at_ambTemp_ambRH = (ambRH - 65.0)/(85.0 - 65.0)*(RsRo_at_ambTemp_85RH - RsRo_at_ambTemp_65RH) + RsRo_at_ambTemp_65RH;
  }
  //  Calc the correction factor to bring RsRo at ambient temp & RH to 20 C and 65% RH.
  const float refRsRo_at_20C65RH = 1.00;
  float RsRoCorrPct = 1 + (refRsRo_at_20C65RH - RsRo_at_ambTemp_ambRH)/refRsRo_at_20C65RH;
  //  Calculate what the measured RsRo at ambient conditions would be corrected to the
  //  conditions for 20 C and 65% RH.
  float measured_RsRo_at_20C65RH = RsRoCorrPct * RsRo_atAmb;
  return measured_RsRo_at_20C65RH;
}

float CalcRsFromVo(float Vo) {
  //  Vo = sensor output voltage in mV.
  //  VRef = supply voltage, 5000 mV
  //  RL = load resistor in k ohms
  //  The equation Rs = (Vc - Vo)*(RL/Vo)
  //  is derived from the voltage divider
  //  principle:  Vo = RL * Vc (Rs + RL)
  //
  //  Note.  Alternatively you could calc
  //         Rs from ADC value using
  //         Rs = RL * (1024 - ADC) / ADC
  float Rs = (vRef - Vo) * (RL / Vo);  
  return Rs;
}

unsigned int GetCOPpmForRatioRsRo(float RsRo_ratio) {
  //  If you extract the data points from the CO concentration
  //  versus Rs/Ro chart in the datasheet, plot the points,
  //  fit a polynomial curve to the points, you come up with the equation
  //  for the curve of:  Rs/Ro = 22.073 * (CO ppm) ^ -0.66659
  //  This equation is valid for ambient conditions of 20 C and 65% RH.
  //  Solving for the concentration of CO you get:
  //    CO ppm = [(Rs/Ro)/22.073]^(1/-0.66666)
  float ppm;
  ppm = pow((RsRo_ratio/22.073), (1/-0.66659));
  return (unsigned int) ppm;
}

float Get_mVfromADC(byte AnalogPin) {
    // read the value from the sensor:
    int ADCval = analogRead(AnalogPin);  
    // It takes about 100 microseconds (0.0001 s) to read an analog input
    delay(1);
    //  Voltage at pin in milliVolts = (reading from ADC) * (5000/1024) 
    float mV = ADCval * (vRef / 1024.0);
    return mV;
}

void blinkLED(byte ledPIN){
  //  consumes 300 ms.
  for(int i = 5; i>0; i--){
    digitalWrite(ledPIN, HIGH);
    delay(30);
    digitalWrite(ledPIN, LOW);
    delay(30);
  }    
}
				
				

 


Do you need help developing or customizing a IoT product for your needs?   Send me an email requesting a free one hour phone / web share consultation.  

 

The information presented on this website is for the author's use only.   Use of this information by anyone other than the author is offered as guidelines and non-professional advice only.   No liability is assumed by the author or this web site.