PID code questions

In the pid code from First’s “Navigation” sample, there is something I don’t understand.

The integral error is combined with “pid_time”, which is unfamiliar to me, I thought the integrated error (sum) should just used as is.

motor_info[motor].pwm = (KP_P * motor_info[motor].pos_error) +
((KI_P * motor_info[motor].pos_error_i)/pid_time) +
(KD_P * motor_info[motor].pos_error_d);

This pid_time variable is reset when the target position (or velocity) is changed,
but the integral value is not also cleared. Won’t that cause problems?

I’m not 100% sure, but it seems like that’s their way of guarding against integral windup.

In case you (or another reader) isn’t familiar with integrator windup,

From this website

Integrator windup is a condition that results when integral action saturates a controller without the controller driving the error signal toward zero. If the integrator does not have saturation, then it can increase without bound, and without leading to faster system response.

I think that by dividing by pid_time, your integral term gets progressively smaller and thus prevents integrator windup.

By not resetting your integral term it seems like you’re basically saying “I’ve been trying to reach my target for a long time, and therefore applying extra power” even though you’re now moving to a new target. It feels like this should mess up your first cycle with a new target. It should correct itself on the second time through the loop when you recalculate your integral term, but it seems like you could have a huge overshoot on the first cycle.

Consider this example.

* Using Kp = 1, Ki = 1 for the sake of argument
* Ignoring the pid_time division
* Assume some scaling of output

Set Point                200            200        70
Current Pos              50             60         70
Error                    150            140         0
-------------------------------------------------------------
P term                   150            150         0 
I term                   150            290       290
-------------------------------------------------------------
P*Kp + I*Ki              300            440       290

This shows that even though you are at your setpoint, your output is still 290. Subsequent loops would decrease your I term, but it would still have to make its way back down to 0 and most likely a negative value to counteract the overshoot. With a more reasonable Ki this shouldn’t be as bad, but it is still present.

Can somebody with a better understanding chime in to help explain?

Could this be the phenomenon we saw at kickoff? When Dave gave the 'bot a nudge and it corrected itself, it overshot before it settled at the target orientation.

Maybe (if my description is correct :confused: ), but that may have just been inherent to the way that the PID loop was tuned.

The effect seen at kickoff was the loop overshooting. This can come from either too large of an integral term or too large of a proportional term or some combination of the two. The problem with PID loops is that it can be tricky to get the behavior “just right” such that the loop doesn’t oscillate, doesn’t overshoot, and has a fast rise time all while dealing with varying external forces. Most likely the problem stemmed from the fact that when Dave kicked the robot, the angular velocity that it encountered was much higher than what the robot was capable of on its own, so the PID loop was not as well tuned for it as when it was driving under its own power.

The proportional gain was set too high. The demo code was finished the evening before the kick-off and we didn’t have/spend enough time tuning the 'bot. When we got it tuned to the point where we were (reasonably) confident it wouldn’t slam into Dave’s ankle, we quit for the evening <grin>.

-Kevin

Everytime I see reference to a PID loop, I can’t help but think it’s a bit overkill for a lot of what we’re doing. Control systems aren’t my expertise so I’m not sure how accurate my statements are. At least for drive train applications, there seems to be enough friction in the system that you’d only need to use a proportional controller. Overshoot and oscillation just won’t be large problems then. I say this because a proportional controller was working perfectly fine for us for driving to the tetra the other day. Is it just because the gain wasn’t set very high? Or is it overkill?

Matt

It all depends on your application. We’ve been doing some tests on last year’s robot to get a feel for PID loop usage.

Last year we used on/off control (if left, go right; if right, go left…there were speeds for close/far) on our crab weels, which caused them to oscillate quite a bit when the wheels were not in contact with the ground. Our test was to try to get the wheel overshoot as little as possible in that configuration, while getting them there as quickly as we could.

We started with simple proportional control. That worked fine, except for the fact that it was undershooting. This was because as the wheel got closer to the target, the speed slowed so much that the motor couldn’t turn the wheels.

To remedy this we added an integral control. This helped to give the motor the extra boost that it needed as it got close to the target.

We added some derivative control just to see how it affected things, but didn’t see significant difference.

The way that it ended up being tuned when we gave up showed an overshoot of a few degrees, then locked into position. I’m sure that we could have gotten it to have less if we had spent more time on it.

Getting back to Matt’s question, when we had the robot on the ground, the added friction on the wheels helped things lock into place using simple control, but that same application without the friction needed the additional control.

It all depends on the application and what kind of performance that you need.

As someone else pointed out, dividing by a time term helps (but does not
completely eliminate) windup.

You are right, set_pos() and set_vel() really should clear the integral term.
However, if you look at the way we used the code in robot.c, we always
call set_pid_stop() which does clear all the terms.

Well, not really. The gyro turn is not really a PID loop it is just turning until
it moves to the correct orientation within an error band. We set the error band
pretty tight so it overshot just a little. Check out cmd_turn() in robot.c.

Overkill? Perhaps, but its only takes 5 lines of code to show how to do it right. If you don’t need it set the I and D gains to 0. But for controlling an arm I would rather have full control. Especially if you are going to load it up some of the time and other times have it moving with no load.

Plus now there are a few hundred more people out there that know that PID control is nothing magic. In fact its pretty simple stuff.

Enjoy…

Just a correction here: we used proportional control last year, not on/off. However, with straight proportional it couldn’t return all the way to center due to friction, so we added in some special ranges that tried to deal with that behavior better, and finally settled on something that oscillated a bit when the wheels were off the ground but was pretty stable when the robot was on the carpet.

From reading the responses in the thread, it looks like pid_time is implemented to reduce wind up. It was also mentioned that set_pos() and set_vel() reset pid_time.

If I’m calling set_vel() every 26ms from my Default_Routine(), am I destroying the I term of the PID control loop? Because I’m resetting pid_time so often?

Would it be better to not reset pid_time?

-SlimBoJones…

Maybe. It would be a simple matter to add a check so that if it’s the same value as last time, don’t do anything.

I have not checked the code yet to see if this is in the standard code, but I urge everyone who uses the I part of **PID **to make sure you put in a term that does not let the integral term build up when the robot is disabled.

Before we had this feature, we had countless times where the robot would do an erratic jump up or down as soon as it was enabled. The problem was that if someone moved the arm or the controls when the robot was disabled and the robot stayed disabled for any period of time, the integral term would build up and then cause a step jump as soon as the robot was able to move.

It was dangerous. It was spooky. And… …we broke ourselve more that once as the robot thrashed around.

The ghosts went away when we kept the integral term from building up when the robot was disabled.

ACTUALLY, while I am on the topic, the most reliable method we had was to add two subrountines to our code: IveJustBeenEnabled() and IveJustBeenDisabled()*

These two routines are good coding practice and should be part of the default code (imho).

You’d be surprised how many robot freakouts are caused by folks not taking care of business when the enter or leave the enabled state. These two make integral wind up due to being disabled a non-issue: just zero the integral term as soon as you are enabled. While you are at it, perhaps you want to set the desired state equal to the current state until someone does something appropriate on the OI like maybe put their finger on the deadman button.

Joe J.

*These two have close cousins: IveJustEnteredAutonMode() and IveJustLeftAutonMode(), MatchJustBegun(), MatchJustEnded(), etc – again these sort of routines should be required coding!

There’s a simple solution:
Double-integrate the difference between the previous error and the current error, then (optionally) divide by time. And as for the reset, the I value should be reset at the same time as the time counter.