332 lines
9.9 KiB
Arduino
332 lines
9.9 KiB
Arduino
|
/* 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();
|
||
|
}
|