Motor position control
The objectives of this exercise are:
- To implement a position control system for an inertia-dominated load
- To explore appropriate path planning
- 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.
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)\).
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 #include
d 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:
Initialize the table editor variables.
Initialize the path profile variables. See the
Sramps()
discussion that follows.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 { ; // context NiFpga_IrqContext irqContext*a_table; // table table *profile; // profile seg int nseg; // no. of segs ; // ready flag NiFpga_Bool irqThreadRdy} ThreadResource;
Call the table editor. The table should contain three “show” values, labeled as follows:
P_ref: revs P_act: revs VDAout: mV
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);
*mySegs = threadResource->profile;
seg 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:
- 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. - Call
Sramps()
to compute the current reference position \(\Theta_R\), as discussed next. - Call
pos()
, to obtain the position of the motor \(\Theta_J\), as discussed next. - Compute the current error \(e=\Theta_R-\Theta_J\).
- 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. - Send the control value to the DAC
AOC0
usingAio_Write()
. - Change the table to reflect the current conditions of the controller.
- 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 theseg
data type. To test the position control system, initialize an arraymySegs
of typeseg
as follows:double vmax = 25.; // rev/s double amax = 30.; // rev/s^2 double dwell = 1.0; // s [8] = { // rev seg mySegs{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.The prototype of the
Sramps()
function from the sramps.h header file isint Sramps( *segs, // segments array seg 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 returns0
otherwise.A typical call of
Sramps()
might be= Sramps(mySegs, nseg, &iseg, &itime, T, &Pref); nsamp
When
Sramps()
is called for the first time, setitime = -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:
- Your name (
string
) - The reference position array (rad), cast to
double *
- The current position array (rad)
- The output voltage array (V)
- The PIDF array, cast to
double *
- 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.
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.
Load the experimental results from the Lab8.mat file.
Define a discrete version of the motor/load plant transfer from lab 7. Consider using
c2d()
.Form the discrete controller from the values in the PIDF array in Lab8.mat.
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} \]
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.In a single MATLAB figure, plot the results in three
subplot()
s versus time, as follows:- Reference position, theoretical position, and experimental position
- Experimental error (reference \(-\) experimental position) and theoretical error (reference \(-\) theoretical position)
- Theoretical and experimental torque
Analyze the results from problem L8.2. What do you conclude?
Online Resources for Section L8
No online resources.