Differential Drive Tutorial#

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 DifferentialDriveController with wheel geometry

  • Why commands are interpreted at the control_point site, not the robot root

  • How 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:

\[\omega_L = \frac{2v - \omega \cdot b}{2r}, \quad \omega_R = \frac{2v + \omega \cdot b}{2r}\]

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#

Differential drive control point, midway between the wheels

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.

../_images/isim_6.0_full_tut_external_differential_drive.webp

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:

  1. Controller setup: Providing wheel geometry and joint names at construction

  2. Setpoint encoding: Wrapping linear and angular velocities in a site RobotState

  3. Control loop: Calling forward() every step and applying joint velocity targets

Next Steps#

Back to Gallery View