Go Down

Topic: Smoothing improved (Read 352 times) previous topic - next topic

antoniodv

Hello,
I improved library "Smoothing" scketch. It remove abnormal and not signficative peaks.
I tested it with a (very bad) LM35 sensor.

Observe green line.

IMPROVED 




NORMAL




Code: [Select]

/*
  Smoothing_improved

  Reads repeatedly from an analog input, calculating a running average and
  printing it to the computer. Keeps ten readings in an array and continually
  averages them.

  The circuit:
  - analog sensor (potentiometer will do) attached to analog input 0

  created 22 Apr 2007
  by David A. Mellis  <dam@mellis.org>
  modified 9 Apr 2012
  by Tom Igoe
  improved (remove abnormal peak) 10 Dec 2019
  by Antonio De Vincentiis <https://devincentiis.it)

  This example code is improved of the the public domain.

  http://www.arduino.cc/en/Tutorial/Smoothing
*/

// Define the number of samples to keep track of. The higher the number, the
// more the readings will be smoothed, but the slower the output will respond to
// the input. Using a constant rather than a normal variable lets us use this
// value to determine the size of the readings array.
const int numReadings = 10;

float readings[numReadings];      // the readings from the analog input
int readIndex = 0;                // the index of the current reading
float total = 0;                  // the running total
float average = 0;                // the average
int continuativePeak = 0;         // countinuos peak sequence counter
int previousPeakIndex = 0;        // readIndex of last peak       
bool noSmoothing=false;           // active - deactive delete peak value
int inputPin = A0;

