RTC Website

Motor position control

TX

The objectives of this exercise are:

  1. To implement a position control system for an inertia-dominated load
  2. To explore appropriate path planning
  3. To integrate the use of a standard MATLAB design tool into the application development system

The T1 target system will be required, but only the components listed in appendix B are needed for this lab exercise. Set up each component in accordance with the instructions for your specific T1 version in section A.2. Connect your components in the manner described in the “Online Resources” section of the web page for this lab exercise: https://rtcbook.org/wt.

Introduction

In this exercise, a closed-loop position control system for the DC motor will be developed. The physical system is identical to that of lab 7: as shown in figure 8.12, the optical encoder, the DAC (connected to the motor amplifier), and the periodic timer interrupt will be combined to control the DC motor.

 Figure 8.12
Figure 8.12: Schematic of the test apparatus.

A MATLAB tool will be used to design an appropriate proportional-integral-derivative (PID) controller, shown in figure 8.13. Later, you will evaluate the controller performance for a time-varying position reference path \(\theta_R(t)\).

 Figure 8.13
Figure 8.13: Control block diagram for lab 8.

This project builds on your past work. The program that you write should be structurally similar to that of lab 7, and many of its components can be reused.

PID control design and evaluation

For this lab exercise, you will write two MATLAB scripts: one to design your PID controller and another to compare its performance to an analytical model. Specifically, the first script should design a PIDF controller using the MATLAB Control System toolbox function pidtune(), as introduced in section 8.5. This compensator should be designed to track the reference input and to have control bandwidth of \(10\) Hz. A PIDF controller, described in section 8.4, improves the noise immunity of a PID controller by limiting the high-frequency response of the derivative term. Check your controller design by plotting the closed-loop step response using the plant parameters of your specific T1 target system. For the T1a target system, you can use the values from section 8.5; for other systems, see section A.2.

The script should convert the continuous-time transfer function to discrete-time (c2d(), tf(), and tfdata(), with sample time \(T=0.005\) s), and then use tf2sos() (transfer functions to second-order sections) to break the transfer function into biquads. Finally, use the sos2header() function (see section E.1) to write the biquad filter to a C header file (myPIDF.h). Once copied into your Eclipse workspace, that header can be #included in your myRIO C program (after the biquad struct has been defined).

For example, once the continuous transfer function of the plant has been defined, the MATLAB design can be completed and written to the header file as follows:

%---design PIDF controller
wc      = 2 * pi * 10;  % 10 Hz control bandwidth (gain crossover)
Options = pidtuneOptions('DesignFocus', 'reference-tracking');
Cp      = pidtune(plant, 'pidf', wc, Options)

%---convert controller to discrete-time biquads
Cdp   = c2d(Cp, T, 'tustin');  % discrete controller in parallel form
Cd    = tf(Cdp);               % convert to transfer function
[b,a] = tfdata(Cd, 'v');       % put in vector form
sos   = tf2sos(b, a);          % extract the biquads

%---write controller description to C header
fid = fopen('myPIDF.h', 'W');
  sos2header(fid, sos, 'PIDF', T, 'PIDF position control');
fclose(fid);

As in lab 7, your second script should load the actual response of the position control system (Lab8.mat), and compare it to both the ideal reference displacement and the dynamic model prediction. See the next discussion for more details.

Program description

The program should be similar in structure to that of lab 7, consisting of (1) a Main thread, which initializes the task and calls ctable2() to communicate with the user; and (2) a Timer thread, which maintains timing using an interrupt, implements the position control, and saves the results. Your specific controller definition should be derived from the header file written from your MATLAB script.

Two threads

Main thread

The Main thread performs the following tasks:

  1. Initialize the table editor variables.

  2. Initialize the path profile variables. See the Sramps() discussion that follows.

  3. Set up the timer IRQ interrupt (as in lab 6, lab 7).

  4. As in lab 7, register and create the Timer thread to catch the timer interrupt. The Timer thread should gain access to both the table data and the path profile through pointers in the Thread resource. For example,

    typedef struct {
      NiFpga_IrqContext irqContext;    // context
      table             *a_table;      // table
      seg               *profile;      // profile
      int               nseg;          // no. of segs
      NiFpga_Bool       irqThreadRdy;  // ready flag
    } ThreadResource;
  5. Call the table editor. The table should contain three “show” values, labeled as follows:

    P_ref: revs
    P_act: revs
    VDAout: mV
  6. When the table editor exits, signal the Timer thread to terminate. Wait for it to do so.

Timer thread

The Timer thread should call the interrupt service routine (ISR). At the beginning of the starting function, declare convenient names for the table entries from the table pointer and for the ramp segment variables. For example,

double *pref  = &((threadResource->a_table + 0)->value);
double *pact  = &((threadResource->a_table + 1)->value);
double *VDAmV = &((threadResource->a_table + 2)->value);
seg *mySegs = threadResource->profile;
int nseg    = threadResource->nseg;

The Timer thread should include a loop timed by the IRQ, and it should be terminated only by its ready flag.

Before the control loop begins, do the following

  • Initialize the analog input/output (I/O), and set the motor voltage to zero, using Aio_Write() (as in lab 7).
  • Set up the encoder counter interface (as in lab 7).

Each time through the loop, it should do the following:

  1. Get ready for the next interrupt by: waiting for IRQ to assert, writing the Timer Write Register, and then writing TRUE to the Timer Set Time Register.
  2. Call Sramps() to compute the current reference position \(\Theta_R\), as discussed next.
  3. Call pos(), to obtain the position of the motor \(\Theta_J\), as discussed next.
  4. Compute the current error \(e=\Theta_R-\Theta_J\).
  5. Call cascade() to compute the control value from the current error using PIDF control filter. Important: Limit the control value to the range \([-10.0, +10.0]\) V.
  6. Send the control value to the DAC AOC0 using Aio_Write().
  7. Change the table to reflect the current conditions of the controller.
  8. Save the results of this sample period for later analysis, as discussed next.

