All projects

3-DOF Spherical Wrist

Forward-kinematics demonstration showing the 3-DOF wrist and its coordinate frames.
Forward kinematics

Joint-angle sliders update the DH frames and resulting wrist pose.

Kinematics derivation

The mechanism is a 3-DOF spherical wrist. Now, in order to define the kinematics, we need to define the joint frames. We’ll use the Denavit-Hartenberg convention to define the frames, mainly because that’s just what I learned in school. Typically, a conventional spherical wrist has all frame origins located at the wrist center, with the exception of the end-effector. This removes a lot of complexity, especially as we tackle the Jacobian later on. Something like this:

Conventional 3-DOF spherical wrist with intersecting joint axes and coincident frame origins.
Figure 1. Conventional spherical-wrist framing.

But you know what? That’s too easy. Since the formulas are already known and sitting in my old course textbook, let’s define a new wrist with some impractical frames. It’ll serve as a good test case for our derivations:

Custom 3-DOF wrist showing frame zero at the base, coincident frames one and two at the upper joint center, and frame three displaced at the output.
Figure 2. Custom wrist framing.

Here we have the base frame at the bottom joint, frames 1 and 2 offset to the ‘center’, and then frame 3 at the end effector. The first thing we need to do is define the DH parameters for this wrist. The table below shows the parameters for each joint:

  • αi\alpha_i: angle from ki1\vect{k}_{i-1} to ki\vect{k}_i, about ii\vect{i}_i
  • did_i: distance between origins oi1\underset{\sim}{o}_{i-1} and oi\underset{\sim}{o}_i, along ki1\vect{k}_{i-1}
  • aia_i: distance between ki1\vect{k}_{i-1} and ki\vect{k}_i, along ii\vect{i}_i
  • θi\theta_i: angle from ii1\vect{i}_{i-1} to ii\vect{i}_i, about ki1\vect{k}_{i-1}

Link 0 is just the base frame.

DHθi\theta_idid_iaia_iαi\alpha_i
Link 1q1q_1l1l_10090-90^\circ
Link 2q290q_2-90^\circ000090-90^\circ
Link 3q3q_3l2l_20000^\circ

l1l_1 is the distance from the base to the center of the wrist (o0o_0 to o1o_1), and l2l_2 is the distance from the center of the wrist to the end effector (o1o_1 to o3o_3). Now, we can use these parameters to define the transformation matrices for each joint.

For a revolute joint, we have the frame given by the following equations. Here, k×k\mathord{\times} is the skew-matrix of the kk vector:

Ci=Ci1eθik×Ri,oi=oi1+Ci1eθik×δi\begin{aligned} C_i &= C_{i-1} e^{\theta_i k\mathord{\times}} R_i, \\ \underset{\sim}{o}_i &= \underset{\sim}{o}_{i-1} + C_{i-1} e^{\theta_i k\mathord{\times}}\vect{\delta}_i \end{aligned}

And for a prismatic joint, we have the frame given by:

Ci=Ci1Ri,oi=oi1+Ci1(δi+dik)\begin{aligned} C_i &= C_{i-1} R_i, \\ \underset{\sim}{o}_i &= \underset{\sim}{o}_{i-1} + C_{i-1}\left(\vect{\delta}_i+d_i k\right) \end{aligned}

Using the parameters above, we can convert the DH parameters into homogeneous transformation matrices. We will then use these matrices to determine the overall transformation from the base frame to the end-effector frame, giving us the forward kinematics of the wrist.

[Cioi0T1]=[Ci1oi10T1][eθik×Rieθik×δi0T1]i1Ti\begin{bmatrix} C_i & \underset{\sim}{o}_i \\ \vect{0}^{\,T} & 1 \end{bmatrix} = \begin{bmatrix} C_{i-1} & \underset{\sim}{o}_{i-1} \\ \vect{0}^{\,T} & 1 \end{bmatrix} \underbrace{ \begin{bmatrix} e^{\theta_i k\mathord{\times}}R_i & e^{\theta_i k\mathord{\times}}\vect{\delta}_i \\ \vect{0}^{\,T} & 1 \end{bmatrix}}_{{}^{i-1}T_i}

Or, for prismatic joints:

[Cioi0T1]=[Ci1oi10T1][Riδi+dik0T1]i1Ti\begin{bmatrix} C_i & \underset{\sim}{o}_i \\ \vect{0}^{\,T} & 1 \end{bmatrix} = \begin{bmatrix} C_{i-1} & \underset{\sim}{o}_{i-1} \\ \vect{0}^{\,T} & 1 \end{bmatrix} \underbrace{ \begin{bmatrix} R_i & \vect{\delta}_i+d_i k \\ \vect{0}^{\,T} & 1 \end{bmatrix}}_{{}^{i-1}T_i}

And so, to combine these in a general formula for the homogeneous transformations, we can write:

i1Ti=[eθik×Rieθik×(δi+dik)0T1]{}^{i-1}T_i = \begin{bmatrix} e^{\theta_i k\mathord{\times}}R_i & e^{\theta_i k\mathord{\times}}\left(\vect{\delta}_i+d_i k\right) \\ \vect{0}^{\,T} & 1 \end{bmatrix}

Now, using our DH parameters, we can write out the transformation matrices for each joint.

0T1=[eq1k×eπ2i×l1k0T1]{}^{0}T_1 = \begin{bmatrix} e^{q_1 k\mathord{\times}}e^{-\frac{\pi}{2}i\mathord{\times}} & l_1 k \\ \vect{0}^{\,T} & 1 \end{bmatrix} 1T2=[e(q2π2)k×eπ2i×00T1]{}^{1}T_2 = \begin{bmatrix} e^{\left(q_2-\frac{\pi}{2}\right)k\mathord{\times}} e^{-\frac{\pi}{2}i\mathord{\times}} & 0 \\ \vect{0}^{\,T} & 1 \end{bmatrix} 2T3=[eq3k×l2k0T1]{}^{2}T_3 = \begin{bmatrix} e^{q_3 k\mathord{\times}} & l_2 k \\ \vect{0}^{\,T} & 1 \end{bmatrix}

And there we go. We can now determine the forward kinematics of the wrist by multiplying these transformation matrices with one another.

And as a bit of an unnecessary but cleaner intermediate step, we can calculate all the transformations in base frame 0:

0T2=0T11T2,0T3=0T11T22T3=0T22T3\begin{aligned} {}^{0}T_2 &= {}^{0}T_1\,{}^{1}T_2, \\ {}^{0}T_3 &= {}^{0}T_1\,{}^{1}T_2\,{}^{2}T_3 = {}^{0}T_2\,{}^{2}T_3 \end{aligned}

And there we have it: the forward kinematics of the wrist. Now we can write the overall transformations as:

[C1o10T1]=[C0o00T1]0T1\begin{aligned} \begin{bmatrix} C_{1} & \underset{\sim}{o}_{1} \\ \vect{0}^{\,T} & 1 \end{bmatrix} &= \begin{bmatrix} C_{0} & \underset{\sim}{o}_{0} \\ \vect{0}^{\,T} & 1 \end{bmatrix} {}^{0}T_1 \end{aligned} [C2o20T1]=[C0o00T1]0T2=[C1o10T1]1T2\begin{aligned} \begin{bmatrix} C_{2} & \underset{\sim}{o}_{2} \\ \vect{0}^{\,T} & 1 \end{bmatrix} &= \begin{bmatrix} C_{0} & \underset{\sim}{o}_{0} \\ \vect{0}^{\,T} & 1 \end{bmatrix} {}^{0}T_2 \\ &= \begin{bmatrix} C_{1} & \underset{\sim}{o}_{1} \\ \vect{0}^{\,T} & 1 \end{bmatrix} {}^{1}T_2 \end{aligned} [C3o30T1]=[C0o00T1]0T3=[C2o20T1]2T3\begin{aligned} \begin{bmatrix} C_{3} & \underset{\sim}{o}_{3} \\ \vect{0}^{\,T} & 1 \end{bmatrix} &= \begin{bmatrix} C_{0} & \underset{\sim}{o}_{0} \\ \vect{0}^{\,T} & 1 \end{bmatrix} {}^{0}T_3 \\ &= \begin{bmatrix} C_{2} & \underset{\sim}{o}_{2} \\ \vect{0}^{\,T} & 1 \end{bmatrix} {}^{2}T_3 \end{aligned}

Velocity kinematics

I’ll be honest because this section was tough to revisit. I actually skipped it and jumped straight to inverse kinematics first. But, it makes the most sense to cover it right after forward kinematics, so here we go.

Angular Velocities

Back to the kinematics. Rather than looking at the whole transformation, including the frame and origin, let’s just look at the frame rotation matrices. These represent the pure orientation changes between frames.

C1=C00C1=C0Q,Q=0C1C_{1}=C_{0}\,{}^0C_1=C_{0}Q, \qquad Q={}^0C_1

Since QQ is a rotation matrix, it is orthogonal, so we can write:

QTQ=QQT=IQ^{T}Q = QQ^{T} = I

With a little bit of algebra, we can manipulate this to get:

Q˙=Q˙I=Q˙QTQ\dot{Q}=\dot{Q}I=\dot{Q}Q^TQ

Let’s define Ω=Q˙QT\Omega=\dot{Q}Q^T:

Q˙=ΩQ\dot{Q}=\Omega Q

To verify this property, let’s go back to the orthogonal identity and differentiate it:

QQT=I,Q˙QT+QQ˙T=0\begin{aligned} QQ^T&=I, \\ \dot{Q}Q^T+Q\dot{Q}^T&=0 \end{aligned}

Substituting Ω=Q˙QT\Omega=\dot{Q}Q^T yields:

Ω+ΩT=0\Omega+\Omega^T=0

or

ΩT=Ω\Omega^T=-\Omega

Thus, Ω\Omega is skew-symmetric. This matrix allows us to extract the physical angular velocity vector. In robotics, the cross-product operator \mathord{\times} converts a 3-element velocity vector into this skew-symmetric matrix form.

Relating this back to those initial formulas, we can express the angular velocity matrix as

0w1,0×=Ω=Q˙QT=0C˙1(0C1)T{}^0w_{1,0}\mathord{\times} =\Omega =\dot{Q}Q^T ={}^0\dot{C}_1\left({}^0C_1\right)^T

Which, in this example, is the angular velocity of frame 1 with respect to frame 0, expressed in frame 0.

To retrieve the actual angular velocity vector, w1,0\vect{w}_{1,0}, we just multiply the angular velocity matrix by the base frame:

w1,0=C00w1,0\vect{w}_{1,0} = \vect{C}_0\,{}^0w_{1,0}

Now that we have that, and assuming we’ve calculated the homogeneous transformations, we can move on to calculating the angular velocities of each frame.First, we grab the angular velocities with respect to the previous frame only, expressed in that local frame:

0w1,0=θ˙1k,1w2,1=θ˙2k,2w3,2=θ˙3k\begin{aligned} {}^0w_{1,0}&=\dot{\theta}_1 k, \\ {}^1w_{2,1}&=\dot{\theta}_2 k, \\ {}^2w_{3,2}&=\dot{\theta}_3 k \end{aligned}

Remember that each kk is the unit z-axis of that respective joint frame about which the rotation occurs.

It is important to remember that angular velocities simply add together, but we need to make sure they are all expressed in the exact same coordinate frame. To make them all relative to and expressed in the base frame 0, we rotate the local vectors forward:

0w2,0=0w1,0+0C11w2,10w3,0=0w2,0+0C22w3,2\begin{aligned} {}^0w_{2,0}&={}^0w_{1,0}+{}^0C_1\,{}^1w_{2,1} \\ {}^0w_{3,0}&={}^0w_{2,0}+{}^0C_2\,{}^2w_{3,2} \end{aligned}

Finally, we determine the global vectors:

w2,0=w1,0+C11w2,1w3,0=w2,0+C22w3,2\begin{aligned} \vect{w}_{2,0} = \vect{w}_{1,0} + \vect{C}_1\,{}^1w_{2,1} \\ \vect{w}_{3,0} = \vect{w}_{2,0} + \vect{C}_2\,{}^2w_{3,2} \end{aligned}

And there we have it, the final angular velocity vectors for our spherical wrist.

Translational velocities

Let’s move on to the translational velocities, or o˙n\underset{\sim}{\dot{o}}_n.

These represent the linear velocities of the origins of each frame with respect to the base frame. We can use the angular velocities we just calculated to find them. While a more direct formula is typically used for the Jacobian, deriving this from first principles helps show exactly what is happening under the hood.

We start by extracting the position vectors of the frame origins from the fourth column of our homogeneous transformation matrices:

o1=(0T1)0:3,3o2=(0T2)0:3,3o3=(0T3)0:3,3\begin{aligned} \underset{\sim}{o}_1&=\left({}^0T_1\right)_{0:3,\,3} \\ \underset{\sim}{o}_2&=\left({}^0T_2\right)_{0:3,\,3} \\ \underset{\sim}{o}_3&=\left({}^0T_3\right)_{0:3,\,3} \end{aligned}

The base origin is simply defined as o0=0\underset{\sim}{o}_0=\vect{0}.

Calculating the velocity of each individual frame origin step-by-step down the chain is the first method I learned as it provides a very intuitive approach, so we’ll do the same here. For this, we essentailly track the physical motion outwards, building the velocity profile link by link.

To find how each joint’s rotation we need the radius vectors. Here, we will define each radius as the displacement between the consecutive origins:

r1=o1o0r2=o2o1r3=o3o2\begin{aligned} \vect{r}_1&=\underset{\sim}{o}_1-\underset{\sim}{o}_0 \\ \vect{r}_2&=\underset{\sim}{o}_2-\underset{\sim}{o}_1 \\ \vect{r}_3&=\underset{\sim}{o}_3-\underset{\sim}{o}_2 \end{aligned}

We then propagate the velocity outwards from the base. Each link inherits the linear velocity of the previous origin and adds its own rotational cross-product contribution:

o˙1=o˙0+w1,0×r1o˙2=o˙1+w2,0×r2o˙3=o˙2+w3,0×r3\begin{aligned} \underset{\sim}{\dot{o}}_1&=\underset{\sim}{\dot{o}}_0 +\vect{w}_{1,0}\times\vect{r}_{1} \\ \underset{\sim}{\dot{o}}_2&=\underset{\sim}{\dot{o}}_1 +\vect{w}_{2,0}\times\vect{r}_{2} \\ \underset{\sim}{\dot{o}}_3&=\underset{\sim}{\dot{o}}_2 +\vect{w}_{3,0}\times\vect{r}_{3} \end{aligned}

As you can imagine, this link-by-link approach, calculating every intermediate step, can get a bit tedious if you only care about the end-effector.

Direct Jacobian Method: each joint’s contribution to the end-effector velocity

Rather than looking at step-by-step segments, let’s instead look directly at how each joint’s rotation impacts the end-effector origin o3\underset{\sim}{o}_{3} directly.

To do this, we change our perspective on the radius vectors. The radius vector becomes the total lever arm extending from the joint origin in question all the way to that final endpoint:

r1=o3o0r2=o3o1r3=o3o2\begin{aligned} \vect{r}_1&=\underset{\sim}{o}_3-\underset{\sim}{o}_0 \\ \vect{r}_2&=\underset{\sim}{o}_3-\underset{\sim}{o}_1 \\ \vect{r}_3&=\underset{\sim}{o}_3-\underset{\sim}{o}_2 \end{aligned}

Using these lever arms, the cross product wi,i1×0ri\vect{w}_{i,i-1}\times{}^0\vect{r}_i gives the linear velocity contribution of each individual joint to the final tip. Summing up the base velocity and the individual joint contributions yields the same total linear velocity of our end-effector origin:

o˙3=o˙0+(w1,0×r1)+(w2,1×r2)+(w3,2×r3)\begin{aligned} \underset{\sim}{\dot{o}}_3&=\underset{\sim}{\dot{o}}_0 + \left(\vect{w}_{1,0}\times \vect{r}_1\right) + \left(\vect{w}_{2,1}\times\vect{r}_2\right) + \left(\vect{w}_{3,2}\times \vect{r}_3\right) \end{aligned}

This structural relationship is exactly what lets us build the geometric manipulator Jacobian. The Jacobian maps joint velocities directly to end-effector velocities:

[o˙nwn,0]=[J1Jn]q˙=J(q)q˙\begin{aligned} \begin{bmatrix} \underset{\sim}{\dot{o}}_n \\ \vect{w}_{n,0} \end{bmatrix} &= \begin{bmatrix} \vect{J}_1 & \cdots & \vect{J}_n \end{bmatrix} \vect{\dot{q}} \\ &=J(\vect{q})\vect{\dot{q}} \end{aligned}

By separating out the individual (q˙i\dot{q}_{i}) terms from our velocity equations, we reveal the standard column formula for a revolute joint:

