cmon-pid

A implementation of several common PID controller transfer functions in C++, header only, without dependencies.

PID law

The PIDs function can be described by the PID law which is a linear ordinary differential equation (ODE):

`u(t)=K_p*e(t)+K_i*int_0^t e(x)dx+K_d*(d e(t))/(dt)`

By Laplace transformation we get:

`u(s)=K_p*e(s)+(K_i)/s * e(s)+K_d*s*e(s)`

Dividing by e(s) gives us the transfer function which is just the ratio of the output to the input signal. Transfer functions of linear ODEs are always rational which makes them handy for manipulating. You can express things like concatenation, addition or feedback by simple algebraic operations. The transfer function of a PID in canonical rational form is:

`(u(s))/(e(s))=(K_d*s^2+K_p*s+K_i)/s`

This transfer function is not proper meaning it is unbounded when the frequencies approach infinity. This is a problem when we want to realize it in some physical form. The usual remedy is to add another pole which acts as a low pass filter to limit the bandwidth. A realizable transfer function of a PID would be:

`(u(s))/(e(s))=(K_d*s^2+K_p*s+K_i)/s * 1/(Tf*s+1)`

The filter time constant Tf is usually determined by the noise level and required bandwidth. But it cannot be arbitrarily low. A minimum of about 20% of the sampling time is necessary to avoid problems with the numerical integration of the ODE. Obviously when you want more bandwidth you also need a higher sampling frequency.

PID Transfer Functions

There are many variations of transfer functions for PIDs depending on the way you specify the tuning parameters.

Series PID

The series PID is a combination of a PI followed by an PD controller and has the transfer function:

`(u(s))/(e(s))=G*(Ti*s+1)/(Ti*s)*(Td*s+1)*1/(Tf*s+1)`

Its zeros are simply the reciprocal of the time constants:

`s_0=-1/(Ti),s_0=-1/(Td)`

There is no difference between the integration and differential time parameter except for the gain. If you adjust for gain Ti and Td are exchangeable. The disadvantage of this function is that you cannot have conjugate complex zeros without using complex arithmetic which this implementation does not support.

Skogestads IMC tuning rules can be applied to this controller.

Skogestad, Sigurd. (2004). Simple Analytic Rules for Model Reduction and PID Controller Tuning. Journal of Process Control. 13. 291-309. 10.1016/S0959-1524(02)00062-8.

Parallel PID

The parallel PID just uses the polynomial coefficients:

`(u(s))/(e(s))=(Kp+(Ki)/s+Kd*s)*1/(Tf*s+1)`

The parameters are not very meaningful to describe the dynamics but it is the most versatile PID since it can represent all variations PID, PI, PD, ID, P, I and D controllers. Also it can express conjugate complex zeros with real valued parameters:

`s_0=-(sqrt(Kp^2-4*Kd*Ki)+Kp)/(2*Kd),s_0=(sqrt(Kp^2-4*Kd*Ki)-Kp)/(2*Kd)`

The site pidtuner can compute tuning parameters for it.

N-Standard PID

The standard PID is a modification of the parallel PID with the gain factored and with differential and integral terms that have parameters with time units.

`(u(s))/(e(s))=Kp*(1+1/(Ti*s)+(Td*s)/((Td)/N*s+1))`

The low pass filter is applied only to the differential term and its time constant is given as `Tf = (Td)/N`. This is somewhat confusing since the differential time parameter and noise filtering are usually unrelated. Also the zeros depend on N in a complicated way:

`s_0=-(sqrt(N^2*Ti^2+(-4*N^2-2*N)*Td*Ti+Td^2)+N*Ti+Td)/((2*N+2)*Td*Ti)`

`s_0=(sqrt(N^2*Ti^2+(-4*N^2-2*N)*Td*Ti+Td^2)-N*Ti-Td)/((2*N+2)*Td*Ti)`

Nonetheless this PID is common probably because it is supported by Matlab where you also can find a function for tuning it.

Standard PID

This PID is like the N-Standard PID except that the low pass filter is applied to all terms.

`(u(s))/(e(s))=Kp*(1+1/(Ti*s)+Td*s)*(1/(Tf*s+1))`

The zeros do not depend on the filter parameter:

`s_0=-(sqrt(Ti^2-4*Td*Ti)+Ti)/(2*Td*Ti),s_0=(sqrt(Ti^2-4*Td*Ti)-Ti)/(2*Td*Ti)`

There are lots of tuning methods for it.

Advanced Methods of PID Controller Tuning for Specified Performance.

Solutions of the Transfer Functions.

Solutions for u(t) with the sampled function e(t) are approximated by backward Euler and Tustin integration.

Series PID Backward Euler Bilinear/Tustin
Parallel PID Backward Euler Bilinear/Tustin
N-Standard PID Backward Euler Bilinear/Tustin
Standard PID Backward Euler Bilinear/Tustin

Simulation Tests

The simulation code can be compiled with Visual Studio 2019. The result is stored as comma separated value file which can be processed and plotted by several programs like Gnuplot, R, Excel, KST2 or the Openmodellica Editor OME. The images you see here were created with KST2. The mkplots.ps1 script will do this when you install KST2 parallel to this directory. Simulations are done with a continuous test system in closed loop with the PID. The test system is integrated by 4th order Runge Kutta with a small step size while the PID uses it's own discrete integration at the given clock rate.

Test system transfer function

The test system is second order with two time constants 3 and 10 seconds and a DC gain of 1.

`H:(y(s))/(u(s))=1/((3*s+1)*(10*s+1))`

PID controller settings

We are choosing the PID parameters so that the poles of the test system will be compensated and set the gain for a closed loop rise time of 1 second. With the serial PID this is easy:

`K:(u(s))/(e(s))=G*(Ti*s+1)/(Ti*s)*(Td*s+1)*1/(Tf*s+1)`

`Ti=10, Td=3, G = 10`

This is a setting you likely do not want in a real world application. While the tracking performance is excellent, disturbance rejection is rather sluggish due to the long integration times. Also making a slow system respond that fast requires to kick it really hard. The kick is not a fault of the controller but a result of the target specification.

Closed Loop Reference Signal

The continuous closed loop system has the transfer function:

`C:(y(s))/(r(s))=(K*H)/(1+K*H)`

where H is the test system and K the continuous PID controller. r(s) is the set-point function. The y(s) signal of this system is plotted as the green curve for reference.

Plotted signals

Signal Value Condition
setpoint r(t)
u_bwe u(t) of K with backward euler integration e(t) = setpoint - y_bwe
u_bil u(t) of K with bilinear integration e(t) = setpoint - y_bil
y_bwe y(t) of H u(t) = u_bwe
y_bil y(t) of H u(t) = u_bil
continuous y(t) of C r(t) = setpoint
openloop y(t) of H u(t) = setpoint

Fast Sampling

The sampling time and filter time constant are a 10th of the closed loop response time. `Tf=0.1, h=0.1`. With ample sampling rate and bandwidth the performance of the discrete and continuous controllers are similar.

Slow Sampling

With a sampling rate of half the closed loop response time we see some difference between the backward Euler and bilinear/tustin integration. `h=0.5`

High bandwidth

This happens when the sampling rate is too low for the bandwidth. `h=0.1, Tf=0.0001`

Low Bandwidth

A low pass filter of half the response time has not much effect on the performance but can reduce the kick. `Tf=0.5`

Disturbance Rejection

Disturbance rejection is slow but they are damped by the system and may not be problem under normal conditions. `Tf=0.1`

Clamping

Even with reduced bandwidth the actuator output is almost 50 times larger than the final value. Most physical system will not have such a large range. The output can be limited by clamping. Here we use `u(t)<=10, Tf=0.1`

While this works reasonably well manipulating the output is akin to disturbance and if we have a system with slow disturbance rejection like this recovery times will be quite long.

Back-calculation

Back-calculation limits the output value like clamping but can be tuned with a parameter, here `Tw=Ti`. But it has the same problem with slow disturbance rejection.

Disturbance with `Ti=2`

To improve the disturbance rejection the integration time can be reduced to 2.

Clamping with `Ti=2`

This also improves the recovery after the output has been clamped.

Step response with `Ti=2`

Tracking performance is reduced though.