Help with PID Controller

Hi everyone,

I'm an ME student and am having difficulties implement the PID controller (Using PID_V1, from Brett B.).

The project is this:
Using the Serial Monitor, imput a desired RPM velocity. This is our setpoint for the PID. The encoder has already been coded to read RPM. The PID will read the encoder as the input, and output to the motor.

Currently, I am using a potentiometer to change our desired Setpoint on the fly. Implementing the serial should not be problem later.

This is what our code looks like:

#include <PID_v1.h>
#define encoder0PinA  2
#define encoder0PinB  4
int pwmh = 11;                            
int dir = 10;
int potpin = A0;
volatile long encoder0Pos=0;
long newposition;
long oldposition = 0;
long posdelta;
long timedelta;
unsigned long newtime;
unsigned long oldtime = 0;
volatile long vel;

int Kp=1;
int Ki=10;
int Kd=1;

int rpm;
int Automatic;
int Direct;

double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, Direct);

const int sampleRate = 1;
const long serialPing = 500;

void setup()
{
  pinMode(encoder0PinA, INPUT);
  digitalWrite(encoder0PinA, HIGH);       
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinB, HIGH);      
  attachInterrupt(0, doEncoder, RISING);
  pinMode(pwmh, OUTPUT);                 
  pinMode(dir, OUTPUT);                   
  pinMode(potpin, INPUT);                 
  Serial.begin (9600);
  Serial.println("start"); 

  myPID.SetMode(Automatic);
  myPID.SetSampleTime(sampleRate);
  
}

void loop()
{
  
  Setpoint = map(analogRead(potpin), 0, 1023, 0, 255);  
  Serial.println(Setpoint);
  newposition = encoder0Pos;
  newtime = millis();
  posdelta=abs(newposition-oldposition);
  timedelta=newtime-oldtime;


  oldposition = newposition;
  oldtime = newtime;

  vel = ((posdelta*60000) / timedelta)/24;
  Serial.print ("RPM = ");
  Serial.println (vel);

  Input = vel;
  myPID.Compute();
  myPID.SetTunings(Kp, Ki, Kd);
  analogWrite(pwmh, Output);


  delay(1000);
}

void doEncoder()
{
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }
}

I am by no means a good programmer/coder. This code is the result of inspiration from a bunch of different examples.

ANY help would be greatly appreciated. I've attached a picture. I'm currently using a Polulu motor driver to control the motor.

Hi, what is the problem you are having?
I notice that the motor driver PCB has GND, DIR and PWM, does it need a V+ to power the control circuit?
You have supply for the motor, check the manual/instructions for the PCB.
Have you just programmed a simple control sketch first, just to get the controller working.
Forget PID until later, after you have got the motor driver working with a pot and no encoder feedback.
Then add the rest in stages.

Tom...... :slight_smile:
PS a spec or manual link for the motor driver PCB would help.

TomGeorge:
Hi, what is the problem you are having?
I notice that the motor driver PCB has GND, DIR and PWM, does it need a V+ to power the control circuit?
You have supply for the motor, check the manual/instructions for the PCB.
Have you just programmed a simple control sketch first, just to get the controller working.
Forget PID until later, after you have got the motor driver working with a pot and no encoder feedback.
Then add the rest in stages.

Tom...... :slight_smile:
PS a spec or manual link for the motor driver PCB would help.

Thanks for the help Tom.

The motor driver has GND, DIR, and PWM. It has a separate supply which is connected but not shown in the picture. I've gotten the motor to work properly already. DIR is either HIGH or LOW for forward or reverse, or vice versa.

My problem is that the implementation of PID I have is not working. Everything else, the motor, the driver, the encoder, i've gotten them all to work together with a potentiometer. But I have not been able to implement the PID successfully.

This is what I have WITHOUT the PID and it is working perfectly. It outputs the RPM of the motor.

#define encoder0PinA  2
#define encoder0PinB  4
int pwmh = 11;                            
int dir = 10;
int potpin = A0;
volatile long encoder0Pos=0;
long newposition;
long oldposition = 0;
long posdelta;
long timedelta;
unsigned long newtime;
unsigned long oldtime = 0;
volatile long vel;

void setup()
{
  pinMode(encoder0PinA, INPUT);
  digitalWrite(encoder0PinA, HIGH);       
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinB, HIGH);      
  attachInterrupt(0, doEncoder, RISING);
  pinMode(pwmh, OUTPUT);                 
  pinMode(dir, OUTPUT);                   
  pinMode(potpin, INPUT);                 
  Serial.begin (9600);
  Serial.println("start");               
}

void loop()
{
  
   int val = analogRead(potpin);         
                                            
   val = map(val, 0, 1023, 0, 255);      
                                          
                                     
   if (val >140){                        
     analogWrite(pwmh, (val - 140 ));     
     digitalWrite(dir, LOW);             
   }                                      
   if (val <110){                        
                                        
     analogWrite(pwmh, abs(val - 109));   
     digitalWrite(dir, HIGH);             
   }                                      
   if (val >=110 && val <=140){           
                                         
     analogWrite (pwmh, 0);
     digitalWrite( dir, LOW);
   }
     
  
  newposition = encoder0Pos;
  newtime = millis();
  posdelta=abs(newposition-oldposition);
  timedelta=newtime-oldtime;


  oldposition = newposition;
  oldtime = newtime;

  vel = ((posdelta*60000) / timedelta)/24;
  Serial.print ("RPM = ");
  Serial.println (vel);


  delay(1000);
}

void doEncoder()
{
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }
}

Hi, okay, how many pulses per turn is the encoder?
What rpm range are you looking for?
I gather you are trying to do forward/stop/reverse with the setpoint pot.
0-110 say forward
110-140 stop
140-255 reverse.
You are giving the pid a setpoint value between 110 and 140 for stop, stop is zero velocity.
The PID will try to get the velocity to 110-140 not zero.
You have scaled okay but no offfset.
Try setpint 0 to 255
velocity 0 to 225
ie forget reverse for the moment.

Tom..... :slight_smile:

TomGeorge:
Hi, okay, how many pulses per turn is the encoder?
What rpm range are you looking for?
I gather you are trying to do forward/stop/reverse with the setpoint pot.
0-110 say forward
110-140 stop
140-255 reverse.
You are giving the pid a setpoint value between 110 and 140 for stop, stop is zero velocity.
The PID will try to get the velocity to 110-140 not zero.
You have scaled okay but no offfset.
Try setpint 0 to 255
velocity 0 to 225
ie forget reverse for the moment.

Tom..... :slight_smile:

If i remember correctly, the pulses per turn is 24, and in my first post, you can see the most up to date version of this code. Where I am mapping the potentiometer from 0 -255 without reverse, i then use that as my Setpoint.

Hi, please don't go back and edit your first post each time you want to change your sketch.
This disjoints the flow of the thread, as the problems mentioned after the sketch will not be relevant to that sketch anymore.
If you change your sketch, please repost it in a new post, this way we can see the evolution of your project, AND the thread can be used by others to help with similar problems they may have.

Please now post your current sketch in a new post, thank-you.

Tom....... :slight_smile:

When you go back to forward and backward control:

http://playground.arduino.cc/Code/PIDLibrarySetOutputLimits
"By default this [Output] range is 0-255: the arduino PWM range."

You might want to change that to +/- 255. Then you can use the sign to set the Direction pin and the magnitude to set the PWM pin.

TomGeorge:
Hi, please don't go back and edit your first post each time you want to change your sketch.
This disjoints the flow of the thread, as the problems mentioned after the sketch will not be relevant to that sketch anymore.
If you change your sketch, please repost it in a new post, this way we can see the evolution of your project, AND the thread can be used by others to help with similar problems they may have.

Please now post your current sketch in a new post, thank-you.

Tom....... :slight_smile:

Sorry, This is my most up-to-date sketch.

#include <PID_v1.h>
#define encoder0PinA  2
#define encoder0PinB  4
int pwmh = 11;                            
int dir = 10;
int potpin = A0;
volatile long encoder0Pos=0;
long newposition;
long oldposition = 0;
long posdelta;
long timedelta;
unsigned long newtime;
unsigned long oldtime = 0;
volatile long vel;

int Kp=1;
int Ki=10;
int Kd=1;

int rpm;
int Automatic;
int Direct;

double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, Direct);

const int sampleRate = 1;
const long serialPing = 500;

void setup()
{
  pinMode(encoder0PinA, INPUT);
  digitalWrite(encoder0PinA, HIGH);       
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinB, HIGH);      
  attachInterrupt(0, doEncoder, RISING);
  pinMode(pwmh, OUTPUT);                 
  pinMode(dir, OUTPUT);                   
  pinMode(potpin, INPUT);                 
  Serial.begin (9600);
  Serial.println("start"); 

  myPID.SetMode(Automatic);
  myPID.SetSampleTime(sampleRate);
  
}

void loop()
{
  
  Setpoint = map(analogRead(potpin), 0, 1023, 0, 255);  
  Serial.println(Setpoint);
  newposition = encoder0Pos;
  newtime = millis();
  posdelta=abs(newposition-oldposition);
  timedelta=newtime-oldtime;


  oldposition = newposition;
  oldtime = newtime;

  vel = ((posdelta*60000) / timedelta)/24;
  Serial.print ("RPM = ");
  Serial.println (vel);

  Input = vel;
  myPID.Compute();
  myPID.SetTunings(Kp, Ki, Kd);
  analogWrite(pwmh, Output);


  delay(1000);
}

void doEncoder()
{
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }
}

I think my main problem here is that I have my PID implemented wrong :(. Before I inputted PID, the potentiometer was varying the power fine, the encoder was outputting RPM fine, but my implementation of PID is not working :frowning:

put in a serial.write before the pid.compute() to confirm the value of the input and after the compute() to display your output to confirm both are expected.

why are you setting the p,i and d each time i cant see anything changing them?

Ok, so I my potentiometer is printing correctly, I set it to 50 and nothing happens, the Input is 0 as there is no velocity from the encoder. Should my input be my potentiometer reading? I have the Setpoint as the potentiometer reading, the output is power to the pwmh. Should I flip my output and input?

Ok, so I set up a Serial.print for my PID Output. The Output is Zero.

#include <PID_v1.h>
#define encoder0PinA  2
#define encoder0PinB  4
int pwmh = 11;                            
int dir = 10;
int potpin = A0;
volatile long encoder0Pos=0;
long newposition;
long oldposition = 0;
long posdelta;
long timedelta;
unsigned long newtime;
unsigned long oldtime = 0;
volatile long vel;

int Kp=1;
int Ki=10;
int Kd=1;

int rpm;
int Automatic;
int Direct;

double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, Direct);

const int sampleRate = 1;
const long serialPing = 500;

void setup()
{
  pinMode(encoder0PinA, INPUT);
  digitalWrite(encoder0PinA, HIGH);       
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinB, HIGH);      
  attachInterrupt(0, doEncoder, RISING);
  pinMode(pwmh, OUTPUT);                 
  pinMode(dir, OUTPUT);                   
  pinMode(potpin, INPUT);                 
  Serial.begin (9600);
  Serial.println("start"); 

  myPID.SetMode(Automatic);
  myPID.SetSampleTime(sampleRate);
  
}

void loop()
{
  
  Setpoint = map(analogRead(potpin), 0, 1023, 0, 255);  
  Serial.println(Setpoint);
  newposition = encoder0Pos;
  newtime = millis();
  posdelta=abs(newposition-oldposition);
  timedelta=newtime-oldtime;


  oldposition = newposition;
  oldtime = newtime;

  vel = ((posdelta*60000) / timedelta)/24;
  Serial.print ("RPM = ");
  Serial.println (vel);

  Input = vel;
  Serial.print("Input =");
  Serial.println(Input);
  myPID.SetTunings(Kp, Ki, Kd);
  myPID.Compute();
  Serial.print("Output = ");
  Serial.println(Output);
  analogWrite(pwmh, Output);
  Serial.println("");
  Serial.println("");

  delay(1000);
}

void doEncoder()
{
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }
}

I've attached a picture of what I see in the Serial Monitor.

I have it all set up right now. This is what it looks like. The power supply is also shown.

i agree you would expect a non zero output, so the only 2 things i can see that could effect the pid compute() are the p, i and d values, how did you come by those? and the sample rate, why set the sample rate so low the default is 200 milliseconds? linky

have you tried modding the basic example pid code

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

#include <PID_v1.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, DIRECT);

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

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
}

void loop()
{
  Input = analogRead(0);
  myPID.Compute();
  analogWrite(3,Output);
}

with some more serial writes to reflect input output with a fixed setpoint with your i/o pins??

i used a pid with temp control ok, using the current temp as the input, so with speed control i asume the input would be the current speed.

Ok, well, some progress, I just realized, Direct, and Automatic are supposed to be ALL CAPS! i changed it and now the motor is running, It does a couple of revolutions then stops, and starts and stops and starts and stops. It does this over and over again. Not sure what's going on now. My output is only 255 also :confused:

Hi, does the output jumping from 0 to 255 to 0 to 255?
If it does with 50 as setpoint, then I would think that you have an overshooting situation.
Read up on PID and see which parameter you will need to adjust to minimise the problem.
Good to see you have some output.
Tom..... :slight_smile:

Yup, I looks like a lot of overshoot, i'm reduced the power from 5v to 1v and it is still overshooting.

This is what I'm using. What is a good guide to properly tuning PID?

#include <PID_v1.h>
#define encoder0PinA  2
#define encoder0PinB  4
int pwmh = 11;                            
int dir = 10;
int potpin = A0;
volatile long encoder0Pos=0;
long newposition;
long oldposition = 0;
long posdelta;
long timedelta;
unsigned long newtime;
unsigned long oldtime = 0;
volatile long vel;

int Kp=10;
int Ki=1;
int Kd=1;

int rpm;

double Setpoint, Input, Output;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

const int sampleRate = 200;
const long serialPing = 500;

void setup()
{
  pinMode(encoder0PinA, INPUT);
  digitalWrite(encoder0PinA, HIGH);       
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinB, HIGH);      
  attachInterrupt(0, doEncoder, RISING);
  pinMode(pwmh, OUTPUT);                 
  pinMode(dir, OUTPUT);                   
  pinMode(potpin, INPUT);                 
  Serial.begin (9600);

  myPID.SetMode(AUTOMATIC);
  myPID.SetSampleTime(sampleRate);
  
}

void loop()
{
  newposition = encoder0Pos;                             //Beginning of Encoder code
  newtime = millis();
  posdelta=abs(newposition-oldposition);
  timedelta=newtime-oldtime;
  oldposition = newposition;
  oldtime = newtime;
  vel = ((posdelta*60000) / timedelta)/24;
  Serial.print ("RPM = ");
  Serial.println (vel);                                  //Print RPM velocity

  Input = vel;                                           //PID Input
  Serial.print("Input =");
  Serial.println(Input);
  
  Setpoint = map(analogRead(potpin), 0, 1023, 0, 255);                                          //PID Setpoint
  Serial.print("Setpoint =");
  Serial.println(Setpoint);
  
  myPID.SetTunings(Kp, Ki, Kd);
  myPID.Compute();
  Serial.print("Output = ");
  Serial.println(Output);
  analogWrite(pwmh,(Output/5));                              //PID Output
  Serial.println("");
  Serial.println("");

}

void doEncoder()
{
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }
}

Hi,

The youtube looks good, about how to start to tune from scratch.

http://www.expertune.com/tutor.aspx

http://www.omega.com/temperature/z/pdf/z115-117.pdf

Hope this helps mate, got to go too work........Tom....... :slight_smile: