I am using a ultrasonic sensor to detect motion by looking at the variance of the sensor readings, however my algorithm is very prone to give false positives thus erroneously rising a motion event.
Algorithm Background
What I do is send a ping 20 times a second and wait for a pulse to come back using the input capture on arduino. I have a limit of 2 metres because I don't need to detect motion beyond that. Using a modifed version of the NewPing library which has a method check_timer
which returns a value greater than 1 if the pulse from the arduino took more than a corresponding wait time for 2 metres and returns 1 if no pulse has been measured yet or 0 if it successfully got a pulse.
The following conditions need to be met to rise a motion event:
If the last measured pulse was a valid pulse (i.e
check_timer
returns 0) it will save it in an array and wait for another positive pulse and only then test for the variance of the two pulses. However if it failed to measure a valid pulse after the first success it will reset the array.If a variance greater than or equal to 500 is measured within a time frame it will rise a motion event.
The code for my algorithm is this:
#include <NewPing.h>
#define TRIGGER_PIN 10 // Arduino pin tied to trigger pin on ping sensor.
#define ECHO_PIN 9 // Arduino pin tied to echo pin on ping sensor.
#define MAX_DISTANCE 200 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
unsigned int pingSpeed = 50; // How frequently are we going to send out a ping (in milliseconds). 50ms would be 20 times a second.
unsigned long pingTimer; // Holds the next ping time.
unsigned long last = 0;
unsigned int samples = 2;
unsigned int distances[2];
unsigned int sum = 0;
void setup() {
Serial.begin(9600); // Open serial monitor at 9600 baud to see ping results.
pingTimer = millis(); // Start now.
}
void loop() {
// Notice how there's no delays in this sketch to allow you to do other processing in-line while doing distance pings.
if (millis() >= pingTimer) { // pingSpeed milliseconds since last ping, do another ping.
pingTimer += pingSpeed; // Set the next ping time.
sonar.ping_timer(echoCheck); // Send out the ping, calls "echoCheck" function every 24uS where you can check the ping status.
}
// Do other stuff here, really. Think of it as multi-tasking.
}
void echoCheck() { // Timer2 interrupt calls this function every 24uS where you can check the ping status.
uint8_t rc = sonar.check_timer();
if(rc==0) { // This is how you check to see if the ping was received.
sum = sum + sonar.ping_result;
distances[--samples] = sonar.ping_result;
if(samples == 0) {
float avrg = (float)(sum) / 2.0f; // Get the average of the samples we recorded
float sum1 = 0;
for(int i=1;i>-1;--i) {
sum1 = sum1 + pow((distances[i] - avrg),2);
}
if((sum1 / 2.0f) >= 500.0f) { // If the variance of the samples is greater than or equal to 500
unsigned long now = millis();
if(now-last <= 250) { // To filter out random false positives we want two positves within this time frame
Serial.print("motion event, variance is ");
Serial.println(sum1 / 2.0f);
}
last = now;
}
sum = 0;
samples = 2;
}
}else if(rc>1) { // Was it a faulty reading (e.g it exceeded the maximum waiting time)?
sum = 0;
samples = 2;
}
}
My code works but is very unreliable. How can I improve this algorithm?
(EDIT: FYI the sensor I am refering to is a cheap HC - SR04 Ultrasonic Ranging Module.)