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.

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.

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).

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 = (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.

### 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.

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 . 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 .

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

Our last experiment for this article is varying the proportional setpoint weight . The PID gains are fixed at , , . Figure 8 shows the responses with , and . Interestingly, the overshoot can be reduced by decreasing , though the rise time becomes slower.

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.