Averaging a process variable for a readout LCD

Hey all I have a arduino acting like a PID heater controller.

I also have a 2x16 LCD screen hooked up... the first line displays the "setpoint" and the second line displays the current temp or "process variable".

All is working fine except the sample rate of the PID script is causing the numbers on the second line to be all scrambled like.

What I am wondering is how can I write a little piece of code that takes the "process variable"(current temp) and averages it out say displaying the current temp every 2 seconds instead of every 200 milliseconds like the sample rate is at.

And FYI the sample rate must be fast like that so I can't change that.

Here's what the part of the code that I am talking about looks like:


Input = analogRead(0); myPID.Compute(); analogWrite(10,Output); //and output to LCD lcd.setCursor(0,0); //if heater is on - show * //if not - empty if( PID_ON ==1 ) { lcd.print("*"); } else { lcd.print(" "); }; lcd.print("--SET--: "); lcd.print((int)(Setpoint) ); lcd.setCursor( 0,1); lcd.print(" Current: "); lcd.print( Input );


I have to write some sort of script that deals with and averages that last line "Input"

I have no clue what to do :(

Is there any advice you recommend or can you point me into a better direction?

Thanks in advance!

How do you normally calculate an average? Add up a bunch of samples and divide by the number of samples.

I know the math part. I just don't know how to write a code that can take 4 samples over 2 seconds and then printout that average.

Lets say that your loop function takes 1mS to execute, well you can keep a counter variable that increments every time and when it gets to 2000, it updates the display and resets the counter variable to 0. That way you only update the display every 2 seconds.

If you actually need to smooth the readings, use Leaky Integration in addition like so:

Adjust alpha for the level of smoothing.

#define alpha 0.8

void loop()
{
 static double lastValue = 0;
 double newValue = readMyValue();
 double smoothValue = alpha * lastValue + (1 - alpha) * newValue;  // this is the key line of code
 lastValue = newValue;
}

Ahh... Thanks for the pointers Si.

So I add the :

define alpha 0.8

before my "loop" set of code?

and then change the sample you posted to something like this?

void loop() { static double lastValue = 0; double newValue = readInput(); double smoothValue = alpha * lastValue + (1 - alpha) * newValue; // this is the key line of code lastValue = newValue; }

I changed "readMyValue" to "readInput". ? hope thats right.

I left it like you posted and added it in right before the variable I want to smooth and when I try and compile I get the following error:

code_beta.cpp: In function 'void loop()': code_beta:113: error: 'readMyValue' was not declared in this scope

I left it like you posted and added it in right before the variable I want to smooth and when I try and compile I get the following error

You need to post all of your code. Otherwise, we are just guessing. Most likely a typo, or you simply haven't (yet) defined the function that you are trying to call.

sorry here it is.

/********************************************************
* PID Simple Example
* Reading analog input 0 to control analog PWM output 3
********************************************************/

#include <PID_Beta6.h>
#include <LiquidCrystal.h>

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 2,5,1);


LiquidCrystal lcd(12, 11, 8, 4, 3, 2);

const int onPin = 5; // choose the input pin to trigger heater
const int upPin = 6;   // choose the input pin to increase temp
const int downPin = 7;   // choose the input pin to decrease temp
int buttonState = 0;     // variable for reading the pin status
//also we would need pre-states of Up and Down button
//because person is slower then processor.
// and when I push button, loop would cycle several times
// then value would encerase for more then 1
int DownPushed = 0;
int UpPushed = 0;
int OnPushed = 0;
int PID_ON = 0;

unsigned long lastTime; 

void setup()
{
 Serial.begin( 19200 );
 //initialize the variables we're linked to
 Input = analogRead(0);
   Setpoint = 100;


 // Declare inputs
 pinMode(onPin, INPUT);    // declare pushbutton as input
 pinMode(upPin, INPUT);    // declare pushbutton as input
 pinMode(downPin, INPUT);    // declare pushbutton as input

 //turn the PID on STANDBY
 myPID.SetMode(MANUAL);
 Output=0;
 myPID.SetSampleTime(200);
 myPID.SetTunings(2,3,0);
 myPID.SetOutputLimits(0, 200);
 lastTime = millis();

      // set up the LCD's number of columns and rows:
      lcd.begin(16,2);
}

void loop()
{
  buttonState = digitalRead(onPin);

if (buttonState == HIGH) {    
    // turn LED/HEATER on:    
     myPID.SetMode(AUTO);  
  }
  else {
    // turn LED/HEATER off:
     myPID.SetMode(MANUAL);
      Output=0;
  }
  unsigned long lastTime;

      // I would not change these lines, because you are expecting 250 ms for a "push"
      // that is if you hold button for more then 1/4 second, 
      if(digitalRead(upPin)==HIGH) {
            if (millis()-lastTime >= 250) {
            Setpoint+=1;
            lastTime=millis();
            }
      }

      if(digitalRead(downPin)==HIGH) {
            if (millis()-lastTime >= 250) {
                  Setpoint-=1;
                  lastTime=millis();
            }
      }
      Input = analogRead(0);
      myPID.Compute();
      analogWrite(10,Output);
      //and output to LCD
      lcd.setCursor(0,0);
      //if heater is on - show *
      //if not - empty
      if( PID_ON ==1 ) {
            lcd.print("*");
      }
      else {
            lcd.print(" ");
      };
      lcd.print("--SET--: ");
      lcd.print((int)(Setpoint) );
      lcd.setCursor( 0,1);
      lcd.print(" -TEMP-: ");
      lcd.print( Input );
}

So, where's the code you added in response to Si's suggestion? Which line of the code you posted causes the compilation error.

Okay so… here’s what I took from Si’s suggestion… Im not sure how to apply it to my present code.

Did I put it in the right place? I just want it to smooth out the readout temp.

/********************************************************
* PID Simple Example
* Reading analog input 0 to control analog PWM output 3
********************************************************/

#include <PID_Beta6.h>
#include <LiquidCrystal.h>

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 2,5,1);

