Smoothing out analog read value

I have a code in which I am trying to use a 10K thermistor as a temperature sensor (ATMEGA 168). I use external 3.3V regulated Analog Reference and You can see the rest in the code. The problem is, after I get my temperature as a float, I truncate it into an integer to display on my two-digit seven-segment display. And sometimes it fluctuates a lot between, say, 24 and 25 *C. I want this to be removed somehow, alter the digits after the decimal because I need only a solid integer. I tried taking more readings and averaging them out, but this slows my sketch down and the multiplexing of the six-digit seven-segment display I have attached does not work anymore and we see flickering. I hope you can steer me back on track :slight_smile:

The code:

/* Final kernel for my clock. Author: Kiril Golov (c) 2014
  The circuit uses a RTC - the DS1307; a 10K thermistor to sense ambient temperature; 
  as well as a six-digit seven-segment-display. It alternates showing the time and then the
  temperature. Segments (A - G) are wired through a NPN transistor into Digital Pins 2-9 
  and the common anodes (6) are wired through a PNP transistor into Digital Pins 10, 11, 12, 13, 15 and 16.
  Thermistor is wired into A0, and RTC is wired into A4 and A5. */
// -------- START ---------

// libraries includes - I2C communication(Wire) + RTC library
#include <Wire.h>
#include "RTClib.h"

// declare RTC
RTC_DS1307 RTC;

// declare some constants and variables for the segments and their pins
const int segmentPins[] = {2, 3, 4, 5, 6, 7, 8, 9};
const int displayPins[] = {10, 11, 12, 13, A1, A2};
const int secondled = A3;
byte digits[10][8] = {
  // a b c d e f g .
  { 1, 1, 1, 1, 1, 1, 0, 0}, // 0
  { 0, 1, 1, 0, 0, 0, 0, 0}, // 1
  { 1, 1, 0, 1, 1, 0, 1, 0}, // 2
  { 1, 1, 1, 1, 0, 0, 1, 0}, // 3
  { 0, 1, 1, 0, 0, 1, 1, 0}, // 4
  { 1, 0, 1, 1, 0, 1, 1, 0}, // 5
  { 1, 0, 1, 1, 1, 1, 1, 0}, // 6
  { 1, 1, 1, 0, 0, 0, 0, 0}, // 7
  { 1, 1, 1, 1, 1, 1, 1, 0}, // 8 
  { 1, 1, 1, 1, 0, 1, 1, 0}  // 9 
};
byte digitsDot[10][8] = {
  // a b c d e f g .
  { 1, 1, 1, 1, 1, 1, 0, 1}, // 0
  { 0, 1, 1, 0, 0, 0, 0, 1}, // 1
  { 1, 1, 0, 1, 1, 0, 1, 1}, // 2
  { 1, 1, 1, 1, 0, 0, 1, 1}, // 3
  { 0, 1, 1, 0, 0, 1, 1, 1}, // 4
  { 1, 0, 1, 1, 0, 1, 1, 1}, // 5
  { 1, 0, 1, 1, 1, 1, 1, 1}, // 6
  { 1, 1, 1, 0, 0, 0, 0, 1}, // 7
  { 1, 1, 1, 1, 1, 1, 1, 1}, // 8 
  { 1, 1, 1, 1, 0, 1, 1, 1}  // 9
};
byte digitOth[2][8] = {
  { 0, 0, 0, 0, 0, 0, 0, 0}, // all off
  { 1, 0, 0, 1, 1, 1, 0, 0}  // C
};

// declare variables for the time and temperature readings storage
int hours;
int hourd[2];
int minutes;
int minuted[2];
int date;
int dated[2];
int months;
int monthd[2];
int ledState = LOW;
long previousMillis = 0;
long interval = 500;
int degree;
int degreed[2];

// define constants for the thermistor
#define THERMISTORPIN A0
#define THERMISTORNOMINAL 10000
#define TEMPERATURENOMINAL 25
#define NUMSAMPLES 3
#define BCOEFFICIENT 3950
#define SERIESRESISTOR 10000
int samples[NUMSAMPLES];


// -------- SETUP ---------
void setup () {
  // RTC and I2C initialise
  Wire.begin();
  RTC.begin();  
  // set RTC if not already set
  if (!RTC.isrunning()){
    //RTC.adjust(DateTime(__DATE__, __TIME__));
  }
  
  // Thermistor setup
  analogReference(EXTERNAL);
  
  // Segments setup
  for (int i=0; i < 8; i++) {
    pinMode(segmentPins[i], OUTPUT);
  }
  for (int i=0; i < 6; i++) {
    pinMode(displayPins[i], OUTPUT);
  }
  pinMode(secondled, OUTPUT);
  digitalWrite(secondled, LOW);
}


