Friday, November 14, 2014

How to Combine a Vibration Sensor and Reed Switch to Detect Door Open or Tailgate Down

In this project I show how to make a door open or tailgate down sensor using an Arduino vibration sensor and Reed switch. This builds on my previous blogs of how to build a high sensitivity vibration sensor, and how to avoid false positives with a high sensitivity piezo vibration sensor. In this latest project I use a Reed switch to detect if something is open, for example a door or a tailgate. The sketch running on the Arduino Uno only records vibration events if the Reed switch is open. In order to trigger the vibration alarm it must receive at least 5 vibration events within a 5 second period (configurable in sketch below). Each vibration event is represented as a square pulse out of the comparator in the vibration sensor circuit. When the vibration alarm is triggered it fires the large LED . This could be placed in the view of a rear view mirror for example, to alert the driver that the car or truck is in motion and something is left open, for example a tailgate may be down.

Below is the sketch I used, running on the Arduino Uno:

#define VIBRATION_SENSOR_DIGITAL_INPUT_PIN 10
#define VIBRATION_SENSOR_DIGITAL_OUTPUT_PIN 8
#define REED_SWITCH_DIGITAL_INPUT_PIN 6

#define VIBRATION_EVENTS_ALARM_THRESHOLD_COUNT 5
#define VIBRATION_EVENTS_ALARM_THRESHOLD_PERIOD_MILLIS 5000

#define VIBRATION_ALARM_FLASHES 5
#define VIBRATION_ALARM_PERIOD_MILLIS 400

long vibrationEvents[VIBRATION_EVENTS_ALARM_THRESHOLD_COUNT];
int vibrationEventIndex;

//------------------------------------------------------------------
void setup(){
  Serial.begin(19200);
  
  pinMode(VIBRATION_SENSOR_DIGITAL_INPUT_PIN, INPUT);
  pinMode(REED_SWITCH_DIGITAL_INPUT_PIN, INPUT);
  pinMode(VIBRATION_SENSOR_DIGITAL_OUTPUT_PIN, OUTPUT);
  
  vibrationEventIndex = 0;
  clearVibrationEvents();
}

//------------------------------------------------------------------
void loop(){
  // only pay attention to vibration when the Reed switch is open
  if(digitalRead(REED_SWITCH_DIGITAL_INPUT_PIN) == HIGH && 
     digitalRead(VIBRATION_SENSOR_DIGITAL_INPUT_PIN) == HIGH){
       
    addVibrationSample();
    delay(100); // debounce current vibration shock
  }
}

//------------------------------------------------------------------
void addVibrationSample(){
   vibrationEvents[vibrationEventIndex++] = millis();
   
   // wrap index around end of sample array
   vibrationEventIndex %= VIBRATION_EVENTS_ALARM_THRESHOLD_COUNT; 
   
  if(isVibrationAlarmTiggered()){
    Serial.println(String(millis()) + "\t ALARM");
    triggerAlarm();
  }
}

//------------------------------------------------------------------
boolean triggerAlarm(){
  for(int i = 0; i < VIBRATION_ALARM_FLASHES; ++i){
    digitalWrite(VIBRATION_SENSOR_DIGITAL_OUTPUT_PIN, HIGH);
    delay(VIBRATION_ALARM_PERIOD_MILLIS/2);
    digitalWrite(VIBRATION_SENSOR_DIGITAL_OUTPUT_PIN, LOW);
    delay(VIBRATION_ALARM_PERIOD_MILLIS/2);
  }
  clearVibrationEvents();
}

//------------------------------------------------------------------
void clearVibrationEvents(){
  for(int i = 0; i < VIBRATION_EVENTS_ALARM_THRESHOLD_COUNT ; ++i){
    vibrationEvents[i] = -1;
  }
}

