# Tuning of a Two-Loop Autopilot

This example shows how to use Simulink® Control Design™ to tune a two-loop autopilot controlling the pitch rate and vertical acceleration of an airframe.

### Model of Airframe Autopilot

The airframe dynamics and the autopilot are modeled in Simulink.

```
open_system('rct_airframe1')
```

The autopilot consists of two cascaded loops. The inner loop controls the pitch rate q, and the outer loop controls the vertical acceleration `az`

in response to the pilot stick command `azref`

. In this architecture, the tunable elements include the PI controller gains ("az Control" block) and the pitch-rate gain ("q Gain" block). The autopilot must be tuned to respond to a step command `azref`

in about 1 second with minimal overshoot. In this example, we tune the autopilot gains for one flight condition corresponding to zero incidence and a speed of 984 m/s.

To analyze the airframe dynamics, trim the airframe for and . The trim condition corresponds to zero normal acceleration and pitching moment ( and steady). Use `findop`

to compute the corresponding closed-loop operating condition. Note that we added a "delta trim" input port so that `findop`

can adjust the fin deflection to produce the desired equilibrium of forces and moments.

opspec = operspec('rct_airframe1'); % Specify trim condition % Xe,Ze: known, not steady opspec.States(1).Known = [1;1]; opspec.States(1).SteadyState = [0;0]; % u,w: known, w steady opspec.States(3).Known = [1 1]; opspec.States(3).SteadyState = [0 1]; % theta: known, not steady opspec.States(2).Known = 1; opspec.States(2).SteadyState = 0; % q: unknown, steady opspec.States(4).Known = 0; opspec.States(4).SteadyState = 1; % integrator states unknown, not steady opspec.States(5).SteadyState = 0; opspec.States(6).SteadyState = 0; op = findop('rct_airframe1',opspec);

Operating point search report: --------------------------------- opreport = Operating point search report for the Model rct_airframe1. (Time-Varying Components Evaluated at time t=0) Operating point specifications were successfully met. States: ---------- Min x Max dxMin dx dxMax ___________ ___________ ___________ ___________ ___________ ___________ (1.) rct_airframe1/Airframe Model/Aerodynamics & Equations of Motion/ Equations of Motion (Body Axes)/Position 0 0 0 -Inf 984 Inf -3047.9999 -3047.9999 -3047.9999 -Inf 0 Inf (2.) rct_airframe1/Airframe Model/Aerodynamics & Equations of Motion/ Equations of Motion (Body Axes)/Theta 0 0 0 -Inf -0.0097235 Inf (3.) rct_airframe1/Airframe Model/Aerodynamics & Equations of Motion/ Equations of Motion (Body Axes)/U,w 984 984 984 -Inf 22.6897 Inf 0 0 0 0 -1.4367e-11 0 (4.) rct_airframe1/Airframe Model/Aerodynamics & Equations of Motion/ Equations of Motion (Body Axes)/q -Inf -0.0097235 Inf 0 1.1477e-16 0 (5.) rct_airframe1/Integrator -Inf 0.00070807 Inf -Inf -0.0097235 Inf (6.) rct_airframe1/az Control/Integrator/Continuous/Integrator -Inf 0 Inf -Inf 0.00024207 Inf Inputs: ---------- Min u Max __________ __________ __________ (1.) rct_airframe1/delta trim -Inf 0.00070807 Inf Outputs: None ----------

Linearize the "Airframe Model" block for the computed trim condition `op`

and plot the gains from the fin deflection `delta`

to `az`

and `q`

:

G = linearize('rct_airframe1','rct_airframe1/Airframe Model',op); G.InputName = 'delta'; G.OutputName = {'az','q'}; bodemag(G), grid

Note that the airframe model has an unstable pole:

pole(G)

ans = -0.0320 -0.0255 0.1253 -29.4685

### Frequency-Domain Tuning with LOOPTUNE

You can use the `looptune`

function to automatically tune multi-loop control systems subject to basic requirements such as integral action, adequate stability margins, and desired bandwidth. To apply `looptune`

to the autopilot model, create an instance of the `slTuner`

interface and designate the Simulink blocks "az Control" and "q Gain" as tunable. Also specify the trim condition `op`

to correctly linearize the airframe dynamics.

ST0 = slTuner('rct_airframe1',{'az Control','q Gain'},op);

Mark the reference, control, and measurement signals as points of interest for analysis and tuning.

addPoint(ST0,{'az ref','delta fin','az','q'});

Finally, tune the control system parameters to meet the 1 second response time requirement. In the frequency domain, this roughly corresponds to a gain crossover frequency `wc`

= 5 rad/s for the open-loop response at the plant input "delta fin".

wc = 5; Controls = 'delta fin'; Measurements = {'az','q'}; [ST,gam,Info] = looptune(ST0,Controls,Measurements,wc);

Final: Peak gain = 1.01, Iterations = 72

The requirements are normalized so a final value near 1 means that all requirements are met. Confirm this by graphically validating the design.

```
figure('Position',[100,100,560,714])
loopview(ST,Info)
```

The first plot confirms that the open-loop response has integral action and the desired gain crossover frequency while the second plot shows that the MIMO stability margins are satisfactory (the blue curve should remain below the yellow bound). Next check the response from the step command `azref`

to the vertical acceleration `az`

:

T = getIOTransfer(ST,'az ref','az'); figure step(T,5)

The acceleration `az`

does not track `azref`

despite the presence of an integrator in the loop. This is because the feedback loop acts on the two variables `az`

and `q`

and we have not specified which one should track `azref`

.

### Adding a Tracking Requirement

To remedy this issue, add an explicit requirement that `az`

should follow the step command `azref`

with a 1 second response time. Also relax the gain crossover requirement to the interval [3,12] to let the tuner find the appropriate gain crossover frequency.

TrackReq = TuningGoal.Tracking('az ref','az',1); ST = looptune(ST0,Controls,Measurements,[3,12],TrackReq);

Final: Peak gain = 1.23, Iterations = 51

The step response from `azref`

to `az`

is now satisfactory:

Tr1 = getIOTransfer(ST,'az ref','az'); step(Tr1,5) grid

Also check the disturbance rejection characteristics by looking at the responses from a disturbance entering at the plant input.

Td1 = getIOTransfer(ST,'delta fin','az'); bodemag(Td1) grid

```
step(Td1,5)
grid
title('Disturbance rejection')
```

Use `showBlockValue`

to see the tuned values of the PI controller and inner-loop gain.

showBlockValue(ST)

AnalysisPoints_ = D = u1 u2 u3 u4 y1 1 0 0 0 y2 0 1 0 0 y3 0 0 1 0 y4 0 0 0 1 Name: AnalysisPoints_ Static gain. ----------------------------------- az_Control = 1 Kp + Ki * --- s with Kp = 0.00166, Ki = 0.0017 Name: az_Control Continuous-time PI controller in parallel form. ----------------------------------- q_Gain = D = u1 y1 1.985 Name: q_Gain Static gain.

If this design is satisfactory, use `writeBlockValue`

to apply the tuned values to the Simulink model and simulate the tuned controller in Simulink.

writeBlockValue(ST)

### MIMO Design with SYSTUNE

Cascaded loops are commonly used for autopilots. Yet one may wonder how a single MIMO controller that uses both `az`

and `q`

to generate the actuator command `delta fin`

would compare with the two-loop architecture. Trying new control architectures is easy with `systune`

or `looptune`

. For variety, we now use `systune`

to tune the following MIMO architecture.

```
open_system('rct_airframe2')
```

As before, compute the trim condition for and .

opspec = operspec('rct_airframe2'); % Specify trim condition % Xe,Ze: known, not steady opspec.States(1).Known = [1;1]; opspec.States(1).SteadyState = [0;0]; % u,w: known, w steady opspec.States(3).Known = [1 1]; opspec.States(3).SteadyState = [0 1]; % theta: known, not steady opspec.States(2).Known = 1; opspec.States(2).SteadyState = 0; % q: unknown, steady opspec.States(4).Known = 0; opspec.States(4).SteadyState = 1; % controller states unknown, not steady opspec.States(5).SteadyState = [0;0]; op = findop('rct_airframe2',opspec);

