Arduino PID Library

Brett,
If this overflows, you could reduce the number of bit-shifts or use 'long long' int64_t data type which should not overflow. I'm fumbling my way through modifying the library, and will email it to you for performance baseline testing for speed and accuracy.

Billy

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;
      }
}

And here's the last portion (wouldn't all fit in one post):

/* Compute() **********************************************************************
 *     This, as they say, is where the magic happens.  this function should be called
 *   every time "void loop()" executes.  the function will decide for itself whether a new
 *   pid Output needs to be computed
 *
 *  Some notes for people familiar with the nuts and bolts of PID control:
 *  - I used the Ideal form of the PID equation.  mainly because I like IMC
 *    tunings.  lock in the I and D, and then just vary P to get more 
 *    aggressive or conservative
 *
 *  - While this controller presented to the outside world as being a Reset Time
 *    controller, when the user enters their tunings the I term is converted to
 *    Reset Rate.  I did this merely to avoid the div0 error when the user wants
 *    to turn Integral action off.
 *    
 *  - Derivative on Measurement is being used instead of Derivative on Error.  The
 *    performance is identical, with one notable exception.  DonE causes a kick in
 *    the controller output whenever there's a setpoint change. DonM does not.
 *
 *  If none of the above made sense to you, and you would like it to, go to:
 *  http://www.controlguru.com .  Dr. Cooper was my controls professor, and is
 *  gifted at concisely and clearly explaining PID control
 *********************************************************************************/
void PID::Compute()
{
      justCalced=false;
      if (!inAuto) return; //if we're in manual just leave;

      unsigned long now = millis();

      //millis() wraps around to 0 at some point.  depending on the version of the 
      //Arduino Program you are using, it could be in 9 hours or 50 days.
      //this is not currently addressed by this algorithm.

      //here's an attempt to address the millis() wrap issue:

      if (now < tSample) nextCompTime = tSample;
      
                                                      
      //...Perform PID Computations if it's time...
      if (now>=nextCompTime)                                          
      {
            
            //pull in the input and setpoint, and scale them into percent span
            long scaledInput = divFix((floatToFix(*myInput) - inMin),inSpan);
            if (scaledInput>(1<<12)) scaledInput = 1<<12;
            else if (scaledInput<0L) scaledInput = 0L;

            long scaledSP = divFix((floatToFix(*mySetpoint) - inMin),inSpan);
            if (scaledSP>(1<<12)) scaledSP = 1<<12;
            else if (scaledSP<0L) scaledSP = 0L;
            
            //compute the error
            long err = scaledSP - scaledInput;
            
            // check and see if the output is pegged at a limit and only 
            // integrate if it is not. (this is to prevent reset-windup)
            if (!(lastOutput >= 1<<12 && err>0L) && !(lastOutput <= 0L && err<0L))
            {
                  accError = accError + err;
            }                                                

            // compute the current slope of the input signal
            long dMeas = (scaledInput - lastInput);      // we'll assume that dTime (the denominator) is 1 second. 
                                          // if it isn't, the taud term will have been adjusted 
                                          // in "SetTunings" to compensate

            //if we're using an external bias (i.e. the user used the 
            //overloaded constructor,) then pull that in now
            if(UsingFeedForward)
            {
                  bias = divFix((*myBias - outMin),outSpan);
            }


            // perform the PID calculation.  
            long output = bias + multFix(kc,(err + multFix(taur,accError) - multFix(taud,dMeas)));

            //make sure the computed output is within output constraints
            if (output < 0L) output = 0L;
            else if (output > 1<<12) output = 1<<12;

            lastOutput = output;            // remember this output for the windup
                                    // check next time            
            lastInput = scaledInput;      // remember the Input for the derivative
                                    // calculation next time

            //scale the output from percent span back out to a real world number
                *myOutput = fixToFloat(multFix(output,outSpan) + outMin);

            nextCompTime += tSample;                        // determine the next time the computation
            if(nextCompTime < now) nextCompTime = now + tSample;      // should be performed      

            justCalced=true;  //set the flag that will tell the outside world that the output was just computed
      }                                                


}


/*****************************************************************************
 * STATUS SECTION
 * These functions allow the outside world to query the status of the PID
 *****************************************************************************/

