MQ7-esp/MQ7-ESP.ino
2018-11-18 19:59:48 +01:00

332 lines
9.9 KiB
C++

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