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 :)