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_zendstop_position
norendstop_pin
. The code would use only the probing code (not any of thehome_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
instancesstepper.py:PrinterRail
reads and stores homing settings from configPrinterRail
createsmcu.py:MCU_endstop
classes
toolhead.py
loads theextras/homing.py
moduleextras/homing.py
registers theG28
command
- kinematic classes declare
- on
G28
:homing.py:cmd_G28()
callskinematics/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
- calls
- which calls back into
- 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 newHOME_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 newHOME_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 aG28
can define it using a[gcode_macro G28]
,[safe_z_home]
, or some other module. Expect some users will only define aHOME
type macro or perform homing directly fromSTART_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 callsHOME_XXX
). - The
[safe_z_home]
module could directly registerG28
(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