This repo is my object-oriented MATLAB toolbox for simulating a serial manipulator, including kinematics, dynamics, and a few controllers. A 6-DoF robot is used as the running example, but the classes are written to be reusable.
Run one of the scripts:
- main.m: joint-space dynamic simulation.
- circle_trajectory.m: task-space circle tracking
- force_motion_circle.m: constrained dynamics + force-motion control (end-effector constrained against the "floor").
The following shows the manipulator in action in different cases.
- Length is in mm.
- Time is in s.
- Default gravity is
$g_0 = 9810 mm/s^2$ in Manipulator.
This is a consistent mm–s system. If you set link masses in kg (as in the examples), the implied force unit becomes kg·mm/s^2, i.e. milli-Newtons. That’s why you’ll see things like F_des = 2000 being treated as 2N. Same story for torques (they end up in a scaled mm-based unit).
If you change units, change them everywhere.
The kinematics are based on the Denavit–Hartenberg (DH) convention.
The homogeneous transform from frame 0 to frame 1 is
where
For the 6DoF robot that's being used in the codes, the DH frames are shown here:
And the parameters here:
In the code:
Manipulator.dh()builds a single DH transform.Manipulator.fk(frameIdx)multiplies transforms up toframeIdx.
The geometric Jacobian maps joint angular (or linear) velocities to end-effector linear and angular velocity:
where
In the code:
-
jacobianOmega(frameIdx)returns$J_\omega$ . -
jacobianLinear(frameIdx)returns$J_v$ . -
jacobian(frameIdx)returns $J = \begin{bmatrix}J_v \ J_\omega\end{bmatrix}$.
For dynamics, each link needs a Jacobian at its center of mass (COM). The code builds COM Jacobians and stacks them into one generalized Jacobian (all COMs).
The basic COM mapping used in the code is
where
In the code:
jacobianCOM(linkIdx)returns one link’s COM Jacobian.jacobianCOM_all()stacks all COM Jacobians.jacobianCOM_dot(dt)computes a numerical derivative (finite difference).
The dynamics of the robot are as follows:
In the code:
-
massMatrix()builds$M$ . -
coriolisMatrix()builds the stacked$S$ . -
gravityVector()builds$g$ . -
inertiaMatrix()returns$D(q)$ . -
coriolisCentrifugalMatrix(dt)returns$C(q,\dot q)$ (numerical$\dot J$ ). -
gravityTorque()returns$G(q)$ .
For constrained simulation, the environment provides an environment Jacobian
Since $\begin{bmatrix}v\\omega\end{bmatrix} = J\dot q$, the velocity constraint is
Differentiating gives an acceleration-level constraint:
The constrained equations of motion are solved via an augmented system with
In the code:
-
setEnvironmentJacobian(J_e)sets the constraint. -
environmentJacobianDot(dt)computes$\dot J_e$ numerically. -
constrainedDynamics(tau, dt)returns$(\ddot q, \lambda, F)$ .
The reported contact force is computed as
All controllers are in the Controller class and are selected via controller.type.
Just outputs zero torque:
Useful for sanity-checking the passive dynamics.
Cancels gravity in joint space:
Joint-space PD to a fixed reference:
Adds gravity cancellation to PD:
This is the Slotine controller for manipulators.
Define
Then
In code, Lambda and K are the gain matrices.
This controller is meant to be used with Simulation.mode = 'constrained'.
The implemented law is
-
$F_d\in\mathbb{R}^{6\times1}$ is a desired end-effector force
Controller.setSaturation(uMax, uMin) clamps joint torques. You can pass scalars or per-joint vectors.
Below is a description of the codes.
Manipulator class
- DH:
a,d,alpha,jointType,n. - State:
q,qdot. - History:
q_his,qdot_his,qddot_his,u_his. - Base:
baseT. - Visualization:
visual,graphics. - COM/dynamics:
comOffset,mass,inertia,g0. - Numerical derivatives:
J_prev(shared internal cache). - Trajectory IK cache:
R_d_prev,O_d_prev. - Constraints:
J_e,J_e_prev,lambda_his,F_his.
dh(theta, d, a, alpha)dhTransform(i)fk(frameIdx)jacobianOmega(frameIdx)jacobianLinear(frameIdx)jacobian(frameIdx)
jacobianCOM(linkIdx)jacobianCOM_all()jacobianCOM_dot(dt)massMatrix()coriolisMatrix()gravityVector()inertiaMatrix()inertiaMatrixDot(dt)coriolisCentrifugalMatrix(dt)gravityTorque()
setEnvironmentJacobian(J_e)environmentJacobianDot(dt)constrainedDynamics(tau, dt)
inverseKinematics(R_d, O_d, ...): velocity-level IK (damped least squares). It can use provided derivatives or approximate them numerically.ik(T_desired, q_init, max_iter, tol): position-level IK using iterative damped least squares. It computes an orientation error vialogm().
skew(w),unskew(S)updatePosition(d, v, dt),updateRotation(R, omega, dt),updateTransform(T, v, omega, dt)integrateJointVelocities(dt)checkInertiaMatrixPD(),checkSkewSymmetry(dt)
The drawing code is in the same class. The main entry points are:
draw(ax)updateGraphics()
Everything else under that (frames, labels, end-effector styles) is a helper.
Controller class
type,robot,dt- Desired signals:
qdes,qdotdes,qddotdes - PD gains:
Kp,Kd - Slotine gains:
Lambda,K - Force-motion:
F_des - Saturation:
useSaturation,uMax,uMin
Controller(robot, type, dt, ...): constructor. Arguments depend ontype.uNext(): computes the torque command based ontype.setSaturation(uMax, uMin)
Note: Robust/Adaptive controllers wil be implemented later, but are not ready at the moment.
Simulation class
- Objects:
robots,controllers - Time:
time,dt,controlTime - UI:
fig,ax,controls,mode - Trajectory tracking:
trajectoryFunc,useTrajectory,R_prev,O_prev - Histories:
qdes_his,qdotdes_his,O_his,Odes_his,F_des_his
run(...)creates UI/figure (unless headless).- Timers drive the dynamic and velocity modes.
dynamicsTimerCallback()is the core integration loop.
The UI is mode-based. The main modes you’ll run into are:
-
manual: sliders for joint positions. -
velocity: sliders for joint velocities. -
dynamic: integrates$\ddot{q}$ using the standard dynamics. -
task-space: dynamic integration plusupdateDesiredFromTrajectory(). -
constrained: usesManipulator.constrainedDynamics()instead of unconstrained dynamics.
plotResults()and helpers:plotVariable,plotVariableWithRef,plotEndEffector,plotCircularTrajectory2D.saveResults(filename)andsaveToExcel(filename, results).- Static helpers:
loadAndPlot(...),animateFromData(results),plotVariableStatic(...).
Joint-space dynamic simulation with a selectable controller.
Task-space circle tracking. The key parts are:
sim.mode = 'task-space'sim.trajectoryFunc = @(t) ...sim.useTrajectory = true
Constrained circle on the “floor” with force regulation. It demonstrates:
- Initializing the robot on the constraint manifold (solve IK at
$t=0$ ). - Setting an environment Jacobian (e.g., constrain end-effector
$v_z$ ). - Running constrained dynamics + force-motion control.
- If you constrain motion, initialize on the constraint. Otherwise the solver will produce very large multipliers
$\lambda$ to fight the initial constraint violation. -
Manipulator.ik()useslogm()for orientation error. For a 180° rotation you may get a MATLAB warning about the principal logarithm. It’s usually harmless if the IK converges. - The augmented constrained matrix can become ill-conditioned near singular Jacobians. If you hit that, the first fix is almost always better initialization + less aggressive gains.
- Add complete support for multiple robots (the infrastructure is partly there).
- Add more controllers (robust/adaptive, impedance, etc.).




