Reinforcement learning control deployment from Isaac Lab (DreamWaQ) to RobStride RS00 actuators via ROS 2.
The cpp branch executes low-level actuator communication through a native Linux SocketCAN driver node (robstride_can_node) running under a real-time SCHED_FIFO priority 80 thread. The node operates deterministically at 200 Hz, coordinating 12 RobStride RS00 Quasi-Direct Drive (QDD) actuators with sub-millisecond transmission latency.
- System Architecture
- C++ Driver Modules
- RL Policy Observation Space (45-D x 5 history)
- Joint Index Remapping (Isaac Lab vs. Hardware)
- Actuator CAN Bus and Node ID Mapping
- Actuator Parameters and PD Impedance Gains
- Teleoperation Interface
- Hardware Bringup Procedure
- Diagnostic and Calibration Utilities
- Safety Systems and Failsafes
- MuJoCo Sim-to-Sim Validation
- Kinematic and Reward Formulation Notes
[ HIGH-LEVEL: RL Policy ]
│ Model: TorchScript JIT (`policy.pt`) trained in Isaac Lab 3.0 (DreamWaQ)
│ Rate: 50 Hz (dt = 0.02 s) | Input: 5x45 Observation History | Output: 12-D Target Δq
▼
[ MID-LEVEL: ROS 2 Controller Node (`scripts/nxp_jaguar_controller.py`) ]
│ • Subscriptions: IMU (`/Imu_data`), Joy/Teleop (`/joy`, `/cmd_vel`), Joint States (`/robot_joint_states`)
│ • Finite State Machine: STANDBY ──(Btn A)──> STANDUP ──(Btn B)──> WALK ──(Btn X)──> E-STOP
│ • Remapping: Isaac Order (Roll->Hip->Knee) ⇄ ROS Hardware Order (BL->BR->FL->FR)
│ • Target Position: q_des = q_nominal + 0.25 * action
│ • Publication: `/joint_command` (std_msgs/Float64MultiArray)
▼
[ LOW-LEVEL: Real-Time C++ CAN Node (`src/robstride_can_node.cpp`) ]
│ • Dual SocketCAN Threads (`can0` and `can1`) via `robstride_can_bus.hpp`
│ • Real-Time Scheduler: Linux `SCHED_FIFO` (Priority 80)
│ • Protocol: RobStride RS00 bit-packed frames (`robstride_protocol.hpp`)
│ • Deterministic Loop Rate: 200 Hz (dt = 0.005 s)
│ • Feedback Publication: `/robot_joint_states` (sensor_msgs/JointState)
▼
[ HARDWARE: 12x RobStride RS00 Actuators and Hiwonder 9-DOF IMU ]
C++ source headers reside in include/jaguar_control/ and executable implementations in src/:
| File | Primary Interface | Description |
|---|---|---|
robstride_protocol.hpp |
RobStrideProtocol |
Encodes floating-point targets ( |
robstride_can_bus.hpp |
RobStrideCANBus |
Manages non-blocking POSIX SocketCAN raw sockets (AF_CAN, SOCK_RAW) with hardware frame filters. |
robstride_hardware_manager.hpp |
RobStrideHardwareManager |
Coordinates dual CAN channels (can0 right 6 motors, can1 left 6 motors), generates quintic startup trajectories, interpolates setpoints, and compensates encoder zero offsets. |
robstride_can_node.cpp |
RobStrideCANNode |
ROS 2 wrapper thread running under SCHED_FIFO at 200 Hz, bridging ROS topics to the low-level hardware manager. |
The actor policy ingests a 5-step history of 45-dimensional observations at 50 Hz:
| Index Range | State Variable | Dimension | Unit | Description |
|---|---|---|---|---|
[0 : 3] |
base_ang_vel |
3 | Base angular velocity in body frame |
|
[3 : 6] |
projected_gravity |
3 | unit | Projected gravity vector |
[6 : 9] |
velocity_commands |
3 | Commanded planar velocity |
|
[9 : 21] |
joint_pos_rel |
12 | Joint position relative to nominal: |
|
[21 : 33] |
joint_vel |
12 | Joint angular velocity |
|
[33 : 45] |
actions |
12 | Previous policy output |
- Hardware CAN Driver: Grouped leg-by-leg (
BL,BR,FL,FR). - Isaac Lab Policy: Grouped joint-by-joint (Rolls, Hips, Knees).
[Hardware CAN Driver Order (C++ / ROS)] [Isaac Lab Policy Order]
Grouped by LEG (BL, BR, FL, FR) Grouped by JOINT TYPE (Rolls, Hips, Knees)
─────────────────────────────────────── ──────────────────────────────────────────
0: BL_collar_joint 0: Fr_roll_joint (Front Right Roll)
1: BL_hip_joint 1: Fl_roll_joint (Front Left Roll)
2: BL_knee_joint 2: Br_roll_joint (Back Right Roll)
3: BR_collar_joint 3: Bl_roll_joint (Back Left Roll)
4: BR_hip_joint 4: Fr_hip_pitch (Front Right Hip)
5: BR_knee_joint 5: Fl_hip_pitch (Front Left Hip)
6: FL_collar_joint 6: Br_hip_pitch (Back Right Hip)
7: FL_hip_joint 7: Bl_hip_pitch (Back Left Hip)
8: FL_knee_joint 8: Fr_knee_joint (Front Right Knee)
9: FR_collar_joint 9: Fl_knee_joint (Front Left Knee)
10: FR_hip_joint 10: Br_knee_joint (Back Right Knee)
11: FR_knee_joint 11: Bl_knee_joint (Back Left Knee)
Permutation arrays in scripts/nxp_jaguar_controller.py:
ROS_TO_ISAAC = [9, 6, 3, 0, 10, 7, 4, 1, 11, 8, 5, 2]
ISAAC_TO_ROS = [3, 7, 11, 2, 6, 10, 1, 5, 9, 0, 4, 8]| Isaac Index | Isaac Joint Name | Nominal Angle ( |
ROS Index | ROS Joint Name (parameters.py) |
CAN ID | Bus | Quadrant |
|---|---|---|---|---|---|---|---|
| 0 | Fr_roll_joint |
9 | FR_collar_joint |
1 | can0 |
Front Right | |
| 1 | Fl_roll_joint |
6 | FL_collar_joint |
1 | can1 |
Front Left | |
| 2 | Br_roll_joint |
3 | BR_collar_joint |
4 | can0 |
Rear Right | |
| 3 | Bl_roll_joint |
0 | BL_collar_joint |
4 | can1 |
Rear Left | |
| 4 | Fr_hip_pitch_joint |
10 | FR_hip_joint |
2 | can0 |
Front Right | |
| 5 | Fl_hip_pitch_joint |
7 | FL_hip_joint |
2 | can1 |
Front Left | |
| 6 | Br_hip_pitch_joint |
4 | BR_hip_joint |
5 | can0 |
Rear Right | |
| 7 | Bl_hip_pitch_joint |
1 | BL_hip_joint |
5 | can1 |
Rear Left | |
| 8 | Fr_knee_joint |
11 | FR_knee_joint |
3 | can0 |
Front Right | |
| 9 | Fl_knee_joint |
8 | FL_knee_joint |
3 | can1 |
Front Left | |
| 10 | Br_knee_joint |
5 | BR_knee_joint |
6 | can0 |
Rear Right | |
| 11 | Bl_knee_joint |
2 | BL_knee_joint |
6 | can1 |
Rear Left |
-
Policy Evaluation Rate: 50 Hz (
$\Delta t = 0.02\text{ s}$ ) -
Low-Level Hardware Rate: 200 Hz (
$\Delta t = 0.005\text{ s}$ ) -
Real-Time Scheduling: Linux
SCHED_FIFO, Priority 80 -
Action Scaling: 0.25 (
$q_{\text{des}} = q_0 + 0.25 \times a_{\text{policy}}$ ) -
Joint Stiffness (
$K_p$ ): 25.0 N m/rad -
Joint Damping (
$K_d$ ): 1.5 N m s/rad - Actuator Torque Limit: 17.0 N m
Run the keyboard teleoperation node:
ros2 run jaguar_control keyboard_teleop.py| Key | Mode / Command | Action |
|---|---|---|
1 |
STANDBY |
Drives all joints to zero position ( |
2 |
STANDUP |
Executes a 2-second quintic S-curve trajectory to nominal stance angles |
3 |
WALK |
Activates 50 Hz neural network policy inference. |
W |
Surge Forward ( |
Increments forward velocity by |
S |
Surge Backward ( |
Increments backward velocity by |
A |
Sway Left ( |
Increments lateral velocity by |
D |
Sway Right ( |
Increments lateral velocity by |
Q |
Yaw Left ( |
Increments counterclockwise yaw by |
E |
Yaw Right ( |
Increments clockwise yaw by |
X |
Brake | Sets planar command velocities to zero while keeping policy active. |
SPACE |
Emergency Stop | Cuts motor commands and commands transition to STANDBY. |
-
A/X(Cross): Stance transition (STANDUP) -
B/Circle: Locomotion mode (WALK) -
X/Square: Passive rest (STANDBY) - Left Analog Stick (Vertical / Horizontal): Planar linear velocity (
$v_x, v_y$ ) - Right Analog Stick (Horizontal): Yaw rate (
$\omega_z$ )
Important
- Suspend the chassis on a gantry until all four feet clear the floor before powering actuators.
- Connect the 24V supply and verify the hardware emergency stop switch.
cd /home/erc/sim2real
colcon build --packages-select jaguar_control --symlink-install
source install/setup.bashLaunch nodes in separate terminal sessions in order:
cd /home/erc/sim2real
sudo ./scripts/bringup_canbus.shcd /home/erc/sim2real
./scripts/bringup_imu.shcd /home/erc/sim2real
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run jaguar_control robstride_can_nodecd /home/erc/sim2real
source /opt/ros/humble/setup.bash
python3 scripts/check_states.pycd /home/erc/sim2real
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run jaguar_control nxp_jaguar_controller.pycd /home/erc/sim2real
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run jaguar_control keyboard_teleop.pyExecution sequence:
- Press 2: Robot transitions to nominal stance (
STANDUP). - Press 3: Robot enables RL locomotion policy (
WALK). - Steer with W / S / A / D / Q / E.
- Press 1 to sit down (
STANDBY), or SPACE for Emergency Stop.
# 1. Bring up CAN interfaces:
sudo /home/erc/sim2real/scripts/bringup_canbus.sh
# 2. Launch full stack:
source /opt/ros/humble/setup.bash
source /home/erc/sim2real/install/setup.bash
ros2 launch jaguar_control sim2real.launch.pyThe launch file starts the teleop hub on the robot PC for direct Xbox input. For interactive keyboard control, disable the launch-managed instance and run the hub in a separate robot-PC terminal:
ros2 launch jaguar_control sim2real.launch.py with_teleop:=false
# In another terminal on the robot PC:
ros2 run jaguar_control keyboard_teleop.pyThe hub reads /dev/input/js* directly, so the separate joy_node remains
disabled by default. Velocity uses /cmd_vel; mode-button pulses use /joy.
Note
See ZERO_CALIBRATION_GUIDE.md for encoder offset calibration on the 12 actuators.
- CAN ID Discovery:
python3 scripts/scan_robostride_ids.py
- Mechanical Zero Calibration:
Place legs in the Relax Pose, then run:
python3 scripts/set_robostride_zero.py
- Sit-Stand Trajectory Validation:
python3 scripts/test_sit_stand.py
- Passive Joint Encoder Readout:
python3 scripts/check_joints.py
-
Orientation Watchdog (Tilt Protection): If projected gravity
$g_z > -0.5$ (tilt angle$> 60^\circ$ ), the controller disables locomotion and returns toSTANDBY. - Communication Watchdog: If IMU or CAN communication delays exceed 0.1 s, the node shuts off motor torques.
-
Thermal Protection: Actuator diagnostic routines issue warnings when motor temperature exceeds
$75^\circ\text{C}$ .
Validate the RL locomotion policy in MuJoCo:
python3 sim2sim/sim2sim_mujoco.py --terrain flat
python3 sim2sim/sim2sim_mujoco.py --terrain roughKeyboard mapping in MuJoCo:
1: Standby (zero angles)2: Stand Up (quintic trajectory)3: Walk (RL policy active)W / S / A / D / Q / E: Planar velocity commands
The URDF tibia_link origin frame resides at the proximal knee joint (
The distal contact point is computed from the lowest mesh vertex in Fr_tibia_pitch.STL:
The reward function foot_clearance_dreamwaq maps this offset to the world frame using the tibia link rotation matrix $\mathbf{R}{\text{tibia}}(q)$:
$$\mathbf{p}{\text{foot_tip, world}} = \mathbf{p}{\text{tibia, world}} + \mathbf{R}{\text{tibia}}(\mathbf{p}_{\text{tip_local}})$$
This formulation enforces physical foot clearance during the swing phase.
When the chassis collapses to the floor (
The reward base_height_l2_safe calculates true vertical clearance between the base origin and the ground plane (
When planar velocity commands drop below threshold (stand_still reward penalizes joint deviations from nominal stance Hips = -1.55 rad, Knees = 1.35 rad, Rolls = 0.0 rad). This prevents standing drift and limit-cycle oscillations while stationary.
Previously, NXP Jaguar modeled foot ground interaction using the convex-hull approximation of the entire tibia CAD mesh (physics:approximation = "convexHull"). In high-frequency physics engines (PhysX), polygonal mesh contacts suffer from:
- Discontinuous Normal Vectors & Force Spikes: As the shank rotates during stance, the contact point abruptly jumps between mesh facets and vertices ("popping"), causing high impulsive torque chatter in the actuators.
- Erratic Contact Sensor Triggers: Polygonal chatter creates high-frequency false positives/negatives in PhysX
contact_forces, corrupting thefeet_air_timereward and stance phase estimation. - High Computational Overhead: Iterative GJK/EPA mesh collision detection is computationally heavier and susceptible to numerical penetration compared to analytical shapes.
Following the industry-standard quadruped design (Unitree A1/Go1/Go2, ANYmal, Boston Dynamics Spot), contact modeling is decoupled into:
-
Shank Bone Collision: A slender primitive box (
<box size="0.02 0.02 0.12"/>) along the tibia bone. This prevents the shank from clipping through obstacles while maintaining a clearance of$> 4.3\text{ cm}$ above the ground plane during normal gait. -
Dedicated Foot Sphere Contact: An explicit rigid link attached via a fixed joint (
dont_collapse="true") at the distal foot pad, with collision geometry defined as a pure primitive sphere of radius$R = 0.01894\text{ m}$ (18.94 mm radius, 37.88 mm diameter):-
FR / BR:
$\mathbf{p}_{\text{foot}} = [+0.1035\text{ m}, -0.0175\text{ m}, -0.1445\text{ m}]$ , with sphere bottom patch at$Z = -0.16344\text{ m}$ (exactly$0.0\text{ mm}$ gap, perfectly flush with the metal shank). -
FL / BL:
$\mathbf{p}_{\text{foot}} = [+0.1035\text{ m}, +0.0175\text{ m}, -0.1445\text{ m}]$ , with sphere bottom patch at$Z = -0.16344\text{ m}$ (exactly$0.0\text{ mm}$ gap, perfectly flush with the metal shank). -
Flush Tangency (Gap = 0 mm): Protrusion is reduced to exactly
$0.0\text{ mm}$ ($0.0000\text{ m}$ ), making the lowest tangent point of the analytical sphere contact surface coincide perfectly with the flat bottom face of the tibia foot pad ($Z = -0.16344\text{ m}$ ), delivering seamless continuous contact kinematics with zero artificial height bulge. -
MuJoCo & IsaacLab Full Parity: Dedicated
<geom name="*_foot" type="sphere" ...>elements are defined identically across both IsaacLab (resources/nxp_jaguar.usd) and MuJoCo (models/nxp_jaguar.xml), ensuring the black spherical feet render and simulate identically in both environments.
-
FR / BR:
- Smooth Rolling Contact: Exact analytical sphere normals ($\mathbf{n} = (\mathbf{x}{\text{sphere}} - \mathbf{x}{\text{ground}}) / R$) eliminate contact chatter across arbitrary foot roll and pitch angles.
-
Clean Contact Force Sensing: Contact sensors target
SceneEntityCfg("contact_forces", body_names=".*_foot")in IsaacLab andgeom name="*_foot"in MuJoCo, delivering clean, continuous normal force profiles for gait phase detection andfeet_air_timerewards. -
Zero Height Bias: Gap of
$0.0\text{ mm}$ aligns analytical ground contact height directly with the CAD tibia frame, avoiding any height offsets between raw kinematics and simulation contact solvers. -
$O(1)$ Solver Execution: Analytical sphere-to-plane collision tests drastically increase simulation throughput and numerical stability in both PhysX and MuJoCo.
In standard exponential velocity tracking rewards ($r = w \exp(-|\mathbf{v}_{\text{cmd}} - \mathbf{v}|^2 / \sigma^2)$), when the robot encounters an obstacle or steep incline and becomes stuck (
Introduces an explicit quadratic penalty on linear velocity tracking error in the robot base frame:
Introduces an explicit quadratic penalty on angular velocity
-
Yaw Stall: Resisting turning commands or failing to pivot around
$Z$ when steering is requested. -
Uncommanded Yaw Drift: Spinning out, twisting, or drifting off heading when commanded to walk straight or stand still (
$\omega_{\text{cmd}, z} = 0$ ). Together, these terms maintain precise heading and drive execution across challenging rough terrains and obstacles.