Code iconI’ve been dancing around writing a post on PID controllers for quite some time, and now it’s finally here! If you’re new to robotics, you may have heard people talking about PID and not known exactly what they were referring to, but if you’re anything like me at that stage, it all sounded pretty complicated.

This is another one of my long posts, so here’s your handy-dandy Table O’Contents.

PID: What It Is

The acronym PID stands for “Proportional, Integral, Derivative” (more detail below), and it’s a computer coding algorithm that makes small adjustments to motor power in order to achieve more precise and smooth robot movement.

PID algorithms are used in combination with sensors: the algorithm checks the value of a given sensor (the “process variable” in PID-speak) and compares it to a pre-determined target (called the “set-point” in PID-speak), and changes the motor power based on those sensor readings. It does this process over and over and over in a while loop. If you don’t have any sensors, you won’t be using PID: the algorithm needs to measure something that’s happening in real-time on the robot, and to measure stuff, you need sensors. On a drive train, you’d use shaft encoders; for a lifting arm, you’d probably use a potentiometer; for turning, a gyro, etc.  

Once your team gets their feet wet in programming, understanding PID should be pretty high on your to-do list. Why? Because using PID coding is the way your team is going to win the autonomous bonus.

PID-Speak

I mention “PID-speak” a few times in this post, but generally avoid these technical terms, because phrases like “set point” and “process variable” don’t carry inherent meaning in my brain; I find it easier to talk instead about a “target” and a “sensor value”. 

Why You Want to Use It

Driving Straight

VEX motor with shaft coupler

Image source: VEX Robotics

VEX motors (V4 393-motors, as of this writing) are all a little different from each other, even right out of the box. Once they get a little wear & tear on them. sometimes they end up being quite a lot different. If you’ve got motors on the left & right sides of your chassis that are not perfectly matched, your robot will drift over to one side. Alternately, a mismatch could be caused by one side of your chassis encountering more friction than the other—maybe the spacers on the shafts of one side don’t have *quite* enough free play to allow the wheels to spin optimally. 

At the start of In The Zone season, our robots just used basic turn-motor-on/turn-motor-off coding in autonomous driving, and we had a really low success rate, because ITZ in particular required a lot of precision to score anything. You had to drive all the way down the side of the field (through a forest of cones) to reach a mobile goal; putting a cone on the stationary tower was not very far away, but required a lot of precision to hit a small target.

Once we got enough knowledge and had the time to devote to it, our PID algorithm for driving straight resulted in near-perfect execution of our autonomous routine.

VEX In The Zone field setup

Shooting Straight / Lifting Straight

The same type of PID algorithm used for driving straight is also used to match left-side and right-side speeds on a double flywheel ball shooter so you can shoot straight.

It can also be used to power the motors on each side of a lift, so that both sides go up in unison. (You’ve probably seen instances of people *not* using PID on their DR4B lifts, and those lifts being very wobbly side-to-side.)

Holding an Arm In Place

In the Starstruck season, our autonomous routine picked up some stars near the field perimeter, held them aloft in our forklift, drove over to the fence, and then dumped them over. Holding the arm in place was another use of PID coding.

If you raise a robot arm and then turn off the motor, gravity will take over and the arm will fall to the ground (sometimes slowly, sometimes immediately). If you have a fixed, known weight that you’re holding, you might be able to apply a constant motor power in order to keep it steady, but even that route is a little iffy.

In Starstruck, we were usually holding between 2 and 4 stars in our forklift. Since this was an unknown weight (sometimes we scooped them all up, sometimes we missed a few), we were not able to apply a fixed amount of motor power to hold it steady. So we needed to add *just* the right amount of power over about a 5-to-7-second time span while we drove to the fence, accomplished by increasing or reducing the motor power by small amounts via PID to hold it at a fixed height, measured by a potentiometer.

Slowing Down as You Approach a Target

This one overlaps somewhat with holding an arm in place, but can be used with various types of driving also.

When raising an arm to a set height and holding it there, let’s say when the potentiometer reads 700, you’ll want to slow down as you approach the 700-mark. If you go full-speed up (or down) to 700, and turn off the motors, it will overshoot, every. single. time. So you’d want to apply less and less power to the motors as you approach the target so that you’re applying *just* enough to hold the arm where it is.

When driving, if you want to travel 800 encoder clicks down the field, simply turning off the motors at 800 clicks will also result in the robot overshooting the target, every. single. time. So a better option is to slow down as you get close to the 800 clicks, so the robot is going pretty slowly when it gets close to 800 clicks, and you can then “put on the brakes”, reversing the motors briefly to make a hard stop (see my other post that covers this type of stopping in autonomous).

The same concept applies to turning with a gyro sensor. You’ll want to slow the robot down so you can make a short breaking-stop when you get to xx-degrees turned. Ditto for the ultrasonic sensor; moving to a set distance from an object also requires slowing up as you’re getting close in order to avoid overshooting.

