DEV Community

Takeo
Takeo

Posted on

Trying VLA (Part 3): Finally Assembling LeRobot — From Joint 2 to Calibration

In the previous article, I explained how to assemble the first joint, Joint 1.

I covered Joint 1 separately because there were several things worth paying attention to, such as the different types of screws, tips for fitting the parts together, and a few points that could easily cause trouble — at least, they did for me.

https://dev.to/takeofuture/trying-vla-part-1-preparing-lerobot-and-assigning-ids-to-the-motors-538g

In this article, I will continue the assembly process and go all the way through calibration.

Based on the difficulties I encountered during the assembly, I want to mention one important point right at the beginning:

For Joint 5, I recommend slightly changing the order of the assembly steps.

Joint 2

(As long as you connect the wiring first, I think you can basically follow the video as-is.)

Slide the second motor into place from above.

Use four M2×6mm screws to secure Motor 2.

Attach motor horns to both sides of Motor 2. As before, use M3×6mm motor-horn screws to secure them.

Attach the Upper Arm and secure it with four M3×6mm screws on each side.

https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint2_v2.mp4

  • It may be easier to connect the motors to each other before assembling them. Also, when fitting the motor into place, the motor horn can easily shift, so it may be better to push it into position in one motion. If it shifts out of alignment, the screw holes can become hidden, making it impossible to insert the screws.

Joint 3

(Again, as long as you connect the wiring first, I think you can basically follow the video as-is.)

Insert the third motor and secure it using four M2×6mm screws.

Attach motor horns to both sides of Motor 3. As before, secure one side using an M3×6mm motor-horn screw.

Attach the Forearm to Motor 3 and secure it with four M3×6mm screws on each side.

https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint3_v2.mp4

Joint 4

(As long as you connect the wiring first, I think you can follow the video as-is.)

https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint4_v2.mp4

Joint 5

(For this joint, I strongly recommend connecting the wiring before tightening the screws!)

First, assemble only the section shown in the video.

Insert the motor and attach the motor horn on only one side.

👆 It is very important to connect the power cables before tightening the screws as shown above!

Do not install the screws immediately. At this stage, connect Motor 4 to Motor 5, and also connect the cable from Motor 5 that will later go to Motor 6.

In other words, once Motor 5 has been inserted into the frame, complete the wiring from Motor 4 to Motor 5 and from Motor 5 to Motor 6.

If you tighten the screws before doing this, inserting the power connectors afterward becomes extremely difficult.

I originally followed the video exactly and ended up struggling quite a lot.

https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Joint5_v2.mp4

Because I installed Motor 5 and tightened the screws first, connecting Motor 4 to Motor 5 and connecting the power cable from Motor 5 to Motor 6 became extremely difficult.

To make things worse, once I had installed the screws, I was unable to remove one of them, so I could not simply take everything apart and redo the assembly.

I suspect this may have been related to the quality of the frame parts I purchased. The screw hole appeared to become damaged or stripped, and I could no longer remove the screw that was holding the frame together.

Eventually, I managed to insert the power connectors using tweezers and carefully pushing them into place with my little finger.

However, I probably spent around two hours on this step alone.

👆 If you assemble and tighten everything before wiring, accessing the power connectors becomes extremely difficult!

Gripper / Handle / Waveshare Board Installation

Attach the gripper to Motor 5.

Secure it to the wrist-side motor horn using four M3×6mm screws.

Insert the gripper motor and secure it using two M2×6mm screws on each side.

Attach the motor horn and secure it using the M3×6mm motor-horn screw.

Attach the Gripper Claw and secure it with four M3×6mm screws on each side.

For this part, I think you can simply follow the video without any major problems.

https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Gripper_v2.mp4

Once the assembly is complete, attach the board and connect it to Motor 1.

Depending on the quality of the frame parts, some components may not fit properly.

If necessary, you may need to carefully widen or clean up a distorted opening using a file or utility knife.

Since I had already connected the motors to each other while assembling the arm, at this stage I only needed to connect the Waveshare board to Motor 1, as shown at the beginning of the video.

https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/Wiring_v2.mp4

Securing the Robot Arm to the Desk

If you leave the robot arm as-is, it can tip over when the arm is extended because of the shift in its center of gravity.

For that reason, I secured the base to my desk.

For some reason, I still had one frame part left over, and I was not completely sure what it was supposed to be used for.
I decided not to worry about it and continued.

Next, connect the PC to the Waveshare board using USB-C and connect power to the Waveshare board as well.

At this point, check that power is reaching Motor 6, which is the last motor in the daisy-chain connection.

Calibration

At this point, each motor can still be rotated manually by hand.

You will feel some resistance and hear a slight mechanical sound while turning them, but they should still rotate manually.

Before starting calibration, move each joint to approximately the middle of its range of motion.

$ source ~/pyenv/lerobot/bin/activate
(lerobot)$ lerobot-calibrate \
  --robot.type=so101_follower \
  --robot.port=/dev/ttyACM0 \
  --robot.id=takeo_so101
Enter fullscreen mode Exit fullscreen mode
INFO 2026-09-02 10:30:46 calibrate.py:89 {'robot': {'calibration_dir': None,
           'cameras': {},
           'disable_torque_on_disconnect': True,
           'id': 'takeo_so101',
           'max_relative_target': None,
           'num_read_retries': 2,
           'port': '/dev/ttyACM0',
           'position_d_coefficient': 32,
           'position_i_coefficient': 0,
           'position_p_coefficient': 16,
           'use_degrees': True},
 'teleop': None}
INFO 2026-09-02 10:30:46 follower.py:109 takeo_so101 SOFollower connected.
Press ENTER to use provided calibration file associated with the id takeo_so101, or type 'c' and press ENTER to run calibration:
Enter fullscreen mode Exit fullscreen mode

I had already calibrated this robot once before.

If I want to reuse the previous calibration file, I can simply press ENTER.

If I want to perform calibration again, I can type c and press ENTER.

If this is your first time calibrating the robot, I believe you can proceed by pressing ENTER.

For this article, however, I wanted to show the calibration process again, so I entered c.

Press ENTER to use provided calibration file associated with the id takeo_so101, or type 'c' and press ENTER to run calibration: c
INFO 2026-09-02 10:35:41 follower.py:126
Running calibration of takeo_so101 SOFollower
Move takeo_so101 SOFollower to the middle of its range of motion and press ENTER....
Move all joints except 'wrist_roll' sequentially through their entire ranges of motion.
Recording positions. Press ENTER to stop...

-------------------------------------------
-------------------------------------------
NAME            |    MIN |    POS |    MAX
shoulder_pan    |   1171 |   2131 |   2968
shoulder_lift   |    325 |   2690 |   2697
elbow_flex      |    818 |    866 |   3021
wrist_flex      |   1100 |   2514 |   3418
gripper         |   1311 |   2719 |   2720
Calibration saved to /home/takeo/.cache/huggingface/lerobot/calibration/robots/so_follower/takeo_so101.json
INFO 2026-09-02 10:37:13 follower.py:238 takeo_so101 SOFollower disconnected.
Enter fullscreen mode Exit fullscreen mode

As you manually rotate each joint, the values in the table above continuously change.

The following motors and joints are involved in the calibration process:

ID1 shoulder_pan → Left/right rotation
ID2 shoulder_lift → Shoulder up/down
ID3 elbow_flex → Elbow
ID4 wrist_flex → Wrist up/down
ID5 wrist_roll → Not included in this calibration process
ID6 gripper → Gripper open/close

The calibration parameters are then stored in the following JSON file.

(lerobot)$ cat /home/takeo/.cache/huggingface/lerobot/calibration/robots/so_follower/takeo_so101.json
{
    "shoulder_pan": {
        "id": 1,
        "drive_mode": 0,
        "homing_offset": -1979,
        "range_min": 1171,
        "range_max": 2968
    },
    "shoulder_lift": {
        "id": 2,
        "drive_mode": 0,
        "homing_offset": -422,
        "range_min": 325,
        "range_max": 2697
    },
    "elbow_flex": {
        "id": 3,
        "drive_mode": 0,
        "homing_offset": 1146,
        "range_min": 818,
        "range_max": 3021
    },
    "wrist_flex": {
        "id": 4,
        "drive_mode": 0,
        "homing_offset": 795,
        "range_min": 1100,
        "range_max": 3418
    },
    "wrist_roll": {
        "id": 5,
        "drive_mode": 0,
        "homing_offset": 2006,
        "range_min": 0,
        "range_max": 4095
    },
    "gripper": {
        "id": 6,
        "drive_mode": 0,
        "homing_offset": 1764,
        "range_min": 1311,
        "range_max": 2720
    }
}
Enter fullscreen mode Exit fullscreen mode

With this, the assembly and calibration of the SO-101 follower arm are complete.

In the next article, I plan to run teleoperation from the command line and check whether the robot moves correctly.

https://dev.to/takeofuture/trying-vla-part-4-testing-teleoperation-from-the-command-line-1721

Top comments (0)