Robohouse ’26 Library
Contents

Chapter 8

StallGuard, CoolStep, and sensorless homing

8 sections · about 7 minutes

8.1 What StallGuard measures

StallGuard is easier to use once you know what it measures, so the principle comes before the registers.

When a stepper turns, the rotor's magnets moving past the stator coils induce a back-EMF in those coils. The magnitude of that back-EMF depends on speed, and its phase relative to the driving current depends on load. Under no load, the rotor sits very close to the field position and the phase relationship is one thing. Under increasing load, the rotor lags further and further behind the rotating field — this is the load angle — and the phase relationship shifts correspondingly. At the point where the load angle reaches 90 electrical degrees, the motor produces maximum torque; beyond that it loses synchronism and stalls.

StallGuard measures that phase relationship, indirectly, by observing the coil voltages during the chopper cycle. It reports the result as SG_RESULT, a number from 0 to 510. High means lightly loaded, low means heavily loaded. As the motor approaches a stall, SG_RESULT falls towards zero.

StallGuard4, which is the version in the TMC2209, is designed to work with StealthChop. This is a change from the older StallGuard2, which required SpreadCycle. For the TMC2209 the recommendation is to use StealthChop when you want StallGuard to work well.

8.2 The registers and the stall condition

SG_RESULT at register 0x41 gives you the raw measurement. It updates once per full step, so at low step rates you get relatively few samples.

SGTHRS at register 0x40 is a threshold from 0 to 255. The driver declares a stall — and asserts DIAG — when:

SG_RESULT2×SGTHRS\texttt{SG\_RESULT} \le 2 \times \texttt{SGTHRS}

So a higher SGTHRS means a more sensitive detector, triggering on lighter loads. The direction is easy to get backwards.

TCOOLTHRS at register 0x14 gates the whole thing. StallGuard output to DIAG is only active when the motor is running faster than the velocity corresponding to TCOOLTHRS — that is, when the measured TSTEP is less than or equal to TCOOLTHRS. Forget to set TCOOLTHRS and DIAG never fires — StallGuard is not broken, it is disabled.

The reason for this gate is that StallGuard is unreliable at very low speeds, where there is not enough back-EMF to measure, and it also misbehaves at very high speeds. There is a usable band in the middle, and your homing speed needs to be inside it.

8.3 What makes StallGuard hard to tune

StallGuard is sensitive to a lot of things, which is why it has a reputation for being fiddly.

SG_RESULT depends on speed — the same physical load reads differently at different velocities, which is why you must home at a fixed, repeatable speed. It depends on motor current — change IRUN and your threshold no longer applies. It depends on supply voltage, so a sagging rail shifts your readings. It depends on the specific motor, since inductance and back-EMF constant vary between part numbers and even between units. And it depends on the mechanics: a joint with a stiff belt, a tight gearbox, or a bearing that binds slightly at one end of travel will show a lower baseline SG_RESULT in that region, which can look like a stall.

On a geared robot joint there is an extra complication. The reduction between motor and joint means the motor sees the mechanical stop through 20:1 of gearbox. The gearbox has compliance and friction of its own, and the load rise when the joint hits its stop is softened and delayed. StallGuard on a directly-driven axis is much crisper than on a heavily geared one, which means tuning per joint rather than globally.

8.4 A practical tuning procedure

Do this once per joint, and write the numbers down.

Start by configuring the driver in StealthChop with your final running current — the current you will actually use — and set TCOOLTHRS to a large value so StallGuard is enabled across a wide speed range while you experiment.

Now move the joint at your intended homing speed, away from any obstruction, and log SG_RESULT continuously. You are looking for the free-running baseline. It should be fairly steady; note the value and note how much it varies over the joint's travel. That variation is your noise floor, and it limits how tight a threshold you can use.

Next, run the joint slowly into its mechanical stop and watch SG_RESULT fall. Note the value at which it clearly and repeatably drops. You now have two numbers: a free-running baseline and a stalled value.