Line Following

Using the VEX line trackers, PID is absolutely required. To follow a line, you must make frequent, small adjustments in left side/right side power to stay on course. Making anything other than constant, small adjustments will result in the robot doing zig-zags across the line, most likely large enough to lose the line completely. See my lengthy post on the line tracker sensor for details about the different ways to use these sensors to follow a line.

Important: Less Power is Better!

Many teams when they’re starting out will set motor power to the maximum (127) when programming autonomous. What a large number of those many-teams figure out, sooner or later, is that too-fast speeds lead to overshooting your target or a robot that’s difficult to make consistent.

As described in my post about cortex ports 1 & 10, VEX motor speeds flatten out and barely increase at all past power 85 or so (see graph below). That means that setting a motor in auton programming to 80 is really not that different than setting it to 127, even though it seems like you reduced the power a lot. So it’s best to think of 85 as “maximum”, and reduce the power even more. Starting around power = 60 for many autonomous movements, including the ones described here, will contribute greatly to your robot’s auton performance, consistency, and reliability.

Motor controller 269 vs 393 motor

Motor power vs. speed. Photo credit: @jpearman, VEX Forum

Measuring “Error”

The entire PID algorithm relies on a measurement that compares the current value of a sensor to the target value (a.k.a. “set-point”) for the sensor; this amount is referred to as the “error”. For example, if I want to drive straight, I’d use the left-side encoder reading as my target value, and make +/- adjustments to the right side power so that the right-side encoder count stays as close as possible to the target (the left-side count).

error = target – sensorValue

Every other part of the PID algorithm relies on this first calculation.

The Components: P, I, and D

As noted above, PID stands for “Proportional, Integral, Derivative”:

  • the Proportional component makes a change to the motor power that is proportional to the error value—big error, bigger adjustment; smaller error, smaller adjustment. Think of it as a conversion factor, to turn sensor values into motor power values, so that you can use PID with any type of sensor data. That conversion factor is known as “Kp”. 

Proportional adjustment = error * Kp

  • the Integral component is the running total of all errors since you started your while-loop. If the 2 sides of a drive train are roughly equal, the Integral will be small, because it’s adding together a whole bunch of + errors and – errors (or a whole bunch of relatively small + errors, for example). If one side is consistently underpowered, and not getting enough boost from the Proportional adjustment, then the Integral grows very very large. The Integral value gets multiplied by another constant known as “Ki”.

Integral = Integral + error
Integral adjustment = Integral * Ki

  • the Derivative component compares the error from this iteration of the while loop to the previous iteration. Is the error getting bigger or getting smaller? The derivative makes a very small additional adjustment depending on the direction things are heading by multiplying this difference by a third constant, known as “Kd”.

Derivative = error – lastError
Derivative adjustment = Derivative * Kd

Putting it all together, for driving straight, my right-side power would now be:

rightPower = leftPower + (error * Kp) + (Integral * Ki) + (Derivative * Kd)

For holding an arm in place, the motor power is:

power = (error * Kp) + (Integral * Ki) + (Derivative * Kd)

I urge you to download and read George Gillard’s An Introduction to PID Controllers, an amazingly helpful document. Keep in mind that the example used throughout his document is the case of slowing down as you approach a target (and not driving straight). He walks you through sample code for each step, and also points out the issues and roadblocks that can occur in certain situations.

More PID-Speak

In some PID descriptions, these 3 constants—Kp, Ki, Kd—are referred to as “gains”, and sometime referred to as “P-gain” or “proportional gain”. Since referring to a constant number as “gain” doesn’t (once again) make much inherent sense to my brain, I mention it here so you can better understand other online resources, but don’t make a habit of using it in my description.

Mix-N-Match

It is not required that you use both the I and D components. You can use P-only, PI, PD, or PID, depending on your usage; more on some of these cases below.

How Can *I* Do This?

There are many resources online that walk you through the information above, and if you’re here, you’ve probably already read some of them. When I was learning this concept, I could understand the theory, but had a hard time leaping from theory to actually doing it.

RobotC: Use the Datalog

If you’re using RobotC (or another software that supports it), first familiarize yourself with the datalog (such as by reading my other post on datalogging). Trying to tune a PID without a system where you can easily see and graph the values of variables in each iteration of the loop is VERY hard. If you’re using easyC, you’ll be making use of the print-to-screen option, and then have to paste that data into an XL spreadsheet and set up your own graphs; it’s worth it to figure out how to do this in easyC before you start.

The RobotC datalog (screenshot below) allows you to say “Hey, keep track of these 5 variables each time through the loop, and display it on the screen”, and then gives you one-click graphing capability. You can also export the data to XL or save graphs as image files.

Datalog and graph screenshot

The Basic Code

PID coding is a great candidate for RobotC’s tasks, as it is something that needs to be running in the background, simultaneously while the robot is doing other things, like driving to where it will put the game objects down after holding them aloft. See my other post on RobotC tasks for more details.

Driving Straight

For driving straight, here’s the basic format of the code.

  • left-side power is the “master”; right-side power is the “slave”, which follows the master
  • targetClicks is the distance you’d like to drive down the field
  • error will compare the left-side encoder clicks (the “target” or “set-point”) and right-side encoder clicks (the “process variable”)
  • Kp, Ki, and Kd are discussed in detail later
Kp = someValue;
Ki = someValue;
Kd = someValue;

error = 0;
lastError = 0;
integral = 0;
derivative = 0;

while ( leftEncoderClicks < targetClicks ) {
   error = leftEncoderClicks - rightEncoderClicks;
   integral = integral + error;
   derivative = error - lastError;

   rightPower = leftPower + (error * Kp) 
                          + (integral * Ki) 
                          + (derivative * Kd);

   // write variables to the datalog here;
   // recommend tracking rightPower, error, integral, derivative

   // do this step after you write the datalog,
   // in case you want to monitor lastError
   lastError = error;

  // don't hog CPU 
   wait1Msec(20);
}

rightPower = 0;
leftPower = 0;

Walking through what happens here, let’s say the right side is lagging behind, then error (left clicks – right clicks) will be a positive number, and so the calculation will make rightPower larger than leftPower (rightPower = leftPower + error * Kp …), helping the right side to catch up. The reverse happens when the right side is too far ahead.

Note: The steps described below for determining the values for Kp, Ki, and Kd all use this example of driving straight, as it happens to be the situation my team uses most frequently. For holding an arm in place, you will have different values for these constants because the scale of the potentiometer is different than that of an encoder (and hence the size of the error will be different, requiring a different Kp value). See this VEX Forum thread for some other K-values that people have used for various coding situations.

Holding an Arm

For holding an arm in place, while loop would look something like the following.

  • Note that the condition to check in the while-statement is a little vague, because the end of the robot’s holding an arm in place will probably depend on other conditions, such as time elapsed or encoder clicks on the drive train (pick up item, hold it, drive somewhere, drop it).
  • The error value now compares a sensor value (such as a potentiometer reading) to a fixed targetHeight value.
  • Motor power is calculated as the sum of the PID components.
while ( some condition to check ) {
   error = targetHeight - sensorValue;
   integral = integral + error;
   derivative = error - lastError;

   power = (error * Kp)
           + (integral * Ki)
           + (derivative * Kd);

   // the rest here is the same as above
}

Step 1: Set All K-values to 0

First thing, have your robot drive down the field without any PID correction. You do this by setting Kp, Ki, and Kd to zero, resulting in rightPower = leftPower. Then look at the datalog, graphing the values for error, integral, derivative. This step will tell you where you stand — how far off is one side from the other?

Step 2: Set Kp

Part of the large jump from theory to practice in PID is figuring out where to start, given that you have to invent the values for Kp, Ki, and Kd yourself. I kind of mentally gave up when I learned that part, because it was a complete mystery for me where to begin.

The first variable to set is Kp; leave the other 2 at 0, so that rightPower = leftPower + error * Kp. While the code will be calculating integral and derivative, they are not being included into the power adjustment yet.

Start by setting Kp somewhere between 0.2 and 0.8. Using your test data from Step 1, you can see how much of a problem you’re trying to correct. If it seems like a big problem, start at 0.8 (you may have to go up from here; we did last year because our robot was so lopsided). If it seems like a reasonably small difference, go lower.

Run the robot down the field again, and look at your graph of error; what happens? Does the value oscillate, with error sometimes above 0 and sometimes below 0?  Is it always above 0? If so, you’ll need to increase Kp because the fixes being implemented are not enough.

You’ll want to adjust Kp until you get a graph that looks like a nice sine wave (or something close to a sine wave). Keep adjusting Kp until you get the best result possible — any change of Kp up or down does not produce better output.

Step 3: Set Ki

Before you do anything with Ki, take a look at your graph from the end of the previous step, and set the datalog to show you the graph of the integral as well as the error (or look at the raw numbers from the datalog/print-to-screen). Since the integral is the running total of all error values, in a perfect world, the integral would be 0 — it would be adding up an equal number of + and – errors. If you look at your data or graph and see that the integral is a curve shooting off to infinity (or -infinity), then you know that your very best value for Kp is not sufficient to make your robot drive straight. We need to add in some of that integral amount to the power too.

