Gas Sensor Sketch

Definitions and Declarations

/*

   GasSensor.ino is used to display and transmi

   conentration of CO,CH4 and LPG.

   It generates visual and audible alarms if 

   excessive concentrations are detected.

   It includes the following items:

   Spark Fum Arduino Pro Mini microcontroller.

   LCD display with parallel interface (4-bit)

   DS3231 Real-time clock with I2C interface.

   MQ-9 gas sensor with switched heater voltages.

   TMP036 temperature sensor.

   LM385-2.5 reference voltage diode.

   Backlight control from CDS photocell 

   with quasi-log compressor

    which includes LM385-2.5 & LM385-1.2 diodes.

   LM317 voltage regulator using a resistor pack.

   PCF8574 8-port I2C expander connected to a r-g-b LED 

   and Adafruit FX OGG Audio Codec

   LM 2040 Audio power amplifier.

   FS1000A 433MHZ RF transmitter module, 

   requires adapter circuit fot Arduino.

   Push buttons with analog encoder using a resistor pack.

   RTC Hour and Minute adjustments

   John Saunders 12/4/2021, 6/4/2022 leading zero restored

*/


#include <LiquidCrystal.h>

#include <SoftwareSerial.h>     //For the transmitter

#include <ds3231.h>             //RTC

#include <Wire.h>               //For the RTC and the port expander

#include "jm_PCF8574.h"         //Port expander

#define PCF_ADDR 0x27


jm_PCF8574 pcf(PCF_ADDR);


#define INIT_TIMEIN 10      //Into Date/Time setting page

#define INIT_TIMEOUT 10     //Exit from Date/Time setting page


// pins

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

const int pc = A6, gas = A3, temp = A2, ref = A1, but = A0;

const int bl = 10, htr = 13, txDr = 6, txPulse = 7, Mute = 8;


// measured values

const float refVoltsC = 2472;   // for degree C , mv, measured ref voltage

const float refVoltsF = 4445.6; // for degree F, mv


const float Rl = 8660.0;      // Load resistor measured value

const float Ro = 1777.0;      // At end of 2 days cycling the 

// gas sensor heater using 1.5V value to get 10.

// The measured value has been divided by 10 according to online instructions


// *************** Adjust RTC minutes and Hours *************************


ts t; //ts is a struct findable in ds3231.h

Setup and Loop

void setup() {

  pinMode(htr, OUTPUT);

  digitalWrite(htr, HIGH);   //5V

  pinMode(txDr, OUTPUT);

  digitalWrite(txDr, HIGH);

  pinMode(txPulse, OUTPUT);

  pinMode(Mute,INPUT_PULLUP);

  digitalWrite(txPulse, LOW);

  lcd.begin(20, 2);

  Wire.begin(); //start i2c (required for connection to the clock chip)

  DS3231_init(DS3231_INTCN);

  serial1.begin(2400);

  delay(400);

  lcd.createChar(0, degSym);

  pcf.begin();                // returns false if not connected

  for (int i = 0; i < 8; i ++) {

    pcf.pinMode(i, OUTPUT);

  }

  for (int i = 0; i < 3; i ++) {

    pcf.digitalWrite(i, 1);

  }

  pcf.digitalWrite(3, 0);

  for (int i = 4; i < 8; i ++) {

    pcf.digitalWrite(i, 1);

  }

  pcf.digitalWrite(2, 0);

  DS3231_get(&t);

  //  t.wday = 0;

  //  DS3231_set(t);

}


