inital push

This commit is contained in:
coelner 2018-11-18 19:59:48 +01:00
commit fa6470723e
2 changed files with 394 additions and 0 deletions

331
MQ7-ESP.ino Normal file
View File

@ -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();
}

63
README:md Normal file
View File

@ -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