Skip to content
LIBRARY
examples.suspension.QuarterCarSuspension.md

examples.suspension.QuarterCarSuspension

Double-wishbone quarter-car suspension linkage.

A planar four-bar linkage built from a JointRRR (three parallel-axis revolute joints, r123), a fourth Revolute (r2) and two FixedTranslation rods (b1, chassis). An optional spring-damper acts between the lower link and the chassis. The chassis attachment is exposed via chassis_frame, and the point where a wheel / excitation rod attaches is exposed via wheel_frame (= r123.frame_ib).

Set mirror = true for the opposite (left) side of the car.

Usage

MultibodyComponents.examples.suspension.QuarterCarSuspension(cs=4000, ks=44000, rod_radius=0.02, jr=0.03, jc=[0.5, 0.5, 0.5, 0.7], n=[1, 0, 0], dir=ifelse(mirror, -1.0, 1.0), ndir=n * dir, AB=146.5 / 1000, BC=233.84 / 1000, CD=228.60 / 1000, DA=221.43 / 1000, t5=19.84 * π / 180, d_rod1=[0, -0.1, 0.2 * dir], d_rod2=[0, 0.2, 0], d_b1=[0, -0.1, 0.3 * dir], d_chassis=[0, 0.2, 0.2 * sin(t5) * dir], rRod1_ia=AB * d_rod1 / MultibodyComponents.norm_(d_rod1), rRod2_ib=BC * d_rod2 / MultibodyComponents.norm_(d_rod2), r_b1=CD * d_b1 / MultibodyComponents.norm_(d_b1), r_chassis=DA * d_chassis / MultibodyComponents.norm_(d_chassis), r_mount_F=0.7 * r_b1, r_mount_E=1.3 * r_chassis)

Parameters:

