Build log · Mechanism & motion

An arm designed by a script, driven across two different buses

A six-axis arm whose geometry is derived from its own parts rather than positioned by eye — bolt patterns matched, gear meshing solved, travel limits measured. It runs two incompatible motor families on one wire on purpose: five FEETECH STS3215 joints where torque and feedback both matter, and an SCS0009 on the jaws, speaking a different dialect of the same half-duplex bus.

Fusion 360 APIParametric CADMG996R PCA9685FEETECH SCS0009Half-duplex UART ESP32-S3SO-101LeRobot

01 — Method

Why the CAD is a script

Every dimension on this arm is wrong until it is built. Link length trades against reach and against the torque the shoulder has to hold. The head mast height depends on a camera module whose real extents were not obvious from its spec sheet. Change one number and half the assembly should move.

In a normal parametric model, that means fighting a timeline: a sketch constraint fails, a downstream extrude loses its face, and you spend the afternoon repairing history instead of designing. So the arm is not a model — it is a generator.

# RIG-Arm — parametric Fusion 360 assembly generator
#
# Builds the whole arm as BRep solid bodies (via TemporaryBRepManager)
# with as-built revolute joints. No sketches, no extrude history
# -> tiny timeline, fast viewport, and every joint is draggable.
#
# Run:  Utilities > ADD-INS > Scripts and Add-Ins > Scripts > RIGArm > Run
# Edit the PARAMETERS block, re-run, and it builds a fresh document.

The whole design is a PARAMETERS block in millimetres followed by code that builds solids directly. Re-running produces a fresh document in seconds. There is no history to repair because there is no history — and because the joints are created as-built revolute joints, the regenerated assembly is immediately draggable for reach checks.

The trade

You give up interactive modelling. You cannot grab a face and push it. In exchange, a design iteration costs one number and one re-run, and the model can never end up in a broken intermediate state. For a machine whose proportions are genuinely undecided, that is the right side of the trade.

Fusion 360 viewport showing the full RIG-Arm assembly generated by the script.
Generated assembly. The full arm in Fusion 360 isometric projection, straight out of the Python BRep generator script. media/rig-arm/arm-cad.png

02 — Geometry & 3D Assembly

The kinematic chain & interactive model

Eight actuated degrees of freedom from base to jaw, plus a two-axis head mast carrying the vision module.

Link dimensions, authored in millimetres
SegmentValueNote
Base diameter190 mmTurntable 140 mm on top of it
Yaw bearing plane40 mmTop face of the base
Shoulder pivot height130 mmCarried in a two-plate yoke
Upper arm150 mmShoulder pivot → elbow pivot
Forearm140 mmElbow pivot → wrist pitch pivot
Forearm roll offset66 mmElbow pivot → roll joint
Wrist42 mmWrist pitch → wrist roll
Gripper base34 mmWrist roll → jaw pivots
Jaw length58 mm16 mm half gap

Interactive 3D STEP model

The complete assembly converted from RIG-Arm.step and rendered directly in WebGL via Three.js with realistic PBR materials matching the physical build. Rotate, zoom, pan, and inspect joints:

Loading 3D STEP Assembly (Z-Up, PBR Materials)…
Printed links Printed brackets STS3215 servos Jaws & claws HuskyLens board Heatsink plate

Loading the parts list…

The shoulder is doubled on purpose

The shoulder is the joint that holds the entire arm's weight at maximum moment arm — 150 mm of upper arm plus 140 mm of forearm plus wrist, gripper and payload, all levering on one pivot. A single MG996R is not enough, so the shoulder uses two servos in a yoke, mirrored across an 8 mm plate pair with a 26 mm inner half-gap.

That doubling is also why the base is 190 mm across and the turntable is 140 mm. A tall arm on a small base tips, and the failure mode of a tipping arm is that it falls on the thing it was reaching for.

03 — Views

Studio assembly views

Front and rear projections showing the vision head mount, dual shoulder yoke, and mechanical clearances verified in the collision audit.

Front projection of RIG-Arm showing HuskyLens 2 head and gripper.
Front studio projection. HuskyLens 2 camera head, 2-jaw end-effector, and vertical reach envelope. media/rig-arm/arm-front.png
Rear view of RIG-Arm showing dual-servo shoulder yoke and cable routes.
Rear yoke & routing. Dual DSS-M15S / MG996R shoulder actuators and internal wiring channel paths. media/rig-arm/arm-back.png

04 — Actuation & Schematics

Two buses, one arm

The obvious move is to pick one servo family and use it everywhere. I did not, because the two families are good at genuinely different things and the arm needs both.

