filtering analog data

Post here about your Arduino projects, get help - for Adafruit customers!

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
keithg
 
Posts: 82
Joined: Thu Oct 30, 2008 8:30 pm

filtering analog data

Post by keithg »

The LED's will change state when the analog output is within six or seven numbers of the threshold, making accurate transitions impossible. The colors oscillate back and forth. There must be a better way! Would appreciate your thoughts.

Code: Select all

unsigned long starTime;
int flashRed = 20000;
 
 const int numReadings = 3;
 int readings[numReadings];      // the readings from the analog input
 int index = 0;                  // the index of the current reading
 int total = 0;                  // the running total
 int average = 0;

#define blueLED 11
#define redLED 12
#define greenLED 13
#define AnIn 1
#define redThresh 670
#define blueThresh 660
#define greenThresh 0

int val;


void setup()
{
  for (int thisReading = 0; thisReading < numReadings; thisReading++)
  readings[thisReading] = 0; 

  starTime = millis();  //timer
   
  pinMode(redLED,OUTPUT); 
  pinMode(blueLED,OUTPUT); 
  pinMode(greenLED,OUTPUT);
 

Serial.begin(9600);
}
void loop()
{
  // subtract the last reading:
   total= total - readings[index];         
   // read from the sensor:  
   readings[index] = analogRead(AnIn); 
   // add the reading to the total:
   total= total + readings[index];       
   // advance to the next position in the array:  
   index = index + 1;                    
   // if we're at the end of the array...
   if (index >= numReadings)              
     // ...wrap around to the beginning: 
     index = 0;                           
   // calculate the average:
   average = total / numReadings;
  
    starTime = millis();  //timer

  //val = analogRead(AnIn);
 // val = val/2;
if(average >= redThresh){
 digitalWrite(redLED,HIGH);
 digitalWrite(blueLED,LOW);
 digitalWrite(greenLED,LOW);
 delay(1000);
}
else {
  digitalWrite(redLED,LOW);

if((average>= blueThresh) && (average <= redThresh))
{
 digitalWrite(blueLED,HIGH);
 digitalWrite(redLED,LOW);
 digitalWrite(greenLED,LOW);
 delay(1000);
}
else {
  digitalWrite(blueLED,LOW);
  
  if((average>= greenThresh) && (average <= blueThresh))
  {
 digitalWrite(greenLED,HIGH);
 digitalWrite(blueLED,LOW);
 digitalWrite(redLED,LOW);
 delay(1000);
  }
else {
  digitalWrite(greenLED,LOW);
}
} 
Serial.println(average);
}

}



  

User avatar
adafruit_support_bill
 
Posts: 88092
Joined: Sat Feb 07, 2009 10:11 am

Re: filtering analog data

Post by adafruit_support_bill »

Analog inputs on the Arduino are pretty noisy. I usually shift off the 2 low order bits as it is generally all noise. In this case, you may need to do three bits.

keithg
 
Posts: 82
Joined: Thu Oct 30, 2008 8:30 pm

Re: filtering analog data

Post by keithg »

Sorry, I have no idea how to shift off lower order bits but I certainly need to learn. Would you please write a snippet that illustrates the procedure? Thanks.

User avatar
adafruit_support_bill
 
Posts: 88092
Joined: Sat Feb 07, 2009 10:11 am

Re: filtering analog data

Post by adafruit_support_bill »

To shift off the lowest three bits you can simply divide by 8.

Or you can use the C shift operator: '>>'

as in:

Code: Select all

value = value >> 3;

keithg
 
Posts: 82
Joined: Thu Oct 30, 2008 8:30 pm

Re: filtering analog data

Post by keithg »

That is more simple than expected. Thanks. I have tried to plug this filter into the sketch but the results are quacky; the red flashes, the blue stay on, and the green is never high. I have rewritten this sketch a bunch of different ways but the LED's will not cooperate. Analog readings from 640~680

Code: Select all

#define FILTER_SHIFT 3

#define redLED 13
#define blueLED 12
#define greenLED 11
#define redThresh 680
#define blueThresh 678
#define greenThresh 0

int32_t filter_reg;
int16_t filter_input;
int16_t filter_output;
void setup(){
  pinMode(redLED,OUTPUT);
  pinMode(blueLED,OUTPUT);
  pinMode(greenLED,OUTPUT); 
  Serial.begin(9600);
 
}
void loop(){

filter_input = analogRead(1);		     // read ADC

// low pass filter to get rid of noise
filter_reg = filter_reg - (filter_reg >> FILTER_SHIFT) + filter_input;
filter_output = filter_reg >> FILTER_SHIFT;
delay(100);

if(filter_output >= redThresh) 
{
digitalWrite(redLED, HIGH);
digitalWrite(blueLED,LOW);
digitalWrite(greenLED, LOW);
delay(100);

}
else{
digitalWrite(redLED, LOW);

}

if((filter_output < redThresh) && (filter_output > greenThresh)) 
{
digitalWrite(blueLED, HIGH);
digitalWrite(redLED, LOW);
digitalWrite(greenLED, HIGH);
delay(100);

}
else{
  digitalWrite(blueLED, LOW);
  

}
if((filter_output >= greenThresh) && (filter_output <= blueThresh)) 
{
digitalWrite(greenLED, HIGH);
digitalWrite(redLED, LOW);
digitalWrite(blueLED, LOW);
delay(100);

}
else{
 digitalWrite(greenLED, LOW); 
}

Serial.println(filter_output);

}
  






User avatar
adafruit_support_bill
 
Posts: 88092
Joined: Sat Feb 07, 2009 10:11 am

Re: filtering analog data

Post by adafruit_support_bill »

You want to be doing the shift on the analog reading. This is not really a low-pass filter. This is just throwing away random bits.

Of course, after the shift the resulting value is divided by 8, so you will need to scale your thresholds accordingly.

Adding a time-domain filter is not a bad idea, but I think your first implementation was closer to what you want. Something like:

Code: Select all

 // subtract the last reading:
   total= total - readings[index];         
   // read from the sensor:  
   readings[index] = analogRead(AnIn) >> FILTER_SHIFT; 
   // add the reading to the total:
   total= total + readings[index];       
   // advance to the next position in the array:  
   index = index + 1;                    
   // if we're at the end of the array...
   if (index >= numReadings)              

...
You might consider printing the inputs & outputs of the filter to aid in debugging.

User avatar
zener
 
Posts: 4567
Joined: Sat Feb 21, 2009 2:38 am

Re: filtering analog data

Post by zener »

You might post an algorithym or flow chart.

keithg
 
Posts: 82
Joined: Thu Oct 30, 2008 8:30 pm

Re: filtering analog data

Post by keithg »

Seems that no matter which way the sketch is tweaked, the odd ball reading sneaks in and makes the lights change state even when their is nothing but silence. I've seen somewhere a procedure that compares new data with old and if there is not close to the current figure, nothing happens. After an exhausting search, I can't find it. Would this eliminate the problem? A sample of the analog input is listed below the code.

Code: Select all

#define FILTER_SHIFT 1 
int val;

 
 int ledRed =12;
 int ledYellow = 11;
 int ledGreen = 13;
 
 unsigned long starTime;
int flashRed = 20000;
 
 
 const int numReadings = 5;
 int readings[numReadings];      // the readings from the analog input
 int index = 0;                  // the index of the current reading
 int total = 0;                  // the running total
 int average = 0;                // the average
 int inputPin = 1;
 void setup()
 {
  val = 0; 
  pinMode(ledRed,OUTPUT);
  pinMode(ledYellow,OUTPUT);
  pinMode(ledGreen,OUTPUT);
  pinMode(inputPin, INPUT);
   
   // initialize serial communication with computer:
   Serial.begin(9600);                   
   // initialize all the readings to 0: 
   for (int thisReading = 0; thisReading < numReadings; thisReading++)
     readings[thisReading] = 0; 

   starTime = millis();  //timer     
 }
 void loop() {
   // subtract the last reading:
   total= total - readings[index];         
   // read from the sensor:  
   readings[index] = analogRead(inputPin) >> FILTER_SHIFT ; 
   // add the reading to the total:
   total= total + readings[index];       
   // advance to the next position in the array:  
   index = index + 1;                    
   // if we're at the end of the array...
   if (index >= numReadings)              
     // ...wrap around to the beginning: 
     index = 0;                           
   // calculate the average:
   average = total / numReadings;
   delay(100);
  
    starTime = millis();  //timer

 // if  ((average >200) && (average < 300)) {  // code here will execute if average > 100
 
    if (average > 342) {
    digitalWrite(ledRed,HIGH);
    digitalWrite(ledYellow,LOW);
    digitalWrite(ledGreen,LOW);
    delay(1000);
    val= val +1;
   
    if((val >8) && (starTime> flashRed )) {
     
      
      digitalWrite(ledRed,HIGH);
      delay(1000);
      digitalWrite(ledRed,LOW);
      delay(1000);
      digitalWrite(ledRed,HIGH);
      delay(1000);
      digitalWrite(ledRed,LOW);
      delay(1000);
      digitalWrite(ledRed,HIGH);
      delay(1000);
      digitalWrite(ledRed,LOW);
      delay(1000);
      digitalWrite(ledRed,HIGH);
      delay(1000);
      digitalWrite(ledRed,LOW);
      delay(1000);
      if (val>8){
        val = 0;
    }
    }    
    
  }
  else if ((average > 332) && (average < 341)) {  // code here will execute if average <= 100 and average > 80
    
    
    digitalWrite(ledRed,LOW);
    digitalWrite(ledYellow,HIGH);
    digitalWrite(ledGreen,LOW);
    delay(1000);
  }
  else {  // code here will execute if average <= 80
  
    digitalWrite(ledRed,LOW);
    digitalWrite(ledYellow,LOW);
    digitalWrite(ledGreen,HIGH);
    delay (200);
  }
  
// Serial.println(average, DEC);
 Serial.println(average, DEC);
 //val = 0; 
 
 }
 

   
   
   
   
   
   


  
332

332

332

333

333

328

339

336

339

338

337

337

331

338

333

340

338

338

339

339

339

339

338

336

332

335

328

338

336

335

333

331

332

326

335

334

333

332

User avatar
zener
 
Posts: 4567
Joined: Sat Feb 21, 2009 2:38 am

Re: filtering analog data

Post by zener »

The advice I have been given before is "Post the absolute smallest piece of code that exhibits the problem."

As for analog filtering, the top two methods used are:

Hardware RC filter
Running average (also known by another name I cannot think of...)

User avatar
ahdavidson
 
Posts: 131
Joined: Wed Jun 03, 2009 9:59 am

Re: filtering analog data

Post by ahdavidson »

Running average (also known by another name I cannot think of...)
Isn't that one of the simplest of low-pass filters?

keithg
 
Posts: 82
Joined: Thu Oct 30, 2008 8:30 pm

Re: filtering analog data

Post by keithg »

Is this the running average?

input = analogRead(1);
adcResult = adcResult * 0.95 + analogRead(1) * .05;

User avatar
zener
 
Posts: 4567
Joined: Sat Feb 21, 2009 2:38 am

Re: filtering analog data

Post by zener »

An easier way is you have a counter and an analog-read total. Every time you take an analog read you add it to your total and increment the counter. At some point you divide the total by the counter and there is your average. Then you start over. That is a simple way that works for many applications.
Last edited by zener on Mon Nov 09, 2009 4:03 pm, edited 1 time in total.

User avatar
adafruit_support_bill
 
Posts: 88092
Joined: Sat Feb 07, 2009 10:11 am

Re: filtering analog data

Post by adafruit_support_bill »

Your analog values range from 326 to 340 (a range of 14). If that represents "silence" you have about 4 bits of noise.

What is the application?

keithg
 
Posts: 82
Joined: Thu Oct 30, 2008 8:30 pm

Re: filtering analog data

Post by keithg »

We want a noise monitor for our cafeteria, which is near riot every lunch hour. Similar commercial units are about $70 but we need one for each classroom if I can persuade the circuit/sketch to turn red, blue, and green LED's on when the noise exceeds their relative threshold.

User avatar
adafruit_support_bill
 
Posts: 88092
Joined: Sat Feb 07, 2009 10:11 am

Re: filtering analog data

Post by adafruit_support_bill »

Before going much further, your original code shows a difference of 10 between the blue and red thresholds. But your sample output shows a range of 14 for a steady-state input. I think you will have some trouble separating the signal from the noise here. You may want to re-evaluate your sensor.

On the processing side, your application can tolerate a fairly aggressive low-pass filter. A running average of 20 (or more!) samples over several seconds should smooth things out a bit. But you should also consider adding some hysteresis to prevent flickering around the threshold levels. For example, once the device is in the "red" state, the raw input level should have to go several counts below the red threshold before changing state to "blue".

Locked
Please be positive and constructive with your questions and comments.

Return to “Arduino”