Open terryjmyers opened 6 years ago
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 notifications@github.com 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 https://github.com/br3ttb/Arduino-PID-Library/issues/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:
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).
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 notifications@github.com 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 https://github.com/br3ttb/Arduino-PID-Library/issues/76#issuecomment-381821647, 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.
I've traced through your code and found that what's happening is that the integral windup is clamped to the output limits.
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. But its entirely appropriate for I to start doing SOMETHING..as its going to be needed in the end anyway. I just didn't want it ramping up to 100%. I think ideally you would want to clamp it at what the output WILL be IF you could somehow know the future. I.E. Output will eventually be 40% and stable, therefore let I get up to 40% at the start even while P is maxed out just trying to get up to the SP in the first place. Something like that.
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 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, 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 notifications@github.com 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 https://github.com/br3ttb/Arduino-PID-Library/issues/76#issuecomment-449596994, 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 SetTunings
, ideally i should be using a smith predictor, but this is working for now
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 notifications@github.com 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 https://github.com/br3ttb/Arduino-PID-Library/issues/76#issuecomment-678643605, 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 will try it again and see what it was, i didn't comment it..
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
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 notifications@github.com 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 https://github.com/br3ttb/Arduino-PID-Library/issues/76#issuecomment-696343852, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACYX4VLIPVVIJSWZV24ET3SG6W73ANCNFSM4E24W2XA .
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:
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 notifications@github.com 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:
- "You are wrong, conditional integration solves nothing" (That way, at least I learn something)
- "I refuse to implement conditional integration because of X" (So I can let this rest, and look for another implementation)
- "Oh, okay"
Thanks again.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/br3ttb/Arduino-PID-Library/issues/76#issuecomment-696967113, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACYX4TC3RNMWF7AVWXTXM3SHEDSDANCNFSM4E24W2XA .
In regards to limiting, the library does it twice. The I term is clamped (to prevent windup)
I understand. What I am trying to say, that clamping can prevent some windup, It does not prevent as much as other, simple methods. In the case discussed here, the I-term increases even though ideally it should not (because the controller output is already at the maximum, and will not increase anyway) In this case (on an initial large setpoint change), a better option than simple clamping is conditional integration, as described above.
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. Why not simply add various options for common features in advanced PID controllers and let the user decide? There are a few methods for anti-windup, some are suggested here, some not. The user might want to enable one or more of these:
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 filtered = k * filtered + (1-k)*newValue
with k e.g. 0.99) but also optionally allow for passing in a filter function pointer for custom filters. This would also benefit the PonM mode. I could supply a PR for some of these if there is interest by @br3ttb to add optional features to the library.
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.
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.
There are other methods though that try to prevent windup earlier, see also https://en.wikipedia.org/wiki/Integral_windup. Why not simply add various options for common features in advanced PID controllers and let the user decide? There are a few methods for anti-windup, some are suggested here, some not. The user might want to enable one or more of these:
- the suggested method of not changing the integrator if the output (before clamping) is outside of the allowed output range
- reset the integrator to zero on error zero-crossings
- limit the integrator to a smaller (user settable) range, separate from the output range so that wind down is faster
- only allow the integrator to change if within a smaller (user settable) error range around the setpoint. This way, the I part only starts building up shortly before or when it becomes necessary to reduce the remaining offset of the P part
- (possibly others)
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 errorkP. 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.
I would be nice to have an API to tinker with the internal calculations and one solely to reset the pid.
You can get the tinkering ability by moving outputSum
from private:
to public:
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.