You are on page 1of 13

Advanced PID Controller Implementation

In this digital era, PID controllers have evolved from basic textbook structure to more
sophisticated algorithms. Features such as setpoint/derivative weightings and anti-windup
scheme are often added to improve the closed-loop response. In our previous article A Decorated
PID Controller, we consider a PID structure with modification and additional functions as
follows

To lessen the effect of measurement noise, derivative part is implemented as a filter with
parameter

Back calculation anti-windup scheme is implemented with tracking gain

Setpoint weightings for proportional and derivative paths can be adjusted via
and

, respectively

A feedback diagram with this advanced PID controller is constructed using Xcos palettes as in
Figure 1.

Figure 1 advanced PID feedback diagram


In equation form, this controller can be described as
(1)

with
(2)

where

and

, are reference command, plant output,

controller output, and saturated controller output, respectively. As described in our Discrete-time
PID Controller Implementation article, using backward difference relationship
(3)

Equation (1) can be converted to z-domain as

(4)

Rewrite (4) in terms of

(5)

To implement this PID scheme as a computer algorithm, we have to convert (5) to a difference
equation. It is straightforward to show that (5) can be rewritten as

(6)

with coefficients

(7)

So the corresponding difference equation is

(8)

Response Comparison via Simulation


Equation (8) is ready for implementation on a target processor. Before that phase, we want to
make sure that our equation and coefficients are without error. One easy way is to perform
simulation on Xcos and compare the response to the original continuous-time PID controller. For
this purpose, we construct a model advpid_imp.zcos as shown in Figure 2, consisting of 2
feedback loops. The upper loop is controlled by discrete-time PID in the form (6), and the lower
loop contains the continuous-time PID. The simulation results from the two closed-loop systems
are then compared to verify how well they match.

Figure 2 model advpid_imp.zcos for discrete and continuous PID comparison


Note that the discrete-time PID in the upper loop is contained in a superblock. The internal
details are shown in Figure 3, which corresponds to the discrete-time controller (6).

Figure 3 the internal details of discrete-time PID superblock


Also, at the output of discrete-time PID controller, a LPF transfer function is inserted to prevent
an algebraic loop error, normally occurred with hybrid simulation. The LPF pole is chosen well
above the closed-loop bandwidth so the filter does not have noticeable effect on the responses.
For easy editing, all the parameters in advpid_imp.zcos are initialized using a script file
advpid_imp.sce. The plant is chosen as a third-order lag transfer function
(9)

which can be created in Scilab by


s = poly(0,'s');
P = syslin('c',1/(s+1)^3);

Then, controller parameters are assigned values. These can be chosen as you like since the
purpose of this simulation is to compare the responses. Here the PID gains are obtained from
Zieglers Nichols frequency domain tuning method, and others are assigned some practical
values.

kp = 4.8;
// PID gains
ki = 2.7;
kd = 2.1;
N = 10;
// filter coefficient
kt = 1.2; // back calculation gain for anti-windup
wp = 0.7;
// setpoint weight for proportional term
wd = 0.1;
// setpoint weight for derivative term
Ts = 0.01;
// sampling peroid

For sampling period Ts, the value should match the simulation sampling period in the clock.
The parameters left to be assigned are the limits in saturation block. Put in some reasonable
values such that some saturation effect happens during transient, since we prefer response
comparison with the back calculation term activated. Too small the limit range would cause
severe performance degradation. By some trial and error, we are finally satisfied with these
values for saturation block
ulim = 2000;
llim = -2000;

Finally, the coefficients in (7) need to be computed. We introduce additional variables x1 and x2
for terms that appear in several places.
x1
x2
a1
a2
b1
b2
b3
c1
c2
c3
c4
d1
d2
d3

=
=
=
=
=
=
=
=
=
=
=
=
=
=

(1+N*Ts);
(2+N*Ts);
x2/x1;
-1/x1;
kp;
-kp*x2/x1;
kp/x1;
ki*Ts;
-ki*Ts/x1;
kt*Ts;
-kt*Ts/x1;
kd*N/x1;
-2*kd*N/x1;
kd*N/x1;

After all parameters are assigned, interactively or by executing advpid_imp.sce, we proceed by


clicking on the simulation start button. The simulation results in Figure 4 show that the plant and
controller outputs from continuous and discrete PID are almost identical. This makes us
confident that the discrete-time PID and its coefficients are derived correctly.

Figure 4 response comparison between continuous and discrete PID

Implementation on Target Processor


After the verification process by Xcos simulation, we are now ready to implement our advanced
PID controller on a target processor, in this case a PIC24EP256MC202 by Microchip. The plant
is a DC motor with H-bridge drive, as described in the DC Motor Control Part I article. Figure 5
shows the experimental setup used. The rightmost board is our controller prototype, where the
PID algorithm will be downloaded and executed.

Figure 5 DC motor experimental setup


The source code is written entirely in C. The whole code, consisting of a couple of source files, is
rather long and messy due to supporting functions such as UART communication, command
handling, etc. Below we discuss only the parts related to our PID controller implementation.
The sampling period, controller parameters and resulting coefficients are defined as global
variables, with some initial values assigned
// sampling
double Ts =
// -- these
double Kp =
double Ki =
double Kd =
double Kt =
double Wp =
double Wd =
int N = 20;

