Calculating Speed with Two Sensors

Hey so I'm working on a project involving with PIR Sensors, one digital and one analog output. So not to overwhelm anyone I will get to the jist of the problem. I want to calculate the speed of a passing vehicle using the time between my sensor detections. Knowing the distance between them (4 ft) I can calculate the speed. Problem is my code is outputing crazy values for my initial testing of just walking by my sensors.

The sensors themselves are interesting because once motion is detected it takes about 1 sec for it to settle again so my code accounts for that. My system should be able to detect cars from both directions and compute the data accordingly. I won't show the rest of my code because I have many other things going on and I want to focus on why my output is so crazy.

The main idea again is to take the time between sensor triggers using micros() and calculate speed. Any help would be great.

void computeData_0() // calculates speed, direction and concentration 
{  
   t0 = micros(); // timestamp for calculating data

   timeout0 = millis();
  
   while(digitalVal == LOW){ // wait for digital sensor to go HIGH
       digitalVal = digitalRead(digitalPin);
       if ((float)millis() - timeout0 > 800) return; // timeout after 800ms (0.8 s) 
   }
   
   time0 = ((float)micros() - t0) / 1000000; // time between sensor dections (secs)
   delay(1500); // let sensor settle
   
   // attempt to delay without actually using delay() but it doesn't work that well, not hugely important
   delayTime = millis();        
   if (((float)millis() - delayTime) > 300){ // wait 300ms until Digital Pin is LOW
        
        newDigital = digitalRead(digitalPin); 
                
            if (newDigital == LOW ){ //check case to see if digitalRead is LOW
            }
        }
   
   speed_0 = (4 / time0) * 0.681818; // calculates speed (mph), distance of 4 ft
   
   
   
   if (speed_0 > 60 || speed_0 < 1) return; // Check for crazy values and omit if so

   else if (speed_0 < 60 || speed_0 > 1){
     
        countTime = ((t0)/1000000) / 60; // minutes since program started
        counter++;   // Add 1 to counter
        counterLeft++; // Add 1 to counterLeft
        trueSpeed = speed_0; // redeclare speed variable  
        concentration = counter / countTime; // calculates concentration (vehicles/min)
        displayData(); // displays data  to serial and lcd screen
   }
}

Exactly which PIR's are you using? Not all PIR's are the same, and some have bizarre behaviors. Did you narrow their detection range by putting them inside of a tube? If not, I highly recommend it. Also PIR's can give you a lot of false triggers, especially in sunlight, and especially if the field of view isn't narrowed.

One possible flaw you may not have thought of is your speed timing might vary widely depending on house close or house far away the car is from the PIR's. Especially if the field of view is significantly narrowed. Let's say car 1 and car 2 appear one after the other with sufficient margin between them, and they are both driving the same direction at exactly the same speed. Car 1 is driving on the far side of the street, and car 2 is driving on the near side of the street. Because car 1 is further away, it will likely be detected with a wider field of view than car 2, and thus the timings might be different between the cars. That said, I don't think that's why you are getting such erratic results.

I'm not a fan of how you are doing the detection on your sensors. If I could perhaps suggest a different way?

I'd rather read both sensors continuously in the main loop and I'd use a state machine to determine when to calculate and display results. Basically you snapshot a timestamp when one of the PIR's goes high and set the state to indicate which direction the car is coming from. Then you continue your loop doing other things until the opposite sensor goes HIGH, and you snapshot a timestamp for when it does and then you calculate your speed. This way you can be updating a display or doing other things while waiting for the opposite sensor to trigger without having to be stuck in a function or a loop.

Something like this:

#define STATE_IDLE                0
#define STATE_DETECT_FROM_LEFT    1
#define STATE_DETECT_FROM_RIGHT   2

int state = STATE_IDLE;
unsigned long sample_start = 0;
unsigned long sample_end = 0;
boolean leftsensor;
boolean rightsensor;
int left = 3;
int right = 4;

void setup()
{
  pinMode(left, INPUT);   
  pinMode(right, INPUT); 
}

