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, we have chosen for the PID Velocity Algorithm (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.

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: init_pid4(), pid_reg4()
  Author       : E. vd Logt
  File name    : $Id: pid_reg.c,v 1.11 2013/07/23 09:42:46 Emile Exp $
  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]
  $Log: pid_reg.c,v $
#include "pid_reg_web.h"

static double xk_1;  // PV[k-1] = Thlt[k-1]
static double xk_2;  // PV[k-2] = Thlt[k-1]

void init_pid4(pid_params *p)
  Purpose  : This function initialises the Type A PID controller.
             It also utilizes filtering of the D-action.
  Variables: p: pointer to struct containing all PID parameters

             k0 =  -----   (for I-term)

             k1 = Kc . --  (for D-term)

  Returns  : No values are returned
   p->ts_ticks = (int)((p->ts * 1000.0) / T_50MSEC);
   if (p->ts_ticks > SIXTY_SECONDS)
      p->ts_ticks = SIXTY_SECONDS;
   } // if
   if (p->ti == 0.0)
      p->k0 = 0.0;
      p->k0 = p->kc * p->ts / p->ti;
   } // else
   p->k1   = p->kc * p->td / p->ts;
} // init_pid4()

void pid_reg4(double xk, double *yk, double tset, pid_params *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).
             The D term is NOT low-pass filtered.
             This function should be called once every TS seconds.
        xk : The input variable x[k] (= measured temperature)
       *yk : The output variable y[k] (= gamma value for power electronics)
      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  : No values are returned
   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->kc * (xk_1 - xk);              // Kc.(x[k-1]-x[k])
      p->pi = p->k0 * (tset - xk);              // (Kc.Ts/Ti).e[k]
      p->pd = p->k1 * (2.0 * xk_1 - xk - xk_2); // (Kc.Td/Ts).(2.x[k-1]-x[k]-x[k-2])
      *yk  += p->pp + p->pi + p->pd;            // add y[k-1] + P, I & D actions to y[k]
   else { *yk = p->pp = p->pi = p->pd = 0.0; }

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

   // limit y[k] to GMA_HLIM and GMA_LLIM
   if (*yk > GMA_HLIM)
      *yk = GMA_HLIM;
   else if (*yk < GMA_LLIM)
      *yk = GMA_LLIM;
   } // else
} // pid_reg4()


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.

  File name    : $Id: pid_reg.h,v 1.13 2013/07/24 14:00:00 Emile Exp $
  Author       : E. vd Logt
  Purpose : This file contains the defines for the PID controller.
  $Log: pid_reg.h,v $
#ifndef PID_REG_H
#define PID_REG_H

#ifdef __cplusplus
extern "C" {

// These defines are needed for loop timing and PID controller timing
#define SIXTY_SECONDS (1200)
#define TEN_SECONDS    (200)
#define FIVE_SECONDS   (100)
#define ONE_SECOND      (20)
// Period time of TTimer in msec.
#define T_50MSEC        (50)
#define PI              (3.141592654)
#define NODF            (1)

// PID controller upper & lower limit [%]
#define GMA_HLIM (100.0)
#define GMA_LLIM   (0.0)

typedef struct _pid_params
   double kc; // Controller gain from Dialog Box
   double ti; // Time-constant for I action from Dialog Box
   double td; // Time-constant for D action from Dialog Box
   double ts; // Sample time [sec.] from Dialog Box
   double k_lpf; // Time constant [sec.] for LPF filter
   double k0; // k0 value for PID controller
   double k1; // k1 value for PID controller
   double k2; // k2 value for PID controller
   double k3; // k3 value for PID controller
   int    ts_ticks;  // ticks for timer
   int    pid_model; // PID Controller type [0..3]
   double pp; // debug
   double pi; // debug
   double pd; // debug
} pid_params; // struct pid_params

// Function Prototypes
void init_pid4(pid_params *p);        // Type C Takahashi PID, no filtering of D-action
void pid_reg4(double xk, double *yk, double tset, pid_params *p, int vrg);

#ifdef __cplusplus
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