Blog Engineering
Taming Stepper Motor Resonance with TMC2209s
Somewhere around 400 mm/min, the shoulder joint of my robotic arm started singing. Not a rattle, not a grind — a clear, sustained tone, like someone bowing a string. This is stepper resonance, and if you build motion systems you will meet it eventually. Here’s what it is and the three layers of fixes that finally silenced it.
What resonance actually is
A stepper motor is a spring-mass system: the rotor is magnetically “sprung” toward each step position. Every discrete step excites that spring. When the step frequency approaches the system’s natural frequency, oscillations stack up instead of cancelling — the rotor overshoots, rings, and in bad cases loses steps entirely.
The natural frequency depends on rotor inertia and magnetic stiffness, which means it changes with load. My arm resonated at different speeds depending on its pose. Delightful.
Layer 1: microstepping
Full steps are hammer blows; microsteps are taps. Switching the TMC2209 from 8 to 64 microsteps spreads each step’s energy across smaller impulses:
driver.microsteps(64); // finer excitation, smoother torque ripple
driver.intpol(true); // interpolate to 256 µsteps internally
The intpol flag is the underrated one — the driver interpolates everything to 256 microsteps internally, so even coarse step pulses from the MCU produce smooth current waveforms.
Layer 2: StealthChop vs SpreadCycle
TMC drivers have two commutation modes. SpreadCycle is a fast hysteresis chopper — great torque at speed, but its current ripple can excite resonance. StealthChop is voltage-based and nearly silent at low speeds:
driver.en_spreadCycle(false); // StealthChop below the threshold…
driver.TPWMTHRS(0x1F4); // …SpreadCycle above it
The crossover threshold matters. I set it just above the resonant band, so the motor glides through the danger zone in StealthChop and only switches to SpreadCycle where the extra torque is actually needed.
Layer 3: don’t dwell where it hurts
The firmware fix is the most robust one: shape the velocity profile so the motor never cruises inside the resonant band. My trapezoidal planner now treats 380–450 mm/min as a transit-only zone — it accelerates through it but refuses to hold a constant speed there.
// Resonant band [vMin, vMax]: never emit a cruise segment inside it.
if (cruise > kResBandMin && cruise < kResBandMax) {
cruise = kResBandMax; // push the plateau above the band
}
Results
| Configuration | Audible ring | Position error (avg) |
|---|---|---|
| 8 µsteps, SpreadCycle | severe | 0.42 mm |
| 64 µsteps, StealthChop | mild | 0.11 mm |
| + band-avoiding planner | none | 0.06 mm |
Three layers, each attacking a different part of the physics: smaller impulses, smoother current, and simply refusing to feed the oscillator. The arm is now quieter than its cooling fan — and repeatability improved 7× as a side effect.
Keep reading
More from the bench
Engineering This Website
How vivaanshah.tech is built: Astro 5 static output on Cloudflare Pages, a dependency-free motion engine, seeded generative circuit art, and a CSP with no unsafe-inline.
Read postServing a Realtime Web UI from an ESP32
WebSockets over LittleFS with gzip-baked assets: how Helios pushes LED state to four browsers at once with ~11 ms median latency on a $4 microcontroller.
Read post