H-infinity Control of DC Motor Part II : Controller Implementation

From H-infinity Control of DC Motor Part I : Plant Modeling and Controller Synthesis, we discuss essential steps required to achieve a controller for DC motor plant, from estimating a plant model by least-square identification, adjusting weighting functions, formulating a generalized plant, synthesizing, and performing controller model reduction. Data captured from real DC motor is provided in the Scilab script dcm_lsid.sce. Before we move on to implementation phase in this part, if you have not done so, I encourage you to try step 1 – 5 in Part I in Scilab by running the scripts in this order: dcm_lsid.sce, kreduced.sci, dcm_hinf_st.sce. You may leave everything untouched on first attempt. When you get the idea how things work, start playing around with weighting functions, adjusting gamma variable, or using the iterative h_inf function in place of hinf.

Now, assuming no error, you would eventually have in Scilab a continuous-time controller from H_\infty synthesis, with the name Skr and Skr_tf for state-space and transfer function data format, respectively. Depending on your weight setup and gamma value, the resulting controller may be different. To be consistent, we stick to this third order controller from Part I

(1)   \begin{equation*}  C(s)=\frac{-500s^3+1146.8162s^2+46179.923s+384.79566}{s^3+31.25635s^2+461.63448s+4.9087826} \end{equation*}

Step 6 : convert to discrete-time controller

To implement a controller, its discrete-time representation is needed. So (1) must be converted to a transfer function in Z-domain. Refer to Module 7 of Scilab Control Engineering Basics, bilinear transformation is normally a preferred choice to obtain C(z). Conveniently, Scilab has a command cls2dls to do so. A linear system in state-state format and sampling period must be specified.

-->Ts = 0.01;   // sampling period
-->Skd = cls2dls(Skr,Ts);
-->Skdtf=ss2tf(Skd)
 Skdtf  =
 
                                      2           3  
    432.0685 - 1290.3395z + 1280.5196z - 422.2483z   
    ----------------------------------------------   
                                          2   3      
      - 0.7323527 + 2.425178z - 2.6928211z + z

For the discrete-time controller to maintain the desired properties, it is imperative that the sampling period Ts passed to cls2dls, must match the timer interrupt period used on the target MCU. For our implementation, we select 0.01 sec as sampling period. In motion control it is sometime called “servo cycle.”

Well, if you have no experience with controller implementation, this is perhaps a good time to read Module 6.

Step 7 : (optional) rearrange to DF II structure

A technique that helps in the controller implementation is known as “direct-form conversion.” This discrete-time manipulation is commonly used in digital signal processing to reduce memory usage by half. Though that benefit is not significant in our low-order controller, it does help formulate a compact algorithm structure.

From now on, DF stands for Direct Form. We are interested in 2 variations: DF I and DF II. As a simple example, for a second-order transfer function

(2)   \begin{equation*}  H(z)=\frac{b_0+b_1z^{-1}+b_2z^{-2}}{1+a_1z^{-1}+a_2z^{-2}} \end{equation*}

The conversion from DF I to an intermediate form, then to DF II is illustrated in Figure 6.



Figure 6 conversion from DF I to DF II

Notice that the number of delays (Z^{-1} blocks) are reduced from 4 to 2. This manipulation can be extended to a transfer function of any order.

Back to our discrete-time controller from H_\infty synthesis

(3)   \begin{equation*}  C(z)=\frac{-422.24831z^3+1280.5196z^2-1290.3395z+432.0685}{z^3-2.6928211z^2+2.42517z-0.7323527} \end{equation*}

Multiply both numerator and denominator by z^{-3}

(4)   \begin{equation*}  C(z)=\frac{-422.24831+1280.5196z^{-1}-1290.3395z^{-2}+432.0685z^{-3}}{1-2.6928211z^{-1}+2.42517z^{-2}-0.7323527z^{-3}} \end{equation*}

and compare to a general third order form

(5)   \begin{equation*}  C(z)=\frac{b_0+b_1z^{-1}+b_2z^{-2}+b_3z^{-3}}{1+a_1z^{-1}+a_2z^{-2}+a_33z^{-3}} \end{equation*}