void loop() {

  int tempValF, gasVal, pcVal, intensity;

  // int tempValC;            // Only the F value is transmitted

  static int secs = 0;

  static int oldSec = 1;

  static int timeIn = INIT_TIMEIN;

  static int timeOut = INIT_TIMEOUT;

  static int page = 0;

  static boolean tempC = false;

  float tempDegF, tempDegC;

  float hiGasRatio ;

  float scratch;

  static float loGasRatio = 10.0;

  byte butCode;

  byte nextLoc;

  char alarmLevel;

  

  butCode = getButID();         // Buttons

  

  DS3231_get(&t);               // Current time & date

  

  if (oldSec != t.sec) {        // Items run at 1 Hz

    oldSec = t.sec;

    if (timeIn > 0) {           // Adjustment page in and out delay

      timeIn--;

    }

    if (timeOut > 0) {

      timeOut--;

    }

    

    tempDegF = getTemp('F');    // Temperature measurement

    tempValF = (int)tempDegF;

    tempDegC = getTemp('C');

    // tempValC = (int)tempDegC;    Only the F value is transmitted

    

    pcVal = analogRead(pc);     // Light measurement

    intensity = 375 - (5 * pcVal / 8);

    if (intensity < 0) {        // PWM generation for thedisplay backlight

      analogWrite(bl, 0);

    }

    else {

      if (intensity > 250) {    // Upper limit not reached

        analogWrite(bl, 250);

      }

      else {

        analogWrite(bl, intensity);

      }

    }

 // Operation is cyclic at 150 seconds period

    

    alarmLevel = (char)(execAlarm(loGasRatio) + 'A');

    switch (secs) {

      case 1:

        digitalWrite(htr, LOW);   // 5V

        break;

      case 59:

        hiGasRatio = getRsDivRo();

      case 60:

        digitalWrite(htr, HIGH); // 1.5V

        break;

      case 75:

        nextLoc = storeVal(tempValF, 11, 1);

        pcVal = analogRead(pc);

        nextLoc = storeVal(pcVal, nextLoc, 0);

        transmit('z', nextLoc);

        break;

      case 149:

        loGasRatio = getRsDivRo();

        break;

      case 150:

        secs = 0;

        scratch = 1000 * hiGasRatio;

        gasVal = (unsigned int)(scratch);

        nextLoc = storeVal(gasVal, 13, 3);

        scratch = 1000 * loGasRatio;

        gasVal = (unsigned int)(scratch);

        nextLoc = storeVal(gasVal, nextLoc, 3);

        charBuffer[11] = alarmLevel;

        charBuffer[12] = ',';

        transmit('n', nextLoc);

        break;

    }

    secs++;

  }


  // Page view control

  

  switch (page) {

    case 0:                       // Time&Date, Gas Sensor Rs/Ro

      lcd.clear();

      if (butCode == 3) {

        page = 1;

      }

      if (butCode == 1) {

        page = 2;

        timeIn = INIT_TIMEIN;

      }

      lcd.setCursor(0, 0);

      displayTimeDate();

      lcd.setCursor(0, 1);

      lcd.print("Low=");

      lcd.print(loGasRatio, 3);

      lcd.print(" High=");

      lcd.print(hiGasRatio, 3);

      break;

    case 1:                         //Temperature and Light-Sec

      if (butCode == 1) {

        page = 2;

        timeIn = INIT_TIMEIN;

      }

      if (butCode == 2)  {

        tempC = true;

      }

      if (butCode == 3)  {

        tempC = false;

      }

      if (butCode == 4) {

        page = 0;

      }

      lcd.clear();

      lcd.setCursor(0, 0);

      lcd.print(" temp=");

      if (tempC == false) {

        lcd.print((tempDegF / 10), 1);

        lcd.write(byte(0));

        lcd.print('F');

      }

      else {

        lcd.print((tempDegC / 10), 1);

        lcd.write(byte(0));

        lcd.print('C');

      }

      lcd.setCursor(0, 1);

      lcd.print("Light=");

      lcd.print(pcVal, DEC);

      lcd.print(" secs=");

      lcd.print(secs, DEC);

      break;

    case 2:

      if (butCode == 3) {

        page = 1;

      }

      if (butCode == 4) {

        page = 0;

      }

      if ((butCode == 0) && (timeIn == 0)) {

        page = 3;

        timeOut = INIT_TIMEOUT;

      }

      lcd.clear();

      lcd.setCursor(0, 0);

      lcd.print("Settings Countdown");

      lcd.setCursor(0, 1);

      lcd.print("  Abort    ");

      lcd.print(timeIn, DEC);

      break;

    case 3:               // Minute and hour adjustments

      if ((butCode == 0) && (timeOut == 0)) {

        page = 0;

      }

      lcd.clear();

      lcd.setCursor(0, 0);

      lcd.print("Hour=");

      lcd.print(t.hour, DEC);

      lcd.print("    Min=");

      lcd.print(t.min, DEC);

      lcd.setCursor(0, 1);

      lcd.print("Up-Down    Up-Down ");

      lcd.print(timeOut);

      if (butCode > 0) {

        adjustTD(butCode);

        timeOut = INIT_TIMEOUT;

      }

      break;

  }

  delay(100);

}

