mirror of
https://github.com/QIDITECH/klipper.git
synced 2026-01-30 23:48:43 +03:00
Upload Q1_Pro klipper
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -147,6 +147,8 @@ class PrinterConfig:
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
gcode.register_command("SAVE_CONFIG", self.cmd_SAVE_CONFIG,
|
||||
desc=self.cmd_SAVE_CONFIG_help)
|
||||
gcode.register_command("SAVE_CONFIG_QD", self.cmd_SAVE_CONFIG_QD,
|
||||
desc=self.cmd_SAVE_CONFIG_QD_help)
|
||||
def get_printer(self):
|
||||
return self.printer
|
||||
def _read_config_file(self, filename):
|
||||
@@ -408,12 +410,62 @@ class PrinterConfig:
|
||||
try:
|
||||
f = open(temp_name, 'w')
|
||||
f.write(data)
|
||||
f.flush()
|
||||
f.close()
|
||||
os.rename(cfgname, backup_name)
|
||||
os.rename(temp_name, cfgname)
|
||||
os.system("sync")
|
||||
except:
|
||||
msg = "Unable to write config file during SAVE_CONFIG"
|
||||
logging.exception(msg)
|
||||
raise gcode.error(msg)
|
||||
# Request a restart
|
||||
gcode.request_restart('restart')
|
||||
gcode.request_restart('firmware_restart')
|
||||
|
||||
cmd_SAVE_CONFIG_QD_help = "Overwrite config file and restart"
|
||||
def cmd_SAVE_CONFIG_QD(self, gcmd):
|
||||
if not self.autosave.fileconfig.sections():
|
||||
return
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
# Create string containing autosave data
|
||||
autosave_data = self._build_config_string(self.autosave)
|
||||
lines = [('#*# ' + l).strip()
|
||||
for l in autosave_data.split('\n')]
|
||||
lines.insert(0, "\n" + AUTOSAVE_HEADER.rstrip())
|
||||
lines.append("")
|
||||
autosave_data = '\n'.join(lines)
|
||||
# Read in and validate current config file
|
||||
cfgname = self.printer.get_start_args()['config_file']
|
||||
try:
|
||||
data = self._read_config_file(cfgname)
|
||||
regular_data, old_autosave_data = self._find_autosave_data(data)
|
||||
config = self._build_config_wrapper(regular_data, cfgname)
|
||||
except error as e:
|
||||
msg = "Unable to parse existing config on SAVE_CONFIG"
|
||||
logging.exception(msg)
|
||||
raise gcode.error(msg)
|
||||
regular_data = self._strip_duplicates(regular_data, self.autosave)
|
||||
self._disallow_include_conflicts(regular_data, cfgname, gcode)
|
||||
data = regular_data.rstrip() + autosave_data
|
||||
# Determine filenames
|
||||
datestr = time.strftime("-%Y%m%d_%H%M%S")
|
||||
backup_name = cfgname + datestr
|
||||
temp_name = cfgname + "_autosave"
|
||||
if cfgname.endswith(".cfg"):
|
||||
backup_name = cfgname[:-4] + datestr + ".cfg"
|
||||
temp_name = cfgname[:-4] + "_autosave.cfg"
|
||||
# Create new config file with temporary name and swap with main config
|
||||
logging.info("SAVE_CONFIG to '%s' (backup in '%s')",
|
||||
cfgname, backup_name)
|
||||
try:
|
||||
f = open(temp_name, 'w')
|
||||
f.write(data)
|
||||
f.flush()
|
||||
f.close()
|
||||
os.rename(cfgname, backup_name)
|
||||
os.rename(temp_name, cfgname)
|
||||
os.system("sync")
|
||||
except:
|
||||
msg = "Unable to write config file during SAVE_CONFIG"
|
||||
logging.exception(msg)
|
||||
raise gcode.error(msg)
|
||||
|
||||
Binary file not shown.
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)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user