Functions

Three functions are needed within the ISR loop.

cascade()

The cascade() function, called once from the ISR during each BTI, implements the general-purpose linear difference equation algorithm from lab 6. For this lab, use the same C code that you used in lab 6. In this case, there will be 1 biquad section.

Note that, as in lab 6, all calculations should be made in (double) floating-point arithmetic.

pos()

Write a pos() function to read the encoder counter and return the displacement as a (double) in units of basic displacement increments (BDI) (encoder counts), relative to the first position read.

Sramps()

The C function Sramps() implements the path planning method of section 8.6, and is available from the T1 library. It returns the current input reference position \(\Theta_R\). The function accepts an input array of structures, each describing a separate displacement ramp segment. Called once each cycle of the control loop, Sramps() steps through the segments, and then repeats the complete path indefinitely.

We will initialize the path array in main(), and then pass the array and the number of segments to the Timer thread through the Thread Resource (described previously in the Main thread section).

The segment data type seg describes the characteristics of each ramp segment as follows:

typedef struct {
  double xfa;       // position
  double v;         // velocity limit
  double a;         // acceleration limit
  double d;         // dwell time (s)
} seg;

Add #include "sramps.h" to your code to have access to the seg data type. To test the position control system, initialize an array mySegs of type seg as follows:

double vmax = 25.;   // rev/s
double amax = 30.;   // rev/s^2
double dwell = 1.0;  // s
seg mySegs[8] = {    // rev
    {20.125,    vmax,   amax,   dwell},
    {40.250,    vmax,   amax,   dwell},
    {60.375,    vmax,   amax,   dwell},
    {80.500,    vmax,   amax,   dwell},
    {60.625,    vmax,   amax,   dwell},
    {40.750,    vmax,   amax,   dwell},
    {20.875,    vmax,   amax,   dwell},
    { 0.000,    vmax,   amax,   dwell}
};
int nseg = 8;

Notice that mySegs consists of four increasing ramps of \(20.125\) revolutions each, followed by four similar decreasing ramps that will return the motor to the starting position. All the segments are subject to the same velocity and acceleration limits, and all dwell for 1 s before proceeding to the next segment. See figure 8.14.

 Figure 8.14
Figure 8.14: Commanded position reference function ΘR(t).

The prototype of the Sramps() function from the sramps.h header file is

int Sramps(
  seg *segs,   // segments array
  int nseg,    // number of segments
  int *iseg,   // current segment index
  int *itime,  // current time index
  double T,    // sample period
  double *xa   // next reference positon
);

At the end of the last segment, Sramps() returns the total number of time steps in all the segments. It returns 0 otherwise.

A typical call of Sramps() might be

nsamp = Sramps(mySegs, nseg, &iseg, &itime, T, &Pref);

When Sramps() is called for the first time, set itime = -1 and *iseg = -1 to initialize its operation.

Saving the responses

The data should be saved by defining data arrays in the ISR for each of the reference position, the actual position, and the torque. Then, an autoincremented index variable can be used to store the data in the arrays during each BTI. Increment the index as needed, stopping when it reaches the length of the arrays. A convenient length would be \(6000\) points each.

After the main loop terminates, but while still in the Timer thread, write the results to the Lab8.mat file. The results should include the following:

  1. Your name (string)
  2. The reference position array (rad), cast to double *
  3. The current position array (rad)
  4. The output voltage array (V)
  5. The PIDF array, cast to double *
  6. The BTI length (s)

Use the same methods as lab 6, lab 7 to bring the Lab8.mat file to MATLAB.

Emulation

As an aid to debugging, our T1 C library includes a software “emulation” (a dynamic model) of the analog output, the amplifier, the motor, and the encoder. The emulator allows you to run your code and save the MATLAB file without being connected to the amplifier and motor.

To activate the emulator, #include the header file emulate.h. When you want to execute your code on the myRIO using the hardware interfaces, comment out the included emulator header and rebuild the project.

Laboratory exploration and evaluation

Write, test, and debug the program described in subsection L8.3.

TODO explain

Reference the listing:

Reference a line number: lab1:nifpga_status

Write a MATLAB script (not the one in which you designed the controller) to analyze the result of running the program that you wrote in problem L8.1. It should do the following.

  1. Load the experimental results from the Lab8.mat file.

  2. Define a discrete version of the motor/load plant transfer from lab 7. Consider using c2d().

  3. Form the discrete controller from the values in the PIDF array in Lab8.mat.

  4. Form the closed-loop system models relating the reference position \(\Theta_R\) input to the position \(\Theta_J\) and torque \(T\) outputs: \[ \begin{aligned} G_1(z)=\frac{\Theta_J(z)}{\Theta_R(z)} \quad \text{and}\quad G_2(z) = \frac{T(z)}{\Theta_R(z)}. \end{aligned} \]

  5. Using lsim(), simulate the system to find the theoretical responses for both the position \(\Theta_J(t)\) and the torque \(T(t)\) to the reference position \(\Theta_R(t)\) array that you stored in Lab8.mat.

  6. In a single MATLAB figure, plot the results in three subplot()s versus time, as follows:

    1. Reference position, theoretical position, and experimental position
    2. Experimental error (reference \(-\) experimental position) and theoretical error (reference \(-\) theoretical position)
    3. Theoretical and experimental torque

Analyze the results from problem L8.2. What do you conclude?

Online Resources for Section L8

No online resources.