//------------------------------------------------------------------
boolean isVibrationAlarmTiggered(){
  long thresholdMillis 
    = millis() - VIBRATION_EVENTS_ALARM_THRESHOLD_PERIOD_MILLIS;
    
  if(thresholdMillis < 0) thresholdMillis = 0;
  int numVibrationsSinceThreshold = 0;
  for(int i = 0; i < VIBRATION_EVENTS_ALARM_THRESHOLD_COUNT ; ++i){
    if(vibrationEvents[i] >= thresholdMillis){
      ++numVibrationsSinceThreshold;
    }
  }
  
  Serial.println(
    String(millis()) + 
    "\t# events: " + 
    String(numVibrationsSinceThreshold));
    
  boolean alarmTriggered = false;
  if(numVibrationsSinceThreshold >= VIBRATION_EVENTS_ALARM_THRESHOLD_COUNT){
    alarmTriggered = true;
  }
  
  return alarmTriggered;
}

//------------------------------------------------------------------

If you are interested in how this type of sensor can be integrated into a broader solution that includes notification of a door open on a users Android phone see Detect if a Door or Gate is Opened

Friday, November 7, 2014

How to Avoid False Positives with a High Sensitivity Piezo Vibration Sensor

Thanks for all the interest and comments in my previous blog on the High Sensitivity Vibration Sensor. In this previous project I described how to use a piezo element, an op amp and comparator to create a high sensitivity vibration sensor. Based on the great level of interest in this project I have created an advanced vibration sensor that uses the same design and builds upon it. One of the challenges with vibration sensors is screening out spurious events such as bumps that can lead to false positives. In this project the sketch running on the Arduino Uno watches for vibration events but only triggers if it sees 5 vibration events within a 5 second period. So if it gets 3 events and then none for a while it will forget those 3 events. Only if it gets 5 or more within 5 seconds will the alarm trigger. This effectively screens out spurious bumps and fires on persistent vibration events. This can be useful for a variety of applications where was is sought is the ability to detect vibration rather than a single bump. The values for the number of events (5 in my sketch) and period of time (5 seconds) are configurable by changing the values at the top of the sketch for your project.

You can find the sketch I used on the Arduino Uno below:

#define VIBRATION_SENSOR_DIGITAL_INPUT_PIN 10
#define VIBRATION_SENSOR_DIGITAL_OUTPUT_PIN 8

#define VIBRATION_SAMPLE_ARRAY_SIZE 100

// if we 5 or more vibration events over a five second period then a vibration alarm is triggered
#define VIBRATION_EVENTS_ALARM_THRESHOLD_COUNT 5
#define VIBRATION_EVENTS_ALARM_THRESHOLD_PERIOD_MILLIS 5000

long vibrationEvents[VIBRATION_SAMPLE_ARRAY_SIZE];
int vibrationEventIndex;

void setup(){
  Serial.begin(19200);
  
  pinMode(VIBRATION_SENSOR_DIGITAL_INPUT_PIN, INPUT);
  pinMode(VIBRATION_SENSOR_DIGITAL_OUTPUT_PIN, OUTPUT);
  
  vibrationEventIndex = 0;
  clearVibrationEvents();
}

void loop(){
  if(digitalRead(VIBRATION_SENSOR_DIGITAL_INPUT_PIN) == HIGH){
    long currentMillis = millis();
    addVibrationSample(currentMillis);
    if(isVibrationAlarmTiggered()){
      triggerAlarm();
      Serial.println(String(millis()) + "\t ALARM");
    }
    delay(100); // wait for current vibration shock to subside
  }
}

void addVibrationSample(long vibrationMillis){
   vibrationEvents[vibrationEventIndex++] = vibrationMillis;
   if(vibrationEventIndex >= VIBRATION_SAMPLE_ARRAY_SIZE){
     vibrationEventIndex = 0; // wrap vibration sample index around when we get to end of sample array
   }
}

boolean triggerAlarm(){
  digitalWrite(VIBRATION_SENSOR_DIGITAL_OUTPUT_PIN, HIGH);
  delay(1000);
  digitalWrite(VIBRATION_SENSOR_DIGITAL_OUTPUT_PIN, LOW);
  clearVibrationEvents();
}

