Upload Q1_Pro klipper

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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.

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

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

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Some files were not shown because too many files have changed in this diff Show More