void loop()
{
  boolean leftsensor = digitalRead(left);
  boolean rightsensor = digitalRead(right);
  
  switch(state)
  {
    case STATE_IDLE:
    {
      if (leftsensor == HIGH)
      {
        sample_start = millis();
        state = STATE_DETECT_FROM_RIGHT;
      }
      else if (rightsensor == HIGH)
      {
        sample_start = millis();
        state = STATE_DETECT_FROM_LEFT;
      }
    }
    break;
    
    case STATE_DETECT_FROM_LEFT:
    {
      if (rightsensor == HIGH)
      {
        // The right sensor just triggered, this means
        // we should calculate and display the speed.
        sample_end = millis();
        calculateSpeed();
        state = STATE_IDLE;
      }
    }
    break;
    
    case STATE_DETECT_FROM_RIGHT:
    {
      if (leftsensor == HIGH)
      {
        // The left sensor just triggered, this means
        // we should calculate and display the speed.
        sample_end = millis();
        calculateSpeed();
        state = STATE_IDLE;
      }
    }
    break;
  }
}

void calculateSpeed()
{
  unsigned long duration = sample_end - sample_start;
  
  // do speed conversion math here using duration
  
  // display results here
  
  // delay until sensors reset
  while (digitalRead(left) == HIGH || digitalRead(right) == HIGH)
  {
    delay(10);
  }
}

You can easily check the direction by looking at which state we are in from the calculateSpeed() function.

EDIT: You can also easily add some timeout logic to reset the state to STATE_IDLE if the opposite sensor never gets triggered within a certain timeframe.

A few things first.

  1. I do have tubing to restrict the vision of the sensor which seems to work (about an inch over the sensor itself)

  2. I am using one digital and one analog PIR sensor, mainly to prove a point that it can be done with both so my code will be a little more complex than if (sensor == HIGH) do blah blah but thats not the hard, I have figured that out. I thought initally this would conflict as each sensor would be off and cause error but only about 75% of the time do I get a crazy value so most of the time it says I walked by going 3 mph, pretty standard, then couple times later, BOOM, I'm moving at 50 mph, a little suspicious.

  3. I am constantly polling each sensor in my main loop, my code above is what I have in one of my state machines (even though I am not using switch case like you suggested, it still works nonetheless). I will post whole code for anyone that is confused.

  4. I do like you switch case and I wish I had more time to go back through and clean my code up (as you can tell I'm not the best at it) however I am closing in on the project due date.

Again, I can't fathom why multiple outputs are working perfectly then all of sudden an extreme value appears.

/*
*/

#include <LiquidCrystal.h>
#include <SD.h>

const int chipSelect = 17;
String message1, message2, message3;
int digitalVal, analogVal, newDigital, newAnalog, newAnalogVal;
int digitalPin = 2;
int analogPin = A0;
int calibrationTime = 60;        
int counter, counterLeft, counterRight = 0;
float t1, t0, countTime, concentration;
float speed_0, trueSpeed;
float time0, time1, delayTime ;
float timeout1, timeout0;

LiquidCrystal lcd(0, 13, 9, 4, 5, 6, 7);


void setup() {
 delay(2000);
 
 lcd.begin(16,2);
 lcd.clear();
 lcd.setCursor(0,0);
 
 Serial.begin(9600);
 while(!Serial) {
 ;
 }
 
 // Wait for sensors to calibrate (60s)
  Serial.print("calibrating sensor ");
  lcd.print("Calibrating");
  lcd.setCursor(0,1);
  lcd.print("Sensor");
  lcd.setCursor(0,0);
    for(int i = 0; i < calibrationTime; i++){
      Serial.print(".");
      delay(1000);
      }
    Serial.println(" done");
    lcd.print("done");
    lcd.setCursor(0,1);
    Serial.println("SENSOR ACTIVE");
    lcd.print("SENSOR ACTIVE");
    lcd.setCursor(0,0);

    delay(50);
 
 Serial.print("Initializing SD card...");
  lcd.print("Initializing SD");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("SD card failed, or not present");
    lcd.setCursor(0,1);
    lcd.print("SD card failed");

    // don't do anything more:
    return;
  }
  Serial.println("SD card initialized."); 
  lcd.setCursor(0,0);
  lcd.print("SD card initialized."); 
  delay(1000);
  lcd.clear();
  
  pinMode(analogPin, INPUT); // Analog Pin (A0) - PIR Sensor
  pinMode(digitalPin, INPUT); // Digital Pin (D2) - PIR Sensor   
 
  File dataFile = SD.open("ricelake.txt", FILE_WRITE);
  if (dataFile)
  {
    dataFile.println(", , , ,"); //Just a leading blank line, incase there was previous data
    String header = "Speed | From Left | From Right | Concentration";
    dataFile.println(header);
    dataFile.close();
    Serial.println(header);
  }
  else
  {
    Serial.println("Couldn't open log file");
  }
}

