Hi, I have a overhead crane alarm system that I'm trying to get the bugs worked out of. The problem that I'm running into is one of the maxsonar WR1 sensors is giving me varying readings because the hook that is below it swings. I need a way to average the value coming from the sensor. Does anyone have an idea on how to do this? The value I need averaged is sensorValueH. Thanks.
/*
Upper lower crane alarm system
The circuit:
* Ultra sonic range finder analog input A1,2,3
* LED outputs D11,12,13
* crane alarm output D2
* Note: LED 13 (red is also an indicator of Control status
*/
int sensorPinH = A2; // input pin for Ultrasonic sensor
int sensorPinN = A1; // input pin for Ultrasonic sensor
int sensorPinS = A3; // input pin for Ultrasonic sensor
int GledPin = 2; // select the pin for the LED
int HornPin = 3; // select the pin for the horn relay
int sensorValueS = 0; // variable to store the value coming from the South sensor
int sensorValueH = 0; // variable to store the value coming from the Hook sensor
int sensorValueN = 0; // variable to store the value coming from the North sensor
void setup()
{
// declare OUTPUTS:
pinMode(GledPin, OUTPUT);
pinMode(HornPin, OUTPUT);
}
void loop()
{
// read the value from the sensors:
sensorValueS = analogRead(sensorPinS);
sensorValueH = analogRead(sensorPinH);
sensorValueN = analogRead(sensorPinN);
// Decide if horn needs to sound North
if (sensorValueN < 200 && sensorValueH > 50)
{
digitalWrite(HornPin, HIGH);
digitalWrite(GledPin, LOW);
delay (1000);
}
else
{
digitalWrite(HornPin, LOW);
digitalWrite(GledPin, LOW);
// Decide if horn needs to sound South
if (sensorValueS < 200 && sensorValueH > 50)
{
digitalWrite(HornPin, HIGH);
digitalWrite(GledPin, LOW);
delay (1000);
}
else
{
digitalWrite(HornPin, LOW);
digitalWrite(GledPin, LOW);
// Indicate hook position
if (sensorValueH < 50)
{
digitalWrite (GledPin, HIGH);
delay (1000);
}
else
{
digitalWrite (GledPin, LOW);
}
}}}
The easiest way is to do several analogRead calls for the pin concerned, adding them all together. Then divide by the number of readings you took. if there are more than about 3 readings, write a for-loop to do them all.
Are you getting mostly good readings with occasional bad ones from the hook? If so then simply averaging will not work. In theory what you want is called a "Median Filter" to eliminate outliers from the generally good data. A true median filter takes a lot of computation though. In many cases you can get away with a much simpler "Olympic Average". The Olympic average throws out the highest and lowest and averages the rest. Here is Olympic average code I wrote for another C compiler:
for (ctr=10;ctr>0;ctr--) {
itemp=read_adc();
if (itemp<imin) imin=itemp;
if (itemp>imax) imax=itemp;
isum+=itemp;
delay_us(250);
}
//Now we have the sum of ten readings, and the max & min readings
isum-=imin;
isum-=imax; //subtract the min and max from the sum, now only 8 are left
itemp=isum/8; //compiler will automatically optimize this to a shift
return itemp;
}
If you want to average over time (hint: you do) while still running other operations at the same time (hint: you do) then you probably want to use a stateful filter.
You can build a filter in a few ways. Two ways that are simple include:
Declare an array of shorts, and shift this array over each time you read a new sample. Calculate the filter value each time you've done this.
Use a low-pass floating-point filter. This could be a single-pole filter with a single variable, or a bi-quad filter with four variables.
Example 1:
short values[7];
short mean = 0;
short filterInput(short inval) {
memmove(values, &values[1], sizeof(short)*6);
values[6] = inval;
short ret = 0;
for (int i = 0; i < 7; ++i) {
ret = ret + values[i];
}
return ret / 7;
}
so based on the example from above this is what I've got. Let me know if I'm on the right track. I'm assuming I will have to replace sensorValueH in my logic with mean. Is this correct?
void loop()
{
// read the value from the sensors
sensorValueS = analogRead(sensorPinS);
sensorValueH = analogRead(sensorPinH);
sensorValueN = analogRead(sensorPinN);
// avg the hook sensor reading
short values[7];
short mean = 0;
short filterInput(short sensorValueH); {
memmove(values, &values[1], sizeof(short)*6);
values[6] = sensorValueH;
short ret = 0;
for (int i = 0; i < 7; ++i) {
ret = ret + values[i];
}
ret / 7;
return;
}
// Decide if horn needs to sound North
if (sensorValueN < 200 && sensorValueH > 50)
{
digitalWrite(HornPin, HIGH);
digitalWrite(GledPin, LOW);
delay (1000);
}
else
{
digitalWrite(HornPin, LOW);
digitalWrite(GledPin, LOW);
That function declaration should not have a semicolon, and should also be outside your loop() function.
Similarly, the array of saved values should be defined outside the loop() function (before the filterInput() function) or it will lose all its values each time loop() is invoked.
I think this produces something like a one pole filter. This filter depends on the period that it is executed. If you wan an accurate time constant you need to introduce a delta t.
No matter how much you think you want to do it this way, you don't. You need to focus more energy on getting the periodic incorrect readings caused by the hook out of the way, then come back and look for a solution to the spurious reading problems.
SherpaDoug:
Are you getting mostly good readings with occasional bad ones from the hook? If so then simply averaging will not work. In theory what you want is called a "Median Filter" to eliminate outliers from the generally good data. A true median filter takes a lot of computation though. In many cases you can get away with a much simpler "Olympic Average". The Olympic average throws out the highest and lowest and averages the rest. Here is Olympic average code I wrote for another C compiler:
for (ctr=10;ctr>0;ctr--) {
itemp=read_adc();
if (itemp<imin) imin=itemp;
if (itemp>imax) imax=itemp;
isum+=itemp;
delay_us(250);
}
//Now we have the sum of ten readings, and the max & min readings
isum-=imin;
isum-=imax; //subtract the min and max from the sum, now only 8 are left
itemp=isum/8; //compiler will automatically optimize this to a shift
return itemp;
}
This is by far the best as well as simplest algorithm I found to eliminate spurious sensor measurements and return decent readings that approximate best as possible the correct values.
I use following code that includes this "Olympic average": take n readings, eliminate the min and max value and average over n-2 readings.
The code here reads an ultrasonic HC-SR04 sensor -far from the best sensor- and displays the values on an LCD in a bar graph format, 14 "blocks" on line two representing 0 to about 4 meters.
The idea comes from DIY interactive name tag | Dimitris Platis who uses a microphone to display sound levels; I converted it to a distance measurement device/interactive nametag.
#include <LiquidCrystal_I2C.h>
#define GPIO_ADDR 0x27
LiquidCrystal_I2C lcd(GPIO_ADDR, 16, 2); // set address & 16 chars / 2 lines
const int TRIG_PIN = 7;
const int ECHO_PIN = 8;
int count = 6; //number of repeat measurements including 2 outliers
const unsigned int MAX_DIST = 400; // Anything over 400 cm (23200 us pulse) is "out of range"
void setup() {
pinMode(ECHO_PIN, INPUT); // Arduino pin tied to echo pin on the ultrasonic sensor.
pinMode(TRIG_PIN, OUTPUT); // The Trigger pin will tell the sensor to range find
digitalWrite(TRIG_PIN, LOW);
Serial.begin(9600);
}
void loop() {
unsigned int sum = 0; // accumulated distance reset
unsigned int signalMax = 0;
unsigned int signalMin = 400;
unsigned long t1;
unsigned long t2;
unsigned long pulse_width;
float cm;
for (int i = 0; i < count; i++)
{
// Hold the trigger pin high for at least 10 us
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
while ( digitalRead(ECHO_PIN) == 0 );// Wait for pulse on echo pin
t1 = micros(); // Measure how long the echo pin was held high (pulse width)
while ( digitalRead(ECHO_PIN) == 1);
t2 = micros();
pulse_width = t2 - t1;
cm = pulse_width / 58.0; // Calculate distance in centimeters
if(cm<signalMin)signalMin=cm;
if(cm>signalMax)signalMax=cm;
sum+=cm;
delay (30);
}
sum-=signalMin;
sum-=signalMax;
sum = sum / (count-2);
printBars(cm/120);
// Print out results
if ( sum > MAX_DIST ) {
Serial.println("Out of range");
delay (20);
} else {
Serial.print(sum);
Serial.println(" cm \t");
// Serial.print(inches);
// Serial.println(" in");
}
}
void printBars(int bars) {
if (bars > 14) bars = 14;
for (int i = 0; i < 15; i++) {
lcd.setCursor(i, 1);
if (i <= bars) {
lcd.print((char)255);
} else {
lcd.print(" ");
}
}
}