Functions

void adjustTD(int code) {

  switch (code) {

    case 4:

      if (t.hour < 23) {

        t.hour++;

      }

      else {

        t.hour = 0;

      }

      break;

    case 3:

      if (t.hour > 0) {

        t.hour--;

      }

      else {

        t.hour = 23;

      }

      break;

    case 2:

      if (t.min < 59) {

        t.min++;

      }

      else {

        t.min = 0;

      }

      break;

    case 1:

      if (t.min > 0) {

        t.min--;

      }

      else {

        t.min = 59;

      }

      break;

  }

  DS3231_set(t);

  while (getButID() > 0) {

    delay(1);

  }

}


// ********************** Displaying *****************************


const char weekdays[25] = {"SUNMONTUEWEDTHUFRISATSUN"};    // 0(SUN) through 6(SAT,SUN is also 7)

void displayTimeDate(void) {

  lcd.print(t.hour);

  lcd.print(":");

  lcd.print(t.min);

  lcd.print(":");

  lcd.print(t.sec);

  lcd.print(" ");

  displayWeekday();

  lcd.print(" ");

  displayMonth();

  lcd.print(" ");

  lcd.print(t.mday);

}

void displayWeekday(void) {

  char c;

  byte indx;

  for (byte i = 0; i < 3; i++)  {

    indx = (3 * (t.wday)) + i;

    c = weekdays[indx];

    lcd.print(c);

  }

}


const char months[40] = {"DECJANFEBMARAPRMAYJUNJLYAUGSEPOCTNOVDEC"}; // 1 - 12


void displayMonth(void) {

  char c;

  byte indx;

  for (byte i = 0; i < 3; i++)  {

    indx = (3 * t.mon) + i;

    c = months[indx];

    lcd.print(c);

  }

}


byte degSym[8] = {

  B11100,

  B10100,

  B11100,

  B00000,

  B00000,

  B00000,

  B00000,

};


// *******************  Hardware and Sensors *****************************


byte getButID(void) {

  int butVal;

  int testVal = 127;

  byte code = 0;

  butVal = analogRead(but);

  while (butVal > testVal) {

    code++;

    testVal += 255;

  }

  return code;

}


int getTemp(char mode) {

  float deg;

  int refCt = analogRead(ref);

  int tempCt = analogRead(temp);

  if (mode == 'C') {

    deg = ((refVoltsC * tempCt) / refCt) - 500;

  }

  else {

    deg = ((refVoltsF * tempCt) / refCt) - 480;

  }

  return (int)deg;

}


float getRsDivRo(void) {    // returns rs/ro

  int gasVal;

  float gasRatio;

  gasVal = analogRead(gas);

  gasRatio = (Rl * ((1024.0 / (float)gasVal) - 1.0)) / Ro;

  return gasRatio;

}


/********************** Alarms ******************

 *  

            Hardware Mapping

  PCF8571 Code               0     1     2     3     4      5     6     7

  PCF8571 Active level       0     0     0     1     0      0     0     0

  PCF8571 Pin                4     5     6    16     11    12     13   14

  LED Color               greeen  blue  red

  FX OGG File nn                              T02    01    04     03   00.ogg

*/

typedef struct {

  float testLevel;      // Rs/Ro value to compare the measurement against

  byte  colorLED;       // 0-2 if on

  byte  flash;          // LED flashes if 1

  byte  portID;         // port 3-7 if on

  byte  period;         // continues at this rate

} levels_t;