void computeData_0() // calculates speed, direction and concentration 
{  
   t0 = millis(); // timestamp for calculating data

   timeout0 = millis();
  
   while(digitalVal == LOW){ // wait for digital sensor to go HIGH
       digitalVal = digitalRead(digitalPin);
       if ((float)millis() - timeout0 > 800) return; // timeout after 800ms (0.8 s) 
   }
   
   time0 = ((float)millis() - t0) / 1000; // time between sensor dections (secs)
   delay(5000); // delay long time to ensure sensor is stable
   
   delayTime = millis();        
   if (((float)millis() - delayTime) > 300){ // wait 300ms until Digital Pin is LOW
        
        newDigital = digitalRead(digitalPin); // Check digital sensor
                
            if (newDigital == LOW ){ //check case to see if digitalRead is LOW
            }
        }
   
   speed_0 = (4 / time0) * 0.681818; // calculates speed (mph), distance of 4 ft
   
   
   
   if (speed_0 > 60 || speed_0 < 1) return; // Check for crazy values and omit if so

   else if (speed_0 < 60 || speed_0 > 1){
     
        countTime = ((t0)/1000) / 60; // minutes since program started
        counter++;   // Add 1 to counter
        counterLeft++; // Add 1 to counterLeft
        trueSpeed = speed_0; // redeclare speed variable  
        concentration = counter / countTime; // calculates concentration (vehicles/min)
        displayData(); // displays data  
   }
}

void computeData_1() // calculates speed, direction and concentration 
{ 
   t1 = millis(); // timestamp for calculating data

   timeout1 = millis(); 
  
   while(!(analogVal <= 400 || analogVal >= 600)){
       analogVal = analogRead(analogPin);
       if ((float)millis() - timeout1 > 800) return; // timeout after 800ms (0.8 s)
   }
  
   time1 = ((float)millis() - t1) / 1000; // time between sensor dections (secs)
   delay(5000); // delay long time to ensure sensor is stable 

  
   delayTime = millis();        
   if (((float)millis() - delayTime) > 300){ // wait 300ms until Digital Pin is LOW
        
        newAnalog = analogRead(analogPin); // check to see motion is done
        newAnalogVal = newAnalog + 500; // absolute value idea  
        
            if (newAnalogVal > 600 ){ //check case to see if digitalRead is LOW
            }
        }

    speed_0 = (4 / time1) * 0.681818; // calculates speed (mph)

    if (speed_0 > 60 || speed_0 < 1) { // If speed is crazy small or large, omit it
      
        
      
        return; 
    }
    else if (speed_0 < 60 || speed_0 > 1) {
     
        countTime = ((t1)/1000) / 60; // minutes since program started
        counter++;   // Add 1 to counter
        counterRight++; // Add 1 to counterLeft
        trueSpeed = speed_0; // redeclare speed variable 
        concentration = counter / countTime; // calculates concentration (vehicles/min)
        displayData(); // display data    
   }
}

void displayData(){ // displays/saves data to Serial, LCD, and SD file
 
   // Displays Speed
 
   lcd.print("Speed=");
   lcd.print(trueSpeed);
   lcd.print(" MPH");
   lcd.setCursor(0,1);
   lcd.print("R = ");
   lcd.print(counterRight);
   lcd.print(", L = ");
   lcd.print(counterLeft);
  
  // open the file, note that only one file can be open at a time,
  // so you have to close this one before opening another.
  
  File dataFile = SD.open("ricelake.txt", FILE_WRITE);
  if (dataFile) {
    
    
    dataFile.print(trueSpeed);
    dataFile.print("        ");
    dataFile.print(counterLeft);
    dataFile.print("           ");
    dataFile.print(counterRight);
    dataFile.print("           ");
    dataFile.println(concentration);
    
    //dataFile.println(trafficData);
    dataFile.close();
    
    // print to the serial port too:
    Serial.print(trueSpeed);
    Serial.print("        ");
    Serial.print(counterLeft);
    Serial.print("            ");
    Serial.print(counterRight);
    Serial.print("            ");
    Serial.println(concentration);
  }  
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening ricelake.txt");
  }    
  
}

