Message Boards Message Boards

3
|
8027 Views
|
2 Replies
|
5 Total Likes
View groups...
Share
Share this post:

Automatic controller design

Posted 12 years ago
How can I automatically design a controller for a model in SystemModeler?
POSTED BY: Johan Rhodin
2 Replies
Posted 11 years ago
Sometimes it might not be possible to design a controller with PIDTune (unstable model, etc).
In the following model we instead design a controller with TransferFunctionModel and dynamically adjust it to a suitable output response with Manipulate.

The plant I want to control is an inverted pendulum driven by a simple motor device. I read the angle values and feed them back to a PID Controller.
 linearizedModel = WSMLinearize["SimplePendulum.Components.Plant"];
 
 tf = TransferFunctionModel@linearizedModel;
 
 pidTf = TransferFunctionModel[
    kp (1 + 1/(\[Tau]i \[FormalS]) + \[Tau]d \[FormalS]), \[FormalS]];
 
 cltf = SystemsModelFeedbackConnect[
    SystemsModelSeriesConnect[pidTf, tf]];

Manipulate[
Plot[{OutputResponse[
     cltf /. {kp -> gain, \[Tau]i -> iTime, \[Tau]d -> dTime}, \[Pi]/
      2 UnitStep[t], {t, 20}], \[Pi]/2} // Evaluate, {t, 0, 2},
  PlotStyle -> Thick, GridLines -> Automatic, PlotRange -> {-1, 2}],
{{gain, 108, "\!\(\*SubscriptBox[\(k\), \(p\)]\)"}, 10,
  400}, {{dTime, 0.06, "\!\(\*SubscriptBox[\(\[Tau]\), \(d\)]\)"},
  0.01, 10}, {{iTime, .85, "\!\(\*SubscriptBox[\(\[Tau]\), \(i\)]\)"},
   0.01, 10}, SaveDefinitions -> True]

Which creates this manipulate:

Once I've found appropriate values in the Manipulate I simulate the model with WSMSimulate and can visualize the results with a plot:
{pars, parVals} = {{"PID.k", "PID.Ti", "PID.Td"}, {108, 0.85, 0.06}};

sim = WSMSimulate["SimplePendulum.SimpleExample", {0, 10},
   WSMParameterValues ->
    Join[Thread[pars -> parVals], {"PID.yMax" -> 100, "PID.wd" -> 1}]];

Plot[{Evaluate@sim[{"plant1.y"}, t][[1]], \[Pi]/2}, {t, 0, 2},
GridLines -> Automatic, PlotRange -> {-1, 2}]

And just to confirm we can see the 3D-animation of the system as well:


