Chapter 11: Lagrange–Euler Dynamics for Manipulators

Lesson 4: Examples — 2-DOF and 3-DOF Arms

This lesson applies the general Lagrange–Euler formulation developed in previous lessons to concrete serial manipulators: a 2-DOF planar R–R arm and a 3-DOF planar R–R–R arm. We derive explicit expressions for the inertia matrix, Coriolis/centrifugal terms, and gravity vector, and then implement these models in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.

1. Conceptual Overview

From Lesson 1 we know that an n-DOF rigid manipulator admits equations of motion of the form

\[ \mathbf{M}(\mathbf{q})\,\ddot{\mathbf{q}} + \mathbf{C}(\mathbf{q},\dot{\mathbf{q}})\,\dot{\mathbf{q}} + \mathbf{g}(\mathbf{q}) = \boldsymbol{\tau}, \]

where \( \mathbf{M}(\mathbf{q}) \) is the symmetric positive definite inertia matrix, \( \mathbf{C}(\mathbf{q},\dot{\mathbf{q}}) \) collects Coriolis and centrifugal terms, \( \mathbf{g}(\mathbf{q}) \) is the gravity torque vector, and \( \boldsymbol{\tau} \) contains joint input torques.

In this lesson we specialize this general structure to:

  • A 2-DOF planar R–R arm (two revolute joints in a plane).
  • A 3-DOF planar R–R–R arm.

This illustrates how geometry, mass distribution, and joint coordinates appear explicitly in \( \mathbf{M},\mathbf{C},\mathbf{g} \), and prepares us for symbolic and numeric derivation workflows in Lesson 5.

flowchart TD
  A["Geometric model (links, joints, COMs)"] --> B["Kinematics: positions, velocities"]
  B --> C["Energies: T(q,qdot), V(q)"]
  C --> D["Lagrangian L = T - V"]
  D --> E["Compute d/dt(dL/dqdot) - dL/dq"]
  E --> F["Arrange into M(q) qddot + C(q,qdot) qdot + g(q) = tau"]
        

2. 2-DOF Planar R–R Manipulator — Geometry and Parameters

Consider a 2-DOF planar manipulator with two revolute joints, joint angles \( q_1, q_2 \), and link lengths \( \ell_1, \ell_2 \). Let each link be a rigid body with:

  • Masses \( m_1, m_2 \).
  • Center-of-mass (COM) distances along each link: \( c_1 \) from joint 1 to the COM of link 1, \( c_2 \) from joint 2 to the COM of link 2.
  • Planar rotational inertias about the COM: \( I_1, I_2 \).

Using the base frame at joint 1, with the x-axis along the first link when \( q_1 = 0 \) and gravity pointing in the negative y-direction (for convenience), the COM positions are:

\[ \begin{aligned} x_1(q_1) &= c_1 \cos q_1, & y_1(q_1) &= c_1 \sin q_1, \\ x_2(q_1,q_2) &= \ell_1 \cos q_1 + c_2 \cos(q_1 + q_2), & y_2(q_1,q_2) &= \ell_1 \sin q_1 + c_2 \sin(q_1 + q_2). \end{aligned} \]

Differentiating with respect to time gives COM velocities \( \mathbf{v}_i = \dot{\mathbf{p}}_i(q,\dot{q}) \) as functions of \( q_1,q_2,\dot{q}_1,\dot{q}_2 \). These feed into the kinetic energy.

3. 2-DOF Arm — Kinetic and Potential Energy

The total kinetic energy is the sum of translational and rotational contributions:

\[ T = \frac{1}{2} m_1 \|\mathbf{v}_1\|^2 + \frac{1}{2} I_1 \dot{q}_1^2 + \frac{1}{2} m_2 \|\mathbf{v}_2\|^2 + \frac{1}{2} I_2 (\dot{q}_1 + \dot{q}_2)^2 . \]

Using the expressions for \( \mathbf{v}_1,\mathbf{v}_2 \), one obtains the standard quadratic form:

\[ T = \tfrac{1}{2} \begin{bmatrix} \dot{q}_1 & \dot{q}_2 \end{bmatrix} \mathbf{M}(q_2) \begin{bmatrix} \dot{q}_1 \\ \dot{q}_2 \end{bmatrix}, \]

with inertia matrix entries

\[ \begin{aligned} M_{11}(q_2) &= I_1 + I_2 + m_1 c_1^2 + m_2\big(\ell_1^2 + c_2^2 + 2 \ell_1 c_2 \cos q_2\big), \\ M_{12}(q_2) &= M_{21}(q_2) = I_2 + m_2\big(c_2^2 + \ell_1 c_2 \cos q_2\big), \\ M_{22}(q_2) &= I_2 + m_2 c_2^2. \end{aligned} \]

The potential energy (with gravity magnitude \( g > 0 \) pointing in negative y) is

\[ V(q_1,q_2) = m_1 g c_1 \sin q_1 + m_2 g\big(\ell_1 \sin q_1 + c_2 \sin(q_1 + q_2)\big). \]

The Lagrangian is \( L(q,\dot{q}) = T(q,\dot{q}) - V(q) \) and feeds directly into the Euler–Lagrange equations.

4. 2-DOF Arm — Explicit Dynamics: \( \mathbf{M}, \mathbf{C}, \mathbf{g} \)

Applying the Euler–Lagrange equation from Lesson 1, \( \frac{\mathrm{d}}{\mathrm{d}t}\frac{\partial L}{\partial \dot{q}_i} - \frac{\partial L}{\partial q_i} = \tau_i \), we obtain the standard dynamic model:

\[ \mathbf{M}(q_2)\,\ddot{\mathbf{q}} + \mathbf{C}(q_2,\dot{\mathbf{q}})\,\dot{\mathbf{q}} + \mathbf{g}(q_1,q_2) = \boldsymbol{\tau}, \quad \mathbf{q} = \begin{bmatrix} q_1 \\ q_2 \end{bmatrix}. \]

Define the coupling term \( h(q_2) = m_2 \ell_1 c_2 \sin q_2 \). A widely used (and energy-consistent) choice for \( \mathbf{C} \) is

\[ \mathbf{C}(q_2,\dot{\mathbf{q}}) = \begin{bmatrix} -h(q_2)\,\dot{q}_2 & -h(q_2)\,(\dot{q}_1 + \dot{q}_2) \\ h(q_2)\,\dot{q}_1 & 0 \end{bmatrix}. \]

The gravity vector is

\[ \mathbf{g}(q_1,q_2) = \begin{bmatrix} (m_1 c_1 + m_2 \ell_1) g \cos q_1 + m_2 c_2 g \cos(q_1 + q_2) \\ m_2 c_2 g \cos(q_1 + q_2) \end{bmatrix}. \]

The following structural properties (from Lesson 3) hold:

  • Symmetry and positive definiteness: \( \mathbf{M}(q_2) = \mathbf{M}(q_2)^\top \) and \( \mathbf{M}(q_2) \) is positive definite for all configurations.
  • Skew-symmetry: the matrix \( \dot{\mathbf{M}}(\mathbf{q}) - 2\mathbf{C}(\mathbf{q},\dot{\mathbf{q}}) \) is skew-symmetric, which underpins passivity of the manipulator dynamics.

5. 3-DOF Planar R–R–R Arm — Structural Dynamics

Consider now a 3-DOF planar manipulator with revolute joints \( q_1,q_2,q_3 \), link lengths \( \ell_1,\ell_2,\ell_3 \), COM distances \( c_1,c_2,c_3 \), masses \( m_1,m_2,m_3 \), and inertias \( I_1,I_2,I_3 \) about the COMs.

The kinetic energy again has quadratic form

\[ T = \tfrac{1}{2} \dot{\mathbf{q}}^\top \mathbf{M}(\mathbf{q}) \dot{\mathbf{q}}, \quad \mathbf{q} = \begin{bmatrix} q_1 \\ q_2 \\ q_3 \end{bmatrix}, \]

with \( \mathbf{M}(\mathbf{q}) \) a \( 3\times 3 \) symmetric matrix. Instead of writing all entries explicitly (which is algebraically long), it is useful to recognize that every entry is a linear combination of a small set of trigonometric functions:

\[ \mathbf{M}(\mathbf{q}) = \mathbf{M}_0 + \mathbf{M}_1 \cos q_2 + \mathbf{M}_2 \cos q_3 + \mathbf{M}_3 \cos(q_2 + q_3), \]

where \( \mathbf{M}_0,\dots,\mathbf{M}_3 \) are constant symmetric matrices determined by \( m_i, I_i, \ell_i, c_i \). This parametric structure is heavily used in identification (Chapter 19).

The gravity vector has the generic structure

\[ \mathbf{g}(\mathbf{q}) = \mathbf{g}_1 \cos q_1 + \mathbf{g}_2 \cos(q_1 + q_2) + \mathbf{g}_3 \cos(q_1 + q_2 + q_3), \]

where the constant vectors \( \mathbf{g}_i \) encode the vertical projection of each link's COM. The Coriolis/centrifugal terms are constructed from the Christoffel symbols (Lesson 2); they can be expressed as bilinear forms in \( \dot{\mathbf{q}} \) with coefficients depending on \( \sin q_2, \sin q_3, \sin(q_2 + q_3) \).

Despite the more complex expressions, all structural properties (symmetry and positive definiteness of \( \mathbf{M} \), skew-symmetry of \( \dot{\mathbf{M}} - 2\mathbf{C} \), passivity) remain valid.

flowchart TD
  Q["q = [q1,q2,q3]"] --> T["Compute link COM speeds: v1,v2,v3"]
  T --> K["Form T = 0.5 * sum( mi * |vi|^2 + Ii * wi^2 )"]
  K --> M["Identify M(q) by collecting terms in qdot"]
  M --> Cmat["Compute Christoffel coefficients for C(q,qdot)"]
  Cmat --> G["Take V(q) from COM heights, then g(q) = dV/dq"]
  G --> FINAL["Assemble 3x3 dynamics: M(q) qddot + C(q,qdot) qdot + g(q) = tau"]
        

6. Python Implementation — 2-DOF and 3-DOF Dynamics

We now implement the 2-DOF planar dynamics explicitly in Python using numpy. Symbolic derivation can be performed with sympy, but here we assume the closed-form expressions from Sections 3 and 4.


import numpy as np

class TwoLinkParams:
    def __init__(self, l1, l2, c1, c2, m1, m2, I1, I2, g=9.81):
        self.l1 = l1
        self.l2 = l2
        self.c1 = c1
        self.c2 = c2
        self.m1 = m1
        self.m2 = m2
        self.I1 = I1
        self.I2 = I2
        self.g = g

def M_2R(q, params):
    q1, q2 = q
    l1 = params.l1
    c1 = params.c1
    c2 = params.c2
    m1 = params.m1
    m2 = params.m2
    I1 = params.I1
    I2 = params.I2

    cos2 = np.cos(q2)

    M11 = I1 + I2 + m1 * c1**2 + m2 * (l1**2 + c2**2 + 2.0 * l1 * c2 * cos2)
    M12 = I2 + m2 * (c2**2 + l1 * c2 * cos2)
    M22 = I2 + m2 * c2**2

    return np.array([[M11, M12],
                     [M12, M22]])

def C_2R(q, qdot, params):
    q1, q2 = q
    q1dot, q2dot = qdot
    l1 = params.l1
    c2 = params.c2
    m2 = params.m2

    sin2 = np.sin(q2)
    h = m2 * l1 * c2 * sin2

    C11 = -h * q2dot
    C12 = -h * (q1dot + q2dot)
    C21 = h * q1dot
    C22 = 0.0

    return np.array([[C11, C12],
                     [C21, C22]])

def g_2R(q, params):
    q1, q2 = q
    c1 = params.c1
    c2 = params.c2
    l1 = params.l1
    m1 = params.m1
    m2 = params.m2
    g = params.g

    g1 = (m1 * c1 + m2 * l1) * g * np.cos(q1) + m2 * c2 * g * np.cos(q1 + q2)
    g2 = m2 * c2 * g * np.cos(q1 + q2)

    return np.array([g1, g2])

def forward_dynamics_2R(q, qdot, tau, params):
    M = M_2R(q, params)
    C = C_2R(q, qdot, params)
    g_vec = g_2R(q, params)
    rhs = tau - C @ qdot - g_vec
    qddot = np.linalg.solve(M, rhs)
    return qddot

# Example usage
if __name__ == "__main__":
    params = TwoLinkParams(
        l1=1.0, l2=1.0,
        c1=0.5, c2=0.5,
        m1=1.0, m2=1.0,
        I1=0.1, I2=0.1,
        g=9.81
    )
    q = np.array([0.3, 0.4])
    qdot = np.array([0.2, -0.1])
    tau = np.array([0.5, 0.2])
    qddot = forward_dynamics_2R(q, qdot, tau, params)
    print("qddot =", qddot)
      

For a 3-DOF planar arm, one practical approach is to precompute constant matrices \( \mathbf{M}_0,\dots,\mathbf{M}_3 \) and implement


def M_3R(q, M0, M1, M2, M3):
    q1, q2, q3 = q
    return (M0
            + np.cos(q2) * M1
            + np.cos(q3) * M2
            + np.cos(q2 + q3) * M3)
      

where M0,...,M3 are 3x3 symmetric numpy arrays determined once from symbolic derivation (Lesson 5).

7. C++ Implementation — 2-DOF Dynamics with Eigen

In C++, a common choice for small dense linear algebra is the Eigen library. Below we implement \( \mathbf{M}, \mathbf{C}, \mathbf{g} \) and the forward dynamics map \( (\mathbf{q},\dot{\mathbf{q}},\boldsymbol{\tau}) \mapsto \ddot{\mathbf{q}} \).


#include <Eigen/Dense>

struct TwoLinkParams {
    double l1, l2;
    double c1, c2;
    double m1, m2;
    double I1, I2;
    double g;
};

Eigen::Matrix2d M_2R(const Eigen::Vector2d& q, const TwoLinkParams& p) {
    double q2 = q(1);
    double cos2 = std::cos(q2);

    double M11 = p.I1 + p.I2
                 + p.m1 * p.c1 * p.c1
                 + p.m2 * (p.l1 * p.l1 + p.c2 * p.c2 + 2.0 * p.l1 * p.c2 * cos2);
    double M12 = p.I2 + p.m2 * (p.c2 * p.c2 + p.l1 * p.c2 * cos2);
    double M22 = p.I2 + p.m2 * p.c2 * p.c2;

    Eigen::Matrix2d M;
    M(0,0) = M11; M(0,1) = M12;
    M(1,0) = M12; M(1,1) = M22;
    return M;
}

Eigen::Matrix2d C_2R(const Eigen::Vector2d& q,
                     const Eigen::Vector2d& qdot,
                     const TwoLinkParams& p) {
    double q2 = q(1);
    double q1dot = qdot(0);
    double q2dot = qdot(1);

    double sin2 = std::sin(q2);
    double h = p.m2 * p.l1 * p.c2 * sin2;

    Eigen::Matrix2d C;
    C(0,0) = -h * q2dot;
    C(0,1) = -h * (q1dot + q2dot);
    C(1,0) =  h * q1dot;
    C(1,1) =  0.0;
    return C;
}

Eigen::Vector2d g_2R(const Eigen::Vector2d& q, const TwoLinkParams& p) {
    double q1 = q(0);
    double q2 = q(1);

    double g1 = (p.m1 * p.c1 + p.m2 * p.l1) * p.g * std::cos(q1)
                + p.m2 * p.c2 * p.g * std::cos(q1 + q2);
    double g2 = p.m2 * p.c2 * p.g * std::cos(q1 + q2);

    Eigen::Vector2d gv;
    gv(0) = g1;
    gv(1) = g2;
    return gv;
}

Eigen::Vector2d forward_dynamics_2R(const Eigen::Vector2d& q,
                                    const Eigen::Vector2d& qdot,
                                    const Eigen::Vector2d& tau,
                                    const TwoLinkParams& p) {
    Eigen::Matrix2d M = M_2R(q, p);
    Eigen::Matrix2d C = C_2R(q, qdot, p);
    Eigen::Vector2d gvec = g_2R(q, p);

    Eigen::Vector2d rhs = tau - C * qdot - gvec;
    Eigen::Vector2d qddot = M.ldlt().solve(rhs);
    return qddot;
}
      

For larger DOF counts or dynamic parameters, one typically organizes the code in classes (e.g., a RigidBody hierarchy) but the structural form remains the same.

8. Java Implementation — 2-DOF Dynamics

In Java, we can implement the same model using primitive arrays without external libraries. The inertia matrix inversion is coded explicitly for the 2x2 case.


public class TwoLinkDynamics {

    public static class Params {
        public double l1, l2;
        public double c1, c2;
        public double m1, m2;
        public double I1, I2;
        public double g;

        public Params(double l1, double l2,
                      double c1, double c2,
                      double m1, double m2,
                      double I1, double I2,
                      double g) {
            this.l1 = l1;
            this.l2 = l2;
            this.c1 = c1;
            this.c2 = c2;
            this.m1 = m1;
            this.m2 = m2;
            this.I1 = I1;
            this.I2 = I2;
            this.g = g;
        }
    }

    public static double[][] M(double[] q, Params p) {
        double q2 = q[1];
        double cos2 = Math.cos(q2);

        double M11 = p.I1 + p.I2
                     + p.m1 * p.c1 * p.c1
                     + p.m2 * (p.l1 * p.l1 + p.c2 * p.c2 + 2.0 * p.l1 * p.c2 * cos2);
        double M12 = p.I2 + p.m2 * (p.c2 * p.c2 + p.l1 * p.c2 * cos2);
        double M22 = p.I2 + p.m2 * p.c2 * p.c2;

        return new double[][] {
            { M11, M12 },
            { M12, M22 }
        };
    }

    public static double[][] C(double[] q, double[] qdot, Params p) {
        double q2 = q[1];
        double q1dot = qdot[0];
        double q2dot = qdot[1];

        double sin2 = Math.sin(q2);
        double h = p.m2 * p.l1 * p.c2 * sin2;

        return new double[][] {
            { -h * q2dot,      -h * (q1dot + q2dot) },
            {  h * q1dot,       0.0 }
        };
    }

    public static double[] g(double[] q, Params p) {
        double q1 = q[0];
        double q2 = q[1];

        double g1 = (p.m1 * p.c1 + p.m2 * p.l1) * p.g * Math.cos(q1)
                    + p.m2 * p.c2 * p.g * Math.cos(q1 + q2);
        double g2 = p.m2 * p.c2 * p.g * Math.cos(q1 + q2);

        return new double[] { g1, g2 };
    }

    public static double[] forwardDynamics(double[] q,
                                           double[] qdot,
                                           double[] tau,
                                           Params p) {
        double[][] M = M(q, p);
        double[][] C = C(q, qdot, p);
        double[] g = g(q, p);

        // rhs = tau - C qdot - g
        double[] rhs = new double[2];
        rhs[0] = tau[0]
                 - (C[0][0] * qdot[0] + C[0][1] * qdot[1])
                 - g[0];
        rhs[1] = tau[1]
                 - (C[1][0] * qdot[0] + C[1][1] * qdot[1])
                 - g[1];

        // Invert 2x2 inertia matrix
        double det = M[0][0] * M[1][1] - M[0][1] * M[1][0];
        double inv00 =  M[1][1] / det;
        double inv01 = -M[0][1] / det;
        double inv10 = -M[1][0] / det;
        double inv11 =  M[0][0] / det;

        double[] qddot = new double[2];
        qddot[0] = inv00 * rhs[0] + inv01 * rhs[1];
        qddot[1] = inv10 * rhs[0] + inv11 * rhs[1];
        return qddot;
    }
}
      

For a 3-DOF arm, similar array-based code can be used with a generic 3x3 matrix inversion routine.

9. MATLAB/Simulink Implementation — 2-DOF Dynamics

MATLAB offers both symbolic and numeric workflows. A compact numeric function for the 2-DOF dynamics is:


function qddot = two_link_forward_dynamics(t, state, tau, p)
% state = [q1; q2; q1dot; q2dot]
q1 = state(1); q2 = state(2);
q1dot = state(3); q2dot = state(4);

l1 = p.l1; c1 = p.c1; c2 = p.c2;
m1 = p.m1; m2 = p.m2;
I1 = p.I1; I2 = p.I2;
g = p.g;

cos2 = cos(q2);
sin2 = sin(q2);

M11 = I1 + I2 + m1 * c1^2 + m2 * (l1^2 + c2^2 + 2 * l1 * c2 * cos2);
M12 = I2 + m2 * (c2^2 + l1 * c2 * cos2);
M22 = I2 + m2 * c2^2;
M = [M11, M12; M12, M22];

h = m2 * l1 * c2 * sin2;
C = [-h * q2dot,          -h * (q1dot + q2dot);
      h * q1dot,           0];

g1 = (m1 * c1 + m2 * l1) * g * cos(q1) + m2 * c2 * g * cos(q1 + q2);
g2 = m2 * c2 * g * cos(q1 + q2);
gvec = [g1; g2];

rhs = tau - C * [q1dot; q2dot] - gvec;
qdd = M \ rhs;

qddot = qdd;
end
      

In Simulink, one typical approach is:

  • Use a MATLAB Function block that encapsulates two_link_forward_dynamics.
  • Integrate the joint accelerations using two cascaded Integrator blocks to obtain \( \dot{\mathbf{q}} \) and \( \mathbf{q} \).
  • Feed commanded \( \boldsymbol{\tau} \) as an input signal to the function block.

For a 3-DOF arm, a 3x3 inertia matrix and 3x1 vectors are used, with the same signal-flow structure.

10. Wolfram Mathematica — Symbolic Derivation Template

Wolfram Mathematica is very convenient for symbolic Lagrangian derivations. Below is a template for the 2-DOF planar arm.


(* Define symbols *)
ClearAll["Global`*"];
q1[t_]; q2[t_];
m1 = Symbol["m1"]; m2 = Symbol["m2"];
l1 = Symbol["l1"]; c1 = Symbol["c1"]; c2 = Symbol["c2"];
I1 = Symbol["I1"]; I2 = Symbol["I2"];
g  = Symbol["g"];

q1t = q1[t]; q2t = q2[t];
q1d = D[q1t, t]; q2d = D[q2t, t];

(* COM positions *)
x1 = c1 Cos[q1t];
y1 = c1 Sin[q1t];

x2 = l1 Cos[q1t] + c2 Cos[q1t + q2t];
y2 = l1 Sin[q1t] + c2 Sin[q1t + q2t];

(* Velocities *)
vx1 = D[x1, t]; vy1 = D[y1, t];
vx2 = D[x2, t]; vy2 = D[y2, t];

(* Kinetic and potential energy *)
T = 1/2 m1 (vx1^2 + vy1^2) + 1/2 I1 q1d^2
  + 1/2 m2 (vx2^2 + vy2^2) + 1/2 I2 (q1d + q2d)^2;

V = m1 g y1 + m2 g y2;

L = T - V;

(* Euler-Lagrange equations *)
eq1 = D[D[L, q1d], t] - D[L, q1t] == Symbol["tau1"];
eq2 = D[D[L, q2d], t] - D[L, q2t] == Symbol["tau2"];

(* Expand and collect with respect to accelerations q1dd, q2dd *)
q1dd = D[q1d, t]; q2dd = D[q2d, t];
eq1exp = Expand[eq1];
eq2exp = Expand[eq2];

(* Coefficients of q1dd, q2dd provide inertia matrix entries *)
M11 = Coefficient[eq1exp, q1dd];
M12 = Coefficient[eq1exp, q2dd];
M21 = Coefficient[eq2exp, q1dd];
M22 = Coefficient[eq2exp, q2dd];

Mmat = { {M11, M12}, {M21, M22} } // Simplify;

(* Residual terms yield C(q,qdot) qdot + g(q) *)
rhs1 = (Symbol["tau1"] - (eq1exp /. {q1dd -> 0, q2dd -> 0})) // Simplify;
rhs2 = (Symbol["tau2"] - (eq2exp /. {q1dd -> 0, q2dd -> 0})) // Simplify;

rhs = {rhs1, rhs2};
      

The resulting Mmat and rhs can be further processed to identify \( \mathbf{C}(\mathbf{q},\dot{\mathbf{q}})\dot{\mathbf{q}} \) and \( \mathbf{g}(\mathbf{q}) \), and exported as C or C++ code.

11. Problems and Solutions

Problem 1 (Deriving \( \mathbf{M} \) for a Simplified 2-DOF Arm): Consider a 2-DOF planar R–R arm where link 1 and link 2 are modeled as point masses at distances \( c_1 \) and \( c_2 \) from their proximal joints with zero rotational inertia (i.e., \( I_1 = I_2 = 0 \)). Derive the inertia matrix \( \mathbf{M}(q_2) \).

Solution:

For point masses, the kinetic energy is purely translational:

\[ T = \tfrac{1}{2} m_1 \|\mathbf{v}_1\|^2 + \tfrac{1}{2} m_2 \|\mathbf{v}_2\|^2. \]

Using the COM positions from Section 2, one finds

\[ \|\mathbf{v}_1\|^2 = c_1^2 \dot{q}_1^2, \quad \|\mathbf{v}_2\|^2 = \ell_1^2 \dot{q}_1^2 + c_2^2 (\dot{q}_1 + \dot{q}_2)^2 + 2 \ell_1 c_2 \cos q_2\,\dot{q}_1(\dot{q}_1 + \dot{q}_2). \]

Expanding and collecting terms in \( \dot{q}_1^2, \dot{q}_1 \dot{q}_2, \dot{q}_2^2 \), one obtains

\[ \begin{aligned} T &= \tfrac{1}{2}\big( [m_1 c_1^2 + m_2(\ell_1^2 + c_2^2 + 2\ell_1 c_2 \cos q_2)] \dot{q}_1^2 \\ &\quad + 2 m_2(c_2^2 + \ell_1 c_2 \cos q_2)\dot{q}_1 \dot{q}_2 + m_2 c_2^2 \dot{q}_2^2\big). \end{aligned} \]

Matching this to \( T = \tfrac{1}{2} \dot{\mathbf{q}}^\top \mathbf{M} \dot{\mathbf{q}} \) yields exactly the same formulas as in Section 3 but with \( I_1 = I_2 = 0 \).

Problem 2 (Skew-Symmetry Check for the 2-DOF Arm): For the 2-DOF model in Section 4, verify that \( \dot{\mathbf{M}}(\mathbf{q}) - 2\mathbf{C}(\mathbf{q},\dot{\mathbf{q}}) \) is skew-symmetric by explicitly computing its (1,1) entry.

Solution:

We only need the (1,1) entry. Since \( M_{11} \) depends on \( q_2 \) but not on \( q_1 \), we have

\[ \dot{M}_{11} = \frac{\partial M_{11}}{\partial q_2} \dot{q}_2 = -2 m_2 \ell_1 c_2 \sin q_2\,\dot{q}_2 = -2 h(q_2)\,\dot{q}_2. \]

From the definition of \( \mathbf{C} \), \( C_{11} = -h(q_2)\dot{q}_2 \), so

\[ \big(\dot{\mathbf{M}} - 2\mathbf{C}\big)_{11} = \dot{M}_{11} - 2 C_{11} = -2 h(q_2)\dot{q}_2 - 2(-h(q_2)\dot{q}_2) = 0. \]

Thus the diagonal element is zero; similar computations for off-diagonal entries verify skew-symmetry.

Problem 3 (Numerical Torque Computation): For a 2-DOF arm with \( \ell_1 = \ell_2 = 1 \), \( c_1 = c_2 = 0.5 \), \( m_1 = m_2 = 1 \), \( I_1 = I_2 = 0.1 \), \( g = 9.81 \), compute \( \boldsymbol{\tau} \) required to hold the arm at the static configuration \( q_1 = \pi/4, q_2 = -\pi/6 \) with \( \dot{\mathbf{q}} = \ddot{\mathbf{q}} = \mathbf{0} \).

Solution:

At static equilibrium with zero velocities and accelerations, the dynamics reduce to \( \mathbf{g}(q_1,q_2) = \boldsymbol{\tau} \). Therefore \( \boldsymbol{\tau} \) equals the gravity vector evaluated at the given configuration:

\[ \begin{aligned} &g_1 = (m_1 c_1 + m_2 \ell_1) g \cos q_1 + m_2 c_2 g \cos(q_1 + q_2), \\ &g_2 = m_2 c_2 g \cos(q_1 + q_2). \end{aligned} \]

Substituting the numeric values yields specific real numbers for \( g_1, g_2 \), which are the required holding torques. Students are encouraged to compute these numerically using any of the code implementations above.

Problem 4 (3-DOF Structural Form): Show that for any 3-DOF planar R–R–R arm with COMs in the plane, each entry of \( \mathbf{M}(\mathbf{q}) \) can be expressed as a finite linear combination of \( \{1, \cos q_2, \cos q_3, \cos(q_2 + q_3)\} \).

Solution:

