PID integrator windup reset

Hi all,

It has been a while since I posted something here, but recently I picked up a new project using SimpleFOC. My current setup consists of an emax GB4114 BLDC motor with a as5047p encoder and I use an stm32f411re with the Simplefoc v1.3.3 shield to control it.

For my application I need to move the BLDC around in a stepwise motion of 8 steps per revolution. So I control it using the position control loop and set a target angle of the first step, the motor moves to this position in around 40 msec., and after another 10 msec I set a the target angle of the next step, etc. All in all, this result in a step wise continues rotation in one direction

This seems to work ok, and I am quite pleased with the step response of the system. Right after startup its behavior look like this:

For my application, I need it to be able to keep the behaving the same for hours. For the current implementation, the system makes 1/0.05 * 3600 = 72000 steps per hour (=72000/8 = 9000 revolutions per hour). However, after running the system for around 30 min, its step response behavior changes:

I have the feeling that this time dependency in behavior is caused by the integrated error of the inner velocity loop slowly increasing, since the motor only rotates in one direction, and therefore the windup is not compensated. I tested this by setting the velocity integrator to 0, which resulted in a slower dynamics, but it did not seem to change over time when running it for an hour or so. I think this kind of confirms my suspicion.

Therefore my question: is this it somehow possible to reset the integrated error of the velocity PID loop every time I set a new target angle? I would highly appreciate your input!:slight_smile:

Could this be heat related? In 30 minutes the heat build up changes the mechanics of the motor? I’m shooting in the dark here. What if you cool it a lot and try running for a long time?

Thanks for your answer. I initially thought the same, but I don’t think it is a heat thing as the motor also warms up with the integral at 0 and then the behavior stays the same (but slower and steady state error).
I also eliminated this hypothesis by turning the system off after 30 min and turning it directly on again. After initializing, the system behaved the same as at 0 min again.

1 Like

Can you then do 1 min in one direction, change direction, 1 minute in the other direction for 1 hour, this would assure the integrated angular value is about the 0 and doesn’t bump up the max or min where the number truncation becomes very pronounced? I’m shooting in the dark here.

Yes, that is something I can do indeed to further test it. Will let you know when I tried it.

