For those who work in industrial controls, proportional, integral, derivative (PID) theory is likely a familiar concept. For most people, the term is entirely foreign, but you might be more familiar with it than you think. When you press the gas pedal in a car, you not only consider how far you are from your ideal speed—the proportional part of PID—you also take into account how your particular vehicle accelerates as well as conditions like hills.
These more subtle effects are what the I and D terms consider mathematically. In this example, they would prevent a car's speed from bouncing from an upper to a lower limit, and we can apply the same concept to a variety of control situations. While limit-based control can get you in the ballpark, your system will tend to act somewhat erratically.
PID Equation
We can express PID control mathematically with the following equation. P, I, and D are represented by the three terms that add together here. Kp, Ki, and Kd are constants that tune how the system reacts to each factor:
We can also replace Ki and Kd with 1/Ti and Td, respectively. This change gives the equation a better relationship to its physical meaning and allows the units to work out properly to a unitless number:
We can also transpose the equation to extract the Kp value and apply it to the entire equation, in what's known as the standard form. One advantage of this form is that we can adjust the overall Kp constant for the whole equation at one time:
All of this may look a bit intimidating, perhaps even to someone who graduated with an engineering degree. The good news is that you don't have to dig out your Modeling and Analysis of Dynamic Systems textbook to understand what's going on here. And while it never hurts, you don't even have to be able to do calculus.
Breaking the first equation down, we produce u(t)—the unitless controller output on the left-hand side of the equation—by adding three mathematical elements on the right-hand side of the equal sign: P, I, and D. Each element has a constant K value in front (Kp, Ki, and Kd), which signifies each element's weight as they form u(t), or the control output at a specific time. We can individually tune each K value for better system performance, which we'll explain further below:
Proportional (P—Kp)
The first and most crucial term in this equation is the e(t). As such, the Kp value that comes before it is generally larger than the other K values in the equation. Here, e(t) is simply the instantaneous error at a point in time—the actual value of a controlled device minus the desired value. Multiply this by Kp and you have its contribution to the overall controller output.
Integral (I—KI)
The second term in this equation has to do with the combined error over time. It's the sum of all errors experienced on the device:
- Each time a controller calculates u(t), it adds the instantaneous error to a running tally.
- This figure is then multiplied by Ki and added into u(t).
Consider a situation where a force holds a motor in place and doesn't allow it to return to the setpoint under normal conditions. As it's out of spec longer and longer, the I term will continue to increase, eventually overcoming this force or reaching the limits of the motor's capability.
Derivative (D—Kd)
The third term in this equation has to do with how fast the error changes:
- Controllers take an instantaneous error reading.
- Subtract the previous instantaneous reading from it.
- Multiply that resulting valueby Kd to calculate its contribution to u(t).
Theoretically, if you only had a D term in this equation and your process steadily maintained the wrong value, this term would remain zero and wouldn't contribute to the proper output. On the other hand, if one of the other two terms tries to make the device's output snap back into place too quickly, the derivative can help dampen the effect.
While the calculus-based expression of this concept can be useful, the reality of PID control is much less magical than it first appears. Practically speaking, we calculate output using only addition, subtraction, and multiplication, factoring in the error and time between readings. In fact, we used pneumatic systems to implement early forms of PID control, using mechanical means to input each "term."
Arduino PID Controller Tutorial
In many situations, it's expedient to plug in a dedicated PID controller to your process, but you can make your own with an Arduino or other similar dev board. You can even write your own PID routine. Express each term in your code in a manner similar to:
- P:instanteneousError = setpoint – input;
- I:cumulativeError = += error * elapsedTime;
- D:rateOfError = (error – errorLastCalculation)/elapsedTime;
To get the necessary output, multiply each of these terms by its respective K value and add them together. On the other hand, if you'd like to avoid reinventing the wheel, you can jump in with Brett Beuregard's PID library. This library takes care of the details for you and lets you focus on tuning P, I, and D constant values as needed.
To demonstrate PID theory in action, I pulled out an Arduino Nano Every from my toolbox, along with:
- Motor driver board
- Infrared sensor
- Motor salvaged from a Hubsan H107C drone
I printed a breadboard mount for the motor along with a "fan" that blocks light 50% of the time to avoid missing pulses as it spins. The motor driver is the input to the (otherwise uncontrolled) motor, and feedback is based on the time between pulses.
When I finally worked out the code and obtained the 3D-printed parts, I was able to control the motor in a more sophisticated way than operating the power button with constant PWM (pulse-width modulation) output.
Code snippet from the PID routine linked above
Notable bits of code include using an interrupt routine to register each pulse as the blocking device spins and calculating the time between pulses using millis(). I set up the PID in the first part of the code outside of the functions with:
PID myPID(&difference, &driverOut, &setPoint,Kp,Ki,Kd, DIRECT);.
Kp, Ki, and Kd are defined before this line in the code, and &difference controls &driverOut, with &setPoint modified by the serial monitor. We can manipulate this math to transform this time difference into RPM, but the PID settings work out as DIRECT outputs because this particular driver turns on when the input is pulled low. As the analog output goes higher (motors off), the delay between pulses also increases.
An earlier version of PID routine. Note CH1 reads input pulses, while CH2 is output calculated using PID routine—low pulses are inputs to motor.
This simple PID controller example was driven by parts on-hand (including the new Arduino Nano Every) and a motor with which I was loosely familiar. But I was able to demonstrate how to apply PID control in a wide variety of situations, even without all the facts. Whether it's motor control, temperature control of an oven, or balancing a robot, a little PID setup and tuning gives you endless experimentation options.