we obtain values for coefficients b_0 - b_3 and a_1 - a_3. Having already the discrete-time transfer function Skdtf in Scilab, we can extract coefficient values using command coeff. Note, however, that Scilab orders the numerator and denominator polynomials in reverse order, and also that Scilab array index starts from 1, so care must be taken to extract the correct coefficient values.

To anticipate an even more confusing scenario, the algorithm will eventually be written in C. And array index in C starts from 0! Anyway, do not let this issue scare you. It’s just some sort of “bookkeeping” problem.

Let’s get back to the controller (5). Its DF II structure can be constructed using standard Xcos blocks as shown in Figure 7. Here we use Xcos only for diagram display purpose. It is left as an exercise to the reader to build an Xcos feedback model for simulation.



Figure 7 discrete-time controller (5) in DF II structure

What we are more interested is to write a control algorithm from the DF II structure in Figure 7. Note the variables cx[0] - cx[3] along the middle vertical path through the delays. These are defined as controller states.

Step 8 : coding the control algorithm

With the above development, we are ready to write a C algorithm for the controller (4). Somewhere at the top of C source file, declare global arrays for the coefficients, controller states, and input and output variables as follows

double coeffa[3] = {-2.692821, 2.425178, -0.732353};
double coeffb[4] = {-422.248307, 1280.519630, -1290.339489, 432.068496};
double cx[4]={0,0,0,0};  // controller states
double u_0;		// output variable for positive sense
double u_0n;   	// output variable for negative sense
double e_0;   	// error input for the controller

and put the control algorithm in a timer interrupt routine. Here our example is for PIC24EP256MC202, a 16-bit MCU that uses Microchip’s XC16 compiler as development tool. The syntax for timer 1 interrupt is

void __attribute__((interrupt, auto_psv)) _T1Interrupt(void)
// Timer 1 interrupts every 0.01 second
{
   // controller state updates
   *(cx+4)=*(cx+3);
   *(cx+3)=*(cx+2);
   *(cx+2)=*(cx+1);
   *(cx+1)=*cx;
 
   e_0 = pcmda - dcmpos; // error input = command – actual pos
   // ******* Control algorithm in DF II structure *******
   *cx = e_0 -*(coeffa)*(*(cx+1))-*(coeffa+1)*(*(cx+2))-*(coeffa+2)*(*(cx+3));
   u_0 = *(coeffb)*(*(cx))+*(coeffb+1)*(*(cx+1))+*(coeffb+2)*(*(cx+2))+*  (coeffb+3)*(*(cx+3));
   // ** The following code converts output to PWM and DIR signals **
   if (u_0>=0)  {  // positive sense
      if (u_0 < PWMMAX) 
          PWMVal = (unsigned int)u_0;  // limit to PWM range
      else PWMVal = PWMMAX;
      DIR = 0;   // rotate clockwise
   }
   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;  // rotate counter-clockwise
   }
   OC1R = PWMVal;  // load to register of output compare 1 module
  _T1IF = 0;  // clear the interrupt flag
}

Some points in the C algorithm above need further explanation

  1. The following variables and macros also need to be declared at the top of source file
    • pcmda: (double) angle command reference, from user input or generated from cubic spline function
    • dcmpos: (double) actual motor angle from QEI module, updated every timer interrupt
    • PWMVal: (unsigned int) PWM value from 0 to PWMMAX
    • PWMMAX: (macro) maximum value of PWM, say, 65535 for 16-bit PWM
    • DIR: (macro) output latch corresponding to the pin used as direction bit for DC motor drive. The prototype board uses pin 25 (RB14)
  2. Pointer arithmetic is used for faster execution. You may argue that the improvement is insignificant for a small order controller in this example. If you are more comfortable with reference by value programming style, feel free to modify it.
  3. The control algorithm consists of two lines of code around the middle of timer routine. The code after that is just a conversion process from a single output variable u_0, to a pair of output signals PWMVal and DIR for the H-bridge driver. PWMVal is always positive integer, while the value of DIR changes with the sign of controller output. Suppose 16-bit PWM is used, for example, PWMMAX = 65535. So when u_0 = -73214.34, PWMValue = 65535 and DIR = 1. The motor turns in a negative sense; in this case, counter-clockwise.
  4. Be careful about indexing. Acess anything outside an array results in system crash.
  5. Pay attention to motor sense and quadrature signals from your encoder unit. The QEI module in PIC MCU can be setup to take care of different phasing of A,B signals. Improper configuration can result in positive feedback and closed-loop unstable.
  6. Make sure that your timer interrupt period matches the value used at discretization in step 6. Here we use 0.01 sec. Suppose you are using a low performance MCU, it could happen that the control algorithm could not be finished within 0.01 sec duration. Then you must use longer interrupt period. Redo step 6 with Ts set to your chosen value.
  7. It is a good idea to have an LED blinking to indicate the algorithm is running. Then you’ll know when problems occur like program crashes, too short interrupt period, etc. What I do is setting a global variable msc, and put these lines at the end of timer routine
    msc++;  // msc defined as global variable of type volatile int
    if (msc>100)   {
         LED1 = !LED1;
         msc = 0;
    }

    For 0.01 sec timer interrupt period, this causes LED1 to turn on and off each second.

  8. Study the mechanisms of the timer module in your MCU. Apart from the initialization, it may need some other attention to work properly. For the PIC24, the interrupt flag _T1IF must be cleared at the end of timer 1 routine so that next interrupt could occur.

That’s it. Compile and download the algorithm to MCU. With proper initialization, the DC motor should lock at an angle determined by command reference value when the feedback loop is closed.

Note : you may experience a “runaway” problem when the loop is closed the first time. That’s because the error between command reference and actual encoder count may be too large. Some initialization trick might be needed. Since in this experiment the DC motor is turning free with no reference point, I simply load the encoder counter with the command reference before closing the loop. You can do the other way around; i.e., read the encoder count and preset the command to that value.

About MCU Prototype Board

Let’s have a close look at the target board I use for this experiment. As claimed earlier, no luxury hardware is required. Figure 8 shows snapshots of the top and buttom of this prototype. All components are soldered and wired manually. I use this board for other embedded experiments so there are more peripherals than needed. Actually, in this H_\infty control experiment only the MCU and FTDI USB-to-serial IC are used.



Figure 8 hand-wired prototype board for the controller

To obtain motor angle feedback, quadrature encoder signals A, B are fed to QEI1 module of PIC24EP256MC202. Only MC (Motor Control) family of PIC24EP has QEI module. The GP (General Purpose) family does not. Read our PIC24EP QEI Basics, in addition to Microchip datasheet and family reference manual, if you want to learn how to setup and use the module.

The experimental results that follow are captured from this board via UART. Simple C functions are written to aid data communication in ASCII format. I simply use Hyperterminal program to save data in text files and copy/paste to Scilab.

The board costs about USD 20. Despite its messy appearance, this prototype serves our purpose well. I am working on PCB design for this board. That would take a while.

Step Response Result

Figure 9 shows step response and controller output data from the experiment. The motor angle was commanded to move from 10 degrees to 100 degrees. Rough assessment from the figure gives rise time = 0.5 second, 40% overshoot, and setting time = 2 seconds. The lower plot shows the controller output is well below 16-bit PWM limit (65535).



Figure 9 step response from controller (4)

Notice small undershoot at the beginning of motion. This is common for a non-minimum phase plant.

Weighting Function Adjustment

Before ending this article, we would like to address a design question. How to improve this step response? Let’s say we want faster motion. That means the controller (and the closed-loop system) must have higher bandwidth.

In classical control, the best solution is to redesign the controller. Lead/lag factors may be added to alter the frequency response of loop transfer function. For H_\infty scheme, the controller needs to be re-synthesized by adjusting weighting functions for S and T.

Referring to our H-infinty Problem Setup article, to achieve higher closd-loop bandwidth, we need to move parameter \omega_B in w_1(s) to higher frequency. Then w_2 has to be adjusted accordingly to allow higher bandwidth for T. In other words, the cutoff frequency of 1/w_2(s) must be higher than \omega_B. Otherwise, a stabilizing controller may not be admissible.

We demonstrate by synthesizing another 2 controllers, call them C_HG1 and C_HG2 where C_HG1 has higher gain than our previous controller, and C_HG2 has the highest gain of all. For HG1 synthesis, the weighting functions are selected as

(6)   \begin{equation*}  w_1(s) = \frac{0.5s+10}{s+0.005} \end{equation*}

(7)   \begin{equation*}  w_2(s) = \frac{0.0033s+1}{0.003s+2} \end{equation*}

Running the synthesis yields the controller

(8)   \begin{equation*}  C_{HG1}(z)=\frac{-385.81434z^3+1226.1926z^2-1286.7205z+446.34286}{z^3-2.646584z^2+2.3450716z-0.6984851} \end{equation*}

The weighing functions for HG2 are

(9)   \begin{equation*}  w_1(s) = \frac{0.5s+20}{s+0.004} \end{equation*}

(10)   \begin{equation*}  w_2(s) = \frac{0.002s+1}{0.002s+2} \end{equation*}

resulting in the controller

(11)   \begin{equation*}  C_{HG2}(z)=\frac{-309.33922z^3+1122.1033z^2-1298.175z+485.41229}{z^3-2.5706201z^2+2.2150312z-0.6444083} \end{equation*}

All we have to do next is replace the coefficients declared in the C source file with new set of values from controller (8), compile and download the hex file to MCU, then run the experiment and capture data. Repeat this procedure for controller (11).

A laborious and error-prone task is to extract the coefficients from a controller and put them in the C source file in correct order. So why don’t we let Scilab do it for us?

With a discrete-time controller Skdtf obtained from dcm_hinf_st.sce, extract coefficients from the numerator and denominator.

-->an=coeff(Skdtf.den);
-->bn=coeff(Skdtf.num);

With desired values contained in bn and an, we have to print them on Scilab console in correct order.

-->msprintf('an = {%f, %f, %f}',an(3),an(2),an(1))
 ans  =
 
 an = {-2.692821, 2.425178, -0.732353}
 
-->msprintf('bn = {%f, %f, %f, %f}',bn(4),bn(3),bn(2),bn(1))
 ans  =
 
 bn = {-422.248299, 1280.519620, -1290.339491, 432.068499}

Our only job left is to copy the value in bracket and paste to the C declaration

double coeffa[3] = {-2.692821, 2.425178, -0.732353};
double coeffb[4] = {-422.248307, 1280.519630, -1290.339489, 432.068496};

Comparison Results

Step response and controller output comparisons from the 3 controllers are shown in Figure 10. Clearly, high gain controllers can improve the response time, and also reduce overshoot and undershoot. The controller output always needs to be monitored to make sure that no saturation occurs.



Figure 10 step response comparison of 3 controllers

Step response is a basic property commonly used to evaluate tracking performance of a feedback system. Beyond that, one may want to experiment with more advanced responses such as ramp or parabola. It is not difficult to execute and capture any type of input response on our prototype board, provided that you can write C function for such input.

To illustrate, suppose we want to observe tracking performance of the 3 controllers to reference points generated by cubic polynomial function. A command generator is coded in C to produce array of points, which are inputted to the controller in place of the pcmda variable in the algorithm above. Both input and response data are sent back to PC via UART. Figure 11 shows resulting tracking response comparison. The dash-dot curve is the command generated by cubic polynomial function.



Figure 11 tracking performance comparison

Conclusion

In this 2-part article, the whole process of H-infinity control from synthesis to implementation is demonstrated using a DC motor plant and a low-cost MCU prototype board. Controller synthesis, order reduction, and discretization are performed using Scilab. Then the discrete-time controller is rearranged to a DF II structure ready to be implemented as a C algorithm. Since most embedded products can be developed using C language, the algorithm can be easily ported to another MCU. The important requirement is it has to run inside a timer routine, with interrupt period equals the value used in discretization step.

Supplement

Scilab files used in Part II

Comments

comments

Comments are closed.