diff --git a/README.md b/README.md index 7f746f50b..fbb9c2a68 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Features merged into the master branch: - [probe: z_calibration](https://github.com/DangerKlippers/danger-klipper/pull/31) ([klipper#4614](https://github.com/Klipper3d/klipper/pull/4614) / [protoloft/z_calibration](https://github.com/protoloft/klipper_z_calibration)) +- [stepper: Sensorless Homing Helper](https://github.com/DangerKlippers/danger-klipper/pull/61) ([eamars/sensorless_homing_helper](https://github.com/eamars/sensorless_homing_helper)) + "Dangerous Klipper for dangerous users" Klipper is a 3d-Printer firmware. It combines the power of a general diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 80bae72cc..a8bc4a536 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1810,7 +1810,7 @@ pins: ## Bed probing hardware -### [probe] +### ⚠️ [probe] Z height probe. One may define this section to enable Z height probing hardware. When this section is enabled, PROBE and QUERY_PROBE extended @@ -1943,7 +1943,7 @@ control_pin: # See the "probe" section for information on these parameters. ``` -### [dockable_probe] +### ⚠️ [dockable_probe] Certain probes are magnetically coupled to the toolhead and stowed in a dock when not in use. One should define this section instead @@ -2187,6 +2187,54 @@ end_gcode: # detach the probe afterwards. ``` +### ⚠️ [sensorless_homing_helper] + +Sensorless Homing Helper with additional retraction along XY axis when + - X or Y is not homed or + - The last known location of the toolhead is too closed to the gantry. + +The sensorless homing requires certain speed while hitting the gantry to +get repeatable XY coordinate. The additional XY retraction allows the carriage to +accelerate to the calibrated speed during the calibration. + +``` +[sensorless_homing_helper] +tmc_stepper_y_name: tmc2209 stepper_y +# The TMC stepper section name for Y. +# This parameter is required. +tmc_stepper_x_name: tmc2209 stepper_x +# The TMC stepper section name for X. +# This parameter is required. +home_current: 0.5 +# The current while running the sensorless homing. +# This parameter is required. +minimum_homing_distance: 10 +# The minimum distance (in mm) to achieve the repeatible sensorless homing. +# The default is 10 +retract_distance: 10 +# The retract distance (in mm) after the axis is homed. The default is 10 +retract_speed: 20 +# The speed (in mm/s) while running the retraction (both before and after homing). +# The default is 20 +stallguard_reset_time: 1 +# The time (in secs) for stallguard to reset before the next homing move. +# The default is 1 +``` + +Additional Macros might be needed for 3rd-party plugins (e.g. Klicky) to use the sensorless routine. + +``` +[gcode_macro _HOME_X] +rename_existing: __HOME_X +gcode: + __HOME_X + +[gcode_macro _HOME_Y] +rename_existing: __HOME_Y +gcode: + __HOME_Y +``` + ## Additional stepper motors and extruders ### [stepper_z1] diff --git a/klippy/extras/sensorless_homing_helper.py b/klippy/extras/sensorless_homing_helper.py new file mode 100644 index 000000000..29c5e9f40 --- /dev/null +++ b/klippy/extras/sensorless_homing_helper.py @@ -0,0 +1,203 @@ +from contextlib import contextmanager + + +class SensorLessHomingHelper(object): + def __init__(self, config): + self.config = config + self.printer = config.get_printer() + self.toolhead = None + self.gcode = self.printer.lookup_object("gcode") + + # Run current + self.tmc_stepper_y_name = config.get("tmc_stepper_y_name") + self.tmc_stepper_x_name = config.get("tmc_stepper_x_name") + + self.pconfig = self.printer.lookup_object("configfile") + + # Read config + self.home_current = config.getfloat("home_current") + self.minimum_homing_distance = config.getfloat( + "minimum_homing_distance", 10 + ) + self.retract_distance = config.getfloat("retract_distance", 10) + self.retract_speed = config.getfloat("retract_speed", 20) + self.stallguard_reset_time = config.getfloat("stallguard_reset_time", 1) + self.use_homing_status = config.getboolean("use_homing_status", True) + + self.gcode.register_command( + "_HOME_X", self.cmd_HOME_X, "Sensorless homing X axis" + ) + self.gcode.register_command( + "_HOME_Y", self.cmd_HOME_Y, "Sensorless homing X axis" + ) + + self.printer.register_event_handler( + "klippy:connect", self.handle_connect + ) + + def handle_connect(self): + self.toolhead = self.printer.lookup_object("toolhead") + + @contextmanager + def set_xy_motor_current(self, homing_current): + x_stepper_name = self.tmc_stepper_x_name.split()[1] + self.gcode.run_script_from_command( + "SET_TMC_CURRENT STEPPER={} CURRENT={}".format( + x_stepper_name, homing_current + ) + ) + + y_stepper_name = self.tmc_stepper_y_name.split()[1] + self.gcode.run_script_from_command( + "SET_TMC_CURRENT STEPPER={} CURRENT={}".format( + y_stepper_name, homing_current + ) + ) + + try: + yield + finally: + curtime = self.printer.get_reactor().monotonic() + settings = self.pconfig.get_status(curtime)["settings"] + self.gcode.run_script_from_command( + "SET_TMC_CURRENT STEPPER={} CURRENT={}".format( + x_stepper_name, + settings[self.tmc_stepper_x_name]["run_current"], + ) + ) + self.gcode.run_script_from_command( + "SET_TMC_CURRENT STEPPER={} CURRENT={}".format( + y_stepper_name, + settings[self.tmc_stepper_y_name]["run_current"], + ) + ) + + def cmd_HOME_X(self, gcmd): + # Check if X axis is homed and its last known position + curtime = self.printer.get_reactor().monotonic() + kin_status = self.toolhead.get_status(curtime) + + pos = self.toolhead.get_position() + + if self.use_homing_status and "x" not in kin_status["homed_axes"]: + gcmd.respond_info( + "X is not homed {}. Will perform the retract before home.".format( + kin_status["homed_axes"] + ) + ) + # Run the sensorless homing to the opposite direction + with self.set_xy_motor_current(self.home_current): + move_pos = pos[:] + move_pos[0] = 0 + current_pos = pos[:] + current_pos[0] = self.minimum_homing_distance + endstops = ( + self.toolhead.get_kinematics().rails[0].get_endstops() + ) + + # Do a manual homing + phoming = self.printer.lookup_object("homing") + self.toolhead.set_position(current_pos, homing_axes=[0]) + phoming.manual_home( + toolhead=self.toolhead, + endstops=endstops, + pos=move_pos, + speed=self.retract_speed, + triggered=True, + check_triggered=False, + ) + + elif ( + kin_status["axis_maximum"][0] - pos[0] + < self.minimum_homing_distance + ): + gcmd.respond_info( + "X is homed but too closed to the maximum range {}. Will perform the retract before home.".format( + pos[0] + ) + ) + pos[0] -= self.minimum_homing_distance + self.toolhead.manual_move(pos, self.retract_speed) + else: + gcmd.respond_info( + "X is homed {} and away from maximum range {}.".format( + kin_status["homed_axes"], pos[0] + ) + ) + + with self.set_xy_motor_current(self.home_current): + self.gcode.run_script_from_command("G28 X") + self.toolhead.wait_moves() + + # Retract + pos = self.toolhead.get_position() + pos[0] -= self.retract_distance + self.toolhead.move(pos, self.retract_speed) + self.toolhead.dwell(self.stallguard_reset_time) + + def cmd_HOME_Y(self, gcmd): + # Check if Y axis is homed and its last known position + curtime = self.printer.get_reactor().monotonic() + kin_status = self.toolhead.get_status(curtime) + + pos = self.toolhead.get_position() + + if self.use_homing_status and "y" not in kin_status["homed_axes"]: + gcmd.respond_info( + "Y is not homed {}. Will perform the retract before home.".format( + kin_status["homed_axes"] + ) + ) + + # Run the sensorless homing to the opposite direction + with self.set_xy_motor_current(self.home_current): + move_pos = pos[:] + move_pos[1] = 0 + current_pos = pos[:] + current_pos[1] = self.minimum_homing_distance + endstops = ( + self.toolhead.get_kinematics().rails[1].get_endstops() + ) + + # Do a manual homing + phoming = self.printer.lookup_object("homing") + self.toolhead.set_position(current_pos, homing_axes=[1]) + phoming.manual_home( + toolhead=self.toolhead, + endstops=endstops, + pos=move_pos, + speed=self.retract_speed, + triggered=True, + check_triggered=False, + ) + elif ( + kin_status["axis_maximum"][1] - pos[1] + < self.minimum_homing_distance + ): + gcmd.respond_info( + "Y is homed but too closed to the maximum range {}. Will perform the retract before home.".format( + pos[1] + ) + ) + pos[1] -= self.minimum_homing_distance + self.toolhead.manual_move(pos, self.retract_speed) + else: + gcmd.respond_info( + "Y is homed {} and away from maximum range {}.".format( + kin_status["homed_axes"], pos[1] + ) + ) + + with self.set_xy_motor_current(self.home_current): + self.gcode.run_script_from_command("G28 Y") + self.toolhead.wait_moves() + + # Retract + pos = self.toolhead.get_position() + pos[1] -= self.retract_distance + self.toolhead.move(pos, self.retract_speed) + self.toolhead.dwell(self.stallguard_reset_time) + + +def load_config(config): + return SensorLessHomingHelper(config) diff --git a/test/klippy/sensorless_homing_helper.cfg b/test/klippy/sensorless_homing_helper.cfg new file mode 100644 index 000000000..a507aedda --- /dev/null +++ b/test/klippy/sensorless_homing_helper.cfg @@ -0,0 +1,122 @@ +# Test config for z_tilt and quad_gantry_level +[stepper_x] +step_pin: PF0 +dir_pin: PF1 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PE5 +position_endstop: 0 +position_max: 250 +position_min: -10 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 +dir_pin: !PF7 +enable_pin: !PF2 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PJ1 +position_endstop: 0 +position_max: 250 +position_min: -10 +homing_speed: 50 + +[tmc2209 stepper_x] +uart_pin: PA5 +diag_pin: PH7 +run_current: .5 +sense_resistor: 0.075 + +[tmc2209 stepper_y] +uart_pin: PA7 +diag_pin: PH8 +run_current: .5 +sense_resistor: 0.075 + +[stepper_z] +step_pin: PL3 +dir_pin: PL1 +enable_pin: !PK0 +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^PD3 +position_endstop: 0.5 +position_max: 250 + +[stepper_z1] +step_pin: PC1 +dir_pin: PC3 +enable_pin: !PC7 +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^PD2 + +[stepper_z2] +step_pin: PH1 +dir_pin: PH0 +enable_pin: !PA1 +microsteps: 16 +rotation_distance: 8 + +[stepper_z3] +step_pin: PE3 +dir_pin: PG5 +microsteps: 16 +rotation_distance: 8 + +[sensorless_homing_helper] +tmc_stepper_y_name: tmc2209 stepper_y +tmc_stepper_x_name: tmc2209 stepper_x +home_current: 0.5 + +[extruder] +step_pin: PA4 +dir_pin: PA6 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 33.5 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 + +[heater_bed] +heater_pin: PH5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK6 +control: watermark +min_temp: 0 +max_temp: 130 + +[probe] +pin: PH6 +z_offset: 1.15 + +[mcu] +serial: /dev/ttyACM0 + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[gcode_macro _HOME_X] +rename_existing: __HOME_X +gcode: + __HOME_X + +[gcode_macro _HOME_Y] +rename_existing: __HOME_Y +gcode: + __HOME_Y diff --git a/test/klippy/sensorless_homing_helper.test b/test/klippy/sensorless_homing_helper.test new file mode 100644 index 000000000..f3d29f695 --- /dev/null +++ b/test/klippy/sensorless_homing_helper.test @@ -0,0 +1,17 @@ +# Test case for z_tilt and quad_gantry_level +CONFIG sensorless_homing_helper.cfg +DICTIONARY atmega2560.dict + +# Start by homing the printer. +G28 +G1 Z5 X10 Y10 F6000 +M400 +GET_POSITION + +_HOME_X +_HOME_Y + +# Report position +G1 Z5 X10 Y10 F6000 +M400 +GET_POSITION