Differential Drive Tutorial#
This tutorial demonstrates how to use DifferentialDriveController
to drive a Jetbot with interactive speed and angular rate sliders.
By the end of this tutorial, you’ll understand:
How to configure
DifferentialDriveControllerwith wheel geometryWhy commands are interpreted at the
control_pointsite, not the robot rootHow to encode a forward-speed and yaw-rate command as a site setpoint
How to apply joint velocity targets from the controller output
Prerequisites
Review the Controllers and the RobotState tutorial to understand
the BaseController interface and RobotState.
To follow along, run:
./python.sh standalone_examples/api/isaacsim.robot_motion.controllers/differential_drive.py
Every code snippet below is taken from that script.
The script opens Isaac Sim with a Jetbot on a flat stage and two slider windows — one for linear speed [m/s] and one for angular rate [rad/s]. Drag the sliders to adjust the robot’s speed and turning rate in real time.
How It Works#
Differential drive kinematics converts a desired linear speed \(v\) and yaw rate \(\omega\) into per-wheel angular velocities:
where \(b\) is the wheel-base (centre-to-centre distance) and \(r\) is the wheel
radius. DifferentialDriveController performs this calculation on
every call to forward().
The Control Point#
The control_point site sits midway between the two wheels. Commands are
interpreted in its frame: forward speed along +X, yaw rate about +Z.#
Every command you give the controller is interpreted at the control point — the point midway between the two wheels — not at the robot’s root prim. The two are not assumed to coincide: a root prim is wherever the USD author placed it, often the centre of mass or a chassis origin that sits behind, above, or off-centre from the wheel axle.
This is why the setpoint is expressed as a named site in
setpoint_state.sites rather than on the root. Asking for 0.5 m/s means
0.5 m/s of the point between the wheels, which is what the kinematics above
actually solve for. Had the same command been interpreted at an offset root,
any yaw component would smear a spurious lateral term into the wheel speeds.
The site name defaults to "control_point" and is configurable via the
control_point_name constructor argument. The frame axes are configurable
too — forward_direction defaults to +X and rotation_direction to +Z, as
drawn above.
Setting Up the Controller#
Construct the controller once before the simulation loop, providing the robot’s full joint space and the wheel-specific geometry:
controller = ctrl.DifferentialDriveController(
robot_joint_space=robot.dof_names, # every joint, not just the wheels
left_wheel_joint=LEFT_WHEEL_JOINT,
right_wheel_joint=RIGHT_WHEEL_JOINT,
wheel_radius=WHEEL_RADIUS, # metres
wheel_base=WHEEL_BASE, # metres, centre-to-centre
max_linear_speed=MAX_SPEED, # optional clamp [m/s]
max_angular_speed=MAX_YAW_RATE, # optional clamp [rad/s]
)
robot_joint_space should list every joint in the articulation, not just the two
wheel joints. It is the shared vocabulary for talking about this robot: the
controller finds its wheel joints in that list, and tags every value it outputs
with the joint name it belongs to.
Listing the whole articulation is what lets controllers work together. Give two
controllers the same robot_joint_space and their outputs are expressed in the
same terms, so CombinedController can run them side by side — this one
driving the wheels while another drives an arm mounted on top — and merge the
results without any manual index bookkeeping.
This controller is stateless, so reset() always succeeds immediately — but
call it once before the loop anyway, since other controllers do need it:
controller.reset(mg.RobotState(), mg.RobotState(), 0.0)
Creating a Setpoint#
Encode the desired linear speed and yaw rate as linear and angular
velocities on the "control_point" site:
# Both linear and angular velocities are required on the control point site.
setpoint = mg.RobotState(
sites=mg.SpatialState.from_name(
spatial_space=["control_point"],
linear_velocities=(["control_point"], wp.array([[v, 0.0, 0.0]], dtype=wp.float32)),
angular_velocities=(["control_point"], wp.array([[0.0, 0.0, omega]], dtype=wp.float32)),
)
)
The controller projects these onto its forward_direction (default +X)
and rotation_direction (default +Z) axes; only the components along
those axes affect the output. Both linear_velocities and
angular_velocities must be present on the site — the controller returns
None if either is missing.
Running the Controller#
In each physics step, call forward() and apply the joint velocity targets.
Resolve the controller’s output joint names against the articulation:
desired_state = controller.forward(mg.RobotState(), setpoint, 0.0)
if desired_state is not None and desired_state.joints is not None:
if desired_state.joints.velocities is not None:
robot.set_dof_velocity_targets(
desired_state.joints.velocities,
dof_indices=robot.get_dof_indices(desired_state.joints.velocity_names),
)
The output carries velocity targets only for the two wheel joints, named in
desired_state.joints.velocity_names. Always take the indices from those
names: the values follow the controller’s own ordering, which need not match
anything you passed in. (velocity_indices also exists, but it indexes the
robot_joint_space you gave at construction, which matches the articulation’s
DOF ordering only when that list came from robot.dof_names.) The ordering is
fixed, so you can cache the lookup after the first step.
Jetbot steered with speed and yaw-rate sliders.#
Note
The Jetbot model wheels cannot rotate at arbitrary speeds (it models a real robot). Therefore, if modifying the maximum forward and rotational speeds in the python examples, keep in mind that the wheels may saturate.
Summary#
This tutorial demonstrated:
Controller setup: Providing wheel geometry and joint names at construction
Setpoint encoding: Wrapping linear and angular velocities in a site
RobotStateControl loop: Calling
forward()every step and applying joint velocity targets
Next Steps#
Ackermann tutorial — four-wheeled steering with front or rear steer layouts
Holonomic tutorial — mecanum and omni-wheel robots with full lateral mobility
Controller composition — chaining and combining controllers for complex behaviors