-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Integral Windup #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I think there's confusion as to what it means for the integral term to
"wind up." wind up means that the integral term is growing even when the
output isn't changing. if there's a large error on startup, and there's
room for the output to move, then the integral should help it move. that's
not windup.
…On Mon, Apr 16, 2018 at 4:11 PM Terry J Myers ***@***.***> wrote:
If Output is maxed due to a very large error (like when the controller
first starts), it looks like Integral will continue to wind up (outputSum
increasing) until it ALSO hits the outMax. This woudl then require outMax
worth of integral needing to be integrated away. Why is the integral term
processed at all when the output is clamped at either end?
Isn't this what is required (line 69):
if (output< outMax && output > outMin) outputSum+= (ki * error);
I realize it will be evaluating the LAST iterations output, but this is
probably good enough.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#76>, or mute the
thread
<https://github.com/notifications/unsubscribe-auth/AAWL8sr4YuwT1MxvlGNcGW2fhx6T4eA3ks5tpPsAgaJpZM4TXLau>
.
|
In my scenario above there was no room for the output to move, in other words the output would be clamped at Max. However the integral some would continue to increment which doesn't seem desirable |
In my testing I'm getting a much more desirable response (less overshoot) by not letting the integral term increment when the output is at 100:
(Note I switched to dependant gain sets, but its basically don't sum when *myOutput < outMax). |
Much more desirable? Can you show plots before and after (including input
and output)
I'm surprised, as the output sum IS clamped to max output (as per the
windup mitigation post in my blog series.)
The integrating process thing makes me wonder if you're getting less
overshoot merely because you're stopping the output from getting all the
way to max, or staying there for as long. If that's the case, this isn't
about windup (or my library,) at all.
The trick for me is to make a library that behaves predictably and
consistently no matter where it's used. So if I see that there's
improvement here, the first thing I'm going to ask is "does it also improve
performance on, say, a throttle control"
The last thing I will suggest is trying ponm of integrating overshoot is
your pain point.
…On Mon, Apr 16, 2018, 11:16 PM Terry J Myers ***@***.***> wrote:
In my testing I'm getting a much more desirable response (less overshoot)
by not letting the integral term increment when the output is at 100:
if (*myOutput < outMax ) {
if (ki != 0) outputSum += kp * (1 / ki * error);
}
(Note I switched to dependant gain sets, but its basically don't sum when
*myOutput < outMax).
I mean..the way the code is right now, I basically gets to max, even while
P is doing all of the heavy lifting at the start of a process.
Also note that my process is mostly an integrating process (reflow oven
with as little heat loss as possible).
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#76 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAWL8uhvIbtdxxiJOkgo0kGXq3sQsqrkks5tpV6ogaJpZM4TXLau>
.
|
I have something to add to this conversation. I've been validating my PID controller by random coefficient testing. The testing has helped my tighten up my code and I want to repay all the help you've given me. Here's a case where ArduinoPID varies significantly from my reference implementation: What's happening in the graph is that the initial step causes a big shift in the output because the coefficients are crazy. In the reference implementation there is no limit on the integral sum and the integral winds up and it takes several steps under the setpoint to wind down. https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.cpp#L74 if(outputSum > outMax) outputSum= outMax;
else if(outputSum < outMin) outputSum= outMin; This means that ArduinoPID will forget a large error quickly, perhaps too quickly. I think you might consider making the integral windup limit separate from the output limit so that the integral can have a bit longer memory for large errors, especially since you have a floating point number to work with. |
Yes this makes sense. My whole issue with it was that when there is a large error (on startup) its appropriate to let P be the main driving force. This is why I didn't want any I at all. |
We should be careful when we talk about what the PID controller should do. What it should do is follow the equations we all know and understand even if that makes it unfit for a particular circumstance or condition. Inside of that @br3ttb has software engineering decisions to make, like whether and how to clamp integral windup, which is a feature of many controllers. Try commenting out the lines that I highlighted. If that helps your use case you have a need for more flexibility with integral windup. If not you might consider using a feed-forward controller to minimize first-step errors. |
Just thinking about the math in terms of what its supposed to represent, the Integral should be cleared when the error changes from negative to positive, or from positive to negative. This allows for the integral tuning term to be high enough for the integral to be useful, while at the same time preventing the integral oscillation the occurs when the offset is large for a long time. If a PR would be welcome, i would be happy to add this feature. |
I've just needed to check if anti-windup was implemented in this library because of a project so here's my thought about the thread: Windup mitigation works by limiting the integral to the maximum output while allowing it to begin decreasing as soon as the error sign changes, and clamping is a simple way of doing it. This also implies that if the Proportional term is high, which usually happens on startup (like @terryjmyers has experienced), the output will be clamped but the integral will not, because the same threshold
If you clear the integral on error sign change it's not a PID anymore, since you're suddently forgetting all the system history. I agree with @mike-matera, controllers are built on a strong math basis and messing with them according to convenience can backfire, unless they're made from scratch to fit a particular application |
Windup mitigation is definitely implemented in the library (
http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-reset-windup/
)
I think the confusion may be because the sum that I'm storing isn't the
error sum, but Sum(Ki+err) (this is why:
http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-tuning-changes/
)
if I were storing just the error sum, it would be tricky to figure out
what value to use for a clamp. how big of an error sum is too big?
because I've got Ki in there, the stored number is already in output
domain. it's is, quite literally, the baseline from which the pid is
working. if error is 0, the pidOutput = [my stored sum]. this is not the
case for implementations that do pidOutput = ...+Ki*errSum+...
because of this, I'm comfortable with how I've limited it. it may not
match exactly what you need for a specific implementation, but in terms of
a generic, robust, pid implementation. I'm happy with it.
as a side note, if you're having trouble with large outputs on startup,
you should be able to get what you need by using the SetOutputLimits()
function. or, you know, changing the code however you like.
…On Sat, Dec 22, 2018 at 3:53 PM SimoDax ***@***.***> wrote:
I've just needed to check if anti-windup was implemented in this library
because of a project so here's my thought about the thread:
Windup mitigation works by *limiting the integral to the maximum output
while allowing it to begin decreasing as soon as the error sign changes*,
and clamping is a simple way of doing it. This also implies that if the
Proportional term is high, which usually happens on startup (like
@terryjmyers <https://github.com/terryjmyers> has experienced), the
output will be clamped but the integral will not, because the same
threshold outMax is used to limit *both* terms (and rightfully so,
there's no windup in the current implementation).
As an example, there may be cases such as this: once the controlled system
is steady and very close to setpoint level, the output is only the
Integral, so it must be allowed to grow up to outMax if needed. If you
prevent its growth when the output is clamped (i.e. when P is still acting)
then it will need to do it when it's unclamped (i.e. when P has become
smaller), slowing down the controller and degrading its dynamic
performance. The benefit, in the OP particular case, come from the fact
that you know your system and built a "custom" controller for it. Not all
system are equal though
Just thinking about the math in terms of what its supposed to represent, the Integral should be cleared when the error changes from negative to positive, or from positive to negative. This allows for the integral tuning term to be high enough for the integral to be useful, while at the same time preventing the integral oscillation the occurs when the offset is large for a long time. If a PR would be welcome, i would be happy to add this feature.
If you clear the integral on error sign change it's not a PID anymore,
since you're suddently forgetting all the system history. I agree with
@mike-matera <https://github.com/mike-matera>, controllers are built on a
strong math basis and messing with them according to convenience can
backfire, unless they're made from scratch to fit a particular application
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#76 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAWL8s8lbcYu-lrLs8HHN73vSf_t3yyYks5u7pvIgaJpZM4TXLau>
.
--
Brett
|
I was having the same problem, what does it mean if the controller is still applying effort after setpoint for a large pulse with integration? How can I do integral resets? to see if its a windup issue? |
I would be nice to have an API to tinker with the internal calculations and one solely to reset the pid. |
I wound up just hacking in an integrator reset at 75% in my loop, calling |
Resetting the internal effectively drops the output to 0. A way to
accomplish this without a hack is leverage that the pid output initializes
to the output pointer on the transition from manual to auto.
Mypid.setmode(manual);
Output =0;
Mypid.setmode(automatic);
…On Sat, Aug 22, 2020, 9:49 AM Shawn A ***@***.***> wrote:
I wound up just hacking in an integrator reset at 75% in my loop, calling
SetTunings, ideally i should be using a smith predictor, but this is
working for now
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#76 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACYX4SHXVFZTOP4DDD4EBDSB7EHLANCNFSM4E24W2XA>
.
|
I am doing that also for startup for example, but i had a problem using it for this for some reason, of course I forget what... |
I have a similar issue. A large set point change on initial start up, leads to integral windup.
Here http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-reset-windup/ you explain your anti-windup, but I don't think it takes care of all cases. Particularly in this case, mentioned in the first post, it does nothing. In the above link, the second to last comment says the same as the person opening this ticket, and the same as me.
For example, in the attached image, output is clamped at 100, yet the I-term accumulates, even when the output is clamped. One could solve this with a simple hack, to reset the i-term once you approach the setpoint, but that is simply not how it is supposed to be done, I agree. But I don't understand why it is happening here, and why it is allowed to continue? Conversely, when other implementations mention "clamping", they also talk about "conditional integration", which I understand to mean: "Only integrate if the output can be changed", just as you said above For example here (ctrl+f "conditional integration") So in summary: If I am not mistaken, you say that the integral term should not increase if the output cannot be changed. That can be achieved by conditional integration. Yet you did not implement conditional integration. Just clamping the I-term to stop windup in all cases, as can be seen from the plot I am just trying understand where the error lies. It may very well be my understanding of PID controllers in general, or of your implementation in particular. Could you please elaborate on why you did not implement conditional integration? Thank you for your time |
The integral action should not grow once the limit is reached. If you're
referring to the first two images in my post, I agree; those images
illustrate the problem I'm trying to solve. The last image is the "fixed"
code.
Most of the time, people misuse the term windup. It is not: "the I term is
growing, and I don't like it!"
The latter can generally be solved by tuning, changing the output limits
themselves, or perhaps even using something instead of/in addition to pid.
There are tons of things that can be added that sometimes work, if you
tweak the levers right. I've never had luck maintaining systems like that.
2 or 3 of those "features" and you've got something that's brittle and
temperamental; that's not what an arduino library should be.
…On Mon, Sep 21, 2020, 5:31 PM Feargus ***@***.***> wrote:
I have a similar issue. A large set point change on initial start up,
leads to integral windup.
Windup mitigation is definitely implemented in the library
Here
http://brettbeauregard.com/blog/2011/04/improving-the-beginner%E2%80%99s-pid-reset-windup/
you explain your anti-windup, but I don't think it takes care of all cases.
Particularly in this case, mentioned in the first post, it does nothing. In
the above link, the second to last comment says the same as the person
opening this ticket, and the same as me.
I think there's confusion as to what it means for the integral term to
"wind up." wind up means that the integral term is growing even when the
output isn't changing
For example, in the attached image, output is clamped at 100, yet the
I-term accumulates, even when the output is clamped.
It is immediately obvious, that this effect is stronger, if the initial
setpoint (temperature) were higher, since it would take longer to reach.
Conversely, if the initial setpoint were slightly lower, output would never
have to be clamped, and everything would work "normally".
One could solve this with a simple hack, to reset the i-term once you
approach the setpoint, but that is simply not how it is supposed to be
done, I agree.
But I don't understand why it is happening here, and why it is allowed to
continue?
In the worst, case, the I-term accumulates up to 100, which is simply
ridiculous.
Conversely, when other implementations mention "clamping", they also talk
about "conditional integration", which I understand to mean: "Only
integrate if the output can be changed", just as you said above
For example here (ctrl+f "conditional integration")
https://www.mathworks.com/help/simulink/slref/anti-windup-control-using-a-pid-controller.html
or here
https://www.scilab.org/pid-anti-windup-schemes
So in summary: If I am not mistaken, you say that the integral term should
not increase if the output cannot be changed. That can be achieved by
conditional integration. Yet you did not implement conditional integration.
Just clamping the I-term to stop windup in all cases, as can be seen from
the plot
I am just trying understand where the error lies. It may very well be my
understanding of PID controllers in general, or of your implementation in
particular.
Could you please elaborate on why you did not implement conditional
integration?
Is this windup on startup expected behaviour?
Thank you for your time
[image: R1-pid]
<https://user-images.githubusercontent.com/17603149/93813843-fb7d4f00-fc53-11ea-9ba4-5b3c737a3a6d.png>
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#76 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACYX4VLIPVVIJSWZV24ET3SG6W73ANCNFSM4E24W2XA>
.
|
I'm sorry to insist, but it seems we are not completely understanding each other.
Which limit do you mean? I agree, if you mean "Once the output reaches the limit (<=> P+I+D > limit)". If you mean something else (for example I-term > limit) please say so, so I can understand where I am wrong.
I was referring to the image I attached to my post. 3 subplots. In the first, the P, I, D-terms are plotted. In the second, the output (= their sum, clamped to 0...100). The last plots the control value, a temperature, as well as the temperature setpoint. I attached the image, since earlier in these posts, you were asking for data
I only have the one, which is illustrating the same problem that the thread-starter has. A needlessly large I-term, due to a (too) large setpoint change on startup. If you look at the first subplot of the image I posted earlier, the I-term (in green) is growing, even though the P-term is >100, and the output is clamped at 100. So the I-term has no effect, yet is growing Why is this allowed to happen in your implementation? Why did you not implement conditional integration (= only allow changes to the I-term if the output is not clamped)
Yes, but it can also simply be solved by "conditional integration". I am simply trying to understand if your opinion is:
Thanks again. |
I'll need to look more closely at your images. I've been on my phone for
this whole thread, (It's when I have time for this these days, I'm afraid)
so I might not have understood.
In regards to limiting, the library does it twice. The I term is clamped
(to prevent windup) then p and d are added, then the sum is again clamped
to keep the output with the limits.
…On Tue, Sep 22, 2020, 4:40 PM Feargus ***@***.***> wrote:
I'm sorry to insist, but it seems we are not completely understanding each
other.
The integral action should not grow once the limit is reached
Which limit do you mean? I agree, if you mean "Once the output reaches the
limit (<=> P+I+D > limit)". If you mean something else (for example I-term
> limit) please say so, so I can understand where I am wrong.
If you're referring to the first two images in my post
I was referring to the image I attached to my post. 3 subplots. In the
first, the P, I, D-terms are plotted. In the second, the output (= their
sum, clamped to 0...100). The last plots the control value, a temperature,
as well as the temperature setpoint. I attached the image, since earlier in
these posts, you were asking for data
Can you show plots before and after
I only have the one, which is illustrating the same problem that the
thread-starter has. A needlessly large I-term, due to a (too) large
setpoint change on startup.
If you look at the first subplot of the image I posted earlier, the I-term
(in green) is growing, even though the P-term is >100, and the output is
clamped at 100. So the I-term has no effect, yet is growing
Why is this allowed to happen in your implementation? Why did you not
implement conditional integration (= only allow changes to the I-term if
the output is not clamped)
Does this simple change have any disadvantages I am not seeing? It's not
like This is something new, or uncommon (see the 2 references above)
In my example, it would allow the output to decrease earlier, since there
is no needlessly large I-term that has to be unwound, and some of the
overshoot could be avoided.
The latter can generally be solved by [...] using something instead of/in
addition to pid.
Yes, but it can also simply be solved by "conditional integration".
I am simply trying to understand if your opinion is:
1. "You are wrong, conditional integration solves nothing" (That way,
at least I learn something)
2. "I refuse to implement conditional integration because of X" (So I
can let this rest, and look for another implementation)
3. "Oh, okay"
Thanks again.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#76 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACYX4TC3RNMWF7AVWXTXM3SHEDSDANCNFSM4E24W2XA>
.
|
I understand. What I am trying to say, that clamping can prevent some windup, It does not prevent as much as other, simple methods. |
I think this is the issue I was having, not just overshoot, actual power being applied way after target was reached. Which should not happen in a pid controller. |
It seems no resolution to this issue has been found in the long time since most of the discussion has happened. I'm also not happy with the current anti-windup code in the library. It will only be effective with very large windup. There are other methods though that try to prevent windup earlier, see also https://en.wikipedia.org/wiki/Integral_windup.
All of these need to be understood and decided for by the user of the library of course, but it would be great if a general purpose PID library already contains them. By default they can stay disabled so the behaviour will not change for backwards compatibility. Also a good candidate for an optional feature I think is low-pass filtering the input for the derivative (but not filtering the overall input, so it can't be added from the code that uses the unchanged library). It currently is simply using the current and previous input value, which is usually not enough for noisy sensor data and will add jumpy D part contributions to the output. The filter could be a simple to implement low-pass implementation in the library (e.g. the exponential moving average like |
The integration should not be set ot zero on zero crossings. Consider tiny fluctuations around zero error: the proportional contribution is negligible, the derivative term is negligible, so the only term driving output would be the integral term. If you are tryiing to operate far from SP=0, you will produce a sawtooth as the integral sums back up to deliver the ControlOutput. |
The names for these are:
The main "possibly others" is back-calculation, essentially dynamically limiting the integral to the un-saturated portion of the CO. If you are starting far from the proportional zone, where error > CO_max/kP , then error * kP > CO_max and saturates the system by itself. Back-calculation would inhibit the integral term from contributing to the Control Output until you get into the proportional zone where there's some slack between CO_max and error*kP. https://folk.ntnu.no/skoge/prost/proceedings/PID-2018/0061.PDF and https://www.cds.caltech.edu/~murray/courses/cds101/fa02/caltech/astrom-ch6.pdf are good references. The old pneumatic controller's integrators essentially had this dynamic limit built-in because their pneumatic memory couldn't integrate beyond the Control Output pressure because it was fed by the control output pressure. |
The PID_v1_bc library is a fork of the https://github.com/br3ttb/Arduino-PID-Library / PID / PID_v1.h library that adds the back-calculation method of integral windup control. See https://github.com/drf5n/Arduino-PID-Library and br3ttb/Arduino-PID-Library#116 and br3ttb/Arduino-PID-Library#76 (comment) for more info.
You can get the tinkering ability by moving PR #133 |
If Output is maxed due to a very large error (like when the controller first starts), it looks like Integral will continue to wind up (outputSum increasing) until it ALSO hits the outMax. This woudl then require outMax worth of integral needing to be integrated away. Why is the integral term processed at all when the output is clamped at either end?
Isn't this what is required (line 69):
if (output< outMax && output > outMin) outputSum+= (ki * error);
I realize it will be evaluating the LAST iterations output, but this is probably good enough.
The text was updated successfully, but these errors were encountered: