What is PID?

Hey guys I was wondering if anyone could explain PID to me and how to use it. Every competition I go to I hear about how PID could help our drive motors from wearing out. Because that has been a consistent problem from my team so could anyone explain what PID is and how to use or program it?

2 Likes

Just to put this out there, PID isn’t the magical fix everyone raises it up to be
It’s just a nice tool that helps you get better results more consistently

People make it out to be like the difference between eyeballing a distance and using a laser distance finder when it’s probably just closer to the difference of eyeballing it and using a tape measure

The crude definition is, “Go fast when you’re far away from the target and slow as you approach” but that’s not quite all there is to it



typedef struct { // init a reusable list of variables
	float current;
	float kP;
	float kI;
	float kD;
	float target;
	float error;
	float integral;
	float derivative;
	float lastError;
	float threshold;
	int   lastTime;
} pid;

pid sPID; // init struct "PID" with the prefix "sPID"
  // Cookie-Cutter PID loop for intelligently reaching a desired destination.
int iPID( int iDes , int iSensorInput, const float kP, const float kI, const float kD, const float kILimit) {
	sPID.current    = iSensorInput;
	sPID.error      = iDes - sPID.current;
	sPID.integral;
  if( kI != 0 ) // integral - if Ki is not 0
      { // If we are inside controllable window then integrate the error
      if( abs(sPID.error) < kILimit )
          sPID.integral = sPID.integral + sPID.error;
      else
          sPID.integral = 0;
      }
  else // Otherwise set integral to 0
      sPID.integral = 0;
  sPID.derivative = sPID.error - sPID.lastError; // Calculate Derivative
	wait1Msec(5);
	sPID.lastError = sPID.error;
	return ( (sPID.error * kP) + (sPID.integral * kI) + (sPID.derivative * kD) );
}


Since I have yet to find a post that straight up displays a good, functioning PID loop that doesn’t get unnecessarily long winded on how to use it, here’s the basics:

kP does most of the work but using it alone can cause your robot to fall a little short
kI gets rid of undershoot but can quickly spiral out of control and cause overshoot if not used carefully ( as you can see there’s a specific case for just not using integral and it’s because of this)
kD is your dampener that makes motions when you’re close to the target less jerky

beware though, setting the control constants (kP, kI, and kD) too high or to the wrong settings can cause unwanted and painful to watch sights

generally all of them should be less than 1, but here’s a general rulesheet:

  1. kP should NEVER go above 1
  2. If kI is above 0 and you’re having overshoot/oscilation issues, just don’t use kI
  3. kD should never exceed .05 UNLESS you’re using PID for fully controlling an arm (AKA not as a brake) in which case your cap should be .15
  4. if to make something work you have to set any of these constants to something outside of these bounds you likely have a problem that’s not coding related because a lot of people will tell you that these bounds are all waaaay too large for PID constants

so, break a leg

[EDIT 1]
something i should probably mention is that I use this loop in RobotC, so I don’t know if it’ll work or no in PROS though I don’t see why it shouldn’t with how its made beyond replacing wait1Msec with taskDelay

3 Likes

^ He explained it really well. I’ll just throw in a few helpful links:
http://www.aura.org.nz/archives/1869
http://georgegillard.com/documents/2-introduction-to-pid-controllers

The AURA one is about a P controller, which is simpler and works pretty well (some people, including me, say it’s good enough for most applications).

And as a side note, it won’t really prevent your motors from wearing out. That’s more about other stuff, like build quality, motor limitations, etc. Maybe some software things like slew rate control and similar things may help, but it’s almost always about the hardware.

Here’s a simple case involving just the P portion of the PID:
https://vexforum.com/t/simple-p-controller-for-arm-position/23699/1

To tack on one more article, written at an even easier level than some of the above, is this one targeted at FLL teams: http://www.inpharmix.com/jps/PID_Controller_For_Lego_Mindstorms_Robots.html

I still keep the reference around because it’s what I used when I first was trying to learn. :stuck_out_tongue:

And while PID is important and great thing to learn, if you want to be nice to yout motors, learn about slew rate. Naive PID would in many cases command a motor to go full speed from zero, causing jerk and contributing both to the motor heating as well as gear damage. Your goal is to reduce jerk, that is, limit the rate of change of the motor power.

Basically is a meme in the vex community for a “hack” to make lifts work better. Its nothing more then a hoax started by the top ranking teams to make others waste their time.

Heres a good youtube demonstration of the affects of the different terms in a PID loop on a system.

For just a general crash course I would read PID without a PHD. It’s really helpful and we use it a lot in our program to help explain PID
here’s a link: http://www.wescottdesign.com/articles/pid/pidWithoutAPhd.pdf

This is not a meme or an attempt to waste others time. Instead, it is a very useful tool to make autonomous programs considerably more consistent. Please don’t accuse the top teams of cheating or anything of the like. Most people are trying to help, such as the many people posting resources to learn pid in this thread.

1 Like

https://vexforum.com/t/a-pid-controller-in-robotc/20105/1
This should help. However, there are simpler controllers out there.

PID is a controlling method to control the power to motors at any given instant in time. PID is only a coding thing, where you weight three different terms (Proportional, Integral, Derivative) and they control the power of the motor. You have a current sensor value, and you want it to get to some target sensor value (there is a constantly changing error value which is just the target minus your current value). Proportional basically increases as you get away from your target sensor value, Integral is the area under the curve of the error vs time graph, which slowly changes the motor speed (and you can imagine, it takes into account all the previous errors), and Derivative just finds the slope at any given instant, which you can imagine, changes as your general trend changes (of error vs time graph). If you want to learn more, just search up some resources online.

