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 N
  • Back calculation anti-windup scheme is implemented with tracking gain K_t
  • Setpoint weightings for proportional and derivative paths can be adjusted via W_p and W_d, 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)   \begin{equation*}  u(s) = K_pe_p(s)+\frac{K_i}{s}e(s)+\frac{K_t}{s}e_{us}(s)+\frac{N}{1+ N/s}K_de_d(s) \end{equation*}

with

(2)   \begin{eqnarray*} e_p(s) &=& W_pr(s) - y(s) \nonumber \\ e(s) &=& r(s) - y(s) \nonumber \\ e_{us}(s) &=& u_{sat}(s) - u(s) \nonumber \\ e_d(s) &=& W_dr(s) - y(s) \nonumber \\  \end{eqnarray*}

where r(s), y(s), u(s) and u_{sat}(s), 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)   \begin{equation*}  s = \frac{z-1}{T_sz} \end{equation*}

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

(4)   \begin{equation*}  u(z) = K_pe_p(z) + \frac{K_iT_sz}{z-1}e(z)+\frac{K_tT_sz}{z-1}e_{us}(z)+\frac{K_dN(z-1)}{(1+NT_s)z-1}K_de_d(z) \end{equation*}

Rewrite (4) in terms of z^{-1}

(5)   \begin{equation*}  u(z) = K_pe_p(z) + \frac{K_iT_s}{1-z^{-1}}e(z)+\frac{K_tT_s}{1-z^{-1}}e_{us}(z)+\frac{K_dN(1-z^{-1})}{1+NT_s-z^{-1}}K_de_d(z) \end{equation*}

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)   \begin{eqnarray*}  u(z) &=& a_1z^{-1}u(z) + a_2z^{-2}u(z) + b_1e_p(z) + b_2z^{-1}e_p(z)+b_3z^{-2}e_p(z)+c_1e(z)+c_2z^{-1}e(z) \nonumber \\  &+&c3e_{us}(z)+c_4z^{-1}e_{us}(z)+d_1e_d(z)+d_2z^{-1}e_d(z)+d_3z^{-2}e_d(z) \nonumber \\ \end{eqnarray*}

with coefficients

(7)   \begin{eqnarray*} a_1&=&\frac{2+NT_s}{1+NT_s}, \qquad a_2=-\frac{1}{1+NT_s}, \qquad b_1=K_p, \qquad b_2=-K_p\frac{2+NT_s}{1+NT_s},  \nonumber \\ b_3&=&\frac{K_p}{1+NT_s}, \qquad c_1=K_iT_s, \qquad c_2=-\frac{K_iT_s}{1+NT_s}, \qquad c_3=K_tT_s, \nonumber \\ c_4&=&-\frac{K_tT_s}{1+NT_s}, \qquad d_1=\frac{K_dN}{1+NT_s}, \qquad d_2=-\frac{2K_dN}{1+NT_s}, \qquad d_3=\frac{K_dN}{1+NT_s} \nonumber \\ \end{eqnarray*}

So the corresponding difference equation is

(8)   \begin{eqnarray*}  u(n)&=& a_1u(n-1)+a_2u(n-2)+b1e_p(n)+b_2e_p(n-1)+b_3e_p(n-2)+c_1e(n)+c_2e(n-1) \nonumber \\ &+&c_3e_{us}(n)+c4e_{us}(n-1)+d_1e_d(n)+d_2e_d(n-1)+d_3e_d(n-2) \nonumber \\ \end{eqnarray*}

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)   \begin{equation*}  P(s) = \frac{1}{(s+1)^3} \end{equation*}

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 = (1+N*Ts);
x2 = (2+N*Ts);
a1 = x2/x1;
a2 = -1/x1;
b1 = kp;
b2 = -kp*x2/x1;
b3 = kp/x1;
c1 = ki*Ts;
c2 = -ki*Ts/x1;
c3 = kt*Ts;
c4 = -kt*Ts/x1;
d1 = kd*N/x1;
d2 = -2*kd*N/x1;
d3 = 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 period
double Ts = 0.01;   // sampling time
// -- these parameters are user-adjustable
double Kp = 1272; // proportional gain
double Ki = 8777; // integral gain
double Kd = 46; // derivative gain
double Kt = 10;  // tracking gain
double Wp = 0.5;  // proportional weight
double Wd = 0;  // derivative weight
int N = 20; // 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 = 1 + N*Ts;
    x2 = 2 + N*Ts;
    a1 = x2/x1;
    a2 = -1/x1;
    b1 = Kp;
    b2 = -Kp*x2/x1;
    b3 = Kp/x1;
    c1 = Ki*Ts;
    c2 = -Ki*Ts/x1;
    c3 = Kt*Ts;
    c4 = -Kt*Ts/x1;
    d1 = Kd*N/x1;
    d2 = -2*Kd*N/x1;
    d3 = Kd*N/x1;
 
    _T1IE = 1;   // enable timer 1
    _T1IF = 0;   // 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;  // weighted proportional error
 
        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   {  // negative sense
            u_0n = -u_0;
            if (u_0n < PWMMAX) PWMVal = (unsigned int)u_0n;  // limit to PWM range
            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 K_p=1273, K_i=8777, K_d=46. Other parameters are set to K_t=20, W_p = 0.5, W_d = 0, N=20. 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 K_i to 6000, and 4000, resulting in the dotted blue, and black, respectively. The overshoot and oscillation lessen with decreasing K_i.



Figure 6 step response with 3 values of K_i

Next, we try incresing K_d from 46, to 100, and 200, resulting in the responses in Figure 7. Oscillation is reduced with increasing K_d.



Figure 7 step response with 3 values of K_d

Our last experiment for this article is varying the proportional setpoint weight W_p. The PID gains are fixed at K_p=1273, K_i=6000, K_d=150. Figure 8 shows the responses with W_p = 0.2, 0.35, 0.5, 0.8, and 1. Interestingly, the overshoot can be reduced by decreasing W_p, though the rise time becomes slower.



Figure 8 step response with 5 values of W_p

We leave it to anyone who implements this advanced PID controller on his/her system to experiment with the anti-windup back calculation gain K_t and derivative setpoint weighting W_d. It is suggested in some literature that W_d be set to 0 so that abrupt change in command would not affect the derivative action of the controller. We set K_t = 10, N = 20, and W_d = 0 for all the experimental responses in this article.

Comments

comments

Comments are closed.