Arduino PID Library

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?

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

There are some manuals around the web. Have you got feedback sent to the computer so you can see the response and measure overshoot, settling time, etc, etc??

I find this one to be acceptable. http://www.expertune.com/tutor.html
Wikipedia's article is a good reference for this also.

Also, if you go through ATMEL's application notes, they have application note 221 for AVR that is the implementation of a PID in C for AVR. You can get some info from that too.

There are some interger versions available e.g. google for:
AVR221: Discrete PID controller on tinyAVR and megaAVR devices

Thanks for the replies guys!

As a matter of fact i´ve found out that the analog accelerometers and Gyros i was using as an input for the PID were too noisy. Even kalman filtered data produced noise that reflected on the output as exagerated or undesirable behaviour.
I got in touch with an italian gentleman that has tested the stabilization with a similar setup to mine, and he told me he used the "Wii Nunchuk + Wii Motion Plus" IMU approach, as they are now well-known devices and output directly to I2C (no noise).

I´m currently about to finish building my second prototype and start experimenting again.

I´ll keep your advices in mind and let you know in a few days how it goes.

fiveohhh

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?

I had nothing but trouble with the LM temperature sensors. I couldn't get stability regardless of the resistors I used. Averaging might work better for you than me, but I had enough and just gave up.

The one-wire sensors from Maxim are accurate and dead easy with the existing libraries.

Anyway, I've waterproofed one or the other a few different ways. The best I've come up with so far is putting the chip in a piece of heatshrink tubing, filing the tubing with silicone sealant, letting the sealant almost set, then heat shrinking the tubing to tighten it all up. It's the best compromise between waterproof and quick temperature response that I've had so far.

Good luck!

Hi br3ttb,
Nice work out there. Library is awesome.I am still studying, not implemented yet. But I like to ask you something. There is a function that is:

void SetSampleTime(int NewSampleTime)
{
if (NewSampleTime > 0)
{
//convert the time-based tunings to reflect this change
taur *= ((double)NewSampleTime)/((double) tSample);
accError *= ((double) tSample)/((double)NewSampleTime);
taud *= ((double)NewSampleTime)/((double) tSample);
tSample = (unsigned long)NewSampleTime;
}
}

I am confused about this line: taud *= ((double)NewSampleTime)/((double) tSample);
Shouldn't it be: taud /= ((double)NewSampleTime)/((double) tSample);
or taud *= ((double)) tSample/((double) NewSampleTime);

I mean: taud=TauD/tSampleInSec , right?