// we should connect LCD like on the page
//http://arduino.cc/en/Tutorial/LiquidCrystalBlink
//only instead pin 5 (which is already in use
//we would use pin 8 instead
//so we would engage pins 12, 11, 8, 4, 3, 2
// so, input pin better to redefine
LiquidCrystal lcd(12, 11, 8, 4, 3, 2);

const int onPin = 5; // choose the input pin to trigger heater
//leave them the same
const int upPin = 6;   // choose the input pin to increase temp
const int downPin = 7;   // choose the input pin to decrease temp
int buttonState = 0;     // variable for reading the pin status
//also we would need pre-states of Up and Down button
//because person is slower then processor.
// and when I push button, loop would cycle several times
// then value would encerase for more then 1
int DownPushed = 0;
int UpPushed = 0;
int OnPushed = 0;
int PID_ON = 0;

unsigned long lastTime; 

void setup()
{
 Serial.begin( 19200 );
 //initialize the variables we're linked to
 Input = analogRead(0);
   Setpoint = 200;


 // Declare inputs
 pinMode(onPin, INPUT);    // declare pushbutton as input
 pinMode(upPin, INPUT);    // declare pushbutton as input
 pinMode(downPin, INPUT);    // declare pushbutton as input

 //turn the PID on STANDBY
 myPID.SetMode(MANUAL);
 Output=0;
 myPID.SetSampleTime(200);
 myPID.SetTunings(2,3,0);
 myPID.SetOutputLimits(0, 190);
 lastTime = millis();

      // set up the LCD's number of columns and rows:
      lcd.begin(16,2);
}

void loop()
{
  buttonState = digitalRead(onPin);

if (buttonState == HIGH) {    
    // turn LED/HEATER on:    
     myPID.SetMode(AUTO);  
  }
  else {
    // turn LED/HEATER off:
     myPID.SetMode(MANUAL);
      Output=0;
  }
  unsigned long lastTime;

      // I would not change these lines, because you are expecting 250 ms for a "push"
      // that is if you hold button for more then 1/4 second, 
      if(digitalRead(upPin)==HIGH) {
            if (millis()-lastTime >= 250) {
            Setpoint+=1;
            lastTime=millis();
            }
      }

      if(digitalRead(downPin)==HIGH) {
            if (millis()-lastTime >= 250) {
                  Setpoint-=1;
                  lastTime=millis();
            }
      }
      Input = analogRead(0);
      myPID.Compute();
      analogWrite(10,Output);
      //and output to LCD
      lcd.setCursor(0,0);
      //if heater is on - show *
      //if not - empty
      if( PID_ON ==1 ) {
            lcd.print("*");
      }
      else {
            lcd.print(" ");
      };
      lcd.print("--SET--: ");
      lcd.print((int)(Setpoint) );
      lcd.setCursor( 0,1);
      lcd.print(" Current: ");
        static double lastValue = 0;
       double newValue = read MyValue();
       double smoothValue = alpha * lastValue + (1 - alpha) * newValue;  // this is the key line of code
       lastValue = newValue;
      lcd.print( Input );

What value are you trying to smooth? Use a name from your code, please. It it's Input, then, no that code is in the wrong place, and readMyInput() should be replaced with the analogRead call that currently values Input.

The value thats really jumpy and unreadable on the LCD is called from "lcd.print( Input ); "

Thats the value I am trying to smooth…

Am I getting closer with this modification? Its all on the last few lines of code.

/********************************************************
 * PID Simple Example
 * Reading analog input 0 to control analog PWM output 3
 ********************************************************/

#include <PID_Beta6.h>
#include <LiquidCrystal.h>
#define alpha 0.8

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, 2,5,1);

// we should connect LCD like on the page
//http://arduino.cc/en/Tutorial/LiquidCrystalBlink
//only instead pin 5 (which is already in use
//we would use pin 8 instead
//so we would engage pins 12, 11, 8, 4, 3, 2
// so, input pin better to redefine
LiquidCrystal lcd(12, 11, 8, 4, 3, 2);