Start by setting Ki to 0.05 or 0.1. Something in this range of values is a good place to start. Again, looking at the graph from Step 2, you’ll know if you have a big problem or a small problem, and can start at the high end or low end of this range. Now run your tests again. Look at the graph or the raw data output: what does it look like now? You’d like for the integral to oscillate around 0 as well; keep adjusting and re-running the test until the integral value oscillates around 0. Again, a running total of all errors should not be continuously increasing (or continuously decreasing).

Integral: Slowing a Drive Train

If you’re using PID to slow down as you approach a target (e.g., for gyro turns, for ultrasonic or encoder driving to a target distance), I am not sure that the integral value is going to be of that much use, because as you approach the target, the error is always positive (error = target – sensorValue), and so the integral will end up being really large, and (integral * Ki) gets to be a big positive number, when in reality we want the power level to be decreasing as we approach the target.

As mentioned above, it is not required that you use P, I, and D in all uses. Slowing down a drive train as you approach a target might be a good case for PD-only or simply P-only.

Integral: Raising or Holding an Arm

If you’re using PID on an arm mechanism — either holding an arm at a set height, or raising it to a height and holding it there — you do want to include the integral component for the “holding it there” component. The integral is an important component when adjusting motor power up & down in small increments to hold an arm in place.

One solution that George Gillard suggests on pages 9-10 of his guide is to ignore the integral while the error value is large; that is, if error > someAmount (you’re more than someAmount away from your target height), then integral = 0; that keeps the integral from coming into the picture until you get reasonably close to your destination. He also suggests resetting integral to 0 when the arm gets to the target and enters the holding-it phase. That way, the integral ends up being a reasonable number, naturally oscillating around 0 as the arm goes slightly above & slightly below its target holding position.

integral = integral + error;
if ( error > someValue || arm passes target height ) {
   integral = 0;
}

Step 4: Set Kd

When you feel like you’ve got Ki as good as you can, it’s time to look at the graph of Kd. It’s probably going to be a square-ish up-down-up-down kind of graph, hovering close to 0. Start by setting Kd to half of whatever your Ki value is, and then do a few trial runs and see the effect of your derivative adjustment on the size of the error and integral oscillations. As I mentioned above, Kd is usually a pretty small component.

Setting K-values Mathematically

As described on page 15 of George Gillard’s guide, there is a way to set your K-values formulaically, called the Ziegler-Nichols method. This method is VERY hard to do without the datalog, because it requires calculating the length of time of one oscillation of the robot’s movement (from peak to peak of your sine-wave graph mentioned above). Doing so with eyeballs and a stopwatch is going to be … inaccurate, and (IMO) you’re better off with guess-and-check, as described above.

The steps to use in the mathematical method are as follows:

  1. Get Kp as good as you can, using the method described above. The value of Kp must be such that your error graph is a stable oscillating movement.
    • Set the variable kU = Kp. The kU variable is referred to as “Ultimate” or “critical gain”
  2. Measure the period of the stable oscillation in seconds. George smartly recommends calculating this oscillation period for many oscillations and taking an average.
    • Set the variable pU = # seconds in oscillation. This variable is referred to at the “period for Ultimate gain”
  3. Set the values of Kp, Ki, and Kd according to the table below:
Algorithm Kp Ki Kd
P-only 0.5 * kU 0 0
PI 0.45 * kU 0.54 * kU / pU 0
PD 0.8 * kU 0 0.1 * kU * pU
PID 0.6 * kU 1.2 * kU / pU 0.075 * kU * pU

George notes in his guide that once you have these values from the table, you’ll still need to do some testing and adjusting, but this process should reduce the number of guess-and-check iterations needed.

In Conclusion

I hope that this post has given the PID-newcomer a place to start.

I will tell you first-hand that the time spent for your team to learn how to do this is 1000% worth it. We went from < 5% success to 100% success in putting a cone on the stationary tower in In The Zone after we were able to make our robot drive straight. Driving down the field to get the mobile goal, score a cone on it, and return it to the 5-point zone went from, well, 0% success to 90+% consistency. 

The beauty of getting a block of PID code working on your robot is that you get to use that same code over and over and over, on into eternity for your team. We revived our v1.0 robot at the end of the ITZ season so we could enter 2 robots in an invitational tournament, and were able to copy & paste the drive-straight PID code from our v2.0 robot and get it working in about 20 minutes. I kid you not.

I will re-iterate that this process is VERY hard to do without the datalog/print-to-screen functionality. If you don’t have the RobotC wireless downloading doo-dad, then you’ll be needing to run down the field with your computer tethered to the robot. It will be inconvenient but worth it, because there’s no substitute for seeing that data. Making small adjustments sometimes produces improvements that are easy to see on a graph but hard to see when viewing the robot on the field. And near the end of the process, the adjustments you’re making are going to be very small, and graphing or viewing the raw data is the only way you’ll definitively say whether this set of constants is better than the ones you used in the previous iteration.

Go for it! If you can view the sensor data and graphs, it turns out to be not that hard!

Share this post: