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:
- 1. PID Control Basics: some basic theory on PID control
- 2. Implementation of a PID controller: a real PID controller with C source code
- 3. Tuning the PID controller: finding the optimum parameters for your brewing system. This paragraph consists of the following sub-paragraphs:
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.
- Setpoint value: The desired or required value for the process variable. In the brewing setup, the setpoint value is derived from the mash scheme. If a particular mash rest has finished, the setpoint-value is set to the next, higher, temperature reference.
- Process variable: a measured value of some physical property. In the brewing setup, this is the measured temperature from the HLT.
- Error Term: the algebraic difference between the process variable and the setpoint value. This is the control loop error, and is equal to zero when the process variable is equal to the setpoint (desired) value. A well-behaved control loop is able to maintain a small error term magnitude.
- Control Output: The output of the PID controller, which is typically a value between 0 % and 100 %. This signal controls the amount of power to dissipate in the HLT.
- A/D: The Analogue to Digital Converter (ADC). It transforms the analogue value into its digital representation. The brewing system uses a digital temperature sensor, the LM92. This neat little device has a built-in A/D converter, so an external one is not necessary.
- D/A: The Digital to Analogue Converter (DAC). It transforms the digital value into an analogue signal.
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:
- PID Position Algorithm : The control output is calculated so it responds to the displacement (position) of the process variable from the setpoint value (error term). The control output is an absolute value. Example: the control output is set to 5 %, the heater is then switched on for 5 % of its time.
- PID Velocity Algorithm: The control output is calculated to represent the rate of change (velocity) for the process variable to become equal to the setpoint value. The control output is a relative value (relative to the previous value of the control output). Example: the control output is set to 5 % and the previous value of the control output was 55 %. The new control output will be equal to 55 % - 5 % = 50 %. The heater is then switched on for 50 % of its time (and not 5 %).
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:
- Direct-Acting Loop: the process has a positive gain, meaning that when the control output is increased, the power is increased as well, which results in an increase of temperature. This is the case in our brewing setup.
- Reverse-Acting Loop: the process has a negative gain, meaning that when the control output is increased, the process variable would decrease. This is commonly found in refrigeration controls, where an increase in the cooling input causes a decrease in the process variable (temperature). Accordingly, reverse-acting loops are sometimes called cooling loops.
In the figure shown above, three parallel paths were shown in the PID-loop calculation block:
- Proportional: The proportional term simply responds proportionally to the current size of the error. This loop controller calculates a proportional term value for each PID calculation. When the error is zero, the proportional term is also zero.
- Integral: The integrator (or reset) term integrates (sums) the error values. Starting from the first PID calculation (after the controller was released), the integrator keeps a running total of the error values.
- Derivative: The derivative (or rate) term responds to change in the current error value from the error used in the previous PID calculation. Its job is to anticipate the probable growth of the error and generate a contribution to the output in advance.
The P, I and D terms work together as a team. To be able to control these terms, each term has a programmable gain:
- Kc: The proportional gain. Some textbooks also call this Kp. The unity (in our brewing setup) is % / °C.
- Ti: The time-constant for the integral gain. Some textbooks also define this as: Ti = Kc / Ki. The unity (in our brewing setup) is seconds.
- Td: The time-constant for the derivative gain. Some textbooks also define this as: Td = Kc / Kd. The unity (in our brewing setup) is seconds.
- In fact, there is another parameter that is also important and that is the sample time Ts. The sample time Ts is defined as the time between two calls to the PID-controller. In our brewing system, the sample-time Ts for the PID controller is set to 5 seconds (meaning that once every 5 seconds, a call to the PID controller is made).
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):
- Type A PID Controller: This is the type of PID controller in the picture above, also called Textbook PID. Not many engineers prefer this type. This is because the D term is also dependent of the error term. And since the error term is equal to the setpoint value minus the process variable (the actual HLT temperature), it means that it also reacts to a change in setpoint value. This is not desired, since we want to change the setpoint value without disturbing the controller output.
- Type B PID Controller: In a type B PID controller, the setpoint value is removed from the D term. This will lead to a scheme where the D term is fed only with the process variable (the actual HLT temperature) and no longer with the error term.
- Type C PID Controller: The type B PID controller can be further improved by also removing the error term from the P term. In a type C controller, the error term is still fed to the I-term, but the P- and D-term are only dependent of the process variable (the actual HLT temperature).
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 Page2. 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"
#include
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
Kc.Ts
k0 = ----- (for I-term)
Ti
Td
k1 = Kc . -- (for D-term)
Ts
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;
}
else
{
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.
Variables:
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:
- This function is called once every TS seconds, with Ts being set to 20 seconds in my brewing program. It means that a new value for the PID controller output is calculated every 20 seconds.
- The actual temperatures are read in every second. These temperatures are filtered and then passed to this function (as xk).
- The values of Kc, Ti, Td and Ts can be adjusted by the brewer (see the GUI section on the software page).
- The extension _1 to several variables (e.g. xk_1, lpf_1) indicates the previous version of that variable. For example: xk_1 means the value of xk during the previous call to this function (20 seconds ago). Same with the extension _2 (which was the value 2 * 20 seconds ago).
- Prior to calling pid_reg4() for the first time, it has to be initialised. For this reason, the function init_pid4() was created which calculates the value of certain constants. This routine has to be called whenever one or more parameters have been changed.
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" {
#endif
// 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
};
#endif
#endif
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:
- Open loop response: the open loop response means that the PID controller output is set to a fixed value (e.g. 20%) and the response of the system (in our case the HLT temperature) is measured. The term open loop comes from the fact that the PID controller is not in control, but set to a fixed value, the loop is not closed.
- Closed loop response: the closed loop response means that the PID controller is enabled and that we apply a step to the setpoint value (the HLT temperature reference). Then we measure how the PID controller is doing by measuring the response of the system (the HLT temperature). For example: we could set the HLT reference temperature from 20 °C up to 50 °C, thereby creating a step of 30 °C.
- Dead-Time (TD): the dead-time of the system is the time delay between the initial response and a 10 % increase in the process variable (which is the HLT temperature in our case). Unity is given in seconds.
- Normalized slope (a): the normalized slope of the step response is the increase in temperature per second per percentage of the PID controller output. Stated in a formula: a = change in temperature divided by time-interval divided by PID controller output.
- Gain (K): the gain of the HLT system. We can model our HLT system as having one input (the PID controller output in %) and one output (the HLT temperature). The Gain of this HLT system is then defined as output divided by input and has a unity of °C / %.
- Time-constant (tau): the time-constant of the system is another important parameter of the system. It describes how quick the temperature will increase, given a particular output of the PID controller. Unity is given in seconds.
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:
- t0 = 09:49:04, T1 = 19.20 °C
- T2 = 52.99 °C, T2 - T1 = 33.79 °C
- T1 + 0.283 * (T2 - T1) = 28.76 °C. Check the HLT temperature graph when this temperature was reached. In the experiment, this temperature was reached 6599 seconds after the start (t0). Therefore: tau/3 = 6599 seconds.
- T1 + 0.632 * (T2 - T1) = 40.56 °C. Check the HLT temperature graph when this temperature was reached. In the experiment, this temperature was reached 16573 seconds after the start (t0). Therefore: tau = 16573 seconds.
- Solving tau from the previous two results: tau - tau/3 = 16573 - 6599 <=> (2/3) * tau = 9974 <=> tau = 14961 seconds.
- The Gain is calculated directly from this data as well: Gain = (T2 - T1) / 20 % = 33.79 °C; / % = 1.69 °C / %.
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:
- Dead-Time TD = 115 seconds
- Normalized slope a = 6.68E-5 °C/(%.s).
- Gain (K) = 1.69 °C / %.
- Time-constant (tau) = tau = 14961 seconds.
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:
- Ziegler-Nichols Open Loop
- Ziegler-Nichols Closed Loop
- Cohen-Coon
- Integral of Time weighted Absolute Error (ITAE-Load): this algorithm normally gives the best results. The error signal is minimized over time.
The detailed formulas for every algorithm are as follows:
- Ziegler-Nichols Open Loop:
PID Kc [%/(°C)] Ti [sec.] Td [sec.] 1.2 / (TD * a) 2.0 * TD 0.5 * TD PI-only, No D term Kc [%/(°C)] Ti [sec.] 0.9 / (TD * a) 3.33 * TD - Ziegler-Nichols Closed Loop:
PID Kc [%/(°C)] Ti [sec.] Td [sec.] (1.2 * tau) / (K * TD) 2.0 * TD 0.5 * TD PI-only, No D term Kc [%/(°C)] Ti [sec.] (0.9 * tau) / (K * TD) 3.33 * TD - Cohen-Coon:
PID Kc [%/(°C)] Ti [sec.] Td [sec.] (tau / (K * TD)) * (TD / (4 * tau) + 4 / 3) TD * (32 * tau + 6 * TD) / (13 * tau + 8 * TD) 4 * TD * tau / (2 * TD + 11 * tau) PI-only, No D term Kc [%/(°C)] Ti [sec.] (tau / (K * TD)) * (TD / (12 * tau) + 9 / 10) TD * ((30 * tau + 3 * TD) / (9 * tau + 20 * TD)) - Integral of Time weighted Absolute Error (ITAE-Load)
PID Kc [%/(°C)] Ti [sec.] Td [sec.] (1.357 / K) * [(TD / tau)^-0.947] (tau / 0.842) * [(TD / tau)^0.738] (0.381 * tau * [(TD / tau)^0.995] PI-only, No D term Kc [%/(°C)] Ti [sec.] (0.859 / K) * [(TD / tau)^-0.977] (tau / 0.674) * [(TD / tau)^0.680]
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:
- The site from BESTune for the description of the three types of PID controllers (and some other info).
- The excellent (!) User Manual of the DL05 Micro PLC from AutomationDirect.