void clearVibrationEvents(){
  for(int i = 0; i < VIBRATION_SAMPLE_ARRAY_SIZE ; ++i){
    vibrationEvents[i] = -1;
  }
}

boolean isVibrationAlarmTiggered(){
  long thresholdMillis = millis() - VIBRATION_EVENTS_ALARM_THRESHOLD_PERIOD_MILLIS;
  if(thresholdMillis < 0) thresholdMillis = 0;
  int numVibrationsSinceThreshold = 0;
  for(int i = 0; i < VIBRATION_SAMPLE_ARRAY_SIZE ; ++i){
    if(vibrationEvents[i] >= thresholdMillis){
      ++numVibrationsSinceThreshold;
    }
  }
  
  Serial.println(String(millis()) + "\t# events: " + String(numVibrationsSinceThreshold));
  boolean alarmTriggered = false;
  if(numVibrationsSinceThreshold >= VIBRATION_EVENTS_ALARM_THRESHOLD_COUNT){
    alarmTriggered = true;
  }
  
  return alarmTriggered;
}



Below is an example of the output on the Arduino IDE console when the Arduino Uno is running off a USB cable:

9018 # events: 1
9703 # events: 2
10330 # events: 3
10935 # events: 4
11697 # events: 5
12698  ALARM
14902 # events: 1
15398 # events: 2
15925 # events: 3
27161 # events: 1

If you found this interesting you may also want to have a look at how to build a High Sensitivity Sound Level Detector. You may also want to look at my subsequent blog on how to use these in an open door or tailgate down sensor which also uses a Reed switch.

If you are interested in how this type of sensor can be integrated into a broader solution that includes notification of detected vibration on a users Android phone see Detect Intrusion with Passive Infrared, Sound, or Vibration

Saturday, June 21, 2014

GPS Location Sensing with the Arduino Mega

This project shows how to get location in latitude and longitude coordinates using an electronic circuit built with the Arduino Mega 2560 and the ITEAD GPS Shield v1.1 . If you want to see how to do GPS location sensing with the Arduino Uno and an LCD display check out my other blog.

The GPS shield has a GPS antenna connected. This is very important as you can't get a location fix if you don't have an antenna.

Using this circuit I got a location fix indoors on the ground floor of a 2 story house in 1 to 2 minutes. Outdoors it got a fix in about 30s or less. Accuracy was very good, to within 10-20ft initially, and improving the longer you leave it (it gets a new reading every second.

The wiring of this diagram is straightforward. First you should set the voltage switch on your GPS shield to 5v before you power it on and connect it up. There is also a 3.3v setting, but for our circuit we are using the Arduino Mega 2560 so the voltage setting I used is 5v. Next you need to set the jumpers for Rx (receive) and Tx (transmit) on the GPS shield. I set my GPS Rx to pin 6 and GPS Tx to pin 5. I wanted to receive the output of this circuit on my computer so I needed the default hardware serial interface for that. To communicate with the GPS shield I used the mega hardware serial interface 1. So I wired GPS Rx pin 6 to Mega Serial1 Tx1 pin 18, and GPS Tx pin 5 to Mega Serial 1 Rx1 pin 19. Using this circuit I was able to get a location fix even inside a house on the ground floor of a 2 story house. If used outside with a clear view of the sky it will get a GPS fix even faster.

You will need TinyGPS to run the sketch I used. Specifically you will need the TinyGPS.cpp and TinyGPS.h files to run this. I just dropped them in the same folder as the Arduino sketch and then when I loaded the sketch the Arduino IDE automatically found these two files and compiled and uploaded them with my sketch to the Mega for testing.

Below is a screenshot of the Arduino IDE serial console showing output from this sketch running. The GPS latitude / longitude coordinates each have a precision of 6 decimal places.

Below is the Arduino sketch I used for testing:

#include "TinyGPS.h"

TinyGPS gps;

#define GPS_TX_DIGITAL_OUT_PIN 5
#define GPS_RX_DIGITAL_OUT_PIN 6

long startMillis;
long secondsToFirstLocation = 0;

#define DEBUG

float latitude = 0.0;
float longitude = 0.0;

void setup()
{
  #ifdef DEBUG
  Serial.begin(19200);
  #endif
  
  // Serial1 is GPS
  Serial1.begin(9600);
  
  // prevent controller pins 5 and 6 from interfering with the comms from GPS
  pinMode(GPS_TX_DIGITAL_OUT_PIN, INPUT);
  pinMode(GPS_RX_DIGITAL_OUT_PIN, INPUT);
  
  startMillis = millis();
  Serial.println("Starting");
}

void loop()
{
  readLocation();
}

//--------------------------------------------------------------------------------------------
void readLocation(){
  bool newData = false;
  unsigned long chars = 0;
  unsigned short sentences, failed;

  // For one second we parse GPS data and report some key values
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (Serial1.available())
    {
      int c = Serial1.read();
//      Serial.print((char)c); // if you uncomment this you will see the raw data from the GPS
      ++chars;
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }
  
  if (newData)
  {
    // we have a location fix so output the lat / long and time to acquire
    if(secondsToFirstLocation == 0){
      secondsToFirstLocation = (millis() - startMillis) / 1000;
      Serial.print("Acquired in:");
      Serial.print(secondsToFirstLocation);
      Serial.println("s");
    }
    
    unsigned long age;
    gps.f_get_position(&latitude, &longitude, &age);
    
    latitude == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : latitude;
    longitude == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : longitude;
    
    Serial.print("Location: ");
    Serial.print(latitude, 6);
    Serial.print(" , ");
    Serial.print(longitude, 6);
    Serial.println("");
  }
  
  if (chars == 0){
    // if you haven't got any chars then likely a wiring issue
    Serial.println("Check wiring");
  }
  else if(secondsToFirstLocation == 0){
    // still working
  }
}


If you are interested in how location sensing can be integrated into a broader solution that includes notification of location and geo-fence alerts on a users Android phone see Geo-Fencing

Hope you found this useful. Let me know if you create any cool enhancements to it. Have fun!

If you need any of the parts for this projects you can find them below:

Saturday, June 7, 2014

GPS Location Sensing with the ITEAD GPS Shield and Arduino Uno

Location is a valuable input for many applications. In this project I use the ITEAD GPS Shield v1.1 with a GPS antenna , an Arduino Uno , and an LCD Display to sense location in terms of latitude and longitude coordinates.

With this setup I was able to acquire lat / long coordinates outdoors with a clear view of the sky in 23s to 32s with an average of 28s. Indoors on the bottom floor of a 2 story house I was able to get a location in 61s to 140s with an average of 101s from time of power up to time of first GPS lat / long coordinate acquisition. With this circuit the longer it is given the more accurate the reading. Typically I found the first reading within a hundred or so feet and given more time it would get down to a few feet accuracy.

Below is the picture of the complete circuit.

The wiring of the circuit is fairly straightforward. There are two parts. The first is how to connect the GPS shield. It connects easily on top of the Arduino Uno so thats no problem. You will also need to set the jumpers for the GPS Rx (receive) pin 7 and GPS Tx (transmit) pin 6 parts of the serial interface.

Note that later on in the sketch for the Arduino code you will see the Uno Rx pin is 6 and Uno Tx pin is 7. This is because the Uno (Tx) transmits to the GPS (Rx) on pin 7 and conversely the Uno (Rx) receives from the GPS (Tx) on pin 6.

The second part of the wiring is the LCD display. Your wiring may vary depending on the display you have, so check its instructions. For my wiring I used the hookup below:

 LCD Pin Connect to
 1 (VSS) GND Arduino pin*
 2 (VDD) + 5v Arduino pin
 3 (contrast)  2.2k resistor to GND
 4 RS Arduino pin 12
 5 R/W Arduino pin 11
 6 Enable Arduino pin 10
 7 No connection 
 8 No connection 
 9 No connection 
 10 No connection 
 11 (Data 4) Arduino pin 5
 12 (Data 5) Arduino pin 4
 13 (Data 6) Arduino pin 3
 14 (Data 7) Arduino pin 2
 15 Backlight +
  1.5k resistor to Arduino pin 13
 16 Backlight GND GND Arduino pin*

I found this blog helpful in wiring my display.

Make sure you have a GPS antenna. I was not able to get a location fix without one.

I used a small 9v battery to power the circuit. This was fine for testing the acquisition time in a variety of locations. If you intend to run this circuit for longer periods of time you may need a more powerful power supply.

You will need TinyGPS to run the sketch I used. Specifically you will need the TinyGPS.cpp and TinyGPS.h files to run this. I just dropped them in the same folder as the GPS_LCD_Display.ino Arduino sketch and then when I loaded the sketch the Arduino IDE automatically found these two files and compiled and uploaded them with my sketch to the Uno for testing.

Below is the Arduino sketch I used for testing:

#include <SoftwareSerial.h>
#include <LiquidCrystal.h>

#include "TinyGPS.h"

TinyGPS gps;

int unoRxPin = 6; // connected to Tx pin of the GPS
int unoTxPin = 7; // connected to Rx pin of the GPS
SoftwareSerial ss(unoRxPin, unoTxPin);

LiquidCrystal lcd(12, 11, 10, 5, 4, 3, 2);
int backLight = 13;    // pin 13 will control the backlight

long startMillis;
long secondsToFirstLocation = 0;

void setup()
{
  ss.begin(9600);
  
  pinMode(backLight, OUTPUT);
  digitalWrite(backLight, HIGH); // turn backlight on. Replace 'HIGH' with 'LOW' to turn it off.
  lcd.begin(20,4); // columns, rows.  use 16,2 for a 16x2 LCD, etc.
  lcd.clear();  // start with a blank screen
  
  startMillis = millis();
}

void loop()
{
  bool newData = false;
  unsigned long chars = 0;
  unsigned short sentences, failed;

  // For one second we parse GPS data and report some key values
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (ss.available())
    {
      int c = ss.read();
      ++chars;
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }

  if (newData)
  {
    // we have a location fix so output the lat / long and time to acquire
    if(secondsToFirstLocation == 0){
      secondsToFirstLocation = (millis() - startMillis) / 1000;
    }
    
    lcd.clear();  // start with a blank screen
    
    float flat, flon;
    unsigned long age;
    gps.f_get_position(&flat, &flon, &age);
    lcd.setCursor(0,0);           // set cursor to column 0, row 0 (the first row)
    lcd.print("Lat=");
    lcd.print(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flat, 6);

    lcd.setCursor(0,1);
    lcd.print("Long=");
    lcd.print(flon == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flon, 6);

    lcd.setCursor(0,2);
    lcd.print("Acquire Time=");
    lcd.print(secondsToFirstLocation);
    lcd.print("s");
  }
  
  if (chars == 0){
    // if you haven't got any chars then likely a wiring issue
    lcd.setCursor(0,0);           // set cursor to column 0, row 0 (the first row)
    lcd.print("No GPS: check wiring");
  }
  else if(secondsToFirstLocation == 0){
    // if you have received some chars but not yet got a fix then indicate still searching and elapsed time
    lcd.clear();  // start with a blank screen

    long seconds = (millis() - startMillis) / 1000;
    
    lcd.setCursor(0,0);           // set cursor to column 0, row 0 (the first row)
    lcd.print("Searching ");
    for(int i = 0; i < seconds % 4; ++i){
      lcd.print(".");
    }
    
    lcd.setCursor(0,1);
    lcd.print("Elapsed time:");
    lcd.print(seconds);
    lcd.print("s");
  }
}

If you are interested in how location sensing can be integrated into a broader solution that includes notification of location and geo-fence alerts on a users Android phone see Geo-Fencing

Hope you found this useful. Let me know if you create any cool enhancements to it. Have fun!

Friday, May 2, 2014

Propane Gas Sensor Using the Arduino Uno

In this latest project I use the MQ6 propane gas sensor with the Arduino Uno to sense propane gas concentration in the air.

This sensor has a heater that requires about 150mA for 90s to take a reading. The Uno can't supply this current so I use a KA278R05 voltage regulator to provide it, with a 12v DC power supply providing the input power for the voltage regulator. The KA278R05 has an enable pin which I drive high using digital pin 8 of the Uno to enable the sensor for the 90 seconds it needs to be on in order to take a stable reading. I read the voltage level signal output of the MQ6 each second and the final stable value right at the 90s point after activating the sensor using analog input pin 0.

The schematic for the circuit I used:

The breadboard layout using the great tool from Fritzing:

The first set of results from clean air (no propane) graphed in excel. Here the measurement stabilized at around 256 after 90s:

The second set of results from air with a bit of propane graphed in excel. I put the sensor inside a grill with the lid closed and gave a small puff of propane, and without any flame. I recommend extreme caution if you try this. Just a tiny puff of propane, and in an outside area with good ventilation is how I did it. Have all the circuitry except the sensor outside the grill area. With this test the measurement stabilized at around 739 after 90s, which is almost 3 times higher than with clean air. Keeping in mind this was a very small puff of propane, this sensor is very sensitive:

Below is the sketch I used to drive the Uno for this test. I use a simple finite state machine to manage the state of the sensor.

#define MQ6_POWER_ON_DIGITAL_OUT_PIN 8
#define MQ6_LP_GAS_LEVEL_MEASURE_ANALOG_IN_PIN   0
#define MQ6_HEATER_TIME_MILLIS 90000
#define MQ6_SAMPLE_PERIOD_MILLIS 1000

int lpGas;

typedef enum {
  ST_MQ6_OFF,
  ST_MQ6_CYCLE_0_HIGH,
  ST_MQ6_DONE
} MQ6_STATE;

MQ6_STATE mq6State = ST_MQ6_OFF;

unsigned long mq6SwitchTimeMillis;
unsigned long mq6NextReadingTimeMillis;
unsigned long lpGasStartTimeMillis;
unsigned long startMillis;

//-----------------------------------------------------
void setup(){
  Serial.begin(19200);
  
  pinMode(MQ6_POWER_ON_DIGITAL_OUT_PIN, OUTPUT);
  
  startMillis = millis();
  
  // start 10s after power up
  lpGasStartTimeMillis = 10000; 

  // print headers for CSV output
  Serial.print("Seconds");
  Serial.print(",");
  Serial.println("LP Gas Level");
}

//-----------------------------------------------------
void loop(){
  readLPGas();
}

//-----------------------------------------------------
// uses a simple finite state machine to manage states of the MQ6 sensor
void readLPGas(){
  switch(mq6State){
    case ST_MQ6_OFF :
    {
      if(millis() > lpGasStartTimeMillis){
        digitalWrite(MQ6_POWER_ON_DIGITAL_OUT_PIN, HIGH);

        mq6State = ST_MQ6_CYCLE_0_HIGH;
        mq6SwitchTimeMillis = millis() + MQ6_HEATER_TIME_MILLIS;
      }
      break;
    }

    case ST_MQ6_CYCLE_0_HIGH :
    {
      if(millis() > mq6NextReadingTimeMillis) {
        lpGas = analogRead(MQ6_LP_GAS_LEVEL_MEASURE_ANALOG_IN_PIN);

        Serial.print((millis() - startMillis)/1000);
        Serial.print(",");
        Serial.println(lpGas);

        mq6NextReadingTimeMillis = millis() + MQ6_SAMPLE_PERIOD_MILLIS;
      }

      if(millis() > mq6SwitchTimeMillis){
        digitalWrite(MQ6_POWER_ON_DIGITAL_OUT_PIN, LOW);

        mq6State = ST_MQ6_DONE;
      }
      
      break;
    }

    case ST_MQ6_DONE :
    {
      break;
    }
  }
}

//-----------------------------------------------------

If you are interested in how this type of sensor can be integrated into a broader solution that includes notification of combustible gasses on a users Android phone see Receive Alerts of Combustible Gasses

You can find a PCB breakout for this sensor if you are ready to create something more polished.

Let me know if you build any cool enhancements to this. Have fun!

Saturday, March 29, 2014

CO (Carbon Monoxide) Gas Sensor Using the Arduino Uno

This simple project uses the Arduino Uno and the MQ7 Gas Sensor to sense the concentration of CO (Carbon Monoxide) in the air. The MQ7 requires a heater voltage that cycles between 5v (60s) and 1.4v (90s), drawing approximately 150mA at 5v which exceeds the power capacity of the Uno, so I use the KA278RA05C adjustable voltage regulator to drive this. The default voltage of the KA278RA05C with Vadj (pin 4) disconnected is 5v which serves for the heater high voltage part of the cycle. I use a 50k potentiometer to adjust the voltage down to 1.4v for the heater high voltage part of the cycle. I use an LH1546 optical solid state relay to switch the adjustable voltage of the potentiometer on for the 1.4v heater low voltage. Pin 8 on the Arduino Uno drives the optical solid state relay and when high turns the relay on, adjusting the voltage of the regulator down to 1.4v. When this pin is low it turns off the relay causing the regulator to go back up to 5v. Analog pin 0 on the Arduino Uno is used to sense the voltage level out of the MQ7 which serves to measure the concentration of CO (Carbon Monoxide) in the air. Below is the diagram of the circuit on Fritzing.

Below is the actual circuit:

Below is the Arduino Uno sketch used:

#define VOLTAGE_REGULATOR_DIGITAL_OUT_PIN 8
#define MQ7_ANALOG_IN_PIN 0

#define MQ7_HEATER_5_V_TIME_MILLIS 60000
#define MQ7_HEATER_1_4_V_TIME_MILLIS 90000

#define GAS_LEVEL_READING_PERIOD_MILLIS 1000

unsigned long startMillis;
unsigned long switchTimeMillis;
boolean heaterInHighPhase;

void setup(){
  Serial.begin(19200);
  
  pinMode(VOLTAGE_REGULATOR_DIGITAL_OUT_PIN, OUTPUT);
  
  startMillis = millis();
  
  turnHeaterHigh();
  
  Serial.println("Elapsed Time (s), Gas Level");
}

void loop(){
  if(heaterInHighPhase){
    // 5v phase of cycle. see if need to switch low yet
    if(millis() > switchTimeMillis) {
      turnHeaterLow();
    }
  }
  else {
    // 1.4v phase of cycle. see if need to switch high yet
    if(millis() > switchTimeMillis) {
      turnHeaterHigh();
    }
  }
  
  readGasLevel();
  delay(GAS_LEVEL_READING_PERIOD_MILLIS);
}

void turnHeaterHigh(){
  // 5v phase
  digitalWrite(VOLTAGE_REGULATOR_DIGITAL_OUT_PIN, LOW);
  heaterInHighPhase = true;
  switchTimeMillis = millis() + MQ7_HEATER_5_V_TIME_MILLIS;
}

void turnHeaterLow(){
  // 1.4v phase
  digitalWrite(VOLTAGE_REGULATOR_DIGITAL_OUT_PIN, HIGH);
  heaterInHighPhase = false;
  switchTimeMillis = millis() + MQ7_HEATER_1_4_V_TIME_MILLIS;
}

void readGasLevel(){
  unsigned int gasLevel = analogRead(MQ7_ANALOG_IN_PIN);
  unsigned int time = (millis() - startMillis) / 1000;
  
  Serial.print(time);
  Serial.print(",");
  Serial.println(gasLevel);
}



The screenshot below shows the CSV data points of time in seconds against the MQ7 output voltage graphed into Excel.

The point at which to read the MQ7 level is at the end of the high 5v heating phase just before transitioning to the low 1.4v heating voltage. I found mine stabilized in an indoor home environment around 211 after the second heating cycle, corresponding to an analog voltage out of the MQ7 of (211/2013) * 5v = 1.03v. To calibrate I'll need a CO gas meter. Next item on the purchase list :-) Enjoy. Feel free to post below if you build some additional cool stuff on this.

If you are interested in how this type of sensor can be integrated into a broader solution that includes notification of carbon monoxide on a users Android phone see Receive Alert of High Carbon Monoxide Level

Saturday, February 22, 2014

High Sensitivity Vibration Sensor Using Arduino

In my last post I described how to build a High Sensitivity Arduino Sound Level Detector. Another useful type of sensor to determine if something interesting is going on in the environment is a vibration sensor. In this post I use a piezo element as a raw sensor to detect vibration.

I found the raw piezo generated a very small signal. To greatly improve its sensitivity I used epoxy to glue a fishing weight to the piezo sensor. The piezo drives a load resistor of 1M in parallel with a 5.1v Zener diode just to protect the IC's against any large voltage spikes in the event of a large physical bump. I found the raw output of the piezo unsuitable for direct input to the Arduino as it is typically a very small voltage signal and needs amplification, so I amplify the signal from the piezo with a 221 gain non-inverting op-amp using one side of an LM358. I use the other side of the LM358 for a comparator. The sensitivity of the vibration sensor is controlled using a potentiometer for the threshold (negative) input into the comparator. The other (positive) input to the comparator comes from the amplifier of the piezo signal. The output of the comparator provides a direct input to Arduino Uno digital pin 8. To hear when it senses vibration I use a simple piezo buzzer driven directly from Arduino Uno pin 13. Below is the circuit diagram:

... and the breadboard circuit:

Here is the actual prototype:

... and a close up of the piezo element with the fishing weight glued on with epoxy for added sensitivity:

Here is the sketch I used on the Arduino Uno:

If you want to use this as a starting point you can copy / paste from below:

#define VIBRATION_DIGITAL_IN_PIN 8
#define BUZZER_DIGITAL_OUT_PIN 13

int buzzerDurationMillis = 1000;

void setup(){
  pinMode(VIBRATION_DIGITAL_IN_PIN, INPUT);
  pinMode(BUZZER_DIGITAL_OUT_PIN, OUTPUT);
}

void loop(){
    if(digitalRead(VIBRATION_DIGITAL_IN_PIN) == HIGH){
      digitalWrite(BUZZER_DIGITAL_OUT_PIN, HIGH);
      delay(buzzerDurationMillis);
      digitalWrite(BUZZER_DIGITAL_OUT_PIN, LOW);
    }
}
Enjoy. Let me know if you make any cool improvements on this.

Note: if you notice your output locking in on state try lowering the feedback resistor of the op-amp from 220k to something lower, for example 160k.

Update: I later added a 0.1uF capacitor to connect the output from the piezo element to the input of the op-amp, also grounded on the op-amp side using a 100k resistor. This acted as a DC decoupler and effectively lowered the comparator threshold required to detect vibration.

Below is the updated circuit diagram showing the refinements for reducing the gain to avoid op-amp output lockup, and DC decoupler on the input.

If you are wanting to differentiate single bumps from vibrations you may also be interested in this blog I did on an Arduino sketch that can be used to avoid false positives. You may also want to look at my subsequent blog on how to use these in an open door or tailgate down sensor which also uses a Reed switch.

If you are interested in how this type of sensor can be integrated into a broader solution that includes notification of detected vibration on a users Android phone see Detect Intrusion with Passive Infrared, Sound, or Vibration