The implementation is pretty simple (and I don’t use RobotC so I can’t really help you), just search it up online.

[same comment here as I just put on “PID straightdrive” post]

FYI all - I’ve added an article to my Coach’s Corner blog, compiling a list of Forum posts and websites discussing PID that I find practical/helpful (with links). Hopefully having all of these resources in one place will make the road a little smoother for someone in the future.
PID Resources.

If anyone has items to add to what’s listed already (useful, practical stuff, not just all stuff), please send me a message or respond here.

Ok, so I just used @DylanTheTactician 's PID example and it compiled! I did it!

Oh wait…
Where do I reference the iPID function and use it with my sensors? Do I need additional callouts for my sensor input values to be compatible with an encoder, for example? Where do I say “hey, this is my target value; go there pls”
This is my first time dealing with this and no one else in my school has done it at all. Send help lol.

@mattynmax There are certain scenarios where PID can dramatically improve a lift. It makes auto-stacking easier.

Our lift has an issue where if we lower it too far our claw gets stuck on the floor, making it extremely difficult to pick up cones. A PID loop now stops that from happening and dramatically improves our speed by allowing us to have a “cone pickup” preset that goes to the right height each time.

@Royal_xD
Start out simple, with just a p-loop. This is our code for our mogo lift


task mglControl(){
	mglPControlIsRunning = true;
	while (true){
		rightMGLAngle = SensorValue(rightMGLPot);
		leftMGLAngle = SensorValue(leftMGLPot);

		rightMGLError = -1 * (rightMGLAngle - rightMGLTarget); //right pot is reversed

		rightMGLSpeed = kpMglLift * rightMGLError;
		leftMGLSpeed = kpMglLift * rightMGLError;

		if (leftMGLSpeed > 80){
			leftMGLSpeed = 80;
		}
		if (leftMGLSpeed < -80){
			leftMGLSpeed = -80;
		}

		if (rightMGLSpeed > 80){
			rightMGLSpeed = 80;
		}
		if (rightMGLSpeed < -80){
			rightMGLSpeed = -80;
		}

		motor[leftMGLift] = leftMGLSpeed;
		motor[rightMGLift] = rightMGLSpeed;

		sleep(20);
	}
}

Use the “Motor and Sensor” setup window to configure your sensors as encoders, pots, IMEs, etc. In this example, since the task is always running, the “target” variables are global variables set from other tasks (like when a button is pressed on the controller). Changing them in one place changes them everywhere, which is convenient and easy to program.

It tends to undershoot the presets without the kI and kD, but since it is a lift, this is easily fixable by compensating by increasing the preset value. Keep raising your kP until you get oscillation, and then lower it a little. I’m mostly using values around 1.0–try using that as a starting point

Thanks, that helps. I was thinking about doing the same for my MG lift earlier, actually. I still don’t understand where I set the kP value… is it an int value set outside the task?

Also what does that statement “mglPControlIsRunning” reference? I don’t think I have that kind of thing…
edit: god I feel so stupid…

I just have that so the rest of my program can check if the mgl code is running

kp is defined as a float elsewhere, I forgot to put it in the example

Ok… I’ll play around with it. Thank you so much!

Well dang. I tried to put this into a driver control loop. I want it to stop at certain positions when I hit the corresponding button in driver control. When I turned it on, the bot did nothing. I posted the parts of the code with the relevant parts; the reusable variable setup, the P controller for the MG lift, and the driver control part:


//set up a library of variables/integers
typedef struct 
{
	float current;
	float kP;
	float kI;
	float kD;
	float target;
	float error;
	float driftError;
	float kDrift;
	float integral;
	float derivative;
	float lastError;
	float threshold;
	float rMGTarget;
	int rMGAngle;
	int lMGAngle;
	int rMGSpeed;
	int lMGSpeed;
	int lastTime;
	float rPotError;
	float lPotError;

} pid;

//call the library as "sPID"
pid sPID;
//----------------------------------------------------------------//
int mgPID (const float kPMG, const float rMGTarget)
{
	while (true)
	{
		//set angle definitions
		sPID.rMGAngle = SensorValue(rMGPot);
		sPID.lMGAngle = SensorValue(lMGPot);

		sPID.rPotError = -1 * (sPID.rMGAngle - sPID.rMGTarget); //right pot is reversed

		sPID.rMGSpeed = kPMG * sPID.rPotError;
		sPID.lMGSpeed = kPMG * sPID.rPotError;
		if(sPID.lMGSpeed > 30)
		{
			sPID.lMGSpeed = 30;
		}
		if(sPID.rMGSpeed < -30)
		{
			sPID.rMGSpeed = -30;
		}

		return ( (sPID.rPotError * kPMG));
		motor[lift1] = sPID.lMGSpeed;
		motor[lift2] = sPID.rMGSpeed;
		wait1Msec(20);
	}
}
//--------------------------------------------------------------------//
		if (vexRT[Btn8D]==1)
		{
			mgPID(0.1, 1836);
		}
		//set position for fully extended MG lift
		else if (vexRT[Btn8U]==1)
		{
			mgPID(0.1, 3335);
		}
		//if i need to stack first cone, set MG lift height accordingly
		else if (vexRT[Btn8L] == 1)
		{
			mgPID(0.1, 2840);
		}
		else
		{
			motor[lift1] = 0;
			motor[lift2] = 0;
		}


What have I done wrong?!