PID control loops - closed loop feedback

I’ve been talking (ranting:c) about this topic on and off on CD for a while, and a couple people have asked questions about it.

I went into some detail in a thread about castors, but thought this subject desirves its own thread.

Feedback is one of the most power tools you learn about in engineering - I stuck this thread here because it involves SW and HW and your controller and sensors, and it can get pretty technical.

I started talking about it in this thread:

but for the sake of the rest of the forum, I like to continue the discussion here - it applies to more than just using castors on your robot - it can be used if you turn your wheels to steer, to postion a robot arm, to control your robots speed, how fast it turns - you could even set up a control algorythm to drive your bot with a steering wheel, so that, when you turn the wheel ten degrees, your bot turns ten degrees…

if you take engineering in college you will learn about feedback and PID control systems - but I think we can use enough of a subset for what we do in FIRST to really make a difference in how well your bot responds to your drivers commands.

(PS. I searched before I posted this, the only threads I could find on PID loops were from previous years, and they are closed now)

For another site that may prove interesting, you can try here [barello.net]. It includes an implementation of PID control in C, along with a description about it, for those interested.

http://nrg.chaosnet.org/repository/viewcode?id=0

Some sample PID code I wrote.

PID control loops… ahh. They seem to make sense sometimes but other times they don’t… Here is the control loop that I have written up. Please take a look at it and critique it. The constants have not been tuned at all. I finally got to plug it into our second arm (with working pots!!) and somehow it was outputting numbers in the 400’s…

Any help???

The two scaled values are pot values, one from our arm and one from the model. They are both scaled from 0 to 255 and are declared as unsigned longs… (don’t ask, it has to do with how we scale them…)

sint = signed int

THIS IS WHERE IT IS CALLED:

//call the control loop to set the pwm values
Arm_Upper_Out = PID(scaled_highjoint_req, scaled_highjoint_act, lasthighjoint_act, sint_highjoint_errorsum);

//keep track of the error for the control loop
sint_highjoint_errorsum = sint_highjoint_errorsum + (scaled_highjoint_req - scaled_highjoint_act);

//keep track of the last position to be able to tell how fast it is moving for the control loop
lasthighjoint_act = scaled_highjoint_act;

THIS IS THE CONTROL LOOP:

unsigned char PID(signed int request, signed int actual, signed int lastactual, signed int sumerror)
{
#define PID_PROP 2
#define PID_DER 1
#define PID_INT 1

signed int pid, error;

request = request - 127;
actual = actual - 127;
lastactual = lastactual - 127;

pid = request;

error = request - actual; //find the difference of the two values

pid = pid + (error/PID_PROP); //add the proportional part of the error

pid = pid + ((actual - lastactual)/PID_DER); // add the derivative part of the error

pid = pid + (sumerror/PID_INT); //integral part of the pid

pid = pid + 127;

return(pid);
}

This might be the problem (it had been screwing up a lot of my code 'till I realized it).

Try casting all your constants to (signed int).

Also, try casting your return value to (unsigned char) both in the return statement and in calling code.

So like:

#define (signed int) PID_PROP 2

?

If you didn’t know already, the analog inputs this year are 10 bit numbers, not 8 bit like previous years. There is a lot of important information about the analog inputs on page 16 of the 2004 programming guide from innovation first.

#define PID_PROP (signed int)2

Yeah. I read up on that and I think I’m safe there.
I’m able to print out the scaled and pre-scaled actual pot values from the arm and they output correctly/as expected, so I don’t think I have a problem there.

Have you made sure that you’re calling the PID function at a consistent time interval? If there’s code that occurs before that in the main loop that might vary in execution time, you might want to consider using an interrupt-driven timing scheme to make sure you’re calling the function at precisely the right time interval. Are the excessive output values appearing as soon as the control loop starts, or are they occurring gradually? If they’re occurring after you move the arm, particularly if you move it rapidly, try reducing Ki to make sure you’re not saturating the integral term of the PID equation.

What kind of time interval would you recommend? Every 25 ms or so?

What is the reason for adding the requested position to the PID control output? Is there a need for a feed-forward term in the control loop? Are you scaling the output after pid is returned?

Also, you might want to add overflow checking, just in case.

Overflow will be added as soon as the code itself is working. No scaling is being done to the output of the above code.

So I should initially set pid to zero?

After testing the code this evening I found that the arm model that we are using was behaving more like a joystick than a model. I’d move it forward and the arm would move forward - but never stop at a certain place, move it back to neutral and it would stop, move it backward from neutral and the arm would go the opposite direction - once again not stopping at the desired position.
Any pointers?

I believe the integral control portion should offset the initial setting of pid, but I would set it to 0, as this will most likely reduce settling time, overshoot, etc.

As for the digital response, my team saw something similar until we realized we were reading the wrong potentiometer value on the robot. We were reading a centered potentiometer, so when the input was neutral, the motor would not move. When the input was forward, the arm went forward, but the feedback wasn’t in place to slow the arm down as it approached it’s desired position because we were monitoring the wrong joint. I would recommend adding some debugging code to display both your arm pot value and your joystick value - this will also help you check to make sure the scaling is the same.

It may also be a function of the gain settings. One suggestion is to comment out the integral and derivative control inputs, leaving only the proportional control effort and attempt to tune it so that it’s satisfactory. This is the simplest loop, and often is enough to get the job done well. If you find you have error that can not be overcome (due to gravity, etc.), then you may have to add in integral control back in, or go back to a full PID. Either way, I try to start as simple as possible and add in complexity only as needed.

Hope this helps.

All the code for PID feedback loops i have seen posted on CD has used only the current and last measured values for the derivative and integral part. Has anyone tried using 3 or more frames? Is the result better?

going back more than 2 loops is only useful for the intergral part - if the arm is not moving you keep adding a small part of the error - so the control signal can actually build up over several loops before the motor move enough to eliminate the error

but for the differential part you only want this reading and the last one - the D is like giving the system a swift kick for a loop or two, so it needs to be very responsive to the most recent action of the system.

There is another type of PID control that does keep track of several previous commands and positions- its an adaptive algorythm - one that responsed differently if the mass of the system changes (like if you pick up a ball with your arm) or it the friction of the system changes (like if you move from carpet to smooth surface) and it adjusts the gains of the feedback accordingly. But I think thats really too fancy for what we need.

so far on our drivetrain and our arm motion, we are only using proportional feedback this year - its works well so we dont want to get lost in complexity.

For the typical low performance robot (I think FIRST robots count here), PD is needed for position control (proportional/derivative) and PI is needed for velocity control.

Usually with positioning servos, the control is sloppy and there is a lot of friction, so you can get away with just P term. Adding the D term allows stiffer snappier control, but, again, with the typical chain driven arm snappy isn’t the image that comes to mind.

W/velocity control, often folks get away with just the I term. Something like:

output += SomeFudgeFactor * VelocityError;

So if you are slow, the output is built up until you are at the right speed, etc.

Team #492 had a arm for manipulating the 2x ball which was pretty sloppy and oscillated terribly when overhead (chain slop). So we simply checked for range and divided the output by 16 (or some other large number) when overhead. Worked great. That is an example of “gain scheduling” where you change the gain based upon position (or some other external factor).

ah, sorry, i do understand what a derivative is, just didn’t think about the fact that previous values wont affect it.

Wouldn’t it be vice-versa? I thought that the integral is neccesary for reaching the right value.

how is this the integral? This looks proportional to me, just the current error.