From e96b71a9a3bffba2339c4b5df1ae6abfde964810 Mon Sep 17 00:00:00 2001
From: Florian Heilmann <Florian.Heilmann@gmx.net>
Date: Sat, 2 Mar 2019 00:41:00 +0000
Subject: [PATCH] tmc2660: Use helper functions for register access and
 simplify TMC2660 parameters

sense_resistor is now a mandatory parameter for tmc2660 blocks. Duet2 users should use a value of 0.051

Signed-off-by: Florian Heilmann <Florian.Heilmann@gmx.net>
---
 config/example-extras.cfg |  55 +++---
 config/generic-duet2.cfg  |  10 ++
 klippy/extras/tmc2660.py  | 367 ++++++++++++++++++--------------------
 3 files changed, 208 insertions(+), 224 deletions(-)

diff --git a/config/example-extras.cfg b/config/example-extras.cfg
index e81e05d18..a686e80ff 100644
--- a/config/example-extras.cfg
+++ b/config/example-extras.cfg
@@ -1050,9 +1050,9 @@
 #run_current:
 #   The amount of current (in ampere) used by the driver during stepper
 #   movement. This parameter must be provided.
-#sense_resistor: 0.051
-#   The resistance (in ohms) of the motor sense resistor. The default
-#   is 0.051 ohms.
+#sense_resistor:
+#   The resistance (in ohms) of the motor sense resistor. This parameter
+#   must be provided.
 #idle_current_percent: 100
 #   The percentage of the run_current the stepper driver will be
 #   lowered to when the idle timeout expires (you need to set up the
@@ -1062,42 +1062,35 @@
 #   their position. There is also small delay until the  current is
 #   raised again, so take this into account when commanding fast moves
 #   while the stepper is idling. The default is 100 (no reduction).
-#driver_DEDGE: False
-#driver_TBL: 36
-#   Valid values are 16, 24, 36, 54.
-#driver_CHM: spreadcycle
-#   Valid values are 'spreadcycle' and 'constant_toff'
-#driver_RNDTF: False
-#driver_HDEC: False
-#driver_HEND: 7 if spreadcycle is used, 3 otherwise
-#driver_HSTRT: 5 if spreadcycle is used, 4 otherwise
-#driver_TOFF: 7 if spreadcycle is used, 4 otherwise
-#driver_SEIMIN: half
-#   Valid values are 'quarter' and 'half'.
-#driver_SEDN: 32
-#   Valid values are 1, 2, 8, 32.
+#driver_DEDGE: 0
+#driver_TBL: 2
+#driver_RNDTF: 0
+#driver_HDEC: 0
+#driver_CHM: 0
+#driver_HEND: 6
+#driver_HSTRT: 3
+#driver_TOFF: 4
+#driver_SEIMIN: 0
+#driver_SEDN: 0
 #driver_SEMAX: 0
-#driver_SEUP: 1
-#   Valid values are 1, 2, 4, 8.
+#driver_SEUP: 0
 #driver_SEMIN: 0
-#driver_SFILT: True
-#driver_SGT: -64
-#driver_SLPH: min
-#   Valid values are 'min', 'min_tc', 'med_tc', 'max'
-#driver_SLPL: min
-#   Valid values are 'min', 'med', 'max'
-#driver_DISS2G: False
-#driver_TS2G: 0.8
-#   Valid values are 0.8, 1.2, 1.6, 3.2.
-#driver_VSENSE: high
-#   Valid values are 'high' and 'low'
+#driver_SFILT: 1
+#driver_SGT: 0
+#driver_SLPH: 0
+#driver_SLPL: 0
+#driver_DISS2G: 0
+#driver_TS2G: 3
+#driver_VSENSE: 1
 #
 #   Set the given parameter during the configuration of the TMC2660
 #   chip. This may be used to set custom driver parameters. The
 #   defaults for each parameter are next to the parameter name in the
 #   list above. See the TMC2660 datasheet about what each parameter
 #   does and what the restrictions on parameter combinations are.
