
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 V | At 7.4 V |
|---|---|---|
| No-load speed | 0.238 s per 60 deg (42 RPM) | 0.192 s per 60 deg (52 RPM) |
| Running current, no load | 130 mA | 150 mA |
| Stall torque | 16.5 kg.cm | 19.5 kg.cm |
| Stall current | 2 A | 2.5 A |
| Rated torque | 4 kg.cm | 5 kg.cm |
| Rated current | 500 mA | 650 mA |
| Idle current, stopped | 21 mA | 21 mA |
| Rated input voltage | 4 V to 7.4 V (single spec, both points) | 4 V to 7.4 V |
| Terminal resistance | 2.5 ohm (single spec) | 2.5 ohm |
| Torque constant Kt | 7.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.
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.
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 voltage | Stall current | Stall torque | No-load speed | Margin above the 4.0 V floor |
|---|---|---|---|---|
| 5.0 V (BOM default, extrapolated, not a datasheet row) | about 1.7 A | about 13 kg.cm | about 35 RPM | 1.0 V |
| 6.0 V (datasheet operating point) | 2 A | 16.5 kg.cm | 42 RPM | 2.0 V |
| 7.4 V (datasheet operating point) | 2.5 A | 19.5 kg.cm | 52 RPM | 3.4 V |
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.
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 follower | Per servo at 7.4 V | Six servos | Power at 7.4 V | Against a 4 A supply |
|---|---|---|---|---|
| Torque disabled, arm limp (a leader arm in normal use) | 21 mA | 0.13 A | 0.9 W | Fits, trivially |
| All joints moving with no load | 150 mA | 0.9 A | 6.7 W | Fits |
| Two joints at rated torque, four unloaded (combination) | 650 mA / 150 mA | 1.9 A | 14 W | Fits |
| All six at rated torque (combination) | 650 mA | 3.9 A | 29 W | At the 4 A limit |
| Two joints stalled, four at rated (combination) | 2.5 A / 650 mA | 7.6 A | 56 W | No |
| All six stalled | 2.5 A | 15 A | 111 W | No, 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.
- 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.
- 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.
- 1Read 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.
pythonfrom 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} - 2Read 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.
pythonimport 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) - 3Decode 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.
pythonBITS = ["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 observe | Register to read | Sag reading | Not a sag if |
|---|---|---|---|
| Joint stops a few degrees short of its target | Present_Voltage (62) | Falls under load, recovers at rest | Voltage is flat and the offset is identical in every pose, which points at calibration |
| Arm holds, then slowly droops | Present_Load (60) | Pegged near 1000, which is full drive duty, before the droop starts | Load is low while the joint moves anyway, which points at a mechanical slip |
| Gripper will not hold an object | Present_Current (69) | Climbs toward the 250-count (1625 mA) trip LeRobot writes for the gripper | Current stays far below 250 counts, which points at grip geometry |
| Servos answer no ping at all | None, use a multimeter | Rail is outside 4.0 V to 8.0 V at the barrel jack | Rail is in range, which points at bus wiring or ids |
| Sync reads start failing when the arm moves | Present_Voltage (62) | Dips coincide with motion onset | Failures are uniform across poses, which points at the serial link |
| Torque collapses to a fraction for a couple of seconds | Status (65) bit 5 | Overload bit set; output falls to Protective_Torque and returns after a new position command | Bit 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.
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.
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 value | Unit | What it means |
|---|---|---|---|
| Max_Voltage_Limit (14) | 80 | 0.1 V | Over-voltage above 8.0 V, released automatically when the rail returns |
| Min_Voltage_Limit (15) | 40 | 0.1 V | Under-voltage below 4.0 V, released automatically |
| Max_Temperature_Limit (13) | 70 | deg C | Torque output is turned off above 70 degrees C; writable, maximum 100 |
| Protection_Current (28) | 500 | 6.5 mA | 3250 mA, which is above the 2.5 A stall current, so it rarely trips first |
| Over_Current_Protection_Time (38) | 200 | 10 ms | 2000 ms above the current limit before the output is closed |
| Overload_Torque (36) | 80 | 0.01 | Load above 80 percent of maximum torque starts the overload countdown |
| Protection_Time (35) | 200 | 10 ms | 2000 ms above that threshold before overload protection engages |
| Protective_Torque (34) | 20 | 0.01 | Output drops to 20 percent of maximum torque once overload has engaged |
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.
- 1Meter 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.
textExpected 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 - 2Install 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.
bashpip 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. - 3Log 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 - 4Fix 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.
bashlerobot-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.
Say the honest thing first: this platform does not read your bus voltage. Nothing in the recording client meters your rail, and no amount of cloud GPU fixes a 20 W brick. What the platform does give you is the thing that is genuinely hard to get alone, which is a reference to compare against.
- A real arm you did not build streams with no signup, queue-based. Driving a known-good SO-100 tells you within a minute whether the hesitation you are chasing lives in your hardware or in your expectations.
- The failure-mode pages name the electrical symptoms separately from the software ones, including servo not responding, joint stops early and arm not detected.
- The desktop client records LeRobot-format datasets straight from a teleop session, so the residual analysis above runs on your own episodes without extra tooling.
- The public dataset directory gives you episodes recorded on other people's hardware. Running the same residual script against one of those is a cheap way to see what a healthy tracking error looks like.
- The robot docs and the SO-100 getting-started path carry the supported hardware list, including which arms run which rails.
A run on a rented GPU sees only the numbers in your dataset. If the follower never reached the commanded pose, the policy learns a mapping that already contains the shortfall, and the loss curve will look fine. The minimum episode counts here are 30 for SmolVLA and 50 for ACT, Pi0.5, GR00T N1.5 and GR00T N1.7, and a sagging rail costs you all of them at once. Fix the supply before you spend the 4 to 12 USD on an A100-tier run.
The one place the platform genuinely changes the electrical picture is on the compute side rather than the arm side: cloud inference pods carry an idle watchdog and destroy themselves after an idle period, so a debugging session that runs long does not quietly keep billing. That is a different kind of power management, and it does not help your servo bus at all.
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 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 indexWhat 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.
| Build | Rail | Why | Source for the spec |
|---|---|---|---|
| SO-100 or SO-101, 7.4 V servos, the documented default | 5 V, 4 A or more | The SO-ARM100 BOM part is a 5 V adapter and Waveshare's wiki states 5 V 4 A for SO-ARM100 | SO-ARM100 README, Waveshare SO-ARM100/101 wiki |
| SO-100 or SO-101, 7.4 V servos, torque-limited task | 7.4 V regulated, 5 A or more | Buys the full 19.5 kg.cm and 3.4 V of headroom above the under-voltage floor | STS3215 spec A/1, operating point 2 |
| Follower built on 12 V STS3215 servos | 12 V, 5 A or more | The README is explicit that the 12 V motors need a 12 V 5 A+ supply instead of the 5 V one | SO-ARM100 README |
| Waveshare SO-ARM101 series kit | 12 V, 5 A | Waveshare specifies that supply for its SO-ARM101 kit, while the upstream SO-101 leader arm is always 7.4 V | Waveshare SO-ARM100/101 wiki, SO-ARM100 README |
| LeKiwi | 12 V 5 A Li-ion battery, or a 65 W laptop power bank for the 5 V version | The 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 in | LeKiwi README, platform arm table |
| Koch v1.1 | 5 V and 12 V rails | Dynamixel XL330 and XL430 servos, a different electrical family entirely | Platform 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
- Feetech STS3215 product specification, edition A/1, 2023-06-23: electrical characteristics (section 5) and electronic protection (section 7-11)
- Feetech STS3215 product specification, edition A/0, 2020-04-10 (translated): 6 mA idle, Kt 8 kg.cm/A and the 7.4 V over-voltage trip
- Feetech ST3215 memory register map, version 3.7: addresses, units, factory values and the protection bit order
- TheRobotStudio SO-ARM100: the bill of materials, the Power Supply line item and the 7.4 V versus 12 V servo note
- Waveshare SO-ARM100/101 wiki: 5 V 4 A for SO-ARM100, 12 V 5 A for SO-ARM101, and USB does not power the servo
- Waveshare Bus Servo Adapter (A): 9 to 12.6 V printed input range, 5.5 x 2.1 mm DC socket, and the rule that input must match servo voltage
- Waveshare ST3215 servo wiki: the 12 V part at 30 kg.cm, 6 to 12.6 V input, and the DC port that directly powers the servo
- LeRobot: Feetech STS/SMS control table with the addresses for Present_Voltage, Present_Current, Present_Load, Status and the protection registers
- LeRobot FeetechMotorsBus: NORMALIZED_DATA is only Goal_Position and Present_Position, and DEFAULT_BAUDRATE is 1 Mbaud
- LeRobot SOFollower: the gripper-only Max_Torque_Limit, Protection_Current and Overload_Torque writes in configure()
- LeRobot SOFollowerConfig and SO100FollowerConfig: which dataclass carries the id field, and the num_read_retries default of 2
- LeRobot issue 3394: STS3215 servos not answering; comment of 2026-06-01 reports a nominally 12 V adapter measured at 16 V
- LeRobot draft PR 3456: optional motor telemetry, with the author's 15 to 30 ms estimate for three extra sync_reads at 1 Mbaud
- Hugging Face LeRobot docs, SO-101: the feetech extra, lerobot-find-port, the daisy-chain order and the loose power supply cable warning
- LeKiwi: the 12 V 5 A Li-ion battery version and the 65 W laptop power bank version
Sources
- Feetech STS3215 product specification, edition A/1, 2023-06-23: electrical characteristics (section 5) and electronic protection (section 7-11)
- Feetech STS3215 product specification, edition A/0, 2020-04-10 (translated): 6 mA idle, Kt 8 kg.cm/A and the 7.4 V over-voltage trip
- Feetech ST3215 memory register map, version 3.7: addresses, units, factory values and the protection bit order
- TheRobotStudio SO-ARM100: the bill of materials, the Power Supply line item and the 7.4 V versus 12 V servo note
- Waveshare SO-ARM100/101 wiki: 5 V 4 A for SO-ARM100, 12 V 5 A for SO-ARM101, and USB does not power the servo
- Waveshare Bus Servo Adapter (A): 9 to 12.6 V printed input range, 5.5 x 2.1 mm DC socket, and the rule that input must match servo voltage
- Waveshare ST3215 servo wiki: the 12 V part at 30 kg.cm, 6 to 12.6 V input, and the DC port that directly powers the servo
- LeRobot: Feetech STS/SMS control table with the addresses for Present_Voltage, Present_Current, Present_Load, Status and the protection registers
- LeRobot FeetechMotorsBus: NORMALIZED_DATA is only Goal_Position and Present_Position, and DEFAULT_BAUDRATE is 1 Mbaud
- LeRobot SOFollower: the gripper-only Max_Torque_Limit, Protection_Current and Overload_Torque writes in configure()
- LeRobot SOFollowerConfig and SO100FollowerConfig: which dataclass carries the id field, and the num_read_retries default of 2
- LeRobot issue 3394: STS3215 servos not answering; comment of 2026-06-01 reports a nominally 12 V adapter measured at 16 V
- LeRobot draft PR 3456: optional motor telemetry, with the author's 15 to 30 ms estimate for three extra sync_reads at 1 Mbaud
- Hugging Face LeRobot docs, SO-101: the feetech extra, lerobot-find-port, the daisy-chain order and the loose power supply cable warning
- LeKiwi: the 12 V 5 A Li-ion battery version and the 65 W laptop power bank version
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started