aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/Measuring_Resonances.md13
-rw-r--r--klippy/extras/resonance_tester.py12
-rw-r--r--klippy/extras/shaper_calibrate.py52
-rwxr-xr-xscripts/calibrate_shaper.py93
4 files changed, 145 insertions, 25 deletions
diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md
index 79f7de0f..d1d99894 100644
--- a/docs/Measuring_Resonances.md
+++ b/docs/Measuring_Resonances.md
@@ -662,6 +662,19 @@ The same notice applies to the input shaper
`max_accel` value after the auto-calibration, and the suggested acceleration
limits will not be applied automatically.
+Keep in mind that the maximum acceleration without too much smoothing depends
+on the `square_corner_velocity`. The general recommendation is not to change
+it from its default value 5.0, and this is the value used by default by the
+`calibrate_shaper.py` script. If you did change it though, you should inform
+the script about it by passing `--square_corner_velocity=...` parameter, e.g.
+```
+~/klipper/scripts/calibrate_shaper.py /tmp/resonances_x_*.csv -o /tmp/shaper_calibrate_x.png --square_corner_velocity=10.0
+```
+so that it can calculate the maximum acceleration recommendations correctly.
+Note that the `SHAPER_CALIBRATE` command already takes the configured
+`square_corner_velocity` parameter into account, and there is no need
+to specify it explicitly.
+
If you are doing a shaper re-calibration and the reported smoothing for the
suggested shaper configuration is almost the same as what you got during the
previous calibration, this step can be skipped.
diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py
index d5f43b77..4b29d6b8 100644
--- a/klippy/extras/resonance_tester.py
+++ b/klippy/extras/resonance_tester.py
@@ -1,6 +1,6 @@
# A utility class to test resonances of the printer
#
-# Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
+# Copyright (C) 2020-2024 Dmitry Butyugin <dmbutyugin@google.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging, math, os, time
@@ -114,6 +114,8 @@ 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):
@@ -302,8 +304,14 @@ class ResonanceTester:
"Calculating the best input shaper parameters for %s axis"
% (axis_name,))
calibration_data[axis].normalize_to_frequencies()
+ systime = self.printer.get_reactor().monotonic()
+ toolhead = self.printer.lookup_object('toolhead')
+ toolhead_info = toolhead.get_status(systime)
+ scv = toolhead_info['square_corner_velocity']
best_shaper, all_shapers = helper.find_best_shaper(
- calibration_data[axis], max_smoothing, gcmd.respond_info)
+ calibration_data[axis], max_smoothing=max_smoothing,
+ scv=scv, max_freq=1.5*self.test.get_max_freq(),
+ logging=gcmd.respond_info)
gcmd.respond_info(
"Recommended shaper_type_%s = %s, shaper_freq_%s = %.1f Hz"
% (axis_name, best_shaper.name,
diff --git a/klippy/extras/shaper_calibrate.py b/klippy/extras/shaper_calibrate.py
index af77845c..f3bfd8d2 100644
--- a/klippy/extras/shaper_calibrate.py
+++ b/klippy/extras/shaper_calibrate.py
@@ -1,6 +1,6 @@
# Automatic calibration of input shapers
#
-# Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
+# Copyright (C) 2020-2024 Dmitry Butyugin <dmbutyugin@google.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import collections, importlib, logging, math, multiprocessing, traceback
@@ -227,34 +227,49 @@ class ShaperCalibrate:
offset_180 *= inv_D
return max(offset_90, offset_180)
- def fit_shaper(self, shaper_cfg, calibration_data, max_smoothing):
+ def fit_shaper(self, shaper_cfg, calibration_data, shaper_freqs,
+ damping_ratio, scv, max_smoothing, test_damping_ratios,
+ max_freq):
np = self.numpy
- test_freqs = np.arange(shaper_cfg.min_freq, MAX_SHAPER_FREQ, .2)
+ damping_ratio = damping_ratio or shaper_defs.DEFAULT_DAMPING_RATIO
+ test_damping_ratios = test_damping_ratios or TEST_DAMPING_RATIOS
+
+ if not shaper_freqs:
+ shaper_freqs = (None, None, None)
+ if isinstance(shaper_freqs, tuple):
+ freq_end = shaper_freqs[1] or MAX_SHAPER_FREQ
+ freq_start = min(shaper_freqs[0] or shaper_cfg.min_freq,
+ freq_end - 1e-7)
+ freq_step = shaper_freqs[2] or .2
+ test_freqs = np.arange(freq_start, freq_end, freq_step)
+ else:
+ test_freqs = np.array(shaper_freqs)
+
+ max_freq = max(max_freq or MAX_FREQ, test_freqs.max())
freq_bins = calibration_data.freq_bins
- psd = calibration_data.psd_sum[freq_bins <= MAX_FREQ]
- freq_bins = freq_bins[freq_bins <= MAX_FREQ]
+ psd = calibration_data.psd_sum[freq_bins <= max_freq]
+ freq_bins = freq_bins[freq_bins <= max_freq]
best_res = None
results = []
for test_freq in test_freqs[::-1]:
shaper_vibrations = 0.
shaper_vals = np.zeros(shape=freq_bins.shape)
- shaper = shaper_cfg.init_func(
- test_freq, shaper_defs.DEFAULT_DAMPING_RATIO)
- shaper_smoothing = self._get_shaper_smoothing(shaper)
+ shaper = shaper_cfg.init_func(test_freq, damping_ratio)
+ shaper_smoothing = self._get_shaper_smoothing(shaper, scv=scv)
if max_smoothing and shaper_smoothing > max_smoothing and best_res:
return best_res
# Exact damping ratio of the printer is unknown, pessimizing
# remaining vibrations over possible damping values
- for dr in TEST_DAMPING_RATIOS:
+ for dr in test_damping_ratios:
vibrations, vals = self._estimate_remaining_vibrations(
shaper, dr, freq_bins, psd)
shaper_vals = np.maximum(shaper_vals, vals)
if vibrations > shaper_vibrations:
shaper_vibrations = vibrations
- max_accel = self.find_shaper_max_accel(shaper)
+ max_accel = self.find_shaper_max_accel(shaper, scv)
# The score trying to minimize vibrations, but also accounting
# the growth of smoothing. The formula itself does not have any
# special meaning, it simply shows good results on real user data
@@ -278,6 +293,8 @@ class ShaperCalibrate:
def _bisect(self, func):
left = right = 1.
+ if not func(1e-9):
+ return 0.
while not func(left):
right = left
left *= .5
@@ -292,22 +309,27 @@ class ShaperCalibrate:
right = middle
return left
- def find_shaper_max_accel(self, shaper):
+ def find_shaper_max_accel(self, shaper, scv):
# Just some empirically chosen value which produces good projections
# for max_accel without much smoothing
TARGET_SMOOTHING = 0.12
max_accel = self._bisect(lambda test_accel: self._get_shaper_smoothing(
- shaper, test_accel) <= TARGET_SMOOTHING)
+ shaper, test_accel, scv) <= TARGET_SMOOTHING)
return max_accel
- def find_best_shaper(self, calibration_data, max_smoothing, logger=None):
+ def find_best_shaper(self, calibration_data, shapers=None,
+ damping_ratio=None, scv=None, shaper_freqs=None,
+ max_smoothing=None, test_damping_ratios=None,
+ max_freq=None, logger=None):
best_shaper = None
all_shapers = []
+ shapers = shapers or AUTOTUNE_SHAPERS
for shaper_cfg in shaper_defs.INPUT_SHAPERS:
- if shaper_cfg.name not in AUTOTUNE_SHAPERS:
+ if shaper_cfg.name not in shapers:
continue
shaper = self.background_process_exec(self.fit_shaper, (
- shaper_cfg, calibration_data, max_smoothing))
+ shaper_cfg, calibration_data, shaper_freqs, damping_ratio,
+ scv, max_smoothing, test_damping_ratios, max_freq))
if logger is not None:
logger("Fitted shaper '%s' frequency = %.1f Hz "
"(vibrations = %.1f%%, smoothing ~= %.3f)" % (
diff --git a/scripts/calibrate_shaper.py b/scripts/calibrate_shaper.py
index 8a0fcdf0..b56ce5da 100755
--- a/scripts/calibrate_shaper.py
+++ b/scripts/calibrate_shaper.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# Shaper auto-calibration script
#
-# Copyright (C) 2020 Dmitry Butyugin <dmbutyugin@google.com>
+# Copyright (C) 2020-2024 Dmitry Butyugin <dmbutyugin@google.com>
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
@@ -40,7 +40,9 @@ def parse_log(logname):
######################################################################
# Find the best shaper parameters
-def calibrate_shaper(datas, csv_output, max_smoothing):
+def calibrate_shaper(datas, csv_output, *, shapers, damping_ratio, scv,
+ shaper_freqs, max_smoothing, test_damping_ratios,
+ max_freq):
helper = shaper_calibrate.ShaperCalibrate(printer=None)
if isinstance(datas[0], shaper_calibrate.CalibrationData):
calibration_data = datas[0]
@@ -52,8 +54,17 @@ def calibrate_shaper(datas, csv_output, max_smoothing):
for data in datas[1:]:
calibration_data.add_data(helper.process_accelerometer_data(data))
calibration_data.normalize_to_frequencies()
+
+
shaper, all_shapers = helper.find_best_shaper(
- calibration_data, max_smoothing, print)
+ calibration_data, shapers=shapers, damping_ratio=damping_ratio,
+ scv=scv, shaper_freqs=shaper_freqs, max_smoothing=max_smoothing,
+ test_damping_ratios=test_damping_ratios, max_freq=max_freq,
+ logger=print)
+ if not shaper:
+ print("No recommended shaper, possibly invalid value for --shapers=%s" %
+ (','.join(shapers)))
+ return None, None, None
print("Recommended shaper is %s @ %.1f Hz" % (shaper.name, shaper.freq))
if csv_output is not None:
helper.save_calibration_data(
@@ -140,28 +151,94 @@ def main():
opts.add_option("-c", "--csv", type="string", dest="csv",
default=None, help="filename of output csv file")
opts.add_option("-f", "--max_freq", type="float", default=200.,
- help="maximum frequency to graph")
- opts.add_option("-s", "--max_smoothing", type="float", default=None,
- help="maximum shaper smoothing to allow")
+ help="maximum frequency to plot")
+ opts.add_option("-s", "--max_smoothing", type="float", dest="max_smoothing",
+ default=None, help="maximum shaper smoothing to allow")
+ opts.add_option("--scv", "--square_corner_velocity", type="float",
+ dest="scv", default=5., help="square corner velocity")
+ opts.add_option("--shaper_freq", type="string", dest="shaper_freq",
+ default=None, help="shaper frequency(-ies) to test, " +
+ "either a comma-separated list of floats, or a range in " +
+ "the format [start]:end[:step]")
+ opts.add_option("--shapers", type="string", dest="shapers", default=None,
+ help="a comma-separated list of shapers to test")
+ opts.add_option("--damping_ratio", type="float", dest="damping_ratio",
+ default=None, help="shaper damping_ratio parameter")
+ opts.add_option("--test_damping_ratios", type="string",
+ dest="test_damping_ratios", default=None,
+ help="a comma-separated liat of damping ratios to test " +
+ "input shaper for")
options, args = opts.parse_args()
if len(args) < 1:
opts.error("Incorrect number of arguments")
if options.max_smoothing is not None and options.max_smoothing < 0.05:
opts.error("Too small max_smoothing specified (must be at least 0.05)")
+ max_freq = options.max_freq
+ if options.shaper_freq is None:
+ shaper_freqs = []
+ elif options.shaper_freq.find(':') >= 0:
+ freq_start = None
+ freq_end = None
+ freq_step = None
+ try:
+ freqs_parsed = options.shaper_freq.partition(':')
+ if freqs_parsed[0]:
+ freq_start = float(freqs_parsed[0])
+ freqs_parsed = freqs_parsed[-1].partition(':')
+ freq_end = float(freqs_parsed[0])
+ if freq_start and freq_start > freq_end:
+ opts.error("Invalid --shaper_freq param: start range larger " +
+ "than its end")
+ if freqs_parsed[-1].find(':') >= 0:
+ opts.error("Invalid --shaper_freq param format")
+ if freqs_parsed[-1]:
+ freq_step = float(freqs_parsed[-1])
+ except ValueError:
+ opts.error("--shaper_freq param does not specify correct range " +
+ "in the format [start]:end[:step]")
+ shaper_freqs = (freq_start, freq_end, freq_step)
+ max_freq = max(max_freq, freq_end * 4./3.)
+ else:
+ try:
+ shaper_freqs = [float(s) for s in options.shaper_freq.split(',')]
+ except ValueError:
+ opts.error("invalid floating point value in --shaper_freq param")
+ max_freq = max(max_freq, max(shaper_freqs) * 4./3.)
+ if options.test_damping_ratios:
+ try:
+ test_damping_ratios = [float(s) for s in
+ options.test_damping_ratios.split(',')]
+ except ValueError:
+ opts.error("invalid floating point value in " +
+ "--test_damping_ratios param")
+ else:
+ test_damping_ratios = None
+ if options.shapers is None:
+ shapers = None
+ else:
+ shapers = options.shapers.lower().split(',')
+
# Parse data
datas = [parse_log(fn) for fn in args]
# Calibrate shaper and generate outputs
selected_shaper, shapers, calibration_data = calibrate_shaper(
- datas, options.csv, options.max_smoothing)
+ datas, options.csv, shapers=shapers,
+ damping_ratio=options.damping_ratio,
+ scv=options.scv, shaper_freqs=shaper_freqs,
+ max_smoothing=options.max_smoothing,
+ test_damping_ratios=test_damping_ratios,
+ max_freq=max_freq)
+ if selected_shaper is None:
+ return
if not options.csv or options.output:
# Draw graph
setup_matplotlib(options.output is not None)
fig = plot_freq_response(args, calibration_data, shapers,
- selected_shaper, options.max_freq)
+ selected_shaper, max_freq)
# Show graph
if options.output is None: