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

ConfigurationAudible ringPosition error (avg)
8 µsteps, SpreadCyclesevere0.42 mm
64 µsteps, StealthChopmild0.11 mm
+ band-avoiding plannernone0.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.