PID Will Not React Properly ABOVE Set Point

I am trying to use PID to move a motor shaft to a particular location.

For example, if the set point is 0, and I move the shaft to -200 and then apply power, the motor will bring itself towards 0, overshoot a bit, then stop at 100.

No matter which values I input for the P, I and D variables, this behavior continues. If I change DIRECT in the PID setup to REVERSE, the PID won't respond BELOW the set point.

The code I'm using is essentially just a combination of an encoder library example and a PID library example, so I'm not so sure what I could have messed up so badly.

#include <Encoder.h>
#include <PID_v1.h>

//set up motor and encoder controls
int spd = 9;
int dir = 12;
Encoder myEnc(2, 3);

//set up PID controls
double Setpoint, Input, Output;
double Kp=100, Ki=5, Kd=1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

void setup(){
  Serial.begin(9600);
  Serial.println("Basic Encoder Test:");
  
  //allow Arduino to send PWM and direction signals
  pinMode(spd, OUTPUT);
  pinMode(dir, OUTPUT);
  digitalWrite(spd, LOW);
  
  //turn PID on
  myPID.SetMode(AUTOMATIC);
}

long oldPosition  = myEnc.read();

void loop(){
  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    //Serial.println(newPosition);
  }
  Input = newPosition;

  //function to move shaft to desired position
  center(newPosition);
  
  //for debugging
  Serial.print(newPosition);
}

void center(long pos){  
  Setpoint = -100;
  
  if(pos < Setpoint){
    digitalWrite(dir, LOW);

    //send encoder position to input of PID
    Input = pos;
    myPID.Compute();

    //write the output of the PID as the speed of the motor
    analogWrite(spd, Output);
    
    //debugging
    Serial.print(" Negative ");
    Serial.println(Output);
  }
  
  if(pos > Setpoint){
    digitalWrite(dir, HIGH);
    Input = pos;
    myPID.Compute();
    analogWrite(spd, Output);
    Serial.print(" Positive ");
    Serial.println(Output);
  }
}

Thanks for your help.

The code doesn't look much like a standard PID controller. For example, what is this supposed to do?

Setpoint = -100;

It creates the set point for which the output (the motor shaft) is supposed to resolve to.

The PID part of the code is a slight modification of the example code the comes with the library:

#include <PID_v1.h>

#define PIN_INPUT 0
#define PIN_OUTPUT 3

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
double Kp=2, Ki=5, Kd=1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

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

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

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

For example, if the set point is 0, and I move the shaft to -200 and then apply power, the motor will bring itself towards 0, overshoot a bit, then stop at 100.

The setpoint is where you want the motor shaft to go. The above makes no sense, nor does the "-100" I asked about earlier. What do you mean by the bit quoted in red above?

What kind of encoder are you using that even understands the concept of a negative position??

Assuming this is a DC motor and encoder, you also need to deal with the fact that the encoder suddenly goes from its maximum value to 0 once per revolution. The standard PID will not understand that at all. Also, you PID coefficients look wrong. I have a hard time believing 100 isn't WAY too large. I would expect to see single digits there unless you have an extremely high resolution PWM. But, it all comes down to the actual hardware you're running (motor, encoder, PWM, etc.), which you've told us nothing about....

Regards,
Ray L.

jremington:
The setpoint is where you want the motor shaft to go. The above makes no sense, nor does the "-100" I asked about earlier. What do you mean by the bit quoted in red above?

I agree that the setpoint is where you want the motor to go. Setpoint = 0 means I want the shaft to return to the starting point of shaft rotation. The encoder ticks equal 0 when the program first runs and the shaft has not moved yet.

Setpoint = -100 means 100 ticks of the encoder to the left of the shaft from the starting point, positive 100 being to the right. According to RayLivingston this seems bizarre. I did not write the code for that section, it is example code from the encoder library.

RayLivingston:
What kind of encoder are you using that even understands the concept of a negative position??

Assuming this is a DC motor and encoder, you also need to deal with the fact that the encoder suddenly goes from its maximum value to 0 once per revolution. The standard PID will not understand that at all. Also, you PID coefficients look wrong. I have a hard time believing 100 isn't WAY too large. I would expect to see single digits there unless you have an extremely high resolution PWM. But, it all comes down to the actual hardware you're running (motor, encoder, PWM, etc.), which you've told us nothing about....

Regards,
Ray L.

I am using this motor and accompanying encoder: Pololu - 30:1 Metal Gearmotor 37Dx68L mm 12V with 64 CPR Encoder (Spur Pinion).

