Stepper Motor Simulator: Step Loss and Lag Angle
Watch a stepper rotor trail the commanded field, push it past 90 electrical degrees, and see exactly how a machine loses position without anything noticing.
- Category
- Embedded Systems
- Time
- 20–40 min
- Platform
- Browser · Arduino · ESP32
01 / Start here
Introduction
A stepper does not take steps — it drags a rotor behind a rotating magnetic field, and the angle between them is what decides whether the move lands. This lab integrates that rotor's equation of motion directly: nothing in it looks for step loss, so when the lag angle runs away past the 90 degree pull-out limit the motor falls out of sync on its own, exactly as one on a bench would.
Live lab / Open-loop step integrity
Stepper motor simulator
Command a move and watch the rotor trail the field. Push the acceleration, drop the voltage or add load until the lag angle runs past the 90° pull-out limit — and the motor silently stops arriving.
Upper plot: the dashed line is where the driver thinks the axis is, the solid line is where the rotor actually is. Lower plot: the lag angle between them. The shaded band is where a steady load can sit — past ±90 electrical degrees the torque starts falling instead of rising, so there is no equilibrium out there, and a rotor that keeps going reaches 180° and slips a pole.
- Commanded (what the driver counted)
- Actual rotor position
- Where a load can sit (±90°e)
- Sync lost
- Full steps lost
- 0
- Peak lag
- 25°e
- Position error
- 0.000 mm
- Resolution
- 80 steps/mm
- Pull-out torque
- 0.311 N·m
- Corner speed
- 599 rpm
Every action has a button above. If you prefer the keyboard, focus the plots and press P for the next preset, M for the next microstepping setting, N to toggle the acceleration ramp, R to reset and F for full screen; sliders respond to the arrow keys.
Controls
The lab re-runs the entire move on every input, so the traces track the slider as you drag it rather than waiting for a Start press. There is nothing to start — the move is 30 000 integration steps and it finishes before your finger moves again.
| Control | What it changes |
|---|---|
| Preset | Five worked situations, from a clean move to four different ways of failing |
| Step rate | A trapezoidal acceleration ramp, or the step rate jumping straight to full |
| Distance / Top speed / Acceleration | The move you are asking for |
| Supply voltage | What the driver has to push current into the winding with |
| Current limit | What you set with the trim pot — this is the peak torque |
| Microstepping | How finely the field is divided, 1/1 to 1/16 |
| Mechanism | GT2 belt (40 mm/rev) or T8 lead screw (8 mm/rev) |
| Resisting torque | Friction and load at the motor shaft |
The upper plot is position: the dashed line is where the driver believes the axis is, the solid line is where the rotor actually is. The lower plot is the lag angle between them, with the stable ±90° band shaded.
Theory
A stepper does not take steps. The driver rotates a magnetic field in increments and the rotor follows it, always trailing behind by some angle — and the torque it produces is the sine of that angle:
T = T_peak(ω) × sin(δ)
δ is measured in electrical degrees. A 1.8° motor has 50 rotor teeth, so its magnetics repeat 50 times per revolution and one full step is exactly 90 electrical degrees. That conversion is the only arithmetic in the whole topic.
Between −90° and +90° the motor is a spring: fall further behind, get pulled harder. That stable region is why an open-loop stepper works at all, and it is where any steady load has to sit. Past 90° the sine turns over, so falling further behind produces less restoring torque, which makes it fall further behind still — and at 180° the torque reverses outright and starts pulling toward the next pole. The rotor slides to the next stable tooth, one electrical cycle — four full steps — away.
Those are two different numbers and the lab keeps them apart. 90° is where the equilibrium ends; a brief overshoot past it is survivable, which is why a full-step command — a 90° kick, every step — does not destroy the machine. 180° is the point of no return.
Three things grow the lag angle:
- Load. At steady speed
sin(δ) = demanded ÷ available. A load at half the available torque sits at 30°, leaving 60° of margin. - Acceleration. Getting the rotor moving costs torque from the same budget, so the lag is worst on the ramp, not at speed.
- Speed, because
T_peakitself falls as the motor turns faster.
That last one is the least obvious. A winding is an inductor, and the faster the motor spins, the harder it is to force current in and back out again — reactance rises with electrical frequency, and the spinning rotor’s own back-EMF eats the supply voltage. The result is a torque curve that is flat up to a corner speed and falls away past it, and the corner is set by supply voltage.
Which gives the two rules the lab exists to make obvious: more amps buy torque, more volts buy speed.
Algorithm
The rotor is integrated directly. Nothing in this code detects step loss, inserts it, or knows it is possible — it falls out of the sine law.
// per 20 us tick
const ideal = sampleProfile(plan, time).position; // mm the planner wants
const command = Math.round(ideal * stepsPerMm) / stepsPerMm; // the driver only does whole microsteps
const lag = rotorTeeth * (commandAngle - theta); // electrical radians
const { torque } = peakTorque(setup, omega); // falls with speed
const friction = loadTorque * Math.tanh(omega / 0.5); // smooth Coulomb, one line
const accel = (torque * Math.sin(lag) - friction - damping * omega) / inertia;
theta += omega * dt + 0.5 * accel * dt * dt;
omega += accel * dt;
Two details matter more than they look.
The command is quantised onto the microstep grid before the rotor sees it. That staircase is the excitation: at full stepping, each command is a 90-electrical-degree kick at a very lightly damped resonance, which is exactly why a full-stepping motor screams and a microstepping one does not.
The winding model sets the torque ceiling:
const backEmf = kT * speed;
const impedance = Math.hypot(resistance, rotorTeeth * speed * inductance);
const current = Math.min(currentLimit, Math.max(0, supplyVolts - backEmf) / impedance);
const torque = Math.SQRT2 * kT * current;
It is a lumped steady-state approximation of a chopping driver, not a phase-by-phase simulation. It reproduces the shape of a real torque curve and gets both engineering answers right; it runs conservative at the very top end.
The commanded trajectory comes from the site’s existing motion profile planner, so “with a ramp” and “without one” are the same code path with a different acceleration limit — there is no separate failure mode written in.
Guided experiments
Each one isolates a single variable. Change nothing else.
1. The ramp is the whole machine. Leave everything at its defaults and flip Step rate between Ramped and Straight to full speed.
| Ramped | Straight to full speed | |
|---|---|---|
| Peak lag | 25°e | past 90° at 1.2 ms, gone by 3.0 ms |
| Full steps lost | 0 | 280 |
| Where the axis ends up | 60.00 mm | 4.0 mm |
Same motor, same driver, same current, same target. The only difference is whether the step rate was ramped, and the difference between them is the entire move.
2. Find the pull-in rate. Set Straight to full speed, then raise Top speed from 100 mm/s in steps. It works, and works, and then somewhere around 200 mm/s it stops working completely — not gradually. That threshold is the pull-in rate, and it is a real number on real motors.
3. Volts buy speed. Load the 12 V supply, run fast preset: the move stalls. Now raise Supply voltage to 24 and watch it complete, with the current limit untouched. Then go back to 12 V and try to rescue it with Current limit instead — you cannot. Past the corner speed the driver was never reaching the set current.
4. Microstepping does not buy accuracy. Fix everything and step Microstepping from 1/1 to 1/16, reading two things:
| Microstepping | Step size | Worst deviation from the path | Steady-state error from load |
|---|---|---|---|
| Full step | 0.200 mm | 0.127 mm | 0.049 mm |
| 1/4 | 0.050 mm | 0.061 mm | 0.044 mm |
| 1/16 | 0.0125 mm | 0.045 mm | 0.044 mm |
The tracking improves about three times and then stops. The load error does not move at all. It was 0.044 mm at full stepping and it is 0.044 mm at sixteenth stepping — the difference is that 0.044 mm is a quarter of a full step and three and a half microsteps, so fine microstepping makes the same error look much worse in the units the driver reports.
5. Pull-out. Raise Resisting torque slowly with a modest speed set. Watch the lag angle in the lower plot climb toward the dashed line. It will sit at 30°, then 50°, then 70° — and then, over one small slider increment, sync is gone. There is no warning region, because there is no equilibrium past 90° for it to settle into.
7. Full stepping is a 90° kick. Load the Full stepping preset and read the peak lag: about 102°e. Every single step commands the full 90°, and the rotor’s momentum carries it past. It still arrives — nothing is lost — but it spends the whole move swinging through the region a loaded motor cannot afford to enter. That is simultaneously why full stepping is loud and why it leaves so little margin for load.
6. The mechanism changes the problem. Switch Mechanism to the T8 lead screw with everything else fixed. Resolution goes from 80 to 400 steps/mm, and the same top speed now needs five times the step rate.
What you should observe
- Failure is a cliff, not a slope. Nothing degrades. The lag angle grows smoothly right up to 90° and then the move is simply gone. This is the defining difference between a stepper and a servo-style closed loop, which just slows down under load.
- The machine never knows. The dashed commanded line always finishes exactly on target, because the driver counted every pulse it was given. Only the solid line tells the truth, and no real machine has that line.
- The load error is always there. Even in a perfect run the rotor sits tens of microns behind where the driver says it is. That is not a fault; it is what
sin(δ) = load ÷ torquerequires. - Lost steps are always a multiple of four. Try to produce a count that is not.
Taking it to hardware
| This lab | A real axis |
|---|---|
| Ideal 50 kHz step timing | A microcontroller’s timer jitter, and a ceiling near 10–15 kHz on an Arduino Uno |
| One rigid mass | Belt stretch, backlash, a frame that flexes |
| Constant resisting torque | Friction that varies along the axis, and cable drag |
| A lumped winding model | A real torque curve, with detent torque and mid-band resonance |
| Perfect current regulation | A driver that thermally throttles when the limit is set too high |
| Nothing ever gets hot | 8 W per motor, continuously, whenever it is energised |
The most important difference is the last one in a different sense: a real machine gives you no lower plot. You cannot see the lag angle, so you cannot see the margin you are running on. The practical substitute is to find the failure point deliberately — raise speed and acceleration until the axis skips, then back off by 30 to 50 percent and live there.
Start from the wiring and current-limit walkthrough, calibrate steps per millimetre against a ruler, and put the whole thing together in the pen plotter build.
Source code
The ramp is not optional on a real machine. The blocking version below is what most first sketches look like, and it is the one that buzzes:
// What not to ship: the step rate jumps from zero to full.
void moveBlocking(long steps, unsigned delayUs) {
for (long i = 0; i < steps; i++) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(2);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(delayUs); // constant => no acceleration at all
}
}
The ramped, non-blocking version. This is the same trapezoid the lab plans, generated one step at a time so nothing else on the microcontroller stalls:
#include <AccelStepper.h>
const float STEPS_PER_MM = 80.0; // 20T GT2 pulley at 1/16 stepping
AccelStepper axis(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
void setup() {
pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, LOW); // active low: energise
axis.setMaxSpeed(200 * STEPS_PER_MM); // 200 mm/s
axis.setAcceleration(2000 * STEPS_PER_MM);
}
void loop() {
if (axis.distanceToGo() == 0) {
axis.moveTo(axis.currentPosition() == 0 ? lround(60 * STEPS_PER_MM) : 0);
}
axis.run(); // returns immediately — call it constantly
checkEndstops(); // which is only possible because it does
}
setMaxSpeed and setAcceleration give you a trapezoidal profile and nothing else — there is no S-curve option in AccelStepper. For a jerk-limited profile you generate the setpoint yourself and feed it to moveTo on a timer.
If you want to know whether your own machine is near the edge, the cheap test is a repeatability one:
// Drive a long move out and back, N times, then check whether home moved.
// Anything other than a multiple of four full steps is not step loss.
for (int i = 0; i < 20; i++) { moveTo(LONG_MOVE); runToPosition(); moveTo(0); runToPosition(); }
// now re-home and compare the reported position against zero
Assumptions and hardware differences
The rotor dynamics are honest: torque proportional to sin(δ) is how a hybrid stepper works, and the slip behaviour is a consequence of integrating it rather than a rule written into the code. The constants describe a 17HS4401-class NEMA 17 — 1.8°, 1.7 A, 2.8 Ω, 3.2 mH, 0.44 N·m holding, with a reflected 300 g carriage on the shaft.
What the model simplifies: the winding is lumped into a steady-state impedance and back-EMF term rather than simulated phase by phase, so the far tail of the torque curve is pessimistic. Detent torque — the small cogging you feel turning an unpowered stepper by hand — is not modelled, nor is mid-band resonance, nor the driver’s decay-mode behaviour, nor any thermal effect. Timing is ideal, so the step jitter of a real microcontroller does not appear.
None of those change which knob to turn. All of them are reasons to verify a real machine rather than trust a number off this page.
Circuit diagram
The physical setup this models is four wires from the motor to a driver, two wires from the driver to a microcontroller, and one capacitor that people keep leaving out:
Arduino 5V ──── VDD
Arduino GND ──── GND (logic) ──┬── GND (motor) ──── PSU −
PSU + (12–24 V) ──── VMOT ──┬──┴── 100 µF electrolytic, close to the board
Pin 3 ──── STEP RESET ──┬── SLEEP
Pin 4 ──── DIR MS1, MS2, MS3 ──── 5 V (1/16)
Pin 5 ──── ENABLE (active low)
Coil A ──── 1A, 1B Coil B ──── 2A, 2B
The A4988 component page has the full pinout, the Vref table for both sense-resistor variants, and the reason that capacitor is not optional.
Hardware checklist
Components
- A bipolar stepper — the 1.8°, 200 full-step kind
- A current-regulating driver with an adjustable limit
- A motor supply of 12 V or more, and the sense to measure it
- A belt or screw axis, because steps/mm is half the answer
Explore the graph
Where this simulator is used
The projects, learning paths, and tutorials that build on this lab.
Continue building
Download resources
Use these on-page references while working through the project. Downloadable project bundles will be added only after their source and version are published.
Common questions
Frequently asked questions
Why does my stepper motor lose steps?
Because the rotor fell too far behind the commanded field. Inside 90 electrical degrees — two full steps — the motor pulls itself back into line, so that is where any steady load has to sit. Past 90 the torque available decreases the further behind it gets, and by 180 it has reversed and is pulling toward the next pole instead, so the rotor slides to the next stable point an entire electrical cycle away. Three things push it past that limit: a load bigger than the torque available, an acceleration steep enough that getting the rotor moving costs more torque than is left, and speed, because available torque falls as the motor turns faster. The lab lets you trip each one separately.
Why does it lose exactly four steps at a time?
Because the magnetic pattern repeats every four full steps. When the rotor breaks sync it does not stop at some arbitrary angle — it slides until it catches on the next stable tooth, which is one full electrical cycle away, and one electrical cycle is 360 electrical degrees, which is four full steps. The lab never enforces this; it integrates the sine torque law and the lost-step count always comes out a multiple of four on its own. If your machine is ever off by a number that is not a multiple of four full steps, the cause is not step loss.
Does microstepping make my machine more accurate?
It makes it smoother, not more accurate. Set the microstepping selector through 1/1 to 1/16 at a fixed load and watch the two numbers separately: the deviation from the commanded path falls by about three times, because you have stopped kicking the rotor 90 electrical degrees at a time and it has stopped ringing. The steady-state error caused by the load does not move at all — it is 0.044 mm at full stepping and 0.044 mm at sixteenth stepping. Dividing the step made that error look bigger in units of steps; it did nothing to the error.
Should I raise the voltage or the current to fix a stall?
Depends where it stalls, and the lab makes the difference obvious. Below the corner speed the driver is holding the current limit you set, so torque is set by current — raise the limit. Above the corner speed the driver can no longer push the set current into the winding against reactance and back-EMF, so torque is set by supply voltage and raising the current limit changes nothing except how hot everything gets. Load the 12 V preset and watch the same move that fails at 12 V sail through at 24 V without touching the current.
Why does the same move work with a ramp and fail without one?
Because getting the rotor up to speed takes torque, and at the instant a move begins the rotor is at rest while the commanded field is already sprinting. Switch the step rate control to straight to full speed with everything else unchanged and the lag is past 90 degrees within about a millisecond and gone by three: the driver counts out the whole 60 mm move while the axis reaches 4 mm. That limit is the pull-in rate, the highest step rate you can command from standstill, and an acceleration ramp is what keeps you under it.
Is this what a real motor does?
The rotor dynamics are exact — torque proportional to the sine of the lag angle is how a hybrid stepper actually works, and the slip behaviour follows from integrating it. The winding model is a lumped steady-state approximation of a chopping driver rather than a phase-by-phase simulation, so it reproduces the shape of a datasheet torque curve and gets the engineering answers right, but it runs conservative in the tail. Use it to build intuition and to see which knob matters; use a real torque curve when you are sizing a machine.
Further reading
References
Authoritative sources for going deeper than this simulator's bounded educational model.