bool PID::JustCalculated()
{
      return justCalced;
}
int PID::GetMode()
{
      if(inAuto)return 1;
      else return 0;
}

double PID::GetINMin()
{
      return fixToFloat(inMin);
}
double PID::GetINMax()
{
      return fixToFloat(inMin + inSpan);
}
double PID::GetOUTMin()
{
      return fixToFloat(outMin);
}
double PID::GetOUTMax()
{
      return fixToFloat(outMin+outSpan);
}
int PID::GetSampleTime()
{
      return tSample;
}
double PID::GetP_Param()
{
      return P_Param;
}
double PID::GetI_Param()
{
      return I_Param;
}

double PID::GetD_Param()
{
      return D_Param;
}

ps. I also made a feeble stab at millis() rollover protection, not sure if it'll work...

And lastly, the modified header file:

#ifndef PID_Beta6b_h
#define PID_Beta6b_h

#define floatToFix(a) ((long)((a)*4096.0))                   // mult by 2^12 to use floats in fixed point math
#define fixToFloat(a) ((double)((a)/4096.0))                   // div by 2^12 to convert back to float
#define multFix(a,b) ((long)((((long)(a))*((long)(b)))>>12))    // bit-shift right 12 to maintain 12 assumed decimal places
#define divFix(a,b) ((long)(((long)(a)<<12)/((long)(b))))        // make Numerator 8:24 int before dividing

class PID
{


  public:

  #define AUTO      1
  #define MANUAL      0
  #define LIBRARY_VERSION      0.6b

  //commonly used functions **************************************************************************
    PID(double*, double*, double*,        // * constructor.  links the PID to the Input, Output, and 
        double, double, double);          //   Setpoint.  Initial tuning parameters are also set here

    PID(double*, double*, double*,        // * Overloaded Constructor.  if the user wants to implement
        double*, double, double, double); //   feed-forward

    void SetMode(int Mode);               // * sets PID to either Manual (0) or Auto (non-0)

    void Compute();                       // * performs the PID calculation.  it should be
                                          //   called every time loop() cycles. ON/OFF and
                                          //   calculation frequency can be set using SetMode
                                          //   SetSampleTime respectively

    void SetInputLimits(double, double);  //Tells the PID what 0-100% are for the Input

    void SetOutputLimits(double, double); //Tells the PID what 0-100% are for the Output


  //available but not commonly used functions ********************************************************
    void SetTunings(double, double,       // * While most users will set the tunings once in the 
                    double);              //   constructor, this function gives the user the option
                                          //   of changing tunings during runtime for Adaptive control

    void SetSampleTime(int);              // * sets the frequency, in Milliseconds, with which 
                                          //   the PID calculation is performed.  default is 1000 

    void Reset();                         // * reinitializes controller internals.  automatically
                                          //   called on a manual to auto transition

    bool JustCalculated();                // * in certain situations, it helps to know when the PID has
                                          //   computed this bit will be true for one cycle after the
                                          //   pid calculation has occurred
    

   //Status functions allow you to query current PID constants ***************************************
    int GetMode();
    double GetINMin();
    double GetINMax();
    double GetOUTMin();
    double GetOUTMax();
    int GetSampleTime();
    double GetP_Param();
    double GetI_Param();
    double GetD_Param();


  private:

    void ConstructorCommon(double*, double*, double*,           // * code that is shared by the constructors
        double, double, double);

   //scaled, tweaked parameters we'll actually be using
    long kc;                      // * (P)roportional Tuning Parameter
    long taur;                    // * (I)ntegral Tuning Parameter
    long taud;                    // * (D)erivative Tuning Parameter

   //nice, pretty parameters we'll give back to the user if they ask what the tunings are
    double P_Param;
    double I_Param;
    double D_Param;


    double *myInput;              // * Pointers to the Input, Output, and Setpoint variables
    double *myOutput;             //   This creates a hard link between the variables and the 
    double *mySetpoint;           //   PID, freeing the user from having to constantly tell us
                                  //   what these values are.  with pointers we'll just know.

    long *myBias;                 // * Pointer to the External FeedForward bias, only used 
                                  //   if the advanced constructor is used
    bool UsingFeedForward;        // * internal flag that tells us if we're using FeedForward or not

