The AY-Robots SO-100 hub page showing the low-cost 6-axis robot arm that runs on Feetech STS3215 bus servos
SO-100HardwareFeetech STS3215TroubleshootingPower

Power Supply and Voltage for Hobby Servos: Sizing an SO-100 Rail

AY-Robots ResearchAugust 23, 202628 min read

What 7.4 V actually means on a Feetech STS3215 bus, how a sagging rail shows up in servo registers and recorded episodes, and how to size a supply that does not brown out.

A robot arm that is short of current does not switch off. It gets weaker. The elbow drops a centimetre under load, the gripper closes on air, a joint that reached its target yesterday stops two degrees short today, and every one of those failures looks like bad calibration or a bad policy. It is neither. It is the rail.

This page covers the electrical side of a SO-100 class arm: what 7.4 V actually buys you, how much current six Feetech STS3215 servos really draw, what a sagging supply looks like in the servo registers and in a recorded LeRobot dataset, and how to size a brick. Every number below comes from the STS3215 product specification, from the Feetech register map version 3.7, or from LeRobot source you can open yourself. Where two documents disagree, both are quoted rather than averaged.

What you need to know

  • 7.4 V is the upper operating point of the STS3215, not a requirement. The datasheet rated input range is 4 V to 7.4 V, and edition A/1 puts the protection trip above 8 V or below 4 V.
  • The SO-ARM100 reference bill of materials ships a 5 V brick, not a 7.4 V one, and Waveshare specifies 5 V 4 A for the SO-ARM100 kit. That is 20 W.
  • Six servos at rated torque draw 3.9 A, which on a 7.4 V rail is 29 W. Stall transients go to 15 A. Size for the transient, not the average.
  • The driver board does not regulate. Waveshare's own instructions say the DC port powers the servo directly, so whatever sits on the barrel jack sits on the servo bus.
  • A sag has a fingerprint: Present_Voltage (register 62) falls under load, Present_Load pegs near 1000, and the gap between action and observation.state grows on the gravity-loaded joints.
  • The factory unloading mask (44) does not include the voltage bit, so an under-volted servo does not cut torque and does not raise an exception. It just makes less torque and keeps going.
  • A dataset recorded on a sagging rail teaches the policy the sag. Fixing the supply afterwards makes the policy wrong in the other direction.

What 7.4 V means on an STS3215, and what it does not

The Feetech STS3215 is a bus servo with a 12-bit magnetic encoder, a 1/345 metal gearbox on the C001 variant, and a half-duplex TTL bus that leaves the factory at 1 Mbaud. Its product specification lists two operating points, 6 V and 7.4 V, and one rated input range that covers both. The 7.4 V figure everyone quotes is the upper operating point, which is where the marketing torque number comes from.

Datasheet item (edition A/1, 2023-06-23)At 6 VAt 7.4 V
No-load speed0.238 s per 60 deg (42 RPM)0.192 s per 60 deg (52 RPM)
Running current, no load130 mA150 mA
Stall torque16.5 kg.cm19.5 kg.cm
Stall current2 A2.5 A
Rated torque4 kg.cm5 kg.cm
Rated current500 mA650 mA
Idle current, stopped21 mA21 mA
Rated input voltage4 V to 7.4 V (single spec, both points)4 V to 7.4 V
Terminal resistance2.5 ohm (single spec)2.5 ohm
Torque constant Kt7.8 kg.cm/A (single spec)7.8 kg.cm/A

Three of those rows changed between editions, which is worth knowing before you argue with someone quoting a different PDF. Edition A/0, dated 2020-04-10, lists idle current as 6 mA and Kt as 8 kg.cm/A, and puts over-voltage protection above 7.4 V. Edition A/1, dated 2023-06-23, raises idle to 21 mA, drops Kt to 7.8 kg.cm/A, and moves the over-voltage trip above 8 V. The register map version 3.7 agrees with A/1: the Maximum input voltage register at address 14 ships with 80 in units of 0.1 V, which is 8.0 V, and the minimum at address 15 ships with 40, which is 4.0 V. Read A/1 as current.

