Updated the scripts and improved vibration graphs
Full revamped documentation
@@ -4,9 +4,10 @@
|
|||||||
######## CoreXY BELTS CALIBRATION SCRIPT ########
|
######## CoreXY BELTS CALIBRATION SCRIPT ########
|
||||||
#################################################
|
#################################################
|
||||||
# Written by Frix_x#0161 #
|
# Written by Frix_x#0161 #
|
||||||
# @version: 1.0
|
# @version: 2.0
|
||||||
|
|
||||||
# CHANGELOG:
|
# CHANGELOG:
|
||||||
|
# v2.0: updated the script to align it to the new K-Shake&Tune module
|
||||||
# v1.0: first version of this tool for enhanced vizualisation of belt graphs
|
# v1.0: first version of this tool for enhanced vizualisation of belt graphs
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +34,6 @@ except locale.Error:
|
|||||||
locale.setlocale(locale.LC_TIME, 'C')
|
locale.setlocale(locale.LC_TIME, 'C')
|
||||||
|
|
||||||
|
|
||||||
MAX_TITLE_LENGTH = 65
|
|
||||||
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # For paired peaks names
|
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # For paired peaks names
|
||||||
|
|
||||||
PEAKS_DETECTION_THRESHOLD = 0.20
|
PEAKS_DETECTION_THRESHOLD = 0.20
|
||||||
@@ -91,7 +91,9 @@ def compute_curve_similarity_factor(signal1, signal2):
|
|||||||
def detect_peaks(psd, freqs, window_size=5, vicinity=3):
|
def detect_peaks(psd, freqs, window_size=5, vicinity=3):
|
||||||
# Smooth the curve using a moving average to avoid catching peaks everywhere in noisy signals
|
# Smooth the curve using a moving average to avoid catching peaks everywhere in noisy signals
|
||||||
kernel = np.ones(window_size) / window_size
|
kernel = np.ones(window_size) / window_size
|
||||||
smoothed_psd = np.convolve(psd, kernel, mode='same')
|
smoothed_psd = np.convolve(psd, kernel, mode='valid')
|
||||||
|
mean_pad = [np.mean(psd[:window_size])] * (window_size // 2)
|
||||||
|
smoothed_psd = np.concatenate((mean_pad, smoothed_psd))
|
||||||
|
|
||||||
# Find peaks on the smoothed curve
|
# Find peaks on the smoothed curve
|
||||||
smoothed_peaks = np.where((smoothed_psd[:-2] < smoothed_psd[1:-1]) & (smoothed_psd[1:-1] > smoothed_psd[2:]))[0] + 1
|
smoothed_peaks = np.where((smoothed_psd[:-2] < smoothed_psd[1:-1]) & (smoothed_psd[1:-1] > smoothed_psd[2:]))[0] + 1
|
||||||
@@ -297,12 +299,12 @@ def combined_spectrogram(data1, data2):
|
|||||||
return combined_data, bins1, t1
|
return combined_data, bins1, t1
|
||||||
|
|
||||||
|
|
||||||
# Compute a composite and highly subjective value indicating the "chance of mechanical issues on the printer (0 to 100%)"
|
# Compute a composite and highly subjective value indicating the "mechanical health of the printer (0 to 100%)" that represent the
|
||||||
# that is based on the differential spectrogram sum of gradient salted with a bit of the estimated similarity cross-correlation
|
# likelihood of mechanical issues on the printer. It is based on the differential spectrogram sum of gradient, salted with a bit
|
||||||
# from compute_curve_similarity_factor() and with a bit of the number of unpaired peaks.
|
# of the estimated similarity cross-correlation from compute_curve_similarity_factor() and with a bit of the number of unpaired peaks.
|
||||||
# This result in a percentage value quantifying the machine behavior around the main resonances that give an hint if only touching belt tension
|
# This result in a percentage value quantifying the machine behavior around the main resonances that give an hint if only touching belt tension
|
||||||
# will give good graphs or if there is a chance of mechanical issues in the background (above 50% should be considered as probably problematic)
|
# will give good graphs or if there is a chance of mechanical issues in the background (above 50% should be considered as probably problematic)
|
||||||
def compute_comi(combined_data, similarity_coefficient, num_unpaired_peaks):
|
def compute_mhi(combined_data, similarity_coefficient, num_unpaired_peaks):
|
||||||
filtered_data = combined_data[combined_data > 100]
|
filtered_data = combined_data[combined_data > 100]
|
||||||
|
|
||||||
# First compute a "total variability metric" based on the sum of the gradient that sum the magnitude of will emphasize regions of the
|
# First compute a "total variability metric" based on the sum of the gradient that sum the magnitude of will emphasize regions of the
|
||||||
@@ -321,7 +323,23 @@ def compute_comi(combined_data, similarity_coefficient, num_unpaired_peaks):
|
|||||||
# Ensure the result lies between 0 and 100 by clipping the computed value
|
# Ensure the result lies between 0 and 100 by clipping the computed value
|
||||||
final_percentage = np.clip(final_percentage, 0, 100)
|
final_percentage = np.clip(final_percentage, 0, 100)
|
||||||
|
|
||||||
return final_percentage
|
return final_percentage, mhi_lut(final_percentage)
|
||||||
|
|
||||||
|
|
||||||
|
# LUT to transform the MHI into a textual value easy to understand for the users of the script
|
||||||
|
def mhi_lut(mhi):
|
||||||
|
if 0 <= mhi <= 30:
|
||||||
|
return "Excellent mechanical health"
|
||||||
|
elif 31 <= mhi <= 45:
|
||||||
|
return "Good mechanical health"
|
||||||
|
elif 46 <= mhi <= 55:
|
||||||
|
return "Acceptable mechanical health"
|
||||||
|
elif 56 <= mhi <= 70:
|
||||||
|
return "Potential signs of a mechanical issue"
|
||||||
|
elif 71 <= mhi <= 85:
|
||||||
|
return "Likely a mechanical issue"
|
||||||
|
elif 86 <= mhi <= 100:
|
||||||
|
return "Mechanical issue detected"
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@@ -361,7 +379,7 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq):
|
|||||||
label = ALPHABET[paired_peak_count]
|
label = ALPHABET[paired_peak_count]
|
||||||
amplitude_offset = abs(((signal2.psd[peak2[0]] - signal1.psd[peak1[0]]) / max(signal1.psd[peak1[0]], signal2.psd[peak2[0]])) * 100)
|
amplitude_offset = abs(((signal2.psd[peak2[0]] - signal1.psd[peak1[0]]) / max(signal1.psd[peak1[0]], signal2.psd[peak2[0]])) * 100)
|
||||||
frequency_offset = abs(signal2.freqs[peak2[0]] - signal1.freqs[peak1[0]])
|
frequency_offset = abs(signal2.freqs[peak2[0]] - signal1.freqs[peak1[0]])
|
||||||
offsets_table_data.append([f"Peaks {label}", f"{frequency_offset:.2f} Hz", f"{amplitude_offset:.2f} %"])
|
offsets_table_data.append([f"Peaks {label}", f"{frequency_offset:.1f} Hz", f"{amplitude_offset:.1f} %"])
|
||||||
|
|
||||||
ax.plot(signal1.freqs[peak1[0]], signal1.psd[peak1[0]], "x", color='black')
|
ax.plot(signal1.freqs[peak1[0]], signal1.psd[peak1[0]], "x", color='black')
|
||||||
ax.plot(signal2.freqs[peak2[0]], signal2.psd[peak2[0]], "x", color='black')
|
ax.plot(signal2.freqs[peak2[0]], signal2.psd[peak2[0]], "x", color='black')
|
||||||
@@ -369,28 +387,29 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq):
|
|||||||
|
|
||||||
ax.annotate(label + "1", (signal1.freqs[peak1[0]], signal1.psd[peak1[0]]),
|
ax.annotate(label + "1", (signal1.freqs[peak1[0]], signal1.psd[peak1[0]]),
|
||||||
textcoords="offset points", xytext=(8, 5),
|
textcoords="offset points", xytext=(8, 5),
|
||||||
ha='left', fontsize=14, color='black')
|
ha='left', fontsize=13, color='black')
|
||||||
ax.annotate(label + "2", (signal2.freqs[peak2[0]], signal2.psd[peak2[0]]),
|
ax.annotate(label + "2", (signal2.freqs[peak2[0]], signal2.psd[peak2[0]]),
|
||||||
textcoords="offset points", xytext=(8, 5),
|
textcoords="offset points", xytext=(8, 5),
|
||||||
ha='left', fontsize=14, color='black')
|
ha='left', fontsize=13, color='black')
|
||||||
paired_peak_count += 1
|
paired_peak_count += 1
|
||||||
|
|
||||||
for peak in signal1.unpaired_peaks:
|
for peak in signal1.unpaired_peaks:
|
||||||
ax.plot(signal1.freqs[peak], signal1.psd[peak], "x", color='black')
|
ax.plot(signal1.freqs[peak], signal1.psd[peak], "x", color='black')
|
||||||
ax.annotate(str(unpaired_peak_count + 1), (signal1.freqs[peak], signal1.psd[peak]),
|
ax.annotate(str(unpaired_peak_count + 1), (signal1.freqs[peak], signal1.psd[peak]),
|
||||||
textcoords="offset points", xytext=(8, 5),
|
textcoords="offset points", xytext=(8, 5),
|
||||||
ha='left', fontsize=14, color='red', weight='bold')
|
ha='left', fontsize=13, color='red', weight='bold')
|
||||||
unpaired_peak_count += 1
|
unpaired_peak_count += 1
|
||||||
|
|
||||||
for peak in signal2.unpaired_peaks:
|
for peak in signal2.unpaired_peaks:
|
||||||
ax.plot(signal2.freqs[peak], signal2.psd[peak], "x", color='black')
|
ax.plot(signal2.freqs[peak], signal2.psd[peak], "x", color='black')
|
||||||
ax.annotate(str(unpaired_peak_count + 1), (signal2.freqs[peak], signal2.psd[peak]),
|
ax.annotate(str(unpaired_peak_count + 1), (signal2.freqs[peak], signal2.psd[peak]),
|
||||||
textcoords="offset points", xytext=(8, 5),
|
textcoords="offset points", xytext=(8, 5),
|
||||||
ha='left', fontsize=14, color='red', weight='bold')
|
ha='left', fontsize=13, color='red', weight='bold')
|
||||||
unpaired_peak_count += 1
|
unpaired_peak_count += 1
|
||||||
|
|
||||||
# Compute the similarity (using cross-correlation of the PSD signals)
|
# Compute the similarity (using cross-correlation of the PSD signals)
|
||||||
ax2 = ax.twinx() # To split the legends in two box
|
ax2 = ax.twinx() # To split the legends in two box
|
||||||
|
ax2.yaxis.set_visible(False)
|
||||||
similarity_factor = compute_curve_similarity_factor(signal1, signal2)
|
similarity_factor = compute_curve_similarity_factor(signal1, signal2)
|
||||||
ax2.plot([], [], ' ', label=f'Estimated similarity: {similarity_factor:.1f}%')
|
ax2.plot([], [], ' ', label=f'Estimated similarity: {similarity_factor:.1f}%')
|
||||||
ax2.plot([], [], ' ', label=f'Number of unpaired peaks: {unpaired_peak_count}')
|
ax2.plot([], [], ' ', label=f'Number of unpaired peaks: {unpaired_peak_count}')
|
||||||
@@ -415,7 +434,7 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq):
|
|||||||
# Print the table of offsets ontop of the graph below the original legend (upper right)
|
# Print the table of offsets ontop of the graph below the original legend (upper right)
|
||||||
if len(offsets_table_data) > 0:
|
if len(offsets_table_data) > 0:
|
||||||
columns = ["", "Frequency delta", "Amplitude delta", ]
|
columns = ["", "Frequency delta", "Amplitude delta", ]
|
||||||
offset_table = ax.table(cellText=offsets_table_data, colLabels=columns, bbox=[0.67, 0.70, 0.3, 0.2], loc='upper right', cellLoc='center')
|
offset_table = ax.table(cellText=offsets_table_data, colLabels=columns, bbox=[0.66, 0.75, 0.33, 0.15], loc='upper right', cellLoc='center')
|
||||||
offset_table.auto_set_font_size(False)
|
offset_table.auto_set_font_size(False)
|
||||||
offset_table.set_fontsize(8)
|
offset_table.set_fontsize(8)
|
||||||
offset_table.auto_set_column_width([0, 1, 2])
|
offset_table.auto_set_column_width([0, 1, 2])
|
||||||
@@ -434,13 +453,13 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq):
|
|||||||
def plot_difference_spectrogram(ax, data1, data2, signal1, signal2, similarity_factor, max_freq):
|
def plot_difference_spectrogram(ax, data1, data2, signal1, signal2, similarity_factor, max_freq):
|
||||||
combined_data, bins, t = combined_spectrogram(data1, data2)
|
combined_data, bins, t = combined_spectrogram(data1, data2)
|
||||||
|
|
||||||
# Compute the COMI value from the differential spectrogram sum of gradient, salted with
|
# Compute the MHI value from the differential spectrogram sum of gradient, salted with
|
||||||
# the similarity factor and the number or unpaired peaks from the belts frequency profile
|
# the similarity factor and the number or unpaired peaks from the belts frequency profile
|
||||||
# Be careful, this value is highly opinionated and is pretty experimental!
|
# Be careful, this value is highly opinionated and is pretty experimental!
|
||||||
comi = compute_comi(combined_data, similarity_factor, len(signal1.unpaired_peaks) + len(signal2.unpaired_peaks))
|
mhi, textual_mhi = compute_mhi(combined_data, similarity_factor, len(signal1.unpaired_peaks) + len(signal2.unpaired_peaks))
|
||||||
print(f"Chances of mechanical issues: {comi:.1f}%")
|
print(f"[experimental] Mechanical Health Indicator: {textual_mhi.lower()} ({mhi:.1f}%)")
|
||||||
ax.set_title(f"Differential Spectrogram (COMI: {comi:.1f}%)", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
ax.set_title(f"Differential Spectrogram", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
||||||
ax.plot([], [], ' ', label=f'Chances of mechanical issues (COMI): {comi:.1f}%')
|
ax.plot([], [], ' ', label=f'{textual_mhi} (experimental)')
|
||||||
|
|
||||||
# Draw the differential spectrogram with a specific norm to get light grey zero values and red for max values (vmin to vcenter is not used)
|
# Draw the differential spectrogram with a specific norm to get light grey zero values and red for max values (vmin to vcenter is not used)
|
||||||
norm = matplotlib.colors.TwoSlopeNorm(vcenter=np.min(combined_data), vmax=np.max(combined_data))
|
norm = matplotlib.colors.TwoSlopeNorm(vcenter=np.min(combined_data), vmax=np.max(combined_data))
|
||||||
@@ -550,19 +569,26 @@ def belts_calibration(lognames, klipperdir="~/klipper", max_freq=200.):
|
|||||||
ax1 = fig.add_subplot(gs[0])
|
ax1 = fig.add_subplot(gs[0])
|
||||||
ax2 = fig.add_subplot(gs[1])
|
ax2 = fig.add_subplot(gs[1])
|
||||||
|
|
||||||
|
# Add title
|
||||||
filename = lognames[0].split('/')[-1]
|
filename = lognames[0].split('/')[-1]
|
||||||
dt = datetime.strptime(f"{filename.split('_')[1]} {filename.split('_')[2]}", "%Y%m%d %H%M%S")
|
dt = datetime.strptime(f"{filename.split('_')[1]} {filename.split('_')[2]}", "%Y%m%d %H%M%S")
|
||||||
title_line1 = "RELATIVE BELT CALIBRATION TOOL"
|
title_line1 = "RELATIVE BELT CALIBRATION TOOL"
|
||||||
title_line2 = dt.strftime('%x %X')
|
title_line2 = dt.strftime('%x %X')
|
||||||
fig.text(0.88, 0.965, title_line1, ha='right', va='bottom', fontsize=20, color=KLIPPAIN_COLORS['purple'], weight='bold')
|
fig.text(0.12, 0.965, title_line1, ha='left', va='bottom', fontsize=20, color=KLIPPAIN_COLORS['purple'], weight='bold')
|
||||||
fig.text(0.88, 0.957, title_line2, ha='right', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
fig.text(0.12, 0.957, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
||||||
|
|
||||||
|
# Plot the graphs
|
||||||
similarity_factor, _ = plot_compare_frequency(ax1, lognames, signal1, signal2, max_freq)
|
similarity_factor, _ = plot_compare_frequency(ax1, lognames, signal1, signal2, max_freq)
|
||||||
plot_difference_spectrogram(ax2, datas[0], datas[1], signal1, signal2, similarity_factor, max_freq)
|
plot_difference_spectrogram(ax2, datas[0], datas[1], signal1, signal2, similarity_factor, max_freq)
|
||||||
|
|
||||||
fig.set_size_inches(10, 13)
|
fig.set_size_inches(8.3, 11.6)
|
||||||
fig.tight_layout()
|
fig.tight_layout()
|
||||||
fig.subplots_adjust(top=0.90)
|
fig.subplots_adjust(top=0.89)
|
||||||
|
|
||||||
|
# Adding a small Klippain logo to the top left corner of the figure
|
||||||
|
ax_logo = fig.add_axes([0.001, 0.899, 0.1, 0.1], anchor='NW', zorder=-1)
|
||||||
|
ax_logo.imshow(matplotlib.pyplot.imread(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'klippain.png')))
|
||||||
|
ax_logo.axis('off')
|
||||||
|
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
@@ -584,12 +610,6 @@ def main():
|
|||||||
opts.error("You must specify an output file.png to use the script (option -o)")
|
opts.error("You must specify an output file.png to use the script (option -o)")
|
||||||
|
|
||||||
fig = belts_calibration(args, options.klipperdir, options.max_freq)
|
fig = belts_calibration(args, options.klipperdir, options.max_freq)
|
||||||
|
|
||||||
# Adding a small Klippain logo to the top left corner of the figure
|
|
||||||
ax_logo = fig.add_axes([0.899, 0.899, 0.1, 0.1], anchor='NE', zorder=-1)
|
|
||||||
ax_logo.imshow(matplotlib.pyplot.imread(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'klippain.png')))
|
|
||||||
ax_logo.axis('off')
|
|
||||||
|
|
||||||
fig.savefig(options.output)
|
fig.savefig(options.output)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
|
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
|
||||||
#
|
#
|
||||||
# Written by Frix_x#0161 #
|
# Written by Frix_x#0161 #
|
||||||
# @version: 1.1
|
# @version: 2.0
|
||||||
|
|
||||||
# CHANGELOG:
|
# CHANGELOG:
|
||||||
|
# v2.0: updated the script to align it to the new K-Shake&Tune module
|
||||||
# v1.1: - improved the damping ratio computation with linear approximation for more precision
|
# v1.1: - improved the damping ratio computation with linear approximation for more precision
|
||||||
# - reworked the top graph to add more information to it with colored zones,
|
# - reworked the top graph to add more information to it with colored zones,
|
||||||
# automated peak detection, etc...
|
# automated peak detection, etc...
|
||||||
@@ -40,8 +41,6 @@ except locale.Error:
|
|||||||
locale.setlocale(locale.LC_TIME, 'C')
|
locale.setlocale(locale.LC_TIME, 'C')
|
||||||
|
|
||||||
|
|
||||||
MAX_TITLE_LENGTH = 65
|
|
||||||
|
|
||||||
PEAKS_DETECTION_THRESHOLD = 0.05
|
PEAKS_DETECTION_THRESHOLD = 0.05
|
||||||
PEAKS_EFFECT_THRESHOLD = 0.12
|
PEAKS_EFFECT_THRESHOLD = 0.12
|
||||||
SPECTROGRAM_LOW_PERCENTILE_FILTER = 5
|
SPECTROGRAM_LOW_PERCENTILE_FILTER = 5
|
||||||
@@ -122,7 +121,9 @@ def compute_spectrogram(data):
|
|||||||
def detect_peaks(psd, freqs, window_size=5, vicinity=3):
|
def detect_peaks(psd, freqs, window_size=5, vicinity=3):
|
||||||
# Smooth the curve using a moving average to avoid catching peaks everywhere in noisy signals
|
# Smooth the curve using a moving average to avoid catching peaks everywhere in noisy signals
|
||||||
kernel = np.ones(window_size) / window_size
|
kernel = np.ones(window_size) / window_size
|
||||||
smoothed_psd = np.convolve(psd, kernel, mode='same')
|
smoothed_psd = np.convolve(psd, kernel, mode='valid')
|
||||||
|
mean_pad = [np.mean(psd[:window_size])] * (window_size // 2)
|
||||||
|
smoothed_psd = np.concatenate((mean_pad, smoothed_psd))
|
||||||
|
|
||||||
# Find peaks on the smoothed curve
|
# Find peaks on the smoothed curve
|
||||||
smoothed_peaks = np.where((smoothed_psd[:-2] < smoothed_psd[1:-1]) & (smoothed_psd[1:-1] > smoothed_psd[2:]))[0] + 1
|
smoothed_peaks = np.where((smoothed_psd[:-2] < smoothed_psd[1:-1]) & (smoothed_psd[1:-1] > smoothed_psd[2:]))[0] + 1
|
||||||
@@ -178,7 +179,7 @@ def plot_freq_response_with_damping(ax, calibration_data, shapers, selected_shap
|
|||||||
ax.grid(which='minor', color='lightgrey')
|
ax.grid(which='minor', color='lightgrey')
|
||||||
|
|
||||||
ax2 = ax.twinx()
|
ax2 = ax.twinx()
|
||||||
ax2.set_ylabel('Shaper vibration reduction (ratio)')
|
ax2.yaxis.set_visible(False)
|
||||||
|
|
||||||
best_shaper_vals = None
|
best_shaper_vals = None
|
||||||
no_vibr_shaper = None
|
no_vibr_shaper = None
|
||||||
@@ -211,7 +212,7 @@ def plot_freq_response_with_damping(ax, calibration_data, shapers, selected_shap
|
|||||||
peaks_warning_threshold = PEAKS_DETECTION_THRESHOLD * psd.max()
|
peaks_warning_threshold = PEAKS_DETECTION_THRESHOLD * psd.max()
|
||||||
peaks_effect_threshold = PEAKS_EFFECT_THRESHOLD * psd.max()
|
peaks_effect_threshold = PEAKS_EFFECT_THRESHOLD * psd.max()
|
||||||
|
|
||||||
ax.plot(freqs[peaks], psd[peaks], "x", color='black', label='Detected peaks', markersize=8)
|
ax.plot(freqs[peaks], psd[peaks], "x", color='black', markersize=8)
|
||||||
for idx, peak in enumerate(peaks):
|
for idx, peak in enumerate(peaks):
|
||||||
if psd[peak] > peaks_effect_threshold:
|
if psd[peak] > peaks_effect_threshold:
|
||||||
fontcolor = 'red'
|
fontcolor = 'red'
|
||||||
@@ -221,7 +222,7 @@ def plot_freq_response_with_damping(ax, calibration_data, shapers, selected_shap
|
|||||||
fontweight = 'normal'
|
fontweight = 'normal'
|
||||||
ax.annotate(f"{idx+1}", (freqs[peak], psd[peak]),
|
ax.annotate(f"{idx+1}", (freqs[peak], psd[peak]),
|
||||||
textcoords="offset points", xytext=(8, 5),
|
textcoords="offset points", xytext=(8, 5),
|
||||||
ha='left', fontsize=14, color=fontcolor, weight=fontweight)
|
ha='left', fontsize=13, color=fontcolor, weight=fontweight)
|
||||||
ax.axhline(y=peaks_warning_threshold, color='black', linestyle='--', linewidth=0.5)
|
ax.axhline(y=peaks_warning_threshold, color='black', linestyle='--', linewidth=0.5)
|
||||||
ax.axhline(y=peaks_effect_threshold, color='black', linestyle='--', linewidth=0.5)
|
ax.axhline(y=peaks_effect_threshold, color='black', linestyle='--', linewidth=0.5)
|
||||||
ax.fill_between(freqs, 0, peaks_warning_threshold, color='green', alpha=0.15, label='Relax Region')
|
ax.fill_between(freqs, 0, peaks_warning_threshold, color='green', alpha=0.15, label='Relax Region')
|
||||||
@@ -260,7 +261,7 @@ def plot_spectrogram(ax, data, peaks, max_freq):
|
|||||||
for idx, peak in enumerate(peaks):
|
for idx, peak in enumerate(peaks):
|
||||||
ax.axvline(peak, color='cyan', linestyle='dotted', linewidth=0.75)
|
ax.axvline(peak, color='cyan', linestyle='dotted', linewidth=0.75)
|
||||||
ax.annotate(f"Peak {idx+1}", (peak, t[-1]*0.9),
|
ax.annotate(f"Peak {idx+1}", (peak, t[-1]*0.9),
|
||||||
textcoords="data", color='cyan', rotation=90, fontsize=12,
|
textcoords="data", color='cyan', rotation=90, fontsize=10,
|
||||||
verticalalignment='top', horizontalalignment='right')
|
verticalalignment='top', horizontalalignment='right')
|
||||||
|
|
||||||
ax.set_xlim([0., max_freq])
|
ax.set_xlim([0., max_freq])
|
||||||
@@ -309,19 +310,26 @@ def shaper_calibration(lognames, klipperdir="~/klipper", max_smoothing=None, max
|
|||||||
ax1 = fig.add_subplot(gs[0])
|
ax1 = fig.add_subplot(gs[0])
|
||||||
ax2 = fig.add_subplot(gs[1])
|
ax2 = fig.add_subplot(gs[1])
|
||||||
|
|
||||||
|
# Add title
|
||||||
filename_parts = (lognames[0].split('/')[-1]).split('_')
|
filename_parts = (lognames[0].split('/')[-1]).split('_')
|
||||||
dt = datetime.strptime(f"{filename_parts[3]} {filename_parts[4].split('.')[0]}", "%Y%m%d %H%M%S")
|
dt = datetime.strptime(f"{filename_parts[3]} {filename_parts[4].split('.')[0]}", "%Y%m%d %H%M%S")
|
||||||
title_line1 = "INPUT SHAPER CALIBRATION TOOL"
|
title_line1 = "INPUT SHAPER CALIBRATION TOOL"
|
||||||
title_line2 = filename_parts[2].upper() + ' axis - ' + dt.strftime('%x %X')
|
title_line2 = dt.strftime('%x %X') + ' -- ' + filename_parts[2].upper() + ' axis'
|
||||||
fig.text(0.88, 0.965, title_line1, ha='right', va='bottom', fontsize=20, color=KLIPPAIN_COLORS['purple'], weight='bold')
|
fig.text(0.12, 0.965, title_line1, ha='left', va='bottom', fontsize=20, color=KLIPPAIN_COLORS['purple'], weight='bold')
|
||||||
fig.text(0.88, 0.957, title_line2, ha='right', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
fig.text(0.12, 0.957, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
||||||
|
|
||||||
|
# Plot the graphs
|
||||||
peaks = plot_freq_response_with_damping(ax1, calibration_data, shapers, selected_shaper, fr, zeta, max_freq)
|
peaks = plot_freq_response_with_damping(ax1, calibration_data, shapers, selected_shaper, fr, zeta, max_freq)
|
||||||
plot_spectrogram(ax2, datas[0], peaks, max_freq)
|
plot_spectrogram(ax2, datas[0], peaks, max_freq)
|
||||||
|
|
||||||
fig.set_size_inches(10, 13)
|
fig.set_size_inches(8.3, 11.6)
|
||||||
fig.tight_layout()
|
fig.tight_layout()
|
||||||
fig.subplots_adjust(top=0.90)
|
fig.subplots_adjust(top=0.89)
|
||||||
|
|
||||||
|
# Adding a small Klippain logo to the top left corner of the figure
|
||||||
|
ax_logo = fig.add_axes([0.001, 0.899, 0.1, 0.1], anchor='NW', zorder=-1)
|
||||||
|
ax_logo.imshow(matplotlib.pyplot.imread(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'klippain.png')))
|
||||||
|
ax_logo.axis('off')
|
||||||
|
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
@@ -347,12 +355,6 @@ def main():
|
|||||||
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)")
|
||||||
|
|
||||||
fig = shaper_calibration(args, options.klipperdir, options.max_smoothing, options.max_freq)
|
fig = shaper_calibration(args, options.klipperdir, options.max_smoothing, options.max_freq)
|
||||||
|
|
||||||
# Adding a small Klippain logo to the top left corner of the figure
|
|
||||||
ax_logo = fig.add_axes([0.899, 0.899, 0.1, 0.1], anchor='NE', zorder=-1)
|
|
||||||
ax_logo.imshow(matplotlib.pyplot.imread(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'klippain.png')))
|
|
||||||
ax_logo.axis('off')
|
|
||||||
|
|
||||||
fig.savefig(options.output)
|
fig.savefig(options.output)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
###### SPEED AND VIBRATIONS PLOTTING SCRIPT ######
|
###### SPEED AND VIBRATIONS PLOTTING SCRIPT ######
|
||||||
##################################################
|
##################################################
|
||||||
# Written by Frix_x#0161 #
|
# Written by Frix_x#0161 #
|
||||||
# @version: 1.2
|
# @version: 2.0
|
||||||
|
|
||||||
# CHANGELOG:
|
# CHANGELOG:
|
||||||
|
# v2.0: - updated the script to align it to the new K-Shake&Tune module
|
||||||
|
# - new features for peaks detection and advised speed zones
|
||||||
# v1.2: fixed a bug that could happen when username is not "pi" (thanks @spikeygg)
|
# v1.2: fixed a bug that could happen when username is not "pi" (thanks @spikeygg)
|
||||||
# v1.1: better graph formatting
|
# v1.1: better graph formatting
|
||||||
# v1.0: first version of the script
|
# v1.0: first version of the script
|
||||||
@@ -22,9 +24,26 @@ import optparse, matplotlib, re, sys, importlib, os, operator
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
|
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
|
||||||
import matplotlib.ticker
|
import matplotlib.ticker, matplotlib.gridspec
|
||||||
|
import locale
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
matplotlib.use('Agg')
|
matplotlib.use('Agg')
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_TIME, locale.getdefaultlocale())
|
||||||
|
except locale.Error:
|
||||||
|
locale.setlocale(locale.LC_TIME, 'C')
|
||||||
|
|
||||||
|
|
||||||
|
PEAKS_DETECTION_THRESHOLD = 0.05
|
||||||
|
PEAKS_RELATIVE_HEIGHT_THRESHOLD = 0.04
|
||||||
|
VALLEY_DETECTION_THRESHOLD = 0.1 # Lower is more sensitive
|
||||||
|
|
||||||
|
KLIPPAIN_COLORS = {
|
||||||
|
"purple": "#70088C",
|
||||||
|
"dark_purple": "#150140",
|
||||||
|
"dark_orange": "#F24130"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@@ -112,41 +131,168 @@ def calc_powertot(psd_list, freqs):
|
|||||||
return [pwrtot_sum, pwrtot_x, pwrtot_y, pwrtot_z]
|
return [pwrtot_sum, pwrtot_x, pwrtot_y, pwrtot_z]
|
||||||
|
|
||||||
|
|
||||||
|
# This find all the peaks in a curve by looking at when the derivative term goes from positive to negative
|
||||||
|
# Then only the peaks found above a threshold are kept to avoid capturing peaks in the low amplitude noise of a signal
|
||||||
|
# Additionaly, we validate that a peak is a real peak based of its neighbors as we can have pretty flat zones in vibration
|
||||||
|
# graphs with a lot of false positive due to small "noise" in these flat zones
|
||||||
|
def detect_peaks(power_total, speeds, window_size=10, vicinity=10):
|
||||||
|
# Smooth the curve using a moving average to avoid catching peaks everywhere in noisy signals
|
||||||
|
kernel = np.ones(window_size) / window_size
|
||||||
|
smoothed_psd = np.convolve(power_total, kernel, mode='valid')
|
||||||
|
mean_pad = [np.mean(power_total[:window_size])] * (window_size // 2)
|
||||||
|
smoothed_psd = np.concatenate((mean_pad, smoothed_psd))
|
||||||
|
|
||||||
|
# Find peaks on the smoothed curve (and excluding the last value of the serie often detected when in a flat zone)
|
||||||
|
smoothed_peaks = np.where((smoothed_psd[:-3] < smoothed_psd[1:-2]) & (smoothed_psd[1:-2] > smoothed_psd[2:-1]))[0] + 1
|
||||||
|
detection_threshold = PEAKS_DETECTION_THRESHOLD * power_total.max()
|
||||||
|
|
||||||
|
valid_peaks = []
|
||||||
|
for peak in smoothed_peaks:
|
||||||
|
peak_height = smoothed_psd[peak] - np.min(smoothed_psd[max(0, peak-vicinity):min(len(smoothed_psd), peak+vicinity+1)])
|
||||||
|
if peak_height > PEAKS_RELATIVE_HEIGHT_THRESHOLD * smoothed_psd[peak] and smoothed_psd[peak] > detection_threshold:
|
||||||
|
valid_peaks.append(peak)
|
||||||
|
|
||||||
|
# Refine peak positions on the original curve
|
||||||
|
refined_peaks = []
|
||||||
|
for peak in valid_peaks:
|
||||||
|
local_max = peak + np.argmax(power_total[max(0, peak-vicinity):min(len(power_total), peak+vicinity+1)]) - vicinity
|
||||||
|
refined_peaks.append(local_max)
|
||||||
|
|
||||||
|
peak_speeds = ["{:.1f}".format(speeds[i]) for i in refined_peaks]
|
||||||
|
num_peaks = len(refined_peaks)
|
||||||
|
print("Vibrations peaks detected: %d @ %s mm/s (avoid running these speeds in your slicer profile)" % (num_peaks, ", ".join(map(str, peak_speeds))))
|
||||||
|
|
||||||
|
return np.array(refined_peaks), num_peaks
|
||||||
|
|
||||||
|
|
||||||
|
# The goal is to find zone outside of peaks (flat low energy zones) to advise them as good speeds range to use in the slicer
|
||||||
|
def identify_low_energy_zones(power_total):
|
||||||
|
valleys = []
|
||||||
|
|
||||||
|
# Calculate the mean and standard deviation of the entire power_total
|
||||||
|
mean_energy = np.mean(power_total)
|
||||||
|
std_energy = np.std(power_total)
|
||||||
|
|
||||||
|
# Define a threshold value as mean minus a certain number of standard deviations
|
||||||
|
threshold_value = mean_energy - VALLEY_DETECTION_THRESHOLD * std_energy
|
||||||
|
|
||||||
|
# Find valleys in power_total based on the threshold
|
||||||
|
in_valley = False
|
||||||
|
start_idx = 0
|
||||||
|
for i, value in enumerate(power_total):
|
||||||
|
if not in_valley and value < threshold_value:
|
||||||
|
in_valley = True
|
||||||
|
start_idx = i
|
||||||
|
elif in_valley and value >= threshold_value:
|
||||||
|
in_valley = False
|
||||||
|
valleys.append((start_idx, i))
|
||||||
|
|
||||||
|
# If the last point is still in a valley, close the valley
|
||||||
|
if in_valley:
|
||||||
|
valleys.append((start_idx, len(power_total) - 1))
|
||||||
|
|
||||||
|
max_signal = np.max(power_total)
|
||||||
|
|
||||||
|
# Calculate mean energy for each valley as a percentage of the maximum of the signal
|
||||||
|
valley_means_percentage = []
|
||||||
|
for start, end in valleys:
|
||||||
|
if not np.isnan(np.mean(power_total[start:end])):
|
||||||
|
valley_means_percentage.append((start, end, (np.mean(power_total[start:end]) / max_signal) * 100))
|
||||||
|
|
||||||
|
# Sort valleys based on mean percentage values
|
||||||
|
sorted_valleys = sorted(valley_means_percentage, key=lambda x: x[2])
|
||||||
|
|
||||||
|
return sorted_valleys
|
||||||
|
|
||||||
|
|
||||||
|
# Resample the signal to achieve denser data points in order to get more precise valley placing and
|
||||||
|
# avoid having to use the original sampling of the signal (that is equal to the speed increment used for the test)
|
||||||
|
def resample_signal(speeds, power_total, new_spacing=0.1):
|
||||||
|
new_speeds = np.arange(speeds[0], speeds[-1] + new_spacing, new_spacing)
|
||||||
|
new_power_total = np.interp(new_speeds, speeds, power_total)
|
||||||
|
return new_speeds, new_power_total
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Graphing
|
# Graphing
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def plot_total_power(ax, speeds, power_total):
|
def plot_total_power(ax, speeds, power_total):
|
||||||
ax.set_title('Vibrations decomposition')
|
resampled_speeds, resampled_power_total = resample_signal(speeds, power_total[0])
|
||||||
|
|
||||||
|
ax.set_title("Vibrations decomposition", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
||||||
ax.set_xlabel('Speed (mm/s)')
|
ax.set_xlabel('Speed (mm/s)')
|
||||||
ax.set_ylabel('Energy')
|
ax.set_ylabel('Energy')
|
||||||
|
|
||||||
|
ax2 = ax.twinx()
|
||||||
|
ax2.yaxis.set_visible(False)
|
||||||
|
|
||||||
|
power_total_sum = np.array(resampled_power_total)
|
||||||
|
speed_array = np.array(resampled_speeds)
|
||||||
|
max_y = power_total_sum.max() + power_total_sum.max() * 0.05
|
||||||
|
ax.set_xlim([speed_array.min(), speed_array.max()])
|
||||||
|
ax.set_ylim([0, max_y])
|
||||||
|
ax2.set_ylim([0, max_y])
|
||||||
|
|
||||||
ax.plot(speeds, power_total[0], label="X+Y+Z", alpha=0.6)
|
ax.plot(resampled_speeds, resampled_power_total, label="X+Y+Z", color='purple')
|
||||||
ax.plot(speeds, power_total[1], label="X", alpha=0.6)
|
ax.plot(speeds, power_total[1], label="X", color='red')
|
||||||
ax.plot(speeds, power_total[2], label="Y", alpha=0.6)
|
ax.plot(speeds, power_total[2], label="Y", color='green')
|
||||||
ax.plot(speeds, power_total[3], label="Z", alpha=0.6)
|
ax.plot(speeds, power_total[3], label="Z", color='blue')
|
||||||
|
|
||||||
|
peaks, num_peaks = detect_peaks(resampled_power_total, resampled_speeds)
|
||||||
|
low_energy_zones = identify_low_energy_zones(resampled_power_total)
|
||||||
|
|
||||||
|
if peaks.size:
|
||||||
|
ax.plot(speed_array[peaks], power_total_sum[peaks], "x", color='black', markersize=8)
|
||||||
|
for idx, peak in enumerate(peaks):
|
||||||
|
fontcolor = 'red'
|
||||||
|
fontweight = 'bold'
|
||||||
|
ax.annotate(f"{idx+1}", (speed_array[peak], power_total_sum[peak]),
|
||||||
|
textcoords="offset points", xytext=(8, 5),
|
||||||
|
ha='left', fontsize=13, color=fontcolor, weight=fontweight)
|
||||||
|
ax2.plot([], [], ' ', label=f'Number of peaks: {num_peaks}')
|
||||||
|
else:
|
||||||
|
ax2.plot([], [], ' ', label=f'No peaks detected')
|
||||||
|
|
||||||
|
for idx, (start, end, energy) in enumerate(low_energy_zones):
|
||||||
|
ax.axvline(speed_array[start], color='red', linestyle='dotted', linewidth=1.5)
|
||||||
|
ax.axvline(speed_array[end], color='red', linestyle='dotted', linewidth=1.5)
|
||||||
|
ax2.fill_between(speed_array[start:end], 0, power_total_sum[start:end], color='green', alpha=0.2, label=f'Zone {idx+1}: {speed_array[start]:.1f} to {speed_array[end]:.1f} mm/s (mean energy: {energy:.2f}%)')
|
||||||
|
|
||||||
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
||||||
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator())
|
||||||
ax.grid(which='major', color='grey')
|
ax.grid(which='major', color='grey')
|
||||||
ax.grid(which='minor', color='lightgrey')
|
ax.grid(which='minor', color='lightgrey')
|
||||||
fontP = matplotlib.font_manager.FontProperties()
|
fontP = matplotlib.font_manager.FontProperties()
|
||||||
fontP.set_size('medium')
|
fontP.set_size('small')
|
||||||
ax.legend(loc='best', prop=fontP)
|
ax.legend(loc='upper left', prop=fontP)
|
||||||
|
ax2.legend(loc='upper right', prop=fontP)
|
||||||
|
|
||||||
return
|
if peaks.size:
|
||||||
|
return speed_array[peaks]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def plot_spectrogram(ax, speeds, freqs, power_spectral_densities, max_freq):
|
def plot_spectrogram(ax, speeds, freqs, power_spectral_densities, peaks, max_freq):
|
||||||
spectrum = np.empty([len(freqs), len(speeds)])
|
spectrum = np.empty([len(freqs), len(speeds)])
|
||||||
|
|
||||||
for i in range(len(speeds)):
|
for i in range(len(speeds)):
|
||||||
for j in range(len(freqs)):
|
for j in range(len(freqs)):
|
||||||
spectrum[j, i] = power_spectral_densities[i][0][j]
|
spectrum[j, i] = power_spectral_densities[i][0][j]
|
||||||
|
|
||||||
ax.set_title("Summed vibrations spectrogram")
|
ax.set_title("Vibrations spectrogram", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
||||||
ax.pcolormesh(speeds, freqs, spectrum, norm=matplotlib.colors.LogNorm(),
|
ax.pcolormesh(speeds, freqs, spectrum, norm=matplotlib.colors.LogNorm(),
|
||||||
cmap='inferno', shading='gouraud')
|
cmap='inferno', shading='gouraud')
|
||||||
|
|
||||||
|
# Add peaks lines in the spectrogram to get hint from peaks found in the first graph
|
||||||
|
if peaks is not None:
|
||||||
|
for idx, peak in enumerate(peaks):
|
||||||
|
ax.axvline(peak, color='cyan', linestyle='dotted', linewidth=0.75)
|
||||||
|
ax.annotate(f"Peak {idx+1}", (peak, freqs[-1]*0.9),
|
||||||
|
textcoords="data", color='cyan', rotation=90, fontsize=10,
|
||||||
|
verticalalignment='top', horizontalalignment='right')
|
||||||
|
|
||||||
ax.set_ylim([0., max_freq])
|
ax.set_ylim([0., max_freq])
|
||||||
ax.set_ylabel('Frequency (hz)')
|
ax.set_ylabel('Frequency (hz)')
|
||||||
ax.set_xlabel('Speed (mm/s)')
|
ax.set_xlabel('Speed (mm/s)')
|
||||||
@@ -217,17 +363,32 @@ def vibrations_calibration(lognames, klipperdir="~/klipper", axisname=None, max_
|
|||||||
freqs, power_spectral_densities = calc_psd(datas, group_by, max_freq)
|
freqs, power_spectral_densities = calc_psd(datas, group_by, max_freq)
|
||||||
power_total = calc_powertot(power_spectral_densities, freqs)
|
power_total = calc_powertot(power_spectral_densities, freqs)
|
||||||
|
|
||||||
fig, (ax1, ax2) = matplotlib.pyplot.subplots(2, 1, sharex=True)
|
fig = matplotlib.pyplot.figure()
|
||||||
fig.suptitle("Machine vibrations - " + axisname + " moves", fontsize=16)
|
gs = matplotlib.gridspec.GridSpec(2, 1, height_ratios=[4, 3])
|
||||||
|
ax1 = fig.add_subplot(gs[0])
|
||||||
|
ax2 = fig.add_subplot(gs[1])
|
||||||
|
|
||||||
|
filename_parts = (lognames[0].split('/')[-1]).split('_')
|
||||||
|
dt = datetime.strptime(f"{filename_parts[1]} {filename_parts[2].split('-')[0]}", "%Y%m%d %H%M%S")
|
||||||
|
title_line1 = "VIBRATIONS MEASUREMENT TOOL"
|
||||||
|
title_line2 = dt.strftime('%x %X') + ' -- ' + axisname.upper() + ' axis'
|
||||||
|
fig.text(0.12, 0.965, title_line1, ha='left', va='bottom', fontsize=20, color=KLIPPAIN_COLORS['purple'], weight='bold')
|
||||||
|
fig.text(0.12, 0.957, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
||||||
|
|
||||||
# Remove speeds duplicates and graph the processed datas
|
# Remove speeds duplicates and graph the processed datas
|
||||||
speeds = list(OrderedDict((x, True) for x in speeds).keys())
|
speeds = list(OrderedDict((x, True) for x in speeds).keys())
|
||||||
plot_total_power(ax1, speeds, power_total)
|
|
||||||
plot_spectrogram(ax2, speeds, freqs, power_spectral_densities, max_freq)
|
|
||||||
|
|
||||||
fig.set_size_inches(10, 10)
|
peaks = plot_total_power(ax1, speeds, power_total)
|
||||||
|
plot_spectrogram(ax2, speeds, freqs, power_spectral_densities, peaks, max_freq)
|
||||||
|
|
||||||
|
fig.set_size_inches(8.3, 11.6)
|
||||||
fig.tight_layout()
|
fig.tight_layout()
|
||||||
fig.subplots_adjust(top=0.92)
|
fig.subplots_adjust(top=0.89)
|
||||||
|
|
||||||
|
# Adding a small Klippain logo to the top left corner of the figure
|
||||||
|
ax_logo = fig.add_axes([0.001, 0.899, 0.1, 0.1], anchor='NW', zorder=-1)
|
||||||
|
ax_logo.imshow(matplotlib.pyplot.imread(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'klippain.png')))
|
||||||
|
ax_logo.axis('off')
|
||||||
|
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|||||||
10
README.md
@@ -11,9 +11,11 @@ It operates in two steps:
|
|||||||
2. Relocates the graphs and associated CSV files to your Klipper config folder for easy access via Mainsail/Fluidd to eliminate the need for SSH.
|
2. Relocates the graphs and associated CSV files to your Klipper config folder for easy access via Mainsail/Fluidd to eliminate the need for SSH.
|
||||||
3. Manages the folder by retaining only the most recent results (default setting of keeping the latest three sets).
|
3. Manages the folder by retaining only the most recent results (default setting of keeping the latest three sets).
|
||||||
|
|
||||||
| Belts graphs | X graphs | Y graphs | Vibrations measurement |
|
The [detailed documentation is here](./docs/README.md).
|
||||||
|:----------------:|:------------:|:------------:|:---------------------:|
|
|
||||||
|  |  |  |  |
|
| Belts graphs | Axis graphs | Vibrations measurement |
|
||||||
|
|:----------------:|:------------:|:---------------------:|
|
||||||
|
|  |  |  |
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -46,4 +48,4 @@ Ensure your machine is homed, then invoke one of the following macros as needed:
|
|||||||
- `VIBRATIONS_CALIBRATION` for machine vibration graphs to optimize your slicer speed profiles.
|
- `VIBRATIONS_CALIBRATION` for machine vibration graphs to optimize your slicer speed profiles.
|
||||||
- `EXCITATE_AXIS_AT_FREQ` to sustain a specific excitation frequency, useful to let you inspect and find out what is resonating.
|
- `EXCITATE_AXIS_AT_FREQ` to sustain a specific excitation frequency, useful to let you inspect and find out what is resonating.
|
||||||
|
|
||||||
For further insights on the usage of the macros and the generated graphs, refer to the [K-Shake&Tune module documentation](./docs/k_shaketune.md).
|
For further insights on the usage of the macros and the generated graphs, refer to the [K-Shake&Tune module documentation](./docs/README.md).
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
# Klippain Shake&Tune module documentation
|
# Klippain Shake&Tune module documentation
|
||||||
|
|
||||||
|
|
||||||
### Detailed documentation
|
### Detailed documentation
|
||||||
|
|
||||||
1. [Input Shaping and tuning generalities](./is_tuning_generalities.md)
|
1. [Input Shaping and tuning generalities](./is_tuning_generalities.md)
|
||||||
1. [Belt graphs](./macros/belts_tuning.md)
|
1. [Belt graphs](./macros/belts_tuning.md)
|
||||||
1. [Axis Input Shaper graphs](./macros/axis_tuning.md)
|
1. [Axis Input Shaper graphs](./macros/axis_tuning.md)
|
||||||
1. [Klippain vibrations graphs](./macros/vibrations_tuning.md)
|
1. [Klippain vibrations graphs](./macros/vibrations_tuning.md)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Complementary ressources
|
||||||
|
|
||||||
|
- [Sineos post](https://klipper.discourse.group/t/interpreting-the-input-shaper-graphs/9879) in the Klipper knowledge base
|
||||||
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 745 KiB |
|
Before Width: | Height: | Size: 754 KiB |
BIN
docs/images/axis_example.png
Normal file
|
After Width: | Height: | Size: 641 KiB |
BIN
docs/images/belt_graphs/belt_graph_explanation.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
docs/images/belt_graphs/beltpath_problem1.png
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
docs/images/belt_graphs/beltpath_problem2.png
Normal file
|
After Width: | Height: | Size: 333 KiB |
BIN
docs/images/belt_graphs/beltpath_problem3.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
BIN
docs/images/belt_graphs/good_graph2.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
docs/images/belt_graphs/low_A_tension.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
docs/images/belt_graphs/low_B_tension.png
Normal file
|
After Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 590 KiB After Width: | Height: | Size: 590 KiB |
BIN
docs/images/generalities/harmonic_oscil.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
BIN
docs/images/shaper_graphs/TAP_125hz_2.png
Normal file
|
After Width: | Height: | Size: 196 KiB |
BIN
docs/images/shaper_graphs/bad_racking.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
docs/images/shaper_graphs/bad_racking2.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
docs/images/shaper_graphs/binding.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
docs/images/shaper_graphs/binding2.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
BIN
docs/images/shaper_graphs/shaper_graph_explanation.png
Normal file
|
After Width: | Height: | Size: 455 KiB |
BIN
docs/images/shaper_graphs/shaper_recommandations.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
docs/images/shaper_graphs/unbalanced_fan_off.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
docs/images/shaper_graphs/unbalanced_fan_on.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 480 KiB After Width: | Height: | Size: 496 KiB |
BIN
docs/images/vibrations_graphs/vibration_graph_explanation.png
Normal file
|
After Width: | Height: | Size: 389 KiB |
@@ -1,13 +1,13 @@
|
|||||||
# Input shaping and tuning generalities
|
# Input shaping and tuning generalities
|
||||||
|
|
||||||
As more and more people use my macros, questions about interpreting the results or properly tuning/fixing a machine mechanical behavior arise. This document aims to provide some guidance on how to interpret them. Keep in mind that there is no universal method: different people may interpret the results differently or could have other opinions. It's important to experiment and find what works best for your own 3D printer.
|
As more and more people are using these macros, questions about interpreting the results or properly tuning/fixing a machine mechanical behavior arise. This document aims to provide some guidance on how to interpret them. Keep in mind that there is no universal method: different people may interpret the results differently or could have other opinions. It's important to experiment and find what works best for your own 3D printer.
|
||||||
|
|
||||||
|
|
||||||
## Understanding ringing
|
## Understanding ringing
|
||||||
|
|
||||||
When a 3D printer moves, the motors apply some force to move the toolhead along a precise path. This force is transmitted from the motor shaft to the toolhead through the entire printer motion system. When the toolhead reaches a sharp corner and needs to change direction, its inertia makes it want to continue the movement in a straight line. The motors force the toolhead to turn, but the belts act like springs, allowing the toolhead to oscillate in the perpendicular direction. These oscillations produce visible artifacts on the printed parts, known as ringing or ghosting.
|
When a 3D printer moves, the motors apply some force to move the toolhead along a precise path. This force is transmitted from the motor shaft to the toolhead through the entire printer motion system. When the toolhead reaches a sharp corner and needs to change direction, its inertia makes it want to continue the movement in a straight line. The motors force the toolhead to turn, but the belts act like springs, allowing the toolhead to oscillate in the perpendicular direction. These oscillations produce visible artifacts on the printed parts, known as ringing or ghosting.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Generalities on the graphs
|
## Generalities on the graphs
|
||||||
@@ -16,18 +16,20 @@ When tuning Input Shaper, keep the following in mind:
|
|||||||
1. **Focus on the shape of the graphs, not the exact numbers**. There could be differences between ADXL boards or even printers, so there is no specific "target" value. This means that you shouldn't expect to get the same graphs between different printers, even if they are similar in term of brand, parts, size and assembly.
|
1. **Focus on the shape of the graphs, not the exact numbers**. There could be differences between ADXL boards or even printers, so there is no specific "target" value. This means that you shouldn't expect to get the same graphs between different printers, even if they are similar in term of brand, parts, size and assembly.
|
||||||
1. Small differences between consecutive test runs are normal, as ADXL quality and sensitivity is quite variable between boards.
|
1. Small differences between consecutive test runs are normal, as ADXL quality and sensitivity is quite variable between boards.
|
||||||
1. Perform the tests when the machine is heat-soaked and close to printing conditions, as the temperature will impact the machine components such as belt tension or even the frame that is known to expand a little bit.
|
1. Perform the tests when the machine is heat-soaked and close to printing conditions, as the temperature will impact the machine components such as belt tension or even the frame that is known to expand a little bit.
|
||||||
1. Avoid running the toolhead fans during the tests, as they introduce unnecessary noise to the graphs, making them harder to interpret. This means that even if you should heatsoak the printer, you should also refrain from activating the hotend heater during the test, as it will also trigger the hotend fan. However, as a bad fan usually introduce some vibrations, you can use the test to diagnose an unbalanced fan as seen in the [Examples of Input Shaper graphs](#examples-of-input-shaper-graphs) section. # TODO: update the link here
|
1. Avoid running the toolhead fans during the tests, as they introduce unnecessary noise to the graphs, making them harder to interpret. This means that even if you should heatsoak the printer, you should also refrain from activating the hotend heater during the test, as it will also trigger the hotend fan. However, as a bad fan usually introduce some vibrations, you can use the test to diagnose an unbalanced fan as seen in the [Examples of Input Shaper graphs](./macros/axis_tuning.md) section.
|
||||||
1. Ensure the accuracy of your ADXL measurements by running a `MEASURE_AXES_NOISE` test and checking that the result is below 100 for all axes. If it's not, check your ADXL board and wiring before continuing.
|
1. Ensure the accuracy of your ADXL measurements by running a `MEASURE_AXES_NOISE` test and checking that the result is below 100 for all axes. If it's not, check your ADXL board and wiring before continuing.
|
||||||
1. The graphs can only show symptoms of possible problems and in different ways. Those symptoms can sometimes suggest causes, but they rarely pinpoint the exact issues. For example, while you may be able to diagnose that some screws are not tightened properly, you will unlikely find which exact screw is problematic using only these tests. You will most always need to tinker and experiment.
|
1. The graphs can only show symptoms of possible problems and in different ways. Those symptoms can sometimes suggest causes, but they rarely pinpoint the exact issues. For example, while you may be able to diagnose that some screws are not tightened properly, you will unlikely find which exact screw is problematic using only these tests. You will most always need to tinker and experiment.
|
||||||
1. Finally, remember why you're running these tests: to get clean prints. Don't become too obsessive over perfect graphs, as the last bits of optimization will probably have the least impact on the printed parts in terms of ringing and ghosting.
|
1. Finally, remember why you're running these tests: to get clean prints. Don't become too obsessive over perfect graphs, as the last bits of optimization will probably have the least impact on the printed parts in terms of ringing and ghosting.
|
||||||
|
|
||||||
|
|
||||||
### Special note on accelerometer (ADXL) mounting point
|
### Special note on accelerometer (ADXL) mounting point
|
||||||
Input Shaping algorithms work by suppressing a single resonant frequency (or a range around a single resonant frequency). When setting the filter, **the primary goal is to target the resonant frequency of the toolhead and belts system** (see the [theory behind it](#theory-behind-it)), as this system has the most significant impact on print quality and is the root cause of ringing.
|
Input Shaping algorithms work by suppressing a single resonant frequency (or a range around a single resonant frequency). When setting the filter, **the primary goal is to target the resonant frequency of the toolhead and belts system** (see the [theory behind it](#theory-behind-it)), as this has the most significant impact on print quality and is the root cause of ringing.
|
||||||
|
|
||||||
When setting up Input Shaper, it is important to consider the accelerometer mounting point. There are mainly two possibilities, each with its pros and cons:
|
When setting up Input Shaper, it is important to consider the accelerometer mounting point. There are mainly two possibilities, each with its pros and cons:
|
||||||
1. **Directly at the nozzle tip**: This method provides a more accurate and comprehensive measurement of everything in your machine. It captures the main resonant frequency along with other vibrations and movements, such as toolhead wobbling and printer frame movements. This approach is excellent for diagnosing your machine's kinematics and troubleshooting problems. However, it also leads to noisier graphs, making it harder for the algorithm to select the correct filter for input shaping. Graphs may appear worse, but this is due to the different "point of view" of the printer's behavior.
|
|
||||||
1. **At the toolhead's center of gravity**: I personally recommend mounting the accelerometer in this way, as it provides a clear view of the main resonant frequency you want to target, allowing for accurate input shaper filter settings. This approach results in cleaner graphs with less visible noise from other subsystem vibrations, making interpretation easier for both automatic algorithms and users. However, this method provides less detail in the graphs and may be slightly less effective for troubleshooting printer problems.
|
| Directly at the nozzle tip | Near the toolhead's center of gravity |
|
||||||
|
| --- | --- |
|
||||||
|
| This method provides a more accurate and comprehensive measurement of everything in your machine. It captures the main resonant frequency along with other vibrations and movements, such as toolhead wobbling and printer frame movements. This approach is excellent for diagnosing your machine's kinematics and troubleshooting problems. However, it also leads to noisier graphs, making it harder for the algorithm to select the correct filter for input shaping. Graphs may appear worse, but this is due to the different "point of view" of the printer's behavior. | I personally recommend mounting the accelerometer in this way, as it provides a clear view of the main resonant frequency you want to target, allowing for accurate input shaper filter settings. This approach results in cleaner graphs with less visible noise from other subsystem vibrations, making interpretation easier for both automatic algorithms and users. However, this method provides less detail in the graphs and may be slightly less effective for troubleshooting printer problems. |
|
||||||
|
|
||||||
A suggested workflow is to first use the nozzle mount to diagnose mechanical issues, such as loose screws or a bad X carriage. Once the mechanics are in good condition, switch to a mounting point closer to the toolhead's center of gravity for setting the input shaper filter settings by using cleaner graphs that highlights the most impactful frequency.
|
A suggested workflow is to first use the nozzle mount to diagnose mechanical issues, such as loose screws or a bad X carriage. Once the mechanics are in good condition, switch to a mounting point closer to the toolhead's center of gravity for setting the input shaper filter settings by using cleaner graphs that highlights the most impactful frequency.
|
||||||
|
|
||||||
@@ -35,12 +37,14 @@ A suggested workflow is to first use the nozzle mount to diagnose mechanical iss
|
|||||||
## Theory behind it
|
## Theory behind it
|
||||||
|
|
||||||
### Modeling the motion system
|
### Modeling the motion system
|
||||||
The motion system of a 3D printer can be described as a spring and mass system, best modeled as a [harmonic oscillator](https://en.wikipedia.org/wiki/Harmonic_oscillator). This type of system has two key parameters:
|
The motion system of a 3D printer can be described as a spring and mass system, best modeled as an harmonic oscillator. Have a look on [this Wikipedia article](https://en.wikipedia.org/wiki/Harmonic_oscillator) or [here](https://beltoforion.de/en/harmonic_oscillator/) for some examples. This type of system has two key parameters:
|
||||||
|
|
||||||
| Schematics | Undamped resonnant frequency<br />(natural frequency) | Damping ratio ζ |
|
| Undamped resonnant frequency<br />(natural frequency) | Damping ratio ζ |
|
||||||
| --- | --- | --- |
|
| --- | --- |
|
||||||
|  | $$\frac{1}{2\pi}\sqrt{\frac{k}{m}}$$ | $$\frac{c}{2}\sqrt{\frac{1}{km}}$$ |
|
| $$\frac{1}{2\pi}\sqrt{\frac{k}{m}}$$ | $$\frac{c}{2}\sqrt{\frac{1}{km}}$$ |
|
||||||
| See [here for examples](https://beltoforion.de/en/harmonic_oscillator/) | `k` [N/m]: spring constant<br />`m` [g]: moving mass | `c` [N·s/m]: viscous damping coefficient<br />`k` [N/m]: spring constant<br />`m` [g]: moving mass |
|
| `k` [N/m]: spring constant<br />`m` [g]: moving mass | `c` [N·s/m]: viscous damping coefficient<br />`k` [N/m]: spring constant<br />`m` [g]: moving mass |
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
When an oscillating input force is applied at a resonant frequency (or a Fourier component of it) on a dynamic system, the system will oscillate at a higher amplitude than when the same force is applied at other, non-resonant frequencies. This is called a resonance and can be dangerous for some systems but on our printers this will mainly lead to vibrations and oscillations of the toolhead.
|
When an oscillating input force is applied at a resonant frequency (or a Fourier component of it) on a dynamic system, the system will oscillate at a higher amplitude than when the same force is applied at other, non-resonant frequencies. This is called a resonance and can be dangerous for some systems but on our printers this will mainly lead to vibrations and oscillations of the toolhead.
|
||||||
|
|
||||||
@@ -53,6 +57,6 @@ The rapid movement of machines is a challenging control problem because it often
|
|||||||
|
|
||||||
It works by creating a command signal that cancels its own vibration, achieved by [convoluting](https://en.wikipedia.org/wiki/Convolution) specifically crafted impulse signals (A2) with the original system control signal (A1). The resulting shaped signal is then used to drive the system (Total Response). To craft these impulses, the system's undamped resonant frequency and damping ratio are used.
|
It works by creating a command signal that cancels its own vibration, achieved by [convoluting](https://en.wikipedia.org/wiki/Convolution) specifically crafted impulse signals (A2) with the original system control signal (A1). The resulting shaped signal is then used to drive the system (Total Response). To craft these impulses, the system's undamped resonant frequency and damping ratio are used.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Klipper measures these parameters by exciting the printer with a series of input commands and recording the response behavior using an accelerometer. Resonances can be identified on the resulting graphs by large spikes indicating their frequency and energy. Additionnaly, the damping ratio is usually unknown and hard to estimate without a special equipment, so Klipper uses 0.1 value by default, which is a good all-round value that works well for most 3D printers.
|
Klipper measures these parameters by exciting the printer with a series of input commands and recording the response behavior using an accelerometer. Resonances can be identified on the resulting graphs by large spikes indicating their frequency and energy. Additionnaly, the damping ratio is usually hard to measure without a special equipment, but these scripts gives an estimation that should be good enough in most cases. There is no need to use Klipper's default 0.1 value anymore!
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ The `AXES_SHAPER_CALIBRATION` macro is used to measure and plot the axis behavio
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
**Before starting, ensure that the belts are properly tensioned** and that you already have good and clear belt graphs (see the previous section).
|
**Before starting, ensure that the belts are properly tensioned** and that you already have good and clear belt graphs (see [the previous section](./belts_tuning.md)).
|
||||||
|
|
||||||
Then, call the `AXES_SHAPER_CALIBRATION` macro and look for the graphs in the results folder. Here are the parameters available:
|
Then, call the `AXES_SHAPER_CALIBRATION` macro and look for the graphs in the results folder. Here are the parameters available:
|
||||||
|
|
||||||
@@ -20,57 +20,132 @@ Then, call the `AXES_SHAPER_CALIBRATION` macro and look for the graphs in the re
|
|||||||
|
|
||||||
## Graphs description
|
## Graphs description
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Analysis of the results
|
## Analysis of the results
|
||||||
|
|
||||||
#### Generalities
|
### Generalities
|
||||||
|
|
||||||
To effectively analyze input shaper graphs, there is no one-size-fits-all approach due to the variety of factors that can impact the 3D printer's performance or input shaper measurements. However, here are some hints on reading the graphs:
|
To effectively analyze input shaper graphs, there is no one-size-fits-all approach due to the variety of factors that can impact the 3D printer's performance or input shaper measurements. However, here are some hints on reading the graphs:
|
||||||
- A graph with a **single and thin peak** well detached from the background noise is ideal, as it can be easily filtered by input shaping. But depending on the machine and its mechanical configuration, it's not always possible to obtain this shape. The key to getting better graphs is a clean mechanical assembly with a special focus on the rigidity and stiffness of everything, from the table under the printer through the frame of the printer to the toolhead.
|
- A graph with a **single and thin peak** well detached from the background noise is ideal, as it can be easily filtered by input shaping. But depending on the machine and its mechanical configuration, it's not always possible to obtain this shape. The key to getting better graphs is a clean mechanical assembly with a special focus on the rigidity and stiffness of everything, from the table the printer sits on to the frame and the toolhead.
|
||||||
- As for the belt graphs, **focus on the shape of the graphs, not the exact frequency and energy value**. Indeed, the energy value doesn't provide much useful information. Use it only to compare two of your own graphs and to measure the impact of your mechanical changes between two consecutive tests, but never use it to compare against graphs from other people or other machines.
|
- As for the belt graphs, **focus on the shape of the graphs, not the values**. Indeed, the energy value doesn't provide much useful information. Use it only to compare two of your own graphs and to measure the impact of your mechanical changes between two consecutive tests, but never use it to compare against graphs from other people or other machines.
|
||||||
|
|
||||||
When you are satisfied with your graphs, you will need to use the auto-computed values at the top to set the Input Shaping filters in your Klipper configuration.
|

|
||||||
|
|
||||||

|
For setting your Input Shaping filters, rely on the auto-computed values displayed in the top right corner of the graph. Here's a breakdown of the legend for a better grasp:
|
||||||
|
- **Filtering algortihms**: Klipper automatically computes these lines. This computation works pretty well if the graphs are clean enough. But if your graphs are junk, it can't do magic and will give you pretty bad recommendations. It's better to address the mechanical issues first before continuing. Each shapers has its pro and cons:
|
||||||
Here is some info to help you understand them:
|
|
||||||
- These data are automatically computed by a specific Klipper algorithm. This algorithm works pretty well if the graphs are clean enough. But **if your graphs are junk, it can't do magic and will give you pretty bad recommendations**: they will do nothing or even make the ringing worse, so do not use the values and fix your printer first!
|
|
||||||
- The recommended acceleration values (`accel<=...`) are not meant to be read alone. You need to also look at the `vibr` and `sm` values. They will give you the percentage of remaining vibrations and the smoothing after Input Shaping, if you use the recommended acceleration.
|
|
||||||
- Nothing will prevent you from using higher acceleration values; they are not a limit. However, if you do so, expect more vibrations and smoothing. Also, Input Shaping may find its limits and not be able to suppress all the ringing on your parts.
|
|
||||||
- The remaining vibrations `vibr` value is highly linked to ringing. So try to choose a filter with a very low value or even 0% if possible.
|
|
||||||
- High acceleration values are not useful at all if there is still a high level of remaining vibrations. You should address any mechanical issues before continuing.
|
|
||||||
- Each line represents the name of a different filtering algorithm. Each of them has its pros and cons:
|
|
||||||
* `ZV` is a pretty light filter and usually has some remaining vibrations. My recommendation would be to use it only if you want to do speed benchies and get the highest acceleration values while maintaining a low amount of smoothing on your parts. If you have "perfect" graphs and do not care that much about some remaining ringing, you can try it.
|
* `ZV` is a pretty light filter and usually has some remaining vibrations. My recommendation would be to use it only if you want to do speed benchies and get the highest acceleration values while maintaining a low amount of smoothing on your parts. If you have "perfect" graphs and do not care that much about some remaining ringing, you can try it.
|
||||||
* `MZV` is most of the time the best filter on a well-tuned machine. It's a good compromise for low remaining vibrations while still allowing pretty good acceleration values. Keep in mind, `MZV` is only recommended by the algorithm on good graphs.
|
* `MZV` is usually the top pick for well-adjusted machines. It's a good compromise for low remaining vibrations while still allowing pretty good acceleration values. Keep in mind, `MZV` is only recommended by Klipper on good graphs.
|
||||||
* `EI` works "ok" if you are not able to get better graphs. But first, try to fix your mechanical issues as best as you can before using it: almost every printer should be able to run `MZV` instead.
|
* `EI` can be used as a fallback for challenging graphs. But first, try to fix your mechanical issues before using it: almost every printer should be able to run `MZV` instead.
|
||||||
* `2HUMP_EI` and `3HUMP_EI` are not recommended and should be used only as a last resort. Usually, they lead to a high level of smoothing in order to suppress the ringing while also using relatively low acceleration values. If you get these algorithms recommended, you can almost be sure that you have mechanical problems under the hood (that lead to pretty bad or "wide" graphs).
|
* `2HUMP_EI` and `3HUMP_EI` are last-resort choices. Usually, they lead to a high level of smoothing in order to suppress the ringing while also using relatively low acceleration values. If they pop up as suggestions, it's likely your machine has underlying mechanical issues (that lead to pretty bad or "wide" graphs).
|
||||||
|
- **Recommended Acceleration** (`accel<=...`): This isn't a standalone figure. It's essential to also consider the `vibr` and `sm` values as it's a compromise between the three. They will give you the percentage of remaining vibrations and the smoothing after Input Shaping, when using the recommended acceleration. Nothing will prevent you from using higher acceleration values; they are not a limit. However, when doing so, Input Shaping may not be able to suppress all the ringing on your parts. Finally, keep in mind that high acceleration values are not useful at all if there is still a high level of remaining vibrations: you should address any mechanical issues first.
|
||||||
|
- **The remaining vibrations** (`vibr`): This directly correlates with ringing. It correspond to the total value of the blue "after shaper" signal. Ideally, you want a filter with minimal or zero vibrations.
|
||||||
|
- **Shaper recommendations**: This script will give you two recommandation. Pick the one that suit your needs:
|
||||||
|
* The first is Klipper's original suggestion, for best performance and acceleration on your machine while also allowing a little bit of remaining vibrations.
|
||||||
|
* The second aims for no remaining vibration to ensure the best print quality.
|
||||||
|
- The final line provides the estimated damping ratio for the axis. This value is generated automatically and is only accurate if the graph displays a clear and well detached single peak.
|
||||||
|
- **Damping Ratio**: Displayed at the end, this estimatation is only reliable when the graph shows a distinct, standalone and clean peak. On a well tuned machine, setting the damping ratio (instead of Klipper's 0.1 default value) can further reduce the ringing at high accelerations and with higher square corner velocities.
|
||||||
|
|
||||||
Then, just add to your configuration:
|
Then, add to your configuration:
|
||||||
```
|
```
|
||||||
[input_shaper]
|
[input_shaper]
|
||||||
shaper_freq_x: ... # center frequency for the X axis filter
|
shaper_freq_x: ... # center frequency for the X axis filter
|
||||||
shaper_type_x: ... # filter type for the X axis
|
shaper_type_x: ... # filter type for the X axis
|
||||||
shaper_freq_y: ... # center frequency for the Y axis filter
|
shaper_freq_y: ... # center frequency for the Y axis filter
|
||||||
shaper_type_y: ... # filter type for the Y axis
|
shaper_type_y: ... # filter type for the Y axis
|
||||||
|
damping_ratio_x: ... # damping ratio for the X axis
|
||||||
|
damping_ratio_y: ... # damping ratio for the Y axis
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Useful facts and myths debunking
|
### Useful facts and myths debunking
|
||||||
|
|
||||||
Sometimes people advise limiting the data to 100 Hz by manually editing the resulting .csv file because excitation does not go that high and these values should be ignored and considered wrong. This is a misconception and a bad idea because the excitation frequency is very different from the response frequency of the system, and they are not correlated at all. Indeed, it's plausible to get higher vibration frequencies, and editing the file manually will just "ignore" them and make them invisible even if they are still there on your printer. While higher frequency vibrations may not have a substantial effect on print quality, they can still indicate other issues within the system, likely noise and wear to the mechanical parts. Instead, focus on addressing the mechanical issues causing these problems.
|
Some people suggest to cap data at 100 Hz by manually editing the .csv file, thinking values beyond that are wrong. But this can be misleading. The excitation and system's response frequencies differ, and aren't directly linked. You might see vibrations beyond the excitation range, and removing them from the file just hides potential issues. Though these high-frequency vibrations might not always affect print quality, they could signal mechanical problems. Instead of hiding them, look into resolving these issues.
|
||||||
|
|
||||||
Another point is that I do not recommend using an extra-light X-beam (aluminum or carbon) on your machine, as it can negatively impact the printer's performance and Input Shaping results. Indeed, there is more than just mass at play (see the [theory behind it](#theory-behind-it)): lower mass also means more flexibility and more prone to wobble under high accelerations. This will impact negatively the Y axis graphs as the X-beam will flex under high accelerations.
|
Regarding printer components, I do not recommend using an extra-light X-beam (aluminum or carbon). They might seem ideal due to their weight, but there's more to consider than just mass such as the rigidity (see the [theory behind it](../is_tuning_generalities.md#theory-behind-it)). These light beams can be more flexible and will impact negatively the Y axis graphs as they will flex under high accelerations.
|
||||||
|
|
||||||
Finally, keep in mind that each axis has its own properties, such as mass and geometry, which will lead to different behaviors for each of them and will require different filters. Using the same input shaping settings for both axes is only valid if both axes are similar mechanically: this may be true for some machines, mainly Cross gantry configurations such as [CroXY](https://github.com/CroXY3D/CroXY) or [Annex-Engineering](https://github.com/Annex-Engineering) printers, but not for others.
|
Finally, keep in mind that each axis has its own properties, such as mass and geometry, which will lead to different behaviors for each of them and will require different filters. Using the same input shaping settings for both axes is only valid if both axes are similar mechanically: this may be true for some machines, mainly Cross gantry configurations such as [CroXY](https://github.com/CroXY3D/CroXY) or [Annex-Engineering](https://github.com/Annex-Engineering) printers, but not for others.
|
||||||
|
|
||||||
|
|
||||||
## Examples of graphs
|
## Examples of graphs
|
||||||
|
|
||||||
In the following examples, the graphs are random graphs found online or sent to me for analysis. They are not necessarily to be read in pairs: the two graph columns are here to illustrate the comment with more than one example.
|
In this section, I'll walk you through some random graphs sourced online or shared with me for analysis. My aim is to highlight the good and the not-so-good, offering insights to help you refine your printer's Input Shaping settings.
|
||||||
|
|
||||||
| Comment | Example 1 | Example 2 |
|
That said, interpreting Input Shaper graphs isn't an exact science. While we can make educated guesses and highlight potential issues from these graphs, pinpointing exact causes isn't always feasible. So, consider the upcoming graphs and their comments as pointers on your input shaping journey, rather than hard truths.
|
||||||
| --- | --- | --- |
|
|
||||||
| **These two graphs are considered good**. As you can see, there is only one thin peak, well separated from the background noise |  |  |
|
### Good graphs
|
||||||
| **These two graphs are really bad**: there is a lot of noise all over the spectrum. Something is really wrong and you should check all moving parts and screws. You should also check the belt tension and proper geometry of the gantry (racking) |  |  |
|
|
||||||
| These two graphs have some **low frequency energy**. This usually means that there is some binding or grinding in the kinematics: something isn't moving freely. Check the belt alignment on the idlers, bearings, etc... |  |  |
|
These two graphs are considered good and is what you're aiming for. They each display a single, distinct peak that stands out clearly against the background noise. Note that the main frequencies of the X and Y graph peaks differ. This variance is expected and normal, as explained in the last point of the [useful facts and myths debunking](#useful-facts-and-myths-debunking) section.
|
||||||
| These two graphs show **the TAP wobble problem**: check that the TAP MGN rail has the correct preload for stiffness and that the magnets are correct N52. Also pay attention to the assembly to make sure that everything is properly tightened |  |  |
|
|
||||||
| Here you can see **the effect of an unbalanced fan**: even if you should let the fan off during the final IS tuning, you can use this test to validate their correct behavior: an unbalanced fan usually add some very thin peak around 100-150Hz that disapear when the fan is off during the measurement |  |  |
|
| Good X graph | Good Y graph |
|
||||||
| The graph on the left shows **a CANbus problem** (problem solved on the right): although the general shape looks good, the graph is not smooth but spiky. There is also usually some low frequency energy. This happens when the bus speed is too low: set it to 1M to solve the problem |  |  |
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
### Low frequency energy
|
||||||
|
|
||||||
|
These graphs have some low frequency energy (signal near 0 Hz) on a rather low maximum amplitude (around 1e2 or 1e3). This means that there is some binding, rubbing or grinding during movements: basically, something isn't moving freely. Minor low frequency energy in the graphs might be due to a lot of issues such as a faulty idler/bearing or an overly tightened carriage screw that prevent it to move freely on its linear rail, ... However, major low frequency energy suggest more important problems like improper belt routing (the most common), or defective motor, ...
|
||||||
|
|
||||||
|
Here's how to troubleshoot the issue:
|
||||||
|
1. **Belts Examination**:
|
||||||
|
- Ensure your belts are properly routed.
|
||||||
|
- Check for correct alignment of the belts on all bearing flanges during movement (check them during a print).
|
||||||
|
- Belt dust is often a sign of misalignment or wear.
|
||||||
|
2. **Toolhead behavior on CoreXY printers**: With motors off and the toolhead centered, gently push the Y-axis front-to-back. The toolhead shouldn't move left or right. If it does, one of the belts might be obstructed and requires inspection to find out the problem.
|
||||||
|
3. **Gantry Squareness**:
|
||||||
|
- Ensure your gantry is perfectly parallel and square. You can refer to [Nero3D's de-racking video](https://youtu.be/cOn6u9kXvy0?si=ZCSdWU6br3Y9rGsy) for guidance.
|
||||||
|
- After removing the belts, test the toolhead's movement by hand across all positions. Movement should be smooth with no hard points or areas of resistance.
|
||||||
|
|
||||||
|
| Small binding | Heavy binding |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
### Double peaks or wide peaks
|
||||||
|
|
||||||
|
Such graph patterns can arise from various factors, and there isn't a one-size-fits-all solution. To address them:
|
||||||
|
1. A wobbly table can be the cause. So first thing to do is to try with the printer directly on the floor.
|
||||||
|
2. Ensure optimal belt tension using the [`BELTS_SHAPER_CALIBRATION` macro](./belts_tuning.md).
|
||||||
|
3. If problems persist, it might be due to an improperly squared gantry. For correction, refer to [Nero3D's de-racking video](https://youtu.be/cOn6u9kXvy0?si=ZCSdWU6br3Y9rGsy).
|
||||||
|
4. If it's still there... you will need to find out what is resonating to fix it. You can use the `EXCITATE_AXIS_AT_FREQ` macro to help you find it.
|
||||||
|
|
||||||
|
| Two peaks | Single wide peak |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
### Problematic CANBUS speed
|
||||||
|
|
||||||
|
Using CANBUS toolheads with an integrated ADXL chip can sometimes pose challenges if the CANBUS speed is set too low. While users might lower the bus speed to fix Klipper's timing errors, this change will also affect input shaping measurements. An example outcome of a low bus speed is the following graph that, though generally well-shaped, appears jagged and spiky throughout. Additional low-frequency energy might also be present. For optimal ADXL board operation on your CANBUS toolhead, a speed setting of 500k is the minimum, but 1M is advisable.
|
||||||
|
|
||||||
|
| CANBUS problem present | CANBUS problem solved |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
### Toolhead or TAP wobble
|
||||||
|
|
||||||
|
The [Voron TAP](https://github.com/VoronDesign/Voron-Tap) can introduce anomalies to input shaper graphs, notably on the X graph. Its design with an internal MGN rail introduces a separate and decoupled mass, leading to its own resonance, typically around 125Hz. Combatting this can be pretty challenging, but using premium components and a careful assembly can help mitigate the issue. Ensure you employ a good quality and well-preloaded TAP MGN rail for optimal assembly stiffness, coupled with genuine and strong N52 magnets (avoid lower-quality N35 or N45 substitutes often found on chinese marketplaces). Prioritize careful assembly and consider using the TAP Rev8 version or above.
|
||||||
|
|
||||||
|
Additionally, without a Voron TAP, small 125hz peaks can sometimes tie back to the toolhead itself. Common culprits include loosely fitted screws or a bad quality X linear MGN axis that can have some play in the carriage, leading to slight toolhead wobbling. This is often represented as a Z component in the graphs.
|
||||||
|
|
||||||
|
If your graph shows this kind of anomalies, begin by disassembling the toolhead up to the X carriage. Check for any looseness, then reassemble, ensuring everything is tightened properly for a rigid assembly. Also, don't forget to check your extruder and validate its assembly as well. Finally, ensure you have some filament loaded during measurements to prevent extruder gear vibrations.
|
||||||
|
|
||||||
|
| TAP wobble problem | TAP wobble problem partially mitigated<br/>Or toolhead wobbling |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
### Unbalanced fan
|
||||||
|
|
||||||
|
The presence of an unbalanced or badly running fan can be directly observed in the graphs. While you should let the toolhead fans off during the final IS tuning, you can use this test to validate their correct behavior: an unbalanced fan usually add some very thin peak around 100-150Hz that disapear when the fan is off. Also please note that an unbalanced fan constant frequency is manifested as a vertical line on the bottom spectrogram.
|
||||||
|
|
||||||
|
| Unbalanced fan running | Unbalanced fan off |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
### Crazy graphs and miscs
|
||||||
|
|
||||||
|
The depicted graphs are challenging to analyze due to the overwhelming noise across the spectrum. Such patterns are often associated with an improperly assembled and non-squared mechanical structure. To address this:
|
||||||
|
1. Refer to the [Low frequency energy](#low-frequency-energy) section for troubleshooting steps.
|
||||||
|
2. If unresolved, consider disassembling the entire gantry, inspect the printed and mechanical components, and ensure meticulous reassembly. A thorough and careful assembly should help alleviate the issue. Measure again post-assembly for changes.
|
||||||
|
|
||||||
|
Also please note that for this kind of graphs, as they are mainly consisting of noise, Klipper's algorithm recommendations must not be used and will not help with ringing. You will need to fix your mechanical issues instead!
|
||||||
|
|
||||||
|
| Crazy X | Crazy Y |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ The `BELTS_SHAPER_CALIBRATION` macro is dedicated for CoreXY machines where it c
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
**Before starting, ensure that the belts are properly tensioned**. For example, you can follow the [Voron belt tensioning documentation](https://docs.vorondesign.com/tuning/secondary_printer_tuning.html#belt-tension). This is crucial!
|
**Before starting, ensure that the belts are properly tensioned**. For example, you can follow the [Voron belt tensioning documentation](https://docs.vorondesign.com/tuning/secondary_printer_tuning.html#belt-tension). This is crucial: you need a good starting point to then iterate from it!
|
||||||
|
|
||||||
Then, call the `BELTS_SHAPER_CALIBRATION` macro and look for the graphs in the results folder. Here are the parameters available:
|
Then, call the `BELTS_SHAPER_CALIBRATION` macro and look for the graphs in the results folder. Here are the parameters available:
|
||||||
|
|
||||||
@@ -19,22 +19,53 @@ Then, call the `BELTS_SHAPER_CALIBRATION` macro and look for the graphs in the r
|
|||||||
|
|
||||||
## Graphs description
|
## Graphs description
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Analysis of the results
|
## Analysis of the results
|
||||||
|
|
||||||
On these graphs, **you want both curves to look similar and overlap to form a single curve**: try to make them fit as closely as possible in frequency **and** in amplitude. Usually a belt graph is composed of one or two main peaks and more peaks can hint about mechanical problems. However, it's acceptable to have "noise" around the main peaks, but it should be present on both curves with a comparable amplitude. Keep in mind that when you tighten a belt, its peaks should move diagonally toward the upper right corner, changing significantly in amplitude and slightly in frequency. Additionally, the magnitude order of the main peaks *should typically* range from ~100k to ~1M on most machines.
|
On these graphs, **you want both curves to look similar and overlap to form a single curve**: try to make them fit as closely as possible in frequency **and** in amplitude. Usually a belt graph is composed of one or two main peaks (more than 2 peaks can hint about mechanical problems). It's acceptable to have "noise" around the main peaks, but it should be present on both curves with a comparable amplitude. Keep in mind that when you tighten a belt, its peaks should move diagonally toward the upper right corner, changing significantly in amplitude and slightly in frequency. Additionally, the magnitude order of the main peaks *should typically* range from ~500k to ~2M on most machines.
|
||||||
|
|
||||||
The resonant frequency/amplitude of the curves depends primarily on three parameters (and the actual belt tension):
|
Aside from the actual belt tension, the resonant frequency/amplitude of the curves depends primarily on three parameters:
|
||||||
- the *mass of the toolhead*, which is identical on CoreXY machines for both belts and has no effect here
|
- the *mass of the toolhead*, which is identical on CoreXY, CrossXY and H-Bot machines for both belts. So this will unlikely have any effect here
|
||||||
- the *belt "elasticity"*, which changes over time as the belt wears. Ensure that you use the **same belt brand and type** for both A and B belts and that they were **installed at the same time**
|
- the *belt "elasticity"*, which changes over time as the belt wears. Ensure that you use the **same belt brand and type** for both A and B belts and that they were **installed at the same time**: you want similar belts with a similar level of wear!
|
||||||
- the *belt path length*, which is why they must have the **exact same number of teeth** so that one belt path is not longer than the other when tightened at the same tension. This specific point is very important: A single tooth difference is enough to prevent you from having a good superposition of the curves. Moreover, it is even one of the main causes of problems found in Discord resonance testing channels.
|
- the *belt path length*, which is why they must have the **exact same number of teeth** so that one belt path is not longer than the other when tightened at the same tension. This specific point is very important: a single tooth difference is enough to prevent you from having a good superposition of the curves. Moreover, it is even one of the main causes of problems found in Discord resonance testing channels.
|
||||||
|
|
||||||
**If these three parameters are met, there is no way that the curves could be different** or you can be sure that there is an underlying problem in at least one of the belt paths. Also, if the belt graphs have low amplitude curves (no distinct peaks) and a lot of noise, you will probably also have poor input shaper graphs. So before you continue, ensure that you have good belt graphs or fix your belt paths. Start by checking the belt tension, bearings, gantry screws, alignment of the belts on the idlers, and so on.
|
**If these three parameters are met, there is no way that the curves could be different** or you can be sure that there is an underlying problem in at least one of the belt paths. Also, if the belt graphs have low amplitude curves (no distinct peaks) and a lot of noise, you will probably also have poor input shaper graphs. So before you continue, ensure that you have good belt graphs or fix your belt paths. Start by checking the belt tension, bearings, gantry screws, alignment of the belts on the idlers, and so on.
|
||||||
|
|
||||||
|
|
||||||
|
## Advanced explanation on why 1 or 2 peaks
|
||||||
|
|
||||||
|
The belt graph is created by doing diagonal movements designed to stimulate the system using only one motor at a time. The goal is to assess the behavior of each belt in order to compare them but it's not that straightforward due to a couple of factors:
|
||||||
|
1. Diagonal movements can be split into two distinct sub-systems: the toolhead moving left-to-right along the X linear axis and the movement of the couple toolhead and X linear axis moving in a front-to-back direction. Essentially, instead of a singular harmonic system, we're observing two intertwined sub-systems in motion. This complexity might lead to two resonance frequencies (or peaks) in the belt graph. But since both subsystems utilize similar belts, tension, etc... these peaks can sometimes merge, appearing as one.
|
||||||
|
2. The toolhead is continuously linked to the two belts. When doing a diagonal movement to stimulate only one belt, the other belt stay static and serves as an anchor. But given its elasticity, this belt doesn't remain rigid. It imparts its unique traits to the overall system response, which may introduce additional noise or even a second peak.
|
||||||
|
|
||||||
|
|
||||||
## Examples of graphs
|
## Examples of graphs
|
||||||
|
|
||||||
| Comment | Belt graphs examples 1 | Belt graphs examples 2 |
|
### Good graphs
|
||||||
| --- | --- | --- |
|
|
||||||
| **Both of these two graphs are considered good**. As you can see, the main peak doesn't have to be perfect if you can get both curves to overlap |  |  |
|
The following graphs are considered good. Both of them have only one or two peaks, and they overlap pretty well to form a single curve. If you get something like this, you can continue with the [Axis Input Shaper Graphs](./axis_tuning.md).
|
||||||
| **These two graphs show incorrect belt tension**: in each case, one of the belts has insufficient tension (first is B belt, second is A belt). Begin by tightening it half a turn and measuring again |  |  |
|
|
||||||
| **These two graphs indicate a belt path problem**: the belt tension could be adequate, but something else is happening in the belt paths. Start by checking the bearings and belt wear, or belt alignment |  |  |
|
| With only a single peak | With two peaks |
|
||||||
|
| --- | --- |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
### Incorrect belt tension
|
||||||
|
|
||||||
|
The following graphs show the effect of incorrect or uneven belt tension. Remember that if you have to make large adjustments, always **check your belt tension between each step and make only small adjustments** to avoid breaking your machine by overtightening the belts!
|
||||||
|
|
||||||
|
| Comment | Example |
|
||||||
|
| --- | --- |
|
||||||
|
| The A belt tension is slightly lower than the B belt tension. This can be quickly remedied by tightening the screw only about one-half to one full turn. |  |
|
||||||
|
| B belt tension is significantly lower than the A belt. If you encounter this graph, I recommend going back to the [Voron belt tensioning documentation](https://docs.vorondesign.com/tuning/secondary_printer_tuning.html#belt-tension) for a more solid base. However, you could slightly increase the B tension and decrease the A tension, but exercise caution to avoid diverging from the recommended 110Hz base. |  |
|
||||||
|
|
||||||
|
|
||||||
|
### Belt path problem
|
||||||
|
|
||||||
|
If there's an issue within the belt path, aligning and overlaying the curve might be unachievable even with proper belt tension. Begin by verifying that each belt has **the exact same number of teeth**. Then, inspect the belt paths, bearings, any signs of wear (like belt dust), and ensure the belt aligns correctly on all bearing flanges during motion.
|
||||||
|
|
||||||
|
| Comment | Example |
|
||||||
|
| --- | --- |
|
||||||
|
| On this chart, there are two peaks. The first pair of peaks seems nearly aligned, but the second peak appears solely on the B belt, significantly deviating from the A belt. This suggests an issue with the belt path, likely with the B belt. |  |
|
||||||
|
| This chart is quite complex, displaying 3 peaks. While all the pairs seem well-aligned and tension ok, there are more than just two total peaks because `[1]` is split in two smaller peaks. This could be an issue, but it's not certain. It's recommended to generate the [Axis Input Shaper Graphs](./axis_tuning.md) to determine its impact. |  |
|
||||||
|
| This graph might indicate too low belt tension, but also potential binding, friction or something impeding the toolhead's smooth movement. Indeed, the signal strength is considerably low (with a peak around 300k, compared to the typical ~1M) and is primarily filled with noise. Start by going back [here](https://docs.vorondesign.com/tuning/secondary_printer_tuning.html#belt-tension) to establish a robust tension foundation. Next, produce the [Axis Input Shaper Graphs](./axis_tuning.md) to identify any binding and address the issue. |  |
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ The `VIBRATIONS_CALIBRATION` macro helps you to identify the speed settings that
|
|||||||
|
|
||||||
> **Warning**
|
> **Warning**
|
||||||
>
|
>
|
||||||
> You will first need to calibrate the standard input shaper algorith of Klipper using the other macros! This test should not be used before as it would be useless and the results invalid.
|
> You will first need to calibrate the standard input shaper algorithm of Klipper using the other macros! This test should not be used before as it would be useless and the results invalid.
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -26,6 +26,23 @@ Call the `VIBRATIONS_CALIBRATION` macro with the direction and speed range you w
|
|||||||
|
|
||||||
## Graphs description
|
## Graphs description
|
||||||
|
|
||||||
## Analysis of the results
|

|
||||||
|
|
||||||
TODO: add the analysis part here
|
## Improving the results
|
||||||
|
|
||||||
|
These graphs essentially depict the behavior of the motor control on your machine. While there isn't much room for easy adjustments to enhance them, most of you should only utilize them to configure your slicer profile to avoid problematic speeds.
|
||||||
|
|
||||||
|
However, if you want to go the rabbit hole, as the data in these graphs largely hinges on the type of motors and their physical characteristic and their control by the TMC black magic, there are opportunities for optimization. Tweaking TMC parameters allow to adjust the peaks, enhance machine performance, or diminish overall machine noise. For this process, I recommend to directly use the [Klipper TMC Autotune](https://github.com/andrewmcgr/klipper_tmc_autotune) plugin, which should simplify everything considerably. But keep in mind that it's still an experimental plugin and it's not perfect.
|
||||||
|
|
||||||
|
For individuals inclined to reach the bottom of the rabbit hole and that want to handle this manually, the use of an oscilloscope is mandatory. Majority of the necessary resources are available directly on the Trinamics TMC website:
|
||||||
|
1. You should first consult the datasheet specific to your TMC model for guidance on parameter names and their respective uses.
|
||||||
|
2. Then to tune the parameters, have a look at the application notes available on their platform, especially [AN001](https://www.trinamic.com/fileadmin/assets/Support/AppNotes/AN001-SpreadCycle.pdf), [AN002](https://www.trinamic.com/fileadmin/assets/Support/AppNotes/AN002-StallGuard2.pdf), [AN003](https://www.trinamic.com/fileadmin/assets/Support/AppNotes/AN003_-_DcStep_Basics_and_Wizard.pdf) and [AN009](https://www.trinamic.com/fileadmin/assets/Support/AppNotes/AN009_Tuning_coolStep.pdf).
|
||||||
|
3. For a more comprehensive understanding, you might also want to explore [AN015](https://www.trinamic.com/fileadmin/assets/Support/AppNotes/AN015-StealthChop_Performance.pdf) and [AN021](https://www.trinamic.com/fileadmin/assets/Support/AppNotes/AN021-StealthChop_Performance_comparison_V1.12.pdf ), although they are more geared towards enhancing comprehension than calibration, akin to the TMC datasheet.
|
||||||
|
|
||||||
|
For reference, the default settings used in Klipper are:
|
||||||
|
```
|
||||||
|
#driver_TBL: 2
|
||||||
|
#driver_TOFF: 3
|
||||||
|
#driver_HEND: 0
|
||||||
|
#driver_HSTRT: 5
|
||||||
|
```
|
||||||
|
|||||||