Problem with tuning PID for motor

I am trying to tune my PID to make my motor have a consistent output:

input(pidOutput) => 100rpm

I used the following step to tune my PID:

Set all gains to zero.

1. Increase the P gain until the response to a disturbance is steady oscillation.
2. Increase the D gain until the the oscillations go away (i.e. it's critically damped).
3. Repeat steps 2 and 3 until increasing the D gain does not stop the oscillations.
4. Set P and D to the last stable values.
5. Increase the I gain until it brings you to the setpoint with the number of oscillations desired (normally zero but a quicker response can be had if you don't mind a couple oscillations of overshoot)

As I do not know the values of PID i should increase,

I started with a value of P = 0.2, D = 0 and this resulted in an output of about 36~38 RPM.

Then I increased P = 0.3, D= 0 and the output goes to about 42~46 RPM.

I then had to slowly increase my D till i found a sweet spot eg, D=0.2 till the range of RPM goes from 44~46 RPM.

After about 3 hours, I only achieved the range of about 60~62 and it is really frustrating as it is really slow and I have 2 motors and have not even tune one motor.

I have a few doubts which is:

1. Am I tuning wrongly as it is taking too long and far from my target of 100rpm.
2. And how much of a range for RPM is counted as unacceptable? I have two motors with a PID on it's own, Would a difference of 2 RPM on each wheel cause the car to go sideways?
3. After tuning (with the I gain), what output am I expected to see? will it be an output of a constant 100rpm? Or will it be 99~101 for example

The PID code I used:

double pidtune1(double rpm){
double kp, ki, kd, p, i, d, error, pid;
kp = 0.79;
ki = 0;
kd = 0.6395;

error = 100 - rpm1;
integral += error;
p = kp*error;
i = ki*integral;
d = kd*(prevRPM - rpm);
prevRPM = rpm;
pid = p+i+d;
return pid;
}


Called By:

while (distance < 100)
{
pid_output1=pidtune1(rpm1);
pid_output2=pidtune1(rpm2);
md.setSpeeds(pid_output1, pid_output2);
recalculateRPMs();
}

md.setBrakes(400,400);

• Have you tried logging the values of rpm and returned pid over time and graphing them? This might give you and us a clue as to where the tuning is going wrong. – Mark Booth Aug 30 '18 at 16:43
• Hi, from you code I would suggest to have the desired rpm as argument of the function. Then what is rpm1 ? shoudln't it be rpm ? Also why is your target 120 ? – N. Staub Aug 30 '18 at 17:08
• Did you try to use a nonzero value for I, because that should often help decrease the steady state error (because yours steps for updating P and D do not say that they should result in a zero steady state error). – fibonatic Aug 31 '18 at 0:14

Your goal is a bit unclear. In the sense that you don't really care about your motor control input, what you want is a given rotational velocity. the pid is going to give you the command (motor input) to achieve it given the desired one and the current one (plus derivative). Most likely this command is going to be a PWM signal which will be fed to a motor controller (the electronic card) which converters it to the given voltage at the motor input. Note that if you want to prevent damage you should saturate your PID output to be sure not to damage the motor with crazy commands.

Am I tuning wrongly as it is taking too long and far from my target of 100rpm.

Your timeline seem rather excessive to me, you should be able to converge way faster. I would suggest to increase the P gain at a faster rate and/or to higher values.

I have two motors with a PID on it's own, Would a difference of 2 RPM on each wheel cause the car to go sideways?

This mostly depends on your robot geometry (wheel size, inter wheel distance). If you goal is to go straight you should have a control loop (PID) ensuring it instead of trying to feedforward the same wheel velocities.

After tuning (with the I gain), what output am I expected to see? will it be an output of a constant 100rpm? Or will it be 99~101 for example

After the initial transient you should reach a steady state, if you have no noise arising from your current wheel velocity measurement. Otherwise the noise while change the proportional error and thus propagate in your PID output

• Hi, thanks for your detailed reply, for your point about "In the sense that you don't really care about your motor control input" did you mean because my input(300) was fixed, I have updated the post with the code I used to call the compute PID function – Bloopie Bloops Aug 30 '18 at 18:19
• And your point of "If you goal is to go straight you should have a control loop (PID) ensuring it instead of trying to feedforward the same wheel velocities." I thought the approach of going straight should be to calibrate both wheels to move at 100rpm at the same input, the car would move straight. Should I use 1 PID to find the error between the two wheels instead? – Bloopie Bloops Aug 30 '18 at 18:22
• for the first thing, you still have an inconsistency in the code of pidtune1. I meant more that in general you don't care which value takes the PID output, you have a desired value (100rpm) and the PID steers you there. – N. Staub Aug 30 '18 at 18:51
• concerning the second point, if you have no heading sensor it's what you should do, it's called feedforward as you assume that giving same velocities will lid to straight line behavior (which neglects some physical effects). The other approach of control is feedback, with a measurement you close the loop, i.e. you compare the desired behavior with the measured/actual one, e.g. with a PID. Both approach can be combined and usually are, as it is nicer to feedforward know effects (same velocity => go straight0 and feedforward to deal with perturbations (neglected physic) – N. Staub Aug 30 '18 at 18:54
• Combining the two method makes sense, however I can’t think of how to start, would one wheel be the current one trying to get an accurate 100rpm and the other would be one with error being rpm1-rpm2? And that error between the wheels will be used to calculate the PID value of the 2nd wheel? – Bloopie Bloops Aug 30 '18 at 19:08

Welcome to Robotics, Bloopie Bloops! You haven't stated what platform/language this is, so I'll just give some illustrative pseudo code. As Mark Booth mentioned, the typical way to evaluate/critique controller performance is by plotting the reference and output values together.

There are several glaring issues with your code, so I'll go over those. You're defining the gains okay, so I'll just repost the remainder of your code here for easy reference:

double pidtune1(double rpm){
<define kp, ki, kd>

error = 120 - rpm1;
integral += error;
p = kp*error;
i = ki*integral;
d = kd*(prevRPM - rpm);
prevRPM = rpm;
pid = p+i+d;
return pid;
}


The first problem is that there's no sample time involved. If your PID controller is running on an interrupt, which would mean the controller gets called on a fixed and repeatable interval, then it is possible to "roll" the sample time into the gains. However, this prevents you from using PID tuning guides like Ziegler-Nichols.

Consider the following:

$$\mbox{D term} = k_P(\Delta \mbox{Error}/\Delta t) \\ \mbox{D term} = \hat{k}_P \Delta\mbox{Error} \\ \hat{k}_P = k_P/\Delta t$$

You're (kind of) using the "hat" terms, $\hat{k}_I$ and $\hat{k}_P$, but again now the relationship between those terms is skewed such that you can't just set one as some empirical constant times another.

If the sample time is known (you know the interrupt interval), then you can hard-code it as a constant. If the sample time is not known, then you need to use a timer to evaluate how much time has elapsed since the previous loop.

Changes in logic flow/branches, state machines, communication delays, sensor delays, etc. can all affect when the controller is called. You need to account for those delays.

You could implement a timer with either a function call to something like elapsedMicros() or by making a variable retain its value across function calls and polling the current time:

double pidtune1(<inputs>){
static double prevTime = 0.0f;
double currentTime = micros();
double  dT = (currentTime - prevTime)*0.000001f;
// or double dT = getMicros()*0.000001f;


Next, you need to calculate the error term correctly. Currently, you're passing an input of rpm, but your error = 120 - rpm1. I would guess that rpm is probably the reference speed, but you don't provide a feedback speed! Where is rpm1 defined? Also a nit-picking kind of point, but I can't actually tell if rpm is the reference or feedback by name alone.

Consider the following changes then:

double pidtune1(double rpm_ref, double rpm_fbk){
<define control gains>
<define dT>

double error = (rpm_ref - rpm_fbk);


Finally, the last important thing to point out is that the integral and derivative terms should be the integral and derivative of the error term. Your integral term is currently

integral += error;


Which should change to include the elapsed time dT as:

integral += error*dT;


But your derivative term is currently defined to be:

(prevRPM - rpm)


This is where the actual definition of rpm and/or the ambiguous name is especially problematic. Are you looking at the change in reference speed, or are you looking at the change in feedback speed?

Regardless of the current intent, you need to be looking at the change in error, not the reference or feedback. That is, you should have:

double derivativeError = (error - prevError)/dT;


Note the above line also has the current term minus the previous term.

Putting this all together, you should have a PID controller that looks more like:

double pidtune1(double rpm_ref, double rpm_fbk){
// Define gains
double kp, ki, kd;
kp = 0.79;
ki = 0;
kd = 0.6395;

// Get elapsed time
static double prevTime = 0.0f;
double currentTime = micros();
double dT = (currentTime - prevTime)*0.000001f;
// or double dT = getMicros()*0.000001f;
// or double dT = <your interrupt interval>;

// Evaluate errors
static double integralError = 0.0f;
static double prevError = 0.0f;
double proportionalError = rpm_ref - rpm_fbk;
integralError += proportionalError*dT;
double derivativeError = (proportionalError - prevError)/dT;

// Apply gains
double p = kp*proportionalError;
double i = ki*integralError;
double d = kd*derivativeError;
double pid = p+i+d;

// Cleanup
prevTime = currentTime;
prevError = proportionalError;
return pid;
}


Again, I'm not sure of your language or platform, so the specific implementations may be a bit off, but the content is there. Be sure to use static or persistent, etc. to ensure the variable is "remembered" across function calls. I get the feeling you may be using global variables to avoid the fact that they're otherwise wiped between function calls, but that gets messy in a hurry, mostly because you can't easily re-use functions - you'd need a second set of global variables instead of creating a new instance of your class. Using static variables keeps everything contained inside the scope of that particular function.

That's all topics for another question, though. Hopefully the code above is enough to get you started. If not, please provide some graphs of the feedback speed versus the speed reference as Mark Booth mentioned and hopefully someone here can give you some more help :)

• Thanks for the detailed explanation! I came across this post brettbeauregard.com/blog/2011/04/… which gives a PID code that takes in time change and I thought it I didn't need it. I updated my post to show the code I called it, which is in a loop until the distance is reached, and distance is indeed per interrupt. I will try it out the the new PID tomorrow and hopefully tuning will be faster! – Bloopie Bloops Aug 30 '18 at 18:34
• @BloopieBloops - As I mentioned in the answer, you don't technically need the sample time included if you can ensure the loop is called at a consistent interval. HOWEVER, and this big, if you want to use standard PID tuning guides, you need to have your PID terms calculated "correctly." I would highly recommend you give the Ziegler-Nichols method a shot and see how that works for you. Keep in mind that this tuning should probably be performed with the motors in the system, i.e., with the vehicle upright and moving. – Chuck Aug 30 '18 at 19:07