Are you telling me that an encoder cannot tell when a motor shaft is being turned counter-clockwise?

That is a good point about encoder turnover. It is going to take me a bit to wrap my head around its implications.

I agree those coefficients are bizarre. They are a result of trying many different combinations to resolve the issue. I have tried lower values as well.

foamfeathers:
I agree that the setpoint is where you want the motor to go. Setpoint = 0 means I want the shaft to return to the starting point of shaft rotation. The encoder ticks equal 0 when the program first runs and the shaft has not moved yet.

Setpoint = -100 means 100 ticks of the encoder to the left of the shaft from the starting point, positive 100 being to the right. According to RayLivingston this seems bizarre. I did not write the code for that section, it is example code from the encoder library.
I am using this motor and accompanying encoder: Pololu - 30:1 Metal Gearmotor 37Dx68L mm 12V with 64 CPR Encoder (Spur Pinion).

Are you telling me that an encoder cannot tell when a motor shaft is being turned counter-clockwise?

That is a good point about encoder turnover. It is going to take me a bit to wrap my head around its implications.

I agree those coefficients are bizarre. They are a result of trying many different combinations to resolve the issue. I have tried lower values as well.

The encoder can not tell you that the motor is running forwards or backwards as its not smart. Now as there are 2 sensors in the encoder 90 degrees apart you can use code that can work out if the motor is running forwards or backwards.

once the distance between the pulses is calculated and rotation is calculated from the pulses then you can count the pulses as plus or minus.

You seem to be treating the encoder as a pot in the code

What encoder library are you using?

Most will keep proper track of positive as well as negative rotations, as long as the encoder is wired correctly, but the position initializes to zero when the Arduino is powered up.

The two lines following the myEnd.read() accomplish nothing useful. The result from myEnc.read() should be used directly as the PID Input parameter.

  long newPosition = myEnc.read();
  if (newPosition != oldPosition) {
    oldPosition = newPosition;

Finally, get rid of the function center() and put all the important code in the main loop.

jremington:
What encoder library are you using?

I am using this encoder library: Encoder Library, for Measuring Quadarature Encoded Position or Rotation Signals

Can someone please answer this:

If the PID output is generated with this function PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);, why does the output change when the input is constant (Setpoint, Kp, Ki, Kd are constants, and Output holds the result)?

For example, I can tune the PID so it outputs a value above the setpoint, but it quickly reduces to 0 despite the input (encoder value) not changing.

This doesn't make any sense to me.

why does the output change when the input is constant (Setpoint, Kp, Ki, Kd are constants, and Output holds the result)?

There are two possibilities.

One, you are using the function incorrectly, which seems very likely.

Two, if Ki and Kd are not zero, Output depends on the history of Input values, so Output might change even if Input is a constant. Set Ki and Kd to zero at first to test this.

Correct your code, test it and then repost explaining what seems to be wrong.

No, the output is NOT generated by the calling the PID constructor. It is generated by repeatedly calling Compute, as many times as it takes to reach the specified setpoint.

The entire point of a PID is it will drive the input to match the setpoint in a controlled manner. Reaching the specified setpoint may take tens, hundreds, or thousands of calls to Compute, depending on the values of the PID coefficients. Each coefficient serves a specific purpose. The P coefficient generates an output based purely on the difference between the Setpoint and the Input. The I coefficient adds (or subtracts) from that output based on the integral of the difference between the Setpoint and the Input over all preceding calls to Compute. The D coefficient further adds (or subtracts) from that output based on the differential of the difference between the Setpoint and the Input on the current and previous calls to Compute. Setting ANY of the coefficients incorrectly can cause almost anything to happen - the output may not change at all, it may change too slowly, it may change too quickly, it may oscillate, over-shoot and under-shoot, and it may even change (seemingly) randomly forever. You can't USE a PID without understanding HOW it works, and HOW to properly set the coefficients.

Here is an excellent article on an improved version of the Arduino PID which works extremely well, when properly used.

Regards,
Ray L.

Thanks Ray, I didn't look very carefully at the code.

Hi foamfeathers,

I just saw this and this is exactly what I want to ask.
Going through the comments, I don't think it is an issue of encoder. However, I think the problem is the default control output range is from 0 to something, so that you cannot output negative.

Maybe try using SetOutputLimits() to give a negative lower boundary.
Although bear in mind that a negative value cannot be used to set your PWM.

In my case, I am using Sabertooth amplifier, which takes 2.5 V as zero output.