# Microstep Shift in Z-Axis Probing
## Issue Description
During Z-axis probi…ng operations, there's a consistent microstep difference between the trigger position and the halt position that should not be present, depending on driver microstep settings and probing speed. This difference is added to the commanded stepper position which causes it to accumulate over multiple probe moves, resulting in inaccurate bed meshes (with probes that require movement in Z) and potentially affecting first layer consistency and printed geometry.
## Reproduction Steps
1. Configure Z steppers to use 64 microsteps
2. Set probing speed to 3 mm/s
3. Apply diff in the `relevant code` section to log the microstep difference.
4. Perform Z probing operations (e.g loop `PROBE` and optionally `GET_POSITION` at the same x/y position).
5. `grep "Probe move" ~/printer_data/logs/klippy.log` to show the step difference, and `grep "Setting toolhead position" ~/printer_data/logs/klippy.log`, to show the continuous drift.
Note: this may require a sufficiently high resolution z-axis or faster probe speeds to reproduce, this bug is consistently reproduced with TR8*4 leadscrews (a rotation distance of 4mm), 1.8 degree steppers, 64 microsteps and 3 mm/s of probing speed. Anything resulting in more steps generated pr sec will show the issue. The microstep shift scales with steps per second.
## Observed Behavior
- A consistent microstep shift occurs between trigger and halt positions during probing
- The shift accumulates with each probe move, moving the nozzle closer to the bed
- Bed mesh results are shifted, with error scaling with point count and probe samples
## Expected Behavior
Trigger and halt positions should align without a consistent shift during probing operations. Unsure about multi-mcu setups, but the issue would be present there as well.
## Additional Observations
- The issue is specific to probing; homing exhibits the same shift but doesn't have major real-world impact as the position is reset, so no accumulation.
- Reducing microsteps to 16 eliminates the issue when probing at 3 mm/s.
- Driver interpolation does not affect the outcome.
- Setting ENDSTOP_SAMPLE_COUNT to 1 eliminates the issue at the cost of disabling noise filtering (which seems unnecessary on modern printer hardware)
- Increasing ENDSTOP_SAMPLE_COUNT exacerbates the problem (e.g., 32 samples increases error to 4-5 microsteps per probe move in our case)
- The issue has been reproduced on several machines with different stepper drivers (TMC2209, TMC5160)
- Some setups exhibit larger errors than others despite using the same stepper and driver configurations
- Increased probing speeds may increase the error.
- There are possible edge cases where X and Y axes may drift as well.
## Relevant Code
The microstep difference was logged by applying the following patch:
```diff
diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py
index 06b52f1ec..a4a3051b4 100644
--- a/klippy/extras/homing.py
+++ b/klippy/extras/homing.py
@@ -121,6 +121,7 @@ class HomingMove:
haltpos = trigpos = self.calc_toolhead_pos(kin_spos, trig_steps)
if trig_steps != halt_steps:
haltpos = self.calc_toolhead_pos(kin_spos, halt_steps)
+ logging.info("Probe move: trigger at %s -> halt at %s", trig_steps, halt_steps)
else:
haltpos = trigpos = movepos
over_steps = {sp.stepper_name: sp.halt_pos - sp.trig_pos
@@ -130,6 +131,8 @@ class HomingMove:
halt_kin_spos = {s.get_name(): s.get_commanded_position()
for s in kin.get_steppers()}
haltpos = self.calc_toolhead_pos(halt_kin_spos, over_steps)
+ logging.info("Homing move: trigger at %s -> halt at %s (%s over steps)", trigpos, haltpos, over_steps)
+ logging.info("Setting toolhead position to %s (was: %s)", haltpos, self.toolhead.get_position())
self.toolhead.set_position(haltpos)
# Signal homing/probing move complete
try:
```
## Sample Data
With 64 microsteps and 3 mm/s probe speed, the following log output demonstrates the issue:
```
Probe move: trigger at {'stepper_z': -9578, 'stepper_z1': -9578, 'stepper_z2': -9578} -> halt at {'stepper_z': -9579, 'stepper_z1': -9579, 'stepper_z2': -9579}
Probe move: trigger at {'stepper_z': -9602, 'stepper_z1': -9602, 'stepper_z2': -9602} -> halt at {'stepper_z': -9603, 'stepper_z1': -9603, 'stepper_z2': -9603}
Probe move: trigger at {'stepper_z': -9601, 'stepper_z1': -9601, 'stepper_z2': -9601} -> halt at {'stepper_z': -9602, 'stepper_z1': -9602, 'stepper_z2': -9602}
Probe move: trigger at {'stepper_z': -9593, 'stepper_z1': -9593, 'stepper_z2': -9593} -> halt at {'stepper_z': -9594, 'stepper_z1': -9594, 'stepper_z2': -9594}
Probe move: trigger at {'stepper_z': -9595, 'stepper_z1': -9595, 'stepper_z2': -9595} -> halt at {'stepper_z': -9596, 'stepper_z1': -9596, 'stepper_z2': -9596}
```
The difference between trigger and halt is consistently 1 microstep and is added to the commanded position after each probe move. We accumulated 300 microns of drift by repeated probing and realized something was wrong.
64 microsteps, 3 mm/s probe speed and `ENDSTOP_SAMPLE_COUNT = 32`.
```
Probe move: trigger at {'stepper_z': -9594, 'stepper_z1': -9594, 'stepper_z2': -9594} -> halt at {'stepper_z': -9599, 'stepper_z1': -9599, 'stepper_z2': -9599}
Probe move: trigger at {'stepper_z': -9585, 'stepper_z1': -9585, 'stepper_z2': -9585} -> halt at {'stepper_z': -9590, 'stepper_z1': -9590, 'stepper_z2': -9590}
Probe move: trigger at {'stepper_z': -9595, 'stepper_z1': -9595, 'stepper_z2': -9595} -> halt at {'stepper_z': -9599, 'stepper_z1': -9599, 'stepper_z2': -9599}
Probe move: trigger at {'stepper_z': -9593, 'stepper_z1': -9593, 'stepper_z2': -9593} -> halt at {'stepper_z': -9598, 'stepper_z1': -9598, 'stepper_z2': -9598}
Probe move: trigger at {'stepper_z': -9595, 'stepper_z1': -9595, 'stepper_z2': -9595} -> halt at {'stepper_z': -9600, 'stepper_z1': -9600, 'stepper_z2': -9600}
Probe move: trigger at {'stepper_z': -9599, 'stepper_z1': -9599, 'stepper_z2': -9599} -> halt at {'stepper_z': -9604, 'stepper_z1': -9604, 'stepper_z2': -9604}
Probe move: trigger at {'stepper_z': -9599, 'stepper_z1': -9599, 'stepper_z2': -9599} -> halt at {'stepper_z': -9603, 'stepper_z1': -9603, 'stepper_z2': -9603}
```
The difference between trigger and halt is consistently 4-5 microsteps and is added to the commanded position after each probe move.
## Possible Causes
- The issue seems related to the `ENDSTOP_SAMPLE_TIME * (ENDSTOP_SAMPLE_COUNT - 1)` delay in position sampling.
- halt_pos and trig_pos is sourced from 2 different places in the pipeline, trig_pos is from the compressed steps, halt_pos is from itersolve (using `stepper.get_mcu_position()`) and any difference between the two is added to `stepper.get_commanded_position()`. I haven't dug into the stack enough to know if this is relevant, but maybe any difference between trigger pos and halt pos is already reflected in the system state, so it may not need to be added to the toolhead position at all? In
- The endstop oversampling was implemented to address noise in endstop signals on older machines ([approximately 7 years ago](https://github.com/Klipper3d/klipper/commit/ece1f71c645da7115f497642c16278417a1f5292)), and may be better suited as an optional feature.
## System Information
- Klipper version: `master`
- Stepper motors: 1.8 degree LDO-42STH48-2504AC
- Stepper drivers: TMC2209 (also reproduced with TMC5160)
- Microstepping: 64
- Axis rotation distance: 4 mm
- Probing speed: 3 mm/s
## Possible Solutions
1. Investigate whether the code currently applies the difference between halt_pos and trigger_pos to stepper state that already reflects halt_pos and not trigger_pos.
3. Evaluate the necessity of the current endstop oversampling approach for modern hardware and perhaps make it an opt-in with the caveat that it may produce drift in sequential probing on high resolution axes and/or fast probe speeds.