NameDescriptionUnitsDefault value
positive_branchAssembly branch (elbow up/down) of the analytic JointRRR loop closuretrue
springtrue
mirrorfalse
n_nonzeroStructural sparsity mask for the revolute axis (set consistently with n; threaded to r2 and the JointRRR so axis*axis' has literal zeros).[true, false, false]
csDamping constant [Ns/m]4000
ksSpring constant [N/m]44000
rod_radiusRadius of the rods0.02
jrRadius of the revolute joints0.03
jcRGBA color of the joints / rods[0.5, 0.5, 0.5, 0.7]
n[1, 0, 0]
AB146.5 / 1000
BC233.84 / 1000
CD228.60 / 1000
DA221.43 / 1000
t519.84 * π / 180

Connectors

  • chassis_frame - Frame3D is the fundamental 3D connector used for 6DOF motion. Most components have one or several Frame

connectors that can be connected together (Frame3D)

  • wheel_frame - Frame3D is the fundamental 3D connector used for 6DOF motion. Most components have one or several Frame

connectors that can be connected together (Frame3D)

Behavior

Source

dyad
"""
Double-wishbone quarter-car suspension linkage.

A planar four-bar linkage built from a `JointRRR` (three parallel-axis revolute
joints, `r123`), a fourth `Revolute` (`r2`) and two `FixedTranslation` rods
(`b1`, `chassis`). An optional spring-damper acts between the lower link and the
chassis. The chassis attachment is exposed via `chassis_frame`, and the point
where a wheel / excitation rod attaches is exposed via `wheel_frame`
(= `r123.frame_ib`).

Set `mirror = true` for the opposite (left) side of the car.
"""
component QuarterCarSuspension
  "Frame rigidly attached to the chassis (= chassis link frame_a)"
  chassis_frame = Frame3D() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 450, "y1": -50, "x2": 550, "y2": 50, "rot": 0}
      },
      "tags": []
    }
  }
  "Frame where the wheel / excitation rod attaches (= r123.frame_ib)"
  wheel_frame = Frame3D() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 450, "y1": 950, "x2": 550, "y2": 1050, "rot": 0}
      },
      "tags": []
    }
  }
  r123 = MultibodyComponents.JointRRR(n_a = ndir, n_b = ndir, n_b_nonzero = n_nonzero, rRod1_ia = rRod1_ia, rRod2_ib = rRod2_ib, positive_branch = positive_branch, rod_radius = 0.018, rod_color = jc) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 450, "y1": 330, "x2": 550, "y2": 430, "rot": 90}
      },
      "tags": []
    }
  }
  # High state priority: the four-bar's single DOF must be represented by this
  # joint's coordinate (phi, w). If state selection instead picks rotation-matrix
  # derivative chains, the torn velocity-level block is numerically singular at
  # axis-aligned joint parameters (StateSelection.jl#98 diagnosis).
  r2 = MultibodyComponents.Revolute(n = ndir, n_nonzero = n_nonzero, radius = jr, color = jc, statePriority = 100) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 190, "y1": 380, "x2": 290, "y2": 480, "rot": 0}
      },
      "tags": []
    }
  }
  b1 = MultibodyComponents.FixedTranslation(r = r_b1, radius = rod_radius) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 320, "y1": 380, "x2": 420, "y2": 480, "rot": 0}
      },
      "tags": []
    }
  }
  chassis = MultibodyComponents.FixedTranslation(r = r_chassis, render = false) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 450, "y1": 140, "x2": 550, "y2": 240, "rot": 90}
      },
      "tags": []
    }
  }
  springdamper = MultibodyComponents.SpringDamperParallel(c = ks, d = cs, s_unstretched = 1.3 * BC, radius = rod_radius, num_windings = 10) if spring {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 265, "y1": 20, "x2": 365, "y2": 120, "rot": 0}
      },
      "tags": []
    }
  }
  spring_mount_F = MultibodyComponents.FixedTranslation(r = r_mount_F, render = false) if spring {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 20, "y1": 20, "x2": 120, "y2": 120, "rot": 0}
      },
      "tags": []
    }
  }
  spring_mount_E = MultibodyComponents.FixedTranslation(r = r_mount_E) if spring {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 140, "y1": 20, "x2": 240, "y2": 120, "rot": 0}
      },
      "tags": []
    }
  }
  "Assembly branch (elbow up/down) of the analytic JointRRR loop closure"
  structural parameter positive_branch::Boolean = true
  structural parameter spring::Boolean = true
  structural parameter mirror::Boolean = false
  "Damping constant [Ns/m]"
  parameter cs::Real = 4000
  "Spring constant [N/m]"
  parameter ks::Real = 44000
  "Radius of the rods"
  parameter rod_radius::Real = 0.02
  "Radius of the revolute joints"
  parameter jr::Real = 0.03
  "RGBA color of the joints / rods"
  parameter jc::Real[4] = [0.5, 0.5, 0.5, 0.7]
  parameter n::Real[3] = [1, 0, 0]
  "Structural sparsity mask for the revolute axis (set consistently with `n`; threaded to r2 and the JointRRR so axis*axis' has literal zeros)."
  structural parameter n_nonzero::Boolean[3] = [true, false, false]
  final parameter dir::Real = ifelse(mirror, -1.0, 1.0)
  final parameter ndir::Real[3] = n * dir
  parameter AB::Real = 146.5 / 1000
  parameter BC::Real = 233.84 / 1000
  parameter CD::Real = 228.60 / 1000
  parameter DA::Real = 221.43 / 1000
  parameter t5::Real = 19.84 * π / 180
  final parameter d_rod1::Real[3] = [0, -0.1, 0.2 * dir]
  final parameter d_rod2::Real[3] = [0, 0.2, 0]
  final parameter d_b1::Real[3] = [0, -0.1, 0.3 * dir]
  final parameter d_chassis::Real[3] = [0, 0.2, 0.2 * sin(t5) * dir]
  final parameter rRod1_ia::Real[3] = AB * d_rod1 / MultibodyComponents.norm_(d_rod1)
  final parameter rRod2_ib::Real[3] = BC * d_rod2 / MultibodyComponents.norm_(d_rod2)
  final parameter r_b1::Length[3] = CD * d_b1 / MultibodyComponents.norm_(d_b1)
  final parameter r_chassis::Length[3] = DA * d_chassis / MultibodyComponents.norm_(d_chassis)
  final parameter r_mount_F::Length[3] = 0.7 * r_b1
  final parameter r_mount_E::Length[3] = 1.3 * r_chassis
