Thoughts on homing support in Klipper

The current homing system in Klipper has become troublesome. I’ve discussed this a couple of times before (eg, Klipper development goals for 2024 ). There has been a bit of work done on the probing code, but the internal homing system is still inflexible. I’ve been working on the problem again and I wanted to describe some of the ideas I’ve been considering.

To recap, the homing system today is basically how Klipper implements the G28 command. The requirement for homing stems from the fact that stepper motors don’t track their location when powered off. So, the software needs a mechanism to determine where each carriage is prior to using a printer. The G28 command basically means “do whatever it takes to determine the carriage positions relative to the printer frame”. Unfortunately, this system in Klipper has become difficult to enhance and difficult to maintain.

I have some goals for a rework:

  • Improve support for homing with “eddy” and “loadcell” type sensors. I’d also like to remove the “probe:z_virtual_endstop” and “tmc2209_stepper_x:virtual_endstop” hacks.
  • I’d like to move the homing code into discrete “extras” modules and remove the code from “kinematic” and “core” logic.
  • It should be realistically possible for printers to not define G28 at all.
  • User facing changes should provide deprecation notices with ample time.

The rest of this post describes some ideas I have to implement the above.

@dmbutyugin , @garethky , @nickw - fyi.

Improved support for new sensors

The current homing code is focused on homing with endstops. The fundamental action of homing involves “move until endstop trigger”, “pull back”, “move again until endstop trigger”, “set Z position based on trigger time”. This logic still works well for actual endstop switches, but it doesn’t work well for other types of sensors.

For loadcell, eddy sensors, and future “eddy tap” implementations the sensing system needs more control over the movement of the toolhead and it needs to perform a detailed analysis to determine the final Z position.

In the past we’ve “shoehorned” probes and sensorless homing into pretending to be an “endstop” so that one could use the existing homing code - hence the weird endstop_pin: tmc2209_stepper_y:virtual_endstop definitions. However, even these sensors really don’t work like endstop switches. It would be preferable to use the regular probe code (including multiples samples and averaging), and it would be preferable for sensorless homing to perform different movements.

Implementation idea:

  • I’ve been considering adding a new command: HOME_Z_USING_PROBE. This command would be registered by the probe.py code. It would only be available on “cartesian like” kinematics. It would descend the toolhead until the probe triggers and it would use the probe code to determine the Z position (eg, probing_move()). The kinematic code would be changed to not require a stepper_z endstop_position nor endstop_pin. The code would use only the probing code (not any of the home_rails() code).

The main advantage of a command like this is that it can utilize the more advanced probing code and support more advanced probe types. It would be like running something like: SET_KINEMATIC_POSITION Z=9999, PROBE, SET_KINEMATIC_POSITION Z=0.202319

Move homing code out of "kinematic” and “core” logic

It’s painful to trace the code flow of a homing operation. It roughly looks like:

  • on init:
    • kinematic classes declare stepper.py:PrinterRail instances
      • stepper.py:PrinterRail reads and stores homing settings from config
        • PrinterRail creates mcu.py:MCU_endstop classes
      • toolhead.py loads the extras/homing.py module
        • extras/homing.py registers the G28 command
  • on G28:
    • homing.py:cmd_G28() calls kinematics/x.py:home()
      • which calls back into homing.py:home_rails()
        • calls rails.get_homing_info(), kin.set_position(), toolhead.move(), endstop.home_start(), …
        • but sometimes calls into extras/probe.py, extras/tmc.py, extras/homing_heaters.py, extras/z_thermal_adjust.py, extras/endstop_phase.py, extras/gcode_move.py, extras/z_thermal_adjust.py, extras/bltouch.py, extras/probe_eddy_current.py, extras/smart_effector.py
    • In addition, because an incorrect G28 can cause damage, it’s behavior is frequently modified with: extras/homing_override.py, extras/safe_z_home.py, extras/gcode_module.py

There’s something like 20 different discrete modules that get directly involved with a homing operation. Unfortunately, that means it is difficult to make changes to the code and it increases the chance that a change will break an obscure area of the code.