void loop() {
  
  lcd.setCursor(0,0); // set cursor to Row 1, Col 1

  analogVal = analogRead(analogPin);
  digitalVal = digitalRead(digitalPin);
  
  if (analogVal <= 400 || analogVal >= 600) {

        computeData_0();
  }
 
  else if (digitalVal == HIGH) {
        
        computeData_1();  
  }
  
  else if (analogVal <= 400 || analogVal >= 600 && digitalVal == HIGH) {
        
  }
}

Again I apologize for the code as it is everywhere but in general it has been working so far minus this little hiccup of random outputs.

There's a big difference from my code and your code. Your code is more of a synchronous state machine, where as mine is an asynchronous state machine. There's nothing wrong with that... but I would argue that my state machine is superior. When you change states, you're going to sub-functions and your code can't progress to another state until that function completes. This severely limits your ability to do things the "easy way". For example, what if you wanted to flash a LED 4 times a second regardless of which state you are in? You'd have to implement this code in each state or call a function in each state, taking care to ensure that the timing doesn't get thrown off. You'll likely discover that the LED blinks somewhat irratic and not what you intended. My code, on the other hand, allows me to put the flash code at the end of the main loop, and this code will get executed for every state. As long as each state doesn't take longer than 125ms, I should have very accurate flashing no matter what is going on in my state machine.

Here's the code updated showing you how to flash the LED. I've taken the "reset" logic and moved it to it's own state so that the LED will continue to flash even while waiting for the sensors to reset. Note that I've also updated the code to handle your analog sensor.

#define STATE_IDLE                0
#define STATE_DETECT_FROM_LEFT    1
#define STATE_DETECT_FROM_RIGHT   2
#define STATE_WAIT_FOR_RESET      3

int state = STATE_IDLE;
unsigned long sample_start = 0;
unsigned long sample_end = 0;
unsigned long flashtimestamp = 0;
unsigned long flashrate = 125;
boolean flipflop;
boolean leftsensor;
boolean rightsensor;
int left = 3;
int right = 4;
int minvalue = 400;
int maxvalue = 600;

void setup()
{
  pinMode(13, OUTPUT);
  pinMode(left, INPUT);   
  pinMode(right, INPUT); 
}

void loop()
{
  leftsensor = digitalRead(left);
  //rightsensor = digitalRead(right);
  int rightanalog = analogRead(right);
  if (rightanalog >= minvalue && rightanalog <= maxvalue) 
  {
    rightsensor = HIGH;
  }
  else
  {
    rightsensor = LOW;
  }
  
  switch(state)
  {
    case STATE_IDLE:
    {
      if (leftsensor == HIGH)
      {
        sample_start = millis();
        state = STATE_DETECT_FROM_RIGHT;
      }
      else if (rightsensor == HIGH)
      {
        sample_start = millis();
        state = STATE_DETECT_FROM_LEFT;
      }
    }
    break;
    
    case STATE_DETECT_FROM_LEFT:
    {
      if (rightsensor == HIGH)
      {
        // The right sensor just triggered, this means
        // we should calculate and display the speed.
        sample_end = millis();
        calculateSpeed();
        state = STATE_IDLE;
      }
    }
    break;
    
    case STATE_DETECT_FROM_RIGHT:
    {
      if (leftsensor == HIGH)
      {
        // The left sensor just triggered, this means
        // we should calculate and display the speed.
        sample_end = millis();
        calculateSpeed();
      }
    }
    break;
    
    case STATE_WAIT_FOR_RESET:
    {
      if (leftsensor == LOW && rightsensor == LOW)
      {
        state = STATE_IDLE;
      }
    }
    break;
  }
  
  // Code that will be executed regardless of which state we are in:
  ///////////////////////////////////////////////////////////////////
  // let's make a LED flash 4 times a second.
  if (millis() > flashtimestamp)
  {
    flipflop = !flipflop;
    flashtimestamp = millis() + flashrate;
  }
  
  if (flipflop)
    digitalWrite(13, HIGH);
  else
    digitalWrite(13, LOW);
    
}