relations
  # Main loop: chassis.frame_b -> r123 -> b1 -> r2 -> chassis.frame_a
  connect(chassis.frame_b, r123.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(r123.frame_b, b1.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(b1.frame_a, r2.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  if spring
    connect(springdamper.frame_b, spring_mount_E.frame_b)
    connect(b1.frame_a, spring_mount_F.frame_a)
    connect(chassis.frame_a, spring_mount_E.frame_a)
    connect(springdamper.frame_a, spring_mount_F.frame_b)
  end
  connect(chassis_frame, chassis.frame_a, r2.frame_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [], "E": 2},
        {"S": 3, "M": [{"x": 130, "y": 430}, {"x": 130, "y": 120}], "E": -1}
      ],
      "junctions": [{"x": 500, "y": 120}],
      "renderStyle": "standard"
    }
  }
  connect(wheel_frame, r123.frame_ib) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [{"x": 500, "y": 960}, {"x": 600, "y": 960}, {"x": 600, "y": 398}],
          "E": 2
        }
      ],
      "renderStyle": "standard"
    }
  }
metadata {
  "Dyad": {"icons": {"default": "dyad://MultibodyComponents/QuarterCarSuspension.svg"}}
}
end
Flattened Source
dyad
"""
Double-wishbone quarter-car suspension linkage.

A planar four-bar linkage built from a `JointRRR` (three parallel-axis revolute
joints, `r123`), a fourth `Revolute` (`r2`) and two `FixedTranslation` rods
(`b1`, `chassis`). An optional spring-damper acts between the lower link and the
chassis. The chassis attachment is exposed via `chassis_frame`, and the point
where a wheel / excitation rod attaches is exposed via `wheel_frame`
(= `r123.frame_ib`).

Set `mirror = true` for the opposite (left) side of the car.
"""
component QuarterCarSuspension
  "Frame rigidly attached to the chassis (= chassis link frame_a)"
  chassis_frame = Frame3D() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 450, "y1": -50, "x2": 550, "y2": 50, "rot": 0}
      },
      "tags": []
    }
  }
  "Frame where the wheel / excitation rod attaches (= r123.frame_ib)"
  wheel_frame = Frame3D() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 450, "y1": 950, "x2": 550, "y2": 1050, "rot": 0}
      },
      "tags": []
    }
  }
  r123 = MultibodyComponents.JointRRR(n_a = ndir, n_b = ndir, n_b_nonzero = n_nonzero, rRod1_ia = rRod1_ia, rRod2_ib = rRod2_ib, positive_branch = positive_branch, rod_radius = 0.018, rod_color = jc) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 450, "y1": 330, "x2": 550, "y2": 430, "rot": 90}
      },
      "tags": []
    }
  }
  # High state priority: the four-bar's single DOF must be represented by this
  # joint's coordinate (phi, w). If state selection instead picks rotation-matrix
  # derivative chains, the torn velocity-level block is numerically singular at
  # axis-aligned joint parameters (StateSelection.jl#98 diagnosis).
  r2 = MultibodyComponents.Revolute(n = ndir, n_nonzero = n_nonzero, radius = jr, color = jc, statePriority = 100) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 190, "y1": 380, "x2": 290, "y2": 480, "rot": 0}
      },
      "tags": []
    }
  }
  b1 = MultibodyComponents.FixedTranslation(r = r_b1, radius = rod_radius) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 320, "y1": 380, "x2": 420, "y2": 480, "rot": 0}
      },
      "tags": []
    }
  }
  chassis = MultibodyComponents.FixedTranslation(r = r_chassis, render = false) {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 450, "y1": 140, "x2": 550, "y2": 240, "rot": 90}
      },
      "tags": []
    }
  }
  springdamper = MultibodyComponents.SpringDamperParallel(c = ks, d = cs, s_unstretched = 1.3 * BC, radius = rod_radius, num_windings = 10) if spring {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 265, "y1": 20, "x2": 365, "y2": 120, "rot": 0}
      },
      "tags": []
    }
  }
  spring_mount_F = MultibodyComponents.FixedTranslation(r = r_mount_F, render = false) if spring {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 20, "y1": 20, "x2": 120, "y2": 120, "rot": 0}
      },
      "tags": []
    }
  }
  spring_mount_E = MultibodyComponents.FixedTranslation(r = r_mount_E) if spring {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 140, "y1": 20, "x2": 240, "y2": 120, "rot": 0}
      },
      "tags": []
    }
  }
  "Assembly branch (elbow up/down) of the analytic JointRRR loop closure"
  structural parameter positive_branch::Boolean = true
  structural parameter spring::Boolean = true
  structural parameter mirror::Boolean = false
  "Damping constant [Ns/m]"
  parameter cs::Real = 4000
  "Spring constant [N/m]"
  parameter ks::Real = 44000
  "Radius of the rods"
  parameter rod_radius::Real = 0.02
  "Radius of the revolute joints"
  parameter jr::Real = 0.03
  "RGBA color of the joints / rods"
  parameter jc::Real[4] = [0.5, 0.5, 0.5, 0.7]
  parameter n::Real[3] = [1, 0, 0]
  "Structural sparsity mask for the revolute axis (set consistently with `n`; threaded to r2 and the JointRRR so axis*axis' has literal zeros)."
  structural parameter n_nonzero::Boolean[3] = [true, false, false]
  final parameter dir::Real = ifelse(mirror, -1.0, 1.0)
  final parameter ndir::Real[3] = n * dir
  parameter AB::Real = 146.5 / 1000
  parameter BC::Real = 233.84 / 1000
  parameter CD::Real = 228.60 / 1000
  parameter DA::Real = 221.43 / 1000
  parameter t5::Real = 19.84 * π / 180
  final parameter d_rod1::Real[3] = [0, -0.1, 0.2 * dir]
  final parameter d_rod2::Real[3] = [0, 0.2, 0]
  final parameter d_b1::Real[3] = [0, -0.1, 0.3 * dir]
  final parameter d_chassis::Real[3] = [0, 0.2, 0.2 * sin(t5) * dir]
  final parameter rRod1_ia::Real[3] = AB * d_rod1 / MultibodyComponents.norm_(d_rod1)
  final parameter rRod2_ib::Real[3] = BC * d_rod2 / MultibodyComponents.norm_(d_rod2)
  final parameter r_b1::Length[3] = CD * d_b1 / MultibodyComponents.norm_(d_b1)
  final parameter r_chassis::Length[3] = DA * d_chassis / MultibodyComponents.norm_(d_chassis)
  final parameter r_mount_F::Length[3] = 0.7 * r_b1
  final parameter r_mount_E::Length[3] = 1.3 * r_chassis