-
+#   Be especially aware of the CHOPCONF register, where setting CHM to
+#   either 0 or one will lead to layout changes (the first bit of HDEC)
+#   is interpreted as the MSB of HSTRT in this case).
 
 # Homing override. One may use this mechanism to run a series of
 # g-code commands in place of a G28 found in the normal g-code input.
diff --git a/config/generic-duet2.cfg b/config/generic-duet2.cfg
index 9a290e5f3..163575ebc 100644
--- a/config/generic-duet2.cfg
+++ b/config/generic-duet2.cfg
@@ -98,6 +98,7 @@ spi_bus: 1 # All TMC2660 drivers are connected to USART1, which is bus 1 on the
 microsteps: 16
 interpolate: True # 1/16 micro-steps interpolated to 1/256
 run_current: 1.000
+sense_resistor: 0.051
 idle_current_percent: 20
 
 [stepper_y]
@@ -115,6 +116,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 idle_current_percent: 20
 
 [stepper_z]
@@ -132,6 +134,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 
 #On drive E4
 [stepper_z1]
@@ -146,6 +149,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 
 #On drive E5
 [stepper_z2]
@@ -160,6 +164,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 
 #On drive E6
 [stepper_z3]
@@ -174,6 +179,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 
 #On drive E0
 [extruder0]
@@ -199,6 +205,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 
 #On drive E1
 [extruder1]
@@ -224,6 +231,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 
 # On drive E2
 [extruder2]
@@ -249,6 +257,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 
 # On drive E3
 [extruder3]
@@ -274,6 +283,7 @@ spi_bus: 1
 microsteps: 16
 interpolate: True
 run_current: 1.000
+sense_resistor: 0.051
 
 [heater_bed]
 heater_pin: !PA19
diff --git a/klippy/extras/tmc2660.py b/klippy/extras/tmc2660.py
index 393b54434..746c30642 100644
--- a/klippy/extras/tmc2660.py
+++ b/klippy/extras/tmc2660.py
@@ -3,77 +3,122 @@
 # Copyright (C) 2018-2019  Florian Heilmann <Florian.Heilmann@gmx.net>
 #
 # This file may be distributed under the terms of the GNU GPLv3 license.
-import math
-import bus
-
-REG_DRVCONF = 0xe << 16
-REG_SGCSCONF = 0xc  << 16
-REG_SMARTEN = 0xa  << 16
-REG_CHOPCONF = 0x8  << 16
-REG_DRVCTRL = 0x0  << 16
-
-def get_bits(reg, field, value):
-    return ((value << reg[field][0]) & reg[field][1])
-
-def read_field(bits, reg, field):
-    return (bits & reg[field][1]) >> reg[field][0]
+import math, collections
+import bus, tmc2130
 
 def current_to_reg(current, sense_resistor, vsense_on):
     vsense = 0.165 if vsense_on else 0.310
     cs = int(32 * current * sense_resistor * math.sqrt(2.) / vsense - 1. + .5)
-    return max(0, min(31, cs))
+    return max(0, min(cs, 31))
 
