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