Upload Q1_Pro klipper

This commit is contained in:
CChen616
2024-05-10 10:41:37 +08:00
parent 81e23fab2b
commit 637c14aa97
459 changed files with 104963 additions and 109121 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -9,7 +9,7 @@ from . import bus, motion_report
MIN_MSG_TIME = 0.100
TCODE_ERROR = 0xff
TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2240", "tmc2660", "tmc5160"]
TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2660", "tmc5160"]
CALIBRATION_BITS = 6 # 64 entries
ANGLE_BITS = 16 # angles range from 0..65535

View File

@@ -305,6 +305,10 @@ class BedMeshCalibrate:
self.gcode.register_command(
'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE,
desc=self.cmd_BED_MESH_CALIBRATE_help)
self.gcode.register_command(
'ADD_Z_OFFSET_TO_BED_MESH',
self.cmd_ADD_Z_OFFSET_TO_BED_MESH,
desc=self.cmd_ADD_Z_OFFSET_TO_BED_MESH_help)
def _generate_points(self, error):
x_cnt = self.mesh_config['x_count']
y_cnt = self.mesh_config['y_count']
@@ -604,6 +608,19 @@ class BedMeshCalibrate:
self.bedmesh.set_mesh(None)
self.update_config(gcmd)
self.probe_helper.start_probe(gcmd)
cmd_ADD_Z_OFFSET_TO_BED_MESH_help = "Add Z Offset To Current Bed Mesh"
def cmd_ADD_Z_OFFSET_TO_BED_MESH(self, gcmd):
zOffset = gcmd.get_float('ZOFFSET', 0)
bed_mesh_pmgr = self.bedmesh.pmgr
matrix = self.bedmesh.get_mesh().get_probed_matrix()
for row in range(len(matrix)):
for col in range(len(matrix[row])):
matrix[row][col] += zOffset
self.bedmesh.get_mesh().build_mesh(matrix)
bed_mesh_pmgr.save_profile(bed_mesh_pmgr.current_profile)
def probe_finalize(self, offsets, positions):
x_offset, y_offset, z_offset = offsets
positions = [[round(p[0], 2), round(p[1], 2), p[2]]
@@ -1135,6 +1152,7 @@ class ProfileManager:
self.gcode.register_command(
'BED_MESH_PROFILE', self.cmd_BED_MESH_PROFILE,
desc=self.cmd_BED_MESH_PROFILE_help)
def initialize(self):
self._check_incompatible_profiles()
if "default" in self.profiles:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,52 @@
from . import fan
PIN_MIN_TIME = 0.100
class ChamberFan:
def __init__(self, config):
self.printer = config.get_printer()
self.printer.register_event_handler("klippy:ready", self.handle_ready)
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
self.printer.load_object(config, 'heaters')
self.heaters = []
self.fan = fan.Fan(config)
self.fan_speed = config.getfloat('fan_speed', default=1.,
minval=0., maxval=1.)
self.idle_speed = config.getfloat(
'idle_speed', default=self.fan_speed, minval=0., maxval=1.)
self.idle_timeout = config.getint("idle_timeout", default=30, minval=0)
self.heater_names = config.getlist("heater", ())
self.last_on = self.idle_timeout
self.last_speed = 0.
def handle_connect(self):
# Heater lookup
pheaters = self.printer.lookup_object('heaters')
self.heaters = [pheaters.lookup_heater(n) for n in self.heater_names]
def handle_ready(self):
reactor = self.printer.get_reactor()
reactor.register_timer(self.callback, reactor.monotonic()+PIN_MIN_TIME)
def get_status(self, eventtime):
return self.fan.get_status(eventtime)
def callback(self, eventtime):
speed = 0.
active = False
for heater in self.heaters:
_, target_temp = heater.get_temp(eventtime)
if target_temp:
active = True
if active:
self.last_on = 0
speed = self.fan_speed
elif self.last_on < self.idle_timeout:
speed = self.idle_speed
self.last_on += 1
if speed != self.last_speed:
self.last_speed = speed
curtime = self.printer.get_reactor().monotonic()
print_time = self.fan.get_mcu().estimated_print_time(curtime)
self.fan.set_speed(print_time + PIN_MIN_TIME, speed)
return eventtime + 1.
def load_config_prefix(config):
return ChamberFan(config)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -6,7 +6,7 @@
import math, logging
import stepper
TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2240", "tmc2660", "tmc5160"]
TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2660", "tmc5160"]
# Calculate the trigger phase of a stepper motor
class PhaseCalc:

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -32,6 +32,7 @@ class ForceMove:
def __init__(self, config):
self.printer = config.get_printer()
self.steppers = {}
self._enable = config.getboolean("enable_force_move", False)
# Setup iterative solver
ffi_main, ffi_lib = chelper.get_ffi()
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
@@ -44,8 +45,8 @@ class ForceMove:
gcode.register_command('STEPPER_BUZZ', self.cmd_STEPPER_BUZZ,
desc=self.cmd_STEPPER_BUZZ_help)
gcode.register_command('SET_KINEMATIC_POSITION',
self.cmd_SET_KINEMATIC_POSITION,
desc=self.cmd_SET_KINEMATIC_POSITION_help)
self.cmd_SET_KINEMATIC_POSITION,
desc=self.cmd_SET_KINEMATIC_POSITION_help)
if config.getboolean("enable_force_move", False):
gcode.register_command('FORCE_MOVE', self.cmd_FORCE_MOVE,
desc=self.cmd_FORCE_MOVE_help)
@@ -113,16 +114,18 @@ class ForceMove:
self._restore_enable(stepper, was_enable)
cmd_FORCE_MOVE_help = "Manually move a stepper; invalidates kinematics"
def cmd_FORCE_MOVE(self, gcmd):
stepper = self._lookup_stepper(gcmd)
distance = gcmd.get_float('DISTANCE')
speed = gcmd.get_float('VELOCITY', above=0.)
accel = gcmd.get_float('ACCEL', 0., minval=0.)
logging.info("FORCE_MOVE %s distance=%.3f velocity=%.3f accel=%.3f",
stepper.get_name(), distance, speed, accel)
self._force_enable(stepper)
self.manual_move(stepper, distance, speed, accel)
if self._enable:
stepper = self._lookup_stepper(gcmd)
distance = gcmd.get_float('DISTANCE')
speed = gcmd.get_float('VELOCITY', above=0.)
accel = gcmd.get_float('ACCEL', 0., minval=0.)
logging.info("FORCE_MOVE %s distance=%.3f velocity=%.3f accel=%.3f",
stepper.get_name(), distance, speed, accel)
self._force_enable(stepper)
self.manual_move(stepper, distance, speed, accel)
cmd_SET_KINEMATIC_POSITION_help = "Force a low-level kinematic position"
def cmd_SET_KINEMATIC_POSITION(self, gcmd):
# if self._enable:
toolhead = self.printer.lookup_object('toolhead')
toolhead.get_last_move_time()
curpos = toolhead.get_position()
@@ -131,7 +134,6 @@ class ForceMove:
z = gcmd.get_float('Z', curpos[2])
logging.info("SET_KINEMATIC_POSITION pos=%.3f,%.3f,%.3f", x, y, z)
toolhead.set_position([x, y, z, curpos[3]], homing_axes=(0, 1, 2))
def load_config(config):
return ForceMove(config)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,15 @@
class GCodeMacroBreaker:
def __init__(self, config):
# Gcode macro interupt
self.printer = config.get_printer()
webhooks = self.printer.lookup_object('webhooks')
webhooks.register_endpoint("breakmacro", self._handle_breakmacro)
webhooks.register_endpoint("resumemacro", self._handle_resumemacro)
self.gcode = self.printer.lookup_object('gcode')
def _handle_breakmacro(self, web_request):
self.gcode.break_flag = True
def _handle_resumemacro(self, web_request):
self.gcode.break_flag = False
def load_config(config):
return GCodeMacroBreaker(config)

Binary file not shown.

View File

@@ -0,0 +1,87 @@
# Run a shell command via gcode
#
# Copyright (C) 2019 Eric Callahan <arksine.code@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os
import shlex
import subprocess
import logging
class ShellCommand:
def __init__(self, config):
self.name = config.get_name().split()[-1]
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
cmd = config.get('command')
cmd = os.path.expanduser(cmd)
self.command = shlex.split(cmd)
self.timeout = config.getfloat('timeout', 2., above=0.)
self.verbose = config.getboolean('verbose', True)
self.proc_fd = None
self.partial_output = ""
self.gcode.register_mux_command(
"RUN_SHELL_COMMAND", "CMD", self.name,
self.cmd_RUN_SHELL_COMMAND,
desc=self.cmd_RUN_SHELL_COMMAND_help)
def _process_output(self, eventime):
if self.proc_fd is None:
return
try:
data = os.read(self.proc_fd, 4096)
except Exception:
pass
data = self.partial_output + data.decode()
if '\n' not in data:
self.partial_output = data
return
elif data[-1] != '\n':
split = data.rfind('\n') + 1
self.partial_output = data[split:]
data = data[:split]
else:
self.partial_output = ""
self.gcode.respond_info(data)
cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command"
def cmd_RUN_SHELL_COMMAND(self, params):
gcode_params = params.get('PARAMS','')
gcode_params = shlex.split(gcode_params)
reactor = self.printer.get_reactor()
try:
proc = subprocess.Popen(
self.command + gcode_params, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception:
logging.exception(
"shell_command: Command {%s} failed" % (self.name))
raise self.gcode.error("Error running command {%s}" % (self.name))
if self.verbose:
self.proc_fd = proc.stdout.fileno()
self.gcode.respond_info("Running Command {%s}...:" % (self.name))
hdl = reactor.register_fd(self.proc_fd, self._process_output)
eventtime = reactor.monotonic()
endtime = eventtime + self.timeout
complete = False
while eventtime < endtime:
eventtime = reactor.pause(eventtime + .05)
if proc.poll() is not None:
complete = True
break
if not complete:
proc.terminate()
if self.verbose:
if self.partial_output:
self.gcode.respond_info(self.partial_output)
self.partial_output = ""
if complete:
msg = "Command {%s} finished\n" % (self.name)
else:
msg = "Command {%s} timed out" % (self.name)
self.gcode.respond_info(msg)
reactor.unregister_fd(hdl)
self.proc_fd = None
def load_config_prefix(config):
return ShellCommand(config)

View File

@@ -146,11 +146,11 @@ class HallFilamentWidthSensor:
and (self.filament_width >= self.min_diameter)):
percentage = round(self.nominal_filament_dia**2
/ self.filament_width**2 * 100)
self.gcode.run_script("M221 S" + str(percentage))
else:
self.gcode.run_script("M221 S100")
# self.gcode.run_script("M221 S" + str(percentage))
# else:
# self.gcode.run_script("M221 S100")
else:
self.gcode.run_script("M221 S100")
# self.gcode.run_script("M221 S100")
self.filament_array = []
if self.is_active:
@@ -171,7 +171,7 @@ class HallFilamentWidthSensor:
self.filament_array = []
gcmd.respond_info("Filament width measurements cleared!")
# Set extrude multiplier to 100%
self.gcode.run_script_from_command("M221 S100")
# self.gcode.run_script_from_command("M221 S100")
def cmd_M405(self, gcmd):
response = "Filament width sensor Turned On"
@@ -196,7 +196,7 @@ class HallFilamentWidthSensor:
# Clear filament array
self.filament_array = []
# Set extrude multiplier to 100%
self.gcode.run_script_from_command("M221 S100")
# self.gcode.run_script_from_command("M221 S100")
gcmd.respond_info(response)
def cmd_Get_Raw_Values(self, gcmd):

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -241,6 +241,16 @@ class PrinterHeaters:
gcode.register_command("M105", self.cmd_M105, when_not_ready=True)
gcode.register_command("TEMPERATURE_WAIT", self.cmd_TEMPERATURE_WAIT,
desc=self.cmd_TEMPERATURE_WAIT_help)
# Wait heater interupt
webhooks = self.printer.lookup_object('webhooks')
webhooks.register_endpoint("breakheater", self._handle_breakheater)
self.break_flag=False
def _handle_breakheater(self,web_request):
reactor = self.printer.get_reactor()
for heater in self.heaters.values():
eventtime = reactor.monotonic()
if heater.check_busy(eventtime):
self.break_flag = True
def load_config(self, config):
self.have_load_sensors = True
# Load default temperature sensors
@@ -330,7 +340,11 @@ class PrinterHeaters:
gcode = self.printer.lookup_object("gcode")
reactor = self.printer.get_reactor()
eventtime = reactor.monotonic()
self.break_flag = False
while not self.printer.is_shutdown() and heater.check_busy(eventtime):
if self.break_flag:
self.break_flag = False
break
print_time = toolhead.get_last_move_time()
gcode.respond_raw(self._get_temp(eventtime))
eventtime = reactor.pause(eventtime + 1.)
@@ -359,7 +373,7 @@ class PrinterHeaters:
toolhead = self.printer.lookup_object("toolhead")
reactor = self.printer.get_reactor()
eventtime = reactor.monotonic()
while not self.printer.is_shutdown():
while not self.printer.is_shutdown() and not self.break_flag:
temp, target = sensor.get_temp(eventtime)
if temp >= min_temp and temp <= max_temp:
return

Binary file not shown.

View File

@@ -228,6 +228,10 @@ class PrinterHoming:
# Register g-code commands
gcode = self.printer.lookup_object('gcode')
gcode.register_command('G28', self.cmd_G28)
# 2023/12/14
gcode_macro = self.printer.load_object(config, 'gcode_macro')
self.vibrate_gcode = gcode_macro.load_template(
config.getsection('bed_mesh'), 'vibrate_gcode', '')
def manual_home(self, toolhead, endstops, pos, speed,
triggered, check_triggered):
hmove = HomingMove(self.printer, endstops, toolhead)
@@ -250,8 +254,14 @@ class PrinterHoming:
"Probing failed due to printer shutdown")
raise
if hmove.check_no_movement() is not None:
raise self.printer.command_error(
"Probe triggered prior to movement")
# 2023/12/14 Chengwei
# error to info
gcode = self.printer.lookup_object('gcode')
gcode.respond_info('Probe triggered prior to movement')
######
# raise self.printer.command_error(
# "Probe triggered prior to movement")
######
return epos
def cmd_G28(self, gcmd):
# Move to origin

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -24,19 +24,9 @@ class ManualProbe:
'Z_OFFSET_APPLY_ENDSTOP',
self.cmd_Z_OFFSET_APPLY_ENDSTOP,
desc=self.cmd_Z_OFFSET_APPLY_ENDSTOP_help)
self.reset_status()
def manual_probe_finalize(self, kin_pos):
if kin_pos is not None:
self.gcode.respond_info("Z position is %.3f" % (kin_pos[2],))
def reset_status(self):
self.status = {
'is_active': False,
'z_position': None,
'z_position_lower': None,
'z_position_upper': None
}
def get_status(self, eventtime):
return self.status
cmd_MANUAL_PROBE_help = "Start manual probe helper script"
def cmd_MANUAL_PROBE(self, gcmd):
ManualProbeHelper(self.printer, gcmd, self.manual_probe_finalize)
@@ -88,7 +78,6 @@ class ManualProbeHelper:
self.finalize_callback = finalize_callback
self.gcode = self.printer.lookup_object('gcode')
self.toolhead = self.printer.lookup_object('toolhead')
self.manual_probe = self.printer.lookup_object('manual_probe')
self.speed = gcmd.get_float("SPEED", 5.)
self.past_positions = []
self.last_toolhead_pos = self.last_kinematics_pos = None
@@ -141,20 +130,11 @@ class ManualProbeHelper:
prev_pos = next_pos - 1
if next_pos < len(pp) and pp[next_pos] == z_pos:
next_pos += 1
prev_pos_val = next_pos_val = None
prev_str = next_str = "??????"
if prev_pos >= 0:
prev_pos_val = pp[prev_pos]
prev_str = "%.3f" % (prev_pos_val,)
prev_str = "%.3f" % (pp[prev_pos],)
if next_pos < len(pp):
next_pos_val = pp[next_pos]
next_str = "%.3f" % (next_pos_val,)
self.manual_probe.status = {
'is_active': True,
'z_position': z_pos,
'z_position_lower': prev_pos_val,
'z_position_upper': next_pos_val,
}
next_str = "%.3f" % (pp[next_pos],)
# Find recent positions
self.gcode.respond_info("Z position: %s --> %.3f <-- %s"
% (prev_str, z_pos, next_str))
@@ -203,7 +183,6 @@ class ManualProbeHelper:
self.move_z(next_z_pos)
self.report_z_status(next_z_pos != z_pos, z_pos)
def finalize(self, success):
self.manual_probe.reset_status()
self.gcode.register_command('ACCEPT', None)
self.gcode.register_command('NEXT', None)
self.gcode.register_command('ABORT', None)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,93 +1,120 @@
# Virtual SDCard print stat tracking
#
# Copyright (C) 2020 Eric Callahan <arksine.code@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
class PrintStats:
def __init__(self, config):
printer = config.get_printer()
self.gcode_move = printer.load_object(config, 'gcode_move')
self.reactor = printer.get_reactor()
self.reset()
def _update_filament_usage(self, eventtime):
gc_status = self.gcode_move.get_status(eventtime)
cur_epos = gc_status['position'].e
self.filament_used += (cur_epos - self.last_epos) \
/ gc_status['extrude_factor']
self.last_epos = cur_epos
def set_current_file(self, filename):
self.reset()
self.filename = filename
def note_start(self):
curtime = self.reactor.monotonic()
if self.print_start_time is None:
self.print_start_time = curtime
elif self.last_pause_time is not None:
# Update pause time duration
pause_duration = curtime - self.last_pause_time
self.prev_pause_duration += pause_duration
self.last_pause_time = None
# Reset last e-position
gc_status = self.gcode_move.get_status(curtime)
self.last_epos = gc_status['position'].e
self.state = "printing"
self.error_message = ""
def note_pause(self):
if self.last_pause_time is None:
curtime = self.reactor.monotonic()
self.last_pause_time = curtime
# update filament usage
self._update_filament_usage(curtime)
if self.state != "error":
self.state = "paused"
def note_complete(self):
self._note_finish("complete")
def note_error(self, message):
self._note_finish("error", message)
def note_cancel(self):
self._note_finish("cancelled")
def _note_finish(self, state, error_message = ""):
if self.print_start_time is None:
return
self.state = state
self.error_message = error_message
eventtime = self.reactor.monotonic()
self.total_duration = eventtime - self.print_start_time
if self.filament_used < 0.0000001:
# No positive extusion detected during print
self.init_duration = self.total_duration - \
self.prev_pause_duration
self.print_start_time = None
def reset(self):
self.filename = self.error_message = ""
self.state = "standby"
self.prev_pause_duration = self.last_epos = 0.
self.filament_used = self.total_duration = 0.
self.print_start_time = self.last_pause_time = None
self.init_duration = 0.
def get_status(self, eventtime):
time_paused = self.prev_pause_duration
if self.print_start_time is not None:
if self.last_pause_time is not None:
# Calculate the total time spent paused during the print
time_paused += eventtime - self.last_pause_time
else:
# Accumulate filament if not paused
self._update_filament_usage(eventtime)
self.total_duration = eventtime - self.print_start_time
if self.filament_used < 0.0000001:
# Track duration prior to extrusion
self.init_duration = self.total_duration - time_paused
print_duration = self.total_duration - self.init_duration - time_paused
return {
'filename': self.filename,
'total_duration': self.total_duration,
'print_duration': print_duration,
'filament_used': self.filament_used,
'state': self.state,
'message': self.error_message
}
def load_config(config):
return PrintStats(config)
# Virtual SDCard print stat tracking
#
# Copyright (C) 2020 Eric Callahan <arksine.code@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
class PrintStats:
def __init__(self, config):
printer = config.get_printer()
self.gcode_move = printer.load_object(config, 'gcode_move')
self.reactor = printer.get_reactor()
self.reset()
# Register commands
self.gcode = printer.lookup_object('gcode')
self.gcode.register_command(
"SET_PRINT_STATS_INFO", self.cmd_SET_PRINT_STATS_INFO,
desc=self.cmd_SET_PRINT_STATS_INFO_help)
def _update_filament_usage(self, eventtime):
gc_status = self.gcode_move.get_status(eventtime)
cur_epos = gc_status['position'].e
self.filament_used += (cur_epos - self.last_epos) \
/ gc_status['extrude_factor']
self.last_epos = cur_epos
def set_current_file(self, filename):
self.reset()
self.filename = filename
def note_start(self):
curtime = self.reactor.monotonic()
if self.print_start_time is None:
self.print_start_time = curtime
elif self.last_pause_time is not None:
# Update pause time duration
pause_duration = curtime - self.last_pause_time
self.prev_pause_duration += pause_duration
self.last_pause_time = None
# Reset last e-position
gc_status = self.gcode_move.get_status(curtime)
self.last_epos = gc_status['position'].e
self.state = "printing"
self.error_message = ""
def note_pause(self):
if self.last_pause_time is None:
curtime = self.reactor.monotonic()
self.last_pause_time = curtime
# update filament usage
self._update_filament_usage(curtime)
if self.state != "error":
self.state = "paused"
def note_complete(self):
self._note_finish("complete")
def note_error(self, message):
self._note_finish("error", message)
def note_cancel(self):
self._note_finish("cancelled")
def _note_finish(self, state, error_message = ""):
if self.print_start_time is None:
return
self.state = state
self.error_message = error_message
eventtime = self.reactor.monotonic()
self.total_duration = eventtime - self.print_start_time
if self.filament_used < 0.0000001:
# No positive extusion detected during print
self.init_duration = self.total_duration - \
self.prev_pause_duration
self.print_start_time = None
cmd_SET_PRINT_STATS_INFO_help = "Pass slicer info like layer act and " \
"total to klipper"
def cmd_SET_PRINT_STATS_INFO(self, gcmd):
total_layer = gcmd.get_int("TOTAL_LAYER", self.info_total_layer, \
minval=0)
current_layer = gcmd.get_int("CURRENT_LAYER", self.info_current_layer, \
minval=0)
if total_layer == 0:
self.info_total_layer = None
self.info_current_layer = None
elif total_layer != self.info_total_layer:
self.info_total_layer = total_layer
self.info_current_layer = 0
if self.info_total_layer is not None and \
current_layer is not None and \
current_layer != self.info_current_layer:
self.info_current_layer = min(current_layer, self.info_total_layer)
def reset(self):
self.filename = self.error_message = ""
self.state = "standby"
self.prev_pause_duration = self.last_epos = 0.
self.filament_used = self.total_duration = 0.
self.print_start_time = self.last_pause_time = None
self.init_duration = 0.
self.info_total_layer = None
self.info_current_layer = None
def get_status(self, eventtime):
time_paused = self.prev_pause_duration
if self.print_start_time is not None:
if self.last_pause_time is not None:
# Calculate the total time spent paused during the print
time_paused += eventtime - self.last_pause_time
else:
# Accumulate filament if not paused
self._update_filament_usage(eventtime)
self.total_duration = eventtime - self.print_start_time
if self.filament_used < 0.0000001:
# Track duration prior to extrusion
self.init_duration = self.total_duration - time_paused
print_duration = self.total_duration - self.init_duration - time_paused
return {
'filename': self.filename,
'total_duration': self.total_duration,
'print_duration': print_duration,
'filament_used': self.filament_used,
'state': self.state,
'message': self.error_message,
'info': {'total_layer': self.info_total_layer,
'current_layer': self.info_current_layer}
}
def load_config(config):
return PrintStats(config)

Binary file not shown.

View File

