SO-101 arms
Motor configuration and calibration for Haller's two SO-101 arms.
This guide brings up the SO-101 arms on Haller. It assumes the LeRobot environment is already installed.
Source: official LeRobot SO-101 docs — huggingface.co/docs/lerobot/so101.
Haller has two SO-101 arms:
- A follower — six STS3215 motors, calibrated as
haller_follower, USB symlink/dev/haller_arm_follower. Controlled by the HMI. - A leader — also six STS3215 motors (same gear ratio as the follower — deliberate symmetric-hardware choice, see "Leader bring-up" below), calibrated as
haller_leader, USB symlink/dev/haller_arm_leader. Used for hand-driven teleop and demonstration data collection.
Both bring-ups follow the same pattern (find port → configure motor IDs → daisy-chain → calibrate → smoke test). The instructions below describe the follower flow in detail; the leader-specific deltas are at the end.
Hardware checklist
Before you start:
- SO-101 arm mechanically assembled per the TheRobotStudio assembly instructions.
- 6× Feetech STS3215 servos. Check the voltage variant on the servo label:
- 7.4 V variant (operating range 6.0–7.4 V) — use a 7.4 V supply (e.g. bench DC set to 7.0–7.4 V at ≥2 A).
- 12 V variant (operating range 4–14 V) — use the official SO-101 kit's 12 V / 5 A brick.
- Wrong supply on the 7.4 V variant = "Input voltage error" + alarm LED at best, dead servos at worst.
- Feetech bus servo adapter board (Waveshare or equivalent).
- The two jumpers select the control path, not the power source:
Achannel = UART control (Pi Zero, ESP32, Arduino, STM32).Bchannel = USB control (PCs, Pi 4B, Jetson Orin Nano). This is what you want for a desktop or Jetson host.
- USB does not power the servo bus. The servos are powered exclusively from the barrel jack — USB only powers the on-board USB-to-TTL logic.
- The two jumpers select the control path, not the power source:
- Power supply matching your servo variant, with a center-positive barrel plug that physically fits the board's DC5521 jack.
- USB cable from the adapter board to the workstation.
- At least one 3-pin TTL cable for connecting one motor at a time during configuration.
Shell access on the workstation with the lerobot conda env activated:
conda activate lerobot1. Find the serial port for the bus adapter
Plug the bus adapter into the barrel-jack power supply and into the workstation via USB. Jumpers on B. Then:
lerobot-find-portThe tool lists all serial devices, asks you to unplug the adapter and press Enter, and then reports which port disappeared. On Linux it's typically /dev/ttyACM0 on the first plug.
For Haller specifically, both bus adapters get a stable udev symlink (see Persistent permissions below), so we use:
- Follower:
/dev/haller_arm_follower - Leader:
/dev/haller_arm_leader
Use those names in every subsequent command — they survive reboots, USB port reorderings, and dual-arm plug-in order changes.
Persistent permissions
Add your user to the dialout group (log out and back in to take effect):
sudo usermod -aG dialout "$USER"For stable device names across reboots, Haller ships udev rules in scripts/99-haller-devices.rules keyed by USB serial number. Install once:
sudo cp scripts/99-haller-devices.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm triggerThe rules set mode 0666 on each symlink, so chmod-on-every-boot is no longer needed.
To map a new board to a new symlink, find its serial first:
udevadm info -a -n /dev/ttyACM0 | grep '{serial}' | head -1…then add a line to 99-haller-devices.rules matching that serial and pick a symlink name.
2. Configure motor IDs and baud rate
Brand-new Feetech motors all ship with ID 1. The bus servo protocol requires unique IDs (1–6 for the SO-101) and a shared baud rate. These values are written to motor EEPROM, so this step is one-time per motor.
Only one motor may be connected to the bus during this step. The script writes ID n to whatever motor it can see — if multiple are on the bus, you'll overwrite IDs you've already set.
Run the LeRobot setup tool for the follower:
lerobot-setup-motors \
--robot.type=so101_follower \
--robot.port=/dev/haller_arm_follower \
--robot.id=haller_followerThe script walks motors in REVERSE order: gripper (ID 6) → wrist_roll (5) → wrist_flex (4) → elbow_flex (3) → shoulder_lift (2) → shoulder_pan (1). For each one:
- Connect a single motor to the board with a 3-pin cable.
- Press Enter. The tool prints e.g.
'gripper' motor id set to 6. - Swap to the next motor and repeat.
Tape-label each motor 1–6 (or by name) as you go — they're visually indistinguishable once the EEPROM is written.
If a motor doesn't respond:
Motor 'gripper' (model 'sts3215') was not found— the servo's V+ pin has no power. The board's logic is alive (USB-powered) but the servo bus isn't. Confirm the barrel-jack supply is connected and on, and that your supply voltage matches the servo variant (e.g. 7.4 V supply for 7.4 V STS3215). USB alone never powers the servos.[RxPacketError] Input voltage error!— the servo sees voltage outside its safe range and is alarming. You're feeding the wrong supply for this servo's voltage variant. Power off, swap supply (e.g. drop from 12 V to 7.4 V for the 7.4 V variant), retry.- Check the USB cable between board and computer.
- Check the 3-pin cable is fully seated on both ends.
- On a Waveshare board, confirm both jumpers are on the
Bchannel (control path = USB).
After all six motors are configured
Daisy-chain the motors with 3-pin cables and connect the chain to the controller board. Plug the chain so that shoulder_pan (ID 1) is closest to the controller board and gripper (ID 6) is at the far end.
3. Calibrate the follower
Calibration aligns the motors' raw encoder positions with the arm's mechanical zero/limits.
lerobot-calibrate \
--robot.type=so101_follower \
--robot.port=/dev/haller_arm_follower \
--robot.id=haller_followerThe tool prompts you to (1) move the arm to the middle of its range and press Enter, then (2) sweep each joint (except wrist_roll, which is treated as continuous) through its full range while it records min/max ticks.
Calibration is saved to:
~/.cache/huggingface/lerobot/calibration/robots/so_follower/haller_follower.jsonEach motor entry has id, drive_mode, homing_offset, range_min, range_max in raw ticks (4096 ticks = 360°).
Once the HMI is running you can also calibrate from the browser via the calibration wizard. The CLI flow above is the path before the HMI exists.
4. Smoke test (follower)
The repo ships a read-only smoke test that streams joint positions while you back-drive the arm by hand:
cd ~/haller_ws
python scripts/test_so101_arm.py --port /dev/haller_arm_follower --id haller_followerMove each joint — values in the table should update at ~5 Hz. Exit with Ctrl-C. Torque is disabled during the test so the arm is freely back-drivable.
5. Bring up the leader
The leader hardware on Haller is identical to the follower (six STS3215 1/345-ratio motors) — a deliberate symmetric-hardware choice, not the mixed-gear-ratio leader spec from the upstream SO-101 docs. Teleop with this leader is slightly stiffer to back-drive than a "true" leader, but the arm is hardware-interchangeable with the follower and converts cleanly when needed.
Same workflow as above, with the type and id swapped:
# 1. Configure motor IDs (one at a time, gripper first; same EEPROM dance as the follower)
lerobot-setup-motors \
--teleop.type=so101_leader \
--teleop.port=/dev/haller_arm_leader \
--teleop.id=haller_leader
# 2. Daisy-chain, then calibrate
lerobot-calibrate \
--teleop.type=so101_leader \
--teleop.port=/dev/haller_arm_leader \
--teleop.id=haller_leaderLeader calibration lands at:
~/.cache/huggingface/lerobot/calibration/teleoperators/so101_leader/haller_leader.jsonNote the path differs from the follower's (robots/so_follower/) — lerobot stores teleoperators and robots in separate sub-trees.
Verify end-to-end teleop
With both arms wired and the follower's 7.4 V supply on:
lerobot-teleoperate \
--robot.type=so101_follower \
--robot.port=/dev/haller_arm_follower \
--robot.id=haller_follower \
--teleop.type=so101_leader \
--teleop.port=/dev/haller_arm_leader \
--teleop.id=haller_leaderMove the leader by hand — the follower should mirror its motion at ~60 Hz. Quit with Ctrl-C; the follower's torque drops on disconnect (disable_torque_on_disconnect=True), leaving the arm back-drivable.
Calibrating two arms that will teleop together
lerobot-calibrate sets each arm's "0°" reference at whatever physical pose you happen to hold the arm in when it prompts "move to the middle of the range." If you calibrate two arms in different neutral poses, their midpoints won't match in physical space and teleop (leader → follower) will show a per-joint offset — typically a few degrees, most visible on shoulder_lift because that joint changes arm height.
When calibrating the second arm, hold it in the same physical neutral pose you used for the first arm (e.g. arm extended straight forward, shoulder centered, elbow centered, wrist horizontal). The midpoints will then line up and teleop tracks 1:1.
If you've already calibrated and noticed a drift, just re-run lerobot-calibrate on one of the arms with attention to matching the other — or use the calibration wizard from the HMI.
Roadmap: leader → second follower
When you decide to convert the leader into a second HMI-controllable follower:
- Re-calibrate as a follower with a new id (e.g.
haller_left):lerobot-calibrate \ --robot.type=so101_follower \ --robot.port=/dev/haller_arm_leader \ --robot.id=haller_left - Add it to
hmi/backend/config.yaml:arms: - id: right model: so101_follower port: /dev/haller_arm_follower calibration_id: haller_follower enabled: true - id: left model: so101_follower port: /dev/haller_arm_leader calibration_id: haller_left enabled: true - Restart the HMI (
systemctl restart haller-hmi.serviceon the Orin, or just re-runhaller-hmion a laptop).
The HMI is already two-arm-keyed; the conversion is a config change, not a code change.
References
- LeRobot SO-101 docs: huggingface.co/docs/lerobot/so101
- LeRobot install: huggingface.co/docs/lerobot/installation
- SO-ARM100 hardware: github.com/TheRobotStudio/SO-ARM100
- Feetech STS3215 servo datasheet: search Feetech's site for STS3215
- Troubleshooting / Hardware not detected for motor + power + udev gotchas