0

I have a PIR sensor which sometimes give false readings and I have added a ultrasonic sensor to compensate for that. I think my algorithm can work, however currently it is very prone to give false positives.

Algorithm theory

My idea is to have an arduino which will obtain six samples and wait 25 milliseconds between each sample and then calculate the variance. If the variance exceeds a specific threshold, which I have set to a thousnad, it will send an motion event over the serial bus.

Problem with the algorithm

Ultrasonic sensors can give faulty responds which usually isn't hard to detect. My HC-SR04 sensor can only accurately read the distance between 2 cm and 400 cm. All the faulty readings are usually way over 400 cm or lower than 2 cm, therefore if is not within these boundaries a new try will be made to get a value within the boundaries, if it fails for three times it will assume the distance is exactly 400 cm.

It is redundant to wait for more than 23280 µs because 400*29.1*2 is the corresponding microseconds it would take for the distance to be 400 cm. pulseIn has a timeout value which can be used for this.

In theory this sounds great to me but very often this algorithm will erroneously send motion events. My belief for this is that when the ultrasonic sensor transmitts a wave and wait for it come back, pulseIn will timeout and retry and yet again ask the sensor to send a wave. This happens before the first wave has come back thus the distance will be incorrect and vary from time to time making the variance higher than my threshold.

If the distance is much lower than 4 metres my algorithm works very well.

What I tried

I figured I might be able to block for some time until the echo pin on the ultrasonic sensor turns low or a time of 100ms has passed. The code hanged and I couldn't communicate with my arduino any more. I can't see the problem with my code to obtain the distance pursuant to the ultrasonic sensor.

for(tries=3;tries;--tries) {
  digitalWrite(TRIG_PIN), LOW); // Set trigger pin low.
  delayMicroseconds(2); // Let signal settle.
  digitalWrite(TRIG_PIN, HIGH); // Set trigger pin high.
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW); // Ping has now been sent.
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 23280); // Duration is in microseconds
  unsigned long time = micros();
  while(!(PINB & 0b00000010)) { // ECHO_PIN is PB1 or digital pin 9
    if((micros() - time) >= 100000) {
      continue;
    }
  }
  if(duration >= 116L && duration <= 23280L) { // Is duration within boundaries?
    return ((float)(duration)/2.0) / 29.1;
  }
}
return 400.0f;

This just blocks my program indefinently and I had to upload a new program removing that code for my arduino to respond again.

So my question boils down to: how can I resolve this issue where my algorithm very frequently sends motion events erroneously? Is there a better way to detect motion with an ultrasonic sensor?

(EDIT: I was really tired when I wrote the code above and Majenko corrected me, I changed the code for this

for(tries=3;tries;--tries) {
  digitalWrite(TRIG_PIN), LOW); // Set trigger pin low.
  delayMicroseconds(2); // Let signal settle.
  digitalWrite(TRIG_PIN, HIGH); // Set trigger pin high.
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW); // Ping has now been sent.
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 23280); // Duration is in microseconds
  unsigned long time = micros();
  while(PINB & 0b00000010) { // ECHO_PIN is PB1 or digital pin 9
    if((micros() - time) >= 100000) {
      goto retry;
    }
  }
  if(duration >= 116L && duration <= 23280L) { // Is duration within boundaries?
    return ((float)(duration)/2.0) / 29.1;
  }
  retry:
  continue;
}
return 400.0f;
1

The main blocking part of your code is this bit:

while(!(PINB & 0b00000010)) { // ECHO_PIN is PB1 or digital pin 9
  if((micros() - time) >= 100000) {
    continue;
  }
}

That is saying "While the echo pin is low, check the time. If too much time has passed then carry on with the loop".

So basically that will loop until the echo pin goes high, regardless of the time.

The continue command means to jump back to the beginning of the loop that it's in, be that a while, for, or do loop. In this case it jumps back to the beginning of the while loop it's in.

Instead you should consider using break, which means "Terminate this loop and carry on with what comes next".


Ultrasound sensing with pulseIn(), while simple to perform is rarely particularly accurate. Any interrupts that occur during the sensing will affect the outcome, and it's only as accurate as the micros() function allows it to be.

If you need a more precise and reliable measurement you should consider using one of the Input Capture modules in the ATMega. I don't know if there is a library to do it off hand, but programming it through the registers shouldn't be that difficult - just read the datasheet to find out how it all works.

Briefly the operation of IC is:

  1. Send trigger pulse
  2. Start IC timer counting
  3. Pulse arrives
  4. IC stops counting automatically and interrupt is triggered
  5. Read counter value and convert to microseconds.
  6. Set flag to indicate a result is available

The timing source is the main system clock and counting happens in the background completely parallel to the operation of the CPU, so nothing can interrupt it or delay it. Even if the execution of the IC interrupt routine is delayed by another interrupt running the counter value will have already stopped incrementing, so it will have been "captured" and give the correct result every time.

  • Thanks it didn't give me any better results though, see my updated question please. – Linus Nov 8 '15 at 9:46
  • @Linus I have added an edit to my answer about doing more reliable and accurate measurements. – Majenko Nov 8 '15 at 11:05
  • Thanks, I'll definently look in to Input Capture modules. Is it gauranteed to never give faulty values? Should I still check the boundaries and clamp to 116 and 23280 ? – Linus Nov 8 '15 at 11:52
  • @Linus It can't hurt to check the boundaries. It should always give good results, but of course it's somewhat dependant on the reliability of the ultrasound module. It will certainly give more reliable results than pulseIn(). – Majenko Nov 8 '15 at 12:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.