raw robotics

Given a desired foot position \(\mathbf{P}=(P_x, P_y, P_z)\) in the robot’s body frame and link lengths \(L_1\) (coxa), \(L_2\) (femur), and \(L_3\) (tibia), we solve for the three joint angles coxa yaw \(\theta_1\), femur pitch \(\theta_2\), and tibia pitch \(\theta_3\) using a singularity-free, computationally efficient formulation for real-time microcontroller performance. All angles are measured in radians and follow the right-hand rule, taken positive when rotating counter-clockwise about their respective joint axes.

\(\Delta z\)\(\Delta y\)\(\|\mathbf{v}\|\)\(\alpha\)\(\beta\)\(\beta\)\(\theta_2\)\(\theta_3\)\(\mathbf{P}_3\)\({L_2}\)\({L_3}\)\(z\)\(y\)\(L_1\)

Definitions & Default Pose

We introduce the following notation for the 2D hip-pitch projection and the leg’s neutral configuration:

1. Computing \(\theta_1\)

Steer the coxa to align with the projection of \(\mathbf P\) onto the ground plane:

\[ \theta_1 = \operatorname{tan}^{-1}\left(\frac{P_x}{P_y}\right). \]

2. Computing the 2D Projection

To decouple the yaw rotation, we rotate the target point \(\mathbf{P}=(P_x,P_y,P_z)\) about the global \(z\)-axis by \(-\theta_1\):

\[ \mathbf{P}_3 \;=\; R_z(-\theta_1)\,\mathbf{P} = \begin{pmatrix} \cos\theta_1 & \sin\theta_1 & 0 \\ -\sin\theta_1 & \cos\theta_1 & 0 \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix}P_x\\P_y\\P_z\end{pmatrix} = \begin{pmatrix} 0\\ \sqrt{P_x^2 + P_y^2}\\ P_z \end{pmatrix}. \]

From which we can find the vector \(\mathbf{v} = \mathbf{P}_3 - \mathbf{P}_1\) with its length \(\|\mathbf{v}\|\) and planar offsets \(\Delta y = \sqrt{P_x^2+P_y^2}-L_1\) and \(\Delta z=-P_z\).

3. Computing \(\alpha\)

From the right triangle formed by \(\Delta y\) and \(\Delta y\), three equivalent forms arise from basic definitions of \(\sin,\cos,\tan\):

\[\alpha = \operatorname{sin}^{-1}{\left(\frac{\Delta y}{\|\mathbf{v}\|} \right)} =\operatorname{cos}^{-1}{\left(\frac{\Delta z}{\|\mathbf{v}\|} \right)} =\operatorname{tan}^{-1}{\left(\frac{\Delta y}{\Delta z} \right)}\]

4. Computing \(\beta+\theta_2\)

Apply Law of Cosines in triangle \((\mathbf{P}_3, \mathbf{P}_1,\mathbf{P}_2)\):

\[ L_3^2 = L_2^2 + \|\mathbf{v}\|^2 - 2\,L_2\,\|\mathbf{v}\|\,\cos(\beta+\theta_2) \quad\Longrightarrow\quad \cos(\beta+\theta_2) = \frac{L_2^2- L_3^2 + \|\mathbf{v}\|^2 }{2\,L_2\,\|\mathbf{v}\|} \]

and define the intermediate constant:

\[ C = L_2^2 - L_3^2 + \|\mathbf{v}\|^2. \]

From the identity:

\[ \sin^2(\beta+\theta_2) + \cos^2(\beta+\theta_2) = 1, \]

and substituting the above for \(\cos\) yields

\[ \sin^2(\beta+\theta_2) = 1 - \frac{C^2}{4\,L_2^2\,\|\mathbf{v}\|^2} = \frac{4\,L_2^2\,\|\mathbf{v}\|^2 - C^2}{4\,L_2^2\,\|\mathbf{v}\|^2}. \]

Define the discriminant:

\[ \Delta = 4\,L_2^2\,\|\mathbf{v}\|^2 - C^2, \quad S = \sqrt{\Delta}. \]

Hence

\[ \sin(\beta+\theta_2) = \frac{S}{2\,L_2\,\|\mathbf{v}\|}. \]

Recover the combined angle with one arctan:

\[ \beta+\theta_2 = \operatorname{tan}^{-1}\left(\frac{S}{C}\right). \]

5. Computing \(\theta_2\)

From \(\alpha+\beta=90^\circ\) follows that

\[\begin{array}{rl} \theta_2 &= (\alpha+\beta+\theta_2) - (\alpha+\beta)\\ &= \alpha + \left( \beta+\theta_2\right) - \frac{\pi}{2}\\ &= \operatorname{tan}^{-1}{\left(\frac{\Delta y}{\Delta z} \right)} + \operatorname{tan}^{-1}\left(\frac{S}{C}\right) - \frac{\pi}{2}\\ &= \operatorname{tan}^{-1}\left(\frac{\Delta y\,S - \Delta z\,C}{\Delta y\,C + \Delta z\,S}\right) \end{array} \]

6. Computing \(\theta_3\)

Again Law of Cosines for triangle \((\mathbf{P}_1,\mathbf{P}_2, \mathbf{P}_3)\):

\[ \|\mathbf{v}\|^2 = L_2^2 + L_3^2 - 2\,L_2\,L_3\,\cos\theta_3. \]

Thus

\[\begin{array}{rl} \theta_3&=\operatorname{cos}^{-1}{\left(\frac{L_{2}^{2} + L_{3}^{2} - \|\mathbf{v}\|^{2}}{2 L_{2} L_3} \right).}\\ \end{array} \]

Coxa Yaw
Femur Pitch
Tibia Pitch

C++ Implementation

The goal of the whole derivation is to have a minimum number of square roots and trig functions for fast execution on Atmegas like Arduino.

#define L1 30.0f  // Coxa length (mm)
#define L2 100.0f // Femur length (mm)
#define L3 150.0f // Tibia length (mm)

// Compute leg joint angles: coxa yaw (theta1), femur pitch (theta2), tibia pitch (theta3)
void calcLegIK(float Px, float Py, float Pz, float *theta1, float *theta2, float *theta3) {

    // 1) Yaw about Z
    *theta1 = -atan2f(Px, Py);

    // 2) Project into hip-pitch plane
    float dy = sqrtf(Px * Px + Py * Py) - L1;
    float dz = Pz;

    float v2 = dy * dy + dz * dz;

    // 3) Inner workspace: too close => knee fully bent
    if (v2 < (L3 - L2) * (L3 - L2)) {
        *theta2 = M_PI + atan2f(dz, dy);
        *theta3 = 0; // folded
        return;
    }

    // 4) Outer workspace: too far => leg fully extended
    if (v2 > (L2 + L3) * (L2 + L3)) {
        *theta2 = atan2f(dz, dy);
        *theta3 = M_PI;
        return;
    }

    // 5) Calculate discriminant
    float C = L2 * L2 - L3 * L3 + v2;
    float disc = 4 * L2 * L2 * v2 - C * C;
    float S = disc > 0 ? sqrtf(disc) : 0;

    // 6) Femur pitch
    *theta2 = atan2f(dy * S + dz * C, dy * C - dz * S);

    // 7) Tibia pitch
    *theta3 = acosf((L2 * L2 + L3 * L3 - v2) / (2 * L2 * L3));
}