let's start with two fundamental ways of describing a robot:
- Joint space
- Task space
Joint Space
When a robot's current configuration is represented in terms of its joint positions, we call it joint space.
Consider a simple 2-link planar robot.
Suppose:
qβ = 5Β°
qβ = 45Β°
Then its joint configuration can be represented as:
q = [5Β°, 45Β°]
Simple.
Now consider something larger, like a UR10, which has six degrees of freedom.
Its joint configuration can be represented as:
q = [qβ, qβ, qβ, qβ, qβ
, qβ]
Each value tells us the position of one joint.
So, in joint space, we describe the robot by asking:
What are the current positions of my joints?
Task Space
There is another way to describe the same robot.
Instead of caring about every individual joint, suppose I only care about the robot's end effector.
Where is it?
How is it oriented?
For a robot operating in 3D space, we can represent the end-effector pose as:
X = [x, y, z, Ο, ΞΈ, Ο]
Here:
x, y, z β Position
Ο, ΞΈ, Ο β Orientation
This is called task space or Cartesian space.
So now we have two different ways of looking at the same robot:
JOINT SPACE TASK SPACE
[qβ, qβ, ... qβ] [x, y, z, orientation]
Robot joints End effector
This distinction is extremely important because a lot of robot manipulation is essentially about moving between these two representations.
Forward Kinematics
Suppose I know all the joint angles of my robot.
The question is:
Where is my end effector?
That's exactly what Forward Kinematics (FK) answers.
We can write this relationship as:
x = f(q)
In simple terms:
Joint angles ββββββ FK ββββββ> End-effector pose
q x
Give forward kinematics the robot's joint configuration, and it calculates where the end effector is.
For example:
q = [qβ, qβ, qβ, qβ, qβ
, qβ]
β
Forward Kinematics
β
x = [x, y, z, Ο, ΞΈ, Ο]
Inverse Kinematics
Now let's reverse the problem.
Suppose I know where I want the robot's end effector to be.
The question becomes:
What should my joint angles be to reach that pose?
That's the Inverse Kinematics (IK) problem.
Conceptually:
q = fβ»ΒΉ(x)
So:
End-effector pose ββββββ IK ββββββ> Joint angles
x q
There is an important detail here.
Writing:
q = fβ»ΒΉ(x)
is useful for understanding the idea, but inverse kinematics is not always a simple mathematical inverse.
For a particular end-effector pose, a robot may have:
- one solution
- multiple solutions
- infinitely many solutions
- or no solution at all
But conceptually, IK answers:
Where should my joints be if I want my end effector here?
Great. We Know FK and IK.
But there's a problem.
My robot still doesn't move.
BORING.
We know where the robot is.
We know what joint angles can produce a particular pose.
But how do we actually describe motion?
How fast should each joint move?
And if I move my joints at certain velocities, how will my end effector move?
That's where things start getting interesting.
Differential Kinematics
Let's go back to our forward kinematics equation:
x = f(q)
This tells us that the end-effector pose x depends on the robot's joint configuration q.
But now we're interested in how these quantities change with time.
So let's differentiate the forward kinematics equation with respect to time.
Conceptually:
x = f(q)
β differentiate with respect to time
αΊ = J(q) qΜ
And suddenly, we get one of the most important equations in robot manipulation:
ββββββββββββββββββββ
β β
β αΊ = J(q) qΜ β
β β
ββββββββββββββββββββ
Let's understand what each term means.
q β Joint positions
qΜ β Joint velocities
x β End-effector pose
αΊ β End-effector velocity
J(q) β Jacobian
So this equation:
αΊ = J(q) qΜ
answers a very practical question:
If I move my joints at these velocities, how will my end effector move?
This is the key idea behind differential kinematics.
But Where Did This Jacobian Come From?
This was one of the things that initially confused me.
The Jacobian can look like some mysterious matrix that suddenly appears in a robotics textbook.
It isn't.
Let's derive it.
And for that, we'll use one of the simplest robots possible.
A 2-Link Planar Manipulator
Consider a robot with two links:
β End Effector
/
/ lβ
/
β qβ
/
/ lβ
/
β qβ
|
Base
The robot has two joints:
q = [qβ, qβ]
and two link lengths:
lβ
lβ
Because the robot moves in a 2D plane, its end-effector position can be represented using:
x = horizontal position
y = vertical position
Using basic trigonometry, we can calculate the end-effector position.
Forward Kinematics of the 2R Robot
The x-coordinate is:
x = lβ cos(qβ) + lβ cos(qβ + qβ)
The y-coordinate is:
y = lβ sin(qβ) + lβ sin(qβ + qβ)
That's our forward kinematics.
Joint angles go in:
[qβ, qβ]
and the end-effector position comes out:
[x, y]
So far, nothing unusual.
Now comes the interesting part.
What Happens When the Joints Move?
Suppose both joints start moving.
Their angular velocities are:
qΜβ
qΜβ
Because the joints are moving, the end effector also moves.
Its Cartesian velocity becomes:
αΊ
αΊ
So we want to find the relationship:
[qΜβ, qΜβ] ββββββββββ> [αΊ, αΊ]
Let's differentiate our forward kinematics equations.
We had:
x = lβ cos(qβ) + lβ cos(qβ + qβ)
and:
y = lβ sin(qβ) + lβ sin(qβ + qβ)
When we differentiate these equations with respect to time, the result can be arranged as:
β αΊ β β βx/βqβ βx/βqβ β β qΜβ β
β β = β β β β
β αΊ β β βy/βqβ βy/βqβ β β qΜβ β
Look carefully at the matrix in the middle.
β βx/βqβ βx/βqβ β
J(q) = β β
β βy/βqβ βy/βqβ β
That's the Jacobian.
Nothing magical happened. We simply differentiated the forward kinematics with respect to the joint variables.
But let's look at what this matrix is actually telling us.
Consider the first column:
β βx/βqβ β
β β
β βy/βqβ β
It tells us how the end-effector position in x and y changes when joint qβ changes, while the other joint is held fixed.
Similarly, the second column:
β βx/βqβ β
β β
β βy/βqβ β
tells us how qβ influences the end-effector position.
So each column of the Jacobian represents the influence of one joint on the motion of the end effector.
For our 2-link planar robot, we are only interested in motion along x and y, so the Jacobian is a 2 Γ 2 matrix:
Joints
qβ qβ
β β
x direction β β βx/βqβ βx/βqβ β
y direction β β βy/βqβ βy/βqβ β
β β
Now let's generalize this idea.
For a robot with n joints, the joint velocity vector is:
qΜ = [qΜβ, qΜβ, ... , qΜβ]
A robot moving freely in 3D space can have an end-effector velocity consisting of:
Linear velocity: [αΊ, αΊ, ΕΌ]
Angular velocity: [Οβ, Οα΅§, Ο_z]
So the full geometric Jacobian has the form:
n joints
βββββββββββββββββββββββββ
β β
Linear β Jα΅₯(q) β 3 rows
β β
βββββββββββββββββββββββββ€
β β
Angular β JΟ(q) β 3 rows
β β
βββββββββββββββββββββββββ
6 Γ n
Therefore, for an n-DOF robot, the full Jacobian is generally a:
6 Γ n matrix
The n columns correspond to the robot's joints.
The first three rows describe how those joints contribute to the end effector's linear velocity:
[αΊ, αΊ, ΕΌ]
The last three rows describe how they contribute to its angular velocity:
[Οβ, Οα΅§, Ο_z]
So we can think of the full Jacobian as:
qΜβ qΜβ qΜβ ... qΜβ
β β β β
βββββββββββββββββββββββββββ
αΊ β β β
αΊ β β Linear part β
ΕΌ β β Jα΅₯ β
βββββββββββββββββββββββββββ€
Οβ β β β
Οα΅§ β β Angular part β
Ο_z β β JΟ β
βββββββββββββββββββββββββββ
In this article, however, we'll keep things simple.
We are mainly interested in how the joints influence the position of the end effector:
x, y, z
So we'll focus on the linear part of the Jacobian and leave orientation and angular velocity for later.
With that intuition in place, let's actually calculate the Jacobian for our 2-link robot.
Let's Actually Calculate It
From:
x = lβ cos(qβ) + lβ cos(qβ + qβ)
we get:
βx/βqβ = -lβ sin(qβ) - lβ sin(qβ + qβ)
βx/βqβ = -lβ sin(qβ + qβ)
And from:
y = lβ sin(qβ) + lβ sin(qβ + qβ)
we get:
βy/βqβ = lβ cos(qβ) + lβ cos(qβ + qβ)
βy/βqβ = lβ cos(qβ + qβ)
Putting everything into the matrix:
β -lβsin(qβ)-lβsin(qβ+qβ) -lβsin(qβ+qβ) β
J(q) = β β
β lβcos(qβ)+lβcos(qβ+qβ) lβcos(qβ+qβ) β
And that's our Jacobian.
Now our velocity relationship becomes:
β αΊ β β qΜβ β
β β = J(q) β β
β αΊ β β qΜβ β
or simply:
αΊ = J(q)qΜ
This equation now has a very intuitive meaning:
Give me the current robot configuration and its joint velocities, and the Jacobian tells me how the end effector is moving.
Enough With the Equations. Let's Make the Robot Move.
We cannot learn swimming by learning about water.
Let's just dive in.
So far, we've talked about joint space, task space, forward kinematics, and the Jacobian.
Now let's actually use them on a robot.
For this experiment, I'm using:
- ROS 2 Jazzy
- RViz for visualization
- UR10 as the robot
- MPU6050 as a gesture sensor
- Raspberry Pi 4 to read and publish the sensor data
The setup looks roughly like this:
Hand Gesture
β
βΌ
MPU6050
β
β Roll / Pitch
βΌ
Raspberry Pi 4
β
β ROS 2
βΌ
Host Machine
β
βΌ
Differential
Kinematics
β
β Joint velocities
βΌ
UR10
β
βΌ
RViz
The MPU6050 isn't really necessary.
I just had one lying around for a long time and finally wanted to put it to use. π
You could achieve exactly the same thing using four keyboard keys:
W β +X
S β -X
A β +Y
D β -Y
The sensor just makes the experiment more interactive.
Converting Gestures Into Cartesian Velocity
For simplicity, I'm only controlling the end effector in the X-Y plane.
I map:
Roll β Velocity along X
Pitch β Velocity along Y
I'm not trying to make the velocity proportional to the exact tilt angle yet.
Instead, once the tilt crosses a threshold, I command a small constant Cartesian velocity.
Conceptually:
Tilt forward β +αΊ
Tilt backward β -αΊ
Tilt left β +αΊ
Tilt right β -αΊ
So after reading the MPU6050, I have something like:
αΊ = 0.05 m/s
αΊ = 0.00 m/s
or:
αΊ = 0.00 m/s
αΊ = -0.05 m/s
Great.
Now I know how I want the end effector to move.
But the UR10 doesn't accept:
Move your end effector at +0.05 m/s in X
as a joint command.
The robot ultimately needs to know:
How should each joint move?
And that brings us straight back to our differential kinematics equation.
Back to the Jacobian
We previously derived:
αΊ = J(q)qΜ
Remember what this equation told us:
Joint velocities βββββ J(q) βββββ> End-effector velocity
qΜ αΊ
But now we have the opposite problem.
From the MPU6050, we already know the desired end-effector velocity:
αΊ
What we need are the corresponding joint velocities:
qΜ
In other words, we want:
End-effector velocity βββββ ??? βββββ> Joint velocities
αΊ qΜ
Starting from:
αΊ = J(q)qΜ
our goal is to solve for:
qΜ
For a simple square and invertible Jacobian, we might be tempted to write:
qΜ = Jβ»ΒΉ(q)αΊ
But with a real robot like the UR10, things aren't always that simple.
Remember, the UR10 has six joints.
And in our experiment, we're currently asking it to perform only a small part of its possible task-space motion:
Desired motion:
[αΊ, αΊ]
while we have:
Joint velocities:
[qΜβ, qΜβ, qΜβ, qΜβ, qΜβ
, qΜβ]
So how do we go from:
[αΊ, αΊ]
to:
[qΜβ, qΜβ, qΜβ, qΜβ, qΜβ
, qΜβ]
?
This is where the Jacobian pseudoinverse enters the picture.
JβΊ(q)
[αΊ, αΊ] βββββββββββββββββββ> [qΜβ, qΜβ, qΜβ, qΜβ, qΜβ
, qΜβ]
Desired Cartesian Required
velocity joint velocities
And now we're finally ready to write some code.
Instead, we need a pseudoinverse.
In my implementation, I'm using a slightly more robust version called the Damped Least-Squares pseudoinverse.
The equation is:
qΜ = Jα΅ (J Jα΅ + λ²I)β»ΒΉ v
where:
J β 3 Γ 6 position Jacobian
Jα΅ β 6 Γ 3
v β desired Cartesian velocity [αΊ, αΊ, ΕΌ]
Ξ» β damping factor
I β 3 Γ 3 identity matrix
qΜ β resulting 6 joint velocities
And this is what it looks like in Eigen:
const Eigen::Matrix3d regularized =
xyz_jacobian * xyz_jacobian.transpose()
+ damping_squared_ * Eigen::Matrix3d::Identity();
const Eigen::Matrix<double, 6, 1> joint_velocity =
xyz_jacobian.transpose()
* regularized.ldlt().solve(cartesian_velocity);
Let's map the code directly to the equation.
xyz_jacobian
β
J
xyz_jacobian.transpose()
β
Jα΅
xyz_jacobian * xyz_jacobian.transpose()
β
JJα΅
damping_squared_ * Identity()
β
λ²I
cartesian_velocity
β
v
So these few lines are essentially implementing:
qΜ = Jα΅ (JJα΅ + λ²I)β»ΒΉ v
That's it.
We Have Joint Velocities. Now What?
At this point, our damped least-squares solution gives us:
qΜ = [qΜβ, qΜβ, qΜβ, qΜβ, qΜβ
, qΜβ]
But remember what these values represent.
They are velocities.
They tell us how fast each joint should move, not where the joints should move to.
What we actually need for the next control step is a small change in joint position:
Ξq
In other words:
Given these joint velocities, how much should each joint move during this control cycle?
That's straightforward.
Velocity is change in position over time:
qΜ = Ξq / Ξt
Therefore:
Ξq = qΜ Ξt
where:
qΜ β Joint velocity
Ξt β Time between two control updates
Ξq β Change in joint position
Once we know Ξq, we can update the current joint configuration:
q_new = q_current + Ξq
or:
q_new = q_current + qΜ Ξt
For all six joints:
qβ_new = qβ_current + qΜβ Ξt
qβ_new = qβ_current + qΜβ Ξt
qβ_new = qβ_current + qΜβ Ξt
qβ_new = qβ_current + qΜβ Ξt
qβ
_new = qβ
_current + qΜβ
Ξt
qβ_new = qβ_current + qΜβ Ξt
We calculate this repeatedly inside the control loop.
So the robot doesn't make one giant movement.
Instead, it makes many tiny movements:
Current q
β
βΌ
Calculate J(q)
β
βΌ
Calculate qΜ
β
βΌ
Ξq = qΜ Ξt
β
βΌ
q_new = q + Ξq
β
βΌ
Robot moves slightly
β
ββββββββββββββββΊ Repeat
This is essentially numerical integration.
We're integrating joint velocity over time to obtain joint position.
Now that we can actually update the robot's joints, let's try something interesting.
Experiment 1: Control Only X
Let's start with the simplest possible task.
I only care about the end effector's velocity along the X direction.
Desired:
αΊ = 0.05 m/s
That's it.
I'm not asking the controller to maintain Y.
I'm not asking the controller to maintain Z.
So our task only contains:
[αΊ]
This distinction is important.
It does not mean:
αΊ = 0.05
αΊ = 0
ΕΌ = 0
Instead, it means:
X β Controlled
Y β Unconstrained
Z β Unconstrained
The controller's job is simply:
Find joint velocities that produce the requested X velocity.
What happens in Y and Z is not part of the task.
Now let's see what the UR10 actually does.
Result
Watch the end effector carefully.
We asked it to move along X, but we never told it what to do in Y or Z.
So those directions are free to change as a consequence of the joint motion.
This is our first glimpse of what an unconstrained direction actually means.
Experiment 2: Control X and Y
Now let's add another requirement.
Instead of controlling only X, let's control both X and Y.
Our task-space velocity becomes:
β αΊ β
β β
β αΊ β
Now:
X β Controlled
Y β Controlled
Z β Unconstrained
This changes our Jacobian too.
Instead of using only the row corresponding to X:
Jx
we now use:
β Jx β
J = β β
β Jy β
Our solver must now find joint velocities that satisfy both requested Cartesian velocities.
X constraint
+
Y constraint
β
βΌ
Jacobian
β
βΌ
Joint velocities
β
βΌ
UR10
But Z is still not part of the task.
We haven't said:
ΕΌ = 0
We simply haven't constrained Z at all.
Let's see how that changes the robot's motion.
Result
Compare this with the previous experiment.
The solver now has fewer freedoms because it has another Cartesian requirement to satisfy.
But the Z direction is still free.
Experiment 3: Now Constrain Z
Finally, let's add Z to our task.
But here's the interesting part:
I don't actually want the robot to move in Z.
So I command:
ΕΌ = 0
Our complete desired linear velocity is now:
β αΊ β
v = β αΊ β
β 0 β
Now:
X β Controlled
Y β Controlled
Z β Controlled at zero velocity
This is fundamentally different from simply leaving Z out of the task.
Previously:
Z β Do whatever is necessary
Now:
Z β Do NOT move
The Jacobian used by our controller now contains all three linear velocity rows:
β Jx β
β β
J = β Jy β
β β
β Jz β
and our desired Cartesian velocity is:
β αΊ β
v = β αΊ β
β 0 β
The damped least-squares solver must therefore find joint velocities that produce the requested X and Y motion while simultaneously trying to keep the Z velocity at zero.
Result
Now compare all three experiments:
Experiment 1
X β Controlled
Y β Unconstrained
Z β Unconstrained
Experiment 2
X β Controlled
Y β Controlled
Z β Unconstrained
Experiment 3
X β Controlled
Y β Controlled
Z β Controlled at 0 m/s
And this demonstrates an important idea:
Not controlling a direction is not the same as commanding zero velocity in that direction.
If Z isn't included in our task, the solver is free to produce motion in Z.
If Z is included with:
ΕΌ = 0
we are explicitly asking the solver to maintain zero velocity along Z.
The more task-space directions we constrain, the fewer freedoms remain for the robot to use elsewhere.
And that observation leads us toward something much more interesting:
What can the robot do with the freedoms that remain?
That's where the null space starts to become useful.

















