PID Control

Why would a home-brewer need a separate page just on control engineering? If you have a RIMS / HERMS brewing setup and you buy a neat little box that does all the tricks, why bother with PID control? As a home-brewer you have to understand a little how PID control works, because you can easily end up with a non-optimum PID controller, which causes significant temperature overshoots or very slow responses.

Suppose you think that you can do without PID control, you just switch off the heater when the temperature has reached its value. What happens in this situation is that the temperature in your setup continues to rise significantly. So you end up with a large overshoot. The heater should be switched off gradually, long before the final temperature is reached. That is where a controller comes in and the PID controller is the most common used one. PID stands for Proportional, Integral and Derivative. If you look at the insides of a PID controller, you see that it indeed consists of three paths. It used to be very common to build such a PID controller with discrete electronics, but nowadays every PID controller is realised in software, making it a digital PID controller. Such a digital PID controller is contained in the ready-for-use devices that you can buy. And if you have read this page, you understand that it is a high price to pay for only a few lines of code (so building your own PID controller can be rewarding).

In the remainder of this page I will try to explain the following items:

If you have an interest in building your own PID controller, check out my hardware and software pages. These pages shows the complete design and build of a brewing system with a PID controller.

1. PID Control Basics

The key parts of a PID control loop are shown in the figure below.

The Proportional-Integral-Derivative (PID) algorithm is widely used in process control. The PID method of control adapts well to electronic solutions, whether implemented in analogue or digital (CPU) components. The brewing program implements the PID equations digitally by solving the basic equations in software. Basically, there are two types of PID controls:

Within the brewing program, the PID Velocity Algorithm is chosen (which is also the algorithm that is used in most digital PID controllers). Another important feature of a control loop is whether it is a direct-acting loop or a reverse-acting loop:

In the figure shown above, three parallel paths were shown in the PID-loop calculation block:

The P, I and D terms work together as a team. To be able to control these terms, each term has a programmable gain:

There is one more remark to make about the type of PID controller (for details and more background, please read the PID controller Calculus document with full C source):

Some industrial PID controllers allow users to select any of these types. However, if a type C PID controller is available, you should select this one. A system blockdiagram of such a type-C controller is given in the figure below. The separate P, I and D-actions are clearly seen. This is also the controller that is used in the brewing system.

Back to the Top of the Page

2. Implementation of a PID controller

Many different implementations are possible. In fact, every modern PLC may have a different implementation of a PID controller, which makes things confusing. Deriving a form suitable for implementation requires a lot of Calculus. I did all this, so if you are really looking for a derivation of the PID controller implementation given here, together with the full C source code (so you can directly build your own controller), please read this document: PID Controller Calculus with full C source code. However, if you are not really into math and you are only interested in an actual implementation, so that you can use this in your own software, then read this paragraph. The document also contains the full C source for the PID controllers. Here, we will be using a PID velocity algorithm of Type C, which is direct-acting and has no filtering of the D-action. This is the so-called Takahashi PID controller and is probably the best way to implement a PID controller. The figure below shows the actual C code. This figure makes it easy (I hope) to transform the code to your own favourite programming language (the gray lines of text are comments). Based on this example, it is relatively easy to make this into a 'real' Takahashi type C PID controller.


	  
/*==================================================================
  Function name: pidInit(), pidCtrl()
  Author       : Emile
  ------------------------------------------------------------------
  Purpose : This file contains the main body of the PID controller.
            For design details, please read the Word document
            "PID Controller Calculus".

            In the GUI, the following parameters can be changed:
            Kc: The controller gain
            Ti: Time-constant for the Integral Gain
            Td: Time-constant for the Derivative Gain
            Ts: The sample period [seconds]
  ==================================================================*/
#include "pid_reg_web.h"
#include 

void pidInit(pidPars *p, double Kc, double Ti, double Td, double Ts)
/*------------------------------------------------------------------
  Purpose  : This function initialises the PID controller.
  Variables: p : pointer to struct containing PID parameters
             Kc: the controller gain [%/C]
             Ti: integral time-constant [sec.]
             Td: differential time-constant [sec.]
             Ts: sample-time period [sec.]

                   Kc.Ts
             ki =  -----   (for I-term)
                    Ti

                       Td
             kd = Kc . --  (for D-term)
                       Ts

  Returns  : No values are returned
  ------------------------------------------------------------------*/
{
    p->kp = Kc;
    if (Ti < 1e-3)
         p->ki = 0.0;
    else p->ki = Kc * Ts / Ti;
    if (Ts < 1e-3)
         p->kd = 0.0;
    else p->kd = Kc * Td / Ts;
    p->xk_2 = p->xk_1 = p->yk = 0.0;
} // pidInit()

double pidCtrl(double xk, double tset, pidPars *p, int vrg)
/*------------------------------------------------------------------
  Purpose  : This function implements the Takahashi Type C PID
             controller: the P and D term are no longer dependent
             on the set-point, only on PV (which is Thlt).
             This function should be called once every TS seconds.
  Variables:
        xk : The input variable x[k] (= measured temperature)
      tset : The setpoint value for the temperature
        *p : Pointer to struct containing PID parameters
        vrg: Release signal: 1 = Start control, 0 = disable PID controller
  Returns  : yk : PID controller output y[k] in % [0..100]
  ------------------------------------------------------------------*/
{
   if (vrg)
   {
      //--------------------------------------------------------------------------------
      // Takahashi Type C PID controller (NO filtering of D-action):
      //
      //                                    Kc.Ts        Kc.Td
      // y[k] = y[k-1] + Kc.(x[k-1]-x[k]) + -----.e[k] + -----.(2.x[k-1]-x[k]-x[k-2])
      //                                      Ti           Ts
      //
      //--------------------------------------------------------------------------------
      p->pp  = p->kp * (p->xk_1 - xk);                 // Kc.(x[k-1]-x[k])
      p->pi  = p->ki * (tset - xk);                    // (Kc.Ts/Ti).e[k]
      p->pd  = p->kd * (2.0 * p->xk_1 - xk - p->xk_2); // (Kc.Td/Ts).(2.x[k-1]-x[k]-x[k-2])
      p->yk += p->pp + p->pi + p->pd;                  // add y[k-1] + P, I & D actions to y[k]
   }
   else { p->yk = p->pp = p->pi = p->pd = 0.0; }

   p->xk_2  = p->xk_1;  // x[k-2] = x[k-1]
   p->xk_1  = xk;       // x[k-1] = x[k]

   // limit y[k] to a maximum and a minimum
   if (p->yk > PID_OUT_HLIM)
   {
      p->yk = PID_OUT_HLIM;
   }
   else if (p->yk < PID_OUT_LLIM)
   {
      p->yk = PID_OUT_LLIM;
   } // else
   return p->yk;
} // pidCtrl()




	  

There are a few remarks to make about this implementation:

Please let me know if you find this listing easy or difficult to read. I would be interested to hear about it.

A header file (.h) is normally created for every C file. Such a file contains the function prototypes and important definitions / constants. If you want to use these function, you would typically include this header file into your C project.


	  
#ifndef PID_REG_H
#define PID_REG_H
/*==================================================================
  File name    : pid_reg_web.h
  Author       : Emile
  ------------------------------------------------------------------
  Purpose : This is the header file for pid_reg_web.c
  ==================================================================*/

#define PID_OUT_HLIM (100.0) /* PID Controller Upper limit (%) */
#define PID_OUT_LLIM   (0.0) /* PID Controller Lower limit (%) */

//------------------------------------------------------------
// By using a struct for every PID controller, you can have
// more than 1 PID controller in your program.
//------------------------------------------------------------
typedef struct _pidPars
{
   double kp;   // Internal Kp value
   double ki;   // Internal Ki value
   double kd;   // Internal Kd value
   double pp;   // debug value for monitoring of P-action
   double pi;   // debug value for monitoring of I-action
   double pd;   // debug value for monitoring of D-action
   double yk;   // y[k]  , value of controller output (%)
   double xk_1; // x[k-1], previous value of the input variable x[k] (= measured temperature)
   double xk_2; // x[k-2], previous value of x[k-1] (= measured temperature)
} pidPars; // struct pidParams

//----------------------
// Function Prototypes
//----------------------
void   pidInit(pidPars *p, double Kc, double Ti, double Td, double Ts);
double pidCtrl(double xk, double tset, pidPars *p, int vrg);
#endif
	  

As an example of how to call these routines from within a C-program, the code below is given. The function pidInit() is called first with the actual PID parameters and then the controller itself is called (the function pidCtrl()). The pidCtrl() function needs an actual temperature, a setpoint value, the struct with all controller parameters and a release signal. The controller will start controlling only, when this release signal is set to 1.


	     
#include 
#include 
#include "pid_reg_web.h"

