Skip to main content

Prime real-hardware bringup

Recipe for bringing the physical Prime up on real hardware — the eRob arm joints over EtherCAT plus the Sito wrists over SocketCAN, running concurrently. It is the Prime counterpart of First real-hardware bringup (which covers Lite), and it assumes you have already built the EtherCAT / Prime packages (see Installation → Build from source).

Experimental — bring-up is still in progress

Prime real-hardware support is not yet at Lite's maturity. A single eRob (left_wrist_yaw) has been brought up and jogged on silicon; the full multi-joint bus-split is still being commissioned. Treat the steps below as the current best-known procedure, not a turnkey flow, and expect to fall back to the single-bus / diagnostic modes while bringing a fresh robot up.

How Prime differs from Lite

Lite is homogeneous (Robstride on two SocketCAN buses). Prime is hybrid: the expansion of prime_dummy.urdf.xacro with use_fake_hardware:=false use_sim:=false emits two concurrent <ros2_control> blocks, and controller_manager runs them together, exposing one flat 14-joint list to the controllers (the waist is dropped in this version):

BlockPluginJointsBus
PrimeEtherCATSideethercat_driver/EthercatDriver10 eRob arm joints (CiA402 / CSP)EtherCAT, via the IgH master
PrimeSitoCANhumanoid_devices_sito/SitoSystem4 Sito wrist jointsone SocketCAN bus (can2)
Hybrid command surface

The 10 eRob joints are CiA402 and expose no stiffness/damping interfaces — they are held in position (CSP), and their impedance is set out-of-band by writing the drive's internal loop gains per mode (see eRob impedance per mode below). Only the 4 Sito wrists (mit_joints) expose the full MIT command surface, so the MIT-style humanoid_controllers fully activate over the wrists until the controllers are reworked for the hybrid. The bus / description / controller_manager wiring is correct regardless.

Bus topology

From humanoid_bringup_prime/config/prime_hardware.yaml — the single source of truth for how this robot is wired:

JointBus / addressJointBus / address
left_shoulder_pitcheRob ring pos 0right_shoulder_pitcheRob ring pos 5
left_shoulder_rolleRob ring pos 1right_shoulder_rolleRob ring pos 6
left_shoulder_yaweRob ring pos 2right_shoulder_yaweRob ring pos 7
left_elbow_pitcheRob ring pos 3right_elbow_pitcheRob ring pos 8
left_wrist_rollSito id 22 (can2)right_wrist_rollSito id 38 (can2)
left_wrist_pitchSito id 23 (can2)right_wrist_pitchSito id 39 (can2)
left_wrist_yaweRob ring pos 4right_wrist_yaweRob ring pos 9

So each arm is 5 eRob (the three shoulder DOF, elbow, and wrist yaw) + 2 Sito (wrist roll and pitch) = 7, for 10 eRob + 4 Sito = 14 total.

Prerequisites

  • Built with the EtherCAT / Prime packages. The default source build skips ethercat.* and humanoid_bringup_prime. To include them, install the IgH EtherLAB master from source on the host, then drop the --packages-skip-regex filter — see Installation → Build from source.
  • The IgH EtherCAT master is running and the drives are powered. The master is a host system service (ethercatctl / systemd) and is not started by the launch — ethercat_driver only connects to it (via master_id, 0 by default). See Step 1.
  • The Sito CAN bus is up. can2 at the 1 Mbit Robstride/Sito bitrate.
  • eRob software calibration is present. real.launch.py folds prime_calibration.yaml (per-joint direction + homing_offset) into each eRob's generated slave config at launch — no drive-NVM writes. Generate it with Calibrate the Prime arms.

Step 1 — Start and verify the EtherCAT master

Start the host master (skip if it is already a running systemd service), then confirm it sees every eRob slave:

sudo ethercatctl start          # or: sudo systemctl start ethercat
ethercat master # expect one master, link UP
ethercat slaves # expect the eRob drives at ring positions 0..9

Each eRob should appear at its ring position from the table above. If slaves are missing, it is a cabling / power / master-config problem — fix it here before launching, because ethercat_driver will not enumerate what the master cannot see.

Step 2 — Bring up the Sito CAN bus

Same as a Lite bus, on the interface Prime uses for the wrists:

sudo ip link set can2 down 2>/dev/null
sudo ip link set can2 up type can bitrate 1000000

Step 3 — Launch the bringup

ros2 launch humanoid_bringup_prime real.launch.py

That defaults to backends:=all (both buses) and bakes in use_fake_hardware:=false use_sim:=false. Useful arguments:

ArgDefaultPurpose
backendsallall (both buses) · ec (eRob/EtherCAT only) · can (Sito/CAN only). The single-bus modes spawn only joint_state_broadcaster — for calibration / diagnostics.
enable_joy_teleoptrueLaunch the joy_teleop node that maps gamepad buttons directly to controller_manager switches (flat, no FSM). Pass false for headless / CI, or when driving mode switches by hand.
enable_erob_impedancetrueSpawn erob_impedance_manager (eRob loop gains per active mode). Pass false to isolate startup races or fall back to factory-gain (stiff) eRob.
enable_gamepadtrueSpawn joy_node. Pass enable_gamepad:=false for headless / CI bringups.
calibration_filebundled prime_calibration.yamlPer-joint eRob software calibration, folded at launch.
hardware_configbundled prime_hardware.yamlbuses: + joints.all_joints.
Gamepad buttons (Prime)

joy_teleop maps each button directly to a controller_manager switch — flat, with no state machine and no ordering, so any mode is reachable from any other in a single press: X activates DAMP (damping_controller); L1+A / L1+B activate STANDBY (standby_controller_a / _b); R1+A activates LOCOMOTION (rl_policy_controller); R1+B activates REMOTE (remote_policy_controller); BACK activates STOP (zero_torque_controller). Each press activates that one controller and deactivates its siblings. Headless, the same switches are plain ros2 control switch_controllers --activate <name> --deactivate <name> calls.

eRob SYNC0 and control_frequency

The eRob distributed-clock SYNC0 cycle (control_frequency) must equal the controller_manager update_rate (50 Hz). ethercat_driver sends PDOs and syncs the DC clock from the CM update() loop, not a dedicated thread, so a mismatch makes the clock never lock → CiA402 Fault 4616. real.launch.py reads update_rate from the same controllers YAML the CM loads and passes it as control_frequency, so they cannot diverge — do not override one by hand.

Step 4 — What to expect

  • The eRob reach EtherCAT OP one at a time. Historically this took ~70 s with cycling 0xA000 faults; with the bring-up pacing fix (now the default in the pinned ethercat_driver) it is ~13.6 s with zero faults. See Troubleshooting → Prime eRob bringup if it stalls.
  • The controller spawners are sequenced (joint_state_broadcasterzero_torque_controller → the inactive damping / standby / remote_policy controllers). They serialize on a file lock; chaining them on process-exit lets the first wait out the eRob activation alone and the rest run fast.
  • /joint_states is published as /prime/joint_states.

Step 5 — Verify

ros2 topic hz /prime/joint_states     # 14 joints at ~50 Hz
ros2 control list_controllers # joint_state_broadcaster + zero_torque active; rest inactive
# the active mode is whichever controller is `active` here

Single-bus / diagnostic modes

When one bus misbehaves, bring the other up on its own. These spawn only joint_state_broadcaster (the joint-consuming controllers claim the full 14-joint list, which cannot fully activate with a bus absent):

ros2 launch humanoid_bringup_prime real.launch.py backends:=ec    # eRob/EtherCAT only
ros2 launch humanoid_bringup_prime real.launch.py backends:=can # Sito/CAN only

backends:=can is also what the Sito calibration sweep uses, so the eRob's EtherCAT activation and DC faults cannot stall the Sito command/feedback path.

eRob impedance per mode

eRob impedance lives in the drive's internal CSP loop as CoE objects that are SDO-only (not PDO-mappable, so not a per-tick command interface). erob_impedance_manager tracks which mode controller is active and writes each eRob's loop gains (kp/kd0x2382/0x2381, gate 0x2383) for the active mode via the EtherLab ethercat CLI. The mode controllers keep commanding eRob position (CSP); these gains decide whether a joint holds, damps, or goes limp. Pass enable_erob_impedance:=false to drop this path while isolating startup races.

If a mode switch propagates slowly across the arm (one joint at a time), keep parallel_sdo enabled — see Troubleshooting → Prime mode switch.

Common Prime bring-up failures

All in Troubleshooting:

  • eRob bringup takes ~70 s with repeated 0xA000 faults — the DC convergence pacing issue (fixed by default; see the entry if it recurs).
  • eRob faults 4616 immediately on enablecontrol_frequencyupdate_rate (see the SYNC0 note above).
  • Mode switch propagates slowly across the arm — keep parallel_sdo on.

See also