Can you just set the gain of the I component of the velocity controller to zero? You don’t seem to be using it anyway. The gain must be very low, almost zero already. To actually do as you propose exactly, I would just modify the library, get in there and find the I term and make it a global variable so I can access it with dot notation and just set it to zero when I want. But obviously that’s not as good as using whatever you are supposed to do if there was provisions for this kind of thing… if you do that make sure you save a copy of the modified library :(.

Hey guys,

This is an interesting finding, I would be interested to see how you’ve solved it and why this happens.

There is a way to reset the integrator suing the PID class of the simplefoc, it has the reset() function.

You can call it on any PID class like this:

motor.PID_velocity.reset();

This function will reset the PID’s memory of previous states along the integrated values, so this might have some other unwanted effects. But at least you can try if your problem still exists. :smiley:

1 Like

Hey,

I also find this very interesting, and agree with your analysis - the PID reset is a solution, but the real question is how to apply it. I wonder if we can solve this problem in a general way?

Would it make sense, for example, to make the PID I and D terms windowed, i.e. limit their extent backwards in time?

Ah, nice that is definitely something I will try. Hopefully, this doesn’t cause too much artifacts, but let’s see. I will keep you informed.

For D I don’t think this makes sense? The derivative is only dependent on the current and previous position right? But not further back in time?

For I it could be a solution, however I think it is quite system dependent what a suitable window is, so therefore I think it makes a lot of sense to just call the integrator reset in the main loop whenever this is needed. In my case, I would like to reset when I set a new target position. I have implemented controllers similarly in the past (when switching from position control to another control mode and then back to position control for example) and it should be possible.

The easiest and a quick test if the effect is a round-off problem would probably be using double instead of float inside the PID. When I saw this post and looked at the code, I also thought that maybe integral_prev could be stepwise reduced if the error was zero, but thinking deeper about this, I am pretty sure now this is nonsense and will mess things up. It is probably better to use different gains for P and I to avoid this situation altogether (please, don’t ask me how to determine those…).

I think reducing the I term like that would defeat the purpose of the I term. Ultimately it appears to serve no purpose here, so I would set the gain to zero but what do I know… I doubt it is a rounding problem because a single precision floating point number that’s supposed to be zero is about 1/10 million I think. That wouldn’t be noticeable even over long periods.

Since this is a slow moving situation, maybe the best thing is to first just prove the hypothesis by resetting the I term or printing the value and checking that.

That was my conclusion too…

BTW: The accuracy of single precision float variables is typically just 7 decimal digits. That’s not really a lot.

Still, I do not really understand, why the I term can be really problematic unless the P and I gain are off, since the control mechanism should also eliminate any (reasonable) numerical problems as far as I understand it. But, as you said as well, what do I know… Control loops, one of the last mysteries of mankind.

I think the average velocity is lagging the commanded velocity. This error gets integrated over time and leads to a significant I term. Eventually the curve shown by the OP should have two lines that intersect each other, with an average line that would be the same for both of them, if the integral term had no wind up limit and a non zero gain. Then the average speed would be the same as the average commanded speed. You could leave it running for 10 hours and see if that happens.

Yes, I think I got a bit overenthusiastic there :slight_smile:

Fair enough, that sounds like a good solution in your case.

But in the more general case, I’m thinking about this, and I think it is a problem.

In this situation there is definitely a precision problem. As integral_prev increases it will begin to affect the precision of the calculation.

I’d like to know more…

Assuming all the gains are set positive, there is no I gain for which this problem doesn’t occur, I don’t think. In a scenario where the motor only turns one way, and the target isn’t (significantly) exceeded like in Jornel’s graph the integral I*Ts*0.5f*(error + error_prev) term will remain positive (or negative) all the time.

Furthermore, the I term is limited, as well as the output itself, so the integral_prev can’t contribute to the I-term beyond the PID limit. Of course, in situations where there is oscillation or the motor is changing direction, a large integral_prev will continue to contribute over time.

The question is whether the time integrated needs to extend back all the way to the beginning?

Assuming positive gains, the only way the integral_prev goes back down is if the target is changed or the controller overshoots, so that the error term is of opposite sign. So we’re talking about special situations here really, where the motor turns in the same direction for a long time.

But these things can also happen for example, if you command a target velocity and then brake the motor - a large I term builds up - after releasing the brake the behaviour might be quite different to beforehand. Is that wanted?

Even if you tune P and I very well, every time a new setpoint is send, there is an error between the setpoint and the current position. When you integrate the error and the movement is only in one direction, this integrated value keeps growing, until you reach the windup limit. It makes sense that the dynamics is then different compared to the behavior right at startup, where the integrated value was 0.

Yes exactly, when the motor is somehow disabled (by a brake for example indeed), but the control loop is still running, the integral needs be be reset to prevent agressive behavior when enabled again.

1 Like

From what I remember reading about pid controllers, these are common problems. Perhaps making it so you can initialize the I term and making it clear in the documentation that it’s important to do this is a good idea.

To address the precision problem near zero, maybe if the I term is close enough to zero just round it down to zero, which is still not actually zero for a floating point, but it won’t creep up that way.

If I gain is in radians per second per (radian/second) of error *second of time, how would 0.0000001 of gain ever accumulate to a serious problem? after 1 hour, 3600 seconds it would still be only 0.00036.

That’s because the integral_prev term is incremented once per call to move() - so at a rate of, lets say 5kHz main loop speed, this translates to many additions over the course of an hour. Even if they are small, it can add up, and I’ll note that the I-gains used are often very much higher than 0.0000001…

The precision problems will arise not around 0 but rather when the integral_prev becomes very large. This is an inherent problem when working with floating point numbers and trying to do maths with quantities which have very different magnitude. There are situations where integral_prev could become “stuck” at a high value even if the error takes the other sign :frowning: I admit this would be a rare case, but I still don’t like it being possible…

1 Like

Is this really the case? Doesn’t the anti windup limit kick in way earlier?

// antiwindup - limit the output
integral = _constrain(integral, -limit, limit);
   
...

// saving for the next pass
integral_prev = integral;