Skip to content

Commit 4e04f02

Browse files
committed
tmc2240: sin/cos phase offset calibration
Coils can be unequal due to factory tolerances This calibration allows us to make the motor symmetric Which will reduce rotor oscillation during rotation Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
1 parent 85ccd1d commit 4e04f02

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

klippy/extras/tmc2240.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,184 @@ def set_current(self, run_current, hold_current, print_time):
340340
val = self.fields.set_field("irun", irun)
341341
self.mcu_tmc.set_register("IHOLD_IRUN", val, print_time)
342342

343+
######################################################################
344+
# TMC2240 Calibrations
345+
######################################################################
346+
347+
class TMC2240PhaseOffset:
348+
_table_256 = {
349+
"mslut0": 1431655765,
350+
"mslut1": 1251289770,
351+
"mslut2": 2299677001,
352+
"mslut3": 67375240,
353+
"mslut4": 4261412864,
354+
"mslut5": 1533918174,
355+
"mslut6": 614804141,
356+
"mslut7": 2105617,
357+
"start_sin": 0,
358+
"start_sin90": 246,
359+
"x1": 153,
360+
"x2": 255,
361+
"x3": 255,
362+
"w0": 2,
363+
"w1": 1,
364+
"w2": 1,
365+
"w3": 1
366+
}
367+
def __init__(self, config, mcu_tmc):
368+
self.printer = config.get_printer()
369+
self.config_name = config.get_name()
370+
self.stepper_name = ' '.join(self.config_name.split()[1:])
371+
self.mcu_tmc = mcu_tmc
372+
self._fields = self.mcu_tmc.get_fields()
373+
self._prev_state = {}
374+
self._drv_fields = ("tpwmthrs", "sg4_filt_en", "sg4_filt_en", "intpol")
375+
self._offset_sin90 = 0
376+
self._threshold = 0xfffff
377+
self._timer = None
378+
self._gcmd = None
379+
self._mslut_changed = False
380+
self._up = 0
381+
self._down = 0
382+
self._equal = 0
383+
gcode = self.printer.lookup_object("gcode")
384+
gcode.register_mux_command("TMC_PHASE_OFFSET_CALIBRATE", "STEPPER",
385+
self.stepper_name,
386+
self.cmd_TMC_CALIBRATE,
387+
desc=self.cmd_TMC_CALIBRATE_help)
388+
def set_field(self, field_name, value):
389+
reg_name = self._fields.lookup_register(field_name)
390+
reg_val = self._fields.set_field(field_name, value)
391+
self.mcu_tmc.set_register(reg_name, reg_val)
392+
393+
def get_field(self, field_name):
394+
return self._fields.get_field(field_name)
395+
def _get_sg4_ind(self):
396+
reg = self.mcu_tmc.get_register("SG4_IND")
397+
ind_a = [
398+
reg & 0xFF, # A Falling
399+
(reg >> 8) & 0xFF, # A Rising
400+
]
401+
ind_b = [
402+
(reg >> 16) & 0xFF, # B Falling
403+
(reg >> 24) & 0xFF # B Rising
404+
]
405+
return sum(ind_a), sum(ind_b)
406+
# Migrate to compressed table
407+
def save_sin_246(self):
408+
cfgname = self.config_name
409+
configfile = self.printer.lookup_object('configfile')
410+
for k in self._table_256:
411+
v = self._table_256[k]
412+
configfile.set(cfgname, 'driver_%s' % k.upper(), v)
413+
def _update_offset_90(self):
414+
degree = round(90 + (90/127 * self._offset_sin90))
415+
self._gcmd.respond_info(
416+
"Test offset: %i ~ %i degree" % (self._offset_sin90, degree))
417+
self.set_field("offset_sin90", self._offset_sin90)
418+
def _calibration_event(self, eventtime):
419+
# Datasheet explains calibration only up to +-17
420+
offset_max = 17
421+
offset_min = -17
422+
423+
tstep = max(1, self.mcu_tmc.get_register("TSTEP"))
424+
# Speed too low
425+
if tstep > self._threshold:
426+
return eventtime + 0.01
427+
428+
ind_a, ind_b = self._get_sg4_ind()
429+
# Adapt the phase offset to match the StallGuard4 results
430+
# phase A (SG4_IND_0+SG4_IND_1) and B (SG4_IND_2+SG4_IND_3)
431+
# If phase A value is > phase B value, increment OFFSET_SIN90,
432+
# otherwise decrement.
433+
# Limited to fit default SIN
434+
if (ind_a - ind_b) > 1:
435+
self._up += 1
436+
elif (ind_b - ind_a) > 1:
437+
self._down += 1
438+
else:
439+
self._equal += 1
440+
# Collect more samples
441+
if self._up + self._down + self._equal < 50:
442+
return eventtime + 0.02
443+
if self._equal > (self._up + self._down):
444+
self._gcmd.respond_info("Done. No more moves required.")
445+
self._gcmd.respond_info("Please, finish the calibration.")
446+
return self.printer.get_reactor().NEVER
447+
if self._up > self._down:
448+
if self._offset_sin90 < offset_max:
449+
self._offset_sin90 += 1
450+
self._update_offset_90()
451+
elif self._up < self._down:
452+
if self._offset_sin90 > offset_min:
453+
self._offset_sin90 -= 1
454+
self._update_offset_90()
455+
self._up = 0
456+
self._down = 0
457+
self._equal = 0
458+
if self._offset_sin90 > 8 or self._offset_sin90 < -8:
459+
if not self._mslut_changed:
460+
self._mslut_changed = True
461+
self._gcmd.respond_info(
462+
"Finish the calibration and powercycle the machine")
463+
return self.printer.get_reactor().NEVER
464+
return eventtime + 0.02
465+
466+
def _start(self, gcmd):
467+
self._gcmd = gcmd
468+
self._offset_sin90 = self.get_field("offset_sin90")
469+
for field in self._drv_fields:
470+
self._prev_state[field] = self.get_field(field)
471+
toolhead = self.printer.lookup_object('toolhead')
472+
fmove = self.printer.lookup_object('force_move')
473+
mcu_stepper = fmove.lookup_stepper(self.stepper_name)
474+
reactor = self.printer.get_reactor()
475+
toolhead.wait_moves()
476+
# Force enable StealthChop
477+
self.set_field("en_pwm_mode", 1)
478+
self.set_field("tpwmthrs", 0)
479+
self.set_field("sg4_filt_en", 1)
480+
self.set_field("intpol", 1)
481+
self._mslut_changed = self.get_field("start_sin90") <= 246
482+
# Minimal speed is 1 RPS
483+
rt_dist, steps_per_rotation = mcu_stepper.get_rotation_distance()
484+
self._threshold = tmc.TMCtstepHelper(self.mcu_tmc, rt_dist,
485+
mcu_stepper)
486+
# Run calibration
487+
self._timer = reactor.register_timer(self._calibration_event,
488+
reactor.NOW)
489+
def _finish(self, gcmd):
490+
toolhead = self.printer.lookup_object('toolhead')
491+
toolhead.wait_moves()
492+
reactor = self.printer.get_reactor()
493+
reactor.unregister_timer(self._timer)
494+
self._timer = None
495+
degree = round(90 + (90/127 * self._offset_sin90))
496+
gcmd.respond_info("New offset: %i ~ %i degree" % (self._offset_sin90,
497+
degree))
498+
for field in self._drv_fields:
499+
val = self._prev_state[field]
500+
self.set_field(field, val)
501+
self._prev_state = {}
502+
if self._mslut_changed and self.get_field("start_sin90") > 246:
503+
self.save_sin_246()
504+
msg = "Use new sin table, require power cycle to apply"
505+
gcmd.respond_info(msg)
506+
configfile = self.printer.lookup_object('configfile')
507+
configfile.set(self.config_name, 'driver_OFFSET_SIN90',
508+
self._offset_sin90)
509+
self._gcmd = None
510+
cmd_TMC_CALIBRATE_help = "Run TMC2240 phase offset calibration"
511+
def cmd_TMC_CALIBRATE(self, gcmd):
512+
msg = """
513+
Driver will be switched to the StealthChop mode during the calibration
514+
Use long moves at axis medium speeds > 1 RPS
515+
"""
516+
if not self._prev_state:
517+
gcmd.respond_info(msg)
518+
self._start(gcmd)
519+
else:
520+
self._finish(gcmd)
343521

344522
######################################################################
345523
# TMC2240 printer object
@@ -363,6 +541,7 @@ def __init__(self, config):
363541
current_helper = TMC2240CurrentHelper(config, self.mcu_tmc)
364542
cmdhelper = tmc.TMCCommandHelper(config, self.mcu_tmc, current_helper)
365543
cmdhelper.setup_register_dump(ReadRegisters)
544+
self.calibraion = TMC2240PhaseOffset(config, self.mcu_tmc)
366545
self.get_phase_offset = cmdhelper.get_phase_offset
367546
self.get_status = cmdhelper.get_status
368547
# Setup basic register values

0 commit comments

Comments
 (0)