Actuator assignment
BusServoQtyJoints
PWM
via PCA9685
MG996R / DSS-M15S 5 Base yaw ×1, shoulder ×2 (paired), elbow ×1, forearm roll ×1
Half-duplex serial
one wire, 1 Mbps
FEETECH SCS0009 3 Wrist pitch, wrist roll, gripper
PWM
same PCA9685
Head pair (MG90S) 2 Head pan, head tilt — 7 of 16 channels used total
Full electronics schematic of RIG-Arm with PCA9685, FEETECH bus, and ESP32.
Electronics architecture. Wiring schematic connecting the PCA9685 PWM driver, half-duplex UART bus transceiver, ESP32-S3 microcontroller, and split 6V/5V/3.3V power rails. media/rig-arm/arm-schematic.png

Why PWM at the base

The big joints need torque and they need it cheaply. An MG996R is a commodity part with metal gears and a well-understood stall figure. It takes a 50 Hz pulse and it does not tell you anything back — but for a shoulder that is being commanded to a position and held there, open-loop is acceptable and the price per newton-metre is unbeatable.

Why a serial bus at the wrist

The wrist and gripper need the opposite. A gripper that closes on an object needs to know it has stopped moving before it has reached its commanded position — that is how you detect a grasp instead of stalling into one. Bus servos report position, and they daisy-chain, so three joints at the far end of a moving arm need one three-wire run instead of three separate PWM cables flexing through every joint.

FEETECH SCS0009 bus servos daisy-chained with a single three-wire link.
The SCS chain. Bus servos daisy-chained on one three-wire run. media/rig-arm/feetech-bus.jpg

05 — Gotcha

The SCS0009 ID collision

This one cost me time and it is the single most useful thing on this page for anyone else building with FEETECH bus servos.

Every SCS0009 ships as ID 1

A bus servo is addressed by its ID. Straight out of the packet, every one of them is ID 1. My chain already used ID 1 for shoulder_pan. Plugging a new claw servo onto that chain puts two devices on the same address — and two devices answering the same address do not produce a clean error, they produce bus contention and garbage.

The safe order of operations

  1. Isolate the new servo first

    Put it alone on a bus with nothing else attached. This is not optional and it cannot be done later — once two devices share an ID, you have no way to address one without the other.

  2. Write the new ID

    Run the set-ID tool against the isolated servo. Mine went to ID 6, landing after wrist_flex at ID 4 on the chain.

  3. Verify on the isolated bus

    Ping the new ID and confirm it answers. Ping the old ID and confirm it does not. Both checks matter — a half-written ID is worse than an unwritten one.

  4. Only then join the chain

    Daisy-chain onto the main bus, sharing its 1 Mbps signalling and its 6.0–7.4 V rail. Confirm the new servo's voltage rating actually covers that rail first: the SCS0009 is specified 4.0–7.4 V, so it does — but check rather than assume, because the rail is shared and the weakest device sets the ceiling.

06 — Power

Power and the safety chain

Eight servos that can all stall simultaneously is a meaningful amount of current, and an arm that can move is an arm that can move somewhere you did not intend.

Power architecture
RailTargetFeeds
Servo rail6 V regulated, 20 APCA9685 V+ and the SCS chain
Logic rail5 V regulated, 3 A minMicrocontroller, camera module
PCA9685 logic3.3 VVCC and the I²C pull-ups only
VCC and V+ are not the same pin

On a PCA9685, VCC is 3.3 V logic and V+ is the 5–6 V motor rail. They are separate supplies that share a ground. Confusing them either does nothing (servos silent) or destroys the board.

The PCA9685 servo driver wired with separate logic and motor rails.
PCA9685 wiring. Separate VCC and V+ rails with a common ground and bulk capacitance at the connectors. media/rig-arm/pca9685.jpg

07 — Telemetry & Cockpit

StudyBot live dashboard & vision tracker

The arm runs under the StudyBot robotics bridge with live dual-bus telemetry, an active gripper safety state machine, and real-time vision tracking from the HuskyLens 2 head.

StudyBot Gripper & Vision Telemetry

ESP32-S3 Bridge · SCS Bus 1 Mbps · PCA9685 50 Hz

COMMS ACTIVE
End-Effector Safety & Aperture IDLE
100% OPEN
Openness
100%
Servo Pos
986
Jaw Angle
88.5°

Safe grip caps torque at 18%, executes a slow closure profile, and auto-torques off after 10s.

Commanded Pos (Purple) & Current Load (Amber) 10 Hz Telemetry
Position: 480–1000 cnt Peak Hold: 210 mA Bus Status: 0 Errors
HuskyLens 2 Vision Tracker · 640×480 Viewfinder
Head Pan: 0.0° · Head Tilt: 12.5° · Error: ΔX: 0 px · ΔY: 0 px
IDTypeLabelConfidenceCoordinatesError Vector
Industrial mockup and base enclosure of RIG-Arm.
StudyBot platform enclosure. Base chassis enclosing the power conversion stages, controller PCB, and turntable bearing mount. media/rig-arm/arm-mockup.png

08 — Detail

Measuring parts you did not design

The head mast carries a HuskyLens 2 vision module. Its published spec says the case is 19 mm deep. The actual solid geometry, taken from DFRobot's own STEP file, is 70.39 × 58.49 × 24.21 mm — the extra 5 mm is the lens boss and connectors standing proud of the case.

A mast designed to the 19 mm published figure would have had the lens fouling its own bracket. So the CAD carries the measured numbers, with the source of each one written down next to it:

HuskyLens 2 (SEN0638) — measured from DFRobot's published STEP
PropertyValue
Solid extents70.39 × 58.49 × 24.21 mm
Case depth vs. total19.0 mm case, 5.2 mm lens boss proud of it
Mass90 g
Primary mounting4 × M2 clearance (⌀2.2, ⌀4.1 counterbore) on a 59.00 × 41.00 mm pattern
Pattern offsetCentre sits 3.35 mm off the module's geometric centre
Secondary mounting2 × ⌀3.2 at 15.0 mm spacing on one edge
Swing radius30.5 mm above the pan turret — sets the 54 mm neck height
The 3.35 mm that matters

The M2 bolt pattern is not centred on the module. Its centre sits 3.35 mm above the geometric centre. Design a bracket around the module's outline and the holes miss; design it around the pattern and the module hangs off-centre. Both facts have to be in the model, which is exactly the kind of thing a parametric script is good at holding.

The 90 g mass is not trivia either. It sits at the top of a 40 mm mast on a 54 mm neck, which is a real moment on the head pan servo and part of why the neck height is a named parameter rather than a number typed into a sketch.

09 — The app

Pinion, the Mac app that drives it

Everything above describes an arm. Pinion is the native macOS app written to actually drive it — a real .app built from one Swift file and a bundle layout, with no Xcode project. It starts the control server, waits for it to answer rather than guessing a delay, and shows it in a native window. Closing the window takes the server down with it, because a stray process still holding the servo bus leaves the next launch unable to talk to the arm at all.

It also carries the geometry work. The gripper assembly is solved from the features that actually locate the parts — a shared four-bolt pattern matched to 0.000 mm RMS, and a rack and pinion kept in mesh by a test that asserts the contact point rather than by how it looks from one flattering angle.

With no arm plugged in it still opens: you get the 3D model, and it watches the bus and attaches by itself the moment a FEETECH adapter appears.

How Pinion works → Download .dmg · 126 KB

10 — Status

Where it stands

The CAD generator produces the full assembly with working joints. The bill of materials is written down with each line marked by how much I actually know about it — confirmed on the bench, on hand but not wired, required by the repo but unverified, or still to buy. That distinction is deliberate: a part appearing in CAD is not evidence that it exists in a drawer.

Running now

  • Parametric assembly generation with as-built revolute joints and 3D STEP export
  • Interactive WebGL 3D model viewer with PBR material shaders
  • StudyBot live telemetry & gripper safety state machine
  • HuskyLens 2 pan/tilt vision tracking loop

Still open

  • Half-duplex interface circuit — direct drive works on the bench, a proper direction/buffer stage belongs on the PCB
  • Head servo model decision — MG90S, MG996R or DSS-M15S as one matched pair; firmware currently assumes MG996R
  • Bearing and pivot selection — head idler, head pan thrust bearing, base turntable
  • Worst-case current measurement — needed before the rail fuse can be sized honestly

Alongside: SO-100 and SO-101

I have also been building and studying the open-source SO-100 / SO-101 arms from the LeRobot ecosystem. Those are a well-tested reference design with a real teleoperation and imitation-learning stack behind them, and having a known-good arm on the bench is what makes it possible to tell whether a problem is in my mechanism or in my expectations.

The partially assembled RIG-Arm with printed links and servos fitted.
Physical build. Printed links and fitted servos. media/rig-arm/arm-assembly.jpg
Close-up of the two-jaw gripper driven by an SCS0009 bus servo.
Gripper. Two-jaw end effector on the re-IDed SCS0009. media/rig-arm/gripper.jpg
The SO-101 open-source robotic arm assembled on the bench.
SO-101. The open-source reference arm, for comparison. media/rig-arm/so101.jpg