relations
  # Main loop: chassis.frame_b -> r123 -> b1 -> r2 -> chassis.frame_a
  connect(chassis.frame_b, r123.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(r123.frame_b, b1.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  connect(b1.frame_a, r2.frame_b) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
  if spring
    connect(springdamper.frame_b, spring_mount_E.frame_b)
    connect(b1.frame_a, spring_mount_F.frame_a)
    connect(chassis.frame_a, spring_mount_E.frame_a)
    connect(springdamper.frame_a, spring_mount_F.frame_b)
  end
  connect(chassis_frame, chassis.frame_a, r2.frame_a) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [], "E": -1},
        {"S": -1, "M": [], "E": 2},
        {"S": 3, "M": [{"x": 130, "y": 430}, {"x": 130, "y": 120}], "E": -1}
      ],
      "junctions": [{"x": 500, "y": 120}],
      "renderStyle": "standard"
    }
  }
  connect(wheel_frame, r123.frame_ib) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [{"x": 500, "y": 960}, {"x": 600, "y": 960}, {"x": 600, "y": 398}],
          "E": 2
        }
      ],
      "renderStyle": "standard"
    }
  }
metadata {
  "Dyad": {"icons": {"default": "dyad://MultibodyComponents/QuarterCarSuspension.svg"}}
}
end


Test Cases

No test cases defined.

  • Examples

  • Experiments

  • Analyses