LimPID

block LimPID "P, PI, PD, and PID controller with limited output, anti-windup compensation, setpoint weighting and optional feed-forward"
    import Modelica.Blocks.Types.InitPID;
    import Modelica.Blocks.Types.Init;
    import Modelica.Blocks.Types.SimpleController;

    extends Modelica.Blocks.Interfaces.SVcontrol;

    output Real controlError = u_s - u_m "Control error (set point - measurement)";
    parameter Modelica.Blocks.Types.SimpleController controllerType = Modelica.Blocks.Types.SimpleController.PID "Type of controller";
    parameter Real k(min = 0, unit = "1") = 1 "Gain of controller";
    parameter Modelica.SIunits.Time Ti(min = Modelica.Constants.small) = 0.5 "Time constant of Integrator block"
        annotation (Dialog(enable = controllerType == .Modelica.Blocks.Types.SimpleController.PI or controllerType == .Modelica.Blocks.Types.SimpleController.PID));
    parameter Modelica.SIunits.Time Td(min = 0) = 0.1 "Time constant of Derivative block"
        annotation (Dialog(enable = controllerType == .Modelica.Blocks.Types.SimpleController.PD or controllerType == .Modelica.Blocks.Types.SimpleController.PID));
    parameter Real yMax(start = 1) "Upper limit of output";
    parameter Real yMin = -yMax "Lower limit of output";
    parameter Real wp(min = 0) = 1 "Set-point weight for Proportional block (0..1)";
    parameter Real wd(min = 0) = 0 "Set-point weight for Derivative block (0..1)"
        annotation (Dialog(enable = controllerType == .Modelica.Blocks.Types.SimpleController.PD or controllerType == .Modelica.Blocks.Types.SimpleController.PID));
    parameter Real Ni(min = 100 * Modelica.Constants.eps) = 0.9 "Ni*Ti is time constant of anti-windup compensation"
        annotation (Dialog(enable = controllerType == .Modelica.Blocks.Types.SimpleController.PI or controllerType == .Modelica.Blocks.Types.SimpleController.PID));
    parameter Real Nd(min = 100 * Modelica.Constants.eps) = 10 "The higher Nd, the more ideal the derivative block"
        annotation (Dialog(enable = controllerType == .Modelica.Blocks.Types.SimpleController.PD or controllerType == .Modelica.Blocks.Types.SimpleController.PID));
    parameter Boolean withFeedForward = false "Use feed-forward input?"
        annotation (
            Evaluate = true,
            choices(checkBox = true));
    parameter Real kFF = 1 "Gain of feed-forward input"
        annotation (Dialog(enable = withFeedForward));
    parameter Modelica.Blocks.Types.InitPID initType = Modelica.Blocks.Types.InitPID.DoNotUse_InitialIntegratorState "Type of initialization (1: no init, 2: steady state, 3: initial state, 4: initial output)"
        annotation (
            Evaluate = true,
            Dialog(group = "Initialization"));
    parameter Real xi_start = 0 "Initial or guess value for integrator output (= integrator state)"
        annotation (Dialog(
            group = "Initialization",
            enable = controllerType == .Modelica.Blocks.Types.SimpleController.PI or controllerType == .Modelica.Blocks.Types.SimpleController.PID));
    parameter Real xd_start = 0 "Initial or guess value for state of derivative block"
        annotation (Dialog(
            group = "Initialization",
            enable = controllerType == .Modelica.Blocks.Types.SimpleController.PD or controllerType == .Modelica.Blocks.Types.SimpleController.PID));
    parameter Real y_start = 0 "Initial value of output"
        annotation (Dialog(
            enable = initType == .Modelica.Blocks.Types.InitPID.InitialOutput,
            group = "Initialization"));
    parameter Modelica.Blocks.Types.LimiterHomotopy homotopyType = Modelica.Blocks.Types.LimiterHomotopy.Linear "Simplified model for homotopy-based initialization"
        annotation (
            Evaluate = true,
            Dialog(group = "Initialization"));
    parameter Boolean strict = false "= true, if strict limits with noEvent(..)"
        annotation (
            Evaluate = true,
            choices(checkBox = true),
            Dialog(tab = "Advanced"));
    parameter Boolean limitsAtInit = true "Has no longer an effect and is only kept for backwards compatibility (the implementation uses now the homotopy operator)"
        annotation (
            Dialog(tab = "Dummy"),
            Evaluate = true,
            choices(checkBox = true));
    constant Modelica.SIunits.Time unitTime = 1 annotation (HideResult = true);
    Modelica.Blocks.Interfaces.RealInput u_ff if withFeedForward "Optional connector of feed-forward input signal"
        annotation (Placement(transformation(
            origin = {60, -120},
            extent = {
                {20, -20}, 
                {-20, 20}},
            rotation = 270)));
    Modelica.Blocks.Math.Add addP(k1 = wp, k2 = -1) annotation (Placement(transformation(extent = {
        {-80, 40}, 
        {-60, 60}})));
    Modelica.Blocks.Math.Add addD(k1 = wd, k2 = -1) if with_D annotation (Placement(transformation(extent = {
        {-80, -10}, 
        {-60, 10}})));
    Modelica.Blocks.Math.Gain P(k = 1) annotation (Placement(transformation(extent = {
        {-50, 40}, 
        {-30, 60}})));
    Modelica.Blocks.Continuous.Integrator I(k = unitTime / Ti, y_start = xi_start, initType = if initType == InitPID.SteadyState then Init.SteadyState else if initType == InitPID.InitialState or initType == InitPID.DoNotUse_InitialIntegratorState then Init.InitialState else Init.NoInit) if with_I annotation (Placement(transformation(extent = {
        {-50, -60}, 
        {-30, -40}})));
    Modelica.Blocks.Continuous.Derivative D(k = Td / unitTime, T = max([Td / Nd,1e-14]), x_start = xd_start, initType = if initType == InitPID.SteadyState or initType == InitPID.InitialOutput then Init.SteadyState else if initType == InitPID.InitialState then Init.InitialState else Init.NoInit) if with_D annotation (Placement(transformation(extent = {
        {-50, -10}, 
        {-30, 10}})));
    Modelica.Blocks.Math.Gain gainPID(k = k) annotation (Placement(transformation(extent = {
        {20, -10}, 
        {40, 10}})));
    Modelica.Blocks.Math.Add3 addPID annotation (Placement(transformation(extent = {
        {-10, -10}, 
        {10, 10}})));
    Modelica.Blocks.Math.Add3 addI(k2 = -1) if with_I annotation (Placement(transformation(extent = {
        {-80, -60}, 
        {-60, -40}})));
    Modelica.Blocks.Math.Add addSat(k1 = 1, k2 = -1) if with_I annotation (Placement(transformation(
        origin = {80, -50},
        extent = {
            {-10, -10}, 
            {10, 10}},
        rotation = 270)));
    Modelica.Blocks.Math.Gain gainTrack(k = (k * Ni) ^ (-1)) if with_I annotation (Placement(transformation(extent = {
        {0, -80}, 
        {-20, -60}})));
    Modelica.Blocks.Nonlinear.Limiter limiter(uMax = yMax, uMin = yMin, strict = strict, limitsAtInit = limitsAtInit, homotopyType = homotopyType) annotation (Placement(transformation(extent = {
        {70, -10}, 
        {90, 10}})));
protected
    parameter Boolean with_I = controllerType == SimpleController.PI or controllerType == SimpleController.PID annotation (
        Evaluate = true,
        HideResult = true);
    parameter Boolean with_D = controllerType == SimpleController.PD or controllerType == SimpleController.PID annotation (
        Evaluate = true,
        HideResult = true);
public
    Modelica.Blocks.Sources.Constant Dzero(k = 0) if not with_D annotation (Placement(transformation(extent = {
        {-40, 20}, 
        {-30, 30}})));
    Modelica.Blocks.Sources.Constant Izero(k = 0) if not with_I annotation (Placement(transformation(extent = {
        {0, -55}, 
        {-10, -45}})));
    Modelica.Blocks.Sources.Constant FFzero(k = 0) if not withFeedForward annotation (Placement(transformation(extent = {
        {30, -35}, 
        {40, -25}})));
    Modelica.Blocks.Math.Add addFF(k1 = 1, k2 = kFF) annotation (Placement(transformation(extent = {
        {48, -6}, 
        {60, 6}})));
initial equation
    if initType == InitPID.InitialOutput then 
        gainPID.y = y_start;
    end if;
equation
    if initType == InitPID.InitialOutput and (y_start < yMin or yMax < y_start) then 
        Modelica.Utilities.Streams.error("LimPID: Start value y_start (=" + String(y_start) + ") is outside of the limits of yMin (=" + String(yMin) + ") and yMax (=" + String(yMax) + ")");
    end if;
    connect(u_m,addD.u2) annotation (Line(
        points = {
            {0, -120}, 
            {0, -92}, 
            {-92, -92}, 
            {-92, -6}, 
            {-82, -6}},
        color = {0, 0, 127},
        thickness = 0.5));
    connect(u_m,addI.u2) annotation (Line(
        points = {
            {0, -120}, 
            {0, -92}, 
            {-92, -92}, 
            {-92, -50}, 
            {-82, -50}},
        color = {0, 0, 127},
        thickness = 0.5));
    connect(u_m,addP.u2) annotation (Line(
        points = {
            {0, -120}, 
            {0, -92}, 
            {-92, -92}, 
            {-92, 44}, 
            {-82, 44}},
        color = {0, 0, 127},
        thickness = 0.5));
    connect(u_s,addD.u1) annotation (Line(
        points = {
            {-120, 0}, 
            {-96, 0}, 
            {-96, 6}, 
            {-82, 6}},
        color = {0, 0, 127}));
    connect(u_s,addI.u1) annotation (Line(
        points = {
            {-120, 0}, 
            {-96, 0}, 
            {-96, -42}, 
            {-82, -42}},
        color = {0, 0, 127}));
    connect(u_s,addP.u1) annotation (Line(
        points = {
            {-120, 0}, 
            {-96, 0}, 
            {-96, 56}, 
            {-82, 56}},
        color = {0, 0, 127}));
    connect(D.y,addPID.u2) annotation (Line(
        points = {
            {-29, 0}, 
            {-12, 0}},
        color = {0, 0, 127}));
    connect(I.y,addPID.u3) annotation (Line(
        points = {
            {-29, -50}, 
            {-20, -50}, 
            {-20, -8}, 
            {-12, -8}},
        color = {0, 0, 127}));
    connect(P.y,addPID.u1) annotation (Line(
        points = {
            {-29, 50}, 
            {-20, 50}, 
            {-20, 8}, 
            {-12, 8}},
        color = {0, 0, 127}));
    connect(Dzero.y,addPID.u2) annotation (Line(
        points = {
            {-29.5, 25}, 
            {-24, 25}, 
            {-24, 0}, 
            {-12, 0}},
        color = {0, 0, 127}));
    connect(FFzero.y,addFF.u2) annotation (Line(
        points = {
            {40.5, -30}, 
            {44, -30}, 
            {44, -3.6}, 
            {46.8, -3.6}},
        color = {0, 0, 127}));
    connect(Izero.y,addPID.u3) annotation (Line(
        points = {
            {-10.5, -50}, 
            {-20, -50}, 
            {-20, -8}, 
            {-12, -8}},
        color = {0, 0, 127}));
    connect(addD.y,D.u) annotation (Line(
        points = {
            {-59, 0}, 
            {-52, 0}},
        color = {0, 0, 127}));
    connect(addFF.y,addSat.u2) annotation (Line(
        points = {
            {60.6, 0}, 
            {64, 0}, 
            {64, -20}, 
            {74, -20}, 
            {74, -38}},
        color = {0, 0, 127}));
    connect(addFF.y,limiter.u) annotation (Line(
        points = {
            {60.6, 0}, 
            {68, 0}},
        color = {0, 0, 127}));
    connect(addFF.u2,u_ff) annotation (Line(
        points = {
            {46.8, -3.6}, 
            {44, -3.6}, 
            {44, -92}, 
            {60, -92}, 
            {60, -120}},
        color = {0, 0, 127}));
    connect(addI.y,I.u) annotation (Line(
        points = {
            {-59, -50}, 
            {-52, -50}},
        color = {0, 0, 127}));
    connect(addP.y,P.u) annotation (Line(
        points = {
            {-59, 50}, 
            {-52, 50}},
        color = {0, 0, 127}));
    connect(addPID.y,gainPID.u) annotation (Line(
        points = {
            {11, 0}, 
            {18, 0}},
        color = {0, 0, 127}));
    connect(addSat.y,gainTrack.u) annotation (Line(
        points = {
            {80, -61}, 
            {80, -70}, 
            {2, -70}},
        color = {0, 0, 127}));
    connect(gainPID.y,addFF.u1) annotation (Line(
        points = {
            {41, 0}, 
            {44, 0}, 
            {44, 3.6}, 
            {46.8, 3.6}},
        color = {0, 0, 127}));
    connect(gainTrack.y,addI.u3) annotation (Line(
        points = {
            {-21, -70}, 
            {-88, -70}, 
            {-88, -58}, 
            {-82, -58}},
        color = {0, 0, 127}));
    connect(limiter.y,y) annotation (Line(
        points = {
            {91, 0}, 
            {110, 0}},
        color = {0, 0, 127}));
    connect(limiter.y,addSat.u1) annotation (Line(
        points = {
            {91, 0}, 
            {94, 0}, 
            {94, -20}, 
            {86, -20}, 
            {86, -38}},
        color = {0, 0, 127}));

    annotation (
        defaultComponentName = "PID",
        Icon(
            coordinateSystem(
                preserveAspectRatio = true,
                extent = {
                    {-100, -100}, 
                    {100, 100}}),
            graphics = {
                Line(
                    points = {
                        {-80, 78}, 
                        {-80, -90}},
                    color = {192, 192, 192}), 
                Polygon(
                    points = {
                        {-80, 90}, 
                        {-88, 68}, 
                        {-72, 68}, 
                        {-80, 90}},
                    lineColor = {192, 192, 192},
                    fillColor = {192, 192, 192},
                    fillPattern = FillPattern.Solid), 
                Line(
                    points = {
                        {-90, -80}, 
                        {82, -80}},
                    color = {192, 192, 192}), 
                Polygon(
                    points = {
                        {90, -80}, 
                        {68, -72}, 
                        {68, -88}, 
                        {90, -80}},
                    lineColor = {192, 192, 192},
                    fillColor = {192, 192, 192},
                    fillPattern = FillPattern.Solid), 
                Line(
                    points = {
                        {-80, -80}, 
                        {-80, -20}, 
                        {30, 60}, 
                        {80, 60}},
                    color = {0, 0, 127}), 
                Text(
                    extent = {
                        {-20, -20}, 
                        {80, -60}},
                    lineColor = {192, 192, 192},
                    textString = "%controllerType"), 
                Line(
                    visible = strict,
                    points = {
                        {30, 60}, 
                        {81, 60}},
                    color = {255, 0, 0})}),
        Diagram(graphics = {
            Text(
                extent = {
                    {79, -112}, 
                    {129, -102}},
                lineColor = {0, 0, 255},
                textString = " (feed-forward)")}),
        Documentation(info = "<html>\n<p>\nVia parameter <strong>controllerType</strong> either <strong>P</strong>, <strong>PI</strong>, <strong>PD</strong>,\nor <strong>PID</strong> can be selected. If, e.g., PI is selected, all components belonging to the\nD-part are removed from the block (via conditional declarations).\nThe example model\n<a href=\"modelica://Modelica.Blocks.Examples.PID_Controller\">Modelica.Blocks.Examples.PID_Controller</a>\ndemonstrates the usage of this controller.\nSeveral practical aspects of PID controller design are incorporated\naccording to chapter 3 of the book:\n</p>\n\n<dl>\n<dt>&Aring;str&ouml;m K.J., and H&auml;gglund T.:</dt>\n<dd> <strong>PID Controllers: Theory, Design, and Tuning</strong>.\n     Instrument Society of America, 2nd edition, 1995.\n</dd>\n</dl>\n\n<p>\nBesides the additive <strong>proportional, integral</strong> and <strong>derivative</strong>\npart of this controller, the following features are present:\n</p>\n<ul>\n<li> The output of this controller is limited. If the controller is\n     in its limits, anti-windup compensation is activated to drive\n     the integrator state to zero.</li>\n<li> The high-frequency gain of the derivative part is limited\n     to avoid excessive amplification of measurement noise.</li>\n<li> Setpoint weighting is present, which allows to weight\n     the setpoint in the proportional and the derivative part\n     independently from the measurement. The controller will respond\n     to load disturbances and measurement noise independently of this setting\n     (parameters wp, wd). However, setpoint changes will depend on this\n     setting. For example, it is useful to set the setpoint weight wd\n     for the derivative part to zero, if steps may occur in the\n     setpoint signal.</li>\n<li> Optional feed-forward. It is possible to add a feed-forward signal.\n     The feed-forward signal is added before limitation.</li>\n</ul>\n\n<p>\nThe parameters of the controller can be manually adjusted by performing\nsimulations of the closed loop system (= controller + plant connected\ntogether) and using the following strategy:\n</p>\n\n<ol>\n<li> Set very large limits, e.g., yMax = Modelica.Constants.inf</li>\n<li> Select a <strong>P</strong>-controller and manually enlarge parameter <strong>k</strong>\n     (the total gain of the controller) until the closed-loop response\n     cannot be improved any more.</li>\n<li> Select a <strong>PI</strong>-controller and manually adjust parameters\n     <strong>k</strong> and <strong>Ti</strong> (the time constant of the integrator).\n     The first value of Ti can be selected, such that it is in the\n     order of the time constant of the oscillations occurring with\n     the P-controller. If, e.g., vibrations in the order of T=10 ms\n     occur in the previous step, start with Ti=0.01 s.</li>\n<li> If you want to make the reaction of the control loop faster\n     (but probably less robust against disturbances and measurement noise)\n     select a <strong>PID</strong>-Controller and manually adjust parameters\n     <strong>k</strong>, <strong>Ti</strong>, <strong>Td</strong> (time constant of derivative block).</li>\n<li> Set the limits yMax and yMin according to your specification.</li>\n<li> Perform simulations such that the output of the PID controller\n     goes in its limits. Tune <strong>Ni</strong> (Ni*Ti is the time constant of\n     the anti-windup compensation) such that the input to the limiter\n     block (= limiter.u) goes quickly enough back to its limits.\n     If Ni is decreased, this happens faster. If Ni=infinity, the\n     anti-windup compensation is switched off and the controller works bad.</li>\n</ol>\n\n<p>\n<strong>Initialization</strong>\n</p>\n\n<p>\nThis block can be initialized in different\nways controlled by parameter <strong>initType</strong>. The possible\nvalues of initType are defined in\n<a href=\"modelica://Modelica.Blocks.Types.InitPID\">Modelica.Blocks.Types.InitPID</a>.\nThis type is identical to\n<a href=\"modelica://Modelica.Blocks.Types.Init\">Types.Init</a>,\nwith the only exception that the additional option\n<strong>DoNotUse_InitialIntegratorState</strong> is added for\nbackward compatibility reasons (= integrator is initialized with\nInitialState whereas differential part is initialized with\nNoInit which was the initialization in version 2.2 of the Modelica\nstandard library).\n</p>\n\n<p>\nBased on the setting of initType, the integrator (I) and derivative (D)\nblocks inside the PID controller are initialized according to the following table:\n</p>\n\n<table border=1 cellspacing=0 cellpadding=2>\n  <tr><td><strong>initType</strong></td>\n      <td><strong>I.initType</strong></td>\n      <td><strong>D.initType</strong></td></tr>\n\n  <tr><td><strong>NoInit</strong></td>\n      <td>NoInit</td>\n      <td>NoInit</td></tr>\n\n  <tr><td><strong>SteadyState</strong></td>\n      <td>SteadyState</td>\n      <td>SteadyState</td></tr>\n\n  <tr><td><strong>InitialState</strong></td>\n      <td>InitialState</td>\n      <td>InitialState</td></tr>\n\n  <tr><td><strong>InitialOutput</strong><br>\n          and initial equation: y = y_start</td>\n      <td>NoInit</td>\n      <td>SteadyState</td></tr>\n\n  <tr><td><strong>DoNotUse_InitialIntegratorState</strong></td>\n      <td>InitialState</td>\n      <td>NoInit</td></tr>\n</table>\n\n<p>\nIn many cases, the most useful initial condition is\n<strong>SteadyState</strong> because initial transients are then no longer\npresent. If initType = InitPID.SteadyState, then in some\ncases difficulties might occur. The reason is the\nequation of the integrator:\n</p>\n\n<pre>\n   <strong>der</strong>(y) = k*u;\n</pre>\n\n<p>\nThe steady state equation \"der(x)=0\" leads to the condition that the input u to the\nintegrator is zero. If the input u is already (directly or indirectly) defined\nby another initial condition, then the initialization problem is <strong>singular</strong>\n(has none or infinitely many solutions). This situation occurs often\nfor mechanical systems, where, e.g., u = desiredSpeed - measuredSpeed and\nsince speed is both a state and a derivative, it is natural to\ninitialize it with zero. As sketched this is, however, not possible.\nThe solution is to not initialize u_m or the variable that is used\nto compute u_m by an algebraic equation.\n</p>\n\n<p>\nWhen initializing in steady-state, homotopy-based initialization can help the convergence of the solver,\nby using a simplified model a the beginning of the solution process. Different options are available.\n</p>\n\n<ul>\n<li><strong>homotopyType=Linear</strong> (default): the limitations are removed from the simplified model,\nmaking it linear. Use this if you know that the controller will not be saturated at steady state.</li>\n<li><strong>homotopyType=UpperLimit</strong>: if it is known a priori the controller will be stuck at the upper\nlimit yMax, this option assumes y = yMax as a simplified model.</li>\n<li><strong>homotopyType=LowerLimit</strong>: if it is known a priori the controller will be stuck at the lower\nlimit yMin, this option assumes y = yMin as a simplified model.</li>\n<li><strong>homotopyType=NoHomotopy</strong>: this option does not apply any simplification and keeps the\nlimiter active throughout the homotopy transformation. Use this if it is unknown whether the controller\nis saturated or not at initialization and if the limitations on the output must be enforced throughout\nthe entire homotopy transformation.</li>\n</ul>\n\n<p>\nThe parameter <strong>limitAtInit</strong> is obsolete since MSL 3.2.2 and only kept for backwards compatibility.\n</p>\n</html>"));
end LimPID;