mirror of
https://github.com/QIDITECH/klipper.git
synced 2026-02-01 16:38:41 +03:00
Upload Q1_Pro klipper
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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.
52
klippy/extras/chamber_fan.py
Normal file
52
klippy/extras/chamber_fan.py
Normal 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.
Binary file not shown.
@@ -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.
Binary file not shown.
@@ -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.
15
klippy/extras/gcode_macro_break.py
Normal file
15
klippy/extras/gcode_macro_break.py
Normal 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.
87
klippy/extras/gcode_shell_command.py
Normal file
87
klippy/extras/gcode_shell_command.py
Normal 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)
|
||||
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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
136
klippy/extras/qdprobe.py
Normal 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.
@@ -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)
|
||||
|
||||
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
@@ -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.
@@ -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.
Binary file not shown.
Reference in New Issue
Block a user