Set SGTHRS to roughly halfway between them, divided by two (since the comparison is against 2 × SGTHRS). Then test it: home the joint twenty times and confirm it triggers every time, and never triggers spuriously during free motion. Nineteen times out of twenty is not good enough for something the rest of the machine's positioning depends on.

Finally, set TCOOLTHRS so that StallGuard is active at your homing speed but inactive at your normal operating speeds. You do not want a fast move that momentarily encounters high load to be interpreted as a stall.

Repeat for every joint, at that joint's own homing speed and current.

8.5 Wiring and reading DIAG

The DIAG pin is a push-pull output referenced to VIO, so with VIO at 3.3 V it is directly Teensy-safe. Give each driver its own Teensy input pin rather than wire-ORing them — you have the pins, and knowing which axis stalled is more useful than knowing one of them did.

You can attach an interrupt to it, which is the right approach for a fast reaction. Two things to note: DIAG is a level rather than a pulse, so it stays asserted while the stall condition persists; and during normal motion, especially acceleration, you may see brief assertions. A short debounce — require the line to stay asserted for a few hundred microseconds — removes most false triggers without adding meaningful latency.

Also note that DIAG can be configured to signal driver errors rather than stalls, and in some configurations both. If your DIAG line asserts the instant you enable the driver, check DRV_STATUS for an over-temperature or short flag before assuming it is a stall.

8.6 The homing state machine

A sensorless homing routine for one joint looks like this in outline.

Configure the driver: StealthChop, homing current, appropriate TCOOLTHRS, tuned SGTHRS. Enable the driver and wait briefly for the current to settle. Now accelerate the joint towards the hard stop, but do not enable stall detection during the acceleration phase — acceleration loads the motor, SG_RESULT drops, and you trigger immediately. Wait until you are at constant homing speed, then arm the detector.

Move until DIAG asserts, or until you have travelled more than the joint's full range, at which point abort rather than continue pushing. On detection, stop immediately, back off by a fixed small amount, and optionally repeat the approach at a lower speed for better repeatability — the classic two-pass home. Set the joint's position counter to the known angle of that hard stop. Disarm stall detection, restore normal running current, and move to a safe standby position.

Then do the next joint. Order matters: home the joints that can crash into things last, or arrange the sequence so that each joint is homed in a configuration where its motion is unobstructed. On a six-axis arm, homing J2 and J3 while the arm is folded up can drive links into each other.

8.7 Should you use sensorless homing at all?

On a robot arm I would use it as a secondary system rather than the primary one.

The PAROL6 uses limit switches for its open-loop homing. A limit switch triggers at the same physical point every time, regardless of temperature, supply voltage, or gearbox condition, and StallGuard's trigger point moves with all of those. For a machine whose accuracy rests on knowing where zero is, that difference in repeatability matters.

Where StallGuard earns its place is as a safety and diagnostic layer. Monitor SG_RESULT continuously during normal operation and you have a real-time measure of how hard each joint is working. A sudden drop means a collision, and stopping the arm within a few milliseconds is worth having for the sake of both the arm and whatever it hit. A gradual drift downward over weeks means a bearing is going or a belt is over-tensioned. A limit switch tells you neither.

So: limit switches for homing, StallGuard for collision detection and condition monitoring, and CoolStep on top if you want the current management.

8.8 CoolStep

CoolStep uses the same SG_RESULT measurement for a different purpose: automatic current adjustment. You configure a window with SEMIN and SEMAX in the COOLCONF register. When SG_RESULT rises above the window — meaning the motor is lightly loaded — the driver reduces current. When it falls below, the driver increases current back up to IRUN.

The effect is that a motor which spends most of its time lightly loaded runs cool, and only draws full current when it actually needs the torque. On a PETG-framed arm with a documented thermal limit, that is worth having.

The caveats are that CoolStep requires the same velocity gating as StallGuard via TCOOLTHRS, and that it needs SG_RESULT to be well-behaved, which means you should tune StallGuard first. It also changes current dynamically, so any StallGuard threshold tuned at fixed current no longer holds. Until you are confident, use one or the other on a given joint during a given operation rather than both.