The change log does not mention the electrical edits

A/1's own revise record lists exactly one change against A/0: added the motion curve chart. It does not mention idle current, Kt or the over-voltage threshold, all three of which differ between the two PDFs. That is a good reason to cite the edition and date whenever you quote an STS3215 number, and to open the PDF you actually have rather than trusting a forum post.

The 7.4 V part and the 12 V part look identical

Feetech sells an STS3215 in a 7.4 V version (19.5 kg.cm) and a 12 V version (30 kg.cm) with the same case, the same connector and the same silkscreen. The SO-ARM100 README is explicit that choosing the 12 V motors means buying a 12 V 5 A+ supply instead of the 5 V one, and that the leader arm is always 7.4 V for the SO-101. Waveshare adds a second trap: its wiki specifies 5 V 4 A for the SO-ARM100 kit and 12 V 5 A for the SO-ARM101 kit, so two kits whose names differ by one digit want supplies that differ by 7 V. On this platform, SO-100 and SO-101 are both listed as 7.4 V arms on STS3215 servos. Read the label on the servo, not the name of the kit. The whole arm shares one bus, so over-voltage takes out all six at once.

The reference build ships a 5 V brick, not a 7.4 V one

This surprises people. The SO-ARM100 bill of materials has a single line item called Power Supply at about 10 USD, linked to a switching adapter with a 5.5 x 2.1 mm barrel, and the README's own prose calls it "a 5V one". Waveshare's SO-ARM100/101 wiki puts a number on it in one sentence: the power supply for SO-ARM100 is 5 V 4 A, and for SO-ARM101 it is 12 V 5 A. The README even acknowledges the cost, noting that the 7.4 V servo has 16.5 kg.cm at 6 V and likely slightly less on a 5 V supply.

How much less is arithmetic you can do from the datasheet. Divide voltage by stall current at the two published points and you get 6 / 2 = 3.0 ohm and 7.4 / 2.5 = 2.96 ohm, so the motor plus driver looks like a constant resistance to a first approximation. Extrapolating to 5 V gives about 1.7 A of stall current, and 1.7 A times the 7.8 kg.cm/A torque constant is roughly 13 kg.cm.

Rail voltageStall currentStall torqueNo-load speedMargin above the 4.0 V floor
5.0 V (BOM default, extrapolated, not a datasheet row)about 1.7 Aabout 13 kg.cmabout 35 RPM1.0 V
6.0 V (datasheet operating point)2 A16.5 kg.cm42 RPM2.0 V
7.4 V (datasheet operating point)2.5 A19.5 kg.cm52 RPM3.4 V
Where the 5 V row comes from, and which way it errs

The 5 V row is not a datasheet row. It is linear extrapolation from the two published operating points, shown so you can check it rather than trust it. The check: Kt times the 7.4 V stall current is 7.8 x 2.5 = 19.5 kg.cm, which reproduces the datasheet exactly. At 6 V the same model gives 7.8 x 2 = 15.6 kg.cm against a listed 16.5, so it reads about 5 percent low at the bottom of the published range. The 13 kg.cm estimate is therefore conservative, and the real 5 V figure is probably a little higher. Speed tracks voltage cleanly over the same span, at 7.03 RPM per volt at 7.4 V and 7.00 at 6 V. Measure your own arm before you build a design decision on the first row.

The margin column is the one that matters for brownouts. A 5 V rail sits 1.0 V above the voltage at which the servo declares an under-voltage condition. A 7.4 V rail sits 3.4 V above it. At 4 A of draw, every 0.1 ohm of round-trip resistance in the barrel lead, the jack and the daisy-chain connectors costs 0.4 V, and 0.1 ohm is not an exotic amount of resistance for a cheap DC lead and six push-fit 3-pin connectors in series.

The driver board does not regulate anything

