Skip to content
LimPID.md

LimPID

PID controller with limited output, back calculation anti-windup compensation, setpoint weighting and feed-forward

The transfer function is:

y=k[e+1Tis(e+ysatyNiTi)+Tds1+TdNdse]+kffuff

SingleVariableController

Usage

LimPID(k=1, Ti=0.5, Td=0.1, y_max=1e300, y_min=-y_max, wp=1, wd=0, Ni=0.9, Nd=10, k_ff=1, xi0=0, xd0=0)

Parameters:

NameDescriptionUnitsDefault value
kGain of controller1
TiTime constant of the integrator blocks0.5
TdTime constant of the derivative blocks0.1
y_maxMaximum output1e+300
y_minMinimum output-y_max
wpSet-point weight for proportional block1
wdSet-point weight for derivative block0
NiNi*Ti is time constant of anti-windup compensation0.9
NdMaximum derivative gain. Higher the value of Nd, the more ideal the derivative block gets (less filtering, higher high-frequency gain).10
k_ffGain of the feed-forward input1
xi0Initial guess value for integrator output0
xd0Initial guess value for derivative output0

Connectors

  • u_s - This connector represents a real signal as an input to a component (RealInput)

  • u_m - This connector represents a real signal as an input to a component (RealInput)

  • y - This connector represents a real signal as an output from a component (RealOutput)

  • u_ff - This connector represents a real signal as an input to a component (RealInput)

Variables

NameDescriptionUnits
control_error

Behavior

julia
using BlockComponents #hide
using ModelingToolkit #hide
@variables k #hide
@variables Ti #hide
@variables Td #hide
@variables y_max #hide
@variables y_min #hide
@variables wp #hide
@variables wd #hide
@variables Ni #hide
@variables Nd #hide
@variables k_ff #hide
@variables xi0 #hide
@variables xd0 #hide
@named sys = LimPID(k=k, Ti=Ti, Td=Td, y_max=y_max, y_min=y_min, wp=wp, wd=wd, Ni=Ni, Nd=Nd, k_ff=k_ff, xi0=xi0, xd0=xd0) #hide
full_equations(sys) #hide
<< @example-block not executed in draft mode >>

Source

dyad
# PID controller with limited output, back calculation anti-windup compensation, setpoint weighting and feed-forward
#
# The transfer function is:
#
# ```math
# y = k \left[e + \dfrac{1}{T_is}\left(e + \dfrac{y_{sat} - y}{N_iT_i}\right)  + \dfrac{T_ds}{1 + {\dfrac{T_d}{N_d}s}}e \right] + k_{ff}u_{ff}
# ```
component LimPID
  extends SingleVariableController
  u_ff = RealInput() [{
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "input", "x1": 850, "y1": 950, "x2": 950, "y2": 1050, "rot": -90},
        "icon": {"iconName": "input", "x1": 650, "y1": 950, "x2": 750, "y2": 1050, "rot": -90}
      }
    }
  }]
  add_p = Add(k1=wp, k2=-1) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 100, "y1": 100, "x2": 200, "y2": 200}}
    }
  }]
  add_d = Add(k2=-1) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 100, "y1": 300, "x2": 200, "y2": 400}}
    }
  }]
  add_i = Add3(k2=-1) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 100, "y1": 500, "x2": 200, "y2": 600}}
    }
  }]
  proportional = Gain(k=1) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 100, "x2": 400, "y2": 200}}
    }
  }]
  derivative = Derivative(k=Td, T=max(Td/Nd, 1e-14), x0=xd0) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 300, "x2": 400, "y2": 400}}
    }
  }]
  integrator = Integrator(k=1/Ti, x0=xi0) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 500, "x2": 400, "y2": 600}}
    }
  }]
  add_pid = Add3() [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 500, "y1": 300, "x2": 600, "y2": 400}}
    }
  }]
  gain_pid = Gain(k=k) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 700, "y1": 300, "x2": 800, "y2": 400}}
    }
  }]
  add_ff = Add() [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 900, "y1": 300, "x2": 1000, "y2": 400}}
    }
  }]
  limiter = Limiter(y_max=y_max, y_min=y_min) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 1100, "y1": 300, "x2": 1200, "y2": 400}}
    }
  }]
  add_sat = Add(k1=1, k2=-1) [{
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 1100, "y1": 500, "x2": 1200, "y2": 600, "rot": 90}
      }
    }
  }]
  gain_track = Gain(k=1/(k*Ni)) [{
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 500, "y1": 700, "x2": 600, "y2": 800, "rot": 180}
      }
    }
  }]
  variable control_error::Real
  # Gain of controller
  parameter k::Real = 1
  # Time constant of the integrator block
  parameter Ti::Time = 0.5
  # Time constant of the derivative block
  parameter Td::Time = 0.1
  # Maximum output
  parameter y_max::Real = 1e300
  # Minimum output
  parameter y_min::Real = -y_max
  # Set-point weight for proportional block
  parameter wp::Real = 1
  # Set-point weight for derivative block
  parameter wd::Real = 0
  # `Ni*Ti` is time constant of anti-windup compensation
  parameter Ni::Real = 0.9
  # Maximum derivative gain. Higher the value of `Nd`, the more ideal the derivative block gets (less filtering, higher high-frequency gain).
  parameter Nd::Real = 10
  # Gain of the feed-forward input
  parameter k_ff::Real = 1
  # Initial guess value for integrator output
  parameter xi0::Real = 0
  # Initial guess value for derivative output
  parameter xd0::Real = 0
relations
  u_s = add_p.u1
  u_s = add_i.u1
  u_s = add_d.u1
  u_m = add_p.u2
  u_m = add_i.u2
  u_m = add_d.u2
  connect(add_p.y, proportional.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(add_d.y, derivative.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(add_i.y, integrator.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(proportional.y, add_pid.u1) [{
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 450, "y": 150}, {"x": 450, "y": 320}], "E": 2}]}
  }]
  connect(derivative.y, add_pid.u2) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(integrator.y, add_pid.u3) [{
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 450, "y": 550}, {"x": 450, "y": 380}], "E": 2}]}
  }]
  connect(add_pid.y, gain_pid.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(gain_pid.y, add_ff.u1) [{
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 825, "y": 350}, {"x": 825, "y": 320}], "E": 2}]}
  }]
  u_ff = add_ff.u2
  connect(add_ff.y, add_sat.u2) [{
    "Dyad": {
      "edges": [{"S": 2, "M": [{"x": 1120, "y": 480}, {"x": 1050, "y": 480}], "E": -1}],
      "junctions": [{"x": 1050, "y": 350}]
    }
  }]
  connect(add_ff.y, limiter.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(limiter.y, add_sat.u1) [{
    "Dyad": {
      "edges": [
        {"S": 1, "E": -1},
        {"S": 2, "M": [{"x": 1180, "y": 480}, {"x": 1250, "y": 480}], "E": -1}
      ],
      "junctions": [{"x": 1250, "y": 350}]
    }
  }]
  connect(add_sat.y, gain_track.u) [{"Dyad": {"edges": [{"S": 1, "M": [{"x": 1150, "y": 750}], "E": 2}]}}]
  connect(gain_track.y, add_i.u3) [{
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 75, "y": 750}, {"x": 75, "y": 580}], "E": 2}]}
  }]
  y = limiter.y
metadata {"Dyad": {"icons": {"default": "dyad://BlockComponents/LimPID.svg"}}}
end
Flattened Source
dyad
# PID controller with limited output, back calculation anti-windup compensation, setpoint weighting and feed-forward
#
# The transfer function is:
#
# ```math
# y = k \left[e + \dfrac{1}{T_is}\left(e + \dfrac{y_{sat} - y}{N_iT_i}\right)  + \dfrac{T_ds}{1 + {\dfrac{T_d}{N_d}s}}e \right] + k_{ff}u_{ff}
# ```
component LimPID
  # Connector of setpoint input signal
  u_s = RealInput() [{
    "Dyad": {
      "placement": {
        "diagram": {"x1": -50, "y1": 240, "x2": 50, "y2": 340, "sw": 0.1, "sh": 0.1, "rot": 0},
        "icon": {"x1": -50, "y1": 450, "x2": 50, "y2": 550}
      }
    }
  }]
  # Connector of measurement input signal
  u_m = RealInput() [{
    "Dyad": {
      "placement": {
        "diagram": {"x1": -50, "y1": 660, "x2": 50, "y2": 760, "rot": 0, "sw": 0.1, "sh": 0.1},
        "icon": {"x1": 250, "y1": 950, "x2": 350, "y2": 1050, "rot": -90}
      }
    }
  }]
  # Connector of actuator output signal
  y = RealOutput() [{
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "output", "x1": 1300, "y1": 300, "x2": 1400, "y2": 400},
        "icon": {"iconName": "output", "x1": 950, "y1": 450, "x2": 1050, "y2": 550}
      }
    }
  }]
  u_ff = RealInput() [{
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "input", "x1": 850, "y1": 950, "x2": 950, "y2": 1050, "rot": -90},
        "icon": {"iconName": "input", "x1": 650, "y1": 950, "x2": 750, "y2": 1050, "rot": -90}
      }
    }
  }]
  add_p = Add(k1=wp, k2=-1) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 100, "y1": 100, "x2": 200, "y2": 200}}
    }
  }]
  add_d = Add(k2=-1) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 100, "y1": 300, "x2": 200, "y2": 400}}
    }
  }]
  add_i = Add3(k2=-1) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 100, "y1": 500, "x2": 200, "y2": 600}}
    }
  }]
  proportional = Gain(k=1) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 100, "x2": 400, "y2": 200}}
    }
  }]
  derivative = Derivative(k=Td, T=max(Td/Nd, 1e-14), x0=xd0) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 300, "x2": 400, "y2": 400}}
    }
  }]
  integrator = Integrator(k=1/Ti, x0=xi0) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 300, "y1": 500, "x2": 400, "y2": 600}}
    }
  }]
  add_pid = Add3() [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 500, "y1": 300, "x2": 600, "y2": 400}}
    }
  }]
  gain_pid = Gain(k=k) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 700, "y1": 300, "x2": 800, "y2": 400}}
    }
  }]
  add_ff = Add() [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 900, "y1": 300, "x2": 1000, "y2": 400}}
    }
  }]
  limiter = Limiter(y_max=y_max, y_min=y_min) [{
    "Dyad": {
      "placement": {"icon": {"iconName": "input", "x1": 1100, "y1": 300, "x2": 1200, "y2": 400}}
    }
  }]
  add_sat = Add(k1=1, k2=-1) [{
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 1100, "y1": 500, "x2": 1200, "y2": 600, "rot": 90}
      }
    }
  }]
  gain_track = Gain(k=1/(k*Ni)) [{
    "Dyad": {
      "placement": {
        "icon": {"iconName": "input", "x1": 500, "y1": 700, "x2": 600, "y2": 800, "rot": 180}
      }
    }
  }]
  variable control_error::Real
  # Gain of controller
  parameter k::Real = 1
  # Time constant of the integrator block
  parameter Ti::Time = 0.5
  # Time constant of the derivative block
  parameter Td::Time = 0.1
  # Maximum output
  parameter y_max::Real = 1e300
  # Minimum output
  parameter y_min::Real = -y_max
  # Set-point weight for proportional block
  parameter wp::Real = 1
  # Set-point weight for derivative block
  parameter wd::Real = 0
  # `Ni*Ti` is time constant of anti-windup compensation
  parameter Ni::Real = 0.9
  # Maximum derivative gain. Higher the value of `Nd`, the more ideal the derivative block gets (less filtering, higher high-frequency gain).
  parameter Nd::Real = 10
  # Gain of the feed-forward input
  parameter k_ff::Real = 1
  # Initial guess value for integrator output
  parameter xi0::Real = 0
  # Initial guess value for derivative output
  parameter xd0::Real = 0
relations
  u_s = add_p.u1
  u_s = add_i.u1
  u_s = add_d.u1
  u_m = add_p.u2
  u_m = add_i.u2
  u_m = add_d.u2
  connect(add_p.y, proportional.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(add_d.y, derivative.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(add_i.y, integrator.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(proportional.y, add_pid.u1) [{
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 450, "y": 150}, {"x": 450, "y": 320}], "E": 2}]}
  }]
  connect(derivative.y, add_pid.u2) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(integrator.y, add_pid.u3) [{
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 450, "y": 550}, {"x": 450, "y": 380}], "E": 2}]}
  }]
  connect(add_pid.y, gain_pid.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(gain_pid.y, add_ff.u1) [{
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 825, "y": 350}, {"x": 825, "y": 320}], "E": 2}]}
  }]
  u_ff = add_ff.u2
  connect(add_ff.y, add_sat.u2) [{
    "Dyad": {
      "edges": [{"S": 2, "M": [{"x": 1120, "y": 480}, {"x": 1050, "y": 480}], "E": -1}],
      "junctions": [{"x": 1050, "y": 350}]
    }
  }]
  connect(add_ff.y, limiter.u) [{"Dyad": {"edges": [{"S": 1, "E": 2}]}}]
  connect(limiter.y, add_sat.u1) [{
    "Dyad": {
      "edges": [
        {"S": 1, "E": -1},
        {"S": 2, "M": [{"x": 1180, "y": 480}, {"x": 1250, "y": 480}], "E": -1}
      ],
      "junctions": [{"x": 1250, "y": 350}]
    }
  }]
  connect(add_sat.y, gain_track.u) [{"Dyad": {"edges": [{"S": 1, "M": [{"x": 1150, "y": 750}], "E": 2}]}}]
  connect(gain_track.y, add_i.u3) [{
    "Dyad": {"edges": [{"S": 1, "M": [{"x": 75, "y": 750}, {"x": 75, "y": 580}], "E": 2}]}
  }]
  y = limiter.y
metadata {"Dyad": {"icons": {"default": "dyad://BlockComponents/LimPID.svg"}}}
end


Test Cases

No test cases defined.