Smoothing analog temperature reading

I’m a newer arduino user and I’m probably trying to walk before I learn to fully crawl…but its still an extension to my ‘crawling’ lesson.

I made a TMP36 temperature sensor as described in the basic tutorials but I noticed that it would occasionally get a noisy signal and read +/- 1 degreeF (most of the noise is consistently reading +/- exactly 0.7 degreeF). I wanted to take my learning the next step and try to learn how to smooth the readings because I would eventually like to graph the data without having to manually remove ‘outliers’ before plotting them. Looking online, I found some smoothing examples but I still seem to get similar results whether or not I run the ‘smoothed’ or just normal ‘read the sensor and print temp’ sketch. This last run that I did with the smoothed sketch (below) had noise that was exactly +/- 0.5 degreeF (see attached excel plot). Is there any way to get rid of that noise though code? Or, is there a mistake in my sketch that might actually be ‘smoothing’ the sensor readings? Again, I’m still not extremely well versed in the language so any helpful steps/explanations are welcome!

/*

  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 April 2007
  By David A. Mellis  <dam@mellis.org>
  modified 9 Apr 2012
  by Tom Igoe
  http://www.arduino.cc/en/Tutorial/Smoothing

  This example code is in the public domain.


*/


// 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
// use this value to determine the size of the readings array.
const int numReadings = 20;

int readings[numReadings];      // the readings from the analog input
int readIndex = 0;              // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average

int inputPin = A0;

float degreesC, degreesF; //added to tutorial

void setup() {
  Serial.begin(9600);
  analogReference(EXTERNAL);    //Set AREF to external since using 3.3v: Added to tutorial
  // initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}

void loop() {
  total = total - readings[readIndex];   // subtract the last reading:
  readings[readIndex] = analogRead(inputPin);  // read from the sensor:
  total = total + readings[readIndex];   // add the reading to the total:
  readIndex = readIndex + 1;   // advance to the next position in the array:

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

  average = total / numReadings;   // calculate the average (of sensor readings)
  delay(1);

  float avgVoltage = average * 0.00322265625;  //Added to tutorial
  degreesC = (avgVoltage - 0.5) * 100.0; //Added to tutorial
  degreesF = degreesC * (9.0/5.0) + 32.0; //Added to tutorial

  Serial.print("DegreesC: ");  //Added to tutorial
  Serial.print(degreesC);  //Added to tutorial
  Serial.print("DegreesF: "); //Added to tutorial
  Serial.println(degreesF);  //Added to tutorial
  delay(5000); 
}

Untitled.jpg

Temperature_3.3V_smoothingTutorial.ino (3.19 KB)

You've gone and used integer (truncating) division and lost most of the benefit of averaging!

Change this line:

  average = total / numReadings;   // calculate the average (of sensor readings)

to:

  float average = 1.0 * total / numReadings;   // calculate the average (of sensor readings)

And lose the declaration of average as an int at top level.

In general its usually much clearer to declare variables when you first use them, not all
at the top of the program or function. Only make a variable global or static if its value
has to persist between function calls.

One thing you might try is reading the input twice in a row and use the second reading. Some times the analog input is not quite stabilized before the initial reading is made.

Thanks Mark. I'm running that change (on 1000ms delay for now) to see if it helps. Definitely makes sense that it should not have been truncated to an integer though.

Zoomkat,
I'm not quite sure what you mean with "read it twice and use the second reading." Are you suggesting something like this?

total = total - readings[readIndex]2;
readings[readIndex] = analogRead(inputPin);
readings[read Index]2 = analogRead(inputPin);

you might also try taking more data over the 5 second interval…

Also, since you have an array, you can pass it to a smoothe() function by reference and get back a lot of the SRAM you used up with globals!

const int numReadings = 200;  
const int inputPin = A0;

int readings[numReadings] = {0};  // initialize all the readings to 0:
int readIndex = 0;
unsigned long lastMillis = 0;

void setup() 
{
  Serial.begin(9600);
  analogReference(EXTERNAL);    //Set AREF to external since using 3.3v: Added to tutorial
}

void loop() 
{
  readings[readIndex] = analogRead(inputPin);  // read from the sensor:
  readIndex++;   // advance to the next position in the array:
  if (readIndex >= numReadings) 
  {
    readIndex = 0;    // ...wrap around to the beginning:
  }
  delay(25);  //let us collect 200 readings over 5 seconds. edit: fixed delay interval
  
  if (millis() - lastMillis > 5000UL)
  {
    float smoothedValue =  smoothe(readings, numReadings);
    Serial.print("Smoothed Value: ");  //Added to tutorial
    Serial.println(smoothedValue);  //Added to tutorial
    float avgVoltage = smoothedValue * 0.00322265625;  //Added to tutorial
    float degreesC = (avgVoltage - 0.5) * 100.0; //Added to tutorial
    float degreesF = degreesC * (9.0 / 5.0) + 32.0; //Added to tutorial
    Serial.print("DegreesC: ");  //Added to tutorial
    Serial.print(degreesC);  //Added to tutorial
    Serial.print("DegreesF: "); //Added to tutorial
    Serial.println(degreesF);  //Added to tutorial
    lastMillis = millis();
  }
}

float smoothe(const int *myArray, const size_t size)
{
  int total = 0;
  for(int i = 0; i < size; i++)
  {
    total+= myArray[i];
  }
  return (float)total/(float)size;
}
//Sketch uses 4,410 bytes (14%) of program storage space. Maximum is 30,720 bytes.

//yours used:

//Sketch uses 4,412 bytes (14%) of program storage space. Maximum is 30,720 bytes.

Thanks for the replies! Changing ‘average’ from integer to float seems to have fixed the issue.

Bulldog, I am going to do some research on parts within your suggestion and try to improve my coding language/skills. Your suggestion seems to be another ‘step’ into learning more detailed language and precise coding methods. As with the stepping though the ‘basic’ tutorials, knowing what outcomes I should expect make it easier to pick up on language application in my opinion. Thanks for taking the time to improve the design of my sketch using some other steps!

I'm not quite sure what you mean with "read it twice and use the second reading." Are you suggesting something like this?

I didn't write that code so I can't speak to what would do. looping "for" code is sometimes difficult to tweak and trouble shoot. With simple inline code you can simply make two reads like below and the second one is used.

newvalS1 = analogRead(potpinS1);  
newvalS1 = analogRead(potpinS1);           
newvalS1 = map(newvalS1, 0, 1023, 0, 179);