// --------- LOOP ---------
void loop () {
  // get time from RTC
  DateTime now = RTC.now();
    unsigned long currentMillis = millis();
    
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;
      if (ledState == LOW) {
        ledState = HIGH;
      } else {
        ledState = LOW;
      }
      digitalWrite(secondled, ledState);
    }
    
    hours = now.hour();
    minutes = now.minute();
    
    if (hours < 10) {
      hourd[0] = 0;
      hourd[1] = hours;
    } else {
      hourd[1] = hours % 10;
      hourd[0] = (hours - hourd[1]) / 10;
    }
    if (minutes < 10) {
      minuted[0] = 0;
      minuted[1] = minutes;
    } else {
      minuted[1] = minutes % 10;
      minuted[0] = (minutes - minuted[1]) / 10;
    }
    
    getTemperature(degreed);
    updateDisplayTime(hourd[0], hourd[1], minuted[0], minuted[1], degreed[0], degreed[1]);
}

void updateDisplayTime(int value1, int value2, int value3, int value4, int value5, int value6) {
  digitalWrite(displayPins[0], 0);
  digitalWrite(displayPins[1], 1);
  digitalWrite(displayPins[2], 1);
  digitalWrite(displayPins[3], 1);
  digitalWrite(displayPins[4], 1);
  digitalWrite(displayPins[5], 1);
  setSegments(value1);
  delay(3);
  digitalWrite(displayPins[0], 1);
  digitalWrite(displayPins[1], 0);
  digitalWrite(displayPins[2], 1);
  digitalWrite(displayPins[3], 1);
  digitalWrite(displayPins[4], 1);
  digitalWrite(displayPins[5], 1);
  setSegments(value2);
  delay(3);
  digitalWrite(displayPins[0], 1);
  digitalWrite(displayPins[1], 1);
  digitalWrite(displayPins[2], 0);
  digitalWrite(displayPins[3], 1);
  digitalWrite(displayPins[4], 1);
  digitalWrite(displayPins[5], 1);
  setSegments(value3);
  delay(3);
  digitalWrite(displayPins[0], 1);
  digitalWrite(displayPins[1], 1);
  digitalWrite(displayPins[2], 1);
  digitalWrite(displayPins[3], 0);
  digitalWrite(displayPins[4], 1);
  digitalWrite(displayPins[5], 1);
  setSegments(value4);
  delay(3);
  digitalWrite(displayPins[0], 1);
  digitalWrite(displayPins[1], 1);
  digitalWrite(displayPins[2], 1);
  digitalWrite(displayPins[3], 1);
  digitalWrite(displayPins[4], 0);
  digitalWrite(displayPins[5], 1);
  setSegments(value5);
  delay(3);
  digitalWrite(displayPins[0], 1);
  digitalWrite(displayPins[1], 1);
  digitalWrite(displayPins[2], 1);
  digitalWrite(displayPins[3], 1);
  digitalWrite(displayPins[4], 1);
  digitalWrite(displayPins[5], 0);
  setSegments(value6);
  delay(3);
}

void setSegments(int n){
  for (int i=0; i < 8; i++){
    digitalWrite(segmentPins[i], digits[n][i]);
  }
}

void setSegmentsDot(int n){
  for (int i=0; i < 8; i++) {
    digitalWrite(segmentPins[i], digitsDot[n][i]);
  }
}

void getTemperature(int* temp) {
  uint8_t i;
  float average;
  for (i=0; i< NUMSAMPLES; i++) {
     samples[i] = analogRead(THERMISTORPIN);
     delayMicroseconds(200);
  }
  average = 0;
  for (i=0; i< NUMSAMPLES; i++) {
    average += samples[i];
  }
  average /= NUMSAMPLES;
  average = 1023 / average - 1;
  average = SERIESRESISTOR / average; 
  float steinhart;
  steinhart = average / THERMISTORNOMINAL;
  steinhart = log(steinhart);
  steinhart /= BCOEFFICIENT;
  steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15);
  steinhart = 1.0 / steinhart;
  steinhart -= 273.15;
  
  int degreesz = (int) steinhart;
  if (degreesz < 0) {
    degreesz = 0;
  }
  if (degreesz > 99) {
    degreesz = 99;
  }
  if (degreesz < 0) {
    temp[0] = 0;
    temp[1] = degreesz;
  } else {
    temp[1] = degreesz % 10;
    temp[0] = (degreesz - temp[1]) / 10;
  }    
}
void getTemperature(int* temp) {
  uint8_t i;
  float average;
  for (i=0; i< NUMSAMPLES; i++) {
     samples[i] = analogRead(THERMISTORPIN);
     delayMicroseconds(200);
  }
  average = 0;
  for (i=0; i< NUMSAMPLES; i++) {
    average += samples[i];
  }

Why the array?
Unless you want to do a rolling average (hint) just sum them as you read them.
(BTW analogRead itself takes about 100us, so the delay is pretty redundant)

Well, I actually took the code from an example sketch of using a thermistor and just combined it with my personal code for the digital clock. However, I cannot find the original sketch now ]:slight_smile: I tweaked it a fair amount, especially for the samples took and the delay. Yes, I know that that delay of 200us is pretty ridiculous, but I guess you can help?

