ROS 2 Learning Path  ยท  Illustrated Textbook

Chapter 8: ros2_control

The translator between ROS algorithms and real or virtual motors
Prerequisite: Chapter 7 (Gazebo)
Running project: ARCHO robot
Tool: controller_manager
Reading time: 100โ€“120 minutes
What we'll cover in this chapter 8.1What Actually Turns the Motor? 8.2The Four-Layer Architecture 8.3Four Well-Known Controllers 8.4Command and State Interfaces Inside the URDF 8.5Full diff_drive_controller Configuration 8.6Simulation and the Real Robot: One Architecture, Two Hardware Layers 8.7Common ros2_control Mistakes 8.8Summary, Glossary, and Exercises

8.1What Actually Turns the Motor?

So far we've built up many layers of ARCHO: URDF/Xacro defined the robot's structure, TF2 tracked the position of its parts, RViz displayed it, and Gazebo simulated the physics. But one big question remains unanswered: when we say "move forward at 0.5 meters per second," how does that simple number actually turn into the real rotation of two motors?

๐Ÿ“– Definition: ros2_control

ros2_control is a standard framework for controlling robot hardware โ€” a layer that sits between high-level ROS 2 algorithms (like Nav2) and the robot's real or virtual hardware, and translates software commands into a language the motors understand.

๐Ÿง  Simple Analogy: The Factory Manager and the Industrial Machine

Suppose a factory manager tells a worker, "produce this product faster." But the industrial machine itself only understands commands like Motor speed = 1500 rpm or Valve = Open. Someone has to translate the high-level order into a precise machine command. Nav2 is that manager; the motor is that industrial machine; and ros2_control is the technical supervisor that translates between them.

ComparisonQuestion It Asks
Nav2 (later chapters)Where should the robot go?
MoveIt (Chapter 13)What path should the arm follow?
ros2_controlHow exactly should the joints execute that motion?
Motor DriverHow does the low-level hardware send the command over the wire?

8.2The Four-Layer Architecture

flowchart TB A["Nav2 / MoveIt / Teleop
High-level command"] --> B["Controller Manager
Chief of all controllers"] B --> C["Controllers
diff_drive_controller and similar"] C --> D["Hardware Interface
Virtual or real"] D --> E["Motors / Encoders / Sensors"] style B fill:#eef0ff,stroke:#3d4bf5,color:#211f1a,font-weight:bold style D fill:#fdf3e4,stroke:#c8862c,color:#211f1a

The Controller Manager is the heart of ros2_control: it's responsible for loading, activating, and deactivating Controllers, managing the control loop, and reading/writing to hardware on every cycle. In the previous chapter, we used the command ros2 control list_controllers to ask this very Node which Controllers were active.

8.3Four Well-Known Controllers

ControllerWorks WithTask
joint_state_broadcasterEvery JointPublishes each Joint's instantaneous state on /joint_states
diff_drive_controllerDifferential-drive robots (ARCHO)Takes /cmd_vel and converts it into separate left/right wheel speeds
joint_trajectory_controllerRobotic armsReceives a time-scheduled trajectory of joint angles and executes it smoothly
forward_command_controllerAny Joint (for testing)Sends a command almost directly to the Joint
๐ŸŒ Why joint_state_broadcaster Matters So Much

This Controller completes the loop we saw in Chapter 5: Encoders โ†’ ros2_control โ†’ /joint_states โ†’ robot_state_publisher โ†’ TF tree โ†’ RViz. Without this Controller, robot_state_publisher never finds out how far ARCHO's wheels have actually turned, and the model in RViz stays frozen even if the robot is really moving.

Each Joint is typically controlled through one of three modes:

Command TypeMeaningExample Use
PositionMove the joint to this angleRobot arm, servo motor
VelocitySpin the joint at this speedARCHO's wheels
EffortApply this amount of force/torqueForce control in industrial arms

8.4Command and State Interfaces Inside the URDF

In the previous chapter we saw the block below inside the Xacro โ€” now let's unpack exactly what each line means:

<ros2_control name="ArchoGazeboSystem" type="system">
  <hardware>
    <plugin>gz_ros2_control/GazeboSimSystem</plugin>
  </hardware>
  <joint name="left_wheel_joint">
    <command_interface name="velocity"/>
    <state_interface name="position"/>
    <state_interface name="velocity"/>
  </joint>
  <joint name="right_wheel_joint">
    <command_interface name="velocity"/>
    <state_interface name="position"/>
    <state_interface name="velocity"/>
  </joint>
</ros2_control>

This block says: we have a control system named ArchoGazeboSystem; its hardware is the Gazebo simulation plugin; and the left and right Joints each accept a velocity command (command_interface) and report their real position and velocity (state_interface).

๐Ÿ”ง Why Is This Block Inside the URDF, Not a Separate File?

Because the URDF already knows the Joint names and the robot's mechanical structure. It makes sense for the control Interface definitions to be attached to those same Joints, rather than living in a completely separate file disconnected from the physical structure.

8.5Full diff_drive_controller Configuration

The Controllers themselves are defined and configured in a separate YAML file:

# archo_bringup/config/controllers.yaml
controller_manager:
  ros__parameters:
    update_rate: 50
    joint_state_broadcaster:
      type: joint_state_broadcaster/JointStateBroadcaster
    diff_drive_controller:
      type: diff_drive_controller/DiffDriveController

diff_drive_controller:
  ros__parameters:
    left_wheel_names: [left_wheel_joint]
    right_wheel_names: [right_wheel_joint]
    wheel_separation: 0.40
    wheel_radius: 0.09
    base_frame_id: base_link
    odom_frame_id: odom
    publish_rate: 50.0
    enable_odom_tf: true
    use_stamped_vel: true
    linear.x.has_velocity_limits: true
    linear.x.max_velocity: 0.7
    linear.x.min_velocity: -0.3
    angular.z.has_velocity_limits: true
    angular.z.max_velocity: 1.5
โš ๏ธ Why wheel_separation and wheel_radius Are So Sensitive

These two numbers must match ARCHO's real dimensions exactly (the same ones we wrote into the Xacro in Chapter 4). If wheel_radius is wrong, the robot's actual speed will differ from its reported speed and Odometry will be incorrect; if wheel_separation is wrong, the rotation angle will be miscalculated and the robot will turn less or more than it should โ€” an error that gradually produces navigation drift.

diff_drive_controller estimates the robot's linear and angular motion from the two wheels' encoder data and publishes it on the /odom Topic and the odom โ†’ base_link Transform โ€” exactly the same loop we saw in Chapter 5 and will examine more deeply in Chapter 9.

8.6Simulation and the Real Robot: One Architecture, Two Hardware Layers

One of the biggest advantages of ros2_control is that the software architecture stays nearly identical between simulation and the real robot โ€” only one bottom layer changes:

flowchart LR subgraph SIM["Simulation"] C1["Controller"] --> H1["Simulated Hardware Interface"] --> G1["Gazebo Joint"] end subgraph REAL["Real Robot"] C2["Controller (same code)"] --> H2["Real Hardware Interface"] --> G2["Motor Driver / CAN"] end style C1 fill:#eef0ff,stroke:#3d4bf5 style C2 fill:#eef0ff,stroke:#3d4bf5

This means the diff_drive_controller we tested today on the simulated ARCHO โ€” the exact same Controller, with no code changes โ€” also works on the real ARCHO; only the Hardware Interface changes from gz_ros2_control/GazeboSimSystem to an actual driver (which we'll build in Chapter 18). This is precisely the power of the layered ROS 2 architecture we discussed back in Chapter 1.

8.7Common ros2_control Mistakes

MistakeConsequence
Two Controllers claiming the same InterfaceConflict, causing one activation to be rejected
Wrong encoder signThe wheel moves forward but the encoder reports a negative value โ†’ incorrect Odometry
Wrong units (RPM instead of rad/s)ROS expects Position in radians and Velocity in rad/s; a mismatch causes unexpected behavior
Wrong wheel radiusSpeed and Odometry errors
Inappropriate update rateUnstable control or sluggish response
No command timeoutIf communication drops, the motor keeps executing the last command forever โ€” dangerous in the real world
Harder Exercise

Suppose wheel_radius in the YAML was mistakenly entered as 0.07, while ARCHO's actual wheel radius is 0.09 meters. If the robot really moves 1 meter, will the Odometry report a value greater than or less than 1 meter? Write out your reasoning.

8.8Chapter 8 Summary

Now we know exactly what happens between a simple cmd_vel command and the actual rotation of ARCHO's motors: the Controller Manager, two main Controllers (joint_state_broadcaster and diff_drive_controller), and a Hardware Interface that can be either virtual or real โ€” all without the higher layers needing to know anything about it.

๐ŸŒ Connection to the Main Project

ARCHO Project now has a complete, properly tuned controllers.yaml. The cmd_vel command is no longer just a raw number โ€” it's actually converted into the rotation of two wheels and produces Odometry.

What the Next Chapter Adds

In Chapter 9, we'll combine this same /odom produced by diff_drive_controller with IMU data โ€” Sensor Fusion using robot_localization โ€” to make ARCHO's position estimate more accurate and stable.

Chapter 8 Glossary

ros2_control
The standard ROS 2 framework for translating software commands into real or virtual hardware motion.
Controller Manager
The Node that loads, activates, deactivates, and manages Controllers.
Controller
A software unit that controls a specific motion behavior (such as differential drive or joint trajectories).
Hardware Interface
The layer that connects a Controller to the real or simulated hardware.
command_interface / state_interface
Respectively: what command we send to a Joint, and what state we read from it.
diff_drive_controller
The standard Controller for differential-drive robots; converts cmd_vel into per-wheel speeds and produces Odometry.

Chapter 8 Common Mistakes โ€” Summary