int main()
{
    pidPars p;          // Create a struct for pidPars
    double out  = 0.0;  // PID controller output
    double temp = 30.0; // Actual temperature
    double sp   = 32.0; // Setpoint temperature

    // Call pidInit with the proper control parameters
    pidInit(&p, 80.0, 280.0, 45.0, 20.0); // p, Kc, Ti, Td, Ts

    // Call the actual controller every Ts seconds
    out = pidCtrl(temp, sp, &p, 0); // x[k], SP, p, vrg
    out = pidCtrl(temp, sp, &p, 0); // call this function every Ts seconds
    // The PID-controller is enabled by setting vrg to 1
    out = pidCtrl(temp, sp, &p, 1); // Controller is released
    out = pidCtrl(temp, sp, &p, 1); // 
    return 0;
} // main()
	     
Back to the Top of the Page

3. Tuning the PID controller

Tuning the PID controller means finding values for Kc, Ti and Td, such that the PID controller responds quickly to changes in setpoint value and/or temperature, while minimising any overshoot in temperature. Tuning a PID controller is always necessary, even if you bought a ready-to-use PID controller. The advantage of such a ready-to-use PID controller is that they contain an auto-setup function, which finds the optimum values automatically for you (well, most of the time). But even such a ready-to-use PID controller uses the same algorithms as the ones I describe here. So this information might also be useful to you if you did not create your own PID controller, but bought one. In order to understand the specific measurements which we are about to do, some terms and definitions have to be explained:

3.1 Determine Dead-Time and normalized slope of the HLT system

Manually set the PID-controller output to a certain value (e.g. 20 %). In case of a heavy load, 100 % is recommended (more accurate), but if you do not know the performance of the system, a lower value to start with is better. The temperature starts to increase and follows the following curve:

After doing this experiment with my HLT system (90 L of water, no lid on the pan, controller output set to 100 %, heating element only), combined with some regression analysis (for more accuracy), I found out that the dead-time for my HLT system is equal to 115 seconds. During the experiment, the temperature change was 0.4 °C per minute when the PID controller output was set to 100 %. The normalized slope a is therefore equal to 0.004 °C / (%.minute) or 6.68E-5 °C/(%.s). BUT... since I switched over to gas-burners, and more recently, to a new HLT kettle, I may have to do this experiment again!.

3.2 Determine Time-Constant of the HLT system

Because of the large time-constant presumably present in the HLT system, the previous experiment is not very accurate in determining the dead-time as well. This is the mean reason to conduct two experiments (theoretically, the step-response would give you all the required information to determine the three parameters). Manually set the PID-controller output to a certain value (e.g. 20 %). The temperature starts to increase and follows the following curve:

The difference with the previous experiment is that we let the PID controller settle, meaning that the HLT temperature will converge to a new value (in my experiment, this took a complete day, because I wanted it as reliable as possible). After doing this experiment with my HLT system (90 L of water, no lid on the pan, controller output set to 20 %, heating element only), I collected the following data:

3.3 Calculate the Optimum values for the PID controller (Kc, Ti and Td)

A summary of the results from the two experiments for my particular HLT system:

There are a few well-known algorithms that calculate the optimum settings for a PI and a PID controller. These algorithms calculate a value for Kc, Ti and Td based upon the values found for TD, a, K and tau. The algorithms are:

The detailed formulas for every algorithm are as follows:

When these formulas are applied to the values found for my HLT system, the following results were obtained:

  Numeric values
Algorithm Kc [%/(°C)] Ti [sec.] Td [sec.]
Ziegler-Nichols Open Loop 156.2 230.0 57.5
117.2 383.0  
Ziegler-Nichols Closed Loop 92.4 230.0 57.5
69.3 383.0  
Cohen-Coon 102.8 282.3 41.8
69.4 377.2  
ITAE-Load 80.8 489.0 44.9
59.2 810.2  

I tried all these solutions in my HLT system and all of them gave good results. I found out that the D term was quite sensitive, especially with the ITAE-Load values. therefore I reduced the D-term and applied some more filtering (the low-pass filter time-constant was set to 10). See How does it work? for a graph showing the performance of the PID-controller (at the bottom of that page). After all this work, I am beginning to understand how the PID controller does its work in relation with my HLT system. Needless to say is that I am very happy with the performance of this PID controller. It is funny to see that designing all this took several months, but that the actual implementation is only a couple of lines of code.

Not everything was written by myself. I found some excellent sources that explain some of the details I gave on this page:

Back to the Top of the Page