The Modelica models used:
 package SimplePendulum
   extends Modelica.Icons.Library;
   package Components
     extends Modelica.Icons.Library;
     model Plant
       annotation(Icon(coordinateSystem(extent={{-100.0,-100.0},{100.0,100.0}}, preserveAspectRatio=true, initialScale=0.1, grid={10,10}), graphics={Rectangle(visible=true, fillColor={255,255,255}, extent={{-100.0,-100.0},{100.0,100.0}})}), Diagram(coordinateSystem(extent={{-148.5,-45.0},{148.5,105.0}}, preserveAspectRatio=true, initialScale=0.1, grid={5,5}), graphics));
       Modelica.Blocks.Interfaces.RealInput u annotation(Placement(visible=true, transformation(origin={-120.0,80.0}, extent={{-20.0,-20.0},{20.0,20.0}}, rotation=0), iconTransformation(origin={-120.0,-0.0}, extent={{-20.0,-20.0},{20.0,20.0}}, rotation=0)));
       Modelica.Blocks.Interfaces.RealOutput y annotation(Placement(visible=true, transformation(origin={140.0,-0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0), iconTransformation(origin={110.0,-0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
       Modelica.Mechanics.Rotational.Sources.Torque torque1 annotation(Placement(visible=true, transformation(origin={-65.0,80.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
      Modelica.Mechanics.Rotational.Components.Inertia inertia1(J=0.01) annotation(Placement(visible=true, transformation(origin={-40.0,80.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
      inner Modelica.Mechanics.MultiBody.World world annotation(Placement(visible=true, transformation(origin={-120.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
      Modelica.Mechanics.MultiBody.Parts.BodyBox bodyBox1(r={0.5,0,0}) annotation(Placement(visible=true, transformation(origin={30.0,-0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
      Modelica.Mechanics.MultiBody.Joints.Revolute revolute1(useAxisFlange=true) annotation(Placement(visible=true, transformation(origin={-20.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
      Modelica.Mechanics.Rotational.Sensors.AngleSensor angleSensor1 annotation(Placement(visible=true, transformation(origin={40.0,80.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
      Modelica.Mechanics.Rotational.Components.Damper damper1(d=0.1) annotation(Placement(visible=true, transformation(origin={-40.0,40.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
    equation
      connect(angleSensor1.phi,y) annotation(Line(visible=true, origin={111.1375,40.0}, points={{-60.1375,40.0},{15.6375,40.0},{15.6375,-40.0},{28.8625,-40.0}}, color={0,0,127}));
      connect(u,torque1.tau) annotation(Line(visible=true, origin={-98.5,80.0}, points={{-21.5,0.0},{21.5,0.0}}, color={0,0,127}));
      connect(revolute1.axis,angleSensor1.flange) annotation(Line(visible=true, origin={-3.3333,63.3333}, points={{-16.6667,-53.3333},{-16.6667,16.6667},{33.3333,16.6667}}));
      connect(torque1.flange,inertia1.flange_a) annotation(Line(visible=true, origin={-52.5,80.0}, points={{-2.5,-0.0},{2.5,0.0}}));
      connect(revolute1.axis,inertia1.flange_b) annotation(Line(visible=true, origin={-23.3333,56.6667}, points={{3.3333,-46.6667},{3.3333,23.3333},{-6.6667,23.3333}}));
      connect(damper1.flange_a,revolute1.support) annotation(Line(visible=true, origin={-41.6,23.29}, points={{-8.4,16.71},{-11.4,16.71},{-11.4,-10.065},{15.6,-10.065},{15.6,-13.29}}));
      connect(damper1.flange_b,revolute1.axis) annotation(Line(visible=true, origin={-23.3333,30.0}, points={{-6.6667,10.0},{3.3333,10.0},{3.3333,-20.0}}));
      connect(world.frame_b,revolute1.frame_a) annotation(Line(visible=true, origin={-70.0,0.0}, points={{-40.0,0.0},{40.0,0.0}}));
      connect(revolute1.frame_b,bodyBox1.frame_a) annotation(Line(visible=true, origin={5.0,-0.0}, points={{-15.0,0.0},{15.0,-0.0}}));
    end Plant;

  end Components;

  model SimpleExample
    extends Modelica.Icons.Example;
    annotation(Diagram(coordinateSystem(extent={{-148.5,-105.0},{148.5,105.0}}, preserveAspectRatio=true, initialScale=0.1, grid={5,5})));
    Modelica.Blocks.Sources.Constant const(k=3.1415/2) annotation(Placement(visible=true, transformation(origin={-80.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
    Modelica.Blocks.Continuous.LimPID PID(k=108, Ti=0.85, Td=0.06, yMax=100) annotation(Placement(visible=true, transformation(origin={-40.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
    SimplePendulum.Components.Plant plant1 annotation(Placement(visible=true, transformation(origin={0.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
  equation
    connect(PID.y,plant1.u) annotation(Line(visible=true, origin={-20.5,0.0}, points={{-8.5,-0.0},{8.5,0.0}}, color={0,0,127}));
    connect(plant1.y,PID.u_m) annotation(Line(visible=true, origin={-8.2,-8.4}, points={{19.2,8.4},{22.2,8.4},{22.2,-6.6},{-31.8,-6.6},{-31.8,-3.6}}, color={0,0,127}));
    connect(const.y,PID.u_s) annotation(Line(visible=true, origin={-60.5,0.0}, points={{-8.5,-0.0},{8.5,0.0}}, color={0,0,127}));
  end SimpleExample;

end SimplePendulum;
POSTED BY: Johan Rhodin
Posted 12 years ago
Mathematica 9 introduced the function PIDTune which make it super easy to automatically design controllers.

In this example the motor depicted below (available in a standard installation of SystemModeler as "MathematicaExamples.Modeling.invertedPendulum.Components.DCMotor" ) has an input V which governs the voltage in the circuit that turns the motor. We want to regulate the speed.


There are multiple ways to do this. One way is to use the function WSMLinearize to create a state space model that PIDTune can create a controller for.
motorStateSpate =
WSMLinearize[
  "MathematicaExamples.Modeling.invertedPendulum.Components.DCMotor"];
controller = PIDTune[motorStateSpate, "PIFD", "PIDData"]
We can verify the result by looking at the response from feeding the system a step function.
or = OutputResponse[controller["ReferenceOutput"],
   UnitStep[t], {t, 0, 2}];
Plot[or, {t, 0, 2}, PlotRange -> All]

In order to use the calculated parameters, we need to add a controller to our model.
 
The computed parameters can be found by querying the PIDData created by PIDTune.
{pars, parVals} = {{"PID.k", "PID.Ti", "PID.Td"},controller["FeedbackIdealParameters"]}
Finally, by simulating with these parameters we can directly compare to the system Mathematica calculated.
simPID = WSMSimulate["ExampleModel", {0, 2},
   WSMParameterValues -> Thread[pars -> parVals]];
Plot[{simPID[{"DCMotor1.w"}, t], or}, {t, 0, 2},
PlotStyle -> {Orange, {Dotted, Thick}}, PlotRange -> All]



PS here's the simple Modelica code for the ExampleModel above:
 model ExampleModel
   extends Modelica.Icons.Example;
   MathematicaExamples.Modeling.invertedPendulum.Components.DCMotor DCMotor1 annotation(Placement(visible=true, transformation(origin={0.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
   Modelica.Blocks.Continuous.LimPID PID(yMax=1500, yMin=-1500, wd=1) annotation(Placement(visible=true, transformation(origin={-40.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
   Modelica.Blocks.Sources.Step step1(startTime=0) annotation(Placement(visible=true, transformation(origin={-80.0,0.0}, extent={{-10.0,-10.0},{10.0,10.0}}, rotation=0)));
 equation
   connect(step1.y,PID.u_s) annotation(Line(visible=true, origin={-60.5,0.0}, points={{-8.5,0.0},{8.5,0.0}}, color={0,0,127}));
   connect(PID.y,DCMotor1.V) annotation(Line(visible=true, origin={-20.5,0.0}, points={{-8.5,0.0},{8.5,0.0}}, color={0,0,127}));
   connect(DCMotor1.w,PID.u_m) annotation(Line(visible=true, origin={-9.5111,-26.7479}, points={{20.5111,19.7479},{29.5111,19.7479},{29.5111,-13.2521},{-30.4889,-13.2521},{-30.4889,6.5042},{-30.4889,14.7479}}, color={0,0,127}));
end ExampleModel;
POSTED BY: Johan Rhodin
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract