Brett,
The integer version of the library is functional, with one caveat... the output increments from 0 to 127 then increments from -128 to -1. It's like the 8th bit is being used for sign. I've renamed the library to PID_Beta6b in order to maintain the integrity of the original library and to make it easy to switch back and forth. Here's the first half of the code:
#include <wiring.h>
#include <PID_Beta6b.h>
/* Standard Constructor (...)***********************************************
* constructor used by most users. the parameters specified are those for
* for which we can't set up reliable defaults, so we need to have the user
* set them.
***************************************************************************/
PID::PID(double *Input, double *Output, double *Setpoint, double Kc, double TauI, double TauD)
{
PID::ConstructorCommon(Input, Output, Setpoint, Kc, TauI, TauD);
UsingFeedForward = false;
PID::Reset();
}
/* Overloaded Constructor(...)**********************************************
* This one is for more advanced users. it's essentially the same as the
* standard constructor, with one addition. you can link to a Feed Forward bias,
* which lets you implement... um.. Feed Forward Control. good stuff.
***************************************************************************/
PID::PID(double *Input, double *Output, double *Setpoint, double *FFBias, double Kc, double TauI, double TauD)
{
PID::ConstructorCommon(Input, Output, Setpoint, Kc, TauI, TauD);
UsingFeedForward = true; //tell the controller that we'll be using an external
long myBias = (*FFBias * 4096.0); //bias, and where to find it
PID::Reset();
}
/* ConstructorCommon(...)****************************************************
* Most of what is done in the two constructors is the same. that code
* was put here for ease of maintenance and (minor) reduction of library size
****************************************************************************/
void PID::ConstructorCommon(double *Input, double *Output, double *Setpoint, double Kc, double TauI, double TauD)
{
PID::SetInputLimits(0, 1023); //default the limits to the
PID::SetOutputLimits(0, 255); //full ranges of the I/O
tSample = 1000; //default Controller Sample Time is 1 second
PID::SetTunings(Kc, TauI, TauD);
nextCompTime = millis();
inAuto = false;
myOutput = Output;
myInput = Input;
mySetpoint = Setpoint;
}
/* SetInputLimits(...)*****************************************************
* I don't see this function being called all that much (other than from the
* constructor.) it needs to be here so we can tell the controller what it's
* input limits are, and in most cases the 0-1023 default should be fine. if
* there's an application where the signal being fed to the controller is
* outside that range, well, then this function's here for you.
**************************************************************************/
void PID::SetInputLimits(double INMin, double INMax)
{
//after verifying that mins are smaller than maxes, set the values
if(INMin >= INMax) return;
//rescale the working variables to reflect the changes
lastInput = multFix(lastInput,divFix(floatToFix(INMax - INMin),inSpan));
accError = multFix(accError,divFix(floatToFix(INMax - INMin),inSpan));
//make sure the working variables are
//within the new limits
if (lastInput > 1<<12) lastInput = 1<<12;
else if (lastInput < 0L) lastInput = 0L;
inMin = floatToFix(INMin);
inSpan = floatToFix(INMax - INMin);
}
/* SetOutputLimits(...)****************************************************
* This function will be used far more often than SetInputLimits. while
* the input to the controller will generally be in the 0-1023 range (which is
* the default already,) the output will be a little different. maybe they'll
* be doing a time window and will need 0-8000 or something. or maybe they'll
* want to clamp it from 0-125. who knows. at any rate, that can all be done
* here.
**************************************************************************/
void PID::SetOutputLimits(double OUTMin, double OUTMax)
{
//after verifying that mins are smaller than maxes, set the values
if(OUTMin >= OUTMax) return;
//rescale the working variables to reflect the changes
lastOutput = multFix(lastOutput,divFix(floatToFix(OUTMax - OUTMin),outSpan));
//make sure the working variables are
//within the new limits
if (lastOutput > 1<<12) lastOutput = 1<<12;
else if (lastOutput < 0L) lastOutput = 0L;
outMin = floatToFix(OUTMin);
outSpan = floatToFix(OUTMax - OUTMin);
}
/* SetTunings(...)*************************************************************
* This function allows the controller's dynamic performance to be adjusted.
* it's called automatically from the constructor, but tunings can also
* be adjusted on the fly during normal operation
******************************************************************************/
void PID::SetTunings(double Kc, double TauI, double TauD)
{
//verify that the tunings make sense
if (Kc == 0.0 || TauI < 0.0 || TauD < 0.0) return;
//we're going to do some funky things to the input numbers so all
//our math works out, but we want to store the numbers intact
//so we can return them to the user when asked.
P_Param = Kc;
I_Param = TauI;
D_Param = TauD;
//convert Reset Time into Reset Rate, and compensate for Calculation frequency
double tSampleInSec = ((double)tSample / 1000.0);
double tempTauR;
if (TauI == 0)
tempTauR = 0;
else
tempTauR = (1.0 / TauI) * tSampleInSec;
if (inAuto)
{ //if we're in auto, and we just change the tunings, the integral term
//will become very, very, confused (trust me.) to achieve "bumpless
// transfer" we need to rescale the accumulated error.
if(tempTauR != 0.0) //(avoid divide by 0)
accError = multFix(accError,divFix(multFix(kc,taur),floatToFix(Kc * tempTauR)));
else
accError = 0L;
}
kc = floatToFix(Kc);
taur = floatToFix(tempTauR);
taud = floatToFix(TauD / tSampleInSec);
}
/* Reset()*********************************************************************
* does all the things that need to happen to ensure a bumpless transfer
* from manual to automatic mode. this shouldn't have to be called from the
* outside. In practice though, it is sometimes helpful to start from scratch,
* so it was made publicly available
******************************************************************************/
void PID::Reset()
{
if(UsingFeedForward)
bias = divFix((*myBias - outMin),outSpan);
else
bias = divFix((floatToFix(*myOutput) - outMin),outSpan);
lastOutput = (long)bias;
lastInput = divFix((floatToFix(*myInput) - inMin),inSpan);
// - clear any error in the integral
accError = 0L;
}
/* SetMode(...)****************************************************************
* Allows the controller Mode to be set to manual (0) or Automatic (non-zero)
* when the transition from manual to auto occurs, the controller is
* automatically initialized
******************************************************************************/
void PID::SetMode(int Mode)
{
if (Mode!=0 && !inAuto)
{ //we were in manual, and we just got set to auto.
//reset the controller internals
PID::Reset();
}
inAuto = (Mode!=0);
}
/* SetSampleTime(...)*******************************************************
* sets the frequency, in Milliseconds, with which the PID calculation is performed
******************************************************************************/
void PID::SetSampleTime(int NewSampleTime)
{
if (NewSampleTime > 0)
{
//convert the time-based tunings to reflect this change
taur = multFix(taur,floatToFix((double)NewSampleTime/(double)tSample));
accError = multFix(accError,floatToFix((double)tSample/(double)NewSampleTime));
taud = multFix(taud,floatToFix((double)NewSampleTime/(double)tSample));
tSample = (unsigned long)NewSampleTime;
}
}