CHAMP + GIM6010-8 CAN

Locomotion

12-joint quadruped robot: gait control, motor communication, safety, and operator driving

LOADING LIMIT

Locomotion Contract

12-joint quadruped robot running on the Limit platform (Jetson Thor, Ubuntu 24.04, ROS 2 Jazzy). The robot carries its own locomotion controller, CHAMP, which plans the gait and calculates the 12 joint positions 200 times per second (when the locomotion stack is running). It picks up the velocity command over DDS (/cmd_vel, geometry_msgs/Twist), sends the motor commands out on the CAN buses as GIM6010-8 motor frames, and writes the live joint angles back as /joint_states and /foot_contacts. The server's sim stack runs its own CHAMP (Gazebo) under the sim_* topic convention, so live and sim topics never collide. Operators drive the robot with W/S + Q/E + A/D keyboard controls in Limit Dash. Emergency stop lives on the robot itself.

Live vs Sim

There are two CHAMP instances, and the topic convention is what keeps them apart on a shared ROS domain: one on the robot (live), one on the server (sim). Teleop only ever drives the live one; the server's Nav2 only drives the sim one, via the sim_* topics.

AspectLiveSim
Runs onrobot, the live driving data (Jetson Thor, ROS 2 Jazzy)server, the sim (Gazebo)
CHAMPthe live instancethe sim instance
Driven byLimit Dash teleop only (live mode, /cmd_vel)the server's Nav2, via the sim_* topics (sim mode in Limit Dash)
Motorsthe real 12× GIM6010-8 on CAN0/CAN1the Gazebo leg joints
Topicsbare names: /joint_states, /foot_contacts, /locomotion/*sim_* convention: /sim_joint_states, /sim_foot_contacts, …
Live vs Sim: two CHAMP instances. Teleop drives the live CHAMP on the robot, which drives the 12 GIM6010-8 motors on CAN. The server's Nav2 drives the sim CHAMP on the server, which drives the Gazebo legs, via the sim_* topics.

Teleop only ever drives the live CHAMP on the robot, which drives the real motors and owns the bare topic names. The sim CHAMP on the server is driven by the server's Nav2 (sim mode in Limit Dash, with Gazebo running) and stays on the sim_* topics, so the two can run at the same time without talking past each other.

ROS Topic Contract

CHAMP input (subscribes)

TopicTypeQoSSemantics
/cmd_velgeometry_msgs/TwistRMW default (reliable, volatile, depth 10)Velocity command, Limit Dash teleop (W/S/Q/E/A/D) or the Nav2 stack (Twist, stamp stripped by the adapter). A silent command is a stop signal (watchdog, see the Safety Contract).
/body_posegeometry_msgs/msg/PoseRMW defaultEstimated body/base pose (from state_estimation_node / EKF chain). Optional to consume; never required for safety.
/safety/e_stopstd_srvs/srv/Triggern/aMandatory. Request → immediate all-axis stop.
/safety/resetstd_srvs/srv/Triggern/aMandatory. Clear stop/fault state, re-enter closed loop.

CHAMP output (publishes)

TopicTypeRateSemantics
/joint_statessensor_msgs/msg/JointState≥ 50 Hz12 joint names in Node-ID Table order. Position in radians, velocity in rad/s, current in amps (Iq). Used by robot_state_publisher and EKF.
/foot_contactschamp_msgs/msg/ContactsStamped≥ 50 HzFour booleans in order: lf (left front), rf (right front), lr (left rear), rr (right rear). The timestamp must always advance, even when all feet are off the ground.
/locomotion/statusstd_msgs/msg/String (JSON)1 Hz + on-changeHealth summary: per-motor {node_id, ok, last_heartbeat_age_ms, iq_a, fault_code}, watchdog state, e-stop state.
/locomotion/canstd_msgs/msg/String (hex)on trafficVerbose CAN trace for bring-up; default OFF.

Inbound stack behavior

GIM6010-8 CAN bus

The 12 motors are driven over two CAN buses, can0 (front legs) and can1 (rear legs). The low-level protocol (GIM6010-8 frame and command table), the 12-motor node-ID mapping, the motor datasheet, and hardware bring-up are on the Canbus page.

Safety Contract

Authority & limits

ParamDefaultNotes
Max body speed0.5 m/s (Nav2 already caps at 1.0)CHAMP hard-caps; ignore cmd above cap with warning
Max yaw rate1.5 rad/smatches Spin cap
Per-joint velocity limit10 rad/s via Set_Limits (0x00F)set at startup
Per-joint current limit15 A via Set_Limits + monitor Get_Iqtrip: |Iq| > 15 A for > 200 ms → stop that axis
Bus voltage floorwarn < 24 V, stop < 20 Vvia Get_Bus_Voltage_Current

Velocity-command watchdog

E-stop

Motor-level fault handling

ConditionDetectionAction
Heartbeat lostno 0x001 for > 3 × 100 msstop that axis, flag HEARTBEAT_LOST, count fault
Motor error flag0x001 Axis_Error bit or 0x003Clear_Errors (0x018) once, then stop axis if persists
OvercurrentGet_Iqsee Authority & limits
Overheat(no temp channel on GIM6010-8)monitor electrical power via 0x01D; trip if > 100 W for > 5 s
Bus voltage0x017see Authority & limits
3+ axes in faultanyglobal e-stop state FAULT_LOCKED; only /safety/reset releases

Startup / shutdown sequence

STARTUP:  [per motor] Set_Limits(0x00F) → Set_Controller_Mode(0x00B: 2/2) → Set_Axis_State(0x007: 8)
          → verify via Heartbeat (axis_state bit) → mark READY
SHUTDOWN: [per motor] Set_Input_* (0.0) → Stop (0x002)
E-STOP:   [per motor] Stop (0x002)

CAN capture

All frames (TX+RX) appended to <log_dir>/can_capture_<UTC>.log, one line per frame: <monotonic_ns> <TX|RX> <can_id_hex:04X> <d0> ... <d7>. Default ON; parameter can_log=false disables.

Foot-Contact Inference

Foot contact state is synthesized from the gait model: stance phase = true, swing phase = false. The CHAMP controller can also publish /foot_contacts directly.

CHAMP Architecture

CHAMP is the locomotion controller. The live instance runs on the robot (Jetson Thor, ROS 2 Jazzy): it subscribes to the velocity command over DDS, runs the 200 Hz gait + IK loop, and drives the 12 GIM6010-8 motors directly on the CAN buses. The sim instance runs in Gazebo on the server, under the sim_* topic convention. Do not re-implement gait planning or inverse kinematics: CHAMP owns both; the CAN layer only turns its 12-joint position targets into GIM6010-8 frames.

Layer breakdown

LayerFileRole
Math core (IK)champ/include/champ/kinematics/kinematics.hinverse(12 joints, 4 foot_positions): 2-link IK per leg
Body pose → feetchamp/include/champ/body_controller/body_controller.hposeCommand(): translates/rotates zero_stance by body pose
Gait / planningchamp/include/champ/leg_controller/leg_controller.hvelocityCommand(): Raibert heuristic: step = (stance_duration/2) × velocity
Gait timingchamp/include/champ/leg_controller/phase_generator.hTrot: Left Front/Right Rear offset 0.0; Right Front/Left Rear offset 0.5; stride 0.5 s
Trajectory plannerchamp/include/champ/leg_controller/trajectory_planner.hSwing = cosine arc; Stance = foot held
ROS 2 wrapperchamp_base/src/quadruped_controller.cpp200 Hz loop → drive the 12 joint position targets out on CAN
Gait configlimitx_limit_sim/config/gait/gait.yamlSee the Gait params section below

Signal flow (verified from quadruped_controller.cpp)

/cmd_vel (Twist: Limit Dash teleop, or Nav2 path via the adapter)
   → CHAMP controlLoop_() @ 200 Hz  (on the robot):
        body_controller_.poseCommand()      # body pose → 4 foot target positions
        leg_controller_.velocityCommand()   # Raibert: foot step = (stance/2)*vel
        kinematics_.inverse()               # 2-link IK → 12 joint position targets
   → CAN driver: Set_Input_Pos frames → 12× GIM6010-8 (1 Mbps)
   → publish /joint_states (encoder feedback), /foot_contacts, /locomotion/status

Source-verified details:

12 joint names (verified from joints.yaml)

lf_hip_joint  lf_upper_leg_joint  lf_lower_leg_joint
rf_hip_joint  rf_upper_leg_joint  rf_lower_leg_joint
lh_hip_joint  lh_upper_leg_joint  lh_lower_leg_joint
rh_hip_joint  rh_upper_leg_joint  rh_lower_leg_joint

The CAN layer (bridge node) MUST map these names → node IDs (see the 12-Motor Node-ID Table). CHAMP publishes them in this order on joint_group_position_controller/command.

Gait params (gait.yaml)

Launch wiring

Operator Teleop (Limit Dash)

The only change to Limit Dash's keyboard handler: it also publishes a geometry_msgs/Twist message to /cmd_vel, W/S for forward/backward, Q/E for left/right, A/D for turn. This is what sends the command to the robot.

End-to-end flow

Operator teleop end to end: browser key press, ros_bridge.py publisher, CHAMP on the robot at 200 Hz, GIM6010-8 motors, and the state badge back in the browser

Key map

KeyTwist fieldWhile held (≈20 Hz)On release
Wlinear.x ++0.2 m/s→ 0
Slinear.x −−0.2 m/s→ 0
Qlinear.y ++0.25 m/s→ 0
Elinear.y −−0.25 m/s→ 0
Aangular.z ++0.4 rad/s→ 0
Dangular.z −−0.4 rad/s→ 0

Speed Cap