Most of the confusion comes from assuming the little board between USB and the servos does something to the power. It does not. Waveshare's ST3215 servo wiki is the page that says it outright: you provide 6 to 12.6 V via the DC port (5.5 x 2.1 mm) on the board, "and the port will directly power the servo", and if you use a large number of servos you need to provide enough current for that port. The board is a serial level shifter and a barrel jack wired to the bus.

The adapter's own page is worth reading next to that, because its spec strip and its instructions point in different directions. Waveshare lists the Bus Servo Adapter (A) as 9 to 12.6 V input with a 5.5 x 2.1 mm DC socket, and then says twice, in the specification and again in the feature list, that the input voltage must match the voltage of the servos you connect. The second rule is the operative one on a 7.4 V arm, and it is why the SO-ARM100 build runs a brick well below the printed input range. If your board is a different Waveshare model, check its own page rather than assuming this one.

  • The barrel jack voltage is the servo bus voltage. There is no buck converter protecting you from a wrong brick.
  • All six servos on a follower arm hang off one 5.5 x 2.1 mm connector, daisy-chained through 3-pin cables.
  • The current for every downstream servo flows through the first cable in the chain and its two contacts. LeRobot's own assembly order runs the board to shoulder_pan (id 1) and out to the gripper (id 6), so the gripper sits at the far end of the accumulated drop.
  • USB carries data only. Waveshare states plainly that USB does not power the servo, and that both the power supply and USB must be connected.
  • The leader arm needs its own supply. The BOM lists two power supplies for a leader plus follower build, one each.
Why the gripper is usually the first thing to misbehave

Two effects stack at the end of the chain, and only one of them is topology. The documented half is in SOFollower.configure(), which restricts the gripper and nothing else: it writes Max_Torque_Limit = 500, Protection_Current = 250 and Overload_Torque = 25. Against the register map's factory values (1000, 500 and 80) that is half the torque ceiling, a 1625 mA over-current trip instead of 3250 mA, and an overload countdown that starts at 25 percent of maximum torque instead of 80 percent. Note that LeRobot's inline comment on the last write reads "25% torque when overloaded", while the register map defines address 36 as the threshold that starts the countdown and address 34 as the output torque afterwards. Either reading leaves the gripper the least tolerant joint on the bus, so a weak rail shows up there before it shows up anywhere else. If that is your symptom, gripper does not close walks the rest of the causes.

Sizing the rail: a current budget you can defend

Six servos, each with a published idle, no-load, rated and stall current, gives you a budget you can write down instead of guessing. Every row below is at 7.4 V, because those are the currents the datasheet publishes; rows marked as combinations are arithmetic on datasheet points, not measured values.

Condition on a six-servo followerPer servo at 7.4 VSix servosPower at 7.4 VAgainst a 4 A supply
Torque disabled, arm limp (a leader arm in normal use)21 mA0.13 A0.9 WFits, trivially
All joints moving with no load150 mA0.9 A6.7 WFits
Two joints at rated torque, four unloaded (combination)650 mA / 150 mA1.9 A14 WFits
All six at rated torque (combination)650 mA3.9 A29 WAt the 4 A limit
Two joints stalled, four at rated (combination)2.5 A / 650 mA7.6 A56 WNo
All six stalled2.5 A15 A111 WNo, by nearly four times

Read the amps column against your supply's amp rating and the watts column against its wattage, and do not mix the two rails up: a 5 V 4 A brick is 20 W, while 4 A at 7.4 V is 29.6 W. The fourth row is the interesting one either way. Six servos merely holding rated torque sits right on a 4 A rating, and 5 kg.cm is not much with the arm extended and something in the end effector. The rows below it are transients rather than steady states, but a transient is exactly what collapses a rail: the supply current-limits or droops for the tens of milliseconds it takes to accelerate the arm, and by the time you reach for a multimeter everything reads fine again.

Running the bus at 7.4 V instead of the BOM's 5 V
Advantages
  • 19.5 kg.cm of stall torque instead of about 13 kg.cm, which is the difference between shoulder_lift holding an extended arm and shoulder_lift creeping downward.
  • 52 RPM instead of about 35 RPM no-load, so the follower tracks a fast leader movement instead of lagging it and writing that lag into every episode.
  • 3.4 V of headroom above the 4.0 V under-voltage floor instead of 1.0 V, so ordinary connector and cable drop no longer reaches the protection threshold.
  • The platform lists both the SO-100 and the SO-101 as 7.4 V arms, so you are running the configuration the rest of the tooling assumes.
Trade-offs
  • You are at the top of the rated input range with 0.6 V to the 8 V protection trip, and a cheap unregulated brick can overshoot by far more than that. A commenter on LeRobot issue 3394 measured 16 V out of a nominally 12 V adapter.
  • Higher stall current means more heat in the same PA+GF case, and the servo turns torque output off above 70 degrees C. The threshold is a writable register (address 13, factory 70, maximum 100), but raising it is a way to cook a servo rather than a way to fix one.
  • You need a regulated supply and a meter to confirm it. The 5 V brick from the BOM is more forgiving precisely because it is further from every ceiling.

What a sagging supply looks like in the data

The reason this failure eats a day is that nothing raises an error. LeRobot reads Present_Position and writes Goal_Position, and both succeed whether or not the servo actually got where it was told. You have to go and ask. The STS3215 reports its own supply voltage, current, load and temperature, and LeRobot's Feetech control table already has names for all of them.

  1. 1
    Read the rail through the servos, at rest

    Present_Voltage lives at register 62 in units of 0.1 V. LeRobot only normalizes Goal_Position and Present_Position, so everything else comes back raw and needs no calibration file. Start with the arm folded and torque off, which is the best case your rail will ever show. Note that connect() runs configure(), which is where the gripper limits above get written.

    python
    from lerobot.robots.so_follower import SO100Follower, SO100FollowerConfig
    
    # SO100FollowerConfig is the registered dataclass; the bare SOFollowerConfig
    # has no `id` field and will raise TypeError if you pass one.
    robot = SO100Follower(SO100FollowerConfig(port="/dev/ttyACM0", id="follower"))
    robot.connect(calibrate=False)
    bus = robot.bus
    
    raw = bus.sync_read("Present_Voltage")   # register 62, 1 byte, 0.1 V per count
    print({m: v / 10 for m, v in raw.items()})
    # {'shoulder_pan': 5.0, 'shoulder_lift': 5.0, 'elbow_flex': 5.0,
    #  'wrist_flex': 4.9, 'wrist_roll': 4.9, 'gripper': 4.9}
  2. 2
    Read it again while the arm is holding itself up

    Enable torque, drive the arm to a fully extended pose, hang the payload you actually intend to manipulate, and sample voltage, current, load and temperature together. Present_Current is register 69 in units of 6.5 mA, Present_Load is register 60 in units of 0.001 of drive duty and is sign-magnitude encoded, and Present_Temperature is register 63 in whole degrees C. A rail that drops more than a few tenths of a volt between the two measurements is the answer to your question.

    python
    import time
    
    for _ in range(50):
        volt = bus.sync_read("Present_Voltage")            # 0.1 V per count
        curr = bus.sync_read("Present_Current")            # 6.5 mA per count
        load = bus.sync_read("Present_Load")               # 0.001 duty, signed
        temp = bus.sync_read("Present_Temperature")        # register 63, deg C
        stat = bus.sync_read("Status")                     # register 65, error bits
        for m in volt:
            print(f"{m:<14} {volt[m]/10:>4.1f} V  {curr[m]*6.5:>6.0f} mA  "
                  f"load {load[m]:>5}  {temp[m]:>3} C  status 0b{stat[m]:06b}")
        time.sleep(0.1)
  3. 3
    Decode the status bits instead of guessing

    Register 65 uses the same bit layout as the unloading and LED alarm masks: bit 0 voltage, bit 1 sensor, bit 2 temperature, bit 3 current, bit 4 angle, bit 5 overload. A set bit 0 is the servo telling you the rail left its configured window. While you are there, read back the two masks so you know what the servo will and will not do about it.

    python
    BITS = ["voltage", "sensor", "temperature", "current", "angle", "overload"]
    
    for name, val in bus.sync_read("Status").items():
        flags = [BITS[i] for i in range(6) if val >> i & 1]
        print(name, flags or ["ok"])
    
    print("unloading mask", bus.read("Unloading_Condition", "shoulder_lift"))   # factory 44
    print("led alarm mask", bus.read("LED_Alarm_Condition", "shoulder_lift"))   # factory 47
    print("min volt", bus.read("Min_Voltage_Limit", "shoulder_lift") / 10)      # factory 4.0
    print("max volt", bus.read("Max_Voltage_Limit", "shoulder_lift") / 10)      # factory 8.0
What you observeRegister to readSag readingNot a sag if
Joint stops a few degrees short of its targetPresent_Voltage (62)Falls under load, recovers at restVoltage is flat and the offset is identical in every pose, which points at calibration
Arm holds, then slowly droopsPresent_Load (60)Pegged near 1000, which is full drive duty, before the droop startsLoad is low while the joint moves anyway, which points at a mechanical slip
Gripper will not hold an objectPresent_Current (69)Climbs toward the 250-count (1625 mA) trip LeRobot writes for the gripperCurrent stays far below 250 counts, which points at grip geometry
Servos answer no ping at allNone, use a multimeterRail is outside 4.0 V to 8.0 V at the barrel jackRail is in range, which points at bus wiring or ids
Sync reads start failing when the arm movesPresent_Voltage (62)Dips coincide with motion onsetFailures are uniform across poses, which points at the serial link
Torque collapses to a fraction for a couple of secondsStatus (65) bit 5Overload bit set; output falls to Protective_Torque and returns after a new position commandBit 3 is set instead, which is the over-current cut that closes the output outright, or bit 2, the 70 degree C thermal cutout

One detail from that fifth row is worth knowing before you chase it. SOFollowerConfig ships with num_read_retries set to 2, and its own comment says Feetech buses occasionally return a corrupted status packet "especially when several joints move at once", which would otherwise abort the control loop. Several joints moving at once is also exactly when a weak rail dips, so retries can mask the electrical symptom while the tracking error quietly grows.

The second half of the fingerprint is in the episodes you already recorded. A LeRobot dataset stores action as the commanded joint vector and observation.state as the measured one, both with names like shoulder_lift.pos, so the residual between them is the tracking error of the follower. Under a healthy rail that residual is small and roughly symmetric. Under a sagging one it grows on the gravity-loaded joints exactly when the arm is extended, and it returns to zero when the arm folds. This belongs in the same pre-flight check as everything in collecting high-quality VLA training data, because it is a data defect that no amount of GPU time undoes.

python
import numpy as np
from lerobot.datasets.lerobot_dataset import LeRobotDataset

ds = LeRobotDataset("your-user/your-so100-dataset")
names = ds.features["observation.state"]["names"]   # ['shoulder_pan.pos', ...]

cols = ds.hf_dataset.select_columns(["action", "observation.state"]).with_format("numpy")
goal = np.asarray(cols["action"])              # (T, 6) what the leader commanded
state = np.asarray(cols["observation.state"])  # (T, 6) where the follower got to
resid = goal - state

for i, n in enumerate(names):
    print(f"{n:<18} mean {resid[:, i].mean():+7.2f}  p95 {np.percentile(np.abs(resid[:, i]), 95):6.2f}")

# A constant non-zero mean on one joint is usually calibration.
# A p95 several times the mean, concentrated on shoulder_lift and elbow_flex,
# is the shape a sagging rail leaves behind.
Run this on a dataset you already recorded. It needs no hardware and takes seconds.
The AY-Robots fix index listing robot failure modes such as arm twitches then sags, servo not responding and joint stops early, each linking to a dedicated page
The /fix index. Several entries here are electrical before they are anything else: an arm that twitches then sags, a joint that stops early, a servo that does not answer.

The protection rules you hit first

The datasheet lists four electronic protections, and you will meet them in a specific order. All four are configurable, all four ship with values you can read out of the register map, and the defaults are not the ones you would guess. In particular, the servo is far more willing to give up on torque than on current.

Register (address)Factory valueUnitWhat it means
Max_Voltage_Limit (14)800.1 VOver-voltage above 8.0 V, released automatically when the rail returns
Min_Voltage_Limit (15)400.1 VUnder-voltage below 4.0 V, released automatically
Max_Temperature_Limit (13)70deg CTorque output is turned off above 70 degrees C; writable, maximum 100
Protection_Current (28)5006.5 mA3250 mA, which is above the 2.5 A stall current, so it rarely trips first
Over_Current_Protection_Time (38)20010 ms2000 ms above the current limit before the output is closed
Overload_Torque (36)800.01Load above 80 percent of maximum torque starts the overload countdown
Protection_Time (35)20010 ms2000 ms above that threshold before overload protection engages
Protective_Torque (34)200.01Output drops to 20 percent of maximum torque once overload has engaged
The trap: overload fires long before over-current does

Read the two thresholds together. Over-current needs current above 2 A sustained for 2 s in the datasheet's own words, or 3250 mA at the register default, and when it fires it closes the output. Overload needs only 80 percent of maximum torque for 2 s, and when it fires the servo does not stop: it drops output to 20 percent of maximum torque and carries on. That is the arm that holds a pose for two seconds and then sinks. The flag clears when a new position command arrives, so the next control tick partially revives it, which is where the twitch-then-sag rhythm comes from. If that is what you are seeing, arm twitches then sags is the page for it. On an undersized rail this happens sooner, because the same mechanical load is a larger fraction of a smaller maximum torque.

There is one more default worth decoding by hand, because it explains why an under-volted arm never tells you it is under-volted. The unloading condition register at address 19 ships with the value 44. In the register map's bit order, that is bit 5 overload plus bit 3 current plus bit 2 temperature: 32 + 8 + 4 = 44. Bit 0 is voltage, and it is clear. The LED alarm register at address 20 ships with 47, which is 32 + 8 + 4 + 2 + 1, and there bit 0 is set.

So the factory configuration flashes an LED for a voltage fault but does not unload on it. The servo keeps trying, at whatever torque a low rail can produce, and your Python never sees an exception. That is the whole reason this failure mode gets misdiagnosed as calibration drift, a bad policy, or a flaky serial link for a full day before anyone puts a meter on the barrel jack.

Two routes to a rail you trust

The measurement is the same either way. The difference is what you have to own to do it, and whether you have a known-good arm to compare against.

Ten minutes of bench work settles it. You need a multimeter, the arm, and a payload that represents the task you actually want to record.

  1. 1
    Meter the brick before anything is plugged in

    Measure the barrel jack with no load. This is the check that would have saved the commenter on LeRobot issue 3394 who, on 2026-06-01, found a nominally 12 V adapter delivering about 16 V and putting servos into protection. Anything above 8.0 V on a 7.4 V arm is a servo-killer, and anything below 4.0 V is under the servo's own minimum.

    text
    Expected on an SO-100 build
      BOM 5 V brick        4.9 to 5.2 V unloaded
      7.4 V regulated      7.2 to 7.6 V unloaded
      Anything > 8.0 V     do not plug it in (factory Max_Voltage_Limit)
      Anything < 4.0 V     below the servo's minimum input voltage
  2. 2
    Install LeRobot with the Feetech SDK and find the port

    The register reads above need the scservo SDK, which is the feetech extra. lerobot-find-port tells you which serial device belongs to which arm by asking you to unplug it. The LeRobot docs add a warning worth heeding while you are handling the board: the power supply cable itself can come loose as you manipulate it.

    bash
    pip install -e ".[feetech]"
    
    lerobot-find-port
    # Finding all available ports for the MotorBus.
    # Remove the USB cable from your MotorsBus and press Enter when done.
  3. 3
    Log voltage under the worst pose you can find

    Extend the arm horizontally, hang your payload, and watch Present_Voltage while shoulder_lift holds. Compare against the folded reading from the same session. A drop of more than a few tenths of a volt at the servo is your answer, and it is a better measurement than a meter on the brick because it includes every connector between them.

    bash
    # sanity-check the bus first: this reports what LeRobot can see
    lerobot-find-port
    
    # then run the Present_Voltage loop from the section above
    python read_rail.py | tee rail_extended.log
  4. 4
    Fix the supply, then recalibrate and re-record

    Changing the rail changes the tracking error, which changes what the follower writes into observation.state. Recalibrate afterwards and treat episodes recorded before the fix as a separate dataset, not as extra data.

    bash
    lerobot-calibrate \
      --robot.type=so100_follower \
      --robot.port=/dev/ttyACM0 \
      --robot.id=follower

One caveat on logging during a real session: reading extra registers costs bus time. The open draft PR 3456 on LeRobot, which proposes an opt-in record_telemetry flag for velocity, load and temperature, puts three extra sync_read calls at roughly 15 to 30 ms on a six-motor bus at 1 Mbaud, and its author is explicit that the figure is an estimate rather than a hardware measurement. It is a draft, not something you can switch on today. Either way, diagnose on the bench rather than inside the recording you intend to keep.

Where a hosted platform cannot help you

It is worth being blunt about the boundary. Everything upstream of the barrel jack is yours. Remote teleoperation does not change how much current a servo draws, fine-tuning a 3 B parameter model does not compensate for a joint that physically cannot hold its pose, and no dataset validation step inspects your rail because your rail is not in the dataset. What is in the dataset is its consequence, which is why the residual check earlier is the only remote-friendly diagnostic on this page.

There is a related trap at the other end of the loop. Per-action inference latency here runs from 20 ms for ACT to 485 ms for Pi0.5, and adding public internet round trips turns a working policy into a hesitant one. A hesitant policy and a sagging rail produce a similar-looking video. Measure the voltage before you blame the network, and the network before you blame the model.

The AY-Robots teleoperator page headlined Become a Robot Operator from anywhere in the world, showing a photo of the SO-100 arm that operators drive remotely
Operators drive arms they never see. When the hardware is somebody else's, the rail has to be right before the session starts, because nobody at the other end can put a meter on it.

The symptom you are chasing probably has a page

Arm twitches then sags, joint stops early, servo not responding, gripper does not close. Each failure mode has its own page with the electrical causes separated from the software ones.

Open the fix index

What to actually buy

There is no single right answer, because the reference build and the datasheet headline point at different voltages. Pick based on what you are doing with the arm rather than on which number is bigger.

BuildRailWhySource for the spec
SO-100 or SO-101, 7.4 V servos, the documented default5 V, 4 A or moreThe SO-ARM100 BOM part is a 5 V adapter and Waveshare's wiki states 5 V 4 A for SO-ARM100SO-ARM100 README, Waveshare SO-ARM100/101 wiki
SO-100 or SO-101, 7.4 V servos, torque-limited task7.4 V regulated, 5 A or moreBuys the full 19.5 kg.cm and 3.4 V of headroom above the under-voltage floorSTS3215 spec A/1, operating point 2
Follower built on 12 V STS3215 servos12 V, 5 A or moreThe README is explicit that the 12 V motors need a 12 V 5 A+ supply instead of the 5 V oneSO-ARM100 README
Waveshare SO-ARM101 series kit12 V, 5 AWaveshare specifies that supply for its SO-ARM101 kit, while the upstream SO-101 leader arm is always 7.4 VWaveshare SO-ARM100/101 wiki, SO-ARM100 README
LeKiwi12 V 5 A Li-ion battery, or a 65 W laptop power bank for the 5 V versionThe README ships two variants, and the platform lists a 7.4 V arm on a 12 V base, so there are two ways to plug the wrong jack inLeKiwi README, platform arm table
Koch v1.15 V and 12 V railsDynamixel XL330 and XL430 servos, a different electrical family entirelyPlatform arm table

Two practical notes that are not about voltage. Buy a regulated supply rather than the cheapest one with the right sticker, because the 16 V measurement reported in issue 3394 was on a brick whose label said 12 V. And give the leader arm its own supply, as the BOM does. It runs with torque disabled and draws almost nothing, but sharing one jack between two arms puts the follower's stall transients on the leader's rail, and the leader is the thing generating your action labels. If you are still choosing hardware, SO-100 against SO-101, LeKiwi and Koch v1.1 lay out the rest of the differences, the arm overview collects them, and the full SO-100 setup guide covers the build end to end.

Can I run SO-100 servos from a 12 V supply for more torque?

No. The 7.4 V STS3215 has a rated input range of 4 V to 7.4 V and protects above 8.0 V, per edition A/1 of the datasheet and the factory value of the Max_Voltage_Limit register. Feetech sells a separate, visually identical 12 V STS3215 with 30 kg.cm of stall torque, and the SO-ARM100 README says choosing it means buying a 12 V 5 A+ supply instead of the 5 V one. Since the whole arm shares one bus, the wrong voltage takes out all six servos at once.

The bill of materials says 5 V but the servo says 7.4 V. Which is right?

Both. 7.4 V is the upper operating point in the datasheet and the rated input range is 4 V to 7.4 V, so 5 V is legal. The SO-ARM100 BOM links a 5 V adapter and Waveshare's wiki puts the SO-ARM100 supply at 5 V 4 A. The cost of running at 5 V is torque and headroom: extrapolating from the datasheet's two operating points gives roughly 13 kg.cm instead of 19.5 kg.cm, and 1.0 V of margin above the under-voltage floor instead of 3.4 V. That 13 kg.cm is a calculated figure, not a datasheet row, and the same model reads about 5 percent low where the datasheet can check it.

How many amps does an SO-100 actually need?

Steady-state teleoperation with a light payload sits under 2 A on the datasheet numbers, which is why the 4 A brick works for most people. Six servos at rated torque is 3.9 A, right on a 4 A rating and 29 W on a 7.4 V rail, and stall transients go far above that: two joints stalled with four at rated torque is 7.6 A. Size for the transients if your task involves lifting anything, and measure Present_Voltage at the servo rather than trusting the label on the brick.

Does the USB cable power the servos?

No, and Waveshare's SO-ARM100/101 wiki says so explicitly: USB does not power the servo, so both the power supply and USB must be connected. The driver board passes the barrel jack voltage straight to the servo bus. This is also why a Raspberry Pi host does not need to budget servo current on its USB rail.

Why does my arm hold a pose for two seconds and then sink?

That is overload protection, not the current limit. Load above 80 percent of maximum torque for 2 s trips it, and the servo then drops to the Protective_Torque value, 20 percent of maximum torque at factory settings, rather than stopping. A new position command clears the flag, so the next control tick partly revives it. An undersized rail brings this on sooner, because the same mechanical load is a larger fraction of a smaller maximum torque.

I already recorded 60 episodes on a weak supply. Are they usable?

Check before you assume. Compute the per-joint residual between action and observation.state on the episodes you have. If the mean offset is constant across poses it is a calibration problem and the data may still be fine; if the 95th percentile is several times the mean and concentrates on shoulder_lift and elbow_flex when the arm is extended, the follower was not reaching its targets and the policy will learn that shortfall. Either way, do not mix episodes from before and after a supply change into one dataset, because the two halves describe two different arms.

Sources

Sources

Ready for high-quality robotics data?

AY-Robots connects your robots to skilled operators worldwide.

Get Started