Each COM position is obtained by composing planar rotations of the form \( R_z(q_i) \), so the squared speeds \( \|\mathbf{v}_i\|^2 \) are sums of terms like \( \dot{q}_j \dot{q}_k (\cos(\cdot), \sin(\cdot)) \) for \( j,k \in \{1,2,3\} \). Because the sum of joint angles in the planar setting always appears as \( q_1 \), \( q_1 + q_2 \), or \( q_1 + q_2 + q_3 \), the dot products of velocity vectors produce only cosines of differences between these angles, specifically functions of \( q_2 \), \( q_3 \), and \( q_2 + q_3 \). Collecting terms in \( \dot{\mathbf{q}} \) yields an inertia matrix whose entries depend only on \( 1, \cos q_2, \cos q_3, \cos(q_2 + q_3) \). The coefficients are determined by link masses, COM distances, and inertias, but no additional trigonometric functions are needed.

Problem 5 (Simple Forward Simulation Step): Using the 2-DOF dynamics \( \mathbf{M}(\mathbf{q})\ddot{\mathbf{q}} + \mathbf{C}(\mathbf{q},\dot{\mathbf{q}})\dot{\mathbf{q}} + \mathbf{g}(\mathbf{q}) = \boldsymbol{\tau} \), write an explicit Euler integration scheme for a time step \( h > 0 \) that updates \( (\mathbf{q}_k,\dot{\mathbf{q}}_k) \) to \( (\mathbf{q}_{k+1},\dot{\mathbf{q}}_{k+1}) \).

Solution:

Given \( \mathbf{q}_k,\dot{\mathbf{q}}_k,\boldsymbol{\tau}_k \), compute \( \ddot{\mathbf{q}}_k \) by solving \( \mathbf{M}(\mathbf{q}_k)\ddot{\mathbf{q}}_k = \boldsymbol{\tau}_k - \mathbf{C}(\mathbf{q}_k,\dot{\mathbf{q}}_k)\dot{\mathbf{q}}_k - \mathbf{g}(\mathbf{q}_k) \). Then set

\[ \dot{\mathbf{q}}_{k+1} = \dot{\mathbf{q}}_k + h\,\ddot{\mathbf{q}}_k, \quad \mathbf{q}_{k+1} = \mathbf{q}_k + h\,\dot{\mathbf{q}}_k. \]

This explicit Euler scheme is only first-order accurate and may require small \( h \) for stable simulations, but it is conceptually simple and matches the forward-dynamics implementations developed earlier.

12. Summary

In this lesson we instantiated the Lagrange–Euler framework on concrete serial manipulators: a 2-DOF planar R–R arm and a 3-DOF planar R–R–R arm. We derived explicit expressions for the inertia matrix, Coriolis/centrifugal terms, and gravity vector, and emphasized the structural properties (symmetry, positive definiteness, skew-symmetry of \( \dot{\mathbf{M}} - 2\mathbf{C} \), and passivity).

We then implemented these models in several programming environments that are central in robotics: Python with numpy, C++ with Eigen, Java with primitive arrays, MATLAB/Simulink, and Wolfram Mathematica. These concrete examples will be the basis for the symbolic and numeric derivation pipelines developed in Lesson 5.

13. References

  1. Luh, J.Y.S., Walker, M.W., & Paul, R.P. (1980). On-line computational scheme for mechanical manipulators. Journal of Dynamic Systems, Measurement, and Control, 102(2), 69–76.
  2. Hollerbach, J.M. (1980). A recursive Lagrangian formulation of manipulator dynamics and a comparative study of dynamics formulation complexity. IEEE Transactions on Systems, Man, and Cybernetics, 10(11), 730–736.
  3. Asada, H., & Slotine, J.-J.E. (1986). Robot analysis and control: The Lagrangian formulation. Various journal and conference contributions.
  4. Khatib, O. (1987). A unified approach for motion and force control of robot manipulators: The operational space formulation. IEEE Journal of Robotics and Automation, 3(1), 43–53.
  5. Craig, J.J. (1989). Dynamic modeling of robot manipulators. Numerous journal papers complementing textbook expositions.
Support CaaT Academy

Help keep these engineering tutorials free and growing

If these lessons, examples, and project pages help you, a small donation supports the continued creation and improvement of free control, robotics, software, and engineering education resources.

Created and maintained by Abolfazl Mohammadijoo.