    unsigned long nextCompTime;   // * Helps us figure out when the PID Calculation needs to
                                  //   be performed next
                                  //   to determine when to compute next
    unsigned long tSample;        // * the frequency, in milliseconds, with which we want the
                                  //   the PID calculation to occur.
    bool inAuto;                  // * Flag letting us know if we are in Automatic or not

    long lastOutput;              // * remembering the last output is used to prevent
                                  //   reset windup.
    long lastInput;               // * we need to remember the last Input Value so we can compute
                                  //   the derivative required for the D term
    long accError;                // * the (I)ntegral term is based on the sum of error over
                                  //   time.  this variable keeps track of that
    long bias;                    // * the base output from which the PID operates
    

    
    long inMin, inSpan;           // * input and output limits, and spans.  used convert
    long outMin, outSpan;         //   real world numbers into percent span, with which
                                  //   the PID algorithm is more comfortable.

    bool justCalced;              // * flag gets set for one cycle after the pid calculates
};
#endif

Yesterday, we uploaded two post by mistake in the "Processing Front-End for the PID Library" thread. Those posts should be here:

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1243714052/36#36
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1243714052/37#37

As explained there, we made minor modifications to the PID, reducing a bit the flash footprint, and adding some small improvements for the compiler (such as const and inlines), and eliminating one of the constructors and the ConstructorCommon member by adding a default value to a param.

Regards,
Julián

Hi guys, and Brett. First of all thank you for your huge contributions on PID library, it was just the thing needed. Im kind of new to Arduino and PID's too. I have designed PID compensators in control fundamentals class but newer used it as a code on a real system :slight_smile:
I have a little problem using PID lib. Its actually very user friendly but im trying to control my quadrotor using pid lib. I use pitch, roll angle values from Ardu Imu V2 as an inputs of PID, trying to get output values as esc pwm's. I dont have problems reading from Imu but i dont get output changes. IMU values change like 300 times but output changes 1 or 2 times, or never change sometimes. I have changed the sampiling period of PID a few times but i couldnt find the solution any way. Any suggestions on that ? Thanks...

Update :
I think i found my mistake, sorry about that. I wasnt careful about PID input variables. I use integer for my pitch, roll, yaw values which is ok for me. My ( int ) pitch doesnt match PID ( double ) Input so PID.Compute calculate just once or a few times.

So, now the question is do we have stable working int version of PID lib ? Or i guess i need a conversion. Thanks...

I would also like to say many thanks to Brett and all those who have commented and gave suggestion in this forum.
I am new as well just got my first Arduino UNO 3 weeks ago.

My wife bought a used manual kiln for doing fused glass and I was able to convert it to a controller base one, using your PID library.
I ended up using this formula to get the setpoint to work for both going up in temperature and down, for the annealing phase.

Setpoint = (((double)(millis()-RampStartTime))/(m_AmntTime*3600000)*m_TempDif)+m_StartTemp;

Thanks again
Glenn

very interesting

Hey all,

I've finished up the code for a standalone sous vide controller with a small LCD screen and a 3-button interface. I'm using the one-wire DS18B20 temperature sensor for input and a solid state relay receives the output.

All the parts are here, and everything works, I just need to put it together now. I figured I'd post the code for others to critique:

#include <TimerOne.h>
#include <PID_Beta6.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal.h>
#include <Button.h>

#define ONE_WIRE_BUS 9

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

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

int controlPin = 10; 

int upButtonPin = 6;
int downButtonPin = 7;
int selButtonPin = 8;

Button upButton = Button(upButtonPin, PULLDOWN);
Button downButton = Button(downButtonPin, PULLDOWN);
Button selButton = Button(selButtonPin, PULLDOWN);

double params[4] = {140, 90,300,0};
char param_char[4] = {'S', 'P', 'I', 'D'};
double increment[4] = {1, 5, 5, 5};

double Input, Output;                             
double Setpoint = params[0];                           
double Bias = 200;

float temperature = 0;                             
int menuPos = 0;
int loopDelay = 0;

PID pid(&Input, &Output, &Setpoint, &Bias, params[1], params[2], params[3]);

