1
$\begingroup$

I was looking for an implementation of a PID controller in Java and I found this one:

https://code.google.com/p/frcteam443/source/browse/trunk/2010_Post_Season/Geisebot/src/freelancelibj/PIDController.java?r=17

So, for what I could understand about it I am using it this way:

package lol.feedback;

public class dsfdsf {

    public static void main(String[] args) throws InterruptedException {
        final PIDController pidController = new PIDController(1, 1, 1);
        pidController.setInputRange(0, 200); // The input limits
        pidController.setOutputRange(50, 100); // The output limits
        pidController.setSetpoint(120); // My target value (PID should minimize the error between the input and this value)
        pidController.enable();
        double input = 0;
        double output = 0;
        while (true) {
            input = output + 30;
            pidController.getInput(input);
            output = pidController.performPID();
            System.out.println("Input: " + input + " | Output: " + output + " | Error: " + pidController.getError());

            Thread.sleep(1000);
        }
    }

}

But he never stabilizes. He doesn't behave like a PID at all... This is the output I get:

Input: 30.0 | Output: 100.0 | Error: 90.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0
Input: 80.0 | Output: 100.0 | Error: 40.0
Input: 130.0 | Output: 50.0 | Error: -10.0

Can someone help me tell me what I am missing?

Thank you!

$\endgroup$
  • 2
    $\begingroup$ Irrelevant side note: doing control in Java is not a very good idea. Ideally, control is done with a real-time system, where you can guarantee delays. With Java, you have unpredictable delays, in the very least from the garbage collector. $\endgroup$ – Shahbaz Apr 10 '15 at 13:23
  • $\begingroup$ For this particular situation, which is temperature control, where thermal delays are huge java is fine. But thanks for the suggestion :-) $\endgroup$ – PedroD Apr 10 '15 at 13:43
3
$\begingroup$

You have a few problems that I see. The biggest problem is that your values for P, I, and D and too aggressive. This is making the PID loop oscillate from hard stop to hard stop.

You generally want to take a cautious approach to tuning a PID loop. Since PID control is feedback based, it is pretty easy to get a positive feedback loop which makes the system diverge. My preferred approach to tuning is to start with tuning P leaving I and D at 0.0. With the way your system is defined, a P value of 1.0 will try to remove all the error in a single time step. I would start with a lower P value and increase it until the system responds quickly without much overshoot. You might start with 0.1 and increase in small steps until you find something reasonable. Note, that using the P value alone, you will always have some residual error.

After tuning the P value, I would move onto the I value. The purpose of the I value is to reduce steady state error. It does this by responding to the accumulation of error over time. If the steady state error is acceptable, it is possible to not use the I term at all. Because it is integrating the error over time, it is more powerful than the P value and you will need a smaller constant. You might start with a value of 0.01 and try increasing it from there. You should be able to remove the steady state error with just P and I. Increasing the value of I will add momentum to your system and will tend to increase overshoot. The implementation you are using calculates the integral error with a simple integration, so the value of I you need will depend on your update rate for an actual physical system.

After tuning P and I, then I would move onto D. The purpose of the D term is to reduce overshoot. It does this by predicting future reduction in error based on estimating the derivative of the error. The derivative of the error can be a noisy estimate on some systems, so keep that in mind if you use D. The value you need for D will depend on the values you are using for P and I. The implementation you are using calculates the D term based on a simple difference, so the constant you need will also depend on your integration rate for an actual physical system. Using some D might allow you to be more aggressive with your usage of P and I.

You have some other problems as well. Your output range is pretty tight around the desired output. Ideally your input would be 120 which implies your ideal output would be 90. The value 90 is near the edge of your allowed output range of [50,100]. This is going to make it a bit more difficult on the PID controller.

The implementation you are using has a bug in the integral error integration. Integral error has a problem that it can accumulate without bound over time in some situations. This integral windup can cause very unintuitive outputs from PID systems. For this reason, all practical PID systems limit the magnitude of the integral error. The calculation used in the implementation only really works for outputs near 0 I think.

The implementation has:

/* Integrate the errors as long as the upcoming integrator does
   not exceed the minimum and maximum output thresholds */
if (((m_totalError + m_error) * m_I < m_maximumOutput) &&
    ((m_totalError + m_error) * m_I > m_minimumOutput)) {
    m_totalError += m_error;
}

but I think this should be:

/* Integrate the errors as long as the upcoming integrator does
   not exceed the minimum and maximum output thresholds */
/* What the output would be if integral error is updated */
double outputWithI = (m_P * m_error + m_I * (m_totalError + m_error) + m_D * (m_error - m_prevError));
if ((outputWithI < m_maximumOutput) &&
    (outputWithI > m_minimumOutput)) {
    m_totalError += m_error;
}

The problem with the original implementation is that if the integral term alone doesn't get the output into the correct range then the error isn't integrated.

A common alternative approach is to directly limit the magnitude of m_totalError and therefore the total contribution of the I term. An implementation along these lines might look something like:

m_totalError += m_error;
if (m_totalError < -m_maxTotalErrorMagnitude) {
    m_totalError = -m_maxTotalErrorMagnitude;
} else if (m_totalError > m_maxTotalErrorMagnitude) {
    m_totalError = m_maxTotalErrorMagnitude;
}

I'm more used to seeing this solution used, although I think either should work.

$\endgroup$
1
$\begingroup$

If a PID loop isn't properly "tuned" then it can result in oscillatory behavior similar to what you are seeing after the first few initial iterations. What I would try first is to change the input value only once (ie pull it out of the while loop) and then see what you get. Doing this would basically be applying what is called a Step input to your system to see how it responds. Since the input does not change after the initial "step", the resulting output over time allows you to easily determine if your tuning is damped, critically damped, or overdamped.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.