Skip to content
LIBRARY
PlanarMechanics.examples.dyad_balans.DyadBalans2D.md

PlanarMechanics.examples.dyad_balans.DyadBalans2D

This example models a wheeled balancing robot with a cascade control system stabilizing the angle in the inner loop and the position in the outer loop.

See also DyadBalans2DFF for a version with a feedforward generator.

Usage

MultibodyComponents.PlanarMechanics.examples.dyad_balans.DyadBalans2D(M=0.1, m=0.05, R=0.04, L=0.08, Ib=2.5e-4, Iw=1e-4, d=0.0001)

Parameters:

NameDescriptionUnitsDefault value
MBody masskg0.1
mWheel masskg0.05
RWheel radiusm0.04
LDistance from wheel axis to body center of massm0.08
IbBody moment of inertiakg.m20.00025
IwWheel + motor moment of inertiakg.m20.0001
dMotor viscous frictionN.m.s/rad0.0001

Behavior

julia
using MultibodyComponents #hide
using ModelingToolkit #hide
@variables M #hide
@variables m #hide
@variables R #hide
@variables L #hide
@variables Ib #hide
@variables Iw #hide
@variables d #hide
@named sys = MultibodyComponents.PlanarMechanics.examples.dyad_balans.DyadBalans2D(M=M, m=m, R=R, L=L, Ib=Ib, Iw=Iw, d=d) #hide
full_equations(sys) #hide
<< @example-block not executed in draft mode >>

Source

dyad
"""
This example models a wheeled balancing robot with a cascade control system stabilizing the angle in the inner loop and the position in the outer loop.

See also DyadBalans2DFF for a version with a feedforward generator.
"""
example component DyadBalans2D
  world = MultibodyComponents.PlanarMechanics.World(g = 9.82) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 110, "y1": 420, "x2": 10, "y2": 320, "rot": 180}
      },
      "tags": []
    }
  }
  firstorder = BlockComponents.Continuous.FirstOrder(T = 0.1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -570, "y1": 600, "x2": -470, "y2": 700, "rot": 0}
      },
      "tags": []
    }
  }
  firstorder1 = BlockComponents.Continuous.FirstOrder(T = 0.1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -430, "y1": 600, "x2": -330, "y2": 700, "rot": 0}
      },
      "tags": []
    }
  }
  wheelJoint = MultibodyComponents.PlanarMechanics.OneDOFRollingWheelJoint(radius = R) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 700, "x2": 460, "y2": 960, "rot": 0}
      },
      "tags": []
    }
  }
  constant2 = BlockComponents.Sources.Constant(k = 0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 50, "y1": 500, "x2": 110, "y2": 560, "rot": 90}
      },
      "tags": []
    }
  }
  IMU = MultibodyComponents.PlanarMechanics.AbsolutePosition(resolve_in_frame = MultibodyComponents.ResolveInFrame.World()) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 460, "y1": 560, "x2": 560, "y2": 660, "rot": 0}
      },
      "tags": []
    }
  }
  square = BlockComponents.Sources.Square(amplitude = 0.05, frequency = 1 / 10) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -720, "y1": 600, "x2": -620, "y2": 700, "rot": 0}
      },
      "tags": []
    }
  }
  wheelinertia = MultibodyComponents.PlanarMechanics.Body(I = Iw, radius = 0.01, m = m) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 390, "y1": 680, "x2": 530, "y2": 820, "rot": 0}
      },
      "tags": []
    }
  }
  simplemotor = MultibodyComponents.PlanarMechanics.examples.dyad_balans.SimpleMotor(d = d) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 280, "y1": 620, "x2": 380, "y2": 720, "rot": 90}
      },
      "tags": []
    }
  }
  constant1 = BlockComponents.Sources.Constant(k = 0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -240, "y1": 500, "x2": -180, "y2": 560, "rot": 90}
      },
      "tags": []
    }
  }
  angle_controller = BlockComponents.Continuous.LimPID(k = 0.487401, Ti = 0.0587352, Td = 0.0420526, Nd = 119.368, y_max = 0.1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 30, "y1": 620, "x2": 130, "y2": 720, "rot": 0}
      },
      "tags": []
    }
  }
  pos_controller = BlockComponents.Continuous.LimPID(k = 0.0666576, Ti = 5.25024, Td = 4.81393, Nd = 4.76616, wd = 1, wp = 1, y_max = deg2rad(25.0)) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -260, "y1": 620, "x2": -160, "y2": 720, "rot": 0}
      },
      "tags": []
    }
  }
  gain = BlockComponents.Math.Gain(k = -1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 170, "y1": 630, "x2": 250, "y2": 710, "rot": 0}
      },
      "tags": []
    }
  }
  gain1 = BlockComponents.Math.Gain(k = -1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -120, "y1": 630, "x2": -40, "y2": 710, "rot": 0}
      },
      "tags": []
    }
  }
  body_mass = MultibodyComponents.PlanarMechanics.BodyShape(color = [0.2, 0.2, 0.2, 0.9], r = [0, 2 * L], r_cm = [0, L], m = M, I = Ib, radius = 0.03) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 180, "y1": 250, "x2": 480, "y2": 550, "rot": 270}
      },
      "tags": []
    }
  }
  "Body mass"
  parameter M::Mass = 0.1
  "Wheel mass"
  parameter m::Mass = 0.05
  "Wheel radius"
  parameter R::Length = 0.04
  "Distance from wheel axis to body center of mass"
  parameter L::Length = 0.08
  "Body moment of inertia"
  parameter Ib::Inertia = 2.5e-4
  "Wheel + motor moment of inertia"
  parameter Iw::Inertia = 1e-4
  "Motor viscous friction"
  parameter d::RotationalDampingConstant = 0.0001
