commit fa6470723e00acf7fc990a543741349755ae572c Author: coelner Date: Sun Nov 18 19:59:48 2018 +0100 inital push diff --git a/MQ7-ESP.ino b/MQ7-ESP.ino new file mode 100644 index 0000000..f33cc15 --- /dev/null +++ b/MQ7-ESP.ino @@ -0,0 +1,331 @@ +/* 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(); +} diff --git a/README:md b/README:md new file mode 100644 index 0000000..3b82c34 --- /dev/null +++ b/README:md @@ -0,0 +1,63 @@ +CO Sensor +-10.12.17: MQ-7 Sensor, Stecksockel + Platine bestellt + +Aufbau mit wemos D1 mini und Dual Board +-ggf Temperatur/Luftfeuchte Korrektur +Brauche Datasheet sowie Berechnung der Kurven +Low Heat bei bestimmten Umgebungswerten wegen Zerstörungsgefahr durch Wasser +SHT30 Shield (Pin D1 und D2), genauer und einfach anzusteuern +-Entscheidung über OLED oder RGB LED +OLED Display wird ebenfalls über I2C angesteuert (Pin D1 und D2) +-Lokaler Alarm +Buzzer Board (Pin D5) +-globaler Alarm +mqtt/ESP Now + +-Spannungsversorgung über NPN --geht da 0,7V Drop --> N-MOSFET https://arduinodiy.wordpress.com/2012/05/02/using-mosfets-with-ttl-levels/ +IRLML6344 bestellt +-Kalibrierung? +Zwei Möglichkeiten, entweder Clean Air Kalibrierung oder 100ppm Wert + Messgerät +ESP8266 Speichern der Kalibrierung im RTC +-Austausch des Sensors? +Platine enthält Pinlöcher für den Sensor sowie den Sockel +-Spannungsteiler für Ausgangsspannung MQ7 +Zusätzlicher Ausgang plus Spannungsteiler für 1V (ESP8266) +560k/150k -> 1.056V --> Kondensator benötigt? http://esp8266-projects.org/2016/08/esp8266-internal-adc-2-the-easy-way-example/ +3k/820 -> 1.073V --> ggf. zu gering +Spannungsteiler für Wemos Board nicht notwendig -> 150kOhm in Reihe zum eingebauten (110k/220k) Spannungsteiler +ggf. Kondensator für den Spannungsteiler +-Regressionsgerade bestimmen +http://davidegironi.blogspot.de/2017/07/mq-gas-sensor-correlation-function.html +http://davidegironi.blogspot.de/2017/05/mq-gas-sensor-correlation-function.html +Achsen sind vertauscht sinnvoller +beide Skalen logarithmisch +mittels: https://apps.automeris.io/wpd/ die punkte bestimmen +power-regresseion berechnen: http://keisan.casio.com/exec/system/14059931777261 +Min/Max bestimmen + +Platine muss angepasst werden +https://forum.arduino.cc/index.php?topic=294085.0 +http://www.instructables.com/id/Arduino-CO-Monitor-Using-MQ-7-Sensor/ +http://www.savvysolutions.info/savvymicrocontrollersolutions/index.php?sensor=mq-7-gas-sensors +http://www.savvysolutions.info/savvymicrocontrollersolutions/arduino.php?topic=arduino-mq7-CO-gas-sensor + + +Ablauf: +Init System +BurnIn Werte prüfen + Falls nicht vorhanden, BurnIn beginnen +Umgebungswerte messen +Prüfen ob Betrieb von Sensor sicher ist ( insbesondere Wasser) + Preheat nutzen +Clean Air Kalibrierung durchführen + BurnIn Werte korrelieren +Nyquist-Theorem einhalten +Vergleich mit vorherigen Werten im EEPROM auf Abweichung +Warnung bei zu großen Abweichungen, Hinweis auf Alterung + +Übergang zum Regelbetrieb +Messwertreihe vorhalten +Zu schnelle Wertänderungen lösen Warnung aus +Zu hoher Wert löst Warnung aus +Meldung an übergeordnete Instanz bei Warnung +