Inside the arm: actuators, encoders, and control modes
The WidowX AI on your desk is a sealed production system: six integrated brushless joints on an internal CAN FD bus behind a 500 Hz real-time controller. The AK60-6 on your bench is the same actuator species with the lid off. Open it up — outrunner, planetary gear, encoder, FOC firmware — and learn the impedance law every modern joint runs before you build policies on top of it.
- Trace a policy action from a UDP packet on the host through the iNerve's 500 Hz loop and the internal CAN FD bus to torque at a joint, naming each layer's rate and what it hides.
- Explain the MIT-mode control law (torque = kp·position-error + kd·velocity-error + feedforward) as a firmware-level impedance controller, and choose the five command fields to realize stiff position, pure torque, or a gravity-compensated float.
- Compute reflected inertia through a 6:1 versus 350:1 reduction, and gravity-torque margin for the rated 1.5 kg payload at full 0.769 m reach against the WidowX AI's 27 N·m shoulder-class effort limit.
- Bring up a CubeMars AK60-6 on a bench CAN bus — wiring verified against the manual, termination checked, MIT mode enabled — and log position/velocity/torque telemetry under a commanded sinusoid.
You have spent years on one side of a host-device boundary: CPU code that queues work into a GPU whose firmware, scheduler, and clocks you influence but do not control. Your new hardware has the same shape — and this time there are two specimens. The WidowX AI on your desk is the production system: a 6-DOF arm plus parallel-jaw gripper whose joints are integrated brushless actuators on an internal CAN FD bus, run by a real-time controller in the base — the iNerve — at 500 Hz, with your workstation talking to it over Ethernet. It is sealed and not user-serviceable, exactly like the GPU in your workstation. The CubeMars AK60-6 on your bench is the teaching specimen: the same class of actuator as the arm's joints, alone on the bench with nothing between you and its firmware but a USB–CAN adapter. The arm is what you will run policies on; the bare actuator is the one you get to take apart, wire up, and command frame by frame. Every intelligent thing you will build in this course — up to and including a π₀-class VLA emitting action chunks — ultimately becomes torque at joints like this one. This lesson opens one up.
Conceptual Foundation: Impedance, Inertia, and Stability
Before manipulating the MIT-mode parameters, define the physical quantity they shape. Mechanical impedance is the dynamic relationship between applied torque and joint motion. Two related conventions appear in the literature, so name the denominator. The rotational dynamic stiffness is torque over angular displacement: for the spring-damper law. The velocity-based mechanical impedance is because . Both describe the same controller; mixing the first formula with the second definition is a units error. The abstraction is useful because it lets the commanded joint behave like a predictable virtual spring and damper at its interface with the world.
A critical property of this behavior is backdrivability. This is the ability of external forces applied at the output to rotate the motor rotor. It is not a binary property but a function of the inertia ratio. The reflected inertia is the equivalent inertia seen by the load, calculated as , where is the moment of inertia of the motor rotor about its spin axis. If is small relative to the load inertia, the load can 'drag' the motor, allowing the encoder to sense external forces. High gear ratios increase reflected inertia, making the joint feel locked and reducing its ability to sense contact.
The gains and are not arbitrary; they must satisfy a stability constraint. The damping ratio quantifies how the system responds to disturbances. If , the system is underdamped and will oscillate around the target. If , it is overdamped and responds slowly. For critical damping (), the system reaches the target as fast as possible without overshoot. The natural frequency sets the upper limit of the tracking bandwidth. A higher increases bandwidth but also increases the torque demand for a given error, potentially hitting current limits.
Worked Example: Calculating Critical Damping
Consider a joint with a load inertia kg·m². We want to design a controller that is critically damped () to ensure fast, stable response without overshoot. We choose a stiffness N·m/rad. We need to find the required damping coefficient .
- Calculate the term under the square root: N·m·s/rad.
- Solve for using the damping ratio formula: N·m·s/rad.
- Verify the natural frequency: rad/s, which is about 5 Hz. This is a reasonable bandwidth for a robotic joint.
If we had chosen N·m·s/rad (a common default for softer motions), the damping ratio would be . This is significantly underdamped, meaning the joint would oscillate several times before settling. For a precise positioning task, this would be unacceptable. The calculation shows that gain selection is a direct function of the physical load, not just a preference for 'stiffness'.
You increase from 50 to 200 N·m/rad but keep at 3.162 N·m·s/rad. What happens to the system's behavior?
Anatomy of an integrated actuator: the AK60-6 with the lid off
Start with the unit of composition. The AK60-6 is a 380-gram disc that contains an entire robot joint: motor, gearbox, sensing, and control electronics in one sealed package. It is the actuator class MIT's Mini Cheetah made famous — the quadruped-joint recipe that has since quietly taken over modern manipulation. Four subsystems, and the rest of this lesson is a tour of the consequences of each one.
- A brushless outrunner motor, KV80 winding. Outrunner means the magnet ring spins around the stator: a large air-gap radius buys more torque per amp than an inrunner of the same mass, at the cost of top speed — the right trade for a joint.
- A 6:1 planetary reducer. One modest stage, not hundreds stacked. It multiplies torque by six and — the number that defines this actuator class — multiplies reflected inertia by only thirty-six.
- A 21-bit magnetic encoder (the inner-ring angle sensor): million counts per revolution, about 3 microradians per count.
- An FOC drive board: field-oriented control of the phase currents at a 10 kHz-class rate, plus overcurrent, over/under-voltage, and over-temperature protection. When you command this actuator, you are talking to this board.
Two datasheet numbers define the envelope, and both trace back to the winding. KV is the motor constant quoted as no-load rpm per volt at the winding; its reciprocal is the torque constant, N·m per amp — about 0.12 N·m/A for this KV80 winding, or roughly 0.7 N·m/A at the output shaft before gear losses. Lower KV means more copper turns: more torque per amp, less speed per volt. The result at the rated 24 V: 3 N·m rated torque, 9 N·m peak, about 490 rpm rated speed and 640 rpm no-load. Read rated-versus-peak exactly like sustained-versus-boost clocks: rated is what thermals allow indefinitely; peak is a burst budget measured in seconds, spent on acceleration and impacts, repaid as heat — the on-board over-temperature protection is what forecloses on the debt.
The gearbox is an impedance transformer — the mechanical analog of the electrical one from your EE past. It scales speed down by the ratio , torque up by (times an efficiency near 0.9 for a single planetary stage), and — the property that decides how the joint feels — it reflects the rotor's inertia by . Write the rotor's kinetic energy in terms of the link velocity and the square falls out:
Plug in numbers. An outrunner rotor this size has an inertia of order kg·m² — a pancake of magnets at a couple of centimeters of radius. Through 6:1 that reflects to roughly kg·m², a rounding error next to any real load on the output. Through 350:1 the same rotor would reflect to over a full kg·m² and dominate everything downstream. Friction reflects by , not , and tells the same story: a couple of millinewton-meters of motor drag becomes ~0.01 N·m through 6:1 but ~0.7 N·m through 350:1. This is why the AK60-6 is backdrivable — grab the output and it turns, the motor spinning a gentle six revolutions per output turn — while a 350:1 hobby servo feels locked. The class name is quasi-direct drive: enough reduction for useful torque, little enough that torque flows honestly in both directions. Backdrivable means the joint can feel the world through its own motor current, and torque you command at the winding actually arrives at the link — the property the next section's control law depends on. One caution while the lid is off: the 21-bit encoder's 3 µrad step is a statement about quantization, not truth. Resolution is not accuracy — a distinction the arm will put a price on shortly.
The MIT-mode law: an impedance controller in firmware
The AK60-6 has two firmware personalities. One is a conventional menu of servo modes — position loop, velocity loop, current loop; pick one, send one setpoint. The other, MIT mode (named for the Mini Cheetah firmware that popularized it), collapses the menu into a single command type: every frame you send carries five numbers — desired position , desired velocity , gains and , and a feedforward torque — and the drive board evaluates one law at FOC rate:
Read it physically, not algebraically. The term is a spring of stiffness N·m/rad anchored at ; the term is a damper of N·m·s/rad referenced to ; is a bias force you superimpose. Commanding this actuator is not "go to angle X" — it is "behave as if a spring-damper of my choosing connected you to a moving anchor." That is an impedance controller, and it runs in firmware, which matters for one reason above all: contact. Contact dynamics play out in single milliseconds, far too fast for any host loop — but when the link hits something, the spring law keeps holding at FOC rate between your host packets, so the actuator's response to a collision is decided by the and you chose in advance, not by how fast your Python process can react. And because the law spans the whole torque interface, the familiar modes are just corner cases of it:
| Mode | Recipe | What the joint feels like | Use it for |
|---|---|---|---|
| Stiff position servo | high , moderate , | A rigid joint that fights every disturbance | Classic setpoint tracking; hides the error dynamics |
| Compliant tracking | low , low | A soft spring pulling toward the target | Contact-rich moves; collisions become bounces, not breakage |
| Pure torque | , only | Direct force; nothing holds position | Force control and system ID; the load decides where things end up |
| Damped float | , small , | Moving through honey | Safe manual repositioning |
| Gravity-comp float | , = gravity model | Weightless — hangs wherever you leave it | Kinesthetic teaching, leader–follower teleop |
| Servo modes (non-MIT) | one setpoint, onboard cascaded loops | Whatever the vendor tuned | Simple single-axis jobs without per-command gains |
In MIT mode you send a frame with p_des = +1.57 rad, v_des = 0, kp = 0, kd = 0, τ_ff = 0. What does the actuator do?
The WidowX AI: the same ideas, sealed and shipped
Now scale the specimen up. The WidowX AI is six integrated brushless actuators — the same species as your bench unit, larger and not user-serviceable — built into a 6-DOF arm with a parallel-jaw gripper, on an internal CAN FD bus you never touch. The controller you do touch is the iNerve in the base: it runs the real-time loop at 500 Hz, closing joint-level control against the actuators and streaming joint state back to your workstation over Ethernet/UDP at roughly the same rate with sub-millisecond timing. Note what changed from the hobby-arm world: the robot has a real-time controller now, and it is not your Python process. Your workstation is a client — it proposes targets and consumes telemetry, and the 500 Hz loop runs whether or not your process shows up on time. Six degrees of freedom also means the arm generically reaches full position-plus-orientation poses — all of SE(3) — within its dexterous workspace: no missing-DOF excuses when a grasp demands a specific approach angle.
| Joint | Position limit | Velocity limit | Effort limit |
|---|---|---|---|
| J0 (base yaw) | ±180° | 360°/s | 27 N·m |
| J1 | 0° to 180° | 360°/s | 27 N·m |
| J2 | 0° to 135° | 360°/s | 27 N·m |
| J3, J4 | ±90° | 540°/s | 7 N·m |
| J5 | ±180° | 540°/s | 7 N·m |
| Gripper | 40 mm stroke | — | 100 N grip force |
The arm-level numbers frame everything downstream: 1.5 kg payload, 0.769 m reach (1.4 m span), 4 kg arm mass, 1 mm repeatability, fed by a 24 V / 25 A supply. Your follower-configuration unit ships with the precision-grip fingers (molded silicone pads) and an Intel RealSense D405 on the wrist mount — the camera whose frames you will spend the rest of this module timestamping and calibrating. Read the joint table like a capacity planner: 27 N·m on the base yaw and the two gravity-bearing shoulder-class axes, 7 N·m on the three wrist joints that mostly steer the last few hundred grams, and velocity limits generous enough — 360°/s at the shoulder is nearly 5 m/s at full reach — that your runtime will clamp far below them.
You reach all of it through `libtrossen_arm`, the open-source C++ driver with Python bindings, which exposes four joint modes: position, velocity, effort, and external effort. Map them back to the law you just learned and the driver stops being magic. Position and velocity modes are stiff tracking with vendor-tuned loop gains — the high- corner. Effort mode is the -only corner: raw torque targets. External effort is the gravity-comp float, shipped as a product: the controller adds its own gravity and friction model — the driver calls these effort corrections and friction parameters, and both are configurable, alongside joint modes, joint limits, and motor parameters — so the arm hangs weightless and a human can move it by hand. That mode is the basis of kinesthetic teaching and leader–follower teleop, and it exists only because the actuators are backdrivable. What the driver hides is the per-command gain freedom: you select a mode per joint rather than / per packet, and the joint-firmware interface underneath is Trossen's business, not yours. That is the correct trade for a production arm — and after the bench exercise you will know exactly what is being traded.
Now read the spec sheet like a capacity planner with a gravity bill to pay. The worst static case is the arm horizontal at full reach with the rated payload — every gram at its longest lever. Trossen does not publish link masses or lengths, so model the moving mass with an illustrative point-mass breakdown of a 0.77 m-reach, 4 kg-class arm and label it as such:
import numpy as np
G = 9.81
REACH_M = 0.769 # published reach
EFFORT_LIMIT_NM = 27.0 # published shoulder-class effort limit
# Illustrative point-mass model of a 0.77 m-reach, 4 kg, 6-DOF arm.
# Trossen does not publish link masses or lengths -- these estimate the
# moving mass beyond the shoulder axis; treat them as +/-30%.
segments = np.array([
[0.90, 0.18], # upper-arm structure + elbow actuator
[0.70, 0.42], # forearm structure + wrist actuators
[0.80, 0.65], # wrist assembly + gripper + D405 camera
])
tau_links = G * np.sum(segments[:, 0] * segments[:, 1])
for payload_kg in (0.0, 0.5, 1.0, 1.5):
tau = tau_links + payload_kg * G * REACH_M
pct = 100.0 * tau / EFFORT_LIMIT_NM
headroom = EFFORT_LIMIT_NM - tau
print(f"payload {payload_kg:.1f} kg: tau {tau:5.1f} N*m | "
f"{pct:3.0f}% of limit | {headroom:4.1f} N*m left for dynamics")The output tells the story: the empty outstretched arm costs about 35% of the effort limit; the rated 1.5 kg payload at full reach pushes it to roughly 77%, leaving about 6 N·m of headroom that must cover every acceleration, deceleration, and disturbance in that pose. That is what a payload rating means — not "the arm breaks above this" but "above this, the torque budget stops covering dynamics." And notice which spec is honest about the whole chain: the 1 mm repeatability figure. Encoder counts on this actuator class are measured in microradians — micrometers at the fingertip — but bracket flex, gear lash, and thermal drift all stand between the encoder and the physical gripper. Resolution is not accuracy; the repeatability line is the vendor telling you where the truth floor actually sits. When the wrist D405 disagrees with forward kinematics about where the fingers are, believe the camera — the calibration that reconciles them begins in the next lesson.
Your static model says holding the rated 1.5 kg at full 0.769 m reach takes about 21 N·m at a shoulder-class joint whose published effort limit is 27 N·m. What is the correct reading of the remaining 6 N·m?
What this means for your policies
When a π₀-class VLA starts emitting action chunks in Phase 04, every action lands on this arm as a position (or velocity) target handed to libtrossen_arm and consumed by the iNerve at 500 Hz. The policy runs at a few hertz; the chunk is interpolated into a dense setpoint stream; the impedance machinery below turns each setpoint into torque. Your safety envelope is exactly the published limits: the driver's configurable joint limits and effort ceilings (27 and 7 N·m), velocity caps far below the datasheet 360–540°/s, step-size clamps so the position loop never sees a lunge-sized error, and a gripper command ceiling well under the 100 N maximum — 100 N over a 40 mm stroke is a ten-kilogram-force pinch, sized for secure grasps, not for anything that must survive being squeezed, fingers included. The ecosystem meets you here: the arm has official LeRobot integration and ROS 2 support, and Trossen's OpenPI tutorial targets exactly this hardware — with the caveat that the published examples are bimanual, so your single-arm adaptation needs its own validation. Lab 0 turns these clamps from advice into code.
Studio: bench bring-up of the AK60-6
Everything above becomes real the first time you feel change under your hand — which is the point of owning the bench unit. You will wire the actuator to a 24 V supply and the CubeMars USB–CAN adapter, bring the bus up, enable MIT mode, and command a slow sinusoid while logging telemetry. Two rules before any power flows. First: verify the exact connector pinout against the CubeMars manual for your unit — connector conventions and wire colors vary between hardware revisions, this cabling is famously not obvious, and you should trust neither forum posts nor this course on it. Second: bolt or clamp the actuator body to the bench — an unfixtured actuator commanded to 9 N·m torques its own stator instead of the load, and 9 N·m is more than enough to send 380 g flying.
import math
import time
AMPL_RAD = 0.5 # +/-0.5 rad: small, slow, visible
FREQ_HZ = 0.2 # one cycle every 5 s
KP = 8.0 # N*m/rad: a soft spring to start
KD = 0.8 # N*m*s/rad: enough damping to kill ringing
DURATION_S = 30.0
RATE_HZ = 200.0 # host command rate; the FOC loop runs ~50x faster
def mit_command(bus, p_des, v_des, kp, kd, tau_ff):
"""Pack and send one MIT-mode frame.
Implement the byte layout from the CubeMars manual for YOUR firmware
version: each field is quantized into a fixed range before packing,
and the ranges differ between models. Do not copy offsets from forums.
"""
raise NotImplementedError
def read_reply(bus):
"""Unpack the status reply -> (pos_rad, vel_rad_s, torque_nm)."""
raise NotImplementedError
def run(bus):
log = []
t0 = time.monotonic()
while (t := time.monotonic() - t0) < DURATION_S:
w = 2.0 * math.pi * FREQ_HZ
p_des = AMPL_RAD * math.sin(w * t)
v_des = AMPL_RAD * w * math.cos(w * t) # feedforward the derivative
mit_command(bus, p_des, v_des, KP, KD, tau_ff=0.0)
pos, vel, tau = read_reply(bus)
log.append((t, p_des, pos, v_des, vel, tau))
time.sleep(max(0.0, 1.0 / RATE_HZ - (time.monotonic() - t0 - t)))
return log # save raw arrays; analysis happens offlineBench bring-up: make the AK60-6 move, then feel the gains
Work through bring-up in stages, verifying each before the next. (1) Wiring: 24 V bench supply to the power leads with polarity triple-checked (XT30-class connector), supply current limit set low — 1–2 A — for first power-up; CAN H and CAN L as a twisted pair to the USB–CAN adapter; 120 Ω termination across H/L; common ground between supply and adapter; every pin verified against the CubeMars manual, not guessed from wire colors. (2) Bus-up: configure the adapter to the bitrate given in the manual and confirm you can see the actuator's frames before commanding anything. (3) Motion: enable MIT mode, command the sinusoid from bench_sinusoid.py with the soft gains given, and log , , , , for 30 seconds. (4) Feel: with the sinusoid stopped and a constant held, gently try to turn the output hub by hand at , then 8, then 20 N·m/rad; repeat with at 0 versus 1. Write five sentences connecting what each gain change felt like to what the logged torque trace showed during your disturbance.
Need a hint?
If the bus is silent: measure the resistance across CAN H–L (about 60 Ω with two 120 Ω terminators present), check the common ground, and confirm the bitrate matches the manual — a bitrate mismatch looks exactly like a dead bus. Plot logged position over the commanded sinusoid: a phase lag that shrinks as rises is the impedance law working. Keep fingers on the smooth output hub, away from any lever arm or pinch point, and leave the supply current limit low until the gains are proven.
Where this goes next: you can now read the arm the way you read a device datasheet — integrated impedance-controlled joints behind a 500 Hz real-time controller, with a driver whose modes are corner cases of one firmware law. The next lesson, Coordinate frames and rigid transforms, stacks this hardware into geometry: how six joint angles compose into a gripper pose, and why the frame conventions you choose now decide how painful hand-eye calibration with the wrist D405 will be later. The 2 ms control tick and the UDP state stream you met here become line items in the latency budget of Time is a sensor: clocks, timestamps, and latency, and the stop levels and effort clamps become hard requirements of Lab 0's episode logger.