PID Autotune Library?

Hi,

I have read here that the PID library for Particle includes autotune. But I could not find anything in the code. I have also compared the Arduino Autotune Library with the Particle PID library and could not find any hints.
So could it be that the Autotune library hast not been ported for Particle devices or did I overlook something?

I began porting the Arduino Autotune Library and I was very successfull so far. My hopefully last error is in line 126 of my autotune.ino

autotune.ino:

// This #include statement was automatically added by the Particle IDE.
#include "PID_AutoTune_v0.h"

// This #include statement was automatically added by the Particle IDE.
#include "pid/pid.h"

byte ATuneModeRemember=2;
double input=80, output=50, setpoint=180;
double kp=2,ki=0.5,kd=2;

double kpmodel=1.5, taup=100, theta[50];
double outputStart=5;
double aTuneStep=50, aTuneNoise=1, aTuneStartValue=100;
unsigned int aTuneLookBack=20;

boolean tuning = false;
unsigned long  modelTime, serialTime;

PID myPID(&input, &output, &setpoint,kp,ki,kd, PID::DIRECT);
PID_ATune aTune(&input, &output);

//set to false to connect to the real world
boolean useSimulation = true;

void setup()
{
  if(useSimulation)
  {
    for(byte i=0;i<50;i++)
    {
      theta[i]=outputStart;
    }
    modelTime = 0;
  }
  //Setup the pid 
  myPID.SetMode(PID::AUTOMATIC);

  if(tuning)
  {
    tuning=false;
    changeAutoTune();
    tuning=true;
  }
  
  serialTime = 0;
  Serial.begin(9600);

}

void loop()
{

  unsigned long now = millis();

  if(!useSimulation)
  { //pull the input in from the real world
    input = analogRead(0);
  }
  
  if(tuning)
  {
    byte val = (aTune.Runtime());
    if (val!=0)
    {
      tuning = false;
    }
    if(!tuning)
    { //we're done, set the tuning parameters
      kp = aTune.GetKp();
      ki = aTune.GetKi();
      kd = aTune.GetKd();
      myPID.SetTunings(kp,ki,kd);
      AutoTuneHelper(false);
    }
  }
  else myPID.Compute();
  
  if(useSimulation)
  {
    theta[30]=output;
    if(now>=modelTime)
    {
      modelTime +=100; 
      DoModel();
    }
  }
  else
  {
     analogWrite(0,output); 
  }
  
  //send-receive with processing if it's time
  if(millis()>serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime+=500;
  }
}

void changeAutoTune()
{
 if(!tuning)
  {
    //Set the output to the desired starting frequency.
    output=aTuneStartValue;
    aTune.SetNoiseBand(aTuneNoise);
    aTune.SetOutputStep(aTuneStep);
    aTune.SetLookbackSec((int)aTuneLookBack);
    AutoTuneHelper(true);
    tuning = true;
  }
  else
  { //cancel autotune
    aTune.Cancel();
    tuning = false;
    AutoTuneHelper(false);
  }
}

void AutoTuneHelper(boolean start)
{
  if(start)
    ATuneModeRemember = myPID.GetMode();
  else
    myPID.SetMode(ATuneModeRemember);
}


void SerialSend()
{
  Serial.print("setpoint: ");Serial.print(setpoint); Serial.print(" ");
  Serial.print("input: ");Serial.print(input); Serial.print(" ");
  Serial.print("output: ");Serial.print(output); Serial.print(" ");
  if(tuning){
    Serial.println("tuning mode");
  } else {
    Serial.print("kp: ");Serial.print(myPID.GetKp());Serial.print(" ");
    Serial.print("ki: ");Serial.print(myPID.GetKi());Serial.print(" ");
    Serial.print("kd: ");Serial.print(myPID.GetKd());Serial.println();
  }
}

void SerialReceive()
{
  if(Serial.available())
  {
   char b = Serial.read(); 
   Serial.flush(); 
   if((b=='1' && !tuning) || (b!='1' && tuning))changeAutoTune();
  }
}

void DoModel()
{
  //cycle the dead time
  for(byte i=0;i<49;i++)
  {
    theta[i] = theta[i+1];
  }
  //compute the input
  input = (kpmodel / taup) *(theta[0]-outputStart) + input*(1-1/taup) + ((float)random(-10,10))/100;

}

PID_AutoTune_v0.cpp

#include <PID_AutoTune_v0.h>
#include "application.h"

PID_ATune::PID_ATune(double* Input, double* Output)
{
	input = Input;
	output = Output;
	controlType =0 ; //default to PI
	noiseBand = 0.5;
	running = false;
	oStep = 30;
	SetLookbackSec(10);
	lastTime = millis();
	
}



void PID_ATune::Cancel()
{
	running = false;
} 
 
int PID_ATune::Runtime()
{
	justevaled=false;
	if(peakCount>9 && running)
	{
		running = false;
		FinishUp();
		return 1;
	}
	unsigned long now = millis();
	
	if((now-lastTime)<sampleTime) return false;
	lastTime = now;
	double refVal = *input;
	justevaled=true;
	if(!running)
	{ //initialize working variables the first time around
		peakType = 0;
		peakCount=0;
		justchanged=false;
		absMax=refVal;
		absMin=refVal;
		setpoint = refVal;
		running = true;
		outputStart = *output;
		*output = outputStart+oStep;
	}
	else
	{
		if(refVal>absMax)absMax=refVal;
		if(refVal<absMin)absMin=refVal;
	}
	
	//oscillate the output base on the input's relation to the setpoint
	
	if(refVal>setpoint+noiseBand) *output = outputStart-oStep;
	else if (refVal<setpoint-noiseBand) *output = outputStart+oStep;
	
	
  //bool isMax=true, isMin=true;
  isMax=true;isMin=true;
  //id peaks
  for(int i=nLookBack-1;i>=0;i--)
  {
    double val = lastInputs[i];
    if(isMax) isMax = refVal>val;
    if(isMin) isMin = refVal<val;
    lastInputs[i+1] = lastInputs[i];
  }
  lastInputs[0] = refVal;  
  if(nLookBack<9)
  {  //we don't want to trust the maxes or mins until the inputs array has been filled
	return 0;
	}
  
  if(isMax)
  {
    if(peakType==0)peakType=1;
    if(peakType==-1)
    {
      peakType = 1;
      justchanged=true;
      peak2 = peak1;
    }
    peak1 = now;
    peaks[peakCount] = refVal;
   
  }
  else if(isMin)
  {
    if(peakType==0)peakType=-1;
    if(peakType==1)
    {
      peakType=-1;
      peakCount++;
      justchanged=true;
    }
    
    if(peakCount<10)peaks[peakCount] = refVal;
  }
  
  if(justchanged && peakCount>2)
  { //we've transitioned.  check if we can autotune based on the last peaks
    double avgSeparation = (abs(peaks[peakCount-1]-peaks[peakCount-2])+abs(peaks[peakCount-2]-peaks[peakCount-3]))/2;
    if( avgSeparation < 0.05*(absMax-absMin))
    {
		FinishUp();
      running = false;
	  return 1;
	 
    }
  }
   justchanged=false;
	return 0;
}
void PID_ATune::FinishUp()
{
	  *output = outputStart;
      //we can generate tuning parameters!
      Ku = 4*(2*oStep)/((absMax-absMin)*3.14159);
      Pu = (double)(peak1-peak2) / 1000;
}

double PID_ATune::GetKp()
{
	return controlType==1 ? 0.6 * Ku : 0.4 * Ku;
}

double PID_ATune::GetKi()
{
	return controlType==1? 1.2*Ku / Pu : 0.48 * Ku / Pu;  // Ki = Kc/Ti
}

double PID_ATune::GetKd()
{
	return controlType==1? 0.075 * Ku * Pu : 0;  //Kd = Kc * Td
}

