aboutsummaryrefslogtreecommitdiffstats
path: root/klippy/extras/resonance_tester.py
diff options
context:
space:
mode:
Diffstat (limited to 'klippy/extras/resonance_tester.py')
-rw-r--r--klippy/extras/resonance_tester.py167
1 files changed, 121 insertions, 46 deletions
diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py
index e9d4e9d9..76e56f53 100644
--- a/klippy/extras/resonance_tester.py
+++ b/klippy/extras/resonance_tester.py
@@ -45,42 +45,96 @@ def _parse_axis(gcmd, raw_axis):
"Unable to parse axis direction '%s'" % (raw_axis,))
return TestAxis(vib_dir=(dir_x, dir_y))
-class VibrationPulseTest:
+class VibrationPulseTestGenerator:
def __init__(self, config):
- self.printer = config.get_printer()
- self.gcode = self.printer.lookup_object('gcode')
self.min_freq = config.getfloat('min_freq', 5., minval=1.)
- # Defaults are such that max_freq * accel_per_hz == 10000 (max_accel)
- self.max_freq = config.getfloat('max_freq', 10000. / 75.,
+ self.max_freq = config.getfloat('max_freq', 135.,
minval=self.min_freq, maxval=300.)
- self.accel_per_hz = config.getfloat('accel_per_hz', 75., above=0.)
+ self.accel_per_hz = config.getfloat('accel_per_hz', 60., above=0.)
self.hz_per_sec = config.getfloat('hz_per_sec', 1.,
minval=0.1, maxval=2.)
-
- self.probe_points = config.getlists('probe_points', seps=(',', '\n'),
- parser=float, count=3)
- def get_start_test_points(self):
- return self.probe_points
def prepare_test(self, gcmd):
self.freq_start = gcmd.get_float("FREQ_START", self.min_freq, minval=1.)
self.freq_end = gcmd.get_float("FREQ_END", self.max_freq,
minval=self.freq_start, maxval=300.)
- self.accel_per_hz = gcmd.get_float("ACCEL_PER_HZ",
- self.accel_per_hz, above=0.)
- self.hz_per_sec = gcmd.get_float("HZ_PER_SEC", self.hz_per_sec,
- above=0., maxval=2.)
- def run_test(self, axis, gcmd):
+ self.test_accel_per_hz = gcmd.get_float("ACCEL_PER_HZ",
+ self.accel_per_hz, above=0.)
+ self.test_hz_per_sec = gcmd.get_float("HZ_PER_SEC", self.hz_per_sec,
+ above=0., maxval=2.)
+ def gen_test(self):
+ freq = self.freq_start
+ res = []
+ sign = 1.
+ time = 0.
+ while freq <= self.freq_end + 0.000001:
+ t_seg = .25 / freq
+ accel = self.test_accel_per_hz * freq
+ time += t_seg
+ res.append((time, sign * accel, freq))
+ time += t_seg
+ res.append((time, -sign * accel, freq))
+ freq += 2. * t_seg * self.test_hz_per_sec
+ sign = -sign
+ return res
+ def get_max_freq(self):
+ return self.freq_end
+
+class SweepingVibrationsTestGenerator:
+ def __init__(self, config):
+ self.vibration_generator = VibrationPulseTestGenerator(config)
+ self.sweeping_accel = config.getfloat('sweeping_accel', 400., above=0.)
+ self.sweeping_period = config.getfloat('sweeping_period', 1.2,
+ minval=0.)
+ def prepare_test(self, gcmd):
+ self.vibration_generator.prepare_test(gcmd)
+ self.test_sweeping_accel = gcmd.get_float(
+ "SWEEPING_ACCEL", self.sweeping_accel, above=0.)
+ self.test_sweeping_period = gcmd.get_float(
+ "SWEEPING_PERIOD", self.sweeping_period, minval=0.)
+ def gen_test(self):
+ test_seq = self.vibration_generator.gen_test()
+ accel_fraction = math.sqrt(2.0) * 0.125
+ if self.test_sweeping_period:
+ t_rem = self.test_sweeping_period * accel_fraction
+ sweeping_accel = self.test_sweeping_accel
+ else:
+ t_rem = float('inf')
+ sweeping_accel = 0.
+ res = []
+ last_t = 0.
+ sig = 1.
+ accel_fraction += 0.25
+ for next_t, accel, freq in test_seq:
+ t_seg = next_t - last_t
+ while t_rem <= t_seg:
+ last_t += t_rem
+ res.append((last_t, accel + sweeping_accel * sig, freq))
+ t_seg -= t_rem
+ t_rem = self.test_sweeping_period * accel_fraction
+ accel_fraction = 0.5
+ sig = -sig
+ t_rem -= t_seg
+ res.append((next_t, accel + sweeping_accel * sig, freq))
+ last_t = next_t
+ return res
+ def get_max_freq(self):
+ return self.vibration_generator.get_max_freq()
+
+class ResonanceTestExecutor:
+ def __init__(self, config):
+ self.printer = config.get_printer()
+ self.gcode = self.printer.lookup_object('gcode')
+ def run_test(self, test_seq, axis, gcmd):
+ reactor = self.printer.get_reactor()
toolhead = self.printer.lookup_object('toolhead')
X, Y, Z, E = toolhead.get_position()
- sign = 1.
- freq = self.freq_start
# Override maximum acceleration and acceleration to
# deceleration based on the maximum test frequency
- systime = self.printer.get_reactor().monotonic()
+ systime = reactor.monotonic()
toolhead_info = toolhead.get_status(systime)
old_max_accel = toolhead_info['max_accel']
old_minimum_cruise_ratio = toolhead_info['minimum_cruise_ratio']
- max_accel = self.freq_end * self.accel_per_hz
+ max_accel = max([abs(a) for _, a, _ in test_seq])
self.gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=0"
% (max_accel,))
@@ -90,24 +144,46 @@ class VibrationPulseTest:
gcmd.respond_info("Disabled [input_shaper] for resonance testing")
else:
input_shaper = None
- gcmd.respond_info("Testing frequency %.0f Hz" % (freq,))
- while freq <= self.freq_end + 0.000001:
- t_seg = .25 / freq
- accel = self.accel_per_hz * freq
- max_v = accel * t_seg
+ last_v = last_t = last_accel = last_freq = 0.
+ for next_t, accel, freq in test_seq:
+ t_seg = next_t - last_t
toolhead.cmd_M204(self.gcode.create_gcode_command(
- "M204", "M204", {"S": accel}))
- L = .5 * accel * t_seg**2
- dX, dY = axis.get_point(L)
- nX = X + sign * dX
- nY = Y + sign * dY
- toolhead.move([nX, nY, Z, E], max_v)
- toolhead.move([X, Y, Z, E], max_v)
- sign = -sign
- old_freq = freq
- freq += 2. * t_seg * self.hz_per_sec
- if math.floor(freq) > math.floor(old_freq):
+ "M204", "M204", {"S": abs(accel)}))
+ v = last_v + accel * t_seg
+ abs_v = abs(v)
+ if abs_v < 0.000001:
+ v = abs_v = 0.
+ abs_last_v = abs(last_v)
+ v2 = v * v
+ last_v2 = last_v * last_v
+ half_inv_accel = .5 / accel
+ d = (v2 - last_v2) * half_inv_accel
+ dX, dY = axis.get_point(d)
+ nX = X + dX
+ nY = Y + dY
+ toolhead.limit_next_junction_speed(abs_last_v)
+ if v * last_v < 0:
+ # The move first goes to a complete stop, then changes direction
+ d_decel = -last_v2 * half_inv_accel
+ decel_X, decel_Y = axis.get_point(d_decel)
+ toolhead.move([X + decel_X, Y + decel_Y, Z, E], abs_last_v)
+ toolhead.move([nX, nY, Z, E], abs_v)
+ else:
+ toolhead.move([nX, nY, Z, E], max(abs_v, abs_last_v))
+ if math.floor(freq) > math.floor(last_freq):
gcmd.respond_info("Testing frequency %.0f Hz" % (freq,))
+ reactor.pause(reactor.monotonic() + 0.01)
+ X, Y = nX, nY
+ last_t = next_t
+ last_v = v
+ last_accel = accel
+ last_freq = freq
+ if last_v:
+ d_decel = -.5 * last_v2 / old_max_accel
+ decel_X, decel_Y = axis.get_point(d_decel)
+ toolhead.cmd_M204(self.gcode.create_gcode_command(
+ "M204", "M204", {"S": old_max_accel}))
+ toolhead.move([X + decel_X, Y + decel_Y, Z, E], abs(last_v))
# Restore the original acceleration values
self.gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=%.3f"
@@ -116,14 +192,13 @@ class VibrationPulseTest:
if input_shaper is not None:
input_shaper.enable_shaping()
gcmd.respond_info("Re-enabled [input_shaper]")
- def get_max_freq(self):
- return self.freq_end
class ResonanceTester:
def __init__(self, config):
self.printer = config.get_printer()
self.move_speed = config.getfloat('move_speed', 50., above=0.)
- self.test = VibrationPulseTest(config)
+ self.generator = SweepingVibrationsTestGenerator(config)
+ self.executor = ResonanceTestExecutor(config)
if not config.get('accel_chip_x', None):
self.accel_chip_names = [('xy', config.get('accel_chip').strip())]
else:
@@ -133,6 +208,8 @@ class ResonanceTester:
if self.accel_chip_names[0][1] == self.accel_chip_names[1][1]:
self.accel_chip_names = [('xy', self.accel_chip_names[0][1])]
self.max_smoothing = config.getfloat('max_smoothing', None, minval=0.05)
+ self.probe_points = config.getlists('probe_points', seps=(',', '\n'),
+ parser=float, count=3)
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command("MEASURE_AXES_NOISE",
@@ -156,12 +233,9 @@ class ResonanceTester:
toolhead = self.printer.lookup_object('toolhead')
calibration_data = {axis: None for axis in axes}
- self.test.prepare_test(gcmd)
+ self.generator.prepare_test(gcmd)
- if test_point is not None:
- test_points = [test_point]
- else:
- test_points = self.test.get_start_test_points()
+ test_points = [test_point] if test_point else self.probe_points
for point in test_points:
toolhead.manual_move(point, self.move_speed)
@@ -186,7 +260,8 @@ class ResonanceTester:
raw_values.append((axis, aclient, chip.name))
# Generate moves
- self.test.run_test(axis, gcmd)
+ test_seq = self.generator.gen_test()
+ self.executor.run_test(test_seq, axis, gcmd)
for chip_axis, aclient, chip_name in raw_values:
aclient.finish_measurements()
if raw_name_suffix is not None:
@@ -218,7 +293,7 @@ class ResonanceTester:
parsed_chips.append(chip)
return parsed_chips
def _get_max_calibration_freq(self):
- return 1.5 * self.test.get_max_freq()
+ return 1.5 * self.generator.get_max_freq()
cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specifed axis")
def cmd_TEST_RESONANCES(self, gcmd):
# Parse parameters