scripts: Added shaper tuning parameters to calibrate_shaper script
The added parameters include square_corner_velocity, shaper frequencies to optimize, input shapers to test, input shaper damping ratio and damping ratios to test. All these options can be useful for fine-tuning the input shapers when the default suggestions generated by the tuning script are not optimal. Also the `SHAPER_CALIBRATE` command was modified to pass some of these parameters to the shaper tuning routine. Specifically, square corner velocity and the maximum tested frequency are used to adjust shaper tuning and maximum acceleration recommendations. Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
This commit is contained in:
parent
4f00f21991
commit
72b301a285
|
@ -662,6 +662,19 @@ The same notice applies to the input shaper
|
||||||
`max_accel` value after the auto-calibration, and the suggested acceleration
|
`max_accel` value after the auto-calibration, and the suggested acceleration
|
||||||
limits will not be applied automatically.
|
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
|
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
|
suggested shaper configuration is almost the same as what you got during the
|
||||||
previous calibration, this step can be skipped.
|
previous calibration, this step can be skipped.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# A utility class to test resonances of the printer
|
# 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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import logging, math, os, time
|
import logging, math, os, time
|
||||||
|
@ -114,6 +114,8 @@ class VibrationPulseTest:
|
||||||
if input_shaper is not None:
|
if input_shaper is not None:
|
||||||
input_shaper.enable_shaping()
|
input_shaper.enable_shaping()
|
||||||
gcmd.respond_info("Re-enabled [input_shaper]")
|
gcmd.respond_info("Re-enabled [input_shaper]")
|
||||||
|
def get_max_freq(self):
|
||||||
|
return self.freq_end
|
||||||
|
|
||||||
class ResonanceTester:
|
class ResonanceTester:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -302,8 +304,14 @@ class ResonanceTester:
|
||||||
"Calculating the best input shaper parameters for %s axis"
|
"Calculating the best input shaper parameters for %s axis"
|
||||||
% (axis_name,))
|
% (axis_name,))
|
||||||
calibration_data[axis].normalize_to_frequencies()
|
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(
|
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(
|
gcmd.respond_info(
|
||||||
"Recommended shaper_type_%s = %s, shaper_freq_%s = %.1f Hz"
|
"Recommended shaper_type_%s = %s, shaper_freq_%s = %.1f Hz"
|
||||||
% (axis_name, best_shaper.name,
|
% (axis_name, best_shaper.name,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Automatic calibration of input shapers
|
# 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.
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||||
import collections, importlib, logging, math, multiprocessing, traceback
|
import collections, importlib, logging, math, multiprocessing, traceback
|
||||||
|
@ -227,34 +227,49 @@ class ShaperCalibrate:
|
||||||
offset_180 *= inv_D
|
offset_180 *= inv_D
|
||||||
return max(offset_90, offset_180)
|
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
|
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
|
freq_bins = calibration_data.freq_bins
|
||||||
psd = calibration_data.psd_sum[freq_bins <= MAX_FREQ]
|
psd = calibration_data.psd_sum[freq_bins <= max_freq]
|
||||||
freq_bins = freq_bins[freq_bins <= MAX_FREQ]
|
freq_bins = freq_bins[freq_bins <= max_freq]
|
||||||
|
|
||||||
best_res = None
|
best_res = None
|
||||||
results = []
|
results = []
|
||||||
for test_freq in test_freqs[::-1]:
|
for test_freq in test_freqs[::-1]:
|
||||||
shaper_vibrations = 0.
|
shaper_vibrations = 0.
|
||||||
shaper_vals = np.zeros(shape=freq_bins.shape)
|
shaper_vals = np.zeros(shape=freq_bins.shape)
|
||||||
shaper = shaper_cfg.init_func(
|
shaper = shaper_cfg.init_func(test_freq, damping_ratio)
|
||||||
test_freq, shaper_defs.DEFAULT_DAMPING_RATIO)
|
shaper_smoothing = self._get_shaper_smoothing(shaper, scv=scv)
|
||||||
shaper_smoothing = self._get_shaper_smoothing(shaper)
|
|
||||||
if max_smoothing and shaper_smoothing > max_smoothing and best_res:
|
if max_smoothing and shaper_smoothing > max_smoothing and best_res:
|
||||||
return best_res
|
return best_res
|
||||||
# Exact damping ratio of the printer is unknown, pessimizing
|
# Exact damping ratio of the printer is unknown, pessimizing
|
||||||
# remaining vibrations over possible damping values
|
# remaining vibrations over possible damping values
|
||||||
for dr in TEST_DAMPING_RATIOS:
|
for dr in test_damping_ratios:
|
||||||
vibrations, vals = self._estimate_remaining_vibrations(
|
vibrations, vals = self._estimate_remaining_vibrations(
|
||||||
shaper, dr, freq_bins, psd)
|
shaper, dr, freq_bins, psd)
|
||||||
shaper_vals = np.maximum(shaper_vals, vals)
|
shaper_vals = np.maximum(shaper_vals, vals)
|
||||||
if vibrations > shaper_vibrations:
|
if vibrations > shaper_vibrations:
|
||||||
shaper_vibrations = 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 score trying to minimize vibrations, but also accounting
|
||||||
# the growth of smoothing. The formula itself does not have any
|
# the growth of smoothing. The formula itself does not have any
|
||||||
# special meaning, it simply shows good results on real user data
|
# special meaning, it simply shows good results on real user data
|
||||||
|
@ -278,6 +293,8 @@ class ShaperCalibrate:
|
||||||
|
|
||||||
def _bisect(self, func):
|
def _bisect(self, func):
|
||||||
left = right = 1.
|
left = right = 1.
|
||||||
|
if not func(1e-9):
|
||||||
|
return 0.
|
||||||
while not func(left):
|
while not func(left):
|
||||||
right = left
|
right = left
|
||||||
left *= .5
|
left *= .5
|
||||||
|
@ -292,22 +309,27 @@ class ShaperCalibrate:
|
||||||
right = middle
|
right = middle
|
||||||
return left
|
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
|
# Just some empirically chosen value which produces good projections
|
||||||
# for max_accel without much smoothing
|
# for max_accel without much smoothing
|
||||||
TARGET_SMOOTHING = 0.12
|
TARGET_SMOOTHING = 0.12
|
||||||
max_accel = self._bisect(lambda test_accel: self._get_shaper_smoothing(
|
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
|
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
|
best_shaper = None
|
||||||
all_shapers = []
|
all_shapers = []
|
||||||
|
shapers = shapers or AUTOTUNE_SHAPERS
|
||||||
for shaper_cfg in shaper_defs.INPUT_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
|
continue
|
||||||
shaper = self.background_process_exec(self.fit_shaper, (
|
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:
|
if logger is not None:
|
||||||
logger("Fitted shaper '%s' frequency = %.1f Hz "
|
logger("Fitted shaper '%s' frequency = %.1f Hz "
|
||||||
"(vibrations = %.1f%%, smoothing ~= %.3f)" % (
|
"(vibrations = %.1f%%, smoothing ~= %.3f)" % (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# Shaper auto-calibration script
|
# 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>
|
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
# 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
|
# 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)
|
helper = shaper_calibrate.ShaperCalibrate(printer=None)
|
||||||
if isinstance(datas[0], shaper_calibrate.CalibrationData):
|
if isinstance(datas[0], shaper_calibrate.CalibrationData):
|
||||||
calibration_data = datas[0]
|
calibration_data = datas[0]
|
||||||
|
@ -52,8 +54,17 @@ def calibrate_shaper(datas, csv_output, max_smoothing):
|
||||||
for data in datas[1:]:
|
for data in datas[1:]:
|
||||||
calibration_data.add_data(helper.process_accelerometer_data(data))
|
calibration_data.add_data(helper.process_accelerometer_data(data))
|
||||||
calibration_data.normalize_to_frequencies()
|
calibration_data.normalize_to_frequencies()
|
||||||
|
|
||||||
|
|
||||||
shaper, all_shapers = helper.find_best_shaper(
|
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))
|
print("Recommended shaper is %s @ %.1f Hz" % (shaper.name, shaper.freq))
|
||||||
if csv_output is not None:
|
if csv_output is not None:
|
||||||
helper.save_calibration_data(
|
helper.save_calibration_data(
|
||||||
|
@ -140,28 +151,94 @@ def main():
|
||||||
opts.add_option("-c", "--csv", type="string", dest="csv",
|
opts.add_option("-c", "--csv", type="string", dest="csv",
|
||||||
default=None, help="filename of output csv file")
|
default=None, help="filename of output csv file")
|
||||||
opts.add_option("-f", "--max_freq", type="float", default=200.,
|
opts.add_option("-f", "--max_freq", type="float", default=200.,
|
||||||
help="maximum frequency to graph")
|
help="maximum frequency to plot")
|
||||||
opts.add_option("-s", "--max_smoothing", type="float", default=None,
|
opts.add_option("-s", "--max_smoothing", type="float", dest="max_smoothing",
|
||||||
help="maximum shaper smoothing to allow")
|
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()
|
options, args = opts.parse_args()
|
||||||
if len(args) < 1:
|
if len(args) < 1:
|
||||||
opts.error("Incorrect number of arguments")
|
opts.error("Incorrect number of arguments")
|
||||||
if options.max_smoothing is not None and options.max_smoothing < 0.05:
|
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)")
|
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
|
# Parse data
|
||||||
datas = [parse_log(fn) for fn in args]
|
datas = [parse_log(fn) for fn in args]
|
||||||
|
|
||||||
# Calibrate shaper and generate outputs
|
# Calibrate shaper and generate outputs
|
||||||
selected_shaper, shapers, calibration_data = calibrate_shaper(
|
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:
|
if not options.csv or options.output:
|
||||||
# Draw graph
|
# Draw graph
|
||||||
setup_matplotlib(options.output is not None)
|
setup_matplotlib(options.output is not None)
|
||||||
|
|
||||||
fig = plot_freq_response(args, calibration_data, shapers,
|
fig = plot_freq_response(args, calibration_data, shapers,
|
||||||
selected_shaper, options.max_freq)
|
selected_shaper, max_freq)
|
||||||
|
|
||||||
# Show graph
|
# Show graph
|
||||||
if options.output is None:
|
if options.output is None:
|
||||||
|
|
Loading…
Reference in New Issue