Ji=[ki1×(onoi1)ki1]\vect{J}_i = \begin{bmatrix} \vect{k}_{i-1}\times \left(\underset{\sim}{o}_n-\underset{\sim}{o}_{i-1}\right) \\ \vect{k}_{i-1} \end{bmatrix}

I know we didn’t really cover it, but for a prismatic joint, there is no angular velocity, only linear velocity along the direction of the joint. So, the Jacobian column for a prismatic joint is simply:

Ji=[ki10]\vect{J}_i = \begin{bmatrix} \vect{k}_{i-1} \\ \vect{0} \end{bmatrix}

Inverse kinematics

Since we went with our own custom wrist frames, the inverse kinematics will need to be derived manually. For a more conventional spherical wrist with all the frames meeting perfectly at the center, the inverse kinematics is well known and can be found in many textbooks. Our inverse kinematics formulas are still quite similar, but let’s go ahead and derive them from scratch.

Our inverse kinematics are still similar, but again, let’s go ahead and derive them.

To start, suppose we know C3C_{3} and o3\underset{\sim }{o}{}_{3}, which represent our target end-effector frame orientation and position. We want to find the corresponding joint angles θ1\theta _{1}, θ2\theta _{2}, and θ3\theta _{3} to achieve this target. We can begin by looking at our forward-kinematics equations and doing some algebra to isolate the joint angles, keeping in mind the custom frames we defined and tracking which vectors line up.

C3=eθ1k0×eθ2k1×eθ3k2×C0=eθ1k0×eθ2j0×eθ3i0×C0=C0eθ1k×eθ2j×eθ3i×=eθ1k0×C0eθ2j×eθ3i×\begin{aligned} C_3 &=e^{\theta_1\vect{k}_0\mathord{\times}} e^{\theta_2\vect{k}_1\mathord{\times}} e^{\theta_3\vect{k}_2\mathord{\times}} \vect{C}_0 \\ &=e^{\theta_1\vect{k}_0\mathord{\times}} e^{\theta_2\vect{j}_0\mathord{\times}} e^{\theta_3\vect{i}_0\mathord{\times}} \vect{C}_0 \\ &= \vect{C}_0 e^{\theta_1 k\mathord{\times}} e^{\theta_2 j\mathord{\times}} e^{\theta_3 i\mathord{\times}} \\ &=e^{\theta_1\vect{k}_0\mathord{\times}} \vect{C}_0 e^{\theta_2 j\mathord{\times}} e^{\theta_3 i\mathord{\times}} \end{aligned}

we post-multiply both sides (multiply on the right) by eθ3i×e^{-\theta_3 i\mathord{\times}}. This corresponds to a rotation of θ3-\theta _{3} about the i3\vect{i}_3 axis and cleanly cancels out that last rotation term.

C3eθ3i×=eθ1k3×C3=eθ1k0×C0eθ2j×\begin{aligned} \vect{C}_3e^{-\theta_3 i\mathord{\times}} &=e^{\theta_1\vect{k}_3\mathord{\times}}\vect{C}_3 \\ &=e^{\theta_1\vect{k}_0\mathord{\times}}\vect{C}_0 e^{\theta_2 j\mathord{\times}} \end{aligned}

Now we’re on to something, but we still have too many variables. We need to remove at least one of them. For θ2\theta _{2}, the rotation happens entirely about the jj axis. If we isolate only the jj component of our matrix equation, the θ2\theta _{2} term drops out because a frame rotating around its own jj axis leaves that axis unchanged.

eθ3k3×C3j=eθ1k0×C0j,eθ2j×j=Ie^{-\theta_3\vect{k}_3\mathord{\times}}C_3j =e^{\theta_1\vect{k}_0\mathord{\times}}C_0j, \qquad e^{\theta_2 j\mathord{\times}}j=I

By looking at this shared relationship from both the base side and the end-effector side, we can break the main equation into two separate vector conditions.

eθ1k0×j0=k1,eθ3k3×j3=k1.\begin{aligned} e^{\theta_1\vect{k}_0\mathord{\times}}\vect{j}_0 &=\vect{k}_1, \\ e^{-\theta_3\vect{k}_3\mathord{\times}}\vect{j}_3 &=-\vect{k}_1. \end{aligned}

These equations do not bring both vectors onto the exact same target vector. In a conventional wrist they would, but our custom layout changes things. The first equation rotates j0\vect{j}_0 onto k1\vect{k}_1, while the second rotates j3\vect{j}_3 onto k1-\vect{k}_1. This opposite sign in the second equation comes directly from our DH frame definitions where at θ3=0\theta_3=0 we have j3=j2=k1\vect{j}_3=\vect{j}_2=-\vect{k}_1.

Even though the two target vectors end up with opposite signs, both equations still depend on that exact same intermediate joint-axis direction, k1\vect{k}_1. From our frame geometry, k1\vect{k}_1 is perpendicular to both k0\vect{k}_0 and k3\vect{k}_3. Therefore, as long as we are in a nonsingular pose, we can calculate k1\vect{k}_1 directly.

k1=±k0×k3k0×k3.\vect{k}_1 = \pm \frac{\vect{k}_0\times\vect{k}_3} {\left\lVert\vect{k}_0\times\vect{k}_3\right\rVert}.

The two possible signs give us the two standard Euler-angle branches of the inverse-kinematics solution. We will just carry this ±\pm choice through the math for now and return to what it physically means after we finish solving for the three joint angles.

k1=±k0×k3k0×k3\vect{k}_1 =\pm\frac{\vect{k}_0\times\vect{k}_3} {\left\lVert\vect{k}_0\times\vect{k}_3\right\rVert}

Now let’s look closely at how those three frames relate geometrically.

For clarity, we can draw these direction vectors with a common tail even though their coordinate-frame origins do not actually sit at the exact same point in space. This visual shift does not alter our vector math. Since ini_{n}, jnj_{n}, and knk_{n} are free unit direction vectors expressed in frame 0, their dot products, cross products, sums, and differences remain completely independent of where we choose to sketch them. The physical joint axes themselves still intersect at the wrist center where O1=O2O_1=O_2.

eθ1k0×j0=k1e^{\theta_1\vect{k}_0\mathord{\times}}\vect{j}_0=\vect{k}_1

This equation shows that rotating the direction vector j0\vect{j}_0 about k0\vect{k}_0 by θ1\theta_1 produces k1\vect{k}_1. It does not mean that the physical base frame is being rotated. Rather, the rotation operator is being applied to a copy of the known direction vector j0\vect{j}_0.

Two-part theta-one vector construction showing \(j_0\) rotating toward \(k_1\) about \(k_0\), followed by the sum-and-difference triangle used for the half-angle relationship.
Figure 3. Vector construction for θ₁.

Now we have our constructed triangle with our three vectors. The trick is to translate the vector k1j0\vect{k}_1-\vect{j}_0 to form two right triangles:

θ12=arctan(12(k1j0)12(k1+j0))θ1=2arctan(k1j0k1+j0)\begin{aligned} \frac{|\theta_1|}{2} &=\arctan\left( \frac{\left\lVert\frac{1}{2}(\vect{k}_1-\vect{j}_0)\right\rVert} {\left\lVert\frac{1}{2}(\vect{k}_1+\vect{j}_0)\right\rVert} \right) \\ |\theta_1| &= 2\arctan\left( \frac{\left\lVert\vect{k}_1-\vect{j}_0\right\rVert} {\left\lVert\vect{k}_1+\vect{j}_0\right\rVert} \right) \end{aligned}

j0\vect{j}_0 is known from the base frame, k3\vect{k}_3 is known from the end-effector frame, and k1\vect{k}_1 \vect{k}_1 comes directly from our cross-product formula.

To determine the sign of θ1\theta_1, we need to check whether k1\vect{k}_1 lies on the positive or negative side of our rotation axis. To do this, recall from figure 3 that positive right-handed rotation about +k0+k_0 carries j0j_0 toward i0-i_0. Therefore, a positive θ1\theta_1 gives k1k_1 a negative i0i_0 component.

sign(θ1)=sign(k1Ti0)\begin{aligned} \operatorname{sign}(\theta_1) % &=-\operatorname{sign}\left( % \vect{k}_1\cdot(\vect{j}_0\times\vect{k}_0) % \right) \\\ &=-\operatorname{sign}\left(\vect{k}_1^T\vect{i}_0\right) \end{aligned}

We can use this exact same triangle trick to solve for θ3\theta _{3}, but we have to keep our custom negative sign in mind. At θ3=0\theta_3=0, our DH definitions state that j3=j2=k1j_3=j_2=-k_1. Since frame 3 is built by rotating frame 2 about their common axis k2=k3k_2=k_3, we can set up the rotation tracking.

j3=eθ3k3×(k1),j_3=e^{\theta_3 k_{3} \times}(-k_1),

Or, as we found earlier:

eθ3k3×j3=k1e^{-\theta_3\vect{k}_3\mathord{\times}}\vect{j}_3=-\vect{k}_1

k1\vect{k}_1, j3\vect{j}_3, and k3\vect{k}_3 are all known.

Since k1\vect{k}_1, j3\vect{j}_3, and k3\vect{k}_3 are all known quantities, we can solve the triangle directly.

eθ3k3×j3=k1,θ3=2arctan(j3(k1)j3+(k1))=2arctan(j3+k1j3k1),sign(θ3)=sign((k1)Ti3)\begin{aligned} e^{-\theta_3\vect{k}_3\mathord{\times}}\vect{j}_3&=-\vect{k}_1, \\ \left|\theta_3\right| &=2\arctan\left( \frac{\left\lVert\vect{j}_3-(-\vect{k}_1)\right\rVert} {\left\lVert\vect{j}_3+(-\vect{k}_1)\right\rVert} \right) \\ &=2\arctan\left( \frac{\left\lVert\vect{j}_3+\vect{k}_1\right\rVert} {\left\lVert\vect{j}_3-\vect{k}_1\right\rVert} \right), \\ \operatorname{sign}(\theta_3) &=\operatorname{sign}\left((-\vect{k}_1)^T\vect{i}_3\right) \end{aligned}

For the sign, again we are essentially checking whether k1-\vect{k}_1 lies on the positive or negative side of the rotation axis k3\vect{k}_3. A positive θ3\theta_3 rotates k1-\vect{k}_1 toward i3\vect{i}_3. Therefore, to determine the sign we check the projection of k1-\vect{k}_1 onto i3\vect{i}_3.

Revisiting θ2\theta_2, we can use a similar approach and the same tricks. But instead of deriving the full the algebraic formula and equivalent vectors, at this point we can just look at it:

j1=k0i1=j1×k1eθ2k1×i1=k2=k3\begin{aligned} \vect{j}_1 = -\vect{k}_0 \\ \vect{i}_1 = \vect{j}_1 \times \vect{k}_1 \\ e^{\theta_2\vect{k}_1\mathord{\times}}\vect{i}_1 =\vect{k}_2 =\vect{k}_3 \end{aligned}

And again, we have:

θ2=2arctan(k3i1k3+i1),sign(θ2)=sign(k3Tk0)\begin{aligned} \left|\theta_2\right| &=2\arctan\left( \frac{\left\lVert\vect{k}_3-\vect{i}_1\right\rVert} {\left\lVert\vect{k}_3+\vect{i}_1\right\rVert} \right), \\ \operatorname{sign}(\theta_2) &=-\operatorname{sign}\left(\vect{k}_3^T\vect{k}_0\right) \end{aligned}

And there we have it: θ1\theta_1, θ2\theta_2, and θ3\theta_3 solved for one nonsingular IK branch. There is still one important detail hiding in our first equation for k1\vect{k}_1: the ±\pm means that there are two possible angle solutions for the same end-effector pose.

We can see where those two solutions come from by going back to the cross product:

k0×k3=cos(θ2)k1\vect{k}_0\times\vect{k}_3 = \cos(\theta_2)\vect{k}_1

If we take the positive normalized cross product, we are choosing the branch where cos(θ2)>0\cos(\theta_2)>0, or equivalently

π2<θ2<π2.-\frac{\pi}{2}<\theta_2<\frac{\pi}{2}.

This is the branch returned by our Python implementation. Taking the negative direction for k1\vect{k}_1 gives the other Euler-angle solution, which uses different joint angles but still produces the same end-effector pose. In a more complete solver, we could choose between the two using joint limits, collision checks, or trajectory optimization. That is a bit beyond what we need here though, so we simply use the positive normalized cross product.

Finally, if k0×k3=0\vect{k}_0\times\vect{k}_3=0, we cannot normalize the cross product at all. This is the wrist’s singular configuration: k1\vect{k}_1 is no longer uniquely determined, which also means that θ1\theta_1 and θ3\theta_3 cannot be recovered individually.

Wrist singularities

