Control theory must be on the short list for the most important subject that no one talks about. Break open a hobbyist robotics book and you are lucky if control systems are even mentioned. Control systems run our cars, fire our multi-million dollar missiles, and keep our buildings at the right temperature. The phase locked loops (PLLs) that set the frequency for our modern radios, televisions, and cellphones, and which have replaced countless analog tuned circuits, are just another PID based control system.

The old analog control systems, as well, have been silently replaced by their digital implementations. Witness the number of “computers” inside a car or a jet fighter built today. Control systems are now seemly everywhere and live in places you least suspect.

As for why control systems are not talked about, the mathematics is just hard. Although real world examples of second order systems are abundant, such as a swinging pendulum, it is the math behind the dynamic behavior of a control loop which is amazingly obtuse.

That said it is possible to design a PID control without touching the complex math. For this note I implemented a temperature control system that maintained a constant temperature on a simple resistor.

As you know there are three gain constants, one for each term used in a full PID control implementation. The three terms are the Proportional, the Integral, and the Derivative.

The PID algorithm itself is very small. To save space on my ATtiny13 I used shift multiplication instead of regular multiplication. If the particular gain value is set to negative the term is ignored.

int8_t p_gain;
int8_t i_gain;
int8_t d_gain;

static int16_t i_state, d_state;

void reset_pid ()
  i_state = 0;
  d_state = 0;

int16_t update_pid (int16_t error)
  int16_t drive = 0;

  drive += (p_gain < 0 ? 0 : error << p_gain);             // P term
  drive += (i_gain < 0 ? 0 : (error + i_state) >> i_gain); // I term
  drive += (d_gain < 0 ? 0 : (error - d_state) << d_gain); // D term

  i_state += error;  // update I state
  d_state = error;   // update D state
  return drive;

So how does one find the correct PID gain constants? What should the sampling frequency for the loop be? And an issue I faced with my own implementation, how much current should be switched through the heating element, the resistor.  Using some mathematical method is an option. There is also the open source simulator Scilab. And there is always the option of tuning the control loop the old analog way with a signal generator and oscilloscope.

There is another option though. A great benefit from implementing a PID controller in software is that its state can be logged out, say, to a host computer through a serial port. In additional a step function can be simulated in software without ever having to use a signal generator. A step function lets one see the loop’s dynamic behavior. This is accomplished by changing the PID control’s set point, or target value, to another value a good distance away. The article “PID Without a PhD” by Tim Wescott describes this particular method.

Below is my code for simulating a step function and then logging the sensor input temperature read by the PID algorithm. The three gain constants are set beforehand. The “ready” variable is set every quarter of a second by an interrupt handler.

reset_pid();                          // clear the PID algorithm's state

for (ready=0, n=0; n<300; n++) {      // do 300 iterations
  while (!ready) ; ready = 0;         // wait until time for a new sample

  temp = read_adc();                  // read the input temperature
  if (n == 0) target = temp + step;   // suddenly increase the set point
  drive = update_pid(target - temp);  // pass the error to the PID
  set_pwm(drive);                     // limit and set the PWM 

  put_char(',');                      // log the temperature
  put_hex(temp);                      // to the serial port


Initially I only switched 60mA through a 1/4W 100ohm resistor, or about 360mW. This was not enough to raise the temperature by more than 4 degrees Celsius making it hard to ring the loop. (Apparently a 1/4W resistor has a thermal resistance of about 12C/W.) I decreased the resistor to 47ohms which increased the switched current to 125mA. I was then able to raise its temperature by at most 10 degrees Celsius. Now using a 5.4 degree Celsius step function, corresponding to an increase in value of 50 from the ADC, any ringing nicely stands out.

The IRF510 switching transistor fully turned itself on with a Rds of less than 1ohm with only a gate voltage of 5V. This is contrary to lore that such a low gate voltage will not work. The drain was at 6V when off. The switching caused a good amount of noise so a .1uF capacitor across the temperature sensor was needed otherwise the sensor was unusable.

Figure 1 shows the behavior of only the proportional term whose gain was varied from 1 to 7. Ringing is clearly evident with the higher gains.

Figure 2 shows the behavior of only the integral term for various gain terms. The windup and winddown with the higher gains can be seen. Please note that shift division is used to calculate the I term.

You might have noticed in figure 1 that the target value of 50 was never reached. This is where the I term comes in. The I term learns from the past. While the P term is concerned with the error in the present, the I term remembers the past to inform its current value. The article “PID Control without Math” by Robert Lacoste explains why this is important.

So if you think your PID is broken because it is not resting on the right value, as I did, it is not. The “loss” within the control system must be compensated by the I term as explained by Lacoste. Figure 3 shows the result when the P and I term are combined. The target value of 50 can now be reached.

While the I term looks back, the D terms looks to the future. The D term senses the speed of the error and uses that to gauge how much drive to apply. Like riding a bike, it is better to coast to a stop than to slam the brakes. The D term as a result will dampen the response of the loop. I was not able to get any meaningful data from runs using the D term alone. Wescott says as much. He recommends setting the gain for the P term to the lowest possible value. This was done for figure 4. The runs have been visually staggered so the effect of the D term can be seen better.

From all these figures I picked the putative best gain constants, P=2, I=5, and D=5. But to sure, I put together a final training round with 27 contenders. The staggered results are given in figure 5. From this the optimal PID gain constants appear to be now, P=1, I=5, and D=4. This is quite a bit different from my first choice. Nevertheless this is still not the best. See later in the note.

With respect to the sampling rate, I found that a 1 second sampling rate for my heater was too long. Any control loop will of course exhibit some delay and that delay probably needs to be observable by the PID control. At least this was my conclusion. A quarter second sampling rate allows for this. The initial delay before the temperature starts rising at the beginning of the step function can be easily seen at this sampling rate. A faster rate was not tested however because my software UART could not handle the speed.

The rule of thumb according to Wescott is that the sample time should be 1/10th to 1/100th of the settling time. The settling time is the time it takes for the system (the transient response) to move to and remain within a certain percentage (say 10%) of a steady state value.

If the sampling rate, coupled with the thermal mass of the heating element, is functionally equivalent to the role the loop filter plays in a PLL then it will be the prime factor in the transient response of the PID control. (I think that sounds right especially since F=ma as we remember.) The transient response determines the settling time, the dampening ratio, the overshoot amount, the damped natural frequency, and the loop’s bandwidth.

With the second optimal solution above I found its settling time to be around 110 seconds or 440 times the sampling time. So maybe it is not so optimal. Trying again from figure 5 and using the gain constants, P=3, I=5, and D=4, the settling time came out to be 21 seconds or 84 times the sampling time. These are the gain constants I will use for my PID controller. See figure 6.

Going out on a mathematical limb and looking at figure 1, the damped natural frequency from the P=3 and P=4 runs is about 1/(128 samples) or 1/(32 seconds). This is calculated from the ringing on these runs. The equation for the damped natural frequency is the undamped natural frequency (that is the bandwidth of the control loop) times the square root of (1 – the damping factor squared). Now estimating that the damping factor for my PID controller is around 0.8, my loop’s bandwidth can be calculated to be 0.05 Hz or 1/(20 seconds). My sampling rate is safely 80 times faster than this.

By the way, this leads me into another way to find the loop’s bandwidth. Since the damped natural frequency and undamped natural frequency approach each other as damping decreases, drive the loop into ringing with high gain and then use that ringing frequency as the bandwidth.

To sum up, training is key. So put your PID controller through its paces and I will meet you at the track.