/* Arduino Sensors & Components 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); } }