And yes, I used the array because of the ready code but I can tweak it. The question is that it works for now and I want to find the other problem before optimizing this code.

Two simple ways to do this without slowing things down:

  1. Keep a global running total, initialised to zero. Take a sample each main loop. Take the oldest sample in the array and subtract it from the running total and add in the new sample. Store the latest sample in the oldest sample's place. Update array pointer.

  2. Keep a global running total, initialised to zero. Take a sample each main loop. Multiply the running total by 7/8ths and add 1/8th of the new sample to the running total.

Thank you very much. I am already tweaking the code with the second option, as well as optimizing it a little. Thanks again :slight_smile:

  1. Your not going to get rid of the swapping between 24 and 25 that's just the way rounding (of any kind ) works.

2 there is no need to take all your analog readings in one go you could do a few each time round loop, that will stop the interference with your display.

Mark

Yes, thanks for your reply. I already understood about how to take the readings.

But, I really want to find a solution to this. After all, this microcontroller has a "brain", there has to be a way to make it work. And yes, the rounding is fiddly, because no matter of it, when you get to a certain threshold you start fluctuating. Whether that is around .90-.10 or .40-.60 and so on.

I was thinking of this: take an averaged reading every 30 seconds. Then, display the number for these 30 seconds before getting a new reading and changing it. This way any change in the number will be at least 30 seconds (or less, depends on the code) and there will be no fluctuating.

Yes, thanks for your reply. I already understood about how to take the readings.

Ha no, I mean take if you want the average of 100 readings then do 5 per loop() when you have a 100 do your calculation.

As for the "fluctuating" I don't know of a way out.

Mark

This function I've created is just one line of code that can emulate a type of averaging
of anything from 1 sample to infinite samples (using a transfer function with response constant).
No arrays required, fast performance, proportionally filters small and large changes
in readings. Spreadsheet attached to test different response and reading values.

Closed-loop transfer function

Pseudo code...

float response = 0.3;              // 0-1 range, 0 = no response, 1 = no filtering
float nresponse = 1 - response;
float tavg;                       //transfer function average
float ptavg;                      //previous transfer function average

void getTemperature(int temp) {
  tavg = (ptavg * nresponse) + (analogRead(THERMISTORPIN) * response);
  ptavg = tavg; 
  delayMicroseconds(200);
}

Note: If response is set to 0.5, then
each 1.0 deg change in temp reading will change tavg by 0.5 deg
each 0.3 deg change in temp reading will change tavg by 0.15 deg

Note: If response is set to 0.3, then
each 1.0 deg change in temp reading from tavg will change tavg by 0.3 deg
each 0.3 deg change in temp reading from tavg will change tavg by 0.1 deg

Note: If response is set to 0.1, then
each 1.0 deg change in temp reading from tavg will change tavg by 0.1 deg
each 0.3 deg change in temp reading from tavg will change tavg by 0.03 deg

Note: If response is set to 0.05, then
each 1.0 deg change in temp reading from tavg will change tavg by 0.05 deg
each 0.3 deg change in temp reading from tavg will change tavg by 0.015 deg

Response.xls (19 KB)

couple of things. first, temperature does not typically change very fast, look for some interference with the A/I,
what are you doing with aref ?

the simple answer is to use a floating decimal in the display, not an integer

another possible solution is to average the average, then compare to the last displayed value and only change if the average of the average is moving too far from the reading.

consider pulse without delay, read the value every 5 seconds.
with 5 registers.
move the data from r4 into r5, from r3 into r4, from r2 into r3, from r1 into r1 and from the reading into r1
average r1 thru r5

that will give you the first rolling average.

then data from rl4 into rl5, from rl3 into rl4, from rl2 into rl3, from rl1 into rl2 and then put the first rolling average into rl1
average that.

if your reading is changing much when you integrate 5 readings, look to your sensor, or input.

also, you can control by the first rolling values, and display only the very slow rolling average.

another way to look at the output is more like a control loop.
you have the very slow reading.
use integral on the readings. do not change your displayed value into there is a half degree change in that integral.