mcu: Support multi-mcu homing

Support endstops and probes attached to a different micro-controller
than their associated steppers.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2021-06-03 23:24:44 -04:00
parent 2e131497ca
commit 950477849d
5 changed files with 89 additions and 20 deletions

View File

@ -119,7 +119,9 @@ different names for the stepper (eg, `stepper_x` vs `stepper_a`).
Below are common stepper definitions. Below are common stepper definitions.
See the [rotation distance document](Rotation_Distance.md) for See the [rotation distance document](Rotation_Distance.md) for
information on calculating the `rotation_distance` parameter. information on calculating the `rotation_distance` parameter. See the
[Multi-MCU homing](Multi_MCU_Homing.md) document for information on
homing using multiple micro-controllers.
``` ```
[stepper_x] [stepper_x]
@ -152,8 +154,10 @@ microsteps:
# distance the axis travels for one full rotation of the final gear. # distance the axis travels for one full rotation of the final gear.
# The default is to not use a gear ratio. # The default is to not use a gear ratio.
endstop_pin: endstop_pin:
# Endstop switch detection pin. This parameter must be provided for # Endstop switch detection pin. If this endstop pin is on a
# the X, Y, and Z steppers on cartesian style printers. # different mcu than the stepper motor then it enables "multi-mcu
# homing". This parameter must be provided for the X, Y, and Z
# steppers on cartesian style printers.
#position_min: 0 #position_min: 0
# Minimum valid distance (in mm) the user may command the stepper to # Minimum valid distance (in mm) the user may command the stepper to
# move to. The default is 0mm. # move to. The default is 0mm.
@ -1611,7 +1615,9 @@ stepper_z config section.
``` ```
[probe] [probe]
pin: pin:
# Probe detection pin. This parameter must be provided. # Probe detection pin. If the pin is on a different microcontroller
# than the Z steppers then it enables "multi-mcu homing". This
# parameter must be provided.
#deactivate_on_each_sample: True #deactivate_on_each_sample: True
# This determines if Klipper should execute deactivation gcode # This determines if Klipper should execute deactivation gcode
# between each probe attempt when performing a multiple probe # between each probe attempt when performing a multiple probe

42
docs/Multi_MCU_Homing.md Normal file
View File

@ -0,0 +1,42 @@
# Multiple Micro-controller Homing and Probing
Klipper supports a mechanism for homing with an endstop attached to
one micro-controller while its stepper motors are on a different
micro-controller. This support is referred to as "multi-mcu
homing". This feature is also used when a Z probe is on a different
micro-controller than the Z stepper motors.
This feature can be useful to simplify wiring, as it may be more
convenient to attach an endstop or probe to a closer micro-controller.
However, using this feature may result in "overshoot" of the stepper
motors during homing and probing operations.
The overshoot occurs due to possible message transmission delays
between the micro-controller monitoring the endstop and the
micro-controllers moving the stepper motors. The Klipper code is
designed to limit this delay to no more than 25ms. (When multi-mcu
homing is activated, the micro-controllers send periodic status
messages and check that corresponding status messages are received
within 25ms.)
So, for example, if homing at 10mm/s then it is possible for an
overshoot of up to 0.250mm (10mm/s * .025s == 0.250mm). Care should be
taken when configuring multi-mcu homing to account for this type of
overshoot. Using slower homing or probing speeds can reduce the
overshoot.
Stepper motor overshoot should not adversely impact the precision of
the homing and probing procedure. The Klipper code will detect the
overshoot and account for it in its calculations. However, it is
important that the hardware design is capable of handling overshoot
without causing damage to the machine.
Should Klipper detect a communication issue between micro-controllers
during multi-mcu homing then it will raise a "Communication timeout
during homing" error.
Note that an axis with multiple steppers (eg, `stepper_z` and
`stepper_z1`) need to be on the same micro-controller in order to use
multi-mcu homing. For example, if an endstop is on a separate
micro-controller from `stepper_z` then `stepper_z1` must be on the
same micro-controller as `stepper_z`.