Operating point search report: --------------------------------- opreport = Operating point search report for the Model rct_airframe2. (Time-Varying Components Evaluated at time t=0) Operating point specifications were successfully met. States: ---------- Min x Max dxMin dx dxMax ___________ ___________ ___________ ___________ ___________ ___________ (1.) rct_airframe2/Airframe Model/Aerodynamics & Equations of Motion/ Equations of Motion (Body Axes)/Position 0 0 0 -Inf 984 Inf -3047.9999 -3047.9999 -3047.9999 -Inf 0 Inf (2.) rct_airframe2/Airframe Model/Aerodynamics & Equations of Motion/ Equations of Motion (Body Axes)/Theta 0 0 0 -Inf -0.0097235 Inf (3.) rct_airframe2/Airframe Model/Aerodynamics & Equations of Motion/ Equations of Motion (Body Axes)/U,w 984 984 984 -Inf 22.6897 Inf 0 0 0 0 2.4588e-11 0 (4.) rct_airframe2/Airframe Model/Aerodynamics & Equations of Motion/ Equations of Motion (Body Axes)/q -Inf -0.0097235 Inf 0 -4.0169e-16 0 (5.) rct_airframe2/MIMO Controller -Inf 0.00065361 Inf -Inf -0.0089973 Inf -Inf 2.6167e-19 Inf -Inf 0.030259 Inf Inputs: ---------- Min u Max __________ __________ __________ (1.) rct_airframe2/delta trim -Inf 0.00043574 Inf Outputs: None ----------

As with `looptune`

, use the `slTuner`

interface to configure the Simulink model for tuning. Note that the signals of interest are already marked as Linear Analysis points in the Simulink model.

ST0 = slTuner('rct_airframe2','MIMO Controller',op);

Try a second-order MIMO controller with zero feedthrough from `e`

to `delta fin`

. To do this, create the desired controller parameterization and associate it with the "MIMO Controller" block using `setBlockParam`

:

C0 = tunableSS('C',2,1,2); % Second-order controller C0.D.Value(1) = 0; % Fix D(1) to zero C0.D.Free(1) = false; setBlockParam(ST0,'MIMO Controller',C0)

Next create the tuning requirements. Here we use the following four requirements:

**Tracking**:`az`

should respond in about 1 second to the`azref`

command**Bandwidth and roll-off**: The loop gain at`delta fin`

should roll off after 25 rad/s with a -20 dB/decade slope**Stability margins**: The margins at`delta fin`

should exceed 7 dB and 45 degrees**Disturbance rejection**: The attenuation factor for input disturbances should be 40 dB at 1 rad/s increasing to 100 dB at 0.001 rad/s.

% Tracking Req1 = TuningGoal.Tracking('az ref','az',1); % Bandwidth and roll-off Req2 = TuningGoal.MaxLoopGain('delta fin',tf(25,[1 0])); % Margins Req3 = TuningGoal.Margins('delta fin',7,45); % Disturbance rejection % Use an FRD model to sketch the desired attenuation profile with a few points Freqs = [0 0.001 1]; MinAtt = [100 100 40]; % in dB Req4 = TuningGoal.Rejection('delta fin',frd(db2mag(MinAtt),Freqs)); Req4.Focus = [0 1];

You can now use `systune`

to tune the controller parameters subject to these requirements.

```
AllReqs = [Req1,Req2,Req3 Req4];
Opt = systuneOptions('RandomStart',3);
rng(0)
[ST,fSoft] = systune(ST0,AllReqs,Opt);
```

Final: Soft = 1.42, Hard = -Inf, Iterations = 47 Final: Soft = 1.42, Hard = -Inf, Iterations = 62 Final: Soft = 1.14, Hard = -Inf, Iterations = 93 Final: Soft = 1.14, Hard = -Inf, Iterations = 127

The best design has an overall objective value close to 1, indicating that all four requirements are nearly met. Use `viewGoal`

to inspect each requirement for the best design.

```
figure('Position',[100,100,987,474])
viewGoal(AllReqs,ST)
```

Compute the closed-loop responses and compare with the two-loop design.

T = getIOTransfer(ST,{'az ref','delta fin'},'az'); figure step(Tr1,'b',T(1),'r',5) title('Tracking') legend('Cascade','2 dof')

ans = Legend (Cascade, 2 dof) with properties: String: {'Cascade' '2 dof'} Location: 'northeast' Orientation: 'vertical' FontSize: 9 Position: [0.7030 0.7695 0.1830 0.0789] Units: 'normalized' Use GET to show all properties

step(Td1,'b',T(2),'r',5) title('Disturbance rejection') legend('Cascade','2 dof')

ans = Legend (Cascade, 2 dof) with properties: String: {'Cascade' '2 dof'} Location: 'northeast' Orientation: 'vertical' FontSize: 9 Position: [0.7030 0.7695 0.1830 0.0789] Units: 'normalized' Use GET to show all properties

The tracking performance is similar but the second design has better disturbance rejection properties.