Certain configurations create singularities in this wrist, which we can see directly using the Jacobian matrix. A singularity does not mean that the wrist is completely stuck. It means that two Jacobian columns have become linearly dependent, meaning two separate joints are producing the same instantaneous direction of motion. As a result, the wrist temporarily loses one independent degree of freedom (the Jacobian loses rank).

For the angular part of our Jacobian, each column represents one joint axis expressed in the base frame 0.

Jω(q)=[0k00k10k2],0w3,0=Jω(q)q˙.J_\omega(\vect{q}) = \begin{bmatrix} {}^0\vect{k}_0 & {}^0\vect{k}_1 & {}^0\vect{k}_2 \end{bmatrix}, \qquad {}^0w_{3,0}=J_\omega(\vect{q})\vect{\dot{q}}.

To see where the determinant comes from, we can write each of those axes in frame 0 coordinates.

0k0=[001],0k1=[sinθ1cosθ10],0k2=[cosθ1cosθ2sinθ1cosθ2sinθ2].\begin{aligned} {}^0\vect{k}_0 &=\begin{bmatrix}0\\0\\1\end{bmatrix}, \\ {}^0\vect{k}_1 &=\begin{bmatrix}-\sin\theta_1\\\cos\theta_1\\0\end{bmatrix}, \\ {}^0\vect{k}_2 &=\begin{bmatrix} \cos\theta_1\cos\theta_2\\ \sin\theta_1\cos\theta_2\\ -\sin\theta_2 \end{bmatrix}. \end{aligned}

Placing those three vectors into the columns of our angular Jacobian, JωJ_\omega, gives:

Jω=[0sinθ1cosθ1cosθ20cosθ1sinθ1cosθ210sinθ2].J_\omega = \begin{bmatrix} 0 & -\sin\theta_1 & \cos\theta_1\cos\theta_2 \\ 0 & \cos\theta_1 & \sin\theta_1\cos\theta_2 \\ 1 & 0 & -\sin\theta_2 \end{bmatrix}.

Because the first column contains only one nonzero entry, expanding the determinant along that column keeps the algebra fairly simple.

det(Jω)=sinθ1cosθ1cosθ2cosθ1sinθ1cosθ2=sin2θ1cosθ2cos2θ1cosθ2=(sin2θ1+cos2θ1)cosθ2=cosθ2.\begin{aligned} \det\left(J_\omega\right) &= \begin{vmatrix} -\sin\theta_1 & \cos\theta_1\cos\theta_2 \\ \cos\theta_1 & \sin\theta_1\cos\theta_2 \end{vmatrix} \\ &=-\sin^2\theta_1\cos\theta_2 -\cos^2\theta_1\cos\theta_2 \\ &=-\left(\sin^2\theta_1+\cos^2\theta_1\right)\cos\theta_2 \\ &=-\cos\theta_2. \end{aligned}

For our custom wrist definitions, the final determinant expression works out to:

det(Jω)=cosθ2.\det\left(J_\omega\right)=-\cos\theta_2.

Therefore, the wrist hits a singularity for the condition:

θ2=±90+n180,nZ.\theta_2=\pm90^\circ + n180^\circ, \quad n\in\mathbb{Z}.

At these specific configurations, k2\vect{k}_2 aligns parallel or antiparallel to k0\vect{k}_0. In other words, joints 1 and 3 are rotating about the exact same physical line in space, meaning their velocity contributions are no longer independent. The Jacobian drops from rank 3 to rank 2. The wrist can still rotate in two independent directions, but it cannot instantaneously produce an angular velocity in every possible direction.

This also explains the failure case in the inverse kinematics derivation. We calculated k1\vect{k}_1 using k0×k3\vect{k}_0\times\vect{k}_3, but at the singularity, k0\vect{k}_0 and k3\vect{k}_3 become parallel, causing that cross product to collapse to zero. The individual values of θ1\theta _{1} and θ3\theta _{3} are no longer unique because only their combined, cumulative effect is defined.

One last interesting note is that near the singularity, although the Jacobian has not quite lost rank, it becomes poorly conditioned. This means that a small requested change in end-effector velocity might demand very large or conflicting joint speeds. In a real-life implementation, these speed spikes may stress the motors and mechanical joints, causing premature wear or failure. And so, these poorly conditioned zones need to be actively avoided just as much as the singularity itself.