Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -4835,6 +4835,7 @@ run_current:
#driver_SEDN: 0
#driver_SEIMIN: 0
#driver_SFILT: 0
#driver_SG4_THRS: 0
#driver_SG4_ANGLE_OFFSET: 1
# Set the given register during the configuration of the TMC2240
# chip. This may be used to set custom motor parameters. The
Expand All @@ -4847,8 +4848,8 @@ run_current:
# is "active low" and is thus normally prefaced with "^!". Setting
# this creates a "tmc2240_stepper_x:virtual_endstop" virtual pin
# which may be used as the stepper's endstop_pin. Doing this enables
# "sensorless homing". (Be sure to also set driver_SGT to an
# appropriate sensitivity value.) The default is to not enable
# "sensorless homing". (Be sure to also set driver_SGT OR driver_SG4_THRS
# to an appropriate sensitivity value.) The default is to not enable
# sensorless homing.
```

Expand Down
1 change: 1 addition & 0 deletions klippy/extras/homing.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ def home_rails(self, rails, forcepos, movepos):
hmove.homing_move(homepos, hi.speed)
finally:
self._set_homing_accel(hi.accel, pre_homing=False)
self._set_homing_current(homing_axes, pre_homing=False)

needs_rehome = False
retract_dist = hi.retract_dist
Expand Down
269 changes: 183 additions & 86 deletions klippy/extras/tmc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

from klippy import stepper

from . import bulk_sensor

######################################################################
# Field helpers
######################################################################
Expand Down Expand Up @@ -262,6 +264,103 @@ def get_status(self, eventtime=None):
return {"drv_status": self.last_drv_fields, "temperature": temp}


######################################################################
# Record driver status
######################################################################


class TMCStallguardDump:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.stepper_name = " ".join(config.get_name().split()[1:])
self.mcu_tmc = mcu_tmc
self.mcu = self.mcu_tmc.get_mcu()
self.fields = self.mcu_tmc.get_fields()
self.sg2_supp = False
self.sg4_reg_name = None
# It is possible to support TMC2660, just disable it for now
if not self.fields.all_fields.get("DRV_STATUS", None):
return
# Collect driver capabilities
if self.fields.all_fields["DRV_STATUS"].get("sg_result", None):
self.sg2_supp = True
# New drivers have separate register for SG4 result
if self.mcu_tmc.name_to_reg.get("SG_RESULT", 0):
self.sg4_reg_name = "SG_RESULT"
# 2240 supports both SG2 & SG4
if self.sg4_reg_name is None:
if self.mcu_tmc.name_to_reg.get("SG4_RESULT", 0):
self.sg4_reg_name = "SG4_RESULT"
# TMC2208
if self.sg2_supp is None and self.sg4_reg_name is None:
return
self.optimized_spi = False
# Bulk API
self.samples = []
self.query_timer = None
self.error = None
self.batch_bulk = bulk_sensor.BatchBulkHelper(
self.printer, self._dump, self._start, self._stop
)
api_resp = {"header": ("time", "sg_result", "cs_actual")}
self.batch_bulk.add_mux_endpoint(
"tmc/stallguard_dump", "name", self.stepper_name, api_resp
)

def _start(self):
self.error = None
status = self.mcu_tmc.get_register_raw("DRV_STATUS")
if status.get("spi_status"):
self.optimized_spi = True
reactor = self.printer.get_reactor()
self.query_timer = reactor.register_timer(self._query_tmc, reactor.NOW)

def _stop(self):
self.printer.get_reactor().unregister_timer(self.query_timer)
self.query_timer = None
self.samples = []

def _query_tmc(self, eventtime):
sg_result = -1
cs_actual = -1
recv_time = eventtime
try:
if self.optimized_spi or self.sg4_reg_name == "SG4_RESULT":
# TMC2130/TMC5160/TMC2240
status = self.mcu_tmc.get_register_raw("DRV_STATUS")
reg_val = status["data"]
cs_actual = self.fields.get_field("cs_actual", reg_val)
sg_result = self.fields.get_field("sg_result", reg_val)
is_stealth = self.fields.get_field("stealth", reg_val)
recv_time = status["#receive_time"]
if is_stealth and self.sg4_reg_name == "SG4_RESULT":
sg4_ret = self.mcu_tmc.get_register_raw("SG4_RESULT")
sg_result = sg4_ret["data"]
recv_time = sg4_ret["#receive_time"]
else:
# TMC2209
if self.sg4_reg_name == "SG_RESULT":
sg4_ret = self.mcu_tmc.get_register_raw("SG_RESULT")
sg_result = sg4_ret["data"]
recv_time = sg4_ret["#receive_time"]
except self.printer.command_error as e:
self.error = e
return self.printer.get_reactor().NEVER
print_time = self.mcu.estimated_print_time(recv_time)
self.samples.append((print_time, sg_result, cs_actual))
if self.optimized_spi:
return eventtime + 0.001
# UART queried as fast as possible
return eventtime + 0.005

def _dump(self, eventtime):
if self.error:
raise self.error
samples = self.samples
self.samples = []
return {"data": samples}


######################################################################
# G-Code command helpers
######################################################################
Expand All @@ -274,27 +373,34 @@ def __init__(self, config, mcu_tmc, current_helper):
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.current_helper = current_helper
self.echeck_helper = TMCErrorCheck(config, mcu_tmc)
self.fields = mcu_tmc.get_fields()
self.read_registers = self.read_translate = None
self.toff = None
self.mcu_phase_offset = None
self.stepper = None
# Stepper phase tracking
self.mcu_phase_offset = None
# Stepper enable/disable tracking
self.toff = None
self.stepper_enable = self.printer.load_object(config, "stepper_enable")
self.enable_mutex = self.printer.get_reactor().mutex()
# DUMP_TMC support
self.read_registers = self.read_translate = None
# Common tmc helpers
self.echeck_helper = TMCErrorCheck(config, mcu_tmc)
self.record_helper = TMCStallguardDump(config, mcu_tmc)
# Set microstep config options
TMCMicrostepHelper(config, mcu_tmc)
# Register callbacks
self.printer.register_event_handler(
"stepper:sync_mcu_position", self._handle_sync_mcu_pos
)
self.printer.register_event_handler(
"stepper:set_sdir_inverted", self._handle_sync_mcu_pos
"stepper:set_dir_inverted", self._handle_sync_mcu_pos
)
self.printer.register_event_handler(
"klippy:mcu_identify", self._handle_mcu_identify
)
self.printer.register_event_handler(
"klippy:connect", self._handle_connect
)
# Set microstep config options
TMCMicrostepHelper(config, mcu_tmc)
# Register commands
gcode = self.printer.lookup_object("gcode")
gcode.register_mux_command(
Expand Down Expand Up @@ -456,62 +562,56 @@ def _handle_sync_mcu_pos(self, stepper):

# Stepper enable/disable tracking
def _do_enable(self, print_time):
try:
if self.toff is not None:
# Shared enable via comms handling
self.fields.set_field("toff", self.toff)
self._init_registers()
did_reset = self.echeck_helper.start_checks()
if did_reset:
self.mcu_phase_offset = None
# Calculate phase offset
if self.toff is not None:
# Shared enable via comms handling
self.fields.set_field("toff", self.toff)
self._init_registers()
did_reset = self.echeck_helper.start_checks()
if did_reset:
self.mcu_phase_offset = None
# Calculate phase offset
if self.mcu_phase_offset is not None:
return
gcode = self.printer.lookup_object("gcode")
with gcode.get_mutex():
if self.mcu_phase_offset is not None:
return
gcode = self.printer.lookup_object("gcode")
with gcode.get_mutex():
if self.mcu_phase_offset is not None:
return
logging.info(
"Pausing toolhead to calculate %s phase offset",
self.stepper_name,
)
self.printer.lookup_object("toolhead").wait_moves()
self._handle_sync_mcu_pos(self.stepper)
except self.printer.command_error as e:
self.printer.invoke_shutdown(str(e))
logging.info(
"Pausing toolhead to calculate %s phase offset",
self.stepper_name,
)
self.printer.lookup_object("toolhead").wait_moves()
self._handle_sync_mcu_pos(self.stepper)

def _do_disable(self, print_time):
try:
if self.toff is not None:
val = self.fields.set_field("toff", 0)
reg_name = self.fields.lookup_register("toff")
self.mcu_tmc.set_register(reg_name, val, print_time)
self.echeck_helper.stop_checks()
except self.printer.command_error as e:
self.printer.invoke_shutdown(str(e))
if self.toff is not None:
val = self.fields.set_field("toff", 0)
reg_name = self.fields.lookup_register("toff")
self.mcu_tmc.set_register(reg_name, val, print_time)
self.echeck_helper.stop_checks()

def _handle_stepper_enable(self, print_time, is_enable):
def enable_disable_cb(eventtime):
try:
with self.enable_mutex:
if is_enable:
self._do_enable(print_time)
else:
self._do_disable(print_time)
except self.printer.command_error as e:
self.printer.invoke_shutdown(str(e))

self.printer.get_reactor().register_callback(enable_disable_cb)

# Initial startup handling
def _handle_mcu_identify(self):
# Lookup stepper object
force_move = self.printer.lookup_object("force_move")
self.stepper = force_move.lookup_stepper(self.stepper_name)
self.stepper.set_tmc_current_helper(self.current_helper)

# Note pulse duration and step_both_edge optimizations available
self.stepper.setup_default_pulse_duration(0.000000100, True)

def _handle_stepper_enable(self, print_time, is_enable):
if is_enable:

def cb(ev):
return self._do_enable(print_time)

else:

def cb(ev):
return self._do_disable(print_time)

self.printer.get_reactor().register_callback(cb)

def _handle_connect(self):
# Check if using step on both edges optimization
pulse_duration, step_both_edge = self.stepper.get_pulse_duration()
Expand Down Expand Up @@ -621,8 +721,8 @@ def __init__(self, config, mcu_tmc):
self.diag_pin = config.get("diag_pin", None)
self.diag_pin_field = None
self.mcu_endstop = None
self.en_pwm = False
self.pwmthrs = self.coolthrs = self.thigh = 0
self._dirty_regs = collections.OrderedDict()
self._prev_state = collections.OrderedDict()
# Register virtual_endstop pin
name_parts = config.get_name().split()
ppins = self.printer.lookup_object("pins")
Expand All @@ -647,57 +747,54 @@ def setup_pin(self, pin_type, pin_params):
self.mcu_endstop = ppins.setup_pin("endstop", self.diag_pin)
return self.mcu_endstop

def _set_field(self, field_name, value):
self._prev_state[field_name] = self.fields.get_field(field_name)
reg_name = self.fields.lookup_register(field_name)
self._dirty_regs[reg_name] = self.fields.set_field(field_name, value)

def _send_fields(self):
for reg, val in self._dirty_regs.items():
self.mcu_tmc.set_register(reg, val)
self._dirty_regs.clear()

def handle_homing_move_begin(self, hmove):
if self.mcu_endstop not in hmove.get_mcu_endstops():
return
sg4_thrs = 0
if self.fields.lookup_register("sg4_thrs", None) is not None:
sg4_thrs = self.fields.get_field("sg4_thrs")
# Enable/disable stealthchop
self.pwmthrs = self.fields.get_field("tpwmthrs")
reg = self.fields.lookup_register("en_pwm_mode", None)
if reg is None:
# On "stallguard4" drivers, "stealthchop" must be enabled
self.en_pwm = not self.fields.get_field("en_spreadcycle")
tp_val = self.fields.set_field("tpwmthrs", 0)
self.mcu_tmc.set_register("TPWMTHRS", tp_val)
val = self.fields.set_field("en_spreadcycle", 0)
self._set_field("tpwmthrs", 0)
self._set_field("en_spreadcycle", 0)
elif sg4_thrs:
# TMC2240 using SG4, "stealthchop" must be enabled
self._set_field("en_pwm_mode", 1)
self._set_field("tpwmthrs", 0)
self._set_field(self.diag_pin_field, 1)
else:
# On earlier drivers, "stealthchop" must be disabled
self.en_pwm = self.fields.get_field("en_pwm_mode")
self.fields.set_field("en_pwm_mode", 0)
val = self.fields.set_field(self.diag_pin_field, 1)
self.mcu_tmc.set_register("GCONF", val)
self._set_field("en_pwm_mode", 0)
self._set_field(self.diag_pin_field, 1)
# Enable tcoolthrs (if not already)
self.coolthrs = self.fields.get_field("tcoolthrs")
if self.coolthrs == 0:
tc_val = self.fields.set_field("tcoolthrs", 0xFFFFF)
self.mcu_tmc.set_register("TCOOLTHRS", tc_val)
if self.fields.get_field("tcoolthrs") == 0:
self._set_field("tcoolthrs", 0xFFFFF)
# Disable thigh
reg = self.fields.lookup_register("thigh", None)
if reg is not None:
self.thigh = self.fields.get_field("thigh")
th_val = self.fields.set_field("thigh", 0)
self.mcu_tmc.set_register(reg, th_val)
self._set_field("thigh", 0)
self._send_fields()

def handle_homing_move_end(self, hmove):
if self.mcu_endstop not in hmove.get_mcu_endstops():
return
# Restore stealthchop/spreadcycle
reg = self.fields.lookup_register("en_pwm_mode", None)
if reg is None:
tp_val = self.fields.set_field("tpwmthrs", self.pwmthrs)
self.mcu_tmc.set_register("TPWMTHRS", tp_val)
val = self.fields.set_field("en_spreadcycle", not self.en_pwm)
else:
self.fields.set_field("en_pwm_mode", self.en_pwm)
val = self.fields.set_field(self.diag_pin_field, 0)
self.mcu_tmc.set_register("GCONF", val)
# Restore tcoolthrs
tc_val = self.fields.set_field("tcoolthrs", self.coolthrs)
self.mcu_tmc.set_register("TCOOLTHRS", tc_val)
# Restore thigh
reg = self.fields.lookup_register("thigh", None)
if reg is not None:
th_val = self.fields.set_field("thigh", self.thigh)
self.mcu_tmc.set_register(reg, th_val)
# Restore previous state
for field, val in list(self._prev_state.items()):
self._set_field(field, val)
self._send_fields()
self._prev_state.clear()


######################################################################
Expand Down
Loading
Loading