void setup()
{
  /*Serial.begin(9600);*/
  pinMode(controlPin, OUTPUT);  
  
  sensors.begin();

  pid.SetOutputLimits(0,1023);   
  Output = 0; 
  pid.SetMode(AUTO); 

  Timer1.initialize();
  Timer1.pwm(controlPin, Output);
  
 
  lcd.begin(16, 2);
  lcd.clear();
  lcd.print("Arduino PID");
  lcd.setCursor(0,1);
  lcd.print("Controller");
  /*Serial.print("Arduino PID Controller\n");*/
  delay(5000);
  lcd.clear();
}

void loop()
{
  sensors.requestTemperatures();
  temperature = sensors.getTempFByIndex(0);

  Input = (double)temperature;
  if(Setpoint - Input > 3){
    Setpoint -= 5;
    pid.Compute();
    Setpoint += 5;
  }
  else{
    pid.Compute();
  }
  
  Timer1.setPwmDuty(controlPin, (int)Output);
  delay(loopDelay);
  
  lcd.setCursor(0,0);
  lcd.print("T:");
  lcd.print(temperature,1);
  lcd.setCursor(9,1);
  lcd.print("O:");
  lcd.print(Output,0);
  lcd.setCursor(0, 1);
  lcd.print("S:");
  lcd.print(Setpoint,0);

  /*Serial.print("T:");
  Serial.print(temperature);
  Serial.print("\t");
  Serial.print("O:");
  Serial.print(Output,0);
  Serial.print("\t");
  Serial.print("S:");
  Serial.print(Setpoint,0);
  Serial.print("\n");*/
  
  if(selButton.uniquePress()) {
    lcd.clear();
    for(menuPos = 0; menuPos < 4; ){
      lcd.setCursor(0,0);
      lcd.print(param_char[menuPos]);
      lcd.print(": ");
      lcd.print(params[menuPos],0);

      /*Serial.print(param_char[menuPos]);
      Serial.print(": ");
      Serial.print(params[menuPos],0);      
      Serial.print("\n");*/

      if (upButton.uniquePress()) {         
        params[menuPos] += increment[menuPos];
      } 
      if (downButton.uniquePress()) {         
        params[menuPos] -= increment[menuPos];
      } 
      if(selButton.uniquePress()) {
        menuPos++;
        lcd.clear();
      } 
    }
    Setpoint = params[0];
    pid.SetTunings(params[1], params[2], params[3]);
    lcd.clear();
  }
}

All the commented out serial stuff is from testing before my LCD came. Let me know if you have any questions or advice on something I could do better.

PS - I just realized scanning my code here that the menu loop keeps the PID from calculating, so if it was left in that mode the output would never change. I guess I'll work a button timer in there to jump back to the main loop if you don't do anything for 5 seconds or so.

Hi, I just got my Arduino last week and have been playing around with it. Im working on part of a robot that is a turntable. Basically a disc with a friction drive (1 DC motor) that needs to rotate to specific locations. I had previously coded it in basic with my own PID but i wanted it to work with my new arduino. I ran across your library and had some questions. How is it that the direction of the motor is changed? not sure if i missed something but i cant seem to find where to define a pin for direction of the motor. The code i have so far is below, the position of the turntable is read with a potentiometer. Any help is appreciated. :smiley:

/*
Created by Alan Sanchez 2010
*/

#include <PID_Beta6.h>
double Setpoint, Input, Output;
PID pid(&Input, &Output, &Setpoint,5,30,1);


//VARIABLES//

int motorpwm = 3;
int direc = 2;
int button = 12;
int pos;
int photo;

void setup() {
  
  Serial.begin(9600);
  pinMode(motorpwm, OUTPUT);
  //pinMode(direc, OUTPUT);
  pinMode(button, INPUT);    //declare button pin as input
  Input = analogRead(A0);
  Output = 0;
  pid.SetOutputLimits(0,500);
  pid.SetMode(AUTO);       //turn on PID
  Setpoint = 313;
  
}


void loop() {
  
  digitalWrite(direc, HIGH);
      if (digitalRead(button) == HIGH){       //if button is pressed go to end()
          Serial.println("button has been pressed");
            while(1) {
                //pos = analogRead(A0);      //read pot in analog pin A0
                //photo = analogRead(A1);    //read photosensor in analog pin A1
            
                Input = analogRead(A0);
                pid.Compute();
                analogWrite(motorpwm, Output/4);
            
            Serial.print("Pot Value = ");   //print pot value
            Serial.print(Input, DEC);
            Serial.print("     ");
            Serial.print("Output = "); //print photo value
            Serial.println(Output/4, DEC);
            }
    analogWrite(motorpwm, 0); 
      }
}

How is it that the direction of the motor is changed?

I understand your question to mean that the motor seems to be going backwards, forcing you away from setpoint. if this is the case, you need to change the sign of the P term. so instead of:

PID pid(&Input, &Output, &Setpoint,5,30,1);

you want:

PID pid(&Input, &Output, &Setpoint,-5,30,1);

no, what i meant to ask is how does the PID switch the direction of the motor to deal with overshoot. The way it is right now it just stops but because of inertia it wont stop at the correct spot, and if it goes past the set point there is no bringing it back.

actually if it isnt supported, i guess what i could do is switch direction depending on error something like:

if (Error<0) {
Pterm = -5
else if (Error>0)
Pterm = 5
}

no, what i meant to ask is how does the PID switch the direction of the motor to deal with overshoot

ah. in that case, take a look at Post 16 on this thread. maybe that will help.

Brett

that works! thank you

this is what the code looks like now

/*
Created by Alan Sanchez 2010
*/

#include <PID_Beta6.h>
double Setpoint, Input, Output;
PID pid(&Input, &Output, &Setpoint,3,30,5);


//VARIABLES//

int motorpwm = 5;
int direc = 4;
int button = 2;
int pos;
int photo;

void setup() {
  
  Serial.begin(9600);
  pinMode(motorpwm, OUTPUT);
  pinMode(direc, OUTPUT);
  pinMode(button, INPUT);    //declare button pin as input
  Input = analogRead(A0);
  Output = 0;
  pid.SetOutputLimits(-400,400);
  pid.SetMode(AUTO);       //turn on PID
  Setpoint = 313;
  
}


void loop() {
  
  //digitalWrite(direc, HIGH);
      if (digitalRead(button) == HIGH){       //if button is pressed go to end()
          Serial.println("button has been pressed");
            while(1) {
                //pos = analogRead(A0);      //read pot in analog pin A0
                //photo = analogRead(A1);    //read photosensor in analog pin A1
            
                Input = analogRead(A0);
                pid.Compute();
               
                if (Output < 0) {
                digitalWrite(direc, LOW);
                }
                else if (Output>0) {
                  digitalWrite(direc, HIGH);
                }
                else {
                  Output=0;
                }
                int drive = Output/4;
                analogWrite(motorpwm, drive);
            
           Serial.print("Pot Value = ");   //print pot value
           Serial.print(Input, DEC);
           Serial.print("     ");
           Serial.print("Output = "); //print photo value
           Serial.println(drive, DEC);
            }
      }
}

all thats left to do is fine tune the PID. Again thanks for the library, it saves soooo much time.

actually, ive been noticing that the Output value doesnt change when i set ranges for output from -255 to 255 . it just goes to max or min no matter what the gains are. as soon as comment out the limits it starts working again. any idea what might be causing this?

code below:

/*
Created by Alan Sanchez 2010
*/

#include <PID_Beta6.h>
double Setpoint, Input, Output;
PID pid(&Input, &Output, &Setpoint,4,35,10);


//VARIABLES//

int motorpwm = 5;
int direc = 4;
int button = 2;
int pos;
int photo;

void setup() {
  
  Serial.begin(9600);
  pinMode(motorpwm, OUTPUT);
  pinMode(direc, OUTPUT);
  pinMode(button, INPUT);    //declare button pin as input
  Input = analogRead(A0);
  Output = 0;
  pid.SetInputLimits(0,1023);
  pid.SetOutputLimits(-255,255);
  pid.SetMode(AUTO);       //turn on PID
  Setpoint = 313;
  
}


void loop() {
  
  //digitalWrite(direc, HIGH);
      if (digitalRead(button) == HIGH){       //if button is pressed go to end()
          Serial.println("button has been pressed");
            while(1) {
                //pos = analogRead(A0);      //read pot in analog pin A0
                //photo = analogRead(A1);    //read photosensor in analog pin A1
            
                Input = analogRead(A0);
                pid.Compute();
               
                if (Output < 0) {
                digitalWrite(direc, LOW);
                }
                else if (Output>0) {
                  digitalWrite(direc, HIGH);
                }
                else {
                  Output=0;
                }
                int drive = abs(Output)/4;
                analogWrite(motorpwm, drive);
            
           //Serial.print("Pot Value = ");   //print pot value
           Serial.println(Input, DEC);
           //Serial.print("     ");
           //Serial.print("Drive = "); //print photo value
           Serial.println(Output, DEC);
           //Serial.print("     ");
           //Serial.print("Direc = "); //print photo value
           //Serial.println(direc, DEC);          
          }
      }
}

Hello Brett,

First of all GREAT WORK!!

Your library has saved me a lot of time.

But i´m having a small problem. For some reason using the Processing Front End, everything works great, but when i try to edit manually the values and click to send it to Arduino, it just comes back to the original values.

As i couldn´t figure out what is wrong, since the communication seems to be ok, and i can see Processing IS sending something to arduino, and the arduino portion seems to be alright.. i had the ideia to have the best of both worlds anyway. I addes 3 pots to manually contro e P.I and D values. I would watch the values and the result on the Processing Front End, and manually adjust the values via the potentiometers.

The only problem now is that for some reason, pid.SetTunings doesn´t seem to change the values correctly.

Remember the Example 2 ? Well i´m doing exactly the same thing exept for the setpoint ramp stuff. Mine´s fixed.

Here´s the relevant portion of my code. I´m using it to stabilize de Y axis of a UAV. It´s loaded into a test platform where i have a horizontal arm being held in place by a fixed vertical arm. It has a motor on each side, and they try to keep the arm stable and leveled. Any disturbances should be imediately compensated.

 double Input, Output, Setpoint;
 PID pid(&Input, &Output, &Setpoint, 3,9,3);
 // PID pid(&Input, &Output, &Setpoint,potPin1,potPin2,potPin3);
double Outputinv;
unsigned long serialTime; //this will help us know when to talk with processing

const int buttonPin = 52;
int buttonState = 0; 