const int onPin = 5; // choose the input pin to trigger heater
//leave them the same
const int upPin = 6;   // choose the input pin to increase temp
const int downPin = 7;   // choose the input pin to decrease temp
int buttonState = 0;     // variable for reading the pin status
//also we would need pre-states of Up and Down button
//because person is slower then processor.
// and when I push button, loop would cycle several times
// then value would encerase for more then 1
int DownPushed = 0;
int UpPushed = 0;
int OnPushed = 0;
int PID_ON = 0;
int UpdScr = 0;
int inpt;

unsigned long lastTime, lShowTime; 

void setup()
{
  Serial.begin( 19200 );
  //initialize the variables we're linked to
  Input = analogRead(0);
    Setpoint = 200;
    
    
  // Declare inputs
  pinMode(onPin, INPUT);    // declare pushbutton as input
  pinMode(upPin, INPUT);    // declare pushbutton as input
  pinMode(downPin, INPUT);    // declare pushbutton as input
 
  //turn the PID on STANDBY
  myPID.SetMode(MANUAL);
  Output=0;
  myPID.SetSampleTime(200);
  myPID.SetTunings(2,3,0);
  myPID.SetOutputLimits(0, 200);
  lShowTime = lastTime = millis();

      // set up the LCD's number of columns and rows:
      lcd.begin(16,2);
}

void loop()
{
      buttonState = digitalRead(onPin);
//reverting back

      if (buttonState == HIGH) {
            // turn HEATER on:
            myPID.SetMode(AUTO);
            PID_ON =1;
      }
      else {
                 myPID.SetMode(MANUAL);
            Output = 0;
            PID_ON =0;
      }

/*
      // here I'm changing the behaviour
      // previously it worked only while you push the on button
      // now it would be switch on/off button
      // you push it once - switch on
      // push second time - switch off
      if (buttonState == HIGH) {
            // turn HEATER on:
            if( OnPushed ==0 && PID_ON == 0 ) {
                  myPID.SetMode(AUTO);
                  OnPushed = 1;
                  PID_ON =1;
            }
            if( OnPushed ==0 && PID_ON == 1) {
                      // turn HEATER off:
                       myPID.SetMode(MANUAL);
                  Output=0;
                  OnPushed = 1;
                  PID_ON = 0;
            }
      }
      else {
            OnPushed= 0; // button is not pushed. may be it was released
      }
*/

      // I would not change these lines, because you are expecting 250 ms for a "push"
      // that is if you hold button for more then 1/4 second, 
      if(digitalRead(upPin)==HIGH) {
            if (millis()-lastTime >= 250) {
                  Setpoint+=1;
                  lastTime=millis();
                  UpdScr = 1;
            }
      }

      if(digitalRead(downPin)==HIGH) {
            if (millis()-lastTime >= 250) {
                  Setpoint-=1;
                  lastTime=millis();
                  UpdScr = 1;
            }
      }
      Input = analogRead(0);
      myPID.Compute();
      analogWrite(10,Output);
      //and output to LCD
            lcd.setCursor(0,0);
      //if heater is on - show *
      //if not - empty
            if( PID_ON ==1 ) {
                  lcd.print("*");
            }
            else {
                  lcd.print(" ");
            };
                static double lastValue = 0;
                double newValue = analogRead(0);
                double smoothValue = alpha * lastValue + (1 - alpha) * newValue;  // this is the key line of code
                lastValue = newValue;
            lcd.print("--SET--: ");
            lcd.print((int)(Setpoint) );
            lcd.setCursor( 0,1);
            lcd.print(" --AIR--: ");
            lcd.print( smoothValue );

Since Input is used by myPid.Compute(), I think that you really need to do the smoothing, and stuff the smoothed result in Input, before you call myPid.Compute().

Hrmm... The thing is I don't really want to interfere with the PID computing sequence.... ya know... smoothing it before the PID computes the output power.

Is there a way to simply read the PV and incorporate that into a smoothing script and then output that to the LCD..... so that I don't affect the PID calculations. Just a ballpark estimate or average of the temp.

Someone msged me this:

exponential smoothing is your answer. 

take a variable n, its measurement at t-1 is n(t-1) and its current measurement is n(t). 

assuming you want to assign a weight alpha (alpha between 0 and 1) to the current measurement. 

a moving average n(avg) can be calcualted as: 

n(avg)=(1-alpha)*n(avg)(t-1) + alpha * n(t); 

a higher alpha means that the history has lower weight (=shorter memory). roughly speaking, the "memory length" is about 1/alpha. 

now, if you already use floating point math, the above code doesn't add much. if you don't already use floating point, you can chose alpha carefully - making it 1/2, 1/4, 1/8, 1/16, ... 

n(avg)=(1-1/w)*n(avg)(t-1) + 1/w*n(t) 
= n(avg)(t-1) + (n(t) - n(avg)(t-1)) * 1/w; where w=2, 4, 8, 16, ...