/* sensor MQ7. alternate heater 5V/1.4V 60sec/90sec // read sensor @ 88sec(of 90-period) // Kn.Ny '16 // This program to be used as CO-alarm in tents wintertime during night. (gasoline or firewood burners) // http://www.savvysolutions.info/savvymicrocontrollersolutions/arduino.php?topic=arduino-mq7-CO-gas-sensor // http://www.savvysolutions.info/savvymicrocontrollersolutions/index.php?sensor=mq-7-gas-sensors // ****************************************************************************************************/ #define serial #define warnlevel 50 //CO ppm warnlevel #define continousMeasurement #define HeatOnPin D0 #define Buzzer D5 #define ADCOffset 3 #define lotime 90000UL // 90 sek low heating #define hitime 60000UL // 60 sek high heating #define read_time 148000UL // read 2 sek ahead of end low period #define thresholdMeasurement 10 //ToDo /* struct for rtc memory */ struct { bool burnIn = false; //set to false in productive unsigned int minADC = 65535; //save minimal adc value byte ringPointer = 0; unsigned int adcRingBuf[thresholdMeasurement] = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }; //ring buffer for ten values } rtcData; // Adjust vRef to be the true supply voltage in mV. const float vRef = 4470.0; //mVolt const float RL = 10.0; //kOhm const float CleanAirRatio = 26.09; float Rs = 0.0; float R0 = 0.0; float ambTemp = 20.0; float ambHum = 65.0; unsigned int measurement = 0; uint32_t calculateCRC32( const uint8_t *data, size_t length ) { uint32_t crc = 0xffffffff; while ( length-- ) { uint8_t c = *data++; for ( uint32_t i = 0x80; i > 0; i >>= 1 ) { bool bit = crc & 0x80000000; if ( c & i ) { bit = !bit; } crc <<= 1; if ( bit ) { crc ^= 0x04c11db7; } } } return crc; } void updateRTC() { //ToDo save rtcData //crc32 stuff } void getRTC() { //crc32 stuff //ToDo get rtcData } void saveMeasurement (unsigned int measurement) { rtcData.adcRingBuf[rtcData.ringPointer] = measurement; rtcData.ringPointer = (rtcData.ringPointer + 1) % thresholdMeasurement; } float CalcRsFromVRL(float VRL) { // VRL = RL 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 if ( VRL == 0.0 ) { return vRef * VRL; } return ((vRef - VRL) * (RL / VRL)); } float CalcRsFromADC(float ADC) { if ( ADC == 0 ) { return 10240; } return (RL * (1024 - ADC) / ADC); } unsigned int GetCOPpmForRatioRsRo(float RsRo_ratio) { /* x=Rs/R0; y=ppm 1,6122158133673148; 50,488950942143966 0,999928961393748; 100 0,39070475612941435; 396,4363929567069 0,22807477512659288; 1000 0,09483831585572762; 3964,363929567065 y = 100.607 * (Rs/R0)^-1.54901 this regression needs to be shifted by the CleanAirRatio. R0 = Rs/CleanAirRatio => Rs/R0 => Rs/(Rs_old/CleanAirRatio) */ float ppm = 0; ppm = 100.607 * pow( (RsRo_ratio * CleanAirRatio), -1.54901); return (unsigned int) ppm; } int measurementAnalog() { //measure x values and get the mean value static unsigned int x = 10; unsigned int valueADC = 0; for (unsigned int i = 0; i < x; i++) { valueADC += analogRead(A0); delay(10); } Serial.println("DEBUG: measurement ADC " + String(valueADC / x)); return (valueADC / x); } void burnIn(bool trigger) { /* if not flag in eeprom set then burn in sensor */ unsigned int measureRow[thresholdMeasurement] = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }; int counter = 0; //set timer with the current milli counter unsigned long starttime = millis(); unsigned long currenttime = millis(); unsigned long static burnInTime = 172800000; //48 hours * 60 minutes * 60 seconds * 1000 milli Serial.println("DEBUG: burnIn"); if ( !rtcData.burnIn ) { digitalWrite(HeatOnPin, HIGH); // => 5V while ( currenttime < burnInTime ) { delay(1000); currenttime = abs(millis() - starttime); Serial.print("\r" + String((int)((currenttime / 1000) / 360)) + ":" + String((int)((currenttime / 1000) / 60)) + ":" + String((int)((currenttime / 1000) % 60)) + ".."); if (trigger) { unsigned int tmp = CalcRsFromADC(measurementAnalog()); measureRow[counter] = tmp; counter = ( counter + 1 ) % thresholdMeasurement; Serial.println("BurnIn: Current RS from ADC " + String(tmp)); //ToDo a value which means that the sensor is clean enough. //However the resistance drop in first sigth and stabilize then at a specific value, mine approx. 37k Ohm. /*if (tmp >= 36.0) { Serial.println("BurnIn: Trigger"); break; }*/ int sum = 0; for (int i = 0; i < thresholdMeasurement - 1; i++) { sum = sum + abs(measureRow[i] - measureRow[i + 1]); } Serial.println("DEBUG: Currentsum value " + String(sum)); if (sum <= 0) { Serial.println("BurnIn: Stabilization"); break; } } } //set initial lowest value rtcData.minADC = measurementAnalog(); rtcData.burnIn = true; Serial.println("DEBUG BurnIn:\t" + String(currenttime / 1000) + " seconds| rtc.minADC" + String(rtcData.minADC) + " || RS " + String(CalcRsFromADC(rtcData.minADC))); } } /* a method to react to changing environmental parameters. ToDo: The values are NOT usuable! void compensateDriftOld() { if (Rs <= 20.0 && Rs > R0) { R0 = Rs; Serial.println("DEBUG: Drift compensate done. New R0:\t" + String(R0)); return; } if ( Rs > 20.0) { Serial.println("DEBUG: Sensor needs cleaning!"); burnIn(false); return; } } */ /* a method to react to changing environmental parameters. take the ADC values and calculate the summed up difference. renew the R0 if stabilizied ADc values in a row and the old R0 value needs to be smaller */ void compensateDrift() { int sum = 0; for (int i = 0; i < thresholdMeasurement - 1 ; i++) { sum = sum + (abs(rtcData.adcRingBuf[i] - rtcData.adcRingBuf[i + 1])); } if (R0 > Rs && sum <= 1) { R0 = Rs; Serial.println("DEBUG: Drift compensate done. New R0:\t" + String(R0)); return; } //check for lowest resistence after heating if ( rtcData.adcRingBuf[rtcData.ringPointer] > rtcData.minADC) { Serial.println("DEBUG: Sensor needs cleaning!"); burnIn(false); return; } } int measurementCycle() { //set timer with the current milli counter unsigned long starttime = millis(); unsigned long currenttime = 0; static unsigned long next_time = hitime + lotime; //set complete runtime for one measurement unsigned int currentMeasurement, lastMeasurement = 0; boolean done_reading = true; //only a simple toggle switch //start cleaning sensitive part of sensor Serial.println("-------------------------------------------------------------"); digitalWrite(HeatOnPin, HIGH); // => 5V Serial.print("5V:\t on"); while (currenttime < next_time) { //refresh current time currenttime = abs(millis() - starttime); #ifndef continousMeasurement lastMeasurement = currentMeasurement; currentMeasurement = measurementAnalog(); if (thresholdMeasurement > abs(lastMeasurement - currentMeasurement) ) { delay(10); } if (done_reading && currenttime >= hitime) //sensor reach heating time limit { digitalWrite(HeatOnPin, LOW); // => 1.4V Serial.print("\n\r1.4V:\t on\n"); done_reading = false; } if (currenttime >= next_time ) { break; } #else if (done_reading && currenttime >= hitime) //sensor reach heating time limit { digitalWrite(HeatOnPin, LOW); // => 1.4V Serial.print("\n\r1.4V:\t on\n"); done_reading = false; } if (currenttime > read_time) // then do a reading now { currentMeasurement = measurementAnalog(); done_reading = true; break; //add nynquist thereom and sums up multiple measurements } delay(50); #endif } return currentMeasurement; } void calibrateCleanAir() { Serial.println("DEBUG: calibrate Clean AIR"); //wait until sensor reach low end resistance unsigned int i = 0; while ( true ) { i++; Rs = CalcRsFromADC(measurementCycle()); Serial.println("Calibrate Clean Air:\t#" + String(i) + "\tRs: \t" + String(Rs)); /* At least three measurements to settle down the sensor ToDo : the datasheet mentions that the Rs is between 2k and 20k for 100ppm in air. Therefore the values multiplies with the clean_air_ratio of 26.09 That leads to a range: 5,2MOhm up to 52,2MOhm which is not in any reach of the real world (17.06.18: current value is ADC 2 up to 3, which is 3403 or 5110 Ohm. the resistance get lower with higher CO measurements. I guess the maximum value of 20k for 100ppm in air (a cold sensor has Rs = 330k) */ //if (i >= 3 && Rs <= 20.0) { if (i >= 3 && Rs >= 3000.0) { //Serial.println ("Rs <= 20.0"); Serial.println ("Rs >= 3000.0"); break; } } R0 = Rs; Serial.println("R0: \t" + String(R0)); } void ambientMeasurement() { } float correlateAmbient() { return 1.0; } // the setup routine runs once when you press reset: void setup() { // initialize serial communication at 115200 bits per second: Serial.begin(115200); pinMode(HeatOnPin, OUTPUT); burnIn(true); calibrateCleanAir(); } void loop() { float ppm = 0; //save adc data saveMeasurement(measurementCycle()); Rs = CalcRsFromADC(rtcData.adcRingBuf[rtcData.ringPointer]); Serial.println("Rs: \t" + String(Rs) + " | R0:\t" + String(R0) + " || Ratio:\t" + String(Rs / R0) + "| Ratio:\t" + String(Rs / R0 * CleanAirRatio) ); ppm = GetCOPpmForRatioRsRo(Rs / R0); Serial.println("CO ppm: \t" + String(ppm)); compensateDrift(); }