@@ -12,12 +12,14 @@ If the probe did not move far enough to trigger, then
consider reducing the Z axis minimum position so the probe
can travel further (the Z minimum position can be negative).
"""
_NERVER = 9999999
class PrinterProbe:
def __init__(self, config, mcu_probe):
self.printer = config.get_printer()
self.name = config.get_name()
self.mcu_probe = mcu_probe
self.config = config
self.speed = config.getfloat('speed', 5.0, above=0.)
self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.)
self.x_offset = config.getfloat('x_offset', 0.)
@@ -28,6 +30,12 @@ class PrinterProbe:
self.last_state = False
self.last_z_result = 0.
self.gcode_move = self.printer.load_object(config, "gcode_move")
# vibrate after retry failed
gcode_macro = self.printer.load_object(config, 'gcode_macro')
self.vibrate_gcode = gcode_macro.load_template(
config.getsection('bed_mesh'), 'vibrate_gcode', '')
self.probe_count = 0
self.vibrate = config.getsection('bed_mesh').getint('vibrate', _NERVER)
# Infer Z position to move to during a probe
if config.has_section('stepper_z'):
zconfig = config.getsection('stepper_z')
@@ -74,6 +82,8 @@ class PrinterProbe:
self.gcode.register_command('Z_OFFSET_APPLY_PROBE',
self.cmd_Z_OFFSET_APPLY_PROBE,
desc=self.cmd_Z_OFFSET_APPLY_PROBE_help)
self.gcode.register_command('MKS_SHOW_Z_OFFSET', self.cmd_MKS_SHOW_Z_OFFSET,
desc=self.cmd_MKS_SHOW_Z_OFFSET_help)
def _handle_homing_move_begin(self, hmove):
if self.mcu_probe in hmove.get_mcu_endstops():
self.mcu_probe.probe_prepare(hmove)
@@ -169,6 +179,8 @@ class PrinterProbe:
probexy = self.printer.lookup_object('toolhead').get_position()[:2]
retries = 0
positions = []
gcode = self.gcode
last_probe_failed = False
while len(positions) < sample_count:
# Probe position
pos = self._probe(speed)
@@ -179,11 +191,27 @@ class PrinterProbe:
if retries >= samples_retries:
raise gcmd.error("Probe samples exceed samples_tolerance")
gcmd.respond_info("Probe samples exceed tolerance. Retrying...")
last_probe_failed = True
retries += 1
positions = []
self.probe_count += 1
# Retract
if len(positions) < sample_count:
self._move(probexy + [pos[2] + sample_retract_dist], lift_speed)
if last_probe_failed:
self._move(probexy + [sample_retract_dist * 2], lift_speed)
if self.probe_count != 0 and self.probe_count % self.vibrate == 0:
gcode.respond_info('Probe ' + str(self.probe_count) + " times, start vibrating")
commands = [
'G91',
'G1 Z20 F300',
'G90',
'G1 Z10 F300'
]
gcode._process_commands(commands, False)
self.vibrate_gcode.run_gcode_from_command()
last_probe_failed = False
else:
self._move(probexy + [pos[2] + sample_retract_dist], lift_speed)
if must_notify_multi_probe:
self.multi_probe_end()
# Calculate and return result
@@ -274,8 +302,7 @@ class PrinterProbe:
# Move the nozzle over the probe point
curpos[0] += self.x_offset
curpos[1] += self.y_offset
#PwAddNew
self._move(curpos,80.)
self._move(curpos, self.speed)
# Start manual probe
manual_probe.ManualProbeHelper(self.printer, gcmd,
self.probe_calibrate_finalize)
@@ -293,6 +320,14 @@ class PrinterProbe:
% (self.name, new_calibrate))
configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,))
cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset"
def cmd_MKS_SHOW_Z_OFFSET(self, gcmd):
offset = self.gcode_move.get_status()['homing_origin'].z
if offset == 0:
self.gcode.respond_info("Nothing to do: Z Offset is 0")
else:
new_calibrate = self.z_offset - offset
self.gcode.respond_info(" %s: z_offset: %.3f\n" % (self.name, new_calibrate))
cmd_MKS_SHOW_Z_OFFSET_help = "Show the probe's z_offset"
# Endstop wrapper that enables probe specific features
class ProbeEndstopWrapper:
@@ -382,6 +417,11 @@ class ProbePointsHelper:
self.lift_speed = self.speed
self.probe_offsets = (0., 0., 0.)
self.results = []
# vibrate
self.vibrate = config.getint('vibrate', 9999999)
gcode_macro = self.printer.load_object(config, 'gcode_macro')
self.vibrate_gcode = gcode_macro.load_template(
config, 'vibrate_gcode', '')
def minimum_points(self,n):
if len(self.probe_points) < n:
raise self.printer.config_error(
@@ -434,11 +474,23 @@ class ProbePointsHelper:
raise gcmd.error("horizontal_move_z can't be less than"
" probe's z_offset")
probe.multi_probe_begin()
probe.probe_count = 0
while 1:
done = self._move_next()
if done:
break
if self.gcode.break_flag:
break
pos = probe.run_probe(gcmd)
probe.probe_count += 1
gcode = self.printer.lookup_object('gcode')
if self.vibrate and probe.probe_count % self.vibrate == 0:
commands = [
'G90',
'G1 Z'+ str(self.horizontal_move_z) + ' F300'
]
gcode._process_commands(commands, False)
self.vibrate_gcode.run_gcode_from_command()
self.results.append(pos)
probe.multi_probe_end()
def _manual_probe_start(self):

Binary file not shown.

Binary file not shown.

136
klippy/extras/qdprobe.py Normal file
View File

@@ -0,0 +1,136 @@
# -*- coding: utf-8 -*-
import logging
from . import smart_effector
from . import probe
# Makerbase Endstop wrapper
class MakerbaseProbeEndstopWrapper:
def __init__(self, config):
self.printer = config.get_printer()
self.position_endstop = config.getfloat('z_offset')
self.stow_on_each_sample = config.getboolean(
'deactivate_on_each_sample', True)
gcode_macro = self.printer.load_object(config, 'gcode_macro')
self.activate_gcode = gcode_macro.load_template(
config, 'activate_gcode', '')
self.deactivate_gcode = gcode_macro.load_template(
config, 'deactivate_gcode', '')
ppins = self.printer.lookup_object('pins')
pin = config.get('pin')
pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True)
mcu = pin_params['chip']
self.mcu_endstop = mcu.setup_pin('endstop', pin_params)
self.printer.register_event_handler('klippy:mcu_identify',
self._handle_mcu_identify)
# Wrappers
self.get_mcu = self.mcu_endstop.get_mcu
self.add_stepper = self.mcu_endstop.add_stepper
self.get_steppers = self.mcu_endstop.get_steppers
self.home_start = self.mcu_endstop.home_start
self.home_wait = self.mcu_endstop.home_wait
self.query_endstop = self.mcu_endstop.query_endstop
# multi probes state
self.multi = 'OFF'
def _handle_mcu_identify(self):
kin = self.printer.lookup_object('toolhead').get_kinematics()
for stepper in kin.get_steppers():
if stepper.is_active_axis('z'):
self.add_stepper(stepper)
def raise_probe(self):
toolhead = self.printer.lookup_object('toolhead')
start_pos = toolhead.get_position()
self.deactivate_gcode.run_gcode_from_command()
if toolhead.get_position()[:3] != start_pos[:3]:
raise self.printer.command_error(
"Toolhead moved during probe activate_gcode script")
def lower_probe(self):
toolhead = self.printer.lookup_object('toolhead')
start_pos = toolhead.get_position()
self.activate_gcode.run_gcode_from_command()
if toolhead.get_position()[:3] != start_pos[:3]:
raise self.printer.command_error(
"Toolhead moved during probe deactivate_gcode script")
def multi_probe_begin(self):
if self.stow_on_each_sample:
return
self.multi = 'FIRST'
def multi_probe_end(self):
if self.stow_on_each_sample:
return
self.raise_probe()
self.multi = 'OFF'
def probe_prepare(self, hmove):
if self.multi == 'OFF' or self.multi == 'FIRST':
self.lower_probe()
if self.multi == 'FIRST':
self.multi = 'ON'
def probe_finish(self, hmove):
if self.multi == 'OFF':
self.raise_probe()
def get_position_endstop(self):
return self.position_endstop
class MakerBase:
def __init__(self, config):
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
# self.speed = config.getfloat('speed', 5.0, above=0.)
# self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.)
# self.x_offset = config.getfloat('x_offset', 0.)
# self.y_offset = config.getfloat('y_offset', 0.)
# self.z_offset = config.getfloat('z_offset')
self.probe = self.printer.lookup_object('probe')
self.endstop_wrapper = probe.ProbeEndstopWrapper(config)
# self.probe_accel = config.getfloat('probe_accel', 0., minval=0.)
# self.recovery_time = config.getfloat('recovery_time', 0.4, minval=0.)
# Register MakerBase commands
self.gcode.register_command('MKS_PROBE_PIN_1', self.cmd_MKS_PROBE_PIN_1,
desc=self.cmd_MKS_PROBE_PIN_1_help)
self.gcode.register_command('MKS_PROBE_PIN_2', self.cmd_MKS_PROBE_PIN_2,
desc=self.cmd_MKS_PROBE_PIN_2_help)
self.gcode.register_command('QIDI_PROBE_PIN_1', self.cmd_MKS_PROBE_PIN_1,
desc=self.cmd_MKS_PROBE_PIN_1_help)
self.gcode.register_command('QIDI_PROBE_PIN_2', self.cmd_MKS_PROBE_PIN_2,
desc=self.cmd_MKS_PROBE_PIN_2_help)
self.gcode.register_command('MKS_REMOVE', self.cmd_MKS_REMOVE,
desc=self.cmd_MKS_REMOVE_help)
cmd_MKS_PROBE_PIN_2_help = 'ENABLE_PROBE_PIN_2'
def cmd_MKS_PROBE_PIN_2(self, gcmd):
self.probe.mcu_probe.probe_wrapper = self.endstop_wrapper
# Wrappers
self.probe.mcu_probe.get_mcu = self.endstop_wrapper.get_mcu
self.probe.mcu_probe.add_stepper = self.endstop_wrapper.add_stepper
self.probe.mcu_probe.get_steppers = self.endstop_wrapper.get_steppers
self.probe.mcu_probe.home_start = self.endstop_wrapper.home_start
self.probe.mcu_probe.home_wait = self.endstop_wrapper.home_wait
self.probe.mcu_probe.query_endstop = self.endstop_wrapper.query_endstop
self.probe.mcu_probe.multi_probe_begin = self.endstop_wrapper.multi_probe_begin
self.probe.mcu_probe.multi_probe_end = self.endstop_wrapper.multi_probe_end
# gcmd.respond_raw("%s" % (self.cmd_MKS_PROBE_PIN_2_help, ))
cmd_MKS_REMOVE_help = 'MKS_REMOVE'
def cmd_MKS_REMOVE(self, gcmd):
self.printer.remove_object('probe')
self.printer.lookup_object('gcode').remove_command('PROBE')
self.printer.lookup_object('gcode').remove_command('QUERY_PROBE')
self.printer.lookup_object('gcode').remove_command('PROBE_CALIBRATE')
self.printer.lookup_object('gcode').remove_command('PROBE_ACCURACY')
self.printer.lookup_object('gcode').remove_command('Z_OFFSET_APPLY_PROBE')
self.printer.lookup_object('gcode').remove_command('MKS_SHOW_Z_OFFSET')
self.printer.lookup_object('pins').remove_chip('probe')
cmd_MKS_PROBE_PIN_1_help = 'ENABLE_PROBE_PIN_1'
def cmd_MKS_PROBE_PIN_1(self, gcmd):
self.probe.mcu_probe.probe_wrapper = self.probe.mcu_probe.probe_wrapper_2
# Wrappers
self.probe.mcu_probe.get_mcu = self.probe.mcu_probe.probe_wrapper_2.get_mcu
self.probe.mcu_probe.add_stepper = self.probe.mcu_probe.probe_wrapper_2.add_stepper
self.probe.mcu_probe.get_steppers = self.probe.mcu_probe.probe_wrapper_2.get_steppers
self.probe.mcu_probe.home_start = self.probe.mcu_probe.probe_wrapper_2.home_start
self.probe.mcu_probe.home_wait = self.probe.mcu_probe.probe_wrapper_2.home_wait
self.probe.mcu_probe.query_endstop = self.probe.mcu_probe.probe_wrapper_2.query_endstop
self.probe.mcu_probe.multi_probe_begin = self.probe.mcu_probe.probe_wrapper_2.multi_probe_begin
self.probe.mcu_probe.multi_probe_end = self.probe.mcu_probe.probe_wrapper_2.multi_probe_end
# gcmd.respond_raw("%s" % (self.cmd_MKS_PROBE_PIN_1_help, ))
def load_config(config):
return MakerBase(config)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -54,7 +54,7 @@ class SmartEffectorEndstopWrapper:
self.gcode = self.printer.lookup_object('gcode')
self.probe_accel = config.getfloat('probe_accel', 0., minval=0.)
self.recovery_time = config.getfloat('recovery_time', 0.4, minval=0.)
self.probe_wrapper = probe.ProbeEndstopWrapper(config)
self.probe_wrapper = self.probe_wrapper_2 = probe.ProbeEndstopWrapper(config)
# Wrappers
self.get_mcu = self.probe_wrapper.get_mcu
self.add_stepper = self.probe_wrapper.add_stepper
@@ -78,6 +78,16 @@ class SmartEffectorEndstopWrapper:
self.gcode.register_command("SET_SMART_EFFECTOR",
self.cmd_SET_SMART_EFFECTOR,
desc=self.cmd_SET_SMART_EFFECTOR_help)
self.gcode.register_command("CHANGE_PIN",
self.cmd_CHANGE_PIN,
desc=self.cmd_CHANGE_PIN_help)
self.pin2 = config.get('pin2', None)
self.pin2_enable = 0
if self.pin2:
self.pin2_enable = 1
def probe_prepare(self, hmove):
toolhead = self.printer.lookup_object('toolhead')
self.probe_wrapper.probe_prepare(hmove)
@@ -146,6 +156,9 @@ class SmartEffectorEndstopWrapper:
buf = [131, 131]
self._send_command(buf)
gcmd.respond_info('SmartEffector sensitivity was reset')
cmd_CHANGE_PIN_help = 'print pin'
def cmd_CHANGE_PIN(self, gcmd):
pass
def load_config(config):
smart_effector = SmartEffectorEndstopWrapper(config)

View File

@@ -216,7 +216,7 @@ class MAX31855(SensorBase):
######################################################################
MAX6675_SCALE = 3
MAX6675_MULT = 0.25 * 0.95
MAX6675_MULT = 0.25
class MAX6675(SensorBase):
def __init__(self, config):

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -93,7 +93,7 @@ class TMCErrorCheck:
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
self.check_timer = None
self.last_drv_status = self.last_status = None
self.last_drv_status = self.last_drv_fields = None
# Setup for GSTAT query
reg_name = self.fields.lookup_register("drv_err")
if reg_name is not None:
@@ -109,9 +109,6 @@ class TMCErrorCheck:
# TMC2130 driver quirks
self.clear_gstat = False
cs_actual_mask = self.fields.all_fields[reg_name]["cs_actual"]
# elif name_parts[0] == 'tmc2240':
# self.clear_gstat = False
# cs_actual_mask = self.fields.all_fields[reg_name]["cs_actual"]
elif name_parts[0] == 'tmc2660':
# TMC2660 driver quirks
self.irun_field = "cs"
@@ -125,6 +122,12 @@ class TMCErrorCheck:
if f in err_fields:
err_mask |= self.fields.all_fields[reg_name][f]
self.drv_status_reg_info = [0, reg_name, mask, err_mask, cs_actual_mask]
# Setup for temperature query
self.adc_temp = None
self.adc_temp_reg = self.fields.lookup_register("adc_temp")
if self.adc_temp_reg is not None:
pheaters = self.printer.load_object(config, 'heaters')
# pheaters.register_monitor(config)
def _query_register(self, reg_info, try_clear=False):
last_value, reg_name, mask, err_mask, cs_actual_mask = reg_info
cleared_flags = 0
@@ -158,17 +161,28 @@ class TMCErrorCheck:
if count >= 3:
fmt = self.fields.pretty_format(reg_name, val)
raise self.printer.command_error("TMC '%s' reports error: %s"
% (self.stepper_name, fmt))
% (self.stepper_name, fmt))
if "uv_cp" in fmt:
try_clear = True
if try_clear and val & err_mask:
try_clear = False
cleared_flags |= val & err_mask
self.mcu_tmc.set_register(reg_name, val & err_mask)
return cleared_flags
def _query_temperature(self):
try:
self.adc_temp = self.mcu_tmc.get_register(self.adc_temp_reg)
except self.printer.command_error as e:
# Ignore comms error for temperature
self.adc_temp = None
return
def _do_periodic_check(self, eventtime):
try:
self._query_register(self.drv_status_reg_info)
if self.gstat_reg_info is not None:
self._query_register(self.gstat_reg_info)
if self.adc_temp_reg is not None:
self._query_temperature()
except self.printer.command_error as e:
self.printer.invoke_shutdown(str(e))
return self.printer.get_reactor().NEVER
@@ -197,14 +211,16 @@ class TMCErrorCheck:
return False
def get_status(self, eventtime=None):
if self.check_timer is None:
return {'drv_status': None}
return {'drv_status': None, 'temperature': None}
temp = None
if self.adc_temp is not None:
temp = round((self.adc_temp - 2038) / 7.7, 2)
last_value, reg_name = self.drv_status_reg_info[:2]
if last_value != self.last_drv_status:
self.last_drv_status = last_value
fields = self.fields.get_reg_fields(reg_name, last_value)
fields = {n: v for n, v in fields.items() if v}
self.last_status = {'drv_status': fields}
return self.last_status
self.last_drv_fields = {n: v for n, v in fields.items() if v}
return {'drv_status': self.last_drv_fields, 'temperature': temp}
######################################################################
@@ -248,7 +264,8 @@ class TMCCommandHelper:
desc=self.cmd_SET_TMC_CURRENT_help)
def _init_registers(self, print_time=None):
# Send registers
for reg_name, val in self.fields.registers.items():
for reg_name in list(self.fields.registers.keys()):
val = self.fields.registers[reg_name] # Val may change during loop
self.mcu_tmc.set_register(reg_name, val, print_time)
cmd_INIT_TMC_help = "Initialize TMC stepper driver registers"
def cmd_INIT_TMC(self, gcmd):
@@ -261,7 +278,18 @@ class TMCCommandHelper:
reg_name = self.fields.lookup_register(field_name, None)
if reg_name is None:
raise gcmd.error("Unknown field name '%s'" % (field_name,))
value = gcmd.get_int('VALUE')
value = gcmd.get_int('VALUE', None)
velocity = gcmd.get_float('VELOCITY', None, minval=0.)
tmc_frequency = self.mcu_tmc.get_tmc_frequency()
if tmc_frequency is None and velocity is not None:
raise gcmd.error("VELOCITY parameter not supported by this driver")
if (value is None) == (velocity is None):
raise gcmd.error("Specify either VALUE or VELOCITY")
if velocity is not None:
step_dist = self.stepper.get_step_dist()
mres = self.fields.get_field("mres")
value = TMCtstepHelper(step_dist, mres, tmc_frequency,
velocity)
reg_val = self.fields.set_field(field_name, value)
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
self.mcu_tmc.set_register(reg_name, reg_val, print_time)
@@ -405,17 +433,32 @@ class TMCCommandHelper:
cmd_DUMP_TMC_help = "Read and display TMC stepper driver registers"
def cmd_DUMP_TMC(self, gcmd):
logging.info("DUMP_TMC %s", self.name)
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
gcmd.respond_info("========== Write-only registers ==========")
for reg_name, val in self.fields.registers.items():
if reg_name not in self.read_registers:
reg_name = gcmd.get('REGISTER', None)
if reg_name is not None:
reg_name = reg_name.upper()
val = self.fields.registers.get(reg_name)
if (val is not None) and (reg_name not in self.read_registers):
# write-only register
gcmd.respond_info(self.fields.pretty_format(reg_name, val))
elif reg_name in self.read_registers:
# readable register
val = self.mcu_tmc.get_register(reg_name)
if self.read_translate is not None:
reg_name, val = self.read_translate(reg_name, val)
gcmd.respond_info(self.fields.pretty_format(reg_name, val))
else:
raise gcmd.error("Unknown register name '%s'" % (reg_name))
else:
gcmd.respond_info("========== Write-only registers ==========")
for reg_name, val in self.fields.registers.items():
if reg_name not in self.read_registers:
gcmd.respond_info(self.fields.pretty_format(reg_name, val))
gcmd.respond_info("========== Queried registers ==========")
for reg_name in self.read_registers:
val = self.mcu_tmc.get_register(reg_name)
if self.read_translate is not None:
reg_name, val = self.read_translate(reg_name, val)
gcmd.respond_info(self.fields.pretty_format(reg_name, val))
gcmd.respond_info("========== Queried registers ==========")
for reg_name in self.read_registers:
val = self.mcu_tmc.get_register(reg_name)
if self.read_translate is not None:
reg_name, val = self.read_translate(reg_name, val)
gcmd.respond_info(self.fields.pretty_format(reg_name, val))
######################################################################
@@ -440,7 +483,7 @@ class TMCVirtualPinHelper:
self.diag_pin_field = None
self.mcu_endstop = None
self.en_pwm = False
self.pwmthrs = 0
self.pwmthrs = self.coolthrs = 0
# Register virtual_endstop pin
name_parts = config.get_name().split()
ppins = self.printer.lookup_object("pins")
@@ -455,13 +498,6 @@ class TMCVirtualPinHelper:
if self.diag_pin is None:
raise ppins.error("tmc virtual endstop requires diag pin config")
# Setup for sensorless homing
reg = self.fields.lookup_register("en_pwm_mode", None)
if reg is None:
self.en_pwm = not self.fields.get_field("en_spreadcycle")
self.pwmthrs = self.fields.get_field("tpwmthrs")
else:
self.en_pwm = self.fields.get_field("en_pwm_mode")
self.pwmthrs = 0
self.printer.register_event_handler("homing:homing_move_begin",
self.handle_homing_move_begin)
self.printer.register_event_handler("homing:homing_move_end",
@@ -471,21 +507,24 @@ class TMCVirtualPinHelper:
def handle_homing_move_begin(self, hmove):
if self.mcu_endstop not in hmove.get_mcu_endstops():
return
self.pwmthrs = self.fields.get_field("tpwmthrs")
self.coolthrs = self.fields.get_field("tcoolthrs")
reg = self.fields.lookup_register("en_pwm_mode", None)
if reg is None:
logging.info("##############################")
# 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)
else:
# On earlier drivers, "stealthchop" must be disabled
logging.info("******************************")
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)
tc_val = self.fields.set_field("tcoolthrs", 0xfffff)
self.mcu_tmc.set_register("TCOOLTHRS", tc_val)
if self.coolthrs == 0:
tc_val = self.fields.set_field("tcoolthrs", 0xfffff)
self.mcu_tmc.set_register("TCOOLTHRS", tc_val)
def handle_homing_move_end(self, hmove):
if self.mcu_endstop not in hmove.get_mcu_endstops():
return
@@ -498,7 +537,7 @@ class TMCVirtualPinHelper:
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)
tc_val = self.fields.set_field("tcoolthrs", 0)
tc_val = self.fields.set_field("tcoolthrs", self.coolthrs)
self.mcu_tmc.set_register("TCOOLTHRS", tc_val)
@@ -506,10 +545,35 @@ class TMCVirtualPinHelper:
# Config reading helpers
######################################################################
# Helper to initialize the wave table from config or defaults
def TMCWaveTableHelper(config, mcu_tmc):
set_config_field = mcu_tmc.get_fields().set_config_field
set_config_field(config, "mslut0", 0xAAAAB554)
set_config_field(config, "mslut1", 0x4A9554AA)
set_config_field(config, "mslut2", 0x24492929)
set_config_field(config, "mslut3", 0x10104222)
set_config_field(config, "mslut4", 0xFBFFFFFF)
set_config_field(config, "mslut5", 0xB5BB777D)
set_config_field(config, "mslut6", 0x49295556)
set_config_field(config, "mslut7", 0x00404222)
set_config_field(config, "w0", 2)
set_config_field(config, "w1", 1)
set_config_field(config, "w2", 1)
set_config_field(config, "w3", 1)
set_config_field(config, "x1", 128)
set_config_field(config, "x2", 255)
set_config_field(config, "x3", 255)
set_config_field(config, "start_sin", 0)
set_config_field(config, "start_sin90", 247)
# Helper to configure and query the microstep settings
def TMCMicrostepHelper(config, mcu_tmc):
fields = mcu_tmc.get_fields()
stepper_name = " ".join(config.get_name().split()[1:])
if not config.has_section(stepper_name):
raise config.error(
"Could not find config section '[%s]' required by tmc driver"
% (stepper_name,))
stepper_config = ms_config = config.getsection(stepper_name)
if (stepper_config.get('microsteps', None, note_valid=False) is None
and config.get('microsteps', None, note_valid=False) is not None):
@@ -520,41 +584,36 @@ def TMCMicrostepHelper(config, mcu_tmc):
fields.set_field("mres", mres)
fields.set_field("intpol", config.getboolean("interpolate", True))
# Helper to configure "stealthchop" mode
# Helper for calculating TSTEP based values from velocity
def TMCtstepHelper(step_dist, mres, tmc_freq, velocity):
if velocity > 0.:
step_dist_256 = step_dist / (1 << mres)
threshold = int(tmc_freq * step_dist_256 / velocity + .5)
return max(0, min(0xfffff, threshold))
else:
return 0xfffff
# Helper to configure stealthChop-spreadCycle transition velocity
def TMCStealthchopHelper(config, mcu_tmc, tmc_freq):
fields = mcu_tmc.get_fields()
en_pwm_mode = False
velocity = config.getfloat('stealthchop_threshold', 0., minval=0.)
if velocity:
velocity = config.getfloat('stealthchop_threshold', None, minval=0.)
tpwmthrs = 0xfffff
if velocity is not None:
en_pwm_mode = True
stepper_name = " ".join(config.get_name().split()[1:])
sconfig = config.getsection(stepper_name)
rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig)
step_dist = rotation_dist / steps_per_rotation
step_dist_256 = step_dist / (1 << fields.get_field("mres"))
threshold = int(tmc_freq * step_dist_256 / velocity + .5)
fields.set_field("tpwmthrs", max(0, min(0xfffff, threshold)))
en_pwm_mode = True
mres = fields.get_field("mres")
tpwmthrs = TMCtstepHelper(step_dist, mres, tmc_freq, velocity)
fields.set_field("tpwmthrs", tpwmthrs)
reg = fields.lookup_register("en_pwm_mode", None)
if reg is not None:
fields.set_field("en_pwm_mode", en_pwm_mode)
else:
# TMC2208 uses en_spreadCycle
fields.set_field("en_spreadcycle", not en_pwm_mode)
# Helper to configure "stealthchop" mode
def TMC2240StealthchopHelper(config, mcu_tmc, tmc_freq):
fields = mcu_tmc.get_fields()
en_pwm_mode = True
velocity = config.getfloat('stealthchop_threshold', 0., minval=0.)
if velocity:
stepper_name = " ".join(config.get_name().split()[1:])
sconfig = config.getsection(stepper_name)
rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig)
step_dist = rotation_dist / steps_per_rotation
step_dist_256 = step_dist / (1 << fields.get_field("mres"))
threshold = int(tmc_freq * step_dist_256 / velocity + .5)
fields.set_field("tpwmthrs", max(0, min(0xfffff, threshold)))
en_pwm_mode = False
reg = fields.lookup_register("en_pwm_mode", None)
if reg is not None:
fields.set_field("en_pwm_mode", en_pwm_mode)

Binary file not shown.

View File

@@ -1,287 +1,320 @@
# TMC2130 configuration
#
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
from . import bus, tmc
TMC_FREQUENCY=13200000.
Registers = {
"GCONF": 0x00, "GSTAT": 0x01, "IOIN": 0x04, "IHOLD_IRUN": 0x10,
"TPOWERDOWN": 0x11, "TSTEP": 0x12, "TPWMTHRS": 0x13, "TCOOLTHRS": 0x14,
"THIGH": 0x15, "XDIRECT": 0x2d, "MSLUT0": 0x60, "MSLUTSEL": 0x68,
"MSLUTSTART": 0x69, "MSCNT": 0x6a, "MSCURACT": 0x6b, "CHOPCONF": 0x6c,
"COOLCONF": 0x6d, "DCCTRL": 0x6e, "DRV_STATUS": 0x6f, "PWMCONF": 0x70,
"PWM_SCALE": 0x71, "ENCM_CTRL": 0x72, "LOST_STEPS": 0x73,
}
ReadRegisters = [
"GCONF", "GSTAT", "IOIN", "TSTEP", "XDIRECT", "MSCNT", "MSCURACT",
"CHOPCONF", "DRV_STATUS", "PWM_SCALE", "LOST_STEPS",
]
Fields = {}
Fields["GCONF"] = {
"i_scale_analog": 1<<0, "internal_rsense": 1<<1, "en_pwm_mode": 1<<2,
"enc_commutation": 1<<3, "shaft": 1<<4, "diag0_error": 1<<5,
"diag0_otpw": 1<<6, "diag0_stall": 1<<7, "diag1_stall": 1<<8,
"diag1_index": 1<<9, "diag1_onstate": 1<<10, "diag1_steps_skipped": 1<<11,
"diag0_int_pushpull": 1<<12, "diag1_pushpull": 1<<13,
"small_hysteresis": 1<<14, "stop_enable": 1<<15, "direct_mode": 1<<16,
"test_mode": 1<<17
}
Fields["GSTAT"] = { "reset": 1<<0, "drv_err": 1<<1, "uv_cp": 1<<2 }
Fields["IOIN"] = {
"step": 1<<0, "dir": 1<<1, "dcen_cfg4": 1<<2, "dcin_cfg5": 1<<3,
"drv_enn_cfg6": 1<<4, "dco": 1<<5, "version": 0xff << 24
}
Fields["IHOLD_IRUN"] = {
"ihold": 0x1f << 0, "irun": 0x1f << 8, "iholddelay": 0x0f << 16
}
Fields["TPOWERDOWN"] = { "tpowerdown": 0xff }
Fields["TSTEP"] = { "tstep": 0xfffff }
Fields["TPWMTHRS"] = { "tpwmthrs": 0xfffff }
Fields["TCOOLTHRS"] = { "tcoolthrs": 0xfffff }
Fields["THIGH"] = { "thigh": 0xfffff }
Fields["MSCNT"] = { "mscnt": 0x3ff }
Fields["MSCURACT"] = { "cur_a": 0x1ff, "cur_b": 0x1ff << 16 }
Fields["CHOPCONF"] = {
"toff": 0x0f, "hstrt": 0x07 << 4, "hend": 0x0f << 7, "fd3": 1<<11,
"disfdcc": 1<<12, "rndtf": 1<<13, "chm": 1<<14, "tbl": 0x03 << 15,
"vsense": 1<<17, "vhighfs": 1<<18, "vhighchm": 1<<19, "sync": 0x0f << 20,
"mres": 0x0f << 24, "intpol": 1<<28, "dedge": 1<<29, "diss2g": 1<<30
}
Fields["COOLCONF"] = {
"semin": 0x0f, "seup": 0x03 << 5, "semax": 0x0f << 8, "sedn": 0x03 << 13,
"seimin": 1<<15, "sgt": 0x7f << 16, "sfilt": 1<<24
}
Fields["DRV_STATUS"] = {
"sg_result": 0x3ff, "fsactive": 1<<15, "cs_actual": 0x1f << 16,
"stallguard": 1<<24, "ot": 1<<25, "otpw": 1<<26, "s2ga": 1<<27,
"s2gb": 1<<28, "ola": 1<<29, "olb": 1<<30, "stst": 1<<31
}
Fields["PWMCONF"] = {
"pwm_ampl": 0xff, "pwm_grad": 0xff << 8, "pwm_freq": 0x03 << 16,
"pwm_autoscale": 1<<18, "pwm_symmetric": 1<<19, "freewheel": 0x03 << 20
}
Fields["PWM_SCALE"] = { "pwm_scale": 0xff }
Fields["LOST_STEPS"] = { "lost_steps": 0xfffff }
SignedFields = ["cur_a", "cur_b", "sgt"]
FieldFormatters = {
"i_scale_analog": (lambda v: "1(ExtVREF)" if v else ""),
"shaft": (lambda v: "1(Reverse)" if v else ""),
"reset": (lambda v: "1(Reset)" if v else ""),
"drv_err": (lambda v: "1(ErrorShutdown!)" if v else ""),
"uv_cp": (lambda v: "1(Undervoltage!)" if v else ""),
"version": (lambda v: "%#x" % v),
"mres": (lambda v: "%d(%dusteps)" % (v, 0x100 >> v)),
"otpw": (lambda v: "1(OvertempWarning!)" if v else ""),
"ot": (lambda v: "1(OvertempError!)" if v else ""),
"s2ga": (lambda v: "1(ShortToGND_A!)" if v else ""),
"s2gb": (lambda v: "1(ShortToGND_B!)" if v else ""),
"ola": (lambda v: "1(OpenLoad_A!)" if v else ""),
"olb": (lambda v: "1(OpenLoad_B!)" if v else ""),
"cs_actual": (lambda v: ("%d" % v) if v else "0(Reset?)"),
}
######################################################################
# TMC stepper current config helper
######################################################################
MAX_CURRENT = 2.000
class TMCCurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
run_current = config.getfloat('run_current',
above=0., maxval=MAX_CURRENT)
hold_current = config.getfloat('hold_current', MAX_CURRENT,
above=0., maxval=MAX_CURRENT)
self.req_hold_current = hold_current
self.sense_resistor = config.getfloat('sense_resistor', 0.110, above=0.)
vsense, irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("vsense", vsense)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
def _calc_current_bits(self, current, vsense):
sense_resistor = self.sense_resistor + 0.020
vref = 0.32
if vsense:
vref = 0.18
cs = int(32. * sense_resistor * current * math.sqrt(2.) / vref + .5) - 1
return max(0, min(31, cs))
def _calc_current_from_bits(self, cs, vsense):
sense_resistor = self.sense_resistor + 0.020
vref = 0.32
if vsense:
vref = 0.18
return (cs + 1) * vref / (32. * sense_resistor * math.sqrt(2.))
def _calc_current(self, run_current, hold_current):
vsense = True
irun = self._calc_current_bits(run_current, True)
if irun == 31:
cur = self._calc_current_from_bits(irun, True)
if cur < run_current:
irun2 = self._calc_current_bits(run_current, False)
cur2 = self._calc_current_from_bits(irun2, False)
if abs(run_current - cur2) < abs(run_current - cur):
vsense = False
irun = irun2
ihold = self._calc_current_bits(min(hold_current, run_current), vsense)
return vsense, irun, ihold
def get_current(self):
irun = self.fields.get_field("irun")
ihold = self.fields.get_field("ihold")
vsense = self.fields.get_field("vsense")
run_current = self._calc_current_from_bits(irun, vsense)
hold_current = self._calc_current_from_bits(ihold, vsense)
return run_current, hold_current, self.req_hold_current, MAX_CURRENT
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
vsense, irun, ihold = self._calc_current(run_current, hold_current)
if vsense != self.fields.get_field("vsense"):
val = self.fields.set_field("vsense", vsense)
self.mcu_tmc.set_register("CHOPCONF", val, print_time)
self.fields.set_field("ihold", ihold)
val = self.fields.set_field("irun", irun)
self.mcu_tmc.set_register("IHOLD_IRUN", val, print_time)
######################################################################
# TMC2130 SPI
######################################################################
class MCU_TMC_SPI_chain:
def __init__(self, config, chain_len=1):
self.printer = config.get_printer()
self.chain_len = chain_len
self.mutex = self.printer.get_reactor().mutex()
share = None
if chain_len > 1:
share = "tmc_spi_cs"
self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=4000000,
share_type=share)
self.taken_chain_positions = []
def _build_cmd(self, data, chain_pos):
return ([0x00] * ((self.chain_len - chain_pos) * 5) +
data + [0x00] * ((chain_pos - 1) * 5))
def reg_read(self, reg, chain_pos):
cmd = self._build_cmd([reg, 0x00, 0x00, 0x00, 0x00], chain_pos)
self.spi.spi_send(cmd)
if self.printer.get_start_args().get('debugoutput') is not None:
return 0
params = self.spi.spi_transfer(cmd)
pr = bytearray(params['response'])
pr = pr[(self.chain_len - chain_pos) * 5 :
(self.chain_len - chain_pos + 1) * 5]
return (pr[1] << 24) | (pr[2] << 16) | (pr[3] << 8) | pr[4]
def reg_write(self, reg, val, chain_pos, print_time=None):
minclock = 0
if print_time is not None:
minclock = self.spi.get_mcu().print_time_to_clock(print_time)
data = [(reg | 0x80) & 0xff, (val >> 24) & 0xff, (val >> 16) & 0xff,
(val >> 8) & 0xff, val & 0xff]
if self.printer.get_start_args().get('debugoutput') is not None:
self.spi.spi_send(self._build_cmd(data, chain_pos), minclock)
return val
write_cmd = self._build_cmd(data, chain_pos)
dummy_read = self._build_cmd([0x00, 0x00, 0x00, 0x00, 0x00], chain_pos)
params = self.spi.spi_transfer_with_preface(write_cmd, dummy_read,
minclock=minclock)
pr = bytearray(params['response'])
pr = pr[(self.chain_len - chain_pos) * 5 :
(self.chain_len - chain_pos + 1) * 5]
return (pr[1] << 24) | (pr[2] << 16) | (pr[3] << 8) | pr[4]
# Helper to setup an spi daisy chain bus from settings in a config section
def lookup_tmc_spi_chain(config):
chain_len = config.getint('chain_length', None, minval=2)
if chain_len is None:
# Simple, non daisy chained SPI connection
return MCU_TMC_SPI_chain(config, 1), 1
# Shared SPI bus - lookup existing MCU_TMC_SPI_chain
ppins = config.get_printer().lookup_object("pins")
cs_pin_params = ppins.lookup_pin(config.get('cs_pin'),
share_type="tmc_spi_cs")
tmc_spi = cs_pin_params.get('class')
if tmc_spi is None:
tmc_spi = cs_pin_params['class'] = MCU_TMC_SPI_chain(config, chain_len)
if chain_len != tmc_spi.chain_len:
raise config.error("TMC SPI chain must have same length")
chain_pos = config.getint('chain_position', minval=1, maxval=chain_len)
if chain_pos in tmc_spi.taken_chain_positions:
raise config.error("TMC SPI chain can not have duplicate position")
tmc_spi.taken_chain_positions.append(chain_pos)
return tmc_spi, chain_pos
# Helper code for working with TMC devices via SPI
class MCU_TMC_SPI:
def __init__(self, config, name_to_reg, fields):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.tmc_spi, self.chain_pos = lookup_tmc_spi_chain(config)
self.mutex = self.tmc_spi.mutex
self.name_to_reg = name_to_reg
self.fields = fields
def get_fields(self):
return self.fields
def get_register(self, reg_name):
reg = self.name_to_reg[reg_name]
with self.mutex:
read = self.tmc_spi.reg_read(reg, self.chain_pos)
return read
def set_register(self, reg_name, val, print_time=None):
reg = self.name_to_reg[reg_name]
with self.mutex:
for retry in range(5):
v = self.tmc_spi.reg_write(reg, val, self.chain_pos, print_time)
if v == val:
return
raise self.printer.command_error(
"Unable to write tmc spi '%s' register %s" % (self.name, reg_name))
######################################################################
# TMC2130 printer object
######################################################################
class TMC2130:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.mcu_tmc = MCU_TMC_SPI(config, Registers, self.fields)
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
current_helper = TMCCurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
# Allow other registers to be set from the config
set_config_field = self.fields.set_config_field
set_config_field(config, "toff", 4)
set_config_field(config, "hstrt", 0)
set_config_field(config, "hend", 7)
set_config_field(config, "tbl", 1)
set_config_field(config, "iholddelay", 8)
set_config_field(config, "tpowerdown", 0)
set_config_field(config, "pwm_ampl", 128)
set_config_field(config, "pwm_grad", 4)
set_config_field(config, "pwm_freq", 1)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "sgt", 0)
def load_config_prefix(config):
return TMC2130(config)
# TMC2130 configuration
#
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
from . import bus, tmc
TMC_FREQUENCY=13200000.
Registers = {
"GCONF": 0x00, "GSTAT": 0x01, "IOIN": 0x04, "IHOLD_IRUN": 0x10,
"TPOWERDOWN": 0x11, "TSTEP": 0x12, "TPWMTHRS": 0x13, "TCOOLTHRS": 0x14,
"THIGH": 0x15, "XDIRECT": 0x2d, "MSLUT0": 0x60, "MSLUT1": 0x61,
"MSLUT2": 0x62, "MSLUT3": 0x63, "MSLUT4": 0x64, "MSLUT5": 0x65,
"MSLUT6": 0x66, "MSLUT7": 0x67, "MSLUTSEL": 0x68, "MSLUTSTART": 0x69,
"MSCNT": 0x6a, "MSCURACT": 0x6b, "CHOPCONF": 0x6c, "COOLCONF": 0x6d,
"DCCTRL": 0x6e, "DRV_STATUS": 0x6f, "PWMCONF": 0x70, "PWM_SCALE": 0x71,
"ENCM_CTRL": 0x72, "LOST_STEPS": 0x73,
}
ReadRegisters = [
"GCONF", "GSTAT", "IOIN", "TSTEP", "XDIRECT", "MSCNT", "MSCURACT",
"CHOPCONF", "DRV_STATUS", "PWM_SCALE", "LOST_STEPS",
]
Fields = {}
Fields["GCONF"] = {
"i_scale_analog": 1<<0, "internal_rsense": 1<<1, "en_pwm_mode": 1<<2,
"enc_commutation": 1<<3, "shaft": 1<<4, "diag0_error": 1<<5,
"diag0_otpw": 1<<6, "diag0_stall": 1<<7, "diag1_stall": 1<<8,
"diag1_index": 1<<9, "diag1_onstate": 1<<10, "diag1_steps_skipped": 1<<11,
"diag0_int_pushpull": 1<<12, "diag1_pushpull": 1<<13,
"small_hysteresis": 1<<14, "stop_enable": 1<<15, "direct_mode": 1<<16,
"test_mode": 1<<17
}
Fields["GSTAT"] = { "reset": 1<<0, "drv_err": 1<<1, "uv_cp": 1<<2 }
Fields["IOIN"] = {
"step": 1<<0, "dir": 1<<1, "dcen_cfg4": 1<<2, "dcin_cfg5": 1<<3,
"drv_enn_cfg6": 1<<4, "dco": 1<<5, "version": 0xff << 24
}
Fields["IHOLD_IRUN"] = {
"ihold": 0x1f << 0, "irun": 0x1f << 8, "iholddelay": 0x0f << 16
}
Fields["TPOWERDOWN"] = { "tpowerdown": 0xff }
Fields["TSTEP"] = { "tstep": 0xfffff }
Fields["TPWMTHRS"] = { "tpwmthrs": 0xfffff }
Fields["TCOOLTHRS"] = { "tcoolthrs": 0xfffff }
Fields["THIGH"] = { "thigh": 0xfffff }
Fields["MSLUT0"] = { "mslut0": 0xffffffff }
Fields["MSLUT1"] = { "mslut1": 0xffffffff }
Fields["MSLUT2"] = { "mslut2": 0xffffffff }
Fields["MSLUT3"] = { "mslut3": 0xffffffff }
Fields["MSLUT4"] = { "mslut4": 0xffffffff }
Fields["MSLUT5"] = { "mslut5": 0xffffffff }
Fields["MSLUT6"] = { "mslut6": 0xffffffff }
Fields["MSLUT7"] = { "mslut7": 0xffffffff }
Fields["MSLUTSEL"] = {
"x3": 0xFF << 24,
"x2": 0xFF << 16,
"x1": 0xFF << 8,
"w3": 0x03 << 6,
"w2": 0x03 << 4,
"w1": 0x03 << 2,
"w0": 0x03 << 0,
}
Fields["MSLUTSTART"] = {
"start_sin": 0xFF << 0,
"start_sin90": 0xFF << 16,
}
Fields["MSCNT"] = { "mscnt": 0x3ff }
Fields["MSCURACT"] = { "cur_a": 0x1ff, "cur_b": 0x1ff << 16 }
Fields["CHOPCONF"] = {
"toff": 0x0f, "hstrt": 0x07 << 4, "hend": 0x0f << 7, "fd3": 1<<11,
"disfdcc": 1<<12, "rndtf": 1<<13, "chm": 1<<14, "tbl": 0x03 << 15,
"vsense": 1<<17, "vhighfs": 1<<18, "vhighchm": 1<<19, "sync": 0x0f << 20,
"mres": 0x0f << 24, "intpol": 1<<28, "dedge": 1<<29, "diss2g": 1<<30
}
Fields["COOLCONF"] = {
"semin": 0x0f, "seup": 0x03 << 5, "semax": 0x0f << 8, "sedn": 0x03 << 13,
"seimin": 1<<15, "sgt": 0x7f << 16, "sfilt": 1<<24
}
Fields["DRV_STATUS"] = {
"sg_result": 0x3ff, "fsactive": 1<<15, "cs_actual": 0x1f << 16,
"stallguard": 1<<24, "ot": 1<<25, "otpw": 1<<26, "s2ga": 1<<27,
"s2gb": 1<<28, "ola": 1<<29, "olb": 1<<30, "stst": 1<<31
}
Fields["PWMCONF"] = {
"pwm_ampl": 0xff, "pwm_grad": 0xff << 8, "pwm_freq": 0x03 << 16,
"pwm_autoscale": 1<<18, "pwm_symmetric": 1<<19, "freewheel": 0x03 << 20
}
Fields["PWM_SCALE"] = { "pwm_scale": 0xff }
Fields["LOST_STEPS"] = { "lost_steps": 0xfffff }
SignedFields = ["cur_a", "cur_b", "sgt"]
FieldFormatters = {
"i_scale_analog": (lambda v: "1(ExtVREF)" if v else ""),
"shaft": (lambda v: "1(Reverse)" if v else ""),
"reset": (lambda v: "1(Reset)" if v else ""),
"drv_err": (lambda v: "1(ErrorShutdown!)" if v else ""),
"uv_cp": (lambda v: "1(Undervoltage!)" if v else ""),
"version": (lambda v: "%#x" % v),
"mres": (lambda v: "%d(%dusteps)" % (v, 0x100 >> v)),
"otpw": (lambda v: "1(OvertempWarning!)" if v else ""),
"ot": (lambda v: "1(OvertempError!)" if v else ""),
"s2ga": (lambda v: "1(ShortToGND_A!)" if v else ""),
"s2gb": (lambda v: "1(ShortToGND_B!)" if v else ""),
"ola": (lambda v: "1(OpenLoad_A!)" if v else ""),
"olb": (lambda v: "1(OpenLoad_B!)" if v else ""),
"cs_actual": (lambda v: ("%d" % v) if v else "0(Reset?)"),
}
######################################################################
# TMC stepper current config helper
######################################################################
MAX_CURRENT = 2.000
class TMCCurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
run_current = config.getfloat('run_current',
above=0., maxval=MAX_CURRENT)
hold_current = config.getfloat('hold_current', MAX_CURRENT,
above=0., maxval=MAX_CURRENT)
self.req_hold_current = hold_current
self.sense_resistor = config.getfloat('sense_resistor', 0.110, above=0.)
vsense, irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("vsense", vsense)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
def _calc_current_bits(self, current, vsense):
sense_resistor = self.sense_resistor + 0.020
vref = 0.32
if vsense:
vref = 0.18
cs = int(32. * sense_resistor * current * math.sqrt(2.) / vref + .5) - 1
return max(0, min(31, cs))
def _calc_current_from_bits(self, cs, vsense):
sense_resistor = self.sense_resistor + 0.020
vref = 0.32
if vsense:
vref = 0.18
return (cs + 1) * vref / (32. * sense_resistor * math.sqrt(2.))
def _calc_current(self, run_current, hold_current):
vsense = True
irun = self._calc_current_bits(run_current, True)
if irun == 31:
cur = self._calc_current_from_bits(irun, True)
if cur < run_current:
irun2 = self._calc_current_bits(run_current, False)
cur2 = self._calc_current_from_bits(irun2, False)
if abs(run_current - cur2) < abs(run_current - cur):
vsense = False
irun = irun2
ihold = self._calc_current_bits(min(hold_current, run_current), vsense)
return vsense, irun, ihold
def get_current(self):
irun = self.fields.get_field("irun")
ihold = self.fields.get_field("ihold")
vsense = self.fields.get_field("vsense")
run_current = self._calc_current_from_bits(irun, vsense)
hold_current = self._calc_current_from_bits(ihold, vsense)
return run_current, hold_current, self.req_hold_current, MAX_CURRENT
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
vsense, irun, ihold = self._calc_current(run_current, hold_current)
if vsense != self.fields.get_field("vsense"):
val = self.fields.set_field("vsense", vsense)
self.mcu_tmc.set_register("CHOPCONF", val, print_time)
self.fields.set_field("ihold", ihold)
val = self.fields.set_field("irun", irun)
self.mcu_tmc.set_register("IHOLD_IRUN", val, print_time)
######################################################################
# TMC2130 SPI
######################################################################
class MCU_TMC_SPI_chain:
def __init__(self, config, chain_len=1):
self.printer = config.get_printer()
self.chain_len = chain_len
self.mutex = self.printer.get_reactor().mutex()
share = None
if chain_len > 1:
share = "tmc_spi_cs"
self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=4000000,
share_type=share)
self.taken_chain_positions = []
def _build_cmd(self, data, chain_pos):
return ([0x00] * ((self.chain_len - chain_pos) * 5) +
data + [0x00] * ((chain_pos - 1) * 5))
def reg_read(self, reg, chain_pos):
cmd = self._build_cmd([reg, 0x00, 0x00, 0x00, 0x00], chain_pos)
self.spi.spi_send(cmd)
if self.printer.get_start_args().get('debugoutput') is not None:
return 0
params = self.spi.spi_transfer(cmd)
pr = bytearray(params['response'])
pr = pr[(self.chain_len - chain_pos) * 5 :
(self.chain_len - chain_pos + 1) * 5]
return (pr[1] << 24) | (pr[2] << 16) | (pr[3] << 8) | pr[4]
def reg_write(self, reg, val, chain_pos, print_time=None):
minclock = 0
if print_time is not None:
minclock = self.spi.get_mcu().print_time_to_clock(print_time)
data = [(reg | 0x80) & 0xff, (val >> 24) & 0xff, (val >> 16) & 0xff,
(val >> 8) & 0xff, val & 0xff]
if self.printer.get_start_args().get('debugoutput') is not None:
self.spi.spi_send(self._build_cmd(data, chain_pos), minclock)
return val
write_cmd = self._build_cmd(data, chain_pos)
dummy_read = self._build_cmd([0x00, 0x00, 0x00, 0x00, 0x00], chain_pos)
params = self.spi.spi_transfer_with_preface(write_cmd, dummy_read,
minclock=minclock)
pr = bytearray(params['response'])
pr = pr[(self.chain_len - chain_pos) * 5 :
(self.chain_len - chain_pos + 1) * 5]
return (pr[1] << 24) | (pr[2] << 16) | (pr[3] << 8) | pr[4]
# Helper to setup an spi daisy chain bus from settings in a config section
def lookup_tmc_spi_chain(config):
chain_len = config.getint('chain_length', None, minval=2)
if chain_len is None:
# Simple, non daisy chained SPI connection
return MCU_TMC_SPI_chain(config, 1), 1
# Shared SPI bus - lookup existing MCU_TMC_SPI_chain
ppins = config.get_printer().lookup_object("pins")
cs_pin_params = ppins.lookup_pin(config.get('cs_pin'),
share_type="tmc_spi_cs")
tmc_spi = cs_pin_params.get('class')
if tmc_spi is None:
tmc_spi = cs_pin_params['class'] = MCU_TMC_SPI_chain(config, chain_len)
if chain_len != tmc_spi.chain_len:
raise config.error("TMC SPI chain must have same length")
chain_pos = config.getint('chain_position', minval=1, maxval=chain_len)
if chain_pos in tmc_spi.taken_chain_positions:
raise config.error("TMC SPI chain can not have duplicate position")
tmc_spi.taken_chain_positions.append(chain_pos)
return tmc_spi, chain_pos
# Helper code for working with TMC devices via SPI
class MCU_TMC_SPI:
def __init__(self, config, name_to_reg, fields, tmc_frequency):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.tmc_spi, self.chain_pos = lookup_tmc_spi_chain(config)
self.mutex = self.tmc_spi.mutex
self.name_to_reg = name_to_reg
self.fields = fields
self.tmc_frequency = tmc_frequency
def get_fields(self):
return self.fields
def get_register(self, reg_name):
reg = self.name_to_reg[reg_name]
with self.mutex:
read = self.tmc_spi.reg_read(reg, self.chain_pos)
return read
def set_register(self, reg_name, val, print_time=None):
reg = self.name_to_reg[reg_name]
with self.mutex:
for retry in range(5):
v = self.tmc_spi.reg_write(reg, val, self.chain_pos, print_time)
if v == val:
return
raise self.printer.command_error(
"Unable to write tmc spi '%s' register %s" % (self.name, reg_name))
def get_tmc_frequency(self):
return self.tmc_frequency
######################################################################
# TMC2130 printer object
######################################################################
class TMC2130:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.mcu_tmc = MCU_TMC_SPI(config, Registers, self.fields,
TMC_FREQUENCY)
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
current_helper = TMCCurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
tmc.TMCWaveTableHelper(config, self.mcu_tmc)
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
# Allow other registers to be set from the config
set_config_field = self.fields.set_config_field
# CHOPCONF
set_config_field(config, "toff", 4)
set_config_field(config, "hstrt", 0)
set_config_field(config, "hend", 7)
set_config_field(config, "tbl", 1)
# COOLCONF
set_config_field(config, "sgt", 0)
# IHOLDIRUN
set_config_field(config, "iholddelay", 8)
# PWMCONF
set_config_field(config, "pwm_ampl", 128)
set_config_field(config, "pwm_grad", 4)
set_config_field(config, "pwm_freq", 1)
set_config_field(config, "pwm_autoscale", True)
# TPOWERDOWN
set_config_field(config, "tpowerdown", 0)
def load_config_prefix(config):
return TMC2130(config)

Binary file not shown.

View File

@@ -1,223 +1,229 @@
# TMC2208 UART communication and configuration
#
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
from . import tmc, tmc_uart, tmc2130
TMC_FREQUENCY=12000000.
Registers = {
"GCONF": 0x00, "GSTAT": 0x01, "IFCNT": 0x02, "SLAVECONF": 0x03,
"OTP_PROG": 0x04, "OTP_READ": 0x05, "IOIN": 0x06, "FACTORY_CONF": 0x07,
"IHOLD_IRUN": 0x10, "TPOWERDOWN": 0x11, "TSTEP": 0x12, "TPWMTHRS": 0x13,
"VACTUAL": 0x22, "MSCNT": 0x6a, "MSCURACT": 0x6b, "CHOPCONF": 0x6c,
"DRV_STATUS": 0x6f, "PWMCONF": 0x70, "PWM_SCALE": 0x71, "PWM_AUTO": 0x72
}
ReadRegisters = [
"GCONF", "GSTAT", "IFCNT", "OTP_READ", "IOIN", "FACTORY_CONF", "TSTEP",
"MSCNT", "MSCURACT", "CHOPCONF", "DRV_STATUS",
"PWMCONF", "PWM_SCALE", "PWM_AUTO"
]
Fields = {}
Fields["GCONF"] = {
"i_scale_analog": 0x01,
"internal_rsense": 0x01 << 1,
"en_spreadcycle": 0x01 << 2,
"shaft": 0x01 << 3,
"index_otpw": 0x01 << 4,
"index_step": 0x01 << 5,
"pdn_disable": 0x01 << 6,
"mstep_reg_select": 0x01 << 7,
"multistep_filt": 0x01 << 8,
"test_mode": 0x01 << 9
}
Fields["GSTAT"] = {
"reset": 0x01,
"drv_err": 0x01 << 1,
"uv_cp": 0x01 << 2
}
Fields["IFCNT"] = {
"ifcnt": 0xff
}
Fields["SLAVECONF"] = {
"senddelay": 0x0f << 8
}
Fields["OTP_PROG"] = {
"otpbit": 0x07,
"otpbyte": 0x03 << 4,
"otpmagic": 0xff << 8
}
Fields["OTP_READ"] = {
"otp_fclktrim": 0x1f,
"otp_ottrim": 0x01 << 5,
"otp_internalrsense": 0x01 << 6,
"otp_tbl": 0x01 << 7,
"otp_pwm_grad": 0x0f << 8,
"otp_pwm_autograd": 0x01 << 12,
"otp_tpwmthrs": 0x07 << 13,
"otp_pwm_ofs": 0x01 << 16,
"otp_pwm_reg": 0x01 << 17,
"otp_pwm_freq": 0x01 << 18,
"otp_iholddelay": 0x03 << 19,
"otp_ihold": 0x03 << 21,
"otp_en_spreadcycle": 0x01 << 23
}
# IOIN mapping depends on the driver type (SEL_A field)
# TMC222x (SEL_A == 0)
Fields["IOIN@TMC222x"] = {
"pdn_uart": 0x01 << 1,
"spread": 0x01 << 2,
"dir": 0x01 << 3,
"enn": 0x01 << 4,
"step": 0x01 << 5,
"ms1": 0x01 << 6,
"ms2": 0x01 << 7,
"sel_a": 0x01 << 8,
"version": 0xff << 24
}
# TMC220x (SEL_A == 1)
Fields["IOIN@TMC220x"] = {
"enn": 0x01,
"ms1": 0x01 << 2,
"ms2": 0x01 << 3,
"diag": 0x01 << 4,
"pdn_uart": 0x01 << 6,
"step": 0x01 << 7,
"sel_a": 0x01 << 8,
"dir": 0x01 << 9,
"version": 0xff << 24,
}
Fields["FACTORY_CONF"] = {
"fclktrim": 0x1f,
"ottrim": 0x03 << 8
}
Fields["IHOLD_IRUN"] = {
"ihold": 0x1f,
"irun": 0x1f << 8,
"iholddelay": 0x0f << 16
}
Fields["TPOWERDOWN"] = {
"tpowerdown": 0xff
}
Fields["TSTEP"] = {
"tstep": 0xfffff
}
Fields["TPWMTHRS"] = {
"tpwmthrs": 0xfffff
}
Fields["VACTUAL"] = {
"vactual": 0xffffff
}
Fields["MSCNT"] = {
"mscnt": 0x3ff
}
Fields["MSCURACT"] = {
"cur_a": 0x1ff,
"cur_b": 0x1ff << 16
}
Fields["CHOPCONF"] = {
"toff": 0x0f,
"hstrt": 0x07 << 4,
"hend": 0x0f << 7,
"tbl": 0x03 << 15,
"vsense": 0x01 << 17,
"mres": 0x0f << 24,
"intpol": 0x01 << 28,
"dedge": 0x01 << 29,
"diss2g": 0x01 << 30,
"diss2vs": 0x01 << 31
}
Fields["DRV_STATUS"] = {
"otpw": 0x01,
"ot": 0x01 << 1,
"s2ga": 0x01 << 2,
"s2gb": 0x01 << 3,
"s2vsa": 0x01 << 4,
"s2vsb": 0x01 << 5,
"ola": 0x01 << 6,
"olb": 0x01 << 7,
"t120": 0x01 << 8,
"t143": 0x01 << 9,
"t150": 0x01 << 10,
"t157": 0x01 << 11,
"cs_actual": 0x1f << 16,
"stealth": 0x01 << 30,
"stst": 0x01 << 31
}
Fields["PWMCONF"] = {
"pwm_ofs": 0xff,
"pwm_grad": 0xff << 8,
"pwm_freq": 0x03 << 16,
"pwm_autoscale": 0x01 << 18,
"pwm_autograd": 0x01 << 19,
"freewheel": 0x03 << 20,
"pwm_reg": 0xf << 24,
"pwm_lim": 0xf << 28
}
Fields["PWM_SCALE"] = {
"pwm_scale_sum": 0xff,
"pwm_scale_auto": 0x1ff << 16
}
Fields["PWM_AUTO"] = {
"pwm_ofs_auto": 0xff,
"pwm_grad_auto": 0xff << 16
}
SignedFields = ["cur_a", "cur_b", "pwm_scale_auto"]
FieldFormatters = dict(tmc2130.FieldFormatters)
FieldFormatters.update({
"sel_a": (lambda v: "%d(%s)" % (v, ["TMC222x", "TMC220x"][v])),
"s2vsa": (lambda v: "1(LowSideShort_A!)" if v else ""),
"s2vsb": (lambda v: "1(LowSideShort_B!)" if v else ""),
})
######################################################################
# TMC2208 printer object
######################################################################
class TMC2208:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields)
self.fields.set_field("pdn_disable", True)
# Register commands
current_helper = tmc2130.TMCCurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters, self.read_translate)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
self.fields.set_field("mstep_reg_select", True)
self.fields.set_field("multistep_filt", True)
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
# Allow other registers to be set from the config
set_config_field = self.fields.set_config_field
set_config_field(config, "toff", 3)
set_config_field(config, "hstrt", 5)
set_config_field(config, "hend", 0)
set_config_field(config, "tbl", 2)
set_config_field(config, "iholddelay", 8)
set_config_field(config, "tpowerdown", 20)
set_config_field(config, "pwm_ofs", 36)
set_config_field(config, "pwm_grad", 14)
set_config_field(config, "pwm_freq", 1)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "pwm_autograd", True)
set_config_field(config, "pwm_reg", 8)
set_config_field(config, "pwm_lim", 12)
def read_translate(self, reg_name, val):
if reg_name == "IOIN":
drv_type = self.fields.get_field("sel_a", val)
reg_name = "IOIN@TMC220x" if drv_type else "IOIN@TMC222x"
return reg_name, val
def load_config_prefix(config):
return TMC2208(config)
# TMC2208 UART communication and configuration
#
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
from . import tmc, tmc_uart, tmc2130
TMC_FREQUENCY=12000000.
Registers = {
"GCONF": 0x00, "GSTAT": 0x01, "IFCNT": 0x02, "SLAVECONF": 0x03,
"OTP_PROG": 0x04, "OTP_READ": 0x05, "IOIN": 0x06, "FACTORY_CONF": 0x07,
"IHOLD_IRUN": 0x10, "TPOWERDOWN": 0x11, "TSTEP": 0x12, "TPWMTHRS": 0x13,
"VACTUAL": 0x22, "MSCNT": 0x6a, "MSCURACT": 0x6b, "CHOPCONF": 0x6c,
"DRV_STATUS": 0x6f, "PWMCONF": 0x70, "PWM_SCALE": 0x71, "PWM_AUTO": 0x72
}
ReadRegisters = [
"GCONF", "GSTAT", "IFCNT", "OTP_READ", "IOIN", "FACTORY_CONF", "TSTEP",
"MSCNT", "MSCURACT", "CHOPCONF", "DRV_STATUS",
"PWMCONF", "PWM_SCALE", "PWM_AUTO"
]
Fields = {}
Fields["GCONF"] = {
"i_scale_analog": 0x01,
"internal_rsense": 0x01 << 1,
"en_spreadcycle": 0x01 << 2,
"shaft": 0x01 << 3,
"index_otpw": 0x01 << 4,
"index_step": 0x01 << 5,
"pdn_disable": 0x01 << 6,
"mstep_reg_select": 0x01 << 7,
"multistep_filt": 0x01 << 8,
"test_mode": 0x01 << 9
}
Fields["GSTAT"] = {
"reset": 0x01,
"drv_err": 0x01 << 1,
"uv_cp": 0x01 << 2
}
Fields["IFCNT"] = {
"ifcnt": 0xff
}
Fields["SLAVECONF"] = {
"senddelay": 0x0f << 8
}
Fields["OTP_PROG"] = {
"otpbit": 0x07,
"otpbyte": 0x03 << 4,
"otpmagic": 0xff << 8
}
Fields["OTP_READ"] = {
"otp_fclktrim": 0x1f,
"otp_ottrim": 0x01 << 5,
"otp_internalrsense": 0x01 << 6,
"otp_tbl": 0x01 << 7,
"otp_pwm_grad": 0x0f << 8,
"otp_pwm_autograd": 0x01 << 12,
"otp_tpwmthrs": 0x07 << 13,
"otp_pwm_ofs": 0x01 << 16,
"otp_pwm_reg": 0x01 << 17,
"otp_pwm_freq": 0x01 << 18,
"otp_iholddelay": 0x03 << 19,
"otp_ihold": 0x03 << 21,
"otp_en_spreadcycle": 0x01 << 23
}
# IOIN mapping depends on the driver type (SEL_A field)
# TMC222x (SEL_A == 0)
Fields["IOIN@TMC222x"] = {
"pdn_uart": 0x01 << 1,
"spread": 0x01 << 2,
"dir": 0x01 << 3,
"enn": 0x01 << 4,
"step": 0x01 << 5,
"ms1": 0x01 << 6,
"ms2": 0x01 << 7,
"sel_a": 0x01 << 8,
"version": 0xff << 24
}
# TMC220x (SEL_A == 1)
Fields["IOIN@TMC220x"] = {
"enn": 0x01,
"ms1": 0x01 << 2,
"ms2": 0x01 << 3,
"diag": 0x01 << 4,
"pdn_uart": 0x01 << 6,
"step": 0x01 << 7,
"sel_a": 0x01 << 8,
"dir": 0x01 << 9,
"version": 0xff << 24,
}
Fields["FACTORY_CONF"] = {
"fclktrim": 0x1f,
"ottrim": 0x03 << 8
}
Fields["IHOLD_IRUN"] = {
"ihold": 0x1f,
"irun": 0x1f << 8,
"iholddelay": 0x0f << 16
}
Fields["TPOWERDOWN"] = {
"tpowerdown": 0xff
}
Fields["TSTEP"] = {
"tstep": 0xfffff
}
Fields["TPWMTHRS"] = {
"tpwmthrs": 0xfffff
}
Fields["VACTUAL"] = {
"vactual": 0xffffff
}
Fields["MSCNT"] = {
"mscnt": 0x3ff
}
Fields["MSCURACT"] = {
"cur_a": 0x1ff,
"cur_b": 0x1ff << 16
}
Fields["CHOPCONF"] = {
"toff": 0x0f,
"hstrt": 0x07 << 4,
"hend": 0x0f << 7,
"tbl": 0x03 << 15,
"vsense": 0x01 << 17,
"mres": 0x0f << 24,
"intpol": 0x01 << 28,
"dedge": 0x01 << 29,
"diss2g": 0x01 << 30,
"diss2vs": 0x01 << 31
}
Fields["DRV_STATUS"] = {
"otpw": 0x01,
"ot": 0x01 << 1,
"s2ga": 0x01 << 2,
"s2gb": 0x01 << 3,
"s2vsa": 0x01 << 4,
"s2vsb": 0x01 << 5,
"ola": 0x01 << 6,
"olb": 0x01 << 7,
"t120": 0x01 << 8,
"t143": 0x01 << 9,
"t150": 0x01 << 10,
"t157": 0x01 << 11,
"cs_actual": 0x1f << 16,
"stealth": 0x01 << 30,
"stst": 0x01 << 31
}
Fields["PWMCONF"] = {
"pwm_ofs": 0xff,
"pwm_grad": 0xff << 8,
"pwm_freq": 0x03 << 16,
"pwm_autoscale": 0x01 << 18,
"pwm_autograd": 0x01 << 19,
"freewheel": 0x03 << 20,
"pwm_reg": 0xf << 24,
"pwm_lim": 0xf << 28
}
Fields["PWM_SCALE"] = {
"pwm_scale_sum": 0xff,
"pwm_scale_auto": 0x1ff << 16
}
Fields["PWM_AUTO"] = {
"pwm_ofs_auto": 0xff,
"pwm_grad_auto": 0xff << 16
}
SignedFields = ["cur_a", "cur_b", "pwm_scale_auto"]
FieldFormatters = dict(tmc2130.FieldFormatters)
FieldFormatters.update({
"sel_a": (lambda v: "%d(%s)" % (v, ["TMC222x", "TMC220x"][v])),
"s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
"s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
})
######################################################################
# TMC2208 printer object
######################################################################
class TMC2208:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields, 0,
TMC_FREQUENCY)
self.fields.set_field("pdn_disable", True)
# Register commands
current_helper = tmc2130.TMCCurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters, self.read_translate)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
self.fields.set_field("mstep_reg_select", True)
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
# Allow other registers to be set from the config
set_config_field = self.fields.set_config_field
# GCONF
set_config_field(config, "multistep_filt", True)
# CHOPCONF
set_config_field(config, "toff", 3)
set_config_field(config, "hstrt", 5)
set_config_field(config, "hend", 0)
set_config_field(config, "tbl", 2)
# IHOLDIRUN
set_config_field(config, "iholddelay", 8)
# PWMCONF
set_config_field(config, "pwm_ofs", 36)
set_config_field(config, "pwm_grad", 14)
set_config_field(config, "pwm_freq", 1)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "pwm_autograd", True)
set_config_field(config, "pwm_reg", 8)
set_config_field(config, "pwm_lim", 12)
# TPOWERDOWN
set_config_field(config, "tpowerdown", 20)
def read_translate(self, reg_name, val):
if reg_name == "IOIN":
drv_type = self.fields.get_field("sel_a", val)
reg_name = "IOIN@TMC220x" if drv_type else "IOIN@TMC222x"
return reg_name, val
def load_config_prefix(config):
return TMC2208(config)

Binary file not shown.

View File

@@ -1,96 +1,102 @@
# TMC2209 configuration
#
# Copyright (C) 2019 Stephan Oelze <stephan.oelze@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import tmc2208, tmc2130, tmc, tmc_uart
TMC_FREQUENCY=12000000.
Registers = dict(tmc2208.Registers)
Registers.update({
"TCOOLTHRS": 0x14,
"COOLCONF": 0x42,
"SGTHRS": 0x40,
"SG_RESULT": 0x41
})
ReadRegisters = tmc2208.ReadRegisters + ["SG_RESULT"]
Fields = dict(tmc2208.Fields)
Fields["COOLCONF"] = {
"semin": 0x0F << 0,
"seup": 0x03 << 5,
"semax": 0x0F << 8,
"sedn": 0x03 << 13,
"seimin": 0x01 << 15
}
Fields["IOIN"] = {
"enn": 0x01 << 0,
"ms1": 0x01 << 2,
"ms2": 0x01 << 3,
"diag": 0x01 << 4,
"pdn_uart": 0x01 << 6,
"step": 0x01 << 7,
"spread_en": 0x01 << 8,
"dir": 0x01 << 9,
"version": 0xff << 24
}
Fields["SGTHRS"] = {
"sgthrs": 0xFF << 0
}
Fields["SG_RESULT"] = {
"sg_result": 0x3FF << 0
}
Fields["TCOOLTHRS"] = {
"tcoolthrs": 0xfffff
}
FieldFormatters = dict(tmc2208.FieldFormatters)
######################################################################
# TMC2209 printer object
######################################################################
class TMC2209:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, tmc2208.SignedFields,
FieldFormatters)
self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields, 3)
# Setup fields for UART
self.fields.set_field("pdn_disable", True)
self.fields.set_field("senddelay", 2) # Avoid tx errors on shared uart
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
current_helper = tmc2130.TMCCurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
self.fields.set_field("pdn_disable", True)
self.fields.set_field("mstep_reg_select", True)
self.fields.set_field("multistep_filt", True)
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
# Allow other registers to be set from the config
set_config_field = self.fields.set_config_field
set_config_field(config, "toff", 3)
set_config_field(config, "hstrt", 5)
set_config_field(config, "hend", 0)
set_config_field(config, "tbl", 2)
set_config_field(config, "iholddelay", 8)
set_config_field(config, "tpowerdown", 20)
set_config_field(config, "pwm_ofs", 36)
set_config_field(config, "pwm_grad", 14)
set_config_field(config, "pwm_freq", 1)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "pwm_autograd", True)
set_config_field(config, "pwm_reg", 8)
set_config_field(config, "pwm_lim", 12)
set_config_field(config, "sgthrs", 0)
def load_config_prefix(config):
return TMC2209(config)
# TMC2209 configuration
#
# Copyright (C) 2019 Stephan Oelze <stephan.oelze@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import tmc2208, tmc2130, tmc, tmc_uart
TMC_FREQUENCY=12000000.
Registers = dict(tmc2208.Registers)
Registers.update({
"TCOOLTHRS": 0x14,
"COOLCONF": 0x42,
"SGTHRS": 0x40,
"SG_RESULT": 0x41
})
ReadRegisters = tmc2208.ReadRegisters + ["SG_RESULT"]
Fields = dict(tmc2208.Fields)
Fields["COOLCONF"] = {
"semin": 0x0F << 0,
"seup": 0x03 << 5,
"semax": 0x0F << 8,
"sedn": 0x03 << 13,
"seimin": 0x01 << 15
}
Fields["IOIN"] = {
"enn": 0x01 << 0,
"ms1": 0x01 << 2,
"ms2": 0x01 << 3,
"diag": 0x01 << 4,
"pdn_uart": 0x01 << 6,
"step": 0x01 << 7,
"spread_en": 0x01 << 8,
"dir": 0x01 << 9,
"version": 0xff << 24
}
Fields["SGTHRS"] = {
"sgthrs": 0xFF << 0
}
Fields["SG_RESULT"] = {
"sg_result": 0x3FF << 0
}
Fields["TCOOLTHRS"] = {
"tcoolthrs": 0xfffff
}
FieldFormatters = dict(tmc2208.FieldFormatters)
######################################################################
# TMC2209 printer object
######################################################################
class TMC2209:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, tmc2208.SignedFields,
FieldFormatters)
self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields, 3,
TMC_FREQUENCY)
# Setup fields for UART
self.fields.set_field("pdn_disable", True)
self.fields.set_field("senddelay", 2) # Avoid tx errors on shared uart
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
current_helper = tmc2130.TMCCurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
self.fields.set_field("mstep_reg_select", True)
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
# Allow other registers to be set from the config
set_config_field = self.fields.set_config_field
# GCONF
set_config_field(config, "multistep_filt", True)
# CHOPCONF
set_config_field(config, "toff", 3)
set_config_field(config, "hstrt", 5)
set_config_field(config, "hend", 0)
set_config_field(config, "tbl", 2)
# IHOLDIRUN
set_config_field(config, "iholddelay", 8)
# PWMCONF
set_config_field(config, "pwm_ofs", 36)
set_config_field(config, "pwm_grad", 14)
set_config_field(config, "pwm_freq", 1)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "pwm_autograd", True)
set_config_field(config, "pwm_reg", 8)
set_config_field(config, "pwm_lim", 12)
# TPOWERDOWN
set_config_field(config, "tpowerdown", 20)
# SGTHRS
set_config_field(config, "sgthrs", 0)
def load_config_prefix(config):
return TMC2209(config)

Binary file not shown.

View File

@@ -1,453 +1,408 @@
# TMC2240 configuration
# <kenneth.lin.gd.cn@gmail.com>
import math, logging
from . import bus, tmc, tmc_uart, tmc2130
TMC_FREQUENCY=10000000.
Registers = {
"GCONF": 0x00,
"GSTAT": 0x01,
"IFCNT": 0x02,
"SLAVECONF": 0x03,
"IOIN": 0x04,
"DRV_CONF": 0x0A,
"GLOBALSCALER": 0x0B,
"IHOLD_IRUN": 0x10,
"TPOWERDOWN": 0x11,
"TSTEP": 0x12,
"TPWMTHRS": 0x13,
"TCOOLTHRS": 0x14,
"THIGH": 0x15,
"DIRECT_MODE": 0x2d,
"ENCMODE": 0x38,
"X_ENC": 0x39,
"ENC_CONST": 0x3a,
"ENC_STATUS": 0x3b,
"ENC_LATCH": 0x3c,
"ADC_VSUPPLY_AIN": 0x50,
"ADC_TEMP": 0x51,
"OTW_OV_VTH": 0x52,
"MSLUT_0": 0x60,
"MSLUT_1": 0x61,
"MSLUT_2": 0x62,
"MSLUT_3": 0x63,
"MSLUT_4": 0x64,
"MSLUT_5": 0x65,
"MSLUT_6": 0x66,
"MSLUT_7": 0x67,
"MSLUTSEL": 0x68,
"MSLUTSTART": 0x69,
"MSCNT": 0x6a,
"MSCURACT": 0x6b,
"CHOPCONF": 0x6c,
"COOLCONF": 0x6d,
"DRV_STATUS": 0x6f,
"PWMCONF": 0x70,
"PWM_SCALE": 0x71,
"PWM_AUTO": 0x72,
# "SG4_THRS": 0x74,
"SGTHRS": 0x74,
"SG4_RESULT": 0x75,
"SG4_IND": 0x76
}
ReadRegisters = [
"GCONF", "CHOPCONF", "GSTAT", "DRV_STATUS", "IOIN",
"MSCNT", "MSCURACT", "PWM_SCALE", "PWM_AUTO", "IFCNT"
]
Fields = {}
Fields["GCONF"] = {
"fast_standstill": 0x01 << 1,
"en_pwm_mode": 0x01 << 2,
"multistep_filt": 0x01 << 3,
"shaft": 0x01 << 4,
"diag0_error": 0x01 << 5,
"diag0_otpw": 0x01 << 6,
"diag0_stall": 0x01 << 7,
"diag1_stall": 0x01 << 8,
"diag1_index": 0x01 << 9,
"diag1_onstate": 0x01 << 10,
"diag0_pushpull": 0x01 << 12,
"diag1_pushpull": 0x01 << 13,
"small_hysteresis": 0x01 << 14,
"stop_enable": 0x01 << 15,
"direct_mode": 0x01 << 16,
}
Fields["GSTAT"] = {
"reset": 0x01 << 0,
"drv_err": 0x01 << 1,
"uv_cp": 0x01 << 2,
"register_reset": 0x01 << 3,
"vm_uvlo": 0x01 << 4
}
Fields["IOIN"] = {
"step": 0x01 << 0,
"dir": 0x01 << 1,
"encb": 0x01 << 2,
"enca": 0x01 << 3,
"drv_enn": 0x01 << 4,
"encn": 0x01 << 5,
"uart_en": 0x01 << 6,
"reserved": 0x01 << 7,
"comp_a": 0x01 << 8,
"comp_b": 0x01 << 9,
"comp_a1_a2": 0x01 << 10,
"comp_b1_b2": 0x01 << 11,
"version": 0xff << 24
}
Fields["GLOBALSCALER"] = {
"globalscaler": 0xff << 0
}
Fields["IHOLD_IRUN"] = {
"ihold": 0x1f << 0,
"irun": 0x1f << 8,
"iholddelay": 0x0f << 16,
"irundelay": 0x0f << 24
}
Fields["TPOWERDOWN"] = {
"tpowerdown": 0xff
}
Fields["TSTEP"] = {
"tstep": 0xfffff << 0
}
Fields["TPWMTHRS"] = {
"tpwmthrs": 0xfffff << 0
}
Fields["TCOOLTHRS"] = {
"tcoolthrs": 0xfffff << 0
}
Fields["THIGH"] = {
"thigh": 0xfffff << 0
}
Fields["DIRECT_MODE"] = {
"direct_coil_a": 0x1ff << 0,
"direct_coil_b": 0x1ff << 16
}
Fields["ENCMODE"] = {
"pol_a": 0x01 << 0,
"pol_b": 0x01 << 1,
"pol_n": 0x01 << 2,
"ignore_ab": 0x01 << 3,
"clr_cont": 0x01 << 4,
"clr_once": 0x01 << 5,
"pos_neg_edge": 0x03 << 6,
"clr_enc_x": 0x01 << 8,
"enc_sel_decimal": 0x01 << 10
}
Fields["X_ENC"] = {
"x_enc": 0xffffffff << 0
}
Fields["ENC_CONST"] = {
"enc_const": 0xffffffff << 0
}
Fields["ENC_STATUS"] = {
"n_event": 0x01 << 0
}
Fields["ENC_LATCH"] = {
"enc_latch": 0xffffffff << 0
}
Fields["MSLUT_0"] = {
"mslut_0": 0xffffffff << 0
}
Fields["MSLUT_1"] = {
"mslut_1": 0xffffffff << 0
}
Fields["MSLUT_2"] = {
"mslut_2": 0xffffffff << 0
}
Fields["MSLUT_3"] = {
"mslut_3": 0xffffffff << 0
}
Fields["MSLUT_4"] = {
"mslut_4": 0xffffffff << 0
}
Fields["MSLUT_5"] = {
"mslut_5": 0xffffffff << 0
}
Fields["MSLUT_6"] = {
"mslut_6": 0xffffffff << 0
}
Fields["MSLUT_7"] = {
"mslut_7": 0xffffffff << 0
}
Fields["MSLUTSEL"] = {
"w0": 0x03 << 0,
"w1": 0x03 << 2,
"w2": 0x03 << 4,
"w3": 0x03 << 6,
"x1": 0xff << 8,
"x2": 0xff << 16,
"x3": 0xff << 24
}
Fields["MSLUTSTART"] = {
"start_sin": 0xff << 0,
"start_sin90": 0xff << 16,
"offset_sin90": 0xff << 24
}
Fields["MSCNT"] = {
"mscnt": 0x3ff << 0
}
Fields["MSCURACT"] = {
"cur_b": 0x1ff << 0,
"cur_a": 0x1ff << 16
}
Fields["CHOPCONF"] = {
"toff": 0x0F << 0,
"hstrt": 0x07 << 4,
"hend": 0x0F << 7,
"fd3": 0x01 << 11,
"disfdcc": 0x01 << 12,
"chm": 0x01 << 14,
"tbl": 0x03 << 15,
"vhighfs": 0x01 << 18,
"vhighchm": 0x01 << 19,
"tpfd": 0x0F << 20, # midrange resonances
"mres": 0x0F << 24,
"intpol": 0x01 << 28,
"dedge": 0x01 << 29,
"diss2g": 0x01 << 30,
"diss2vs": 0x01 << 31
}
Fields["COOLCONF"] = {
"semin": 0x0f << 0,
"seup": 0x03 << 5,
"semax": 0x0f << 8,
"sedn": 0x03 << 13,
"seimin": 0x01 << 15,
"sgt": 0x7f << 16,
"sfilt": 0x01 << 24
}
Fields["DRV_STATUS"] = {
"sg_result": 0x3FF << 0,
"s2vsa": 0x01 << 12,
"s2vsb": 0x01 << 13,
"stealth": 0x01 << 14,
"fsactive": 0x01 << 15,
"csactual": 0xFF << 16,
"stallguard": 0x01 << 24,
"ot": 0x01 << 25,
"otpw": 0x01 << 26,
"s2ga": 0x01 << 27,
"s2gb": 0x01 << 28,
"ola": 0x01 << 29,
"olb": 0x01 << 30,
"stst": 0x01 << 31
}
Fields["PWMCONF"] = {
"pwm_ofs": 0xFF << 0,
"pwm_grad": 0xFF << 8,
"pwm_freq": 0x03 << 16,
"pwm_autoscale": 0x01 << 18,
"pwm_autograd": 0x01 << 19,
"freewheel": 0x03 << 20,
"pwm_meas_sd_enable": 0x01 << 22,
"pwm_dis_reg_stst": 0x01 << 23,
"pwm_reg": 0x0F << 24,
"pwm_lim": 0x0F << 28
}
Fields["PWM_SCALE"] = {
"pwm_scale_sum": 0xff << 0,
"pwm_scale_auto": 0x1ff << 16
}
Fields["IFCNT"] = {
"ifcnt": 0xff
}
'''
Fields["SG4_THRS"] = {
"sg4_thrs": 0xff,
"sg4_filt_en": 0x01 << 8,
"sg4_angle_offset": 0x01 << 9
}
'''
Fields["SGTHRS"] = {
"sgthrs": 0xff,
"sg4_filt_en": 0x01 << 8,
"sg4_angle_offset": 0x01 << 9
}
Fields["SG4_RESULT"] = {
"sg4_result": 0x3ff << 9
}
Fields["DRV_CONF"] = {
"current_range": 0x03 << 0,
"slope_control": 0x03 << 4,
}
Fields["OTW_OV_VTH"] = {
"overtempperwarning_vth": 0x1ff << 16
}
SignedFields = ["cur_a", "cur_b", "sgt",
"pwm_scale_auto"]
FieldFormatters = dict(tmc2130.FieldFormatters)
######################################################################
# TMC stepper current config helper
######################################################################
MAX_CURRENT = 2.000
class TMC2240CurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
run_current = config.getfloat('run_current',
above=0., maxval=MAX_CURRENT)
hold_current = config.getfloat('hold_current', MAX_CURRENT,
above=0., maxval=MAX_CURRENT)
self.req_hold_current = hold_current
self._set_globalscaler(run_current)
irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
# self.fields.set_field("irun", 31)
def _set_globalscaler(self, current):
globalscaler = int((current * 256. * math.sqrt(2.)
/ 2.0 + .5))
globalscaler = max(32, globalscaler)
if globalscaler >= 256:
globalscaler = 0
self.fields.set_field("globalscaler", globalscaler)
# self.fields.set_field("globalscaler", 0)
def _calc_current_bits(self, current):
globalscaler = self.fields.get_field("globalscaler")
if not globalscaler:
globalscaler = 256
cs = int((current * 256. * 32. * math.sqrt(2.)) / (3. * globalscaler) -1. + .5)
return max(0, min(31, cs))
def _calc_current(self, run_current, hold_current):
irun = self._calc_current_bits(run_current)
ihold = self._calc_current_bits(min(hold_current, run_current))
return irun, ihold
def _calc_current_from_field(self, field_name):
globalscaler = self.fields.get_field("globalscaler")
if not globalscaler:
globalscaler = 256
bits = self.fields.get_field(field_name)
return (globalscaler * (bits + 1) * 0.325
/ (256. * 32. * math.sqrt(2.)))
def get_current(self):
run_current = self._calc_current_from_field("irun")
hold_current = self._calc_current_from_field("ihold")
return run_current, hold_current, self.req_hold_current, MAX_CURRENT
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("ihold", ihold)
val = self.fields.set_field("irun", irun)
# self.printer.command_error("val = %d, print_time = ", val, print_time)
self.mcu_tmc.set_register("IHOLD_IRUN", val, print_time)
######################################################################
# TMC2240 printer object
######################################################################
class TMC2240:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.mcu_tmc = tmc2130.MCU_TMC_SPI(config, Registers, self.fields)
# self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields, 4)
# Setup fields for UART
# self.fields.set_field("uart_en", True)
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
current_helper = TMC2240CurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
# tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
tmc.TMC2240StealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
set_config_field = self.fields.set_config_field
# set_config_field(config, "en_pwm_mode", 1)
set_config_field(config, "reset", 1)
set_config_field(config, "drv_err", 1)
set_config_field(config, "uv_cp", 1)
set_config_field(config, "register_reset", 1)
set_config_field(config, "vm_uvlo", 1)
# set_config_field(config, "tpwmthrs", 100)
# set_config_field(config, "intpol", 0)
# set_config_field(config, "dedge", 0)
# set_config_field(config, "disfdcc", 1)
set_config_field(config, "toff", 3)
set_config_field(config, "hstrt", 5)
set_config_field(config, "hend", 2)
set_config_field(config, "fd3", 0)
set_config_field(config, "disfdcc", 0)
set_config_field(config, "chm", 0)
set_config_field(config, "tbl", 2)
set_config_field(config, "vhighfs", 0)
set_config_field(config, "vhighchm", 0)
set_config_field(config, "tpfd", 4)
set_config_field(config, "diss2g", 0)
set_config_field(config, "diss2vs", 0)
set_config_field(config, "semin", 0)
set_config_field(config, "seup", 0)
set_config_field(config, "semax", 0)
set_config_field(config, "sedn", 0)
set_config_field(config, "seimin", 0)
set_config_field(config, "sgt", 0)
set_config_field(config, "sfilt", 0)
set_config_field(config, "iholddelay", 6)
set_config_field(config, "pwm_ofs", 30)
set_config_field(config, "pwm_grad", 0)
set_config_field(config, "pwm_freq", 0)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "pwm_autograd", True)
set_config_field(config, "freewheel", 0)
set_config_field(config, "pwm_reg", 4)
set_config_field(config, "pwm_lim", 12)
set_config_field(config, "tpowerdown", 10)
set_config_field(config, "current_range", 2)
set_config_field(config, "overtempperwarning_vth", 0x1ff)
def load_config_prefix(config):
return TMC2240(config)
# TMC2240 configuration
#
# Copyright (C) 2018-2023 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2023 Alex Voinea <voinea.dragos.alexandru@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
from . import bus, tmc, tmc2130, tmc_uart
TMC_FREQUENCY=12500000.
Registers = {
"GCONF": 0x00,
"GSTAT": 0x01,
"IFCNT": 0x02,
"NODECONF": 0x03,
"IOIN": 0x04,
"DRV_CONF": 0x0A,
"GLOBALSCALER": 0x0B,
"IHOLD_IRUN": 0x10,
"TPOWERDOWN": 0x11,
"TSTEP": 0x12,
"TPWMTHRS": 0x13,
"TCOOLTHRS": 0x14,
"THIGH": 0x15,
"DIRECT_MODE": 0x2D,
"ENCMODE": 0x38,
"X_ENC": 0x39,
"ENC_CONST": 0x3A,
"ENC_STATUS": 0x3B,
"ENC_LATCH": 0x3C,
"ADC_VSUPPLY_AIN": 0x50,
"ADC_TEMP": 0x51,
"OTW_OV_VTH": 0x52,
"MSLUT0": 0x60,
"MSLUT1": 0x61,
"MSLUT2": 0x62,
"MSLUT3": 0x63,
"MSLUT4": 0x64,
"MSLUT5": 0x65,
"MSLUT6": 0x66,
"MSLUT7": 0x67,
"MSLUTSEL": 0x68,
"MSLUTSTART": 0x69,
"MSCNT": 0x6A,
"MSCURACT": 0x6B,
"CHOPCONF": 0x6C,
"COOLCONF": 0x6D,
"DRV_STATUS": 0x6F,
"PWMCONF": 0x70,
"PWM_SCALE": 0x71,
"PWM_AUTO": 0x72,
"SG4_THRS": 0x74,
"SG4_RESULT": 0x75,
"SG4_IND": 0x76,
}
ReadRegisters = [
"GCONF", "GSTAT", "IOIN", "DRV_CONF", "GLOBALSCALER", "IHOLD_IRUN",
"TPOWERDOWN", "TSTEP", "TPWMTHRS", "TCOOLTHRS", "THIGH", "ADC_VSUPPLY_AIN",
"ADC_TEMP", "MSCNT", "MSCURACT", "CHOPCONF", "COOLCONF", "DRV_STATUS",
"PWMCONF", "PWM_SCALE", "PWM_AUTO", "SG4_THRS", "SG4_RESULT", "SG4_IND"
]
Fields = {}
Fields["COOLCONF"] = {
"semin": 0x0F << 0,
"seup": 0x03 << 5,
"semax": 0x0F << 8,
"sedn": 0x03 << 13,
"seimin": 0x01 << 15,
"sgt": 0x7F << 16,
"sfilt": 0x01 << 24
}
Fields["CHOPCONF"] = {
"toff": 0x0F << 0,
"hstrt": 0x07 << 4,
"hend": 0x0F << 7,
"fd3": 0x01 << 11,
"disfdcc": 0x01 << 12,
"chm": 0x01 << 14,
"tbl": 0x03 << 15,
"vhighfs": 0x01 << 18,
"vhighchm": 0x01 << 19,
"tpfd": 0x0F << 20, # midrange resonances
"mres": 0x0F << 24,
"intpol": 0x01 << 28,
"dedge": 0x01 << 29,
"diss2g": 0x01 << 30,
"diss2vs": 0x01 << 31
}
Fields["DRV_STATUS"] = {
"sg_result": 0x3FF << 0,
"s2vsa": 0x01 << 12,
"s2vsb": 0x01 << 13,
"stealth": 0x01 << 14,
"fsactive": 0x01 << 15,
"csactual": 0x1F << 16,
"stallguard": 0x01 << 24,
"ot": 0x01 << 25,
"otpw": 0x01 << 26,
"s2ga": 0x01 << 27,
"s2gb": 0x01 << 28,
"ola": 0x01 << 29,
"olb": 0x01 << 30,
"stst": 0x01 << 31
}
Fields["GCONF"] = {
"faststandstill": 0x01 << 1,
"en_pwm_mode": 0x01 << 2,
"multistep_filt": 0x01 << 3,
"shaft": 0x01 << 4,
"diag0_error": 0x01 << 5,
"diag0_otpw": 0x01 << 6,
"diag0_stall": 0x01 << 7,
"diag1_stall": 0x01 << 8,
"diag1_index": 0x01 << 9,
"diag1_onstate": 0x01 << 10,
"diag0_pushpull": 0x01 << 12,
"diag1_pushpull": 0x01 << 13,
"small_hysteresis": 0x01 << 14,
"stop_enable": 0x01 << 15,
"direct_mode": 0x01 << 16
}
Fields["GSTAT"] = {
"reset": 0x01 << 0,
"drv_err": 0x01 << 1,
"uv_cp": 0x01 << 2,
"register_reset": 0x01 << 3,
"vm_uvlo": 0x01 << 4
}
Fields["GLOBALSCALER"] = {
"globalscaler": 0xFF << 0
}
Fields["IHOLD_IRUN"] = {
"ihold": 0x1F << 0,
"irun": 0x1F << 8,
"iholddelay": 0x0F << 16,
"irundelay": 0x0F << 24
}
Fields["IOIN"] = {
"step": 0x01 << 0,
"dir": 0x01 << 1,
"encb": 0x01 << 2,
"enca": 0x01 << 3,
"drv_enn": 0x01 << 4,
"encn": 0x01 << 5,
"uart_en": 0x01 << 6,
"comp_a": 0x01 << 8,
"comp_b": 0x01 << 9,
"comp_a1_a2": 0x01 << 10,
"comp_b1_b2": 0x01 << 11,
"output": 0x01 << 12,
"ext_res_det": 0x01 << 13,
"ext_clk": 0x01 << 14,
"adc_err": 0x01 << 15,
"silicon_rv": 0x07 << 16,
"version": 0xFF << 24
}
Fields["MSLUT0"] = { "mslut0": 0xffffffff }
Fields["MSLUT1"] = { "mslut1": 0xffffffff }
Fields["MSLUT2"] = { "mslut2": 0xffffffff }
Fields["MSLUT3"] = { "mslut3": 0xffffffff }
Fields["MSLUT4"] = { "mslut4": 0xffffffff }
Fields["MSLUT5"] = { "mslut5": 0xffffffff }
Fields["MSLUT6"] = { "mslut6": 0xffffffff }
Fields["MSLUT7"] = { "mslut7": 0xffffffff }
Fields["MSLUTSEL"] = {
"x3": 0xFF << 24,
"x2": 0xFF << 16,
"x1": 0xFF << 8,
"w3": 0x03 << 6,
"w2": 0x03 << 4,
"w1": 0x03 << 2,
"w0": 0x03 << 0,
}
Fields["MSLUTSTART"] = {
"start_sin": 0xFF << 0,
"start_sin90": 0xFF << 16,
"offset_sin90": 0xFF << 24,
}
Fields["MSCNT"] = {
"mscnt": 0x3ff << 0
}
Fields["MSCURACT"] = {
"cur_a": 0x1ff << 0,
"cur_b": 0x1ff << 16
}
Fields["PWM_AUTO"] = {
"pwm_ofs_auto": 0xff << 0,
"pwm_grad_auto": 0xff << 16
}
Fields["PWMCONF"] = {
"pwm_ofs": 0xFF << 0,
"pwm_grad": 0xFF << 8,
"pwm_freq": 0x03 << 16,
"pwm_autoscale": 0x01 << 18,
"pwm_autograd": 0x01 << 19,
"freewheel": 0x03 << 20,
"pwm_meas_sd_enable": 0x01 << 22,
"pwm_dis_reg_stst": 0x01 << 23,
"pwm_reg": 0x0F << 24,
"pwm_lim": 0x0F << 28
}
Fields["PWM_SCALE"] = {
"pwm_scale_sum": 0x3ff << 0,
"pwm_scale_auto": 0x1ff << 16
}
Fields["TPOWERDOWN"] = {
"tpowerdown": 0xff << 0
}
Fields["TPWMTHRS"] = {
"tpwmthrs": 0xfffff << 0
}
Fields["TCOOLTHRS"] = {
"tcoolthrs": 0xfffff << 0
}
Fields["TSTEP"] = {
"tstep": 0xfffff << 0
}
Fields["THIGH"] = {
"thigh": 0xfffff << 0
}
Fields["DRV_CONF"] = {
"current_range": 0x03 << 0,
"slope_control": 0x03 << 4
}
Fields["ADC_VSUPPLY_AIN"] = {
"adc_vsupply": 0x1fff << 0,
"adc_ain": 0x1fff << 16
}
Fields["ADC_TEMP"] = {
"adc_temp": 0x1fff << 0
}
Fields["OTW_OV_VTH"] = {
"overvoltage_vth": 0x1fff << 0,
"overtempprewarning_vth": 0x1fff << 16
}
Fields["SG4_THRS"] = {
"sg4_thrs": 0xFF << 0,
"sg4_filt_en": 0x01 << 8,
"sg4_angle_offset": 0x01 << 9
}
Fields["SG4_RESULT"] = {
"sg4_result": 0x3FF << 0
}
Fields["SG4_IND"] = {
"sg4_ind_0": 0xFF << 0,
"sg4_ind_1": 0xFF << 8,
"sg4_ind_2": 0xFF << 16,
"sg4_ind_3": 0xFF << 24
}
SignedFields = ["cur_a", "cur_b", "sgt", "pwm_scale_auto", "offset_sin90"]
FieldFormatters = dict(tmc2130.FieldFormatters)
FieldFormatters.update({
"s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
"s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
"adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))),
})
######################################################################
# TMC stepper current config helper
######################################################################
class TMC2240CurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
self.Rref = config.getfloat('rref', 12000.,
minval=12000., maxval=60000.)
max_cur = self._get_ifs_rms(3)
run_current = config.getfloat('run_current', above=0., maxval=max_cur)
hold_current = config.getfloat('hold_current', max_cur,
above=0., maxval=max_cur)
self.req_hold_current = hold_current
current_range = self._calc_current_range(run_current)
self.fields.set_field("current_range", current_range)
gscaler, irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("globalscaler", gscaler)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
def _get_ifs_rms(self, current_range=None):
if current_range is None:
current_range = self.fields.get_field("current_range")
KIFS = [11750., 24000., 36000., 36000.]
return (KIFS[current_range] / self.Rref) / math.sqrt(2.)
def _calc_current_range(self, current):
for current_range in range(4):
if current <= self._get_ifs_rms(current_range):
break
return current_range
def _calc_globalscaler(self, current):
ifs_rms = self._get_ifs_rms()
globalscaler = int(((current * 256.) / ifs_rms) + .5)
globalscaler = max(32, globalscaler)
if globalscaler >= 256:
globalscaler = 0
return globalscaler
def _calc_current_bits(self, current, globalscaler):
ifs_rms = self._get_ifs_rms()
if not globalscaler:
globalscaler = 256
cs = int((current * 256. * 32.) / (globalscaler * ifs_rms) - 1. + .5)
return max(0, min(31, cs))
def _calc_current(self, run_current, hold_current):
gscaler = self._calc_globalscaler(run_current)
irun = self._calc_current_bits(run_current, gscaler)
ihold = self._calc_current_bits(min(hold_current, run_current), gscaler)
return gscaler, irun, ihold
def _calc_current_from_field(self, field_name):
ifs_rms = self._get_ifs_rms()
globalscaler = self.fields.get_field("globalscaler")
if not globalscaler:
globalscaler = 256
bits = self.fields.get_field(field_name)
return globalscaler * (bits + 1) * ifs_rms / (256. * 32.)
def get_current(self):
ifs_rms = self._get_ifs_rms()
run_current = self._calc_current_from_field("irun")
hold_current = self._calc_current_from_field("ihold")
return (run_current, hold_current, self.req_hold_current, ifs_rms)
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
gscaler, irun, ihold = self._calc_current(run_current, hold_current)
val = self.fields.set_field("globalscaler", gscaler)
self.mcu_tmc.set_register("GLOBALSCALER", val, print_time)
self.fields.set_field("ihold", ihold)
val = self.fields.set_field("irun", irun)
self.mcu_tmc.set_register("IHOLD_IRUN", val, print_time)
######################################################################
# TMC2240 printer object
######################################################################
class TMC2240:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
if config.get("uart_pin", None) is not None:
# use UART for communication
self.mcu_tmc = tmc_uart.MCU_TMC_uart(config, Registers, self.fields,
3, TMC_FREQUENCY)
else:
# Use SPI bus for communication
self.mcu_tmc = tmc2130.MCU_TMC_SPI(config, Registers, self.fields,
TMC_FREQUENCY)
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
current_helper = TMC2240CurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
tmc.TMCWaveTableHelper(config, self.mcu_tmc)
self.fields.set_config_field(config, "offset_sin90", 0)
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
set_config_field = self.fields.set_config_field
# GCONF
set_config_field(config, "multistep_filt", True)
# CHOPCONF
set_config_field(config, "toff", 3)
set_config_field(config, "hstrt", 5)
set_config_field(config, "hend", 2)
set_config_field(config, "fd3", 0)
set_config_field(config, "disfdcc", 0)
set_config_field(config, "chm", 0)
set_config_field(config, "tbl", 2)
set_config_field(config, "vhighfs", 0)
set_config_field(config, "vhighchm", 0)
set_config_field(config, "tpfd", 4)
set_config_field(config, "diss2g", 0)
set_config_field(config, "diss2vs", 0)
# COOLCONF
set_config_field(config, "semin", 0)
set_config_field(config, "seup", 0)
set_config_field(config, "semax", 0)
set_config_field(config, "sedn", 0)
set_config_field(config, "seimin", 0)
set_config_field(config, "sgt", 0)
set_config_field(config, "sfilt", 0)
# IHOLDIRUN
set_config_field(config, "iholddelay", 6)
set_config_field(config, "irundelay", 4)
# PWMCONF
set_config_field(config, "pwm_ofs", 29)
set_config_field(config, "pwm_grad", 0)
set_config_field(config, "pwm_freq", 0)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "pwm_autograd", True)
set_config_field(config, "freewheel", 0)
set_config_field(config, "pwm_reg", 4)
set_config_field(config, "pwm_lim", 12)
# TPOWERDOWN
set_config_field(config, "tpowerdown", 10)
# SG4_THRS
set_config_field(config, "sg4_angle_offset", 1)
def load_config_prefix(config):
return TMC2240(config)

View File

@@ -1,274 +1,276 @@
# TMC2660 configuration
#
# Copyright (C) 2018-2019 Florian Heilmann <Florian.Heilmann@gmx.net>
# Copyright (C) 2019-2021 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
from . import bus, tmc, tmc2130
Registers = {
"DRVCONF": 0xE, "SGCSCONF": 0xC, "SMARTEN": 0xA,
"CHOPCONF": 0x8, "DRVCTRL": 0x0
}
ReadRegisters = [ "READRSP@RDSEL0", "READRSP@RDSEL1", "READRSP@RDSEL2" ]
Fields = {}
Fields["DRVCTRL"] = {
"mres": 0x0f,
"dedge": 0x01 << 8,
"intpol": 0x01 << 9,
}
Fields["CHOPCONF"] = {
"toff": 0x0f,
"hstrt": 0x7 << 4,
"hend": 0x0f << 7,
"hdec": 0x03 << 11,
"rndtf": 0x01 << 13,
"chm": 0x01 << 14,
"tbl": 0x03 << 15
}
Fields["SMARTEN"] = {
"semin" : 0x0f,
"seup": 0x03 << 5,
"semax": 0x0f << 8,
"sedn": 0x03 << 13,
"seimin": 0x01 << 15
}
Fields["SGCSCONF"] = {
"cs": 0x1f,
"sgt": 0x7F << 8,
"sfilt": 0x01 << 16
}
Fields["DRVCONF"] = {
"rdsel": 0x03 << 4,
"vsense": 0x01 << 6,
"sdoff": 0x01 << 7,
"ts2g": 0x03 << 8,
"diss2g": 0x01 << 10,
"slpl": 0x03 << 12,
"slph": 0x03 << 14,
"tst": 0x01 << 16
}
Fields["READRSP@RDSEL0"] = {
"stallguard": 0x01 << 4,
"ot": 0x01 << 5,
"otpw": 0x01 << 6,
"s2ga": 0x01 << 7,
"s2gb": 0x01 << 8,
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
"mstep": 0x3ff << 14
}
Fields["READRSP@RDSEL1"] = {
"stallguard": 0x01 << 4,
"ot": 0x01 << 5,
"otpw": 0x01 << 6,
"s2ga": 0x01 << 7,
"s2gb": 0x01 << 8,
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
"sg_result": 0x3ff << 14
}
Fields["READRSP@RDSEL2"] = {
"stallguard": 0x01 << 4,
"ot": 0x01 << 5,
"otpw": 0x01 << 6,
"s2ga": 0x01 << 7,
"s2gb": 0x01 << 8,
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
"se": 0x1f << 14,
"sg_result@rdsel2": 0x1f << 19
}
SignedFields = ["sgt"]
FieldFormatters = dict(tmc2130.FieldFormatters)
FieldFormatters.update({
"chm": (lambda v: "1(constant toff)" if v else "0(spreadCycle)"),
"vsense": (lambda v: "1(165mV)" if v else "0(305mV)"),
"sdoff": (lambda v: "1(Step/Dir disabled!)" if v else ""),
"diss2g": (lambda v: "1(Short to GND disabled!)" if v else ""),
"se": (lambda v: ("%d" % v) if v else "0(Reset?)"),
})
######################################################################
# TMC stepper current config helper
######################################################################
MAX_CURRENT = 2.400
class TMC2660CurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
self.current = config.getfloat('run_current', minval=0.1,
maxval=MAX_CURRENT)
self.sense_resistor = config.getfloat('sense_resistor')
vsense, cs = self._calc_current(self.current)
self.fields.set_field("cs", cs)
self.fields.set_field("vsense", vsense)
# Register ready/printing handlers
self.idle_current_percentage = config.getint(
'idle_current_percent', default=100, minval=0, maxval=100)
if self.idle_current_percentage < 100:
self.printer.register_event_handler("idle_timeout:printing",
self._handle_printing)
self.printer.register_event_handler("idle_timeout:ready",
self._handle_ready)
def _calc_current_bits(self, current, vsense):
vref = 0.165 if vsense else 0.310
sr = self.sense_resistor
cs = int(32. * sr * current * math.sqrt(2.) / vref + .5) - 1
return max(0, min(31, cs))
def _calc_current_from_bits(self, cs, vsense):
vref = 0.165 if vsense else 0.310
return (cs + 1) * vref / (32. * self.sense_resistor * math.sqrt(2.))
def _calc_current(self, run_current):
vsense = True
irun = self._calc_current_bits(run_current, True)
if irun == 31:
cur = self._calc_current_from_bits(irun, True)
if cur < run_current:
irun2 = self._calc_current_bits(run_current, False)
cur2 = self._calc_current_from_bits(irun2, False)
if abs(run_current - cur2) < abs(run_current - cur):
vsense = False
irun = irun2
return vsense, irun
def _handle_printing(self, print_time):
print_time -= 0.100 # Schedule slightly before deadline
self.printer.get_reactor().register_callback(
(lambda ev: self._update_current(self.current, print_time)))
def _handle_ready(self, print_time):
current = self.current * float(self.idle_current_percentage) / 100.
self.printer.get_reactor().register_callback(
(lambda ev: self._update_current(current, print_time)))
def _update_current(self, current, print_time):
vsense, cs = self._calc_current(current)
val = self.fields.set_field("cs", cs)
self.mcu_tmc.set_register("SGCSCONF", val, print_time)
# Only update VSENSE if we need to
if vsense != self.fields.get_field("vsense"):
val = self.fields.set_field("vsense", vsense)
self.mcu_tmc.set_register("DRVCONF", val, print_time)
def get_current(self):
return self.current, None, None, MAX_CURRENT
def set_current(self, run_current, hold_current, print_time):
self.current = run_current
self._update_current(run_current, print_time)
######################################################################
# TMC2660 SPI
######################################################################
# Helper code for working with TMC2660 devices via SPI
class MCU_TMC2660_SPI:
def __init__(self, config, name_to_reg, fields):
self.printer = config.get_printer()
self.mutex = self.printer.get_reactor().mutex()
self.spi = bus.MCU_SPI_from_config(config, 0, default_speed=4000000)
self.name_to_reg = name_to_reg
self.fields = fields
def get_fields(self):
return self.fields
def get_register(self, reg_name):
new_rdsel = ReadRegisters.index(reg_name)
reg = self.name_to_reg["DRVCONF"]
if self.printer.get_start_args().get('debugoutput') is not None:
return 0
with self.mutex:
old_rdsel = self.fields.get_field("rdsel")
val = self.fields.set_field("rdsel", new_rdsel)
msg = [((val >> 16) | reg) & 0xff, (val >> 8) & 0xff, val & 0xff]
if new_rdsel != old_rdsel:
# Must set RDSEL value first
self.spi.spi_send(msg)
params = self.spi.spi_transfer(msg)
pr = bytearray(params['response'])
return (pr[0] << 16) | (pr[1] << 8) | pr[2]
def set_register(self, reg_name, val, print_time=None):
minclock = 0
if print_time is not None:
minclock = self.spi.get_mcu().print_time_to_clock(print_time)
reg = self.name_to_reg[reg_name]
msg = [((val >> 16) | reg) & 0xff, (val >> 8) & 0xff, val & 0xff]
with self.mutex:
self.spi.spi_send(msg, minclock)
######################################################################
# TMC2660 printer object
######################################################################
class TMC2660:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.fields.set_field("sdoff", 0) # Access DRVCTRL in step/dir mode
self.mcu_tmc = MCU_TMC2660_SPI(config, Registers, self.fields)
# Register commands
current_helper = TMC2660CurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# CHOPCONF
set_config_field = self.fields.set_config_field
set_config_field(config, "tbl", 2)
set_config_field(config, "rndtf", 0)
set_config_field(config, "hdec", 0)
set_config_field(config, "chm", 0)
set_config_field(config, "hend", 3)
set_config_field(config, "hstrt", 3)
set_config_field(config, "toff", 4)
if not self.fields.get_field("chm"):
if (self.fields.get_field("hstrt") +
self.fields.get_field("hend")) > 15:
raise config.error("driver_HEND + driver_HSTRT must be <= 15")
# SMARTEN
set_config_field(config, "seimin", 0)
set_config_field(config, "sedn", 0)
set_config_field(config, "semax", 0)
set_config_field(config, "seup", 0)
set_config_field(config, "semin", 0)
# SGSCONF
set_config_field(config, "sfilt", 0)
set_config_field(config, "sgt", 0)
# DRVCONF
set_config_field(config, "slph", 0)
set_config_field(config, "slpl", 0)
set_config_field(config, "diss2g", 0)
set_config_field(config, "ts2g", 3)
def load_config_prefix(config):
return TMC2660(config)
# TMC2660 configuration
#
# Copyright (C) 2018-2019 Florian Heilmann <Florian.Heilmann@gmx.net>
# Copyright (C) 2019-2021 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
from . import bus, tmc, tmc2130
Registers = {
"DRVCONF": 0xE, "SGCSCONF": 0xC, "SMARTEN": 0xA,
"CHOPCONF": 0x8, "DRVCTRL": 0x0
}
ReadRegisters = [ "READRSP@RDSEL0", "READRSP@RDSEL1", "READRSP@RDSEL2" ]
Fields = {}
Fields["DRVCTRL"] = {
"mres": 0x0f,
"dedge": 0x01 << 8,
"intpol": 0x01 << 9,
}
Fields["CHOPCONF"] = {
"toff": 0x0f,
"hstrt": 0x7 << 4,
"hend": 0x0f << 7,
"hdec": 0x03 << 11,
"rndtf": 0x01 << 13,
"chm": 0x01 << 14,
"tbl": 0x03 << 15
}
Fields["SMARTEN"] = {
"semin" : 0x0f,
"seup": 0x03 << 5,
"semax": 0x0f << 8,
"sedn": 0x03 << 13,
"seimin": 0x01 << 15
}
Fields["SGCSCONF"] = {
"cs": 0x1f,
"sgt": 0x7F << 8,
"sfilt": 0x01 << 16
}
Fields["DRVCONF"] = {
"rdsel": 0x03 << 4,
"vsense": 0x01 << 6,
"sdoff": 0x01 << 7,
"ts2g": 0x03 << 8,
"diss2g": 0x01 << 10,
"slpl": 0x03 << 12,
"slph": 0x03 << 14,
"tst": 0x01 << 16
}
Fields["READRSP@RDSEL0"] = {
"stallguard": 0x01 << 4,
"ot": 0x01 << 5,
"otpw": 0x01 << 6,
"s2ga": 0x01 << 7,
"s2gb": 0x01 << 8,
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
"mstep": 0x3ff << 14
}
Fields["READRSP@RDSEL1"] = {
"stallguard": 0x01 << 4,
"ot": 0x01 << 5,
"otpw": 0x01 << 6,
"s2ga": 0x01 << 7,
"s2gb": 0x01 << 8,
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
"sg_result": 0x3ff << 14
}
Fields["READRSP@RDSEL2"] = {
"stallguard": 0x01 << 4,
"ot": 0x01 << 5,
"otpw": 0x01 << 6,
"s2ga": 0x01 << 7,
"s2gb": 0x01 << 8,
"ola": 0x01 << 9,
"olb": 0x01 << 10,
"stst": 0x01 << 11,
"se": 0x1f << 14,
"sg_result@rdsel2": 0x1f << 19
}
SignedFields = ["sgt"]
FieldFormatters = dict(tmc2130.FieldFormatters)
FieldFormatters.update({
"chm": (lambda v: "1(constant toff)" if v else "0(spreadCycle)"),
"vsense": (lambda v: "1(165mV)" if v else "0(305mV)"),
"sdoff": (lambda v: "1(Step/Dir disabled!)" if v else ""),
"diss2g": (lambda v: "1(Short to GND disabled!)" if v else ""),
"se": (lambda v: ("%d" % v) if v else "0(Reset?)"),
})
######################################################################
# TMC stepper current config helper
######################################################################
MAX_CURRENT = 2.400
class TMC2660CurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
self.current = config.getfloat('run_current', minval=0.1,
maxval=MAX_CURRENT)
self.sense_resistor = config.getfloat('sense_resistor')
vsense, cs = self._calc_current(self.current)
self.fields.set_field("cs", cs)
self.fields.set_field("vsense", vsense)
# Register ready/printing handlers
self.idle_current_percentage = config.getint(
'idle_current_percent', default=100, minval=0, maxval=100)
if self.idle_current_percentage < 100:
self.printer.register_event_handler("idle_timeout:printing",
self._handle_printing)
self.printer.register_event_handler("idle_timeout:ready",
self._handle_ready)
def _calc_current_bits(self, current, vsense):
vref = 0.165 if vsense else 0.310
sr = self.sense_resistor
cs = int(32. * sr * current * math.sqrt(2.) / vref + .5) - 1
return max(0, min(31, cs))
def _calc_current_from_bits(self, cs, vsense):
vref = 0.165 if vsense else 0.310
return (cs + 1) * vref / (32. * self.sense_resistor * math.sqrt(2.))
def _calc_current(self, run_current):
vsense = True
irun = self._calc_current_bits(run_current, True)
if irun == 31:
cur = self._calc_current_from_bits(irun, True)
if cur < run_current:
irun2 = self._calc_current_bits(run_current, False)
cur2 = self._calc_current_from_bits(irun2, False)
if abs(run_current - cur2) < abs(run_current - cur):
vsense = False
irun = irun2
return vsense, irun
def _handle_printing(self, print_time):
print_time -= 0.100 # Schedule slightly before deadline
self.printer.get_reactor().register_callback(
(lambda ev: self._update_current(self.current, print_time)))
def _handle_ready(self, print_time):
current = self.current * float(self.idle_current_percentage) / 100.
self.printer.get_reactor().register_callback(
(lambda ev: self._update_current(current, print_time)))
def _update_current(self, current, print_time):
vsense, cs = self._calc_current(current)
val = self.fields.set_field("cs", cs)
self.mcu_tmc.set_register("SGCSCONF", val, print_time)
# Only update VSENSE if we need to
if vsense != self.fields.get_field("vsense"):
val = self.fields.set_field("vsense", vsense)
self.mcu_tmc.set_register("DRVCONF", val, print_time)
def get_current(self):
return self.current, None, None, MAX_CURRENT
def set_current(self, run_current, hold_current, print_time):
self.current = run_current
self._update_current(run_current, print_time)
######################################################################
# TMC2660 SPI
######################################################################
# Helper code for working with TMC2660 devices via SPI
class MCU_TMC2660_SPI:
def __init__(self, config, name_to_reg, fields):
self.printer = config.get_printer()
self.mutex = self.printer.get_reactor().mutex()
self.spi = bus.MCU_SPI_from_config(config, 0, default_speed=4000000)
self.name_to_reg = name_to_reg
self.fields = fields
def get_fields(self):
return self.fields
def get_register(self, reg_name):
new_rdsel = ReadRegisters.index(reg_name)
reg = self.name_to_reg["DRVCONF"]
if self.printer.get_start_args().get('debugoutput') is not None:
return 0
with self.mutex:
old_rdsel = self.fields.get_field("rdsel")
val = self.fields.set_field("rdsel", new_rdsel)
msg = [((val >> 16) | reg) & 0xff, (val >> 8) & 0xff, val & 0xff]
if new_rdsel != old_rdsel:
# Must set RDSEL value first
self.spi.spi_send(msg)
params = self.spi.spi_transfer(msg)
pr = bytearray(params['response'])
return (pr[0] << 16) | (pr[1] << 8) | pr[2]
def set_register(self, reg_name, val, print_time=None):
minclock = 0
if print_time is not None:
minclock = self.spi.get_mcu().print_time_to_clock(print_time)
reg = self.name_to_reg[reg_name]
msg = [((val >> 16) | reg) & 0xff, (val >> 8) & 0xff, val & 0xff]
with self.mutex:
self.spi.spi_send(msg, minclock)
def get_tmc_frequency(self):
return None
######################################################################
# TMC2660 printer object
######################################################################
class TMC2660:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.fields.set_field("sdoff", 0) # Access DRVCTRL in step/dir mode
self.mcu_tmc = MCU_TMC2660_SPI(config, Registers, self.fields)
# Register commands
current_helper = TMC2660CurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# CHOPCONF
set_config_field = self.fields.set_config_field
set_config_field(config, "tbl", 2)
set_config_field(config, "rndtf", 0)
set_config_field(config, "hdec", 0)
set_config_field(config, "chm", 0)
set_config_field(config, "hend", 3)
set_config_field(config, "hstrt", 3)
set_config_field(config, "toff", 4)
if not self.fields.get_field("chm"):
if (self.fields.get_field("hstrt") +
self.fields.get_field("hend")) > 15:
raise config.error("driver_HEND + driver_HSTRT must be <= 15")
# SMARTEN
set_config_field(config, "seimin", 0)
set_config_field(config, "sedn", 0)
set_config_field(config, "semax", 0)
set_config_field(config, "seup", 0)
set_config_field(config, "semin", 0)
# SGSCONF
set_config_field(config, "sfilt", 0)
set_config_field(config, "sgt", 0)
# DRVCONF
set_config_field(config, "slph", 0)
set_config_field(config, "slpl", 0)
set_config_field(config, "diss2g", 0)
set_config_field(config, "ts2g", 3)
def load_config_prefix(config):
return TMC2660(config)

View File

@@ -1,340 +1,383 @@
# TMC5160 configuration
#
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
from . import bus, tmc, tmc2130
TMC_FREQUENCY=12000000.
Registers = {
"GCONF": 0x00,
"GSTAT": 0x01,
"IFCNT": 0x02,
"SLAVECONF": 0x03,
"IOIN": 0x04,
"X_COMPARE": 0x05,
"OTP_READ": 0x07,
"FACTORY_CONF": 0x08,
"SHORT_CONF": 0x09,
"DRV_CONF": 0x0A,
"GLOBALSCALER": 0x0B,
"OFFSET_READ": 0x0C,
"IHOLD_IRUN": 0x10,
"TPOWERDOWN": 0x11,
"TSTEP": 0x12,
"TPWMTHRS": 0x13,
"TCOOLTHRS": 0x14,
"THIGH": 0x15,
"RAMPMODE": 0x20,
"XACTUAL": 0x21,
"VACTUAL": 0x22,
"VSTART": 0x23,
"A1": 0x24,
"V1": 0x25,
"AMAX": 0x26,
"VMAX": 0x27,
"DMAX": 0x28,
"D1": 0x2A,
"VSTOP": 0x2B,
"TZEROWAIT": 0x2C,
"XTARGET": 0x2D,
"VDCMIN": 0x33,
"SW_MODE": 0x34,
"RAMP_STAT": 0x35,
"XLATCH": 0x36,
"ENCMODE": 0x38,
"X_ENC": 0x39,
"ENC_CONST": 0x3A,
"ENC_STATUS": 0x3B,
"ENC_LATCH": 0x3C,
"ENC_DEVIATION": 0x3D,
"MSLUT0": 0x60,
"MSLUT1": 0x61,
"MSLUT2": 0x62,
"MSLUT3": 0x63,
"MSLUT4": 0x64,
"MSLUT5": 0x65,
"MSLUT6": 0x66,
"MSLUT7": 0x67,
"MSLUTSEL": 0x68,
"MSLUTSTART": 0x69,
"MSCNT": 0x6A,
"MSCURACT": 0x6B,
"CHOPCONF": 0x6C,
"COOLCONF": 0x6D,
"DCCTRL": 0x6E,
"DRV_STATUS": 0x6F,
"PWMCONF": 0x70,
"PWM_SCALE": 0x71,
"PWM_AUTO": 0x72,
"LOST_STEPS": 0x73,
}
ReadRegisters = [
"GCONF", "CHOPCONF", "GSTAT", "DRV_STATUS", "FACTORY_CONF", "IOIN",
"LOST_STEPS", "MSCNT", "MSCURACT", "OTP_READ", "PWM_SCALE",
"PWM_AUTO", "TSTEP"
]
Fields = {}
Fields["COOLCONF"] = {
"semin": 0x0F << 0,
"seup": 0x03 << 5,
"semax": 0x0F << 8,
"sedn": 0x03 << 13,
"seimin": 0x01 << 15,
"sgt": 0x7F << 16,
"sfilt": 0x01 << 24
}
Fields["CHOPCONF"] = {
"toff": 0x0F << 0,
"hstrt": 0x07 << 4,
"hend": 0x0F << 7,
"fd3": 0x01 << 11,
"disfdcc": 0x01 << 12,
"chm": 0x01 << 14,
"tbl": 0x03 << 15,
"vhighfs": 0x01 << 18,
"vhighchm": 0x01 << 19,
"tpfd": 0x0F << 20, # midrange resonances
"mres": 0x0F << 24,
"intpol": 0x01 << 28,
"dedge": 0x01 << 29,
"diss2g": 0x01 << 30,
"diss2vs": 0x01 << 31
}
Fields["DRV_STATUS"] = {
"sg_result": 0x3FF << 0,
"s2vsa": 0x01 << 12,
"s2vsb": 0x01 << 13,
"stealth": 0x01 << 14,
"fsactive": 0x01 << 15,
"csactual": 0xFF << 16,
"stallguard": 0x01 << 24,
"ot": 0x01 << 25,
"otpw": 0x01 << 26,
"s2ga": 0x01 << 27,
"s2gb": 0x01 << 28,
"ola": 0x01 << 29,
"olb": 0x01 << 30,
"stst": 0x01 << 31
}
Fields["FACTORY_CONF"] = {
"factory_conf": 0x1F << 0
}
Fields["GCONF"] = {
"recalibrate": 0x01 << 0,
"faststandstill": 0x01 << 1,
"en_pwm_mode": 0x01 << 2,
"multistep_filt": 0x01 << 3,
"shaft": 0x01 << 4,
"diag0_error": 0x01 << 5,
"diag0_otpw": 0x01 << 6,
"diag0_stall": 0x01 << 7,
"diag1_stall": 0x01 << 8,
"diag1_index": 0x01 << 9,
"diag1_onstate": 0x01 << 10,
"diag1_steps_skipped": 0x01 << 11,
"diag0_int_pushpull": 0x01 << 12,
"diag1_poscomp_pushpull": 0x01 << 13,
"small_hysteresis": 0x01 << 14,
"stop_enable": 0x01 << 15,
"direct_mode": 0x01 << 16,
"test_mode": 0x01 << 17
}
Fields["GSTAT"] = {
"reset": 0x01 << 0,
"drv_err": 0x01 << 1,
"uv_cp": 0x01 << 2
}
Fields["GLOBALSCALER"] = {
"globalscaler": 0xFF << 0
}
Fields["IHOLD_IRUN"] = {
"ihold": 0x1F << 0,
"irun": 0x1F << 8,
"iholddelay": 0x0F << 16
}
Fields["IOIN"] = {
"refl_step": 0x01 << 0,
"refr_dir": 0x01 << 1,
"encb_dcen_cfg4": 0x01 << 2,
"enca_dcin_cfg5": 0x01 << 3,
"drv_enn": 0x01 << 4,
"enc_n_dco_cfg6": 0x01 << 5,
"sd_mode": 0x01 << 6,
"swcomp_in": 0x01 << 7,
"version": 0xFF << 24
}
Fields["LOST_STEPS"] = {
"lost_steps": 0xfffff << 0
}
Fields["MSCNT"] = {
"mscnt": 0x3ff << 0
}
Fields["MSCURACT"] = {
"cur_a": 0x1ff << 0,
"cur_b": 0x1ff << 16
}
Fields["OTP_READ"] = {
"otp_fclktrim": 0x1f << 0,
"otp_s2_level": 0x01 << 5,
"otp_bbm": 0x01 << 6,
"otp_tbl": 0x01 << 7
}
Fields["PWM_AUTO"] = {
"pwm_ofs_auto": 0xff << 0,
"pwm_grad_auto": 0xff << 16
}
Fields["PWMCONF"] = {
"pwm_ofs": 0xFF << 0,
"pwm_grad": 0xFF << 8,
"pwm_freq": 0x03 << 16,
"pwm_autoscale": 0x01 << 18,
"pwm_autograd": 0x01 << 19,
"freewheel": 0x03 << 20,
"pwm_reg": 0x0F << 24,
"pwm_lim": 0x0F << 28
}
Fields["PWM_SCALE"] = {
"pwm_scale_sum": 0xff << 0,
"pwm_scale_auto": 0x1ff << 16
}
Fields["TPOWERDOWN"] = {
"tpowerdown": 0xff << 0
}
Fields["TPWMTHRS"] = {
"tpwmthrs": 0xfffff << 0
}
Fields["TCOOLTHRS"] = {
"tcoolthrs": 0xfffff << 0
}
Fields["TSTEP"] = {
"tstep": 0xfffff << 0
}
SignedFields = ["cur_a", "cur_b", "sgt", "xactual", "vactual", "pwm_scale_auto"]
FieldFormatters = dict(tmc2130.FieldFormatters)
######################################################################
# TMC stepper current config helper
######################################################################
VREF = 0.325
MAX_CURRENT = 3.000
class TMC5160CurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
run_current = config.getfloat('run_current',
above=0., maxval=MAX_CURRENT)
hold_current = config.getfloat('hold_current', MAX_CURRENT,
above=0., maxval=MAX_CURRENT)
self.req_hold_current = hold_current
self.sense_resistor = config.getfloat('sense_resistor', 0.075, above=0.)
self._set_globalscaler(run_current)
irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
def _set_globalscaler(self, current):
globalscaler = int((current * 256. * math.sqrt(2.)
* self.sense_resistor / VREF) + .5)
globalscaler = max(32, globalscaler)
if globalscaler >= 256:
globalscaler = 0
self.fields.set_field("globalscaler", globalscaler)
def _calc_current_bits(self, current):
globalscaler = self.fields.get_field("globalscaler")
if not globalscaler:
globalscaler = 256
cs = int((current * 256. * 32. * math.sqrt(2.) * self.sense_resistor)
/ (globalscaler * VREF)
- 1. + .5)
return max(0, min(31, cs))
def _calc_current(self, run_current, hold_current):
irun = self._calc_current_bits(run_current)
ihold = self._calc_current_bits(min(hold_current, run_current))
return irun, ihold
def _calc_current_from_field(self, field_name):
globalscaler = self.fields.get_field("globalscaler")
if not globalscaler:
globalscaler = 256
bits = self.fields.get_field(field_name)
return (globalscaler * (bits + 1) * VREF
/ (256. * 32. * math.sqrt(2.) * self.sense_resistor))
def get_current(self):
run_current = self._calc_current_from_field("irun")
hold_current = self._calc_current_from_field("ihold")
return run_current, hold_current, self.req_hold_current, MAX_CURRENT
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("ihold", ihold)
val = self.fields.set_field("irun", irun)
self.mcu_tmc.set_register("IHOLD_IRUN", val, print_time)
######################################################################
# TMC5160 printer object
######################################################################
class TMC5160:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.mcu_tmc = tmc2130.MCU_TMC_SPI(config, Registers, self.fields)
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
current_helper = TMC5160CurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
# CHOPCONF
set_config_field = self.fields.set_config_field
set_config_field(config, "toff", 3)
set_config_field(config, "hstrt", 5)
set_config_field(config, "hend", 2)
set_config_field(config, "fd3", 0)
set_config_field(config, "disfdcc", 0)
set_config_field(config, "chm", 0)
set_config_field(config, "tbl", 2)
set_config_field(config, "vhighfs", 0)
set_config_field(config, "vhighchm", 0)
set_config_field(config, "tpfd", 4)
set_config_field(config, "diss2g", 0)
set_config_field(config, "diss2vs", 0)
# COOLCONF
set_config_field(config, "semin", 0) # page 52
set_config_field(config, "seup", 0)
set_config_field(config, "semax", 0)
set_config_field(config, "sedn", 0)
set_config_field(config, "seimin", 0)
set_config_field(config, "sgt", 0)
set_config_field(config, "sfilt", 0)
# IHOLDIRUN
set_config_field(config, "iholddelay", 6)
# PWMCONF
set_config_field(config, "pwm_ofs", 30)
set_config_field(config, "pwm_grad", 0)
set_config_field(config, "pwm_freq", 0)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "pwm_autograd", True)
set_config_field(config, "freewheel", 0)
set_config_field(config, "pwm_reg", 4)
set_config_field(config, "pwm_lim", 12)
# TPOWERDOWN
set_config_field(config, "tpowerdown", 10)
def load_config_prefix(config):
return TMC5160(config)
# TMC5160 configuration
#
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
from . import bus, tmc, tmc2130
TMC_FREQUENCY=12000000.
Registers = {
"GCONF": 0x00,
"GSTAT": 0x01,
"IFCNT": 0x02,
"SLAVECONF": 0x03,
"IOIN": 0x04,
"X_COMPARE": 0x05,
"OTP_READ": 0x07,
"FACTORY_CONF": 0x08,
"SHORT_CONF": 0x09,
"DRV_CONF": 0x0A,
"GLOBALSCALER": 0x0B,
"OFFSET_READ": 0x0C,
"IHOLD_IRUN": 0x10,
"TPOWERDOWN": 0x11,
"TSTEP": 0x12,
"TPWMTHRS": 0x13,
"TCOOLTHRS": 0x14,
"THIGH": 0x15,
"RAMPMODE": 0x20,
"XACTUAL": 0x21,
"VACTUAL": 0x22,
"VSTART": 0x23,
"A1": 0x24,
"V1": 0x25,
"AMAX": 0x26,
"VMAX": 0x27,
"DMAX": 0x28,
"D1": 0x2A,
"VSTOP": 0x2B,
"TZEROWAIT": 0x2C,
"XTARGET": 0x2D,
"VDCMIN": 0x33,
"SW_MODE": 0x34,
"RAMP_STAT": 0x35,
"XLATCH": 0x36,
"ENCMODE": 0x38,
"X_ENC": 0x39,
"ENC_CONST": 0x3A,
"ENC_STATUS": 0x3B,
"ENC_LATCH": 0x3C,
"ENC_DEVIATION": 0x3D,
"MSLUT0": 0x60,
"MSLUT1": 0x61,
"MSLUT2": 0x62,
"MSLUT3": 0x63,
"MSLUT4": 0x64,
"MSLUT5": 0x65,
"MSLUT6": 0x66,
"MSLUT7": 0x67,
"MSLUTSEL": 0x68,
"MSLUTSTART": 0x69,
"MSCNT": 0x6A,
"MSCURACT": 0x6B,
"CHOPCONF": 0x6C,
"COOLCONF": 0x6D,
"DCCTRL": 0x6E,
"DRV_STATUS": 0x6F,
"PWMCONF": 0x70,
"PWM_SCALE": 0x71,
"PWM_AUTO": 0x72,
"LOST_STEPS": 0x73,
}
ReadRegisters = [
"GCONF", "CHOPCONF", "GSTAT", "DRV_STATUS", "FACTORY_CONF", "IOIN",
"LOST_STEPS", "MSCNT", "MSCURACT", "OTP_READ", "PWM_SCALE",
"PWM_AUTO", "TSTEP"
]
Fields = {}
Fields["COOLCONF"] = {
"semin": 0x0F << 0,
"seup": 0x03 << 5,
"semax": 0x0F << 8,
"sedn": 0x03 << 13,
"seimin": 0x01 << 15,
"sgt": 0x7F << 16,
"sfilt": 0x01 << 24
}
Fields["CHOPCONF"] = {
"toff": 0x0F << 0,
"hstrt": 0x07 << 4,
"hend": 0x0F << 7,
"fd3": 0x01 << 11,
"disfdcc": 0x01 << 12,
"chm": 0x01 << 14,
"tbl": 0x03 << 15,
"vhighfs": 0x01 << 18,
"vhighchm": 0x01 << 19,
"tpfd": 0x0F << 20, # midrange resonances
"mres": 0x0F << 24,
"intpol": 0x01 << 28,
"dedge": 0x01 << 29,
"diss2g": 0x01 << 30,
"diss2vs": 0x01 << 31
}
Fields["DRV_CONF"] = {
"bbmtime": 0x1F << 0,
"bbmclks": 0x0F << 8,
"otselect": 0x03 << 16,
"drvstrength": 0x03 << 18,
"filt_isense": 0x03 << 20,
}
Fields["DRV_STATUS"] = {
"sg_result": 0x3FF << 0,
"s2vsa": 0x01 << 12,
"s2vsb": 0x01 << 13,
"stealth": 0x01 << 14,
"fsactive": 0x01 << 15,
"csactual": 0xFF << 16,
"stallguard": 0x01 << 24,
"ot": 0x01 << 25,
"otpw": 0x01 << 26,
"s2ga": 0x01 << 27,
"s2gb": 0x01 << 28,
"ola": 0x01 << 29,
"olb": 0x01 << 30,
"stst": 0x01 << 31
}
Fields["FACTORY_CONF"] = {
"factory_conf": 0x1F << 0
}
Fields["GCONF"] = {
"recalibrate": 0x01 << 0,
"faststandstill": 0x01 << 1,
"en_pwm_mode": 0x01 << 2,
"multistep_filt": 0x01 << 3,
"shaft": 0x01 << 4,
"diag0_error": 0x01 << 5,
"diag0_otpw": 0x01 << 6,
"diag0_stall": 0x01 << 7,
"diag1_stall": 0x01 << 8,
"diag1_index": 0x01 << 9,
"diag1_onstate": 0x01 << 10,
"diag1_steps_skipped": 0x01 << 11,
"diag0_int_pushpull": 0x01 << 12,
"diag1_poscomp_pushpull": 0x01 << 13,
"small_hysteresis": 0x01 << 14,
"stop_enable": 0x01 << 15,
"direct_mode": 0x01 << 16,
"test_mode": 0x01 << 17
}
Fields["GSTAT"] = {
"reset": 0x01 << 0,
"drv_err": 0x01 << 1,
"uv_cp": 0x01 << 2
}
Fields["GLOBALSCALER"] = {
"globalscaler": 0xFF << 0
}
Fields["IHOLD_IRUN"] = {
"ihold": 0x1F << 0,
"irun": 0x1F << 8,
"iholddelay": 0x0F << 16
}
Fields["IOIN"] = {
"refl_step": 0x01 << 0,
"refr_dir": 0x01 << 1,
"encb_dcen_cfg4": 0x01 << 2,
"enca_dcin_cfg5": 0x01 << 3,
"drv_enn": 0x01 << 4,
"enc_n_dco_cfg6": 0x01 << 5,
"sd_mode": 0x01 << 6,
"swcomp_in": 0x01 << 7,
"version": 0xFF << 24
}
Fields["LOST_STEPS"] = {
"lost_steps": 0xfffff << 0
}
Fields["MSLUT0"] = { "mslut0": 0xffffffff }
Fields["MSLUT1"] = { "mslut1": 0xffffffff }
Fields["MSLUT2"] = { "mslut2": 0xffffffff }
Fields["MSLUT3"] = { "mslut3": 0xffffffff }
Fields["MSLUT4"] = { "mslut4": 0xffffffff }
Fields["MSLUT5"] = { "mslut5": 0xffffffff }
Fields["MSLUT6"] = { "mslut6": 0xffffffff }
Fields["MSLUT7"] = { "mslut7": 0xffffffff }
Fields["MSLUTSEL"] = {
"x3": 0xFF << 24,
"x2": 0xFF << 16,
"x1": 0xFF << 8,
"w3": 0x03 << 6,
"w2": 0x03 << 4,
"w1": 0x03 << 2,
"w0": 0x03 << 0,
}
Fields["MSLUTSTART"] = {
"start_sin": 0xFF << 0,
"start_sin90": 0xFF << 16,
}
Fields["MSCNT"] = {
"mscnt": 0x3ff << 0
}
Fields["MSCURACT"] = {
"cur_a": 0x1ff << 0,
"cur_b": 0x1ff << 16
}
Fields["OTP_READ"] = {
"otp_fclktrim": 0x1f << 0,
"otp_s2_level": 0x01 << 5,
"otp_bbm": 0x01 << 6,
"otp_tbl": 0x01 << 7
}
Fields["PWM_AUTO"] = {
"pwm_ofs_auto": 0xff << 0,
"pwm_grad_auto": 0xff << 16
}
Fields["PWMCONF"] = {
"pwm_ofs": 0xFF << 0,
"pwm_grad": 0xFF << 8,
"pwm_freq": 0x03 << 16,
"pwm_autoscale": 0x01 << 18,
"pwm_autograd": 0x01 << 19,
"freewheel": 0x03 << 20,
"pwm_reg": 0x0F << 24,
"pwm_lim": 0x0F << 28
}
Fields["PWM_SCALE"] = {
"pwm_scale_sum": 0xff << 0,
"pwm_scale_auto": 0x1ff << 16
}
Fields["TPOWERDOWN"] = {
"tpowerdown": 0xff << 0
}
Fields["TPWMTHRS"] = {
"tpwmthrs": 0xfffff << 0
}
Fields["TCOOLTHRS"] = {
"tcoolthrs": 0xfffff << 0
}
Fields["TSTEP"] = {
"tstep": 0xfffff << 0
}
SignedFields = ["cur_a", "cur_b", "sgt", "xactual", "vactual", "pwm_scale_auto"]
FieldFormatters = dict(tmc2130.FieldFormatters)
FieldFormatters.update({
"s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""),
"s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""),
})
######################################################################
# TMC stepper current config helper
######################################################################
VREF = 0.325
MAX_CURRENT = 10.000 # Maximum dependent on board, but 10 is safe sanity check
class TMC5160CurrentHelper:
def __init__(self, config, mcu_tmc):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.mcu_tmc = mcu_tmc
self.fields = mcu_tmc.get_fields()
run_current = config.getfloat('run_current',
above=0., maxval=MAX_CURRENT)
hold_current = config.getfloat('hold_current', MAX_CURRENT,
above=0., maxval=MAX_CURRENT)
self.req_hold_current = hold_current
self.sense_resistor = config.getfloat('sense_resistor', 0.075, above=0.)
gscaler, irun, ihold = self._calc_current(run_current, hold_current)
self.fields.set_field("globalscaler", gscaler)
self.fields.set_field("ihold", ihold)
self.fields.set_field("irun", irun)
def _calc_globalscaler(self, current):
globalscaler = int((current * 256. * math.sqrt(2.)
* self.sense_resistor / VREF) + .5)
globalscaler = max(32, globalscaler)
if globalscaler >= 256:
globalscaler = 0
return globalscaler
def _calc_current_bits(self, current, globalscaler):
if not globalscaler:
globalscaler = 256
cs = int((current * 256. * 32. * math.sqrt(2.) * self.sense_resistor)
/ (globalscaler * VREF)
- 1. + .5)
return max(0, min(31, cs))
def _calc_current(self, run_current, hold_current):
gscaler = self._calc_globalscaler(run_current)
irun = self._calc_current_bits(run_current, gscaler)
ihold = self._calc_current_bits(min(hold_current, run_current), gscaler)
return gscaler, irun, ihold
def _calc_current_from_field(self, field_name):
globalscaler = self.fields.get_field("globalscaler")
if not globalscaler:
globalscaler = 256
bits = self.fields.get_field(field_name)
return (globalscaler * (bits + 1) * VREF
/ (256. * 32. * math.sqrt(2.) * self.sense_resistor))
def get_current(self):
run_current = self._calc_current_from_field("irun")
hold_current = self._calc_current_from_field("ihold")
return run_current, hold_current, self.req_hold_current, MAX_CURRENT
def set_current(self, run_current, hold_current, print_time):
self.req_hold_current = hold_current
gscaler, irun, ihold = self._calc_current(run_current, hold_current)
val = self.fields.set_field("globalscaler", gscaler)
self.mcu_tmc.set_register("GLOBALSCALER", val, print_time)
self.fields.set_field("ihold", ihold)
val = self.fields.set_field("irun", irun)
self.mcu_tmc.set_register("IHOLD_IRUN", val, print_time)
######################################################################
# TMC5160 printer object
######################################################################
class TMC5160:
def __init__(self, config):
# Setup mcu communication
self.fields = tmc.FieldHelper(Fields, SignedFields, FieldFormatters)
self.mcu_tmc = tmc2130.MCU_TMC_SPI(config, Registers, self.fields,
TMC_FREQUENCY)
# Allow virtual pins to be created
tmc.TMCVirtualPinHelper(config, self.mcu_tmc)
# Register commands
current_helper = TMC5160CurrentHelper(config, self.mcu_tmc)
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
cmdhelper.setup_register_dump(ReadRegisters)
self.get_phase_offset = cmdhelper.get_phase_offset
self.get_status = cmdhelper.get_status
# Setup basic register values
tmc.TMCWaveTableHelper(config, self.mcu_tmc)
tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY)
set_config_field = self.fields.set_config_field
# GCONF
set_config_field(config, "multistep_filt", True)
# CHOPCONF
set_config_field(config, "toff", 3)
set_config_field(config, "hstrt", 5)
set_config_field(config, "hend", 2)
set_config_field(config, "fd3", 0)
set_config_field(config, "disfdcc", 0)
set_config_field(config, "chm", 0)
set_config_field(config, "tbl", 2)
set_config_field(config, "vhighfs", 0)
set_config_field(config, "vhighchm", 0)
set_config_field(config, "tpfd", 4)
set_config_field(config, "diss2g", 0)
set_config_field(config, "diss2vs", 0)
# COOLCONF
set_config_field(config, "semin", 0) # page 52
set_config_field(config, "seup", 0)
set_config_field(config, "semax", 0)
set_config_field(config, "sedn", 0)
set_config_field(config, "seimin", 0)
set_config_field(config, "sgt", 0)
set_config_field(config, "sfilt", 0)
# DRV_CONF
set_config_field(config, "drvstrength", 0)
set_config_field(config, "bbmclks", 4)
set_config_field(config, "bbmtime", 0)
set_config_field(config, "filt_isense", 0)
# IHOLDIRUN
set_config_field(config, "iholddelay", 6)
# PWMCONF
set_config_field(config, "pwm_ofs", 30)
set_config_field(config, "pwm_grad", 0)
set_config_field(config, "pwm_freq", 0)
set_config_field(config, "pwm_autoscale", True)
set_config_field(config, "pwm_autograd", True)
set_config_field(config, "freewheel", 0)
set_config_field(config, "pwm_reg", 4)
set_config_field(config, "pwm_lim", 12)
# TPOWERDOWN
set_config_field(config, "tpowerdown", 10)
def load_config_prefix(config):
return TMC5160(config)

View File

@@ -1,252 +1,255 @@
# Helper code for communicating with TMC stepper drivers via UART
#
# Copyright (C) 2018-2021 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
######################################################################
# TMC uart analog mux support
######################################################################
class MCU_analog_mux:
def __init__(self, mcu, cmd_queue, select_pins_desc):
self.mcu = mcu
self.cmd_queue = cmd_queue
ppins = mcu.get_printer().lookup_object("pins")
select_pin_params = [ppins.lookup_pin(spd, can_invert=True)
for spd in select_pins_desc]
self.oids = [self.mcu.create_oid() for pp in select_pin_params]
self.pins = [pp['pin'] for pp in select_pin_params]
self.pin_values = tuple([-1 for pp in select_pin_params])
for oid, pin in zip(self.oids, self.pins):
self.mcu.add_config_cmd("config_digital_out oid=%d pin=%s"
" value=0 default_value=0 max_duration=0"
% (oid, pin))
self.update_pin_cmd = None
self.mcu.register_config_callback(self.build_config)
def build_config(self):
self.update_pin_cmd = self.mcu.lookup_command(
"update_digital_out oid=%c value=%c", cq=self.cmd_queue)
def get_instance_id(self, select_pins_desc):
ppins = self.mcu.get_printer().lookup_object("pins")
select_pin_params = [ppins.parse_pin(spd, can_invert=True)
for spd in select_pins_desc]
for pin_params in select_pin_params:
if pin_params['chip'] != self.mcu:
raise self.mcu.get_printer().config_error(
"TMC mux pins must be on the same mcu")
pins = [pp['pin'] for pp in select_pin_params]
if pins != self.pins:
raise self.mcu.get_printer().config_error(
"All TMC mux instances must use identical pins")
return tuple([not pp['invert'] for pp in select_pin_params])
def activate(self, instance_id):
for oid, old, new in zip(self.oids, self.pin_values, instance_id):
if old != new:
self.update_pin_cmd.send([oid, new])
self.pin_values = instance_id
######################################################################
# TMC uart communication
######################################################################
# Share mutexes so only one active tmc_uart command on a single mcu at
# a time. This helps limit cpu usage on slower micro-controllers.
class PrinterTMCUartMutexes:
def __init__(self):
self.mcu_to_mutex = {}
def lookup_tmc_uart_mutex(mcu):
printer = mcu.get_printer()
pmutexes = printer.lookup_object('tmc_uart', None)
if pmutexes is None:
pmutexes = PrinterTMCUartMutexes()
printer.add_object('tmc_uart', pmutexes)
mutex = pmutexes.mcu_to_mutex.get(mcu)
if mutex is None:
mutex = printer.get_reactor().mutex()
pmutexes.mcu_to_mutex[mcu] = mutex
return mutex
TMC_BAUD_RATE = 40000
TMC_BAUD_RATE_AVR = 9000
# Code for sending messages on a TMC uart
class MCU_TMC_uart_bitbang:
def __init__(self, rx_pin_params, tx_pin_params, select_pins_desc):
self.mcu = rx_pin_params['chip']
self.mutex = lookup_tmc_uart_mutex(self.mcu)
self.pullup = rx_pin_params['pullup']
self.rx_pin = rx_pin_params['pin']
self.tx_pin = tx_pin_params['pin']
self.oid = self.mcu.create_oid()
self.cmd_queue = self.mcu.alloc_command_queue()
self.analog_mux = None
if select_pins_desc is not None:
self.analog_mux = MCU_analog_mux(self.mcu, self.cmd_queue,
select_pins_desc)
self.instances = {}
self.tmcuart_send_cmd = None
self.mcu.register_config_callback(self.build_config)
def build_config(self):
baud = TMC_BAUD_RATE
mcu_type = self.mcu.get_constants().get("MCU", "")
if mcu_type.startswith("atmega") or mcu_type.startswith("at90usb"):
baud = TMC_BAUD_RATE_AVR
bit_ticks = self.mcu.seconds_to_clock(1. / baud)
self.mcu.add_config_cmd(
"config_tmcuart oid=%d rx_pin=%s pull_up=%d tx_pin=%s bit_time=%d"
% (self.oid, self.rx_pin, self.pullup, self.tx_pin, bit_ticks))
self.tmcuart_send_cmd = self.mcu.lookup_query_command(
"tmcuart_send oid=%c write=%*s read=%c",
"tmcuart_response oid=%c read=%*s", oid=self.oid,
cq=self.cmd_queue, is_async=True)
def register_instance(self, rx_pin_params, tx_pin_params,
select_pins_desc, addr):
if (rx_pin_params['pin'] != self.rx_pin
or tx_pin_params['pin'] != self.tx_pin
or (select_pins_desc is None) != (self.analog_mux is None)):
raise self.mcu.get_printer().config_error(
"Shared TMC uarts must use the same pins")
instance_id = None
if self.analog_mux is not None:
instance_id = self.analog_mux.get_instance_id(select_pins_desc)
if (instance_id, addr) in self.instances:
raise self.mcu.get_printer().config_error(
"Shared TMC uarts need unique address or select_pins polarity")
self.instances[(instance_id, addr)] = True
return instance_id
def _calc_crc8(self, data):
# Generate a CRC8-ATM value for a bytearray
crc = 0
for b in data:
for i in range(8):
if (crc >> 7) ^ (b & 0x01):
crc = (crc << 1) ^ 0x07
else:
crc = (crc << 1)
crc &= 0xff
b >>= 1
return crc
def _add_serial_bits(self, data):
# Add serial start and stop bits to a message in a bytearray
out = 0
pos = 0
for d in data:
b = (d << 1) | 0x200
out |= (b << pos)
pos += 10
res = bytearray()
for i in range((pos+7)//8):
res.append((out >> (i*8)) & 0xff)
return res
def _encode_read(self, sync, addr, reg):
# Generate a uart read register message
msg = bytearray([sync, addr, reg])
msg.append(self._calc_crc8(msg))
return self._add_serial_bits(msg)
def _encode_write(self, sync, addr, reg, val):
# Generate a uart write register message
msg = bytearray([sync, addr, reg, (val >> 24) & 0xff,
(val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff])
msg.append(self._calc_crc8(msg))
return self._add_serial_bits(msg)
def _decode_read(self, reg, data):
# Extract a uart read response message
if len(data) != 10:
return None
# Convert data into a long integer for easy manipulation
mval = pos = 0
for d in bytearray(data):
mval |= d << pos
pos += 8
# Extract register value
val = ((((mval >> 31) & 0xff) << 24) | (((mval >> 41) & 0xff) << 16)
| (((mval >> 51) & 0xff) << 8) | ((mval >> 61) & 0xff))
# Verify start/stop bits and crc
encoded_data = self._encode_write(0x05, 0xff, reg, val)
if data != encoded_data:
return None
return val
def reg_read(self, instance_id, addr, reg):
if self.analog_mux is not None:
self.analog_mux.activate(instance_id)
msg = self._encode_read(0xf5, addr, reg)
params = self.tmcuart_send_cmd.send([self.oid, msg, 10])
return self._decode_read(reg, params['read'])
def reg_write(self, instance_id, addr, reg, val, print_time=None):
minclock = 0
if print_time is not None:
minclock = self.mcu.print_time_to_clock(print_time)
if self.analog_mux is not None:
self.analog_mux.activate(instance_id)
msg = self._encode_write(0xf5, addr, reg | 0x80, val)
self.tmcuart_send_cmd.send([self.oid, msg, 0], minclock=minclock)
# Lookup a (possibly shared) tmc uart
def lookup_tmc_uart_bitbang(config, max_addr):
ppins = config.get_printer().lookup_object("pins")
rx_pin_params = ppins.lookup_pin(config.get('uart_pin'), can_pullup=True,
share_type="tmc_uart_rx")
tx_pin_desc = config.get('tx_pin', None)
if tx_pin_desc is None:
tx_pin_params = rx_pin_params
else:
tx_pin_params = ppins.lookup_pin(tx_pin_desc, share_type="tmc_uart_tx")
if rx_pin_params['chip'] is not tx_pin_params['chip']:
raise ppins.error("TMC uart rx and tx pins must be on the same mcu")
select_pins_desc = config.getlist('select_pins', None)
addr = config.getint('uart_address', 0, minval=0, maxval=max_addr)
mcu_uart = rx_pin_params.get('class')
if mcu_uart is None:
mcu_uart = MCU_TMC_uart_bitbang(rx_pin_params, tx_pin_params,
select_pins_desc)
rx_pin_params['class'] = mcu_uart
instance_id = mcu_uart.register_instance(rx_pin_params, tx_pin_params,
select_pins_desc, addr)
return instance_id, addr, mcu_uart
# Helper code for communicating via TMC uart
class MCU_TMC_uart:
def __init__(self, config, name_to_reg, fields, max_addr=0):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.name_to_reg = name_to_reg
self.fields = fields
self.ifcnt = None
self.instance_id, self.addr, self.mcu_uart = lookup_tmc_uart_bitbang(
config, max_addr)
self.mutex = self.mcu_uart.mutex
def get_fields(self):
return self.fields
def _do_get_register(self, reg_name):
reg = self.name_to_reg[reg_name]
if self.printer.get_start_args().get('debugoutput') is not None:
return 0
for retry in range(5):
val = self.mcu_uart.reg_read(self.instance_id, self.addr, reg)
if val is not None:
return val
raise self.printer.command_error(
"Unable to read tmc uart '%s' register %s" % (self.name, reg_name))
def get_register(self, reg_name):
with self.mutex:
return self._do_get_register(reg_name)
def set_register(self, reg_name, val, print_time=None):
reg = self.name_to_reg[reg_name]
if self.printer.get_start_args().get('debugoutput') is not None:
return
with self.mutex:
for retry in range(5):
ifcnt = self.ifcnt
if ifcnt is None:
self.ifcnt = ifcnt = self._do_get_register("IFCNT")
self.mcu_uart.reg_write(self.instance_id, self.addr, reg, val,
print_time)
self.ifcnt = self._do_get_register("IFCNT")
if self.ifcnt == (ifcnt + 1) & 0xff:
return
raise self.printer.command_error(
"Unable to write tmc uart '%s' register %s" % (self.name, reg_name))
# Helper code for communicating with TMC stepper drivers via UART
#
# Copyright (C) 2018-2021 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
######################################################################
# TMC uart analog mux support
######################################################################
class MCU_analog_mux:
def __init__(self, mcu, cmd_queue, select_pins_desc):
self.mcu = mcu
self.cmd_queue = cmd_queue
ppins = mcu.get_printer().lookup_object("pins")
select_pin_params = [ppins.lookup_pin(spd, can_invert=True)
for spd in select_pins_desc]
self.oids = [self.mcu.create_oid() for pp in select_pin_params]
self.pins = [pp['pin'] for pp in select_pin_params]
self.pin_values = tuple([-1 for pp in select_pin_params])
for oid, pin in zip(self.oids, self.pins):
self.mcu.add_config_cmd("config_digital_out oid=%d pin=%s"
" value=0 default_value=0 max_duration=0"
% (oid, pin))
self.update_pin_cmd = None
self.mcu.register_config_callback(self.build_config)
def build_config(self):
self.update_pin_cmd = self.mcu.lookup_command(
"update_digital_out oid=%c value=%c", cq=self.cmd_queue)
def get_instance_id(self, select_pins_desc):
ppins = self.mcu.get_printer().lookup_object("pins")
select_pin_params = [ppins.parse_pin(spd, can_invert=True)
for spd in select_pins_desc]
for pin_params in select_pin_params:
if pin_params['chip'] != self.mcu:
raise self.mcu.get_printer().config_error(
"TMC mux pins must be on the same mcu")
pins = [pp['pin'] for pp in select_pin_params]
if pins != self.pins:
raise self.mcu.get_printer().config_error(
"All TMC mux instances must use identical pins")
return tuple([not pp['invert'] for pp in select_pin_params])
def activate(self, instance_id):
for oid, old, new in zip(self.oids, self.pin_values, instance_id):
if old != new:
self.update_pin_cmd.send([oid, new])
self.pin_values = instance_id
######################################################################
# TMC uart communication
######################################################################
# Share mutexes so only one active tmc_uart command on a single mcu at
# a time. This helps limit cpu usage on slower micro-controllers.
class PrinterTMCUartMutexes:
def __init__(self):
self.mcu_to_mutex = {}
def lookup_tmc_uart_mutex(mcu):
printer = mcu.get_printer()
pmutexes = printer.lookup_object('tmc_uart', None)
if pmutexes is None:
pmutexes = PrinterTMCUartMutexes()
printer.add_object('tmc_uart', pmutexes)
mutex = pmutexes.mcu_to_mutex.get(mcu)
if mutex is None:
mutex = printer.get_reactor().mutex()
pmutexes.mcu_to_mutex[mcu] = mutex
return mutex
TMC_BAUD_RATE = 40000
TMC_BAUD_RATE_AVR = 9000
# Code for sending messages on a TMC uart
class MCU_TMC_uart_bitbang:
def __init__(self, rx_pin_params, tx_pin_params, select_pins_desc):
self.mcu = rx_pin_params['chip']
self.mutex = lookup_tmc_uart_mutex(self.mcu)
self.pullup = rx_pin_params['pullup']
self.rx_pin = rx_pin_params['pin']
self.tx_pin = tx_pin_params['pin']
self.oid = self.mcu.create_oid()
self.cmd_queue = self.mcu.alloc_command_queue()
self.analog_mux = None
if select_pins_desc is not None:
self.analog_mux = MCU_analog_mux(self.mcu, self.cmd_queue,
select_pins_desc)
self.instances = {}
self.tmcuart_send_cmd = None
self.mcu.register_config_callback(self.build_config)
def build_config(self):
baud = TMC_BAUD_RATE
mcu_type = self.mcu.get_constants().get("MCU", "")
if mcu_type.startswith("atmega") or mcu_type.startswith("at90usb"):
baud = TMC_BAUD_RATE_AVR
bit_ticks = self.mcu.seconds_to_clock(1. / baud)
self.mcu.add_config_cmd(
"config_tmcuart oid=%d rx_pin=%s pull_up=%d tx_pin=%s bit_time=%d"
% (self.oid, self.rx_pin, self.pullup, self.tx_pin, bit_ticks))
self.tmcuart_send_cmd = self.mcu.lookup_query_command(
"tmcuart_send oid=%c write=%*s read=%c",
"tmcuart_response oid=%c read=%*s", oid=self.oid,
cq=self.cmd_queue, is_async=True)
def register_instance(self, rx_pin_params, tx_pin_params,
select_pins_desc, addr):
if (rx_pin_params['pin'] != self.rx_pin
or tx_pin_params['pin'] != self.tx_pin
or (select_pins_desc is None) != (self.analog_mux is None)):
raise self.mcu.get_printer().config_error(
"Shared TMC uarts must use the same pins")
instance_id = None
if self.analog_mux is not None:
instance_id = self.analog_mux.get_instance_id(select_pins_desc)
if (instance_id, addr) in self.instances:
raise self.mcu.get_printer().config_error(
"Shared TMC uarts need unique address or select_pins polarity")
self.instances[(instance_id, addr)] = True
return instance_id
def _calc_crc8(self, data):
# Generate a CRC8-ATM value for a bytearray
crc = 0
for b in data:
for i in range(8):
if (crc >> 7) ^ (b & 0x01):
crc = (crc << 1) ^ 0x07
else:
crc = (crc << 1)
crc &= 0xff
b >>= 1
return crc
def _add_serial_bits(self, data):
# Add serial start and stop bits to a message in a bytearray
out = 0
pos = 0
for d in data:
b = (d << 1) | 0x200
out |= (b << pos)
pos += 10
res = bytearray()
for i in range((pos+7)//8):
res.append((out >> (i*8)) & 0xff)
return res
def _encode_read(self, sync, addr, reg):
# Generate a uart read register message
msg = bytearray([sync, addr, reg])
msg.append(self._calc_crc8(msg))
return self._add_serial_bits(msg)
def _encode_write(self, sync, addr, reg, val):
# Generate a uart write register message
msg = bytearray([sync, addr, reg, (val >> 24) & 0xff,
(val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff])
msg.append(self._calc_crc8(msg))
return self._add_serial_bits(msg)
def _decode_read(self, reg, data):
# Extract a uart read response message
if len(data) != 10:
return None
# Convert data into a long integer for easy manipulation
mval = pos = 0
for d in bytearray(data):
mval |= d << pos
pos += 8
# Extract register value
val = ((((mval >> 31) & 0xff) << 24) | (((mval >> 41) & 0xff) << 16)
| (((mval >> 51) & 0xff) << 8) | ((mval >> 61) & 0xff))
# Verify start/stop bits and crc
encoded_data = self._encode_write(0x05, 0xff, reg, val)
if data != encoded_data:
return None
return val
def reg_read(self, instance_id, addr, reg):
if self.analog_mux is not None:
self.analog_mux.activate(instance_id)
msg = self._encode_read(0xf5, addr, reg)
params = self.tmcuart_send_cmd.send([self.oid, msg, 10])
return self._decode_read(reg, params['read'])
def reg_write(self, instance_id, addr, reg, val, print_time=None):
minclock = 0
if print_time is not None:
minclock = self.mcu.print_time_to_clock(print_time)
if self.analog_mux is not None:
self.analog_mux.activate(instance_id)
msg = self._encode_write(0xf5, addr, reg | 0x80, val)
self.tmcuart_send_cmd.send([self.oid, msg, 0], minclock=minclock)
# Lookup a (possibly shared) tmc uart
def lookup_tmc_uart_bitbang(config, max_addr):
ppins = config.get_printer().lookup_object("pins")
rx_pin_params = ppins.lookup_pin(config.get('uart_pin'), can_pullup=True,
share_type="tmc_uart_rx")
tx_pin_desc = config.get('tx_pin', None)
if tx_pin_desc is None:
tx_pin_params = rx_pin_params
else:
tx_pin_params = ppins.lookup_pin(tx_pin_desc, share_type="tmc_uart_tx")
if rx_pin_params['chip'] is not tx_pin_params['chip']:
raise ppins.error("TMC uart rx and tx pins must be on the same mcu")
select_pins_desc = config.getlist('select_pins', None)
addr = config.getint('uart_address', 0, minval=0, maxval=max_addr)
mcu_uart = rx_pin_params.get('class')
if mcu_uart is None:
mcu_uart = MCU_TMC_uart_bitbang(rx_pin_params, tx_pin_params,
select_pins_desc)
rx_pin_params['class'] = mcu_uart
instance_id = mcu_uart.register_instance(rx_pin_params, tx_pin_params,
select_pins_desc, addr)
return instance_id, addr, mcu_uart
# Helper code for communicating via TMC uart
class MCU_TMC_uart:
def __init__(self, config, name_to_reg, fields, max_addr, tmc_frequency):
self.printer = config.get_printer()
self.name = config.get_name().split()[-1]
self.name_to_reg = name_to_reg
self.fields = fields
self.ifcnt = None
self.instance_id, self.addr, self.mcu_uart = lookup_tmc_uart_bitbang(
config, max_addr)
self.mutex = self.mcu_uart.mutex
self.tmc_frequency = tmc_frequency
def get_fields(self):
return self.fields
def _do_get_register(self, reg_name):
reg = self.name_to_reg[reg_name]
if self.printer.get_start_args().get('debugoutput') is not None:
return 0
for retry in range(5):
val = self.mcu_uart.reg_read(self.instance_id, self.addr, reg)
if val is not None:
return val
raise self.printer.command_error(
"Unable to read tmc uart '%s' register %s" % (self.name, reg_name))
def get_register(self, reg_name):
with self.mutex:
return self._do_get_register(reg_name)
def set_register(self, reg_name, val, print_time=None):
reg = self.name_to_reg[reg_name]
if self.printer.get_start_args().get('debugoutput') is not None:
return
with self.mutex:
for retry in range(5):
ifcnt = self.ifcnt
if ifcnt is None:
self.ifcnt = ifcnt = self._do_get_register("IFCNT")
self.mcu_uart.reg_write(self.instance_id, self.addr, reg, val,
print_time)
self.ifcnt = self._do_get_register("IFCNT")
if self.ifcnt == (ifcnt + 1) & 0xff:
return
raise self.printer.command_error(
"Unable to write tmc uart '%s' register %s" % (self.name, reg_name))
def get_tmc_frequency(self):
return self.tmc_frequency

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -30,6 +30,9 @@ class VirtualSD:
gcode_macro = self.printer.load_object(config, 'gcode_macro')
self.on_error_gcode = gcode_macro.load_template(
config, 'on_error_gcode', '')
# power lose resume
self.lines = 0
self.save_every_n_lines = 50
# Register commands
self.gcode = self.printer.lookup_object('gcode')
for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']:
@@ -120,6 +123,7 @@ class VirtualSD:
self.do_pause()
self.current_file.close()
self.current_file = None
self.lines = 0
self.print_stats.note_cancel()
self.file_position = self.file_size = 0.
# G-Code commands
@@ -232,16 +236,20 @@ class VirtualSD:
partial_input = ""
lines = []
error_message = None
plr_file = open("/home/mks/scripts/plr/plr_record", 'w', buffering=0)
while not self.must_pause_work:
if not lines:
# Read more data
try:
data = self.current_file.read(8192)
except:
plr_file.close()
logging.exception("virtual_sdcard read")
break
if not data:
# End of file
self.lines = 0
plr_file.close()
self.current_file.close()
self.current_file = None
logging.info("Finished SD card print")
@@ -263,6 +271,13 @@ class VirtualSD:
next_file_position = self.file_position + len(line) + 1
self.next_file_position = next_file_position
try:
self.lines += 1
if self.lines % self.save_every_n_lines == 0:
# temp = "SAVE_VARIABLE VARIABLE=gcode_lines VALUE=" + str(self.lines)
# self.gcode._process_commands([temp], need_ack=False)
plr_file.seek(0)
plr_file.write(str(self.lines))
plr_file.truncate()
self.gcode.run_script(line)
except self.gcode.error as e:
error_message = str(e)
@@ -287,6 +302,7 @@ class VirtualSD:
lines = []
partial_input = ""
logging.info("Exiting SD card print (position %d)", self.file_position)
plr_file.close()
self.work_timer = None
self.cmd_from_sd = False
if error_message is not None:
@@ -296,6 +312,16 @@ class VirtualSD:
else:
self.print_stats.note_complete()
return self.reactor.NEVER
# cmd_TOGGLE_PLR_help = "enable or disable power lose resume"
# def cmd_TOGGLE_PLR(self, gcmd):
# if self.plr_enable:
# self.plr_enable = False
# temp = "SAVE_VARIABLE VARIABLE=plr_enable VALUE=" + str(self.plr_enable)
# self.gcode._process_commands([temp], need_ack=False)
# gcmd.respond_raw("Power lose resume disabled")
# else:
# self.plr_enable = True
# gcmd.respond_raw("Power lose resume enabled")
def load_config(config):
return VirtualSD(config)

Binary file not shown.

View File

@@ -1,664 +0,0 @@
# X Twist Compensation
# Copyright (C) 2022 Jeremy Tan <jeremytkw98@gmail.com>
# This file may be distributed under the terms of the GNU GPLv3 license.
"""
[x_twist_compensation]
horizontal_move_z: 10
speed: 50
start_x: 0 ; nozzle's x coordinate at the start of the calibration ! required
end_x: 200 ; nozzle's x coordinate at the end of the calibration ! required
y: 100 ; nozzle's y coordinate during the calibration ! required
"""
import logging
import math
from . import manual_probe as ManualProbe, bed_mesh as BedMesh
DEFAULT_N_POINTS = 3
DEFAULT_PROFILE_NAME = 'default'
BED_MESH_CONFIG_NAME = 'bed_mesh'
class Config:
# values stored in printer.cfg when a profile is saved
DEFAULT_SPEED = 50.
DEFAULT_HORIZONTAL_MOVE_Z = 10.
REQUIRED = True
OPTIONAL = False
CONFIG_OPTIONS = {
'horizontal_move_z': (float, OPTIONAL, DEFAULT_HORIZONTAL_MOVE_Z),
'speed': (float, OPTIONAL, DEFAULT_SPEED),
'start_x': (float, REQUIRED, None),
'end_x': (float, REQUIRED, None),
'y': (float, REQUIRED, None)
}
class XTwistCompensation:
def __init__(self, config):
# get printer
self.printer = config.get_printer()
# get values from [x_twist_compensation] section in printer .cfg
for config_key, \
(config_type, required, default) in Config.CONFIG_OPTIONS.items():
value = None
if config_type == float:
value = config.getfloat(config_key, default)
else:
value = config.get(config_key, default)
if required and value is None:
raise config.error(
"Missing required config option for section [{}]: {}"
.format(config.get_name(), config_key))
setattr(self, config_key, value)
# setup persistent storage
self.pmgr = ProfileManager(config, self)
# setup calibrater
calibrater_config = {
'horizontal_move_z': self.horizontal_move_z
if hasattr(self, 'horizontal_move_z') else None,
'speed': self.speed if hasattr(self, 'speed') else None,
'start_x': self.start_x if hasattr(self, 'start_x') else None,
'end_x': self.end_x if hasattr(self, 'end_x') else None,
'y': self.y if hasattr(self, 'y') else None
}
self.calibrater = Calibrater(
config, self.pmgr, calibrater_config)
self.enabled = False
# register gcode handlers
self._register_gcode_handlers()
def _register_gcode_handlers(self):
# register gcode handlers
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command(
'X_TWIST_COMPENSATE_MESH',
self.cmd_X_TWIST_COMPENSATE_MESH,
desc=self.cmd_X_TWIST_COMPENSATE_MESH_help)
self.gcode.register_command(
'X_TWIST_COMPENSATE_STATUS',
self.cmd_X_TWIST_COMPENSATE_STATUS,
desc=self.cmd_X_TWIST_COMPENSATE_STATUS_help)
def get_z_compensation_value(self, x_coord, optional_profile_name=None):
# returns the (lineraly interpolated) z compensation value
# for the given x coordinate
# uses the current profile if optional_profile_name is not specified
enabled = self.pmgr.get_is_enabled()
if enabled or optional_profile_name is not None:
current_profile = \
self.pmgr.get_current_profile() \
if optional_profile_name is None \
else self.pmgr.get_profile(optional_profile_name)
z_compensations = current_profile.z_compensations
n_points = len(z_compensations)
spacing = (self.end_x - self.start_x) / (n_points - 1)
interpolate_t = (x_coord - self.start_x) / spacing
interpolate_i = int(math.floor(interpolate_t))
interpolate_i = BedMesh.constrain(interpolate_i, 0, n_points - 2)
interpolate_t -= interpolate_i
interpolated_z_compensation = BedMesh.lerp(
interpolate_t, z_compensations[interpolate_i],
z_compensations[interpolate_i + 1])
return interpolated_z_compensation
else:
return 0
cmd_X_TWIST_COMPENSATE_MESH_help = \
"Compensate a mesh by applying the x" \
"twist compensation to the given raw mesh"
def cmd_X_TWIST_COMPENSATE_MESH(self, gcmd):
# get the mesh name from the gcode command
raw_mesh_name = gcmd.get('MESH_NAME', None)
Helpers.check_non_empty_param(raw_mesh_name, gcmd, 'MESH_NAME')
# get the compensation profile name from the gcode command
compensation_name = gcmd.get('COMPENSATION_NAME', None)
Helpers.check_non_empty_param(
compensation_name, gcmd, 'COMPENSATION_NAME')
# get the bed_mesh object, then the bed_mesh profile manager
bed_mesh = self.printer.lookup_object('bed_mesh', None)
if not bed_mesh:
raise gcmd.error(
"[bed_mesh] is not specified in your printer configuration")
bed_mesh_pmgr = bed_mesh.pmgr
# load specified bed mesh as active bed mesh
bed_mesh_pmgr.load_profile(raw_mesh_name)
# get the active bed mesh
active_bed_mesh = bed_mesh.get_mesh()
# modify the probed matrix by applying the x twist compensation
#modified_probed_matrix = self._modify_probed_matrix(
# active_bed_mesh, compensation_name)
enabled = self.pmgr.get_is_enabled()
if enabled or compensation_name is not None:
current_profile = \
self.pmgr.get_current_profile() \
if compensation_name is None \
else self.pmgr.get_profile(compensation_name)
z_compensations = current_profile.z_compensations
modified_probed_matrix = self._modify_probed_matrix_XY(active_bed_mesh, z_compensations[0], z_compensations[1])
# update active mesh with modified probed matrix, save under new name
#compensated_mesh_name = \
# raw_mesh_name + '_compensated_' + compensation_name
active_bed_mesh.build_mesh(modified_probed_matrix)
#bed_mesh_pmgr.save_profile(compensated_mesh_name)
bed_mesh_pmgr.save_profile(raw_mesh_name)
def _modify_probed_matrix_XY(self, bed_mesh, xz, yz):
probed_matrix = bed_mesh.get_probed_matrix()
compensated_matrix = []
row_count = len(probed_matrix) - 1
yz_step = yz / row_count
col_count = len(probed_matrix[0]) - 1
xz_step = xz / col_count
for row_index in range(len(probed_matrix)):
compensated_row = []
row = probed_matrix[row_index]
for col_index in range(len(row)):
z = row[col_index]
compensated_z = z + (xz_step * col_index) + (yz_step * row_index)
compensated_row.append(compensated_z)
compensated_matrix.append(compensated_row)
compensated_matrix = tuple(tuple(row) for row in compensated_matrix)
return compensated_matrix
def _modify_probed_matrix(self, bed_mesh, compensation_profile_name):
# do compensating, by modifying z values in probed matrix
# probed matrix is a list of rows of probed z values
# eg. probed_matrix[0][0] = bottom left corner of mesh, z value
probed_matrix = bed_mesh.get_probed_matrix()
compensated_matrix = []
for row_index in range(len(probed_matrix)):
compensated_row = []
row = probed_matrix[row_index]
for col_index in range(len(row)):
z = row[col_index]
x_coord = self._get_mesh_point_x_coord(col_index, bed_mesh)
compensated_z = z + \
self.get_z_compensation_value(
x_coord, compensation_profile_name)
compensated_row.append(compensated_z)
compensated_matrix.append(compensated_row)
# compensated_matrix is a list of list
# bed_mesh expects tuple of tuple hence convert
compensated_matrix = tuple(tuple(row) for row in compensated_matrix)
return compensated_matrix
def _get_mesh_point_x_coord(self, col_index, mesh):
# returns the x coordinate of the given column index
# in the probed matrix
x_min = mesh.mesh_x_min
x_range = mesh.mesh_x_max - mesh.mesh_x_min
x_step = x_range / (len(mesh.probed_matrix[0]) - 1)
return x_min + col_index * x_step
cmd_X_TWIST_COMPENSATE_STATUS_help = \
"Get the status of the x twist compensation"
def cmd_X_TWIST_COMPENSATE_STATUS(self, gcmd):
if (self.pmgr.get_is_enabled()):
profile = self.pmgr.get_current_profile()
profile_name = profile.name
profile_z_compensations = profile.z_compensations
profile_recommended_z_offset = profile.recommended_z_offset
gcmd.respond_info(
"""
X twist compensation is enabled
Profile name: {}
Profile z compensations: {}
Profile recommended z offset: {}
""".format(profile_name,
profile_z_compensations, profile_recommended_z_offset))
else:
gcmd.respond_info(
"X twist compensation is disabled, "\
"load a profile using X_TWIST_PROFILE_LOAD"
)
class Calibrater:
def __init__(self, config, pmgr, calibrater_config):
# setup self attributes
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
self.pmgr = pmgr
self.probe = None
# probe settings are set to none, until they are available
self.lift_speed, self.probe_x_offset, self.probe_y_offset, \
self.stored_probe_z_offset = None, None, None, None
self.printer.register_event_handler("klippy:connect",
self._handle_connect(config))
self.speed = calibrater_config['speed']
self.horizontal_move_z = calibrater_config['horizontal_move_z']
self.start_point = (
calibrater_config['start_x'], calibrater_config['y'])
self.end_point = (calibrater_config['end_x'], calibrater_config['y'])
self.results = []
self.current_point_index = None
self.gcmd = None
# register gcode handlers
self._register_gcode_handlers()
def _handle_connect(self, config):
# gets probe settings when they are available
def callback():
self.probe = self.printer.lookup_object('probe', None)
if (self.probe is None):
raise config.error(
"X_TWIST_COMPENSATION requires [probe] to be defined")
self.lift_speed = self.probe.get_lift_speed()
self.probe_x_offset, self.probe_y_offset, \
self.stored_probe_z_offset = self.probe.get_offsets()
return callback
def _register_gcode_handlers(self):
# register gcode handlers
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command(
'X_TWIST_CALIBRATE', self.cmd_X_TWIST_CALIBRATE,
desc=self.cmd_X_TWIST_CALIBRATE_help)
cmd_X_TWIST_CALIBRATE_help = """
Performs the x twist calibration wizard
Measure z probe offset at n points along the x axis,
and calculate x twist compensation
Specify PROFILE_NAME=<PROFILE_NAME> - optional, default is 'default'
"""
def cmd_X_TWIST_CALIBRATE(self, gcmd):
self.gcmd = gcmd
# performs the x twist calibration wizard
# get params from command
profile_name = gcmd.get('PROFILE_NAME', DEFAULT_PROFILE_NAME)
n_points = gcmd.get_int('N_POINTS', DEFAULT_N_POINTS)
# check for valid profile_name
Helpers.check_non_empty_param(profile_name, self.gcmd, 'PROFILE_NAME')
# check for valid n_points
if n_points is None or n_points < 3:
raise self.gcmd.error(
"N_POINTS to probe must be at least 3")
# clear the current profile
self.pmgr.clear_profile()
# calculate some values
x_range = self.end_point[0] - self.start_point[0]
interval_dist = x_range / (n_points - 1)
nozzle_points = self._calculate_nozzle_points(n_points, interval_dist)
probe_points = self._calculate_probe_points(
nozzle_points, self.probe_x_offset, self.probe_y_offset)
# verify no other manual probe is in progress
ManualProbe.verify_no_manual_probe(self.printer)
# begin calibration
self.current_point_index = 0
self._calibration(
profile_name, probe_points, nozzle_points, interval_dist)
def _calculate_nozzle_points(self, n_points, interval_dist):
# calculate the points to put the probe at, returned as a list of tuples
nozzle_points = []
for i in range(n_points):
x = self.start_point[0] + i * interval_dist
y = self.start_point[1]
nozzle_points.append((x, y))
return nozzle_points
def _calculate_probe_points(self, nozzle_points,
probe_x_offset, probe_y_offset):
# calculate the points to put the nozzle at
# returned as a list of tuples
probe_points = []
for point in nozzle_points:
x = point[0] - probe_x_offset
y = point[1] - probe_y_offset
probe_points.append((x, y))
return probe_points
def _move_helper(self, target_coordinates, override_speed=None):
# pad target coordinates
target_coordinates = \
(target_coordinates[0], target_coordinates[1], None) \
if len(target_coordinates) == 2 else target_coordinates
toolhead = self.printer.lookup_object('toolhead')
speed = self.speed if target_coordinates[2] == None else self.lift_speed
speed = override_speed if override_speed is not None else speed
toolhead.manual_move(target_coordinates, speed)
def _calibration(self, profile_name, probe_points, nozzle_points, interval):
# begin the calibration process
self.gcmd.respond_info("X_TWIST_CALIBRATE: Probing point %d of %d" % (
self.current_point_index + 1, len(probe_points)))
# horizontal_move_z (to prevent probe trigger or hitting bed)
self._move_helper((None, None, self.horizontal_move_z))
# move to point to probe
self._move_helper((probe_points[self.current_point_index]
[0], probe_points[self.current_point_index][1], None))
# probe the point
self.probe.run_probe(self.gcmd)
# horizontal_move_z (to prevent probe trigger or hitting bed)
self._move_helper((None, None, self.horizontal_move_z))
# move the nozzle over the probe point
self._move_helper((nozzle_points[self.current_point_index]))
# start the manual (nozzle) probe
ManualProbe.ManualProbeHelper(
self.printer, self.gcmd,
self._manual_probe_callback_factory(profile_name,
probe_points, nozzle_points, interval))
def _manual_probe_callback_factory(self, profile_name, probe_points,
nozzle_points, interval):
# returns a callback function for the manual probe
is_end = self.current_point_index == len(probe_points) - 1
def callback(kin_pos):
if kin_pos is None:
# probe was cancelled
self.gcmd.respond_info(
"X_TWIST_CALIBRATE: Probe cancelled, calibration aborted")
return
z_offset = self.stored_probe_z_offset - kin_pos[2]
self.results.append(z_offset)
if is_end:
# end of calibration
self._finalize_calibration(profile_name)
else:
# move to next point
self.current_point_index += 1
self._calibration(
profile_name, probe_points, nozzle_points, interval)
return callback
def _finalize_calibration(self, profile_name):
# finalize the calibration process
# calculate average of results
avg = sum(self.results) / len(self.results)
# subtract average from each result
# so that they are independent of z_offset
self.results = [avg - x for x in self.results]
# create a new profile using profile manager
self.pmgr.create_profile(profile_name, self.results, avg)
# recommend z offset to user
self.gcmd.respond_info(
"X_TWIST_CALIBRATE: Calibration complete, reccomended z_offset: %f"
% (avg))
class Profile:
PROFILE_OPTIONS = {
'z_compensations': str, 'recommended_z_offset': float
}
def __init__(self, name, z_compensations, recommended_z_offset):
self.name = name
self.z_compensations = z_compensations
self.recommended_z_offset = recommended_z_offset
class ProfileManager:
def __init__(self, config, x_twist_compensation):
# setup self attributes
self.name = config.get_name()
self.printer = config.get_printer()
self.x_twist_compensation = x_twist_compensation
self.profiles = {}
self.gcode = self.printer.lookup_object('gcode')
self.current_profile = None
# fetch the stored profiles
self._fetch_stored_profiles(config)
logging.info('stored profiles: %s', self.profiles)
# register gcode handlers
self._register_gcode_handlers()
def get_is_enabled(self):
# returns
return self.current_profile is not None
def get_current_profile(self):
# return the current profile
if self.current_profile is None:
raise self.gcode.error(
"No X_TWIST_PROFILE loaded")
return self.current_profile
def get_profiles(self):
# dictionary of profiles loaded from printer.cfg, key is profile name
return self.profiles
def get_profile(self, profile_name):
# attempt to get profile from self.profiles
# throws error if profile does not exist or is corrupt
logging.info('attempting to get profile name %s', profile_name)
logging.info('available profiles: %s', self.profiles)
profile = self.profiles.get(profile_name, None)
if profile is None:
raise self.gcode.error(
"X_TWIST_PROFILE %s does not exist" % (profile_name))
z_compensations = profile.get('z_compensations', None)
if z_compensations is None:
raise self.gcode.error(
"X_TWIST_PROFILE %s does not have z_compensations"
% (profile_name))
recommended_z_offset = profile.get('recommended_z_offset', None)
if recommended_z_offset is None:
raise self.gcode.error(
"X_TWIST_PROFILE %s does not have recommended_z_offset"
% (profile_name))
return Profile(profile_name, z_compensations, recommended_z_offset)
def _fetch_stored_profiles(self, config):
# fetch stored profiles in printer.cfg
# (using prefix of "x_twist_compensation"")
stored_profiles = config.get_prefix_sections(self.name)
stored_profiles = [
stored_profile for stored_profile in stored_profiles
if stored_profile.get_name() != self.name
]
# add stored profiles to self.profiles
for stored_profile in stored_profiles:
prefixed_name = stored_profile.get_name()
# remove prefix from name
name = prefixed_name.split(' ', 1)[1]
self.profiles[name] = {}
for option, option_type in Profile.PROFILE_OPTIONS.items():
if option_type == float:
self.profiles[name][option] = stored_profile.getfloat(
option)
elif option_type == str:
value = stored_profile.get(option)
if option == 'z_compensations':
self.profiles[name][option] = \
Helpers.parse_comma_separated_floats(value)
else:
self.profiles[name][option] = stored_profile.get(
option)
def _register_gcode_handlers(self):
# register gcode handlers
self.gcode.register_command(
'X_TWIST_PROFILE', self.cmd_X_TWIST_PROFILE,
desc=self.cmd_X_TWIST_PROFILE_help)
self.gcode.register_command(
'X_TWIST_PROFILE_LOAD', self.cmd_X_TWIST_PROFILE_LOAD,
desc=self.cmd_X_TWIST_PROFILE_LOAD_help)
self.gcode.register_command(
'X_TWIST_PROFILE_SAVE', self.cmd_X_TWIST_PROFILE_SAVE,
desc=self.cmd_X_TWIST_PROFILE_SAVE_help)
self.gcode.register_command(
'X_TWIST_PROFILE_DELETE', self.cmd_X_TWIST_PROFILE_DELETE,
desc=self.cmd_X_TWIST_PROFILE_DELETE_help)
self.gcode.register_command(
'X_TWIST_PROFILE_CLEAR', self.cmd_X_TWIST_PROFILE_CLEAR,
desc=self.cmd_X_TWIST_PROFILE_CLEAR_help)
def create_profile(self, profile_name, z_compensations,
recommended_z_offset):
# create a new profile
new_profile = Profile(
profile_name, z_compensations, recommended_z_offset)
# save the profile
self._save_profile(new_profile)
def load_profile(self, profile_name):
# set the current profile
self.current_profile = self.get_profile(profile_name)
def clear_profile(self):
# clear the current profile
self.current_profile = None
def delete_profile(self, profile_name):
# try getting the profile to ensure it exists
self.get_profile(profile_name)
# remove the profile from config file
configfile = self.printer.lookup_object('configfile')
configfile.remove_section('%s %s' % (self.name, profile_name))
# remove the profile from self.profiles
profiles = dict(self.profiles)
del profiles[profile_name]
self.profiles = profiles
# inform user to save deletion
self.gcode.respond_info(
"Profile [%s] removed from storage for this session.\n"
"The SAVE_CONFIG command will update the printer\n"
"configuration and restart the printer" % (profile_name))
def _save_profile(self, profile):
profile_name = profile.name
config_name = '%s %s' % (self.name, profile_name)
configfile = self.printer.lookup_object('configfile')
# save the profile to config file
# also save to self.profiles, make sure immutable by making a copy
profiles = dict(self.profiles)
profiles[profile_name] = new_profile = {}
for option, option_type in Profile.PROFILE_OPTIONS.items():
value = getattr(profile, option)
if option_type == float:
value = float(value)
new_profile[option] = value # save to self.profiles
configfile.set(config_name, option,
Helpers.format_float_to_n_decimals(value))
elif option_type == str:
if option == 'z_compensations':
# convert to list of floats
value = [float(x) for x in value]
new_profile[option] = value # save to self.profiles
value_as_str = [Helpers.format_float_to_n_decimals(
x) for x in value] # convert to list of strs
configfile.set(config_name, option, ', '.join(
value_as_str)) # store as comma separated
else:
new_profile[option] = value # save to self.profiles
configfile.set(config_name, option, value)
# inform user to save changes
self.gcode.respond_info(
"X_TWIST_COMPENSATION state has been saved to profile [%s]\n"
"for the current session. The SAVE_CONFIG command will\n"
"update the printer config file and restart the printer."
% (profile_name))
def save_current_profile(self, profile_name):
# get the current profile
profile = self.get_current_profile()
# set the name
profile.name = profile_name
# save the profile
self._save_profile(profile)
cmd_X_TWIST_PROFILE_LOAD_help = \
"Loads a saved mesh as the active mesh"
def cmd_X_TWIST_PROFILE_LOAD(self, gcmd):
# loads a saved mesh as the active mesh
profile_name = gcmd.get('NAME', None)
Helpers.check_non_empty_param(profile_name, gcmd, 'NAME')
self.load_profile(profile_name)
cmd_X_TWIST_PROFILE_CLEAR_help = \
"Clears the active mesh"
def cmd_X_TWIST_PROFILE_CLEAR(self, gcmd):
# clears the active mesh
self.clear_profile()
cmd_X_TWIST_PROFILE_SAVE_help = \
"Saves the active mesh to the config file"
def cmd_X_TWIST_PROFILE_SAVE(self, gcmd):
# saves the active mesh to the config file
profile_name = gcmd.get('NAME', None)
Helpers.check_non_empty_param(profile_name, gcmd, 'NAME')
self.save_current_profile(profile_name)
cmd_X_TWIST_PROFILE_DELETE_help = \
"Deletes a saved profile from the config file"
def cmd_X_TWIST_PROFILE_DELETE(self, gcmd):
# deletes a saved mesh from the config file
profile_name = gcmd.get('NAME', None)
Helpers.check_non_empty_param(profile_name, gcmd, 'NAME')
self.delete_profile(profile_name)
cmd_X_TWIST_PROFILE_help = \
"Prints information on how to use the X_TWIST_PROFILE command"
def cmd_X_TWIST_PROFILE(self, gcmd):
raise self.gcode.error(
"""
Please follow the following syntax:
X_TWIST_PROFILE_LOAD NAME=<PROFILE_NAME>
X_TWIST_PROFILE_SAVE NAME=<PROFILE_NAME>
X_TWIST_PROFILE_DELETE NAME=<PROFILE_NAME>
X_TWIST_PROFILE_CLEAR
"""
)
class Helpers:
@staticmethod
def format_float_to_n_decimals(raw_float, n=6):
# format float to n decimals, defaults to 6
return "{:.{}f}".format(raw_float, n)
@staticmethod
def parse_comma_separated_floats(comma_separated_floats):
# parse comma separated floats into list of floats
return [float(value) for value in comma_separated_floats.split(', ')]
@staticmethod
def check_non_empty_param(param_str, gcmd, param_name=None):
# throws gcmd error if parameter is None or just spaces
if param_str is None or not param_str.strip():
error = "Parameter [%s] is required" % (
param_name) if param_name else "Parameter is required"
raise gcmd.error(error)
# klipper's entry point using [x_twist_compensation] section in printer.cfg
def load_config(config):
return XTwistCompensation(config)

Binary file not shown.