Tutorial · Intermediate · 30 min
Homing With Endstops: Machine and Work Coordinates
An open-loop machine wakes up not knowing where it is. Homing is how it finds out — the three-phase seek, soft limits, and why machine zero is not part zero.
The problem homing solves
A stepper axis running open loop has a perfect memory of everything it has been told to do and no knowledge whatever of where it is. Power it on and its internal position counter reads zero — not because the carriage is at zero, but because that is what an integer initialises to.
Worse, the counter can be wrong even while the machine is running. A lost step is silent: the driver keeps counting pulses, the firmware keeps believing them, and the carriage is now permanently offset by some multiple of four full steps. Nothing in the control loop will ever notice, because there is no loop.
Homing is the answer to both. It is the one moment in a machine’s operation when position is measured from the physical world rather than accumulated from a count — and everything downstream depends on it.
The three-phase seek
Drive at the switch, stop when it trips, call that zero. That works, and it repeats to about half a millimetre, which is useless. Every real machine does it in three passes:
- Fast seek. Drive toward the switch at a speed chosen for impatience, not precision. This pass exists only to get close, and it may travel the full length of the axis.
- Back off. Retreat a few millimetres until the switch releases. This is what makes the third pass possible, and it also proves the switch is a switch and not a stuck input.
- Slow re-seek. Approach again at a small fraction of the first speed and record the trip point. This is the measurement.
The reason the slow pass matters is mechanical. A lever microswitch trips when its plunger has moved a specific distance, but the carriage keeps moving while the firmware reacts and while the axis decelerates. At 50 mm/s with a 2 ms detection latency, the carriage travels 0.1 mm past the trip point before anyone knows. At 2 mm/s, it travels 0.004 mm. The slow approach converts the switch’s own repeatability — a few microns on a decent microswitch — into the machine’s repeatability, instead of letting deceleration distance dominate it.
Typical settings look like this:
| Parameter | Typical | Why |
|---|---|---|
| Fast seek feed | 30–50 mm/s | Fast enough not to be annoying |
| Back-off distance | 3–5 mm | Enough to release the switch fully |
| Slow seek feed | 1–3 mm/s | Deceleration distance becomes negligible |
| Pull-off after homing | 1–5 mm | Leaves the switch released so it can be a limit too |
Wiring an endstop
The switch itself is the same lever microswitch a robot uses as a bumper, and the wiring rule is the same:
const int X_MIN_PIN = 9;
void setup() {
pinMode(X_MIN_PIN, INPUT_PULLUP); // NO to ground, pin pulled up
}
bool endstopTriggered() {
return digitalRead(X_MIN_PIN) == LOW;
}
Wire COM to ground and NO to the pin, with the internal pull-up on. A broken wire then reads as “not triggered”, which for a homing switch is the safe failure: the machine fails to find home and stops, rather than deciding it is already there.
That is the opposite of the rule for an emergency stop, where you want normally closed so that a cut wire stops the machine. A homing switch and an e-stop have opposite safe states, and wiring both the same way is a real mistake rather than a stylistic one.
Two details that cause most endstop trouble:
- Debounce it. A single press produces about nine transitions over 4.7 ms. During homing you are polling in a tight loop, so the first edge is the one you want and everything after it should be ignored for a few milliseconds. Never attach an interrupt that counts edges.
- Route the endstop lead away from the motor leads. A long unshielded wire running parallel to a chopping stepper cable picks up exactly the kind of noise that looks like a switch closure. If homing works with the motors disabled and fails with them running, this is why.
Machine coordinates and work coordinates
Once homed, the machine knows one thing precisely: where the endstop is. That point, plus a fixed offset, defines machine zero — in G-code, the G53 coordinate system. It is a property of the frame. It does not move, and it is the same after every homing cycle.
That is almost never where you want to measure your work from. So machines keep a second, movable origin: the work coordinate system, G54 through G59, defined as an offset from machine zero.
| Machine coordinates (G53) | Work coordinates (G54…) | |
|---|---|---|
| Origin | Fixed by the endstops and frame | Wherever you set it |
| Set by | Homing | You, per job |
| Survives a restart | Yes, after re-homing | Yes, stored in the controller |
| Used for | Soft limits, tool changes, park positions | Every line of the actual job |
| Changes when you re-clamp | No | Yes — that is the point |
The separation earns its keep the moment you run the same job twice. The G-code says “draw a 50 mm square starting at X0 Y0”. Tape the paper down somewhere convenient, jog to its corner, zero the work coordinates there, and run. Move the paper, re-zero, run the identical file. The program never changed; the offset did.
Machine zero is what the frame knows. Work zero is what the job knows. Keeping them separate is why you do not have to edit a file to move a part.
Soft limits
With a known machine origin, the firmware can refuse moves that would leave the envelope — before the carriage gets there. These are soft limits, and they are strictly better than the hardware kind because they stop the machine at a planned decelerate rather than by collision.
; GRBL
$20=1 ; soft limits on
$130=200.0 ; X max travel, mm
$131=200.0 ; Y
$132=60.0 ; Z
A machine with soft limits enabled and no homing cycle is a machine with soft limits it cannot enforce, because the envelope is measured from an origin it does not have. Most firmware therefore refuses to run until homed, and the error message that produces is not a nuisance — it is the interlock working.
Hardware limit switches at the far end of each axis are a separate, cruder backstop for when the soft limits are wrong. Many small machines skip them and rely on the soft limits alone, which is a reasonable trade when nothing on the machine can hurt you.
The state machine
Homing is naturally a small finite state machine, and writing it as one keeps the timeout handling honest:
enum HomeState { IDLE, SEEK_FAST, BACK_OFF, SEEK_SLOW, HOMED, FAILED };
HomeState state = IDLE;
long stateStartedAt = 0;
void homingStep() {
switch (state) {
case SEEK_FAST:
if (endstopTriggered()) { stopNow(); enter(BACK_OFF); }
// an axis that never finds its switch must give up, not grind
else if (travelled() > AXIS_LENGTH_MM + 10) enter(FAILED);
break;
case BACK_OFF:
if (!endstopTriggered() && travelled() >= BACKOFF_MM) enter(SEEK_SLOW);
break;
case SEEK_SLOW:
if (endstopTriggered()) {
stopNow();
machinePosition = HOME_OFFSET_MM; // the one real measurement
enter(HOMED);
} else if (travelled() > BACKOFF_MM * 2) enter(FAILED);
break;
}
}
The FAILED branches are the part worth copying. An axis that seeks and never trips has a disconnected switch, a jammed carriage, or is losing every step it is given — and in all three cases the right answer is to stop and say so. A homing routine with no timeout will happily grind a carriage into a frame for as long as you let it.
When it goes wrong
| Symptom | Cause | Fix |
|---|---|---|
| Homes to a slightly different spot each time | No slow second pass, or it is too fast | Add the re-seek; drop it to 1–3 mm/s |
| Drives the wrong way and crashes | Homing direction or motor direction inverted | Flip the direction setting, or reverse one coil pair |
| “Endstop triggered” before it moves | Switch wired normally closed, or stuck | Check NO vs NC; confirm the pin reads high when released |
| Works cold, fails once the motors run | Endstop lead picking up driver noise | Re-route away from motor cables; add a 100 nF to ground |
| Homes fine, then every move is offset | HOME_OFFSET or pull-off wrong |
Measure from the switch to where you want zero |
| Machine refuses to move after power-up | Soft limits on, not homed | Home it — that is the interlock, not a fault |
| Repeats well but the part is in the wrong place | Work offset, not homing | Re-zero G54 where the job actually is |
| Z homes into the bed | Z direction inverted, or probe offset unset | Check direction before you trust any offset |
Where this fits
Homing is how an open-loop machine recovers the one thing it structurally cannot measure. It is worth being clear about what it does and does not buy you: it makes the machine’s position correct at the start of a job, and nothing more. If the axis is losing steps mid-job because the current limit or the acceleration profile is wrong, homing between runs just resets the error to zero each time without ever fixing it.
The tell is simple. A machine that homes accurately and finishes a job in the wrong place has a motion problem, not a homing problem.
Explore the graph
Part of these builds
Projects and learning paths that include this tutorial.
Further reading