averaging an analog sensor value

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:

int16 olympic_ten(void) {
int16 imax=0;
int16 imin=65535;
int16 isum=0;
int16 itemp;
int8 ctr;

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:

  1. 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.
  2. 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;
}

Example 2:

float svar = 0;
float filterInput(short invar) {
  svar = svar * 0.9f + invar * 0.1f;
  return svar;
}

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);

short filterInput(short sensorValueH); {

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.

The usual way to maintain an average of the last N values is along these lines:

const unsigned int numValues = 7;

uint16_t values[numValues];
uint8_t valIndex;
uint16_t sum;       // invariant: initialized => sum == + over values
bool initialized = false;

uint16_t filterInput(short sensorValue) 
{
  if (initialized)
  {
    sum -= values[valIndex];
    values[valIndex] = sensorValue;
    sum += sensorValue;
    ++valIndex;
    if (valIndex = numValues) 
    { 
      valIndex = 0; 
    }
  }
  else
  {
    for (uint8_t i = 0; i < numValues; ++i)
    {
      values[i] = sensorValue;
    }
    valIndex = 0;
    sum = sensorValue * numReadings;
    initialized = true;
  }
  return sum/numValues;
}

This saves you from having to shift all the elements along and add them all up every time you take a new reading.

Another option I've used in the past is integrating the values to minimize the error. Basically just sum them all up and change your threshold value.

Perhaps:

float ave;
float tc = .9; //a number between 0 and 1 - note if you need execution speed precompute 1-tc in setup.

void loop(void)
,,,
ave = (1-tc) * analogRead(pin) + ave * tc;
...

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.

Its a single pole, single zero IIR digital filter given by 0.1Z / (Z - 0.9) [ or (1-tc)Z / (Z - tc) in terms of tc ]

It has been 40 years since I did a Z transform. Even when I couild, it was a bit of magic.

Well there's an awful lot of maths topics explained on Wikipedia if you want to get back into it :wink:

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.

if it's just simply averaging an analog value. wouldn't it be a lot easier to just add a capacitor?

wrote some classes for these kind of problems:

to prevent spikes you can use a running median - Arduino Playground - RunningMedian -
Advantage is that spikes do not influence the middle value

if you just have noise a running average might be better - http://arduino.cc/playground/Main/RunningAverage -
If you have both -> combine them :slight_smile:

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:

int16 olympic_ten(void) {
int16 imax=0;
int16 imin=65535;
int16 isum=0;
int16 itemp;
int8 ctr;

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(" ");
    }
  }
}