void setup() {
  // initialize serial communication with computer:
  Serial.begin(9600);
  // initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}

void loop() {
  // read from the sensor:
  float readValue=analogRead(inputPin)/5;
  // float readValue=analogRead(inputPin)/5; // example LM35 temperature sensor

  if (!(abs(readValue)/abs(readValue-average)<20&&noSmoothing)){ 
    // value accepted only if the difference with the average
    // is between 0 and 5% (<20) 

    // subtract the last reading:
    total -= readings[readIndex];
    readings[readIndex] = readValue;
    // add the reading to the total:
    total += readings[readIndex];
    // advance to the next position in the array:
    readIndex++;

    // if we're at the end of the array...
    if (readIndex >= numReadings) {
      // ...wrap around to the beginning:
      readIndex = 0;
      noSmoothing=true; // ... and add peak control
    }

    // calculate the average:
    average = total / numReadings;

  } else { // read an abnormal peak
    //Serial.println("Peak!!");
    // if it is a peak immediately following the counter increment
    if (previousPeakIndex==readIndex){
      continuativePeak++;
    }else{ // else reset peak counter
      continuativePeak=0;
    }
   
    // remove smoothing if more than half of the values exceed the average
    if (continuativePeak==(numReadings/2)){
      noSmoothing=false;
      continuativePeak=0;
    }
    previousPeakIndex=readIndex;
  }
  // send it to the plotter
  //Serial.print("Read value:");
  //Serial.print(readValue);
  //Serial.print(", average:");
  //Serial.println(average);
  delay(10);        // delay in between reads for stability
}




Wawa

#1
Dec 10, 2019, 10:48 am Last Edit: Dec 10, 2019, 10:53 am by Wawa
The returned A/D value depends on two things. Temp and Aref.
If you measure with the potentially dirty/unstable default Aref (5volt supply), then you never will get a stable readout.

Try measuring the LM35 with 1.1volt Aref enabled in setup.
That will also get you a five times higher resolution.
Also make sure you don't share LM35 ground with other (breadboard) users.
Example sketch attached.
Leo..
Code: [Select]
// connect LM35 to 5volt A0 and ground
// calibrate temp by changing the last digit(s) of "0.1039"

const byte tempPin = A0;
float calibration = 0.1039;
float tempC; // Celcius
float tempF; // Fahrenheit

void setup() {
  Serial.begin(9600);
  analogReference(INTERNAL); // use internal 1.1volt Aref
  // change INTERNAL to INTERNAL1V1 for a Mega
}

void loop() {
  tempC = analogRead(tempPin) * calibration; // get temp

  tempF = tempC * 1.8 + 32.0; // C to F
  Serial.print("Temperature is  ");
  Serial.print(tempC, 1); // one decimal place resolution is all you get
  Serial.print(" Celcius  ");
  Serial.print(tempF, 1);
  Serial.println(" Fahrenheit");

  delay(1000); // use a non-blocking delay when combined with other code
}

antoniodv

Thanks Wawa,
I used Vref = 1.1v and 5v directly on the LM35 with the share ground.

I updated the sketch because in my opinion there is another conceptual error when subtracting and adding the reading to total (lines 45 and 49), it could happen that when the microcontroller works for a long time, every possible calculation error is added up.
I preferred to recalculate the total, and therefore the average for each cycle.

Code: [Select]

/*
  Smoothing

  Reads repeatedly from an analog input, calculating a running average and
  printing it to the computer. Keeps ten readings in an array and continually
  averages them.

  The circuit:
  - analog sensor (potentiometer will do) attached to analog input 0

  created 22 Apr 2007
  by David A. Mellis  <dam@mellis.org>
  modified 9 Apr 2012
  by Tom Igoe
  improved (remove abnormal peak) 10 Dec 2019
  by Antonio De Vincentiis <https://devincentiis.it)

  This example code is improved of the the public domain.

  http://www.arduino.cc/en/Tutorial/Smoothing
*/

// Define the number of samples to keep track of. The higher the number, the
// more the readings will be smoothed, but the slower the output will respond to
// the input. Using a constant rather than a normal variable lets us use this
// value to determine the size of the readings array.
const int numReadings = 10;

int readValue =0;
int readings[numReadings];        // the readings from the analog input
int readIndex = 0;                // the index of the current reading
int total = 0;                    // the running total
float average = 0.01;             // the average
int continuativePeak = 0;         // countinuos peak sequence counter
int previousPeakIndex = 0;        // readIndex of last peak      
bool noSmoothing=false;           // active - deactive delete peak value
int inputPin = A0;

void setup() {
  // initialize serial communication with computer:
  Serial.begin(9600);
  // initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}

void loop() {
  // read from the sensor:
  readValue=analogRead(inputPin);

  // value accepted only if the difference with the average
  // is between 0 and 5% (<20)  
  if (!((abs(readValue)/abs(readValue-average))<20&&noSmoothing)){  
    readings[readIndex] = readValue;
    // advance to the next position in the array:
    readIndex++;

    // compute total from array content
    total=0;
    for (int thisReading = 0; thisReading < numReadings; thisReading++) {
      total += readings[thisReading];
    }
    // calculate the average:
    average = total / numReadings/1.01;

    // if we're at the end of the array...
    if (readIndex >= numReadings) {
      // ...wrap around to the beginning:
      readIndex = 0;
      noSmoothing=true; // ... and add peak control
    }

  } else { // read an abnormal peak
    Serial.println("Peak!!");
    // if it is a peak immediately following the counter increment
    if (previousPeakIndex==readIndex){
      continuativePeak++;
    }else{ // else reset peak counter
      continuativePeak=0;
    }
    
    // remove smoothing if more than half of the values exceed the average
    if (continuativePeak==(numReadings/2)){
      noSmoothing=false;
      continuativePeak=0;
    }
    previousPeakIndex=readIndex;
  }
  // send it to the plotter
  Serial.print("Total:");
  Serial.print(total);
  Serial.print("Read value:");
  Serial.print(readValue);
  Serial.print(", average:");
  Serial.println(average);
  delay(1);        // delay in between reads for stability
}


enjoyneering

But there are still some peaks in the IMPROVED chart. Should it be like that?

Wawa

#5
Dec 10, 2019, 08:20 pm Last Edit: Dec 10, 2019, 08:23 pm by Wawa
Thanks Wawa,
I used Vref = 1.1v and 5v directly on the LM35 with the share ground.
What does that mean.
I see nothing of that in your code, and I did say NOT share ground.

An LM35 should NOT have ANY peaks at all.
Post a picture of the setup.
Leo..

antoniodv

What does that mean.
I see nothing of that in your code, and I did say NOT share ground.

An LM35 should NOT have ANY peaks at all.
Post a picture of the setup.
Leo..
I have voluntarily used and created a hardware device as noisy as possible to analyze and improve the result through the code. The aim is to achieve a very resilient response to noise.
Greetings

P.S.
Not even the original example Smoothing.ino has Vref

antoniodv

But there are still some peaks in the IMPROVED chart. Should it be like that?
Yes.
I said "improved" not "flatted".
If you want peaks more flatted, then increase numReadings value.

Wawa

Not even the original example Smoothing.ino has Vref
The Arduino smoothing example uses a ratiometric source (a potentiometer).
Then you must use default Aref (which is internally connected to the same supply as the pot).
If both sensor and A/D are ratiometric, then supply fluctuations are cancelled out.

You told us you were testing an LM35, which has a 'voltage' output (not ratiometric).
Then it's a bad idea to use default Aref.
A stable LM35 and a potentially unstable/fluctuating supply results in unstable A/D values.

All of this has nothing to do with smoothing, just the correct way of measuring a 'sensor'.
The wrong way can introduce instablility. No amount of smoothing can fix that.
Leo..

Go Up