void PID_ATune::SetOutputStep(double Step)
{
	oStep = Step;
}

double PID_ATune::GetOutputStep()
{
	return oStep;
}

void PID_ATune::SetControlType(int Type) //0=PI, 1=PID
{
	controlType = Type;
}
int PID_ATune::GetControlType()
{
	return controlType;
}
	
void PID_ATune::SetNoiseBand(double Band)
{
	noiseBand = Band;
}

double PID_ATune::GetNoiseBand()
{
	return noiseBand;
}

void PID_ATune::SetLookbackSec(int value)
{
    if (value<1) value = 1;
	
	if(value<25)
	{
		nLookBack = value * 4;
		sampleTime = 250;
	}
	else
	{
		nLookBack = 100;
		sampleTime = value*10;
	}
}

int PID_ATune::GetLookbackSec()
{
	return nLookBack * sampleTime / 1000;
}

PID_AutoTune_v0.h

#ifndef PID_AutoTune_v0
#define PID_AutoTune_v0
#define LIBRARY_VERSION	0.0.1

class PID_ATune
{


  public:
  //commonly used functions **************************************************************************
    PID_ATune(double*, double*);                       	// * Constructor.  links the Autotune to a given PID
    int Runtime();						   			   	// * Similar to the PID Compue function, returns non 0 when done
	void Cancel();									   	// * Stops the AutoTune	
	
	void SetOutputStep(double);						   	// * how far above and below the starting value will the output step?	
	double GetOutputStep();							   	// 
	
	void SetControlType(int); 						   	// * Determies if the tuning parameters returned will be PI (D=0)
	int GetControlType();							   	//   or PID.  (0=PI, 1=PID)			
	
	void SetLookbackSec(int);							// * how far back are we looking to identify peaks
	int GetLookbackSec();								//
	
	void SetNoiseBand(double);							// * the autotune will ignore signal chatter smaller than this value
	double GetNoiseBand();								//   this should be acurately set
	
	double GetKp();										// * once autotune is complete, these functions contain the
	double GetKi();										//   computed tuning parameters.  
	double GetKd();										//
	
  private:
    void FinishUp();
	bool isMax, isMin;
	double *input, *output;
	double setpoint;
	double noiseBand;
	int controlType;
	bool running;
	unsigned long peak1, peak2, lastTime;
	int sampleTime;
	int nLookBack;
	int peakType;
	double lastInputs[101];
    double peaks[10];
	int peakCount;
	bool justchanged;
	bool justevaled;
	double absMax, absMin;
	double oStep;
	double outputStart;
	double Ku, Pu;
	
};
#endif

My last two errors:

PID_AutoTune_v0.cpp: In member function 'int PID_ATune::Runtime()':
PID_AutoTune_v0.cpp:35:20: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
  if((now-lastTime)<sampleTime) return false;
                    ^
autotune.cpp: In function 'void AutoTuneHelper(boolean)':
autotune.cpp:126:36: error: invalid conversion from 'byte {aka unsigned char}' to 'PID::mode_t' [-fpermissive]
     tuning = false;
                                    ^

In file included from autotune.cpp:5:0:
This looks like an error in pid library. Would you like to create an issue on GitHub to let the author know?
CREATE ISSUE
pid/pid.h:20:10: error:   initializing argument 1 of 'void PID::SetMode(PID::mode_t)' [-fpermissive]
     void SetMode(mode_t);                 // * sets PID to either MANUAL (0) or AUTOMATIC (1)
          ^

make[1]: *** [../build/target/user/platform-6autotune.o] Error 1
make: *** [user] Error 2

I think I get these errors because SetMode is missing a PID:: between the brackets.
But I have no idea how to add it to this line: myPID.SetMode(ATuneModeRemember);
If I change it to myPID.SetMode(PID::ATuneModeRemember); I get the following error:

PID_AutoTune_v0.cpp: In member function 'int PID_ATune::Runtime()':
PID_AutoTune_v0.cpp:35:20: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
  if((now-lastTime)<sampleTime) return false;
                    ^