period
0.01;
// sampling time
parameters are user-adjustable
1272; // proportional gain
8777; // integral gain
46; // derivative gain
10; // tracking gain
0.5; // proportional weight
0; // derivative weight
// filter coefficient

// ----- coefficients of PID algorithm -------------double a1, a2, b1, b2, b3, c1, c2, c3, c4;
double d1, d2, d3;

and also variables to keep previous values of controller inputs and outputs
double ep_2, ep_1, ep_0, e_1, e_0, eus_1, eus_0, ed_2, ed_1, ed_0 ;
double u_2, u_1, u_0, u_0n; // variables used in PID computation

Now, the coefficients have to be computed before the algorithm starts, and every time the user
changes any parameter involved. So it is convenient to put the computation in a function
void PIDSetup(void) // PID coefficient setup
// -- this function must be invoked anytime
// -- any parameter involved is changed by user
{
double x1, x2;
_T1IE = 0; // disable timer 1
x1
x2
a1
a2
b1
b2
b3
c1
c2
c3
c4
d1
d2
d3

=
=
=
=
=
=
=
=
=
=
=
=
=
=

1 + N*Ts;
2 + N*Ts;
x2/x1;
-1/x1;
Kp;
-Kp*x2/x1;
Kp/x1;
Ki*Ts;
-Ki*Ts/x1;
Kt*Ts;
-Kt*Ts/x1;
Kd*N/x1;
-2*Kd*N/x1;
Kd*N/x1;

_T1IE = 1;
_T1IF = 0;

// enable timer 1
// reset timer 1 interrupt flag

As usual, the actual PID algorithm is placed in a timer interrupt, in this case timer 1.
void __attribute__((interrupt, auto_psv)) _T1Interrupt(void)
// Timer 1 interrupt every Ts second
{
// perform position read from QEI module of PIC24EP256MC202
QEIpVal.half[0] = POS1CNTL; // read lsw
QEIpVal.half[1] = POS1HLD; // read msw from hold register
dcmpos = QEIpVal.full*360/ENCPPMx4;
// position in degree
if (SysFlag.PosLoop == CLOSED) // closed loop PID control
{
u_2 = u_1;
u_1 = u_0;
ep_2 = ep_1;
ep_1 = ep_0;
ep_0 = Wp*pcmd - dcmpos;
e_1 = e_0;
e_0 = pcmd - dcmpos;

// weighted proportional error

// true error

eus_1 = eus_0;
// back calculation error
if (abs(u_0) <= PWMMAX) eus_0 = 0;
else if (u_0>PWMMAX) eus_0 = PWMMAX - u_0;
else eus_0 = -u_0 - PWMMAX;

ed_2 = ed_1;
ed_1 = ed_0;
ed_0 = Wd*pcmd - dcmpos;
u_0 =
a1*u_1+a2*u_2+b1*ep_0+b2*ep_1+b3*ep_2+c1*e_0+c2*e_1+c3*eus_0+c4*eus_1+d1*ed_0
+d2*ed_1+d3*ed_2;
if (u_0>=0) { // positive sense
if (u_0 < PWMMAX) PWMVal = (unsigned int)u_0; // limit to PWM
range
else PWMVal = PWMMAX;
DIR = 0;
}
else
range

{ // negative sense
u_0n = -u_0;
if (u_0n < PWMMAX) PWMVal = (unsigned int)u_0n;

// limit to PWM

else PWMVal = PWMMAX;


DIR = 1;
}
OC1R = PWMVal;
} // if (SysFlag.PosLoop == CLOSED)
_T1IF = 0; // reset interrupt flag
}

Note that our H-brige driver is commanded by a pair of signals: PWM and DIR, as explained in
the DC Motor Control Part I article. The motor turns clockwise and counter-clockwise when DIR
= 0 and 1, respectively, and the motor speed corresponds to the duty cycle of PWM signal,
dictated by PWMVal variable.

Experimental Results
An initial set of PID gains is achieved by performing some naive automatic tuning based on the
Ziegler-Nichols frequency domain method. The C code is not shown in this article, though it is
quite similar to that given in our Digital PID Controllers document. The automatic tuning yields
. Other parameters are set to
. As shown in Figure 6, This set of PID gains with
rather high Ki value results in oscillary response (dashed red). So we begin fine tuning by
reducing
to
, and
, resulting in the dotted blue, and black, respectively.
The overshoot and oscillation lessen with decreasing

Figure 6 step response with 3 values of


Next, we try incresing

from

, to

Figure 7. Oscillation is reduced with increasing

, and
.

, resulting in the responses in

Figure 7 step response with 3 values of


Our last experiment for this article is varying the proportional setpoint weight
gains are fixed at
responses with
reduced by decreasing

,
, and

. The PID

. Figure 8 shows the


. Interestingly, the overshoot can be

, though the rise time becomes slower.

Figure 8 step response with 5 values of


We leave it to anyone who implements this advanced PID controller on his/her system to
experiment with the anti-windup back calculation gain
and derivative setpoint weighting
. It is suggested in some literature that

be set to 0 so that abrupt change in

command would not affect the derivative action of the controller. We set
and

for all the experimental responses in this article.