void calculateSpeed()
{
  unsigned long duration = sample_end - sample_start;
  
  // do speed conversion math here using duration
  
  // display results here
  
  // delay until sensors reset
  state = STATE_WAIT_FOR_RESET;
}

Another thing that will prove to you that my code is the better way to go... What if you wanted to monitor another set of sensors for a different road from the same Arduino? In other words, you'd have 2 sets of PIRs. Your code isn't going to work so hot... Once one of the sensors is triggered, you're going to be unable to process the speed on the other set of sensors if they should happen to be triggered while waiting for the second sensor on the first set. Try to solve that one using your code. Bet you'll have a difficult time with that! But if you used my state machine, you could detect both "lanes" simultaneously and independently of each other. All you would need to do is add another state machine after the first state machine and modify the code a little to deal with the different sets of sensors. This would be darn near impossible with your code. Just saying. :wink:

As for your problems... are you using any pullup or pulldown resistors? You may have your signals floating which would cause erratic reads on the digital & analog IO.

Your state machine makes alot of since and I am giving it shot but as always in programming something will go wrong. We have a pulldown resistor from the digital output to GND but not on the analog so the pins aren't floating I believe. Thanks for the help I will update hopefully if something works or needs tweaking.

Lol, that's what makes programming a challenge! :wink:

How does the analog sensor work? Do you have a part number or datasheet? Depending on how it works, it might benefit with a pulldown resistor.

I am using spot detection type AMN 2311 and 1311.

I think a pulldown on the analog resistor wouldn't effect the overall system to the point where its necessary. The multimeter shows an idle around 2.5V DC and goes to ±1V DC when motion is detected. So thats why my threshold voltages are at 400 and 600 because on the O-scope shows the analog sensor dipping either above or below 2.5V, and theres no rhyme or reason why it chooses to go either below or above 2.5V. So thats why I didn't care to much. Yes, I could use a comparator and force to a 1 or 0 but at this rate again I don't have much time to keep fiddling around.

A pulldown might help but I don't think it will fix my issue.

Since the arduino's ADC converts to a value between 0 and 1023, I can simply add 500 to my analogRead value and that should effectively keep the sensor between 500 and 1023, according to analogRead. But again I don't think this fixed my issue either. Once I have a chance to test on Monday I can update with a possible solution.

Here is my code in its entirety. I'm trying to record speed, the count from each direction of the passing vehicle, and the overall concentration of vehicles on the road, then save to a SD for later analysis.

#define STATE_IDLE                0
#define STATE_DETECT_FROM_LEFT    1
#define STATE_DETECT_FROM_RIGHT   2
#define STATE_WAIT_FOR_RESET      3

#include <LiquidCrystal.h>
#include <SD.h>

const int chipSelect = 17;
int state = STATE_IDLE;
unsigned long sample_start = 0;
unsigned long sample_end = 0;
unsigned long countTime = 0;
int counter, counterLeft, counterRight, concentration = 0;
boolean digitalsensor;
boolean analogsensor;
int digitalPin = 2;
int analogPin = A0;
int minvalue = 400;
int maxvalue = 600;
float speed_0;

LiquidCrystal lcd(0, 13, 9, 4, 5, 6, 7);

void setup()
{
  
 delay(2000);
 
 lcd.begin(16,2);
 lcd.clear();
 lcd.setCursor(0,0);
 
 Serial.begin(9600);
 while(!Serial) {
 ;
 }
 
  Serial.print("Initializing SD card...");
  lcd.print("Initializing SD");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("SD card failed, or not present");
    lcd.setCursor(0,1);
    lcd.print("SD card failed");

    // don't do anything more:
    return;
  }
  
  Serial.println("SD card initialized."); 
  lcd.setCursor(0,0);
  lcd.print("SD card initialized."); 
  delay(1000);
  lcd.clear();
  
  pinMode(analogPin, INPUT);   
  pinMode(digitalPin, INPUT); 
  
  File dataFile = SD.open("newdata.txt", FILE_WRITE);
  if (dataFile)
  {
    dataFile.println(", , , ,"); //Just a leading blank line, incase there was previous data
    String header = "Speed | From Left | From Right | Concentration";
    dataFile.println(header);
    dataFile.close();
    Serial.println(header);
  }
  else
  {
    Serial.println("Couldn't open log file");
  } 
}

void loop()
{
  digitalsensor = digitalRead(digitalPin);
  int rightanalog = analogRead(analogPin);
  
  if (rightanalog >= minvalue && rightanalog <= maxvalue) 
  {
    analogsensor = HIGH;
  }
  else
  {
    analogsensor = LOW;
  }
  
  switch(state)
  {
    case STATE_IDLE:
    {
      if (digitalsensor == HIGH)
      {
        sample_start = millis();
        counter++;
        counterLeft++;
        state = STATE_DETECT_FROM_LEFT;
      }
      else if (analogsensor == HIGH)
      {
        sample_start = millis();
        counter++;
        counterRight++;
        state = STATE_DETECT_FROM_RIGHT;
      }
    }
    break;
    
    case STATE_DETECT_FROM_LEFT:
    {
      if (analogsensor == HIGH)
      {
        // The analog sensor just triggered, this means
        // we should calculate and display the speed.
        sample_end = millis();
        calculateData();
        state = STATE_IDLE;
      }
    }
    break;
    
    case STATE_DETECT_FROM_RIGHT:
    {
      if (digitalsensor == HIGH)
      {
        // The digital sensor just triggered, this means
        // we should calculate and display the speed.
        sample_end = millis();
        calculateData();
      }
    }
    break;
    
    case STATE_WAIT_FOR_RESET:
    {
      if (digitalsensor == LOW && analogsensor == LOW)
      {
        state = STATE_IDLE;
      }
    }
    break;
  }  
}

void calculateData()
{
  unsigned long time = (sample_end - sample_start)/1000;
  
  // do speed conversion math here using duration
  // speed is in MPH
  float speed_0 = (4/time) * 0.681818;
  
  // determine time since program start
  // determine concentration
  countTime = ((sample_start)/1000)/60;
  concentration = counter/countTime;
  
  // display results here
  displayData();
  // delay until sensors reset
  state = STATE_WAIT_FOR_RESET;
}

void displayData(){ // displays/saves data to Serial, LCD, and SD file
   
   lcd.setCursor(0,0);
 
   // Displays Speed
   lcd.print("Speed=");
   lcd.print(speed_0);
   lcd.print(" MPH");
   lcd.setCursor(0,1);
   lcd.print("R = ");
   lcd.print(counterRight);
   lcd.print(", L = ");
   lcd.print(counterLeft);
  
  // open the file, note that only one file can be open at a time,
  // so you have to close this one before opening another.
  
  File dataFile = SD.open("newdata.txt", FILE_WRITE);
  if (dataFile) {
    
    
    dataFile.print(speed_0);
    dataFile.print("        ");
    dataFile.print(counterLeft);
    dataFile.print("           ");
    dataFile.print(counterRight);
    dataFile.print("           ");
    dataFile.println(concentration);
    
    //dataFile.println(trafficData);
    dataFile.close();
    
    // print to the serial port too:
    Serial.print(speed_0);
    Serial.print("        ");
    Serial.print(counterLeft);
    Serial.print("            ");
    Serial.print(counterRight);
    Serial.print("            ");
    Serial.println(concentration);
  }  
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening newdata.txt");
  }    
  
}

Why are you dividing the difference between sample end and sample start by 1000? You are losing all your precision. Doing that will give you only whole seconds to work with. So when go to calculate speed, you will only use 1 second, 2 seconds, etc, but 1.234 would be truncated to 1 second. Which will give you a completely wrong result when you do the math.

That makes alot of sense and I can't believe I didn't see that sooner. Thanks again for help hopefully some good things happen this week testing it.

Glad we got you on the right track. Happy to help!