Robotics Software · ROS 2 · Motion Planning

SO-101 Robot Arm with ROS 2 and MoveIt 2

ROS 2 Jazzy MoveIt 2 ros2_control C++ CMake URDF / xacro RViz Feetech ST3215 Linux

Recorded in RViz. MoveIt 2 plans a path to the goal pose set with the interactive marker, then executes it.

RobotSO-101 (LeRobot) arm, 5 arm joints plus gripper
Actuators6 Feetech ST3215 serial bus servos, 12-bit encoders (4096 ticks/rev)
ConnectionUSB serial bus adapter on the Linux host
MiddlewareROS 2 Jazzy, ros2_control, controller_manager at 100 Hz
Motion planningMoveIt 2 (move_group), RViz MotionPlanning panel
Hardware interfaceCustom C++ SystemInterface plugin, servo driver linked in-process
ModesReal hardware or mock hardware, switched with one launch argument
AutomationC++ startup node using MoveGroupInterface to run named poses on launch

Overview

The goal was simple to state: when I plan and execute a motion in RViz, the physical arm should follow. The SO-101 had a URDF and a servo driver, but nothing that connected MoveIt 2 to the real servos. I wrote that layer as a ros2_control hardware plugin and brought the arm up end to end.

  • Wrote a hardware_interface::SystemInterface plugin that reads and writes all six ST3215 servos directly, with no bridge node in between.
  • Calibrated every joint by hand: measured the zero offset and rotation direction of each servo and stored them as ros2_control parameters.
  • Built one launch file that starts MoveIt 2, RViz and the controllers in either real or mock mode from a single mode:= argument.
  • Added a C++ node that uses MoveGroupInterface to move through two named poses automatically after launch.
  • Confirmed the result on hardware: the physical arm moves to the pose planned in RViz.

Real-world sync

The physical arm follows the motion planned in RViz on the laptop beside it.

Engineering problems solved

  • Linking a static driver into a plugin. The servo driver was a static library, so the shared plugin failed to link. I built it as position-independent code and exported it as a proper CMake target for the hardware package.
  • Launch arguments being dropped. The standard MoveIt demo launch rebuilds its own config internally, so the real/mock setting never reached the robot description. I replaced it with a launch file that builds every component from one shared MoveIt config.
  • xacro expression parsing. A real/mock condition failed because xacro's $(eval ...) stops at the first closing bracket. I traced this in the xacro source and switched to the ${...} form.
  • Kinematic limits of a 5-joint arm. Five joints cannot reach every position and orientation, so some marker directions found no exact IK solution. I diagnosed this and enabled approximate IK for interactive goal setting.
  • Start-state failures on power-up. An arm that sagged while unpowered could start outside its joint limits. I traced this to how the servos behave when torque is enabled, not to the code, and made re-homing near zero part of the bring-up routine.

Next steps

  • Start the automatic pose sequence from a controller-ready event instead of a fixed delay.
  • Add a startup safety check that refuses to enable torque near a joint limit.
  • Profile the per-cycle serial reads and writes against the 100 Hz control budget.
  • Exercise the gripper on hardware and tune servo speed and acceleration.