-DRVCTRL = {
-    "INTPOL" : (9, 0x00100),
-    "DEDGE"  : (8, 0x00080),
-    "MRES"   : (0, 0x0000F)
+Registers = {
+    "DRVCONF": 0xE, "SGCSCONF": 0xC, "SMARTEN": 0xA,
+    "CHOPCONF": 0x8, "DRVCTRL": 0x0
 }
 
-CHOPCONF = {
-    "TBL"   : (15, 0x18000),
-    "CHM"   : (14, 0x04000),
-    "RNDTF" : (13, 0x02000),
-    "HDEC"  : (11, 0x01800),
-    "HEND"  : (7,  0x00780),
-    "HSTRT" : (4,  0x00070),
-    "TOFF"  : (0,  0x0000F)
+Fields = {}
+
+Fields["DRVCTRL"] = {
+    "MRES": 0x0f,
+    "DEDGE": 0x01 << 8,
+    "INTPOL": 0x01 << 9,
 }
 
-SMARTEN = {
-    "SEIMIN" : (15, 0x08000),
-    "SEDN"   : (14, 0x06000),
-    "SEMAX"  : (8,  0x00F00),
-    "SEUP"   : (5,  0x00060),
-    "SEMIN"  : (0,  0x0000F)
+Fields["CHOPCONF"] = {
+    "TOFF": 0x0f,
+    "HSTRT": 0x7 << 4,
+    "HEND": 0x0f << 7,
+    "HDEC": 0x03 << 11,
+    "RNDTF": 0x01 << 13,
+    "CHM": 0x01 << 14,
+    "TBL": 0x03 << 15
 }
 
-SGCSCONF = {
-    "SFILT"  : (16, 0x10000),
-    "SGT"    : (8,  0x07800),
-    "CS"     : (0,  0x0001F)
+Fields["SMARTEN"] = {
+    "SEMIN" : 0x0f,
+    "SEUP": 0x03 << 5,
+    "SEMAX": 0x0f << 8,
+    "SEDN": 0x03 << 13,
+    "SEIMIN": 0x01 << 15
 }
 
-DRVCONF = {
-    "TST"    : (16, 0x10000),
-    "SLPH"   : (14, 0x0C000),
-    "SLPL"   : (12, 0x03000),
-    "DISS2G" : (10, 0x00400),
-    "TS2G"   : (8,  0x00300),
-    "SDOFF"  : (7,  0x00080),
-    "VSENSE" : (6,  0x00040),
-    "RDSEL"  : (4,  0x00030)
+Fields["SGCSCONF"] = {
+    "CS": 0x1f,
+    "SGT": 0x7F << 8,
+    "SFILT": 0x01 << 16
 }
 
-READRSP = {
-    "MSTEP" : (10, 0xFFC00),
-    "STST"  : (7,  0x00080),
-    "OLB"   : (6,  0x00040),
-    "OLA"   : (5,  0x00020),
-    "S2GB"  : (4,  0x00010),
-    "S2GA"  : (3,  0x00008),
-    "OTPW"  : (2,  0x00004),
-    "OT"    : (1,  0x00002),
-    "SG"    : (0,  0x00001)
+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"] = {
+    "SG": 0x01,
+    "OT": 0x01 << 1,
+    "OTPW": 0x01 << 2,
+    "S2GA": 0x01 << 3,
+    "S2GB": 0x01 << 4,
+    "OLA": 0x01 << 5,
+    "OLB": 0x01 << 6,
+    "STST": 0x01 << 7,
+    "MSTEP": 0x3ff << 10
+}
+
+Fields["READRSP@RDSEL1"] = {
+    "SG": 0x01,
+    "OT": 0x01 << 1,
+    "OTPW": 0x01 << 2,
+    "S2GA": 0x01 << 3,
+    "S2GB": 0x01 << 4,
+    "OLA": 0x01 << 5,
+    "OLB": 0x01 << 6,
+    "STST": 0x01 << 7,
+    "SG@RDSEL1": 0x3ff << 10
+}
+
+Fields["READRSP@RDSEL2"] = {
+    "SG": 0x01,
+    "OT": 0x01 << 1,
+    "OTPW": 0x01 << 2,
+    "S2GA": 0x01 << 3,
+    "S2GB": 0x01 << 4,
+    "OLA": 0x01 << 5,
+    "OLB": 0x01 << 6,
+    "STST": 0x01 << 7,
+    "SG@RDSEL2": 0x1f << 15,
+    "SE": 0x1f << 10
+}
+
+FieldFormatters = {
+    "MRES": (lambda v: "%d(%dusteps)" % (v, 0x100 >> v)),
+    "DEDGE": (lambda v:
+        "1(Both Edges Active)" if v else "0(Only Rising Edge active)"),
+    "INTPOL": (lambda v: "1(On)" if v else "0(Off)"),
+    "TOFF": (lambda v: ("%d" % v) if v else "0(Driver Disabled!)"),
+    "CHM": (lambda v: "1(constant toff)" if v else "0(spreadCycle)"),
+    "SGT": (lambda v: "%d" % (v)),
+    "SFILT": (lambda v: "1(Filtered mode)" if v else "0(Standard mode)"),
+    "VSENSE": (lambda v: "%d(%dmV)" % (v, 165 if v else 305)),
+    "SDOFF": (lambda v: "1(Step/Dir disabled" if v else "0(Step/dir enabled)"),
+    "DISS2G": (lambda v: "%d(Short to GND protection %s)" % (v,
+                                          "disabled" if v else "enabled")),
+    "MSTEP": (lambda v: "%d(%d, OA1 %s OA2)" % (v, v & 0xff,
+                                                "<=" if v & 0x100 else "=>")),
+    "SG": (lambda v: "%d(%s)" % (v, "Stall!" if v else "No Stall!")),
+    "OT": (lambda v: "1(Overtemp shutdown!)" if v else ""),
+    "OTPW": (lambda v: "1(Overtemp warning!)" if v else ""),
+    "S2GA": (lambda v: "1(Short to GND Coil A!)" if v else ""),
+    "S2GB": (lambda v: "1(Short to GND Coil B!)" if v else ""),
+    "OLA": (lambda v: "1(Open Load Coil A at slow speed!)" if v else ""),
+    "OLB": (lambda v: "1(Open Load Coil B at slow speed!)" if v else ""),
+    "STST": (lambda v: "1(Standstill detected!)" if v else ""),
 }
 
 class TMC2660:
@@ -90,121 +135,59 @@ class TMC2660:
             "DUMP_TMC", "STEPPER", self.name,
             self.cmd_DUMP_TMC, desc=self.cmd_DUMP_TMC_help)
         # Setup driver registers
+        self.regs = collections.OrderedDict()
+        self.fields = tmc2130.FieldHelper(Fields, FieldFormatters, self.regs)
+        set_config_field = self.fields.set_config_field
+
         # DRVCTRL
         steps = {'256': 0, '128': 1, '64': 2, '32': 3, '16': 4,
                  '8': 5, '4': 6, '2': 7, '1': 8}
         self.driver_mres = config.getchoice('microsteps', steps)
-        self.driver_intpol = config.getboolean('interpolate', default=True)
-        self.current = config.getfloat('run_current',  minval=0.1,
-                                       maxval=2.4)
-        self.driver_dedge = config.getboolean('driver_DEDGE', default=False)
-
+        self.fields.set_field("MRES", self.driver_mres)
+        set_config_field(config, "DEDGE", 0)
+        set_config_field(config, "INTPOL", True, 'interpolate')
         # CHOPCONF
-        btime = {'16': 0, '24': 1, '36': 2, '54': 3}
-        self.driver_tbl = config.getchoice('driver_TBL', btime, default='36')
-        chm = {'spreadcycle': 0, 'constant_toff': 1}
-        self.driver_chm = config.getchoice('driver_CHM', chm,
-                                           default='spreadcycle')
-        self.driver_rndtf = config.getboolean('driver_RNDTF', False)
-        if self.driver_chm:
-            self.driver_hend = config.getint('driver_HEND', default=7,
-                                             minval=-3, maxval=12) + 3
-            self.driver_hstrt = config.getint('driver_HSTRT', default=5,
-                                              minval=0, maxval=15)
-            self.driver_toff = config.getint('driver_TOFF', default=7,
-                                             minval=0, maxval=15)
-            # if chm is 1, HDEC1 is the MSB of HSTRT
-            self.driver_hdec = (config.getboolean('driver_HDEC', default=False)
-                                | ((self.driver_hstrt & 0x8) >> 1))
-        else:
-            self.driver_hdec = config.getboolean('driver_HDEC', default=False)
-            self.driver_hend = config.getint('driver_HEND', default=3,
-                                             minval=-3, maxval=12) + 3
-            self.driver_hstrt = config.getint('driver_HSTRT', default=4,
-                                              minval=1, maxval=8) - 1
-            self.driver_toff = config.getint('driver_TOFF', default=4,
-                                             minval=0, maxval=15)
-            if self.driver_hstrt + self.driver_hend > 15:
-                raise config.error("driver_HEND + HSTRT must be <= 15")
-
+        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", 6)
+        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
-        csc = {'quarter': 1, 'half': 0}
-        self.driver_seimin = config.getchoice('driver_SEIMIN', csc,
-                                              default='half')
-        cds = {'32': 0, '8': 1, '2': 2, '1': 3}
-        self.driver_sedn = config.getchoice('driver_SEDN', cds, default='32')
-        self.driver_semax = config.getint('driver_SEMAX', default=0,
-                                          minval=0, maxval=15)
-        cis = {'1': 0, '2': 1, '4': 2, '8': 3}
-        self.driver_seup = config.getchoice('driver_SEUP', cis, default='1')
-        self.driver_semin = config.getint('driver_SEMIN', default=0,
-                                          minval=0, maxval=15)
+        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)
 
         # DRVCONF
-        slph = {'min': 0, 'min_tc': 1, 'med_tc': 2, 'max': 3}
-        self.driver_slph = config.getchoice('driver_SLPH', slph, default='min')
-        slpl = {'min': 0, 'med': 2, 'max': 3}
-        self.driver_slpl = config.getchoice('driver_SLPL', slpl, default='min')
-        self.driver_diss2g = config.getboolean('driver_DISS2G', default=False)
-        ts2g = {'3.2': 0, '1.6': 1, '1.2': 2, '0.8': 3}
-        self.driver_ts2g = config.getchoice('driver_TS2G', ts2g, default='0.8')
-        # since we don't support SPI mode yet, this has to be False
-        self.driver_sdoff = False
-        vsense = {'low': 0, 'high': 1}
-        self.driver_vsense = config.getchoice('driver_VSENSE', vsense,
-                                              default='high')
-        self.sense_resistor = config.getfloat('sense_resistor', default=0.051)
-        self.driver_rdsel = 0  # Microsteps (used by endstop phase)
+        set_config_field(config, "SLPH", 0)
+        set_config_field(config, "SLPL", 0)
+        set_config_field(config, "DISS2G", 0)
+        set_config_field(config, "TS2G", 3)
+        set_config_field(config, "VSENSE", 1)
+        self.fields.set_field("RDSEL", 0) # needed for phase calculations
+        self.fields.set_field("SDOFF", 0) # only step/dir mode supported
+        self.sense_resistor = config.getfloat('sense_resistor')
 
         # SGSCONF
-        self.driver_sfilt = config.getboolean('driver_SFILT', default=True)
-        self.driver_sgt = config.getint('driver_sgt', default=-64,
-                                        minval=-64, maxval=63) + 64
-        self.driver_cs = current_to_reg(self.current, self.sense_resistor, self.driver_vsense)
+        set_config_field(config, "SFILT", 1)
+        set_config_field(config, "SGT", 0)
+        self.current = config.getfloat('run_current',  minval=0.1,
+                                       maxval=2.4)
+        self.driver_cs = current_to_reg(self.current,
+        self.sense_resistor, self.fields.get_field("VSENSE"))
+        self.fields.set_field("CS", self.driver_cs)
 
-        # Build and send registers
-        self.reg_drvconf =  REG_DRVCONF | \
-                            get_bits(DRVCONF, "TST", 0) | \
-                            get_bits(DRVCONF, "SLPH", self.driver_slph) | \
-                            get_bits(DRVCONF, "SLPL", self.driver_slpl) | \
-                            get_bits(DRVCONF, "DISS2G", self.driver_diss2g) | \
-                            get_bits(DRVCONF, "TS2G", self.driver_ts2g) | \
-                            get_bits(DRVCONF, "SDOFF", self.driver_sdoff) | \
-                            get_bits(DRVCONF, "VSENSE", self.driver_vsense) | \
-                            get_bits(DRVCONF, "RDSEL", self.driver_rdsel)
-        self.add_config_cmd(self.reg_drvconf)
+        # Init Registers
+        self._init_registers(self)
 
-        self.reg_drvctrl = REG_DRVCTRL | \
-                           get_bits(DRVCTRL, "INTPOL", self.driver_intpol) | \
-                           get_bits(DRVCTRL, "DEDGE", self.driver_dedge) | \
-                           get_bits(DRVCTRL, "MRES", self.driver_mres)
-        self.add_config_cmd(self.reg_drvctrl)
-
-        self.reg_chopconf = REG_CHOPCONF | \
-                            get_bits(CHOPCONF, "TBL", self.driver_tbl) | \
-                            get_bits(CHOPCONF, "CHM", self.driver_chm) | \
-                            get_bits(CHOPCONF, "RNDTF", self.driver_rndtf) | \
-                            get_bits(CHOPCONF, "HDEC", self.driver_hdec) | \
-                            get_bits(CHOPCONF, "HEND", self.driver_hend) | \
-                            get_bits(CHOPCONF, "HSTRT", self.driver_hstrt) | \
-                            get_bits(CHOPCONF, "TOFF", self.driver_toff)
-        self.add_config_cmd(self.reg_chopconf)
-
-        self.reg_sgcsconf = REG_SGCSCONF | \
-                            get_bits(SGCSCONF, "SFILT", self.driver_sfilt) | \
-                            get_bits(SGCSCONF, "SGT", self.driver_sgt) | \
-                            get_bits(SGCSCONF, "CS", self.driver_cs)
-        self.add_config_cmd(self.reg_sgcsconf)
-
-        self.reg_smarten = REG_SMARTEN | \
-                           get_bits(SMARTEN, "SEIMIN", self.driver_seimin) | \
-                           get_bits(SMARTEN, "SEDN", self.driver_sedn) | \
-                           get_bits(SMARTEN, "SEMAX", self.driver_semax) | \
-                           get_bits(SMARTEN, "SEUP", self.driver_seup) | \
-                           get_bits(SMARTEN, "SEMIN", self.driver_semin)
-        self.add_config_cmd(self.reg_smarten)
-
-        # Idle timeout
+        # 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:
@@ -213,42 +196,45 @@ class TMC2660:
             self.printer.register_event_handler("idle_timeout:ready",
                                                 self.handle_ready)
 
-    def add_config_cmd(self, val):
-        data = [(val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff]
-        self.spi.spi_send(data)
+    def _init_registers(self, min_clock=0):
+        for reg_name in Registers:
+            self.set_register(reg_name, self.regs[reg_name])
+
+    def set_register(self, reg_name, val, min_clock=0):
+        reg = Registers[reg_name]
+        self.spi.spi_send([((val >> 16) | reg) & 0xff,
+                            (val >> 8) & 0xff, val & 0xff], min_clock)
+
+    def get_response(self):
+        reg = Registers["DRVCTRL"]
+        val = self.regs["DRVCTRL"]
+        params = self.spi.spi_transfer([((val >> 16) | reg) & 0xff,
+                            (val >> 8) & 0xff, val & 0xff])
+        pr = bytearray(params['response'])
+        return (pr[0] << 16) | (pr[1] << 8) | pr[2]
 
     def get_microsteps(self):
-        return 256 >> self.driver_mres
+        return 256 >> self.fields.get_field("MRES")
 
     def get_phase(self):
-        # Send DRVCTRL to get a response
-        reg_data = [(self.reg_drvctrl >> 16) & 0xff,
-                    (self.reg_drvctrl >> 8) & 0xff, self.reg_drvctrl & 0xff]
-        params = self.spi.spi_transfer(reg_data)
-        pr = bytearray(params['response'])
-        steps = (((pr[0] << 16) | (pr[1] << 8) | pr[2])
-                 & READRSP['MSTEP'][1]) >> READRSP['MSTEP'][0]
-        return steps >> self.driver_mres
+        mscnt =  self.fields.get_field("MSTEP", self.get_response())
+        return mscnt >> self.driver_mres
 
     def handle_printing(self, print_time):
-        self.set_current(print_time, self.current)
+        self.set_current(0., self.current) # workaround
 
     def handle_ready(self, print_time):
         self.set_current(print_time, (float(self.idle_current_percentage)
                                       * self.current / 100))
 
     def set_current(self, print_time, current):
-        self.driver_cs = current_to_reg(self.current, self.sense_resistor, self.driver_vsense)
-        reg = self.reg_sgcsconf
-        reg &= ~(SGCSCONF["CS"][1])
-        reg |= get_bits(SGCSCONF, "CS", self.driver_cs)
-        reg_data = [(reg >> 16) & 0xff, (reg >> 8) & 0xff, reg & 0xff]
+        self.driver_cs = current_to_reg(current, self.sense_resistor,
+            self.fields.get_field("VSENSE"))
+        self.fields.set_field("CS", self.driver_cs)
         clock = self.spi.get_mcu().print_time_to_clock(print_time)
-        self.spi.spi_send(reg_data, minclock=clock, reqclock=clock)
+        self.set_register("SGCSCONF", self.regs["SGCSCONF"], min_clock=clock)
 
-    cmd_SET_TMC_CURRENT_help = (
-        "Set the current of a TMC2660 driver (between %d and %d)" % (
-            0.1, 2.4))
+    cmd_SET_TMC_CURRENT_help = "Set the current of a TMC2660 driver"
     def cmd_SET_TMC_CURRENT(self, params):
         gcode = self.printer.lookup_object('gcode')
         if 'CURRENT' in params:
@@ -262,20 +248,15 @@ class TMC2660:
     def cmd_DUMP_TMC(self, params):
         self.printer.lookup_object('toolhead').get_last_move_time()
         gcode = self.printer.lookup_object('gcode')
-        for reg_name , val in zip(["DRVCONF", "DRVCTRL", "CHOPCONF",
-                                   "SGCSCONF", "SMARTEN"],
-                            [self.reg_drvconf, self.reg_drvctrl,
-                             self.reg_chopconf, self.reg_sgcsconf,
-                             self.reg_smarten]):
-            msg = "%-15s %08x" % (reg_name + " (cached):", val)
+        gcode.respond_info("========== Write-only registers ==========")
+        for reg_name in Registers:
+            msg = self.fields.pretty_format(reg_name, self.regs[reg_name])
             gcode.respond_info(msg)
+
         # Send one register to get the return data
-        reg_data = [(self.reg_drvctrl >> 16) & 0xff,
-                    (self.reg_drvctrl >> 8) & 0xff, self.reg_drvctrl & 0xff]
-        params = self.spi.spi_transfer(reg_data)
-        pr = bytearray(params['response'])
-        msg = "%-15s %08x" % (
-            "RESPONSE:", ((pr[0] << 16) | (pr[1] << 8) | pr[2]))
+        gcode.respond_info("========== Queried registers ==========")
+        return_format = "READRSP@RDSEL" + str(self.fields.get_field("RDSEL"))
+        msg = self.fields.pretty_format(return_format, self.get_response())
         gcode.respond_info(msg)
 
 def load_config_prefix(config):