relations
  initial body_mass.body.phi = 0.1
  initial body_mass.body.w = 0.0
  initial wheelinertia.phi = 0.0
  initial wheelinertia.w = 0.0
  #initial wheelJoint.x = 0.0
  #initial wheelJoint.v = 0.0
  initial firstorder.x = 0.0
  initial firstorder1.x = 0.0
  connect(square.y, firstorder.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(firstorder.y, firstorder1.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(wheelinertia.frame_a, wheelJoint.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 330, "y": 750}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(simplemotor.frame_b, wheelJoint.frame_a) {"Dyad": {"renderStyle": "standard", "edges": [{"S": 1, "E": 2, "M": []}]}}
  connect(simplemotor.frame_a, IMU.frame_a, body_mass.frame_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [{"x": 330, "y": 610}], "E": 2},
        {"S": 3, "M": [], "E": -1}
      ],
      "junctions": [{"x": 330, "y": 610}],
      "renderStyle": "standard"
    }
  }
  y: analysis_point(IMU.phi, angle_controller.u_m)
  y2: analysis_point(IMU.x, pos_controller.u_m)
  u: analysis_point(angle_controller.y, gain.u)
  u2: analysis_point(pos_controller.y, gain1.u)
  connect(IMU.phi, angle_controller.u_m) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 660, "y": 641},
            {"x": 660, "y": 1040},
            {"x": 0, "y": 1040},
            {"x": 0, "y": 693}
          ],
          "E": 2
        }
      ],
      "renderStyle": "standard"
    }
  }
  connect(IMU.x, pos_controller.u_m) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 710, "y": 580},
            {"x": 710, "y": 1090},
            {"x": -300, "y": 1090},
            {"x": -300, "y": 693}
          ],
          "E": 2
        }
      ],
      "renderStyle": "standard"
    }
  }
  connect(pos_controller.y, gain1.u) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": -134.25, "y": 671}, {"x": -134.25, "y": 670}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(gain1.y, angle_controller.u_s) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": -10, "y": 670}, {"x": -10, "y": 647}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(firstorder1.y, pos_controller.u_s) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": -295, "y": 650}, {"x": -295, "y": 647}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(gain.y, simplemotor.torqueinput) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(angle_controller.y, gain.u) {
    "Dyad": {
      "renderStyle": "standard",
      "edges": [{"S": 1, "M": [{"x": 150.5, "y": 671}, {"x": 150.5, "y": 670}], "E": 2}]
    }
  }
  connect(constant1.y, pos_controller.u_ff) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(constant2.y, angle_controller.u_ff) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
metadata {"Dyad": {"tests": {"case1": {"stop": 50}}}}
end
Flattened Source
dyad
"""
This example models a wheeled balancing robot with a cascade control system stabilizing the angle in the inner loop and the position in the outer loop.

See also DyadBalans2DFF for a version with a feedforward generator.
"""
example component DyadBalans2D
  world = MultibodyComponents.PlanarMechanics.World(g = 9.82) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 110, "y1": 420, "x2": 10, "y2": 320, "rot": 180}
      },
      "tags": []
    }
  }
  firstorder = BlockComponents.Continuous.FirstOrder(T = 0.1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -570, "y1": 600, "x2": -470, "y2": 700, "rot": 0}
      },
      "tags": []
    }
  }
  firstorder1 = BlockComponents.Continuous.FirstOrder(T = 0.1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -430, "y1": 600, "x2": -330, "y2": 700, "rot": 0}
      },
      "tags": []
    }
  }
  wheelJoint = MultibodyComponents.PlanarMechanics.OneDOFRollingWheelJoint(radius = R) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 200, "y1": 700, "x2": 460, "y2": 960, "rot": 0}
      },
      "tags": []
    }
  }
  constant2 = BlockComponents.Sources.Constant(k = 0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 50, "y1": 500, "x2": 110, "y2": 560, "rot": 90}
      },
      "tags": []
    }
  }
  IMU = MultibodyComponents.PlanarMechanics.AbsolutePosition(resolve_in_frame = MultibodyComponents.ResolveInFrame.World()) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 460, "y1": 560, "x2": 560, "y2": 660, "rot": 0}
      },
      "tags": []
    }
  }
  square = BlockComponents.Sources.Square(amplitude = 0.05, frequency = 1 / 10) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -720, "y1": 600, "x2": -620, "y2": 700, "rot": 0}
      },
      "tags": []
    }
  }
  wheelinertia = MultibodyComponents.PlanarMechanics.Body(I = Iw, radius = 0.01, m = m) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 390, "y1": 680, "x2": 530, "y2": 820, "rot": 0}
      },
      "tags": []
    }
  }
  simplemotor = MultibodyComponents.PlanarMechanics.examples.dyad_balans.SimpleMotor(d = d) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 280, "y1": 620, "x2": 380, "y2": 720, "rot": 90}
      },
      "tags": []
    }
  }
  constant1 = BlockComponents.Sources.Constant(k = 0) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -240, "y1": 500, "x2": -180, "y2": 560, "rot": 90}
      },
      "tags": []
    }
  }
  angle_controller = BlockComponents.Continuous.LimPID(k = 0.487401, Ti = 0.0587352, Td = 0.0420526, Nd = 119.368, y_max = 0.1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 30, "y1": 620, "x2": 130, "y2": 720, "rot": 0}
      },
      "tags": []
    }
  }
  pos_controller = BlockComponents.Continuous.LimPID(k = 0.0666576, Ti = 5.25024, Td = 4.81393, Nd = 4.76616, wd = 1, wp = 1, y_max = deg2rad(25.0)) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -260, "y1": 620, "x2": -160, "y2": 720, "rot": 0}
      },
      "tags": []
    }
  }
  gain = BlockComponents.Math.Gain(k = -1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 170, "y1": 630, "x2": 250, "y2": 710, "rot": 0}
      },
      "tags": []
    }
  }
  gain1 = BlockComponents.Math.Gain(k = -1) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -120, "y1": 630, "x2": -40, "y2": 710, "rot": 0}
      },
      "tags": []
    }
  }
  body_mass = MultibodyComponents.PlanarMechanics.BodyShape(color = [0.2, 0.2, 0.2, 0.9], r = [0, 2 * L], r_cm = [0, L], m = M, I = Ib, radius = 0.03) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 180, "y1": 250, "x2": 480, "y2": 550, "rot": 270}
      },
      "tags": []
    }
  }
  "Body mass"
  parameter M::Mass = 0.1
  "Wheel mass"
  parameter m::Mass = 0.05
  "Wheel radius"
  parameter R::Length = 0.04
  "Distance from wheel axis to body center of mass"
  parameter L::Length = 0.08
  "Body moment of inertia"
  parameter Ib::Inertia = 2.5e-4
  "Wheel + motor moment of inertia"
  parameter Iw::Inertia = 1e-4
  "Motor viscous friction"
  parameter d::RotationalDampingConstant = 0.0001
relations
  initial body_mass.body.phi = 0.1
  initial body_mass.body.w = 0.0
  initial wheelinertia.phi = 0.0
  initial wheelinertia.w = 0.0
  #initial wheelJoint.x = 0.0
  #initial wheelJoint.v = 0.0
  initial firstorder.x = 0.0
  initial firstorder1.x = 0.0
  connect(square.y, firstorder.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(firstorder.y, firstorder1.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(wheelinertia.frame_a, wheelJoint.frame_a) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": 330, "y": 750}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(simplemotor.frame_b, wheelJoint.frame_a) {"Dyad": {"renderStyle": "standard", "edges": [{"S": 1, "E": 2, "M": []}]}}
  connect(simplemotor.frame_a, IMU.frame_a, body_mass.frame_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [{"x": 330, "y": 610}], "E": 2},
        {"S": 3, "M": [], "E": -1}
      ],
      "junctions": [{"x": 330, "y": 610}],
      "renderStyle": "standard"
    }
  }
  y: analysis_point(IMU.phi, angle_controller.u_m)
  y2: analysis_point(IMU.x, pos_controller.u_m)
  u: analysis_point(angle_controller.y, gain.u)
  u2: analysis_point(pos_controller.y, gain1.u)
  connect(IMU.phi, angle_controller.u_m) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 660, "y": 641},
            {"x": 660, "y": 1040},
            {"x": 0, "y": 1040},
            {"x": 0, "y": 693}
          ],
          "E": 2
        }
      ],
      "renderStyle": "standard"
    }
  }
  connect(IMU.x, pos_controller.u_m) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 710, "y": 580},
            {"x": 710, "y": 1090},
            {"x": -300, "y": 1090},
            {"x": -300, "y": 693}
          ],
          "E": 2
        }
      ],
      "renderStyle": "standard"
    }
  }
  connect(pos_controller.y, gain1.u) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": -134.25, "y": 671}, {"x": -134.25, "y": 670}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(gain1.y, angle_controller.u_s) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": -10, "y": 670}, {"x": -10, "y": 647}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(firstorder1.y, pos_controller.u_s) {
    "Dyad": {
      "edges": [{"S": 1, "M": [{"x": -295, "y": 650}, {"x": -295, "y": 647}], "E": 2}],
      "renderStyle": "standard"
    }
  }
  connect(gain.y, simplemotor.torqueinput) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(angle_controller.y, gain.u) {
    "Dyad": {
      "renderStyle": "standard",
      "edges": [{"S": 1, "M": [{"x": 150.5, "y": 671}, {"x": 150.5, "y": 670}], "E": 2}]
    }
  }
  connect(constant1.y, pos_controller.u_ff) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(constant2.y, angle_controller.u_ff) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
metadata {"Dyad": {"tests": {"case1": {"stop": 50}}}}
end


Test Cases

julia
using MultibodyComponents
using DyadInterface: TransientAnalysis, rebuild_sol, ODEAlg
using ModelingToolkit: toggle_namespacing, get_initial_conditions, @named
using CSV, DataFrames, Plots

snapshotsdir = joinpath(dirname(dirname(pathof(MultibodyComponents))), "test", "snapshots")
<< @setup-block not executed in draft mode >>

Test Case case1

julia
@named model_case1 = MultibodyComponents.PlanarMechanics.examples.dyad_balans.DyadBalans2D()
model_case1 = toggle_namespacing(model_case1, false)

model_case1 = toggle_namespacing(model_case1, true)
result_case1 = TransientAnalysis(; model = model_case1, alg = ODEAlg.Auto(), start = 0e+0, stop = 5e+1, abstol=1e-6, reltol=1e-6)
sol_case1 = rebuild_sol(result_case1)
<< @setup-block not executed in draft mode >>
  • Examples

  • Experiments

  • Analyses