PowerBroker2 / ArduPID

PID library for Arduinos with greater accuracy than the legacy Arduino PID library
MIT License
85 stars 23 forks source link

Anti Windup - Limits #1

Closed 7eter closed 2 years ago

7eter commented 2 years ago

In the linked explaination the method to prevent windup was Back calculation. Limiting the the I to a certain value doest prevent windup. Of course it gives a somewhat better results than with no limit - but a more efficient way would be to introduce clamping by pausing the integration in the wrong direction during saturated controller output. For example by placing the following above output clamping in line122:

if(newOutput > outputMax || newOutput < outputMin)
    iOut -= (ki * ((curError + lastError) / 2.0)); //undo integral
PowerBroker2 commented 2 years ago

This is a good point, however I'd like to implement a more accurate approach. I'm not completely sure if this will work (compiled but untested), but this should do the trick:

void ArduPID::compute()
{
    if (timer.fire() && modeType == ON)
    {
        kp = pIn;
        ki = iIn * (timer.timeDiff * 1000.0);
        kd = dIn / (timer.timeDiff * 1000.0);

        if (direction == BACKWARD)
        {
            kp *= -1;
            ki *= -1;
            kd *= -1;
        }

        lastInput    = curInput;
        lastSetpoint = curSetpoint;
        lastError    = curError;

        curInput    = *input;
        curSetpoint = *setpoint;
        curError    = curSetpoint - curInput;

        double dInput = *input - lastInput;

        if (pOnType == P_ON_E)
            pOut = kp * curError;
        else if (pOnType == P_ON_M)
            pOut = -kp * dInput;

        dOut = -kd * dInput; // Derrivative on measurement

        double iTemp = iOut + (ki * ((curError + lastError) / 2.0)); // Trapezoidal integration
        iTemp        = constrain(iTemp, windupMin, windupMax);       // Prevent integral windup

        double outTemp = bias + pOut + dOut;                           // Output without integral
        double iMax    = constrain(outputMax - outTemp, 0, outputMax); // Maximum allowed integral term before saturating output
        double iMin    = constrain(outTemp - outputMin, outputMin, 0); // Minimum allowed integral term before saturating output

        iOut = constrain(iTemp, iMin, iMax);
        double newOutput = bias + pOut + iOut + dOut;

        newOutput = constrain(newOutput, outputMin, outputMax);
        *output   = newOutput;
    }
}

This new compute() function will determine how much leeway the integral term has before saturating the output and constrains the I term appropriately. Hopefully this will not introduce unwanted oscillations - do you have an easy setup/project to test with?

PowerBroker2 commented 2 years ago

Any thoughts on this?

7eter commented 2 years ago

This is a good point, however I'd like to implement a more accurate approach. I'm not completely sure if this will work (compiled but untested), but this should do the trick:

Sounds very promising! Sadly I don't have a fast setup to test it right now. I used your library for a simple heater. And only for Temperatures close to the set point and it works flawlessly. Right now I don't have access to the setup. But if ill ever get a new control setup I'll definitely test your updated library. Thanks!

PowerBroker2 commented 2 years ago

This issue is a low priority for me rn - life is quite busy at the moment. That being said, I'm not sure if my approach is best - I'll have to think about it more when I get time. Because of this, I haven't updated the master branch yet. I would be interested in your thoughts (plus anyone else who sees this) while I think about it.

PowerBroker2 commented 2 years ago

It's been a log time, but I finally had a minute to update. I think this is fixed in 0.2.0. Let me know if this is not the case.