View File

@ -41,18 +41,19 @@ communication with the Klipper developers.
using adxl345 accelerometer hardware to measure resonance. using adxl345 accelerometer hardware to measure resonance.
- [Pressure advance](Pressure_Advance.md): Calibrate extruder - [Pressure advance](Pressure_Advance.md): Calibrate extruder
pressure. pressure.
- [Slicers](Slicers.md): Configure "slicer" software for Klipper. - [G-Codes](G-Codes.md): Information on commands supported by Klipper.
- [Command Templates](Command_Templates.md): G-Code macros and - [Command Templates](Command_Templates.md): G-Code macros and
conditional evaluation. conditional evaluation.
- [Status Reference](Status_Reference.md): Information available to - [Status Reference](Status_Reference.md): Information available to
macros (and similar). macros (and similar).
- [TMC Drivers](TMC_Drivers.md): Using Trinamic stepper motor drivers - [TMC Drivers](TMC_Drivers.md): Using Trinamic stepper motor drivers
with Klipper. with Klipper.
- [Multi-MCU Homing](Multi_MCU_Homing.md): Homing and probing using multiple micro-controllers.
- [Slicers](Slicers.md): Configure "slicer" software for Klipper.
- [Skew correction](skew_correction.md): Adjustments for axes not - [Skew correction](skew_correction.md): Adjustments for axes not
perfectly square. perfectly square.
- [PWM tools](Using_PWM_Tools.md): Guide on how to use PWM controlled - [PWM tools](Using_PWM_Tools.md): Guide on how to use PWM controlled
tools such as lasers or spindles. tools such as lasers or spindles.
- [G-Codes](G-Codes.md): Information on commands supported by Klipper.
## Developer Documentation ## Developer Documentation

View File

@ -94,14 +94,15 @@ nav:
- Resonance_Compensation.md - Resonance_Compensation.md
- Measuring_Resonances.md - Measuring_Resonances.md
- Pressure_Advance.md - Pressure_Advance.md
- Slicers.md - G-Codes.md
- Command templates: - Command templates:
- Command_Templates.md - Command_Templates.md
- Status_Reference.md - Status_Reference.md
- TMC_Drivers.md - TMC_Drivers.md
- Multi_MCU_Homing.md
- Slicers.md
- skew_correction.md - skew_correction.md
- Using_PWM_Tools.md - Using_PWM_Tools.md
- G-Codes.md
- Developer Documentation: - Developer Documentation:
- Code_Overview.md - Code_Overview.md
- Kinematics.md - Kinematics.md

View File

@ -125,6 +125,9 @@ class MCU_trsync:
s.note_homing_end() s.note_homing_end()
return params['trigger_reason'] return params['trigger_reason']
TRSYNC_TIMEOUT = 0.025
TRSYNC_SINGLE_MCU_TIMEOUT = 0.250
class MCU_endstop: class MCU_endstop:
RETRY_QUERY = 1.000 RETRY_QUERY = 1.000
def __init__(self, mcu, pin_params): def __init__(self, mcu, pin_params):
@ -139,15 +142,27 @@ class MCU_endstop:
self._rest_ticks = 0 self._rest_ticks = 0
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
self._trdispatch = ffi_main.gc(ffi_lib.trdispatch_alloc(), ffi_lib.free) self._trdispatch = ffi_main.gc(ffi_lib.trdispatch_alloc(), ffi_lib.free)
self._trsync = MCU_trsync(mcu, self._trdispatch) self._trsyncs = [MCU_trsync(mcu, self._trdispatch)]
def get_mcu(self): def get_mcu(self):
return self._mcu return self._mcu
def add_stepper(self, stepper): def add_stepper(self, stepper):
if stepper.get_mcu() is not self._mcu: trsyncs = {trsync.get_mcu(): trsync for trsync in self._trsyncs}
raise pins.error("Endstop and stepper must be on the same mcu") trsync = trsyncs.get(stepper.get_mcu())
self._trsync.add_stepper(stepper) if trsync is None:
trsync = MCU_trsync(stepper.get_mcu(), self._trdispatch)
self._trsyncs.append(trsync)
trsync.add_stepper(stepper)
# Check for unsupported multi-mcu shared stepper rails
sname = stepper.get_name()
if sname.startswith('stepper_'):
for ot in self._trsyncs:
for s in ot.get_steppers():
if ot is not trsync and s.get_name().startswith(sname[:9]):
cerror = self._mcu.get_printer().config_error
raise cerror("Multi-mcu homing not supported on"
" multi-mcu shared axis")
def get_steppers(self): def get_steppers(self):
return self._trsync.get_steppers() return [s for trsync in self._trsyncs for s in trsync.get_steppers()]
def _build_config(self): def _build_config(self):
# Setup config # Setup config
self._mcu.add_config_cmd("config_endstop oid=%d pin=%s pull_up=%d" self._mcu.add_config_cmd("config_endstop oid=%d pin=%s pull_up=%d"
@ -157,7 +172,7 @@ class MCU_endstop:
" rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0" " rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0"
% (self._oid,), on_restart=True) % (self._oid,), on_restart=True)
# Lookup commands # Lookup commands
cmd_queue = self._trsync.get_command_queue() cmd_queue = self._trsyncs[0].get_command_queue()
self._home_cmd = self._mcu.lookup_command( self._home_cmd = self._mcu.lookup_command(
"endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c" "endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c"
" rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c", " rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c",
@ -173,8 +188,12 @@ class MCU_endstop:
self._rest_ticks = rest_ticks self._rest_ticks = rest_ticks
reactor = self._mcu.get_printer().get_reactor() reactor = self._mcu.get_printer().get_reactor()
self._trigger_completion = reactor.completion() self._trigger_completion = reactor.completion()
etrsync = self._trsync expire_timeout = TRSYNC_TIMEOUT
etrsync.start(print_time, self._trigger_completion, .250) if len(self._trsyncs) == 1:
expire_timeout = TRSYNC_SINGLE_MCU_TIMEOUT
for trsync in self._trsyncs:
trsync.start(print_time, self._trigger_completion, expire_timeout)
etrsync = self._trsyncs[0]
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST) ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST)
self._home_cmd.send( self._home_cmd.send(
@ -183,7 +202,7 @@ class MCU_endstop:
etrsync.get_oid(), etrsync.REASON_ENDSTOP_HIT], reqclock=clock) etrsync.get_oid(), etrsync.REASON_ENDSTOP_HIT], reqclock=clock)
return self._trigger_completion return self._trigger_completion
def home_wait(self, home_end_time): def home_wait(self, home_end_time):
etrsync = self._trsync etrsync = self._trsyncs[0]
etrsync.set_home_end_time(home_end_time) etrsync.set_home_end_time(home_end_time)
if self._mcu.is_fileoutput(): if self._mcu.is_fileoutput():
self._trigger_completion.complete(True) self._trigger_completion.complete(True)
@ -191,10 +210,10 @@ class MCU_endstop:
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0]) self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0])
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.trdispatch_stop(self._trdispatch) ffi_lib.trdispatch_stop(self._trdispatch)
res = etrsync.stop() res = [trsync.stop() for trsync in self._trsyncs]
if res == etrsync.REASON_COMMS_TIMEOUT: if any([r == etrsync.REASON_COMMS_TIMEOUT for r in res]):
return -1. return -1.
if res != etrsync.REASON_ENDSTOP_HIT: if res[0] != etrsync.REASON_ENDSTOP_HIT:
return 0. return 0.
if self._mcu.is_fileoutput(): if self._mcu.is_fileoutput():
return home_end_time return home_end_time