Advanced PID Controller Implementation
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
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.
with
(2)
where
and
controller output, and saturated controller output, respectively. As described in our Discrete-time
PID Controller Implementation article, using backward difference relationship
(3)
(4)
(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)
(8)
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;
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;
// 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
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
from
, to
, and
.
,
, and
. The PID
command would not affect the derivative action of the controller. We set
and