// This is the heart of this project. 

// The FX board is mountable in OSX, files need renaming to T(##),ogg

const levels_t levelList[7] =   {

//     > 2.3               1.8-2.5         1.5-1.8                

  {2.5, 3, 1, 0, 0}, {2.5, 0, 1, 0, 0}, {1.8, 0, 0, 3, 4}, 

//     1.1-1.5            0.8-1.1          0.6-0.8              0.4-0.6

  { 1.5, 1, 1, 7, 3}, {1.1, 1, 0, 6, 9}, {0.8, 2, 1, 4, 4}, {0.6, 2, 0, 5, 9}

}; 


const byte colorAction[12] = {0, 1, 1, 1,  1, 0, 1, 1,  1, 1, 0, 1};


byte execAlarm(float level) {

  int index = 0;

  byte portC, portF;

  static int ct = 0;

  byte blinkLED;

  while (level < levelList[index].testLevel) {

    index++;

  }

  blinkLED = levelList[index].flash & (ct & 1);

  // Operates the rgb LED colorLED

  portC = levelList[index].colorLED;

  if (portC > 2) {

    portC = 3;

  }

  pcf.digitalWrite(0, colorAction[portC] | blinkLED);

  pcf.digitalWrite(1, colorAction[portC + 4] | blinkLED);

  pcf.digitalWrite(2, colorAction[portC + 8] | blinkLED);

  // Operates the sound codec

  if (ct < levelList[index].period) {

    ct++;

  }

  else {  // Makes sounds

    ct = 0;

    portF = levelList[index].portID;

    if ((portF > 2) && (portF < 8) && (digitalRead(Mute) != 0)) {

      pcf.digitalWrite(portF, (portF == 3));   // port 3 = pin 16; is inverted

      delay(125);

      pcf.digitalWrite(portF, (portF != 3));

    }

  }

  return index;

}

char charBuffer[47] = {"14L1776n,F,?, "};


// ************************** Storing ****************


byte storeVal(unsigned int txData, int loc, int dp) {

  int rem = txData / 10000;

  // if (rem > 0) {

    charBuffer[loc++] = rem + '0';

  // }

  rem = txData % 10000;

  charBuffer[loc++] = rem / 1000 + '0';

  if (dp == 3) {

    charBuffer[loc++] = '.';

  }

  txData = rem % 1000;

  charBuffer[loc++] = txData / 100 + '0';

  if (dp == 2) {

    charBuffer[loc++] = '.';

  }

  rem = txData % 100;

  charBuffer[loc++] = rem / 10 + '0';

  if (dp == 1) {

    charBuffer[loc++] = '.';

  }

  charBuffer[loc++] = rem % 10 + '0';

  charBuffer[loc++] = ',';

  return loc;

}


// ********************* Transmitting ****************


SoftwareSerial serial1 = SoftwareSerial(8, txDr);    // The 8 is jjust a dummy




void transmit(char key, byte len) {   // Points to the first checksum character

  byte cksum = 0;

  byte ckhex;

  charBuffer[7] = key;

  charBuffer[9] = '<' + len - 12;

  for (int i = 11; i < (len - 1); i++) {

    cksum += charBuffer[i];

  }

  ckhex = cksum / 16;

  if (ckhex < 10) {

    charBuffer[len++] = ckhex + '0';

  }

  else {

    charBuffer[len++] = ckhex + '7';

  }

  ckhex = cksum & 0x0F;

  if (ckhex < 10) {

    charBuffer[len++] = ckhex + '0';

  }

  else {

    charBuffer[len++] = ckhex + '7';

  }

  charBuffer[len++] = 0x0d;

  charBuffer[len++] = 0x0a;

  charBuffer[len++] = 0;

  digitalWrite(txPulse, HIGH);

  delay(20);

  digitalWrite(txPulse, LOW);

  delay(10);

  serial1.write(charBuffer, len);

}