autotune.cpp: In function 'void AutoTuneHelper(boolean)':
autotune.cpp:126:19: error: 'ATuneModeRemember' is not a member of 'PID'
     tuning = false;
                   ^

make[1]: *** [../build/target/user/platform-6autotune.o] Error 1
make: *** [user] Error 2

So any help will be greatly appreciated! :smile:

Where is the definition of type PID::mode_t?

You either declare PID::mode_t ATuneModeRemember=2; or have to cast myPID.SetMode((PID::mode_t)ATuneModeRemember); (only works if byte is castable that way).
Either way you need the type declared somewhere.

I even seem to be unable to locate SetMode(), where does that come from?


Update:
Found both in PID.h

class PID
{
    ...
    enum mode_t { AUTOMATIC = 1, MANUAL = 0 };
    ...
    void SetMode(mode_t); 
    ...
}

So casting should do, but using the correct type is cleaner.

But as you see, mode_t would not really allow 2 as initial value for ATuneModeRemember.

Thank you very much for your reply @ScruffR! The code compiles now when I use myPID.SetMode((PID::mode_t)ATuneModeRemember); and I hope it does what it is expected to do :smile:

The code does not compile when I declare PID::mode_t ATuneModeRemember=2;

PID_AutoTune_v0.cpp: In member function 'int PID_ATune::Runtime()':
PID_AutoTune_v0.cpp:35:20: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
  if((now-lastTime)<sampleTime) return false;
                    ^
autotune.cpp:7:31: error: invalid conversion from 'int' to 'PID::mode_t' [-fpermissive]
 void SerialSend();
                               ^

autotune.cpp: In function 'void AutoTuneHelper(boolean)':
autotune.cpp:125:39: error: invalid conversion from 'int' to 'PID::mode_t' [-fpermissive]
   { //cancel autotune
                                       ^

make[1]: *** [../build/target/user/platform-6autotune.o] Error 1
make: *** [user] Error 2

The value 2 comes from the original library and I am not sure why it is there and what it is doing. Maybe it prevents an unwanted start of the algorithm?

Yup, the only allowed values for that would be

PID::mode_t ATuneModeRemember = AUTOMATIC; 
// or
PID::mode_t ATuneModeRemember =  MANUAL;

I’d think not even 0 or 1 should be allowed.

Would it be a good idea to publish this library to the web IDE, @ScruffR ? I cannot test it at the moment because I am waiting for the necessary hardware.

1 Like

Sure, just go ahead :+1:

1 Like

Is there a way to include the PID library into my example file?
#include "pid/pid.h" does not work :frowning:

Edit: I have found a solution. I simply added pid.h and pid.cpp to my firmware folder and now it compiles in the online IDE.
Is this a good solution or will there be any problems when somebody wants to use the pid library and the pid-autotune library?

Edit 2: I wasn’t a good idea to do that. When I add the pid library to my autotune example everything is defined two times and I get a compile error.

Yup, that’s the problem.
You should not include the files of another contributed library for several reasons.

The most important is the risk to get out of sync with the lib when it gets updated.
Redundancy is also not the best thing to produce.

If you just clearly comment in your lib (and the samples) that importing the respective libraries is required, it’s up to the user to read and do that :wink:

thanks for your suggestions, @ScruffR! :smile:
I have added a note to the example so that hopefully nobody forgets to includ the pid library!

So the library should be visible and ready to use for everyone :smile:

2 Likes

Hi guys. Could you share the library link?

Thanks.

@kariston, the PID and PID-AutoTune libraries are available on the web IDE. Just search for “pid” in the libraries.

Thank you for the answer, @peekay123. But I didn't find the PID-AutoTune Library on the Web IDE. Even looking for more results on Library Manager.

@kariston, here is a direct link:

https://build.particle.io/libs/PID-AutoTune/0.0.3/tab/PID-AutoTune.cpp