diff --git a/Thomas_Portable_Air_Quality.pdf b/Thomas_Portable_Air_Quality.pdf new file mode 100644 index 0000000..688b57d Binary files /dev/null and b/Thomas_Portable_Air_Quality.pdf differ diff --git a/savvy.ino b/savvy.ino new file mode 100644 index 0000000..889b3d8 --- /dev/null +++ b/savvy.ino @@ -0,0 +1,392 @@ +/* +Arduino Sensors & Components +Arduino + MQ-7 CO gas sensor + +The MQ-7 gas sensor measures concentrations of carbon monoxide (CO). +Background + +See link at http://www.savvymicrocontrollersolutions.com/index.php?sensor=mq-7-gas-sensors for information on calculations and equations within the Arduino sketch file. + +See link at http://www.savvymicrocontrollersolutions.com/index.php?sensor=mq-gas-sensors for general information about the MQ line of gas sensors and how to make connections to them. +The Code +*/ +/* + + MQ-7 CO (carbon monoxide) Gas Sensor + + This sketch includes the timer function and the sensor + reading functions. + + Sensor must be one time 'burned-in' for 24 hours prior to + general use. Burn-in consists of 5.0V for 60 sec followed + by 1.4V for 90 sec. You can run this sketch for 24 hours + (ignoring the readings / alarms), to burn-in the sensor. + + After burn-in, regular use of the sensor consists of + calibration in clean air. Therafter, the sensor needs + to be subjected to a heating cycle of 5.0V for 60 sec, + followed by 1.4V for 90 sec. After two heating cycles (5.0 min + total), a reading may be taken at the end of the high 5.0V + heating cycle, just before transitioning to the low 1.4V + heating cycle. + + This sketch checks for the presense of CO every 20 minutes. + It performs a 2 cycle (5.0 min) warm up (blinking the + green LED), then it takes a sensor reading (blinking the red LED). + If the COG threshold of 50 ppm is exceeded, an alarm condition is + set and it persists until the device is reset. + + + In the United States, OSHA limits long-term workplace exposure + levels above 50 ppm. The average level in homes is 0.5-5ppm. + The level near properly adjusted gas stoves in homes and from + modern vehicle exhaust emissions is 5-15ppm. The exhaust from + automobiles in Mexico City central area is 100-200ppm. + The amount of CO that can be created from the exhaust from + a home wood fire is 5000ppm. Concentrations as low as 667ppm + may cause up to 50% of the body's hemoglobin to convert to + carboxyhemoglobin (a level that may result in seizure, + coma, and death). + + Startup & calibration: + Green LED = fast blink. Heating up MQ-7 sensor. + Red LED = fast blink. Taking MQ-7 sensor reading. + Red LED = on 5 sec + buzzer chirp. DHT11 sensor error. + Red LED = steady on + buzzer = CO concentration exceeded ! + + Normal operation: + Green LED = steady on. Power on + Red LED = on ~3 sec. Taking MQ-7 sensor reading. + Red LED = on 5 sec + buzzer chirp. DHT11 sensor error. + Red LED = steady on + buzzer = CO concentration exceeded ! + + Resources: + A0 gas sensor signal + DIO2 green LED. + DIO3 buzzer + DIO4 red LED. + DIO5 DHT11 temperature / humidity sensor. + DIO7 NPN transistor for 12VDC relay + + Written by: Mark Kiehl + + */ + +byte pinGreenLED = 2; +byte pinRedLED = 4; + +byte pinBuzzer = 3; + +boolean heaterHigh = true; +byte heat_cycles = 0; + +// 60 sec high heat (5.0V) +unsigned long timerA = 60000; +unsigned long timerAlap = millis(); // timer + +// 90 sec low heat (1.4V) +unsigned long timerB = 90000; +unsigned long timerBlap = millis(); // timer +// The difference between timerB and timerRead is +// how long a measurement will be made for MQ-7. +unsigned long timerRead = 60000; + +byte pinNPN = 7; +byte pinMQ = A0; +boolean alarmCO = false; +// The USA OSHA exposure limit for CO is 50 ppm. +// The average level in a home is 0.5 to 5 ppm. +// The level in a home with a proper adj gas stove is 5 to 15 ppm. +// A CO of 667 ppm may result in seizure, coma, and death. +const unsigned int CO_threshold = 50; +// Adjust vRef to be the true supply voltage in mV. +float vRef = 5000.0; +float RL = 10.0; // load resistor value in k ohms +float Ro = 10.0; // default value 10 k ohms. Revised during calibration. +const float Ro_clean_air_factor = 10.0; + +float mV = 0.0; +unsigned long samples = 0; + +//////////////////////////////////////////////////////////////////////// +// DHT11 humidity/temperature sensor +#include "DHT.h" +const unsigned int pinDHT = 5; +boolean errDHT11 = false; +// Uncomment whatever type you're using! +//#define DHTTYPE DHT11 // DHT 11 +#define DHTTYPE DHT22 // DHT 22 (AM2302) +//#define DHTTYPE DHT21 // DHT 21 (AM2301) + +DHT dht(pinDHT, DHTTYPE); +//////////////////////////////////////////////////////////////////////// +// With sensor holes facing you: +// Connect pin 1 (on the left) of the sensor to +5V +// Connect pin 2 of the sensor to whatever your DHTPIN is +// Connect pin 4 (on the right) of the sensor to GROUND +// Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor + + +void setup() { + pinMode(pinGreenLED, OUTPUT); + delay(1); + pinMode(pinRedLED, OUTPUT); + delay(1); + pinMode(pinMQ, INPUT); + delay(1); + pinMode(pinBuzzer, OUTPUT); + delay(1); + pinMode(pinNPN, OUTPUT); + delay(1); + + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect + } + // Delay to give the Arduino Micro time to connect the Serial Monitor. + delay(35000); + + Serial.print("DHT"); + Serial.print(DHTTYPE); + Serial.println(" setup"); + dht.begin(); + // Get the ambient conditions (deg C & relative humidity) from DHT11 + float ambRH = dht.readHumidity(); + float ambTemp = dht.readTemperature(); + Serial.println(" "); + // check if returns are valid, if they are NaN (not a number) then something went wrong! + if (isnan(ambTemp) || isnan(ambRH)) { + Serial.println("Failed to read from DHT"); + Serial.println(" "); + errDHT11 = true; + digitalWrite(pinRedLED, HIGH); + digitalWrite(pinBuzzer, HIGH); + delay(100); + digitalWrite(pinBuzzer, LOW); + delay(5000); + digitalWrite(pinRedLED, LOW); + } else { + // DHT11 ok, .. proceed. + Serial.print("ambTemp = "); + Serial.print(ambTemp); + Serial.println(" deg C"); + Serial.print("ambRH = "); + Serial.print(ambRH); + Serial.println("% "); + } + Serial.println(" "); + + Serial.println("Calibrating MQ-7 CO sensor in clean air.."); + Serial.println(" 60 sec high heat cycle.."); + digitalWrite(pinNPN, HIGH); + // set = 200 + for(int i = 200; i > 0; i--){ + blinkLED(pinGreenLED); + } + digitalWrite(pinNPN, LOW); + Serial.println(" 60 sec warmup complete"); + + Serial.println(" 90 sec heat cycle.."); + // set = 300 + for(int i = 300; i>0; i--){ + blinkLED(pinGreenLED); + } + Serial.println(" 90 sec warmup complete. Reading MQ-7.."); + + // If mV > 3000, then repeat warm-up.. + + // take a reading.. + // set = 300 + for(int i = 300; i>0; i--){ + blinkLED(pinGreenLED); + mV += Get_mVfromADC(pinMQ); + samples += 1; + } + mV = mV / (float) samples; + Serial.print(" avg A"); + Serial.print(pinMQ); + Serial.print(" for "); + Serial.print(samples); + Serial.print(" samples = "); + Serial.print(mV); + Serial.println(" mV"); + Serial.print(" Rs = "); + Serial.println(CalcRsFromVo(mV)); + // Conv output to Ro + // Ro = calibration factor for measurement in clean air. + // Ro = ((vRef - mV) * RL) / (mV * Ro_clean_air_factor); + // Hereafter, measure the sensor output, convert to Rs, and + // then calculate Rs/Ro using: Rs = ((Vc-Vo)*RL) / Vo + Ro = CalcRsFromVo(mV) / Ro_clean_air_factor; + Serial.print(" Ro = "); + Serial.println(Ro); + // Values in clean air are: + // Rs = 6.99 + // Ro = 0.70 + Serial.println("Sensor calibration in clean air complete"); + Serial.println("Setup complete. Monitoring for CO.."); + Serial.println(" "); + digitalWrite(pinNPN, LOW); + mV = 0.0; + samples = 0; + + // Start with heater on high + heaterHigh = true; + timerAlap = millis(); // reset the timer + + blinkLED(pinGreenLED); + blinkLED(pinRedLED); + // Chirp the buzzer + digitalWrite(pinBuzzer, HIGH); + delay(1); + digitalWrite(pinBuzzer, LOW); + delay(1); + //digitalWrite(pinGreenLED, HIGH); + //delay(1); +} + +void loop() { + + // if millis() or timer wraps around, we'll just reset it + if (timerAlap > millis()) timerAlap = millis(); + if (timerBlap > millis()) timerBlap = millis(); + + if (heaterHigh == false && heat_cycles == 2 && (millis() - timerBlap > timerRead)) { + // take reading of MQ sensor.. + digitalWrite(pinGreenLED, HIGH); + mV += Get_mVfromADC(pinMQ); + samples += 1; + } else { + digitalWrite(pinGreenLED, LOW); + } + + + if (heaterHigh == true) { + // High heat applied for 60 sec + digitalWrite(pinNPN, HIGH); + // Timer A + if (millis() - timerAlap > timerA) { + timerAlap = millis(); // reset the timer + timerBlap = millis(); // reset the timer + heaterHigh = false; + } + } else { + // heaterHigh = false + // Low heat applied for 90 sec + digitalWrite(pinNPN, LOW); + // Timer B + if (millis() - timerBlap > timerB) { + timerAlap = millis(); // reset the timer + timerBlap = millis(); // reset the timer + heaterHigh = true; + heat_cycles += 1; + Serial.print("end of heat_cycle = "); + Serial.println(heat_cycles); + // Report on MQ-7 measurement at end of + // the low phase of the 3rd heat cycle. + if (heat_cycles == 3) { + mV = mV / float (samples); + Serial.print("samples = "); + Serial.println(samples); + Serial.print("A"); + Serial.print(pinMQ); + Serial.print(" = "); + Serial.print(mV); + Serial.println(" mV"); + Serial.print("Rs = "); + Serial.println(CalcRsFromVo(mV)); + Serial.println(" "); + mV = 0.0; + samples = 0; + } + } + } + + if (heat_cycles >= 3) { + heat_cycles = 0; + } + +} + +float RsRoAtAmbientTo20C65RH(float RsRo_atAmb, float ambTemp, float ambRH) { + // Using the datasheet for MQ-7 sensor, derive Rs/Ro values + // from - 10 to 50 C and 33, 65, and 85 % relative humidity. + // For the measured Rs/Ro, use linear interpolation to calculate the + // standard Rs/Ro values for the measured ambient temperature and RH. + // Next, calculate a correction factor from the standard Rs/Ro at ambient + // temp and RH relative to standard Rs/Ro at 20C and 65 RH. + // Apply this correction factor to the measured Rs/Ro value and return the + // corrected value. This corrected value may then be used against the Rs/Ro + // Rs/Ro vs CO concentration (ppm) chart to estimate the concentration of CO. + + // Calc RsRo values at ambTemp & 33% RH, 65% and 85% RH + float RsRo_at_ambTemp_33RH = -0.00000593 * pow(ambTemp, 3) + 0.000533 * pow(ambTemp, 2) - 0.0182 * ambTemp + 1.20; + float RsRo_at_ambTemp_85RH = -0.0000000741 * pow(ambTemp, 3) + 0.000114 * pow(ambTemp, 2) - 0.0114 * ambTemp + 1.03; + //float RsRo_at_65RH = ((65.0-33.0)/(85.0-65.0)); + float RsRo_at_ambTemp_65RH = ((65.0-33.0)/(85.0-33.0)*(RsRo_at_ambTemp_85RH-RsRo_at_ambTemp_33RH)+RsRo_at_ambTemp_33RH)*1.102; + // Linear interpolate to get the RsRo at the ambient RH value (ambRH). + float RsRo_at_ambTemp_ambRH; + if (ambRH < 65.0) { + RsRo_at_ambTemp_ambRH = (ambRH - 33.0)/(65.0 - 33.0)*(RsRo_at_ambTemp_65RH - RsRo_at_ambTemp_33RH) + RsRo_at_ambTemp_33RH; + } else { + // ambRH > 65.0 + RsRo_at_ambTemp_ambRH = (ambRH - 65.0)/(85.0 - 65.0)*(RsRo_at_ambTemp_85RH - RsRo_at_ambTemp_65RH) + RsRo_at_ambTemp_65RH; + } + // Calc the correction factor to bring RsRo at ambient temp & RH to 20 C and 65% RH. + const float refRsRo_at_20C65RH = 1.00; + float RsRoCorrPct = 1 + (refRsRo_at_20C65RH - RsRo_at_ambTemp_ambRH)/refRsRo_at_20C65RH; + // Calculate what the measured RsRo at ambient conditions would be corrected to the + // conditions for 20 C and 65% RH. + float measured_RsRo_at_20C65RH = RsRoCorrPct * RsRo_atAmb; + return measured_RsRo_at_20C65RH; +} + +float CalcRsFromVo(float Vo) { + // Vo = sensor output 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 + float Rs = (vRef - Vo) * (RL / Vo); + return Rs; +} + +unsigned int GetCOPpmForRatioRsRo(float RsRo_ratio) { + // If you extract the data points from the CO concentration + // versus Rs/Ro chart in the datasheet, plot the points, + // fit a polynomial curve to the points, you come up with the equation + // for the curve of: Rs/Ro = 22.073 * (CO ppm) ^ -0.66659 + // This equation is valid for ambient conditions of 20 C and 65% RH. + // Solving for the concentration of CO you get: + // CO ppm = [(Rs/Ro)/22.073]^(1/-0.66666) + float ppm; + ppm = pow((RsRo_ratio/22.073), (1/-0.66659)); + return (unsigned int) ppm; +} + +float Get_mVfromADC(byte AnalogPin) { + // read the value from the sensor: + int ADCval = analogRead(AnalogPin); + // It takes about 100 microseconds (0.0001 s) to read an analog input + delay(1); + // Voltage at pin in milliVolts = (reading from ADC) * (5000/1024) + float mV = ADCval * (vRef / 1024.0); + return mV; +} + +void blinkLED(byte ledPIN){ + // consumes 300 ms. + for(int i = 5; i>0; i--){ + digitalWrite(ledPIN, HIGH); + delay(30); + digitalWrite(ledPIN, LOW); + delay(30); + } +} + +