void setup() {
  static int i;
  Serial.begin(57600); 
   pinMode(13, OUTPUT);
   servoL.attach(2);
   servoR.attach(3);
  pinMode(buttonPin, INPUT);
 
   
   pid.SetInputLimits(-80,89);
   pid.SetOutputLimits(900,1200); //tell the PID to range the output from 1000 to 2000 
   Output = 900; //start the output at min and let the PID adjust it from there
  
   pid.SetMode(AUTO); //turn on the PID
   pid.SetSampleTime(10);
Setpoint = 57;
void loop() {
  
   buttonState = digitalRead(buttonPin);
  if (buttonState == HIGH) {   
    initmotors();  
    delay(2000);
  }
  
  getEstimatedInclination();

 pot1Value = analogRead(potPin1);
   pot1Value = pot1Value /100;
    pot2Value = analogRead(potPin2);
   pot2Value = pot2Value /100;
    pot3Value = analogRead(potPin3);
   pot3Value = pot3Value /100;

 pid.SetTunings(potPin1,potPin2,potPin3);

 Input = (RwEst[1]*100);
   pid.Compute();
   servoR.writeMicroseconds(Output);
   Outputinv = map(Output, 900, 1200, 1200, 900); 
   servoL.writeMicroseconds(Outputinv);
   
     if(millis()>serialTime)
  {
    SerialReceive();
    SerialSend();
    serialTime+=500;
  }
  
  
}

Any help would be much appreciated.

hello everyone, I gave up using the library for my DC motor project. I was just wasting too much time and getting nowhere. However I wrote my own PID. Code is attached below for anyone to use if they run into the same problems. It will allow you to switch motor directions to do precise positioning with a DC motor, all you have to do is adjust the gains to suit your needs.

-Alan

/*
Created by Alan Sanchez 2010
*/

//VARIABLES//

  int motorpin = 5;
  int drive;
  int direc = 4;
 // int direc;
  int button = 2;
  int pos;
  int photo;
  int target = 313;
  
//PID Variables//

  int kp = 90;
  int ki = 2;
  int kd = 100;
  int error;
  int last;
  int P;
  int I;
  int D;
  int integral = 0;
  int inthresh = 25;
  int nMet;
  int nMetReq = 5;
  int Emax = 2;
  int Vmax = 2;
  int V;
  
void setup() {
  
  Serial.begin(9600);
  pinMode(motorpin, OUTPUT);
  pinMode(direc, OUTPUT);
  pinMode(button, INPUT);    //declare button pin as input
 
}


void loop() {
 // direc = HIGH;
  //digitalWrite(direc, HIGH);
      analogWrite(motorpin,0);
      int state = digitalRead(button);
      if (state == HIGH){       //if button is pressed begin PID
          Serial.println("button has been pressed");
            while(1) {
                pos = analogRead(A0);      //read pot in analog pin A0
                photo = analogRead(A1);    //read photosensor in analog pin A1        
                drive = PID();             //get drive val from PID
                analogWrite(motorpin, drive); //send PID drive command to motor
                Serial.print("nMet =    ");
                Serial.println(nMet,DEC);
                //Check nMet, if satisfied end program.
                if(abs(error) < Emax && abs(V) < Vmax) {
                  nMet = nMet+1;
                  if(nMet > nMetReq) {
                    analogWrite(motorpin, 0);
                    Serial.println("Target reached :D");
                    end();
                  }
                }
                    
           //Serial.print("Pot Value = ");   //print pot value
           //Serial.print(pos, DEC);
           //Serial.print("     ");
           Serial.print("Drive = "); //print drive value
           Serial.print(drive, DEC);
           Serial.print("     ");
           Serial.print("Direc = "); //print direc 
           Serial.println(direc, DEC);          
            }
         
      }
}

//PID: Calculates PID drive value.
int PID()
{
    //Serial.println("inside PID");
    error = target-pos;
    //Serial.print("abs(error)=");
    //Serial.println(abs(error),DEC);
    if (abs(error) < inthresh) {
    integral = integral + error;
    }
    else {
      integral = 0;
    }
    //Serial.println(integral,DEC);
   P = error*kp;
   I = integral*ki;
   D = V*kd;
   drive = P+I+D;
     Serial.print("P+I+D=  ");
     Serial.println(drive, DEC);
     if (drive<0) {
       digitalWrite(direc, LOW);
     }
     else if (drive>0) {
       digitalWrite(direc, HIGH);
     }
     else {
       drive=0;
     }
     Serial.print("direc_inside=  ");
     Serial.println(direc, DEC);
  drive = abs(drive)/50;
   Serial.print("drive1=  ");
     Serial.println(drive, DEC);
  drive = min(drive, 170);
     Serial.print("drive2=  ");
     Serial.println(drive, DEC);
  drive = max(drive, 80);
     Serial.print("drive 3  ");
     Serial.println(drive, DEC);
  last = pos;
  return(drive);
}

//end program
int end() {
  Serial.println("Program will now end");
  while(1);
}

Never mind, i got it to work. Still don´t know what was causing the error though. I just deleted that portion of the code and re-typed it, and when i tried to compile, it did with no errors this time. Go figure.

Anyways, i´m still trying to get the tunings right. That is one hard task to acomplish.

Here´s my first test with manual tuning:

I wonder if i ever am going to see that "axis" stable.

Either i am too "intelectually challenged" , or there´s a looot of smart guys out there, because so many people seem to have built this kind of UAV´s seemingly with no big problems at all..

Maybe there´s an easier implementation of PID for a UAV that works better, with a fast response time and an easier way of tuning it.

One more thing, during the tests i have noticed that after a few minutes of continuous running of the engines, they start to stress out and they characteristics change a bit (sensitivity to commands and stuff). Isn´t there a way of implementing some kind of "dynamic tuning" system?

Thanks !

Hey all,

I've finished up the code for a standalone sous vide controller with a small LCD screen and a 3-button interface. I'm using the one-wire DS18B20 temperature sensor for input and a solid state relay receives the output.

I'm planning on doing the same thing in a few weeks here(with an lm335 though). What did you do to waterproof the sensor?