Arduino self-calibrating laser trip wire

When I first started this project I figured there would be plenty of references on the web for how to put this together, so I went ahead an ordered a laser from Adafruit and didn’t give it much thought. However once I received and started to attempt to build one, I found myself unsatisfied with what’s out there. As these things usually happen, I started with an example from somewhere else, then as I tried to use it, ran into an issue, then went back and modified the circuit and the sketch to overcome that issue.

The first thing was to just to get it to go off when the laser beam is interrupted on its way to the sensor. The first issue I ran into was the need for an arming mechanism so I could align them first without setting them off. Then I ran into multiple problems with calibration of the sensor in different light and distance settings. I was surprised to find other projects were simply hard coding values into their sketch, which is fine if you just want to get it to working then move on to another project, but is problematic if you actually want the tripwire to work in different places and at different times.

So at any rate, over several iterations, and the addition of visual indicators to aid in the calibration process I finalized on the following design and code. If you find it useful, leave me a comment.

What you’ll need for the main detector and siren:

For the laser, you’ll need:

  • 1 small breadboard
  • 1 laser (I used a small 5mW 650nm Red laser from Adafruit)
  • 1 small switch
  • 1 9v battery
  • 1 9v battery clip

First wire up your laser to a small switch and 9v battery, as pictured below:

Laser with switch Laser with switch

Next wire up the detector, indicator lights and siren according to the diagram below:

This image was made with Fritzing 0.7.11
This image was made with Fritzing 0.7.11

When finished it should look something like this:

Finished laser trip wire Finished laser trip wire Finished laser trip wire

Finally, load the following code into your Arduino sketch, upload to your Arduino board, tweak if you need to and test. This code can also be found on github.

/*
 * Laser trip wire
 * v 1.3
 * by: Keith Kay
 * 1/31/2013
 * CC by-sa v3.0 - http://creativecommons.org/licenses/by-sa/3.0/
 * http://keithkay.com
 * 
 * Laser trip-wire sketch which implements the following functionality: 
 * - Calibration to current light environment
 * - Visual feedback on alignment
 * - Arming and disarming mechanism
 *
 * attribution: siren code modified from
 *   Annoying siren
 *   CC by-sa v3.0
 *   http://tronixstuff.wordpress.com
 * 
 */

// first define the constants used for sensor / emitter pins
const int triggeredLED = 7;  // pin for the warning light LED
const int RedLED = 3;        // pin for the 'armed' state indicator
const int GreenLED = 4;      // pin for the 'un-armed' state indicator
const int inputPin = A0;     // pin for analog input
const int speakerPin = 12;   // pin for the speaker output
const int armButton = 6;     // pin for the arming button

// define variables used for readings and programatic control
boolean isArmed = true;      // variable for the armed state
boolean isTriggered = false; // has the wire been tripped
int buttonVal = 0;           // variable to store button state and compare with previous
int prev_buttonVal = 0;      // variable to 'debounce' button
int reading = 0;             // variable to store the analog value coming from the sensor
int threshold = 0;           // variable set by the calibration process

// constants used for the siren
const int lowrange = 2000;   // the lowest frequency value to use
const int highrange = 4000;  //  the highest...

void setup(){

  // configure LEDs for output
  pinMode(triggeredLED, OUTPUT);
  pinMode(RedLED, OUTPUT);
  pinMode(GreenLED, OUTPUT);

  //configure the button for input
  pinMode(armButton, INPUT);

  // for debugging and calibration review
  Serial.begin(9600);
  Serial.println("");
  Serial.println("Initializing...");

  // intial 'test' to be sure all LEDs and the speaker are working
  digitalWrite(triggeredLED, HIGH);
  delay(500);
  digitalWrite(triggeredLED, LOW);

  Serial.println("Unarmed");
  setArmedState();
  delay(500);

  Serial.println("Armed");
  setArmedState();
  delay(500);  

  // calibrate the laser light level in the current environment
  // this was moved to a function for future integration of a reset function
  calibrate();

  // start unarmed
  setArmedState();  

}

void loop(){

  // read the LDR sensor
  reading = analogRead(inputPin);

  // check to see if the button is pressed
  int buttonVal = digitalRead(armButton);
  if ((buttonVal == HIGH) && (prev_buttonVal == LOW)){
    setArmedState();
    delay(500);
    Serial.print("button val= ");
    Serial.println(buttonVal);
  }

  // print the value to serial
  Serial.print("Reading = ");
  Serial.println(reading);

  // check to see if the laser beam is interrupted based on the threshold
  if ((isArmed) && (reading < threshold)){
    isTriggered = true;}

  if (isTriggered){

    // siren code
    // increasing tone
    for (int i = lowrange; i <= highrange; i++)
    {
      tone (speakerPin, i, 250);
    }
    // decreasing tone
    for (int i = highrange; i >= lowrange; i--)
    {
      tone (speakerPin, i, 250);
    }

    // flash LED
    digitalWrite(triggeredLED, HIGH);
    delay(10);
    digitalWrite(triggeredLED, LOW);

  }

  // short delay - if you are debugging a modification you may want to increase this to slow down the serial readout
  delay(20);

}

// function to flip the armed state of the trip wire
void setArmedState(){

  if (isArmed){
    digitalWrite(GreenLED, HIGH);
    digitalWrite(RedLED, LOW);
    isTriggered = false;
    isArmed = false;
  } else {
    digitalWrite(GreenLED, LOW);
    digitalWrite(RedLED, HIGH);
    tone(speakerPin, 220, 125);
    delay(200);
    tone(speakerPin, 196, 250);
    isArmed = true;
  } 
}

void calibrate(){

  int sample = 0;              // array to hold the initial sample
  int baseline = 0;            // variable to set the baseline reading
  const int min_diff = 200;    // minimum difference needed between current light level and laser calibration
  const int sensitivity = 50;
  int success_count = 0;

  // ensure both LEDs are off
  digitalWrite(RedLED, LOW);
  digitalWrite(GreenLED, LOW);

  // start by taking a 10 reading sample, then take the average for our baseline
  for (int i=0; i<10; i++){
    sample += analogRead(inputPin); // take reading and add it to the sample
    digitalWrite(GreenLED, HIGH);
    delay (50); // delay to blink the LED and space readings
    digitalWrite(GreenLED, LOW);
    delay (50); // delay to blink the LED and space readings
  }

  // calculate and print the baseline
  baseline = sample / 10;
  Serial.print("baseline = ");
  Serial.println(baseline);  

  // now keep taking a reading until we've gotten 3 successful reads in a row
  do
  {
    sample = analogRead(inputPin);      // this time we work with one reading at a time

    if (sample > baseline + min_diff){
      success_count++;
      threshold += sample;

      digitalWrite(GreenLED, HIGH);
      delay (100);                     // delay to blink the LED and space readings
      digitalWrite(GreenLED, LOW);
      delay (100);                     // delay to blink the LED and space readings
    } else {
      success_count = 0;               // this give us the 'in a row' result
      threshold = 0;
    }

  } while (success_count < 3);

  //lastly we need to correctly set the threshold as it now hold the sum of 3 samples
  threshold = (threshold/3) - sensitivity;

  // play the arming tone and in reverse and print the threshold to condfrim threshold set
  tone(speakerPin, 196, 250);
  delay(200);
  tone(speakerPin, 220, 125);
  Serial.print("baseline = ");
  Serial.println(baseline);

}

For an overview of how this functions, I have included a video walkthru on youtube

At this point, this is as far as I am going to take this, but would love to hear ideas that build on this.