If we can deprecate probe:z_virtual_endstop that will help significantly. I’d also like to remove steppers.py and kinematics/*.py from the homing logic.

Implementation idea:

  • Introduce a new [home_endstop stepper_x] “extras” module that adds a new HOME_ENDSTOP CARRIAGE=stepper_x command. The config might look like:
[home_endstop stepper_x]
endstop_pin: PB3
position_endstop: -5
homing_speed: 20
  • Introduce a new [home_sensorless stepper_x] “extras” module that adds a new HOME_SENSORLESS CARRIAGE=stepper_x command. The config might look like:
[home_sensorless stepper_x]
position_endstop: -5
homing_speed: 20

These would be two distinct modules. Each would directly load and store the homing settings, and directly register their associated G-Code command. The home_endstop module would perform the standard “endstop homing” available today - move until endstop triggers, back off, move again, and set position based on trigger time. The home_sensorless module would be similar, but it would not take an “endstop_pin” and the homing procedure would be slightly different. It would back off, but not home a second time (and it may perform additional checks specific to sensorless homing). The HOME_ENDSTOP and HOME_SENSORLESS commands would always home the carriage and only just the specified carriage. It would be a low-level command as homing a carriage in the wrong situation (eg, homing Z when not over the bed) could cause damage.

There are some advantages to adding new commands. The commands can fully track their own state and not require the kinematics and core code to store/interpret homing state. The code is easier to implement and describe because it has a more defined outcome than G28 (it always means home the specific carriage using the specific procedure and it never means “figure out what carriages to home and how”). Also, it would allow passing in custom parameters to the homing operation (eg, HOME_ENDSTOP CARRIAGE=stepper_x SECOND_HOME_SPEED=10).

It should be possible to not define G28

The G28 command is a mess. In the early Reprap days it simply meant “home x”, then “home y”, then “home z”. Things are much more complicated today on nearly all mainstream printers. Many printers today could damage themselves if a G28 is issued at the wrong time. For example, a G28 Z0 on printers that use a probe could easily damage the bed. On my Voron2, I use sensorless homing for XY, and a G28 X0 without the proper preparation would likely result in an incorrect X setting which could easily lead to a damaging toolhead collision. Modules like [safe_z_home] help with this, but they also lead to uncertainty for 3rd party code - to wit, “maybe issuing a G28 Z0 will do the right thing … but maybe safe_z_home wasn’t declared and G28 Z0 will rip apart the bed…”

On my Voron2, I define a [gcode_macro HOME] macro that I use to home the printer. I always home the printer using that macro, and never issue a G28 directly. However, I still have G28 exposed because the HOME macro uses that to implement the low-level homing. I’d feel better if the low-level carriage homing wasn’t exposed as I never want to run it outside of the HOME macro.

Implementation idea:

  • Strive for a future where the core Klipper code does not define G28. Users that want a G28 can define it using a [gcode_macro G28], [safe_z_home], or some other module. Expect some users will only define a HOME type macro or perform homing directly from START_PRINT.

Don’t break everyone’s existing config

Every printer today has to home and that means basically every printer today is using G28 in some form. Obviously changing the homing system risks causing a huge disruption. Minimizing that would certainly be a priority.

I have some ideas that could potentially mitigate the disruption:

  • It may be possible to introduce some kind of hidden “legacy_G28” module that uses the existing config layout (eg, “position_endstop” in “[stepper_x]”) and registers a G28 command with the existing behavior, while invoking some kind of “HOME_XXX” logic behind the scenes. Possibly with some gentle deprecation warning suggesting that the config be updated.
  • We could deprecate [homing_override] and urge users to convert to [gcode_macro G28] (that calls HOME_XXX).
  • The [safe_z_home] module could directly register G28 (instead of overriding it). It could also gain options to use “endstop”, “sensorless”, or “probe” mechanisms for each xyz carriage.

Of course, another valid option is to not make some or all of the above changes.

The above is some high-level ideas. Nothing is “set in stone”. I wanted to write up some of the ideas I had. There has been some recent work on “generic_cartesian”, recent work on “loadcell probing”, and recent discussions on “eddy tap”. Given all this I think it would be helpful to have a feel for both long-term and short-term goals for the homing implementation in Klipper. Hopefully that’ll make it easier to get new features into Klipper in the short term in a way that also aligns with long-term thinking.

-Kevin

6 Likes

If getting rid of probe:z_virtual_endstop is going to enable probing on x/y axes (which I really want!!) I’d like to lobby for having the probe config express that somehow.

Proposal: A probe should define what axes it supports:

[probe]
axes: xyz

Printers with multiple homing & probing systems exist in the commercial world. Sometimes with multiple systems that work on the same axis. E.g.:

  • Sensorless homing
  • A load cell nozzle touch-off system
  • An eddy type bed scanner
  • A computer vision system
[home_sensorless stepper_x]
position_endstop: -5
homing_speed: 20

[home_sensorless stepper_y]
position_endstop: -5
homing_speed: 20

[load_cell_probe]
axes: xyz
...

[probe_eddy_current]
axes: z
...

[probe_computer_vision]
axes: xy
...

Another use case is having sensorless homing in both directions on an axis. Commercial machine use this to validate that the axes move to full length and that there are no obstructions or tight spots in the motion.

1 Like

I guess my proposal is already included in what @garethky sketched out:
One of my printers has a static X/Y gantry and a moving bed. Because it is belt-driven, the bed drops to Z-max when powered off.

Today, an involved mechanical solution prevents this, but it would be easier to catch this with endstops at Z-max and then use whatever probe is installed to get Z=0.

Basically, a multi-stage homing process:

  1. Get triggers for Z-max (potentially already triggered at startup).
  2. Move up towards Z-min.
  3. Get probe trigger for Z=0.

I agree that it could be useful to home x/y with loadcells in the future. (It would also be useful to probe X and Y.)

For what it is worth, I’m not sure we should have the probe.py module declare what axes it can home … so that some other software can determine which hardware modules can home each axis … so that some other software can determine what actions and hardware to use when requested to home an axis…

An alternative would be for the hardware modules capable of advanced homing to register the appropriate commands themselves. For example, loadcell.py could register a command like: LOADCELL_HOME AXIS=X X_TAP_POSITION=4.0 HOMING_SPEED=30. Users can then execute those commands (if they wish).

Similarly for probing - we may want to have the hardware modules define the commands they support (eg, LOADCELL_PROBE AXIS=X PROBING_SPEED=10).

That is a good point. It would be useful to have multiple modules that can home a single axis (the user can choose which one to invoke), and you have a good point that a [home_sensorless] type of module may want to support commands that can home in both directions.

Similarly, we should support printers with multiple Z probes, but that is a slightly different topic.

Thanks for the feedback.
-Kevin

This is an interesting direction! I see that you, Kevin, want to do a major revamp of homing in Klipper. I want to share my thoughts on this matter.

Generally, I think the idea of adding more primitive, low-level commands for homing makes a lot of sense. Unlike fiddling with various ways to override G28, these will provide a way to get the expected behavior in a predictable and guaranteed way. It will also be much easier to add new homing functionality.

That said, I have some questions about the current proposal. For example

  • It is not immediately clear to me how homing of a delta printer can be represented using the suggested primitives? Specifically, for a delta printer all 3 rails must be homed simultaneously, but they must be moved until their respective endstops trigger, and the proposed low-level commands do not support that behavior AFAICT. I think deltesian will have similar issues, not sure about other kinematics.
  • It is a little bit difficult for me to picture how features like endstop_phase, multi-mcu homing and such would fit this new architecture - such as to not introduce code deduplication and having to reimplement their support in each of the homing module (though it is probably possible, just would require some thinking).

Also, if we are doing complete revamp of the homing, I think the following features would be really nice to have (not a must, just nice to have):

  • An ability to home any stepper (or combination of steppers); for example a common request from various filament changing projects is to be able to move several steppers, including extruder, until a certain endstop triggers, something Klipper does not support today.
  • An ability to define a homing sequence using default homing primitives for obscure kinematics like deltesian and linear axis tripteron.
  • An ability to home several axes in parallel, of course when it is meaningful to do so, something that the user could configure using default homing primitives.
  • endstop_phase support on Cartesian-style kinematics other than plain Cartesian (FWIW, this would only work if all ‘entangled’ axes are homed together in the same homing sequence).

And my thoughts on ‘homing’ decomposition into building blocks:

  • The most basic low-level homing primitive is moving groups of steppers with a certain ‘overall’ speed and ramp-up acceleration, but with each stepper having its own linear coefficient, until a certain event occurs for each stepper group, or until main Klipper issues a stop even (e.g. if the max distance is traveled). In case of a simple endstop or probe homing these events would be that a certain pin is triggered, and in case of a multi-mcu, loadcell, etc. homing, it would just be the main Klipper event. FWIW, this primitive may or may not be exposed to the user.
  • Interpretation of such ‘homing move’, given the time of the event(s), stepper positions at the event(s) and their final positions, kinematics-aware.
  • Implementation of the ‘homing’ of an ‘axis’ (or groups of axes): depends on the hardware used (endstop, sensorless homing, loadcell) and must use the other two blocks above in order to organize the motion of the axis of interest as appropriate. This may or may not be exposed to the user.
  • Kinematics-aware homing of the printer axes: defines which axes/carriages can be homed and defines and implements commands to do so.
  • High-level homing routine (which axes to home and in which order), defined by the user.

So, we can still have

[home_endstop stepper_x]
endstop_pin: PB3
position_endstop: -5
homing_speed: 20

[home_sensorless stepper_y]
position_endstop: -5
homing_speed: 20

[home_loadcell stepper_z]
position_endstop: 0
homing_speed: 5
...

However the user would not typically call HOME_ENDSTOP CARRIAGE=stepper_x. Instead, the kinematic would define a command like HOME_TOOLHEAD with the arguments that would depend on the kinematics. For example, for Cartesian-style kinematics that would be HOME_TOOLHEAD CARRIAGE=<carriage_id>. For a delta, it could be HOME_TOOLHEAD [AXIS=Z], and for deltesian, it could be HOME_TOOLHEAD [AXIS=[Y|Z]]. It would also be appropriate to restrict that all carriages participating in the single homing action from kinematic perspective (e.g. all delta carriages) must use the same endstop mechanism, so that the kinematic class could just forward the execution to the appropriate class.

And we could also have a class [extruder_endstop] and extruder would define a command similar to manual_stepper like MOVE_EXTRUDER EXTRUDER=<extruder_id> [STOP_ON_ENDSTOP=[1|2|-1|-2]] ....

Alternatively, we’d have to support HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b,stepper_c and alike, but I feel like here the kinematic abstraction would be leaking into the underlying endstop classes, and also a simple typo could easily ruin the day and damage the printer (e.g. if a user of a delta printer enters something like HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b ,stepper_c or say prematurely accidentally hits ‘Enter’ after typing HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b).

Let me know what your thoughts are.