diff --git a/K-ShakeTune/scripts/graph_belts.py b/K-ShakeTune/scripts/graph_belts.py index fe67a22..a98b437 100755 --- a/K-ShakeTune/scripts/graph_belts.py +++ b/K-ShakeTune/scripts/graph_belts.py @@ -23,8 +23,15 @@ import numpy as np import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager import matplotlib.ticker, matplotlib.gridspec, matplotlib.colors import matplotlib.patches +import locale +from datetime import datetime matplotlib.use('Agg') +try: + locale.setlocale(locale.LC_TIME, locale.getdefaultlocale()) +except locale.Error: + locale.setlocale(locale.LC_TIME, 'C') + MAX_TITLE_LENGTH = 65 ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # For paired peaks names @@ -38,6 +45,14 @@ DC_MAX_UNPAIRED_PEAKS_ALLOWED = 4 # Define the SignalData namedtuple SignalData = namedtuple('CalibrationData', ['freqs', 'psd', 'peaks', 'paired_peaks', 'unpaired_peaks']) +KLIPPAIN_COLORS = { + "purple": "#70088C", + "orange": "#FF8D32", + "dark_purple": "#150140", + "dark_orange": "#F24130", + "red_pink": "#F2055C" +} + ###################################################################### # Computation of the PSD graph @@ -140,7 +155,7 @@ def pair_peaks(peaks1, freqs1, psd1, peaks2, freqs2, psd2): # Computation of a basic signal spectrogram ###################################################################### -def calc_specgram(data): +def compute_spectrogram(data): N = data.shape[0] Fs = N / (data[-1,0] - data[0,0]) # Round up to a power of 2 for faster FFT @@ -265,8 +280,8 @@ def shift_data_in_time(data, shift_amount): # the time lag and realigning them. Finally this function combine (by substracting signals) the aligned spectrograms in a new one. # This result of a mostly zero-ed new spectrogram with some colored zones highlighting differences in the belts paths. def combined_spectrogram(data1, data2): - pdata1, bins1, t1 = calc_specgram(data1) - pdata2, _, _ = calc_specgram(data2) + pdata1, bins1, t1 = compute_spectrogram(data1) + pdata2, _, _ = compute_spectrogram(data2) # Detect ridges ridge1 = detect_ridge(pdata1) @@ -314,9 +329,22 @@ def compute_comi(combined_data, similarity_coefficient, num_unpaired_peaks): ###################################################################### def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq): + # Get the belt name for the legend to avoid putting the full file name + signal1_belt = (lognames[0].split('/')[-1]).split('_')[-1][0] + signal2_belt = (lognames[1].split('/')[-1]).split('_')[-1][0] + + if signal1_belt == 'A' and signal2_belt == 'B': + signal1_belt += " (axis 1,-1)" + signal2_belt += " (axis 1, 1)" + elif signal1_belt == 'B' and signal2_belt == 'A': + signal1_belt += " (axis 1, 1)" + signal2_belt += " (axis 1,-1)" + else: + print("Warning: belts doesn't seem to have the correct name A and B (extracted from the filename.csv)") + # Plot the two belts PSD signals - ax.plot(signal1.freqs, signal1.psd, label="\n".join(wrap(lognames[0], 60)), alpha=0.6) - ax.plot(signal2.freqs, signal2.psd, label="\n".join(wrap(lognames[1], 60)), alpha=0.6) + ax.plot(signal1.freqs, signal1.psd, label="Belt " + signal1_belt, color=KLIPPAIN_COLORS['purple']) + ax.plot(signal2.freqs, signal2.psd, label="Belt " + signal2_belt, color=KLIPPAIN_COLORS['orange']) # Trace the "relax region" (also used as a threshold to filter and detect the peaks) psd_lowest_max = min(signal1.psd.max(), signal2.psd.max()) @@ -362,8 +390,10 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq): unpaired_peak_count += 1 # Compute the similarity (using cross-correlation of the PSD signals) + ax2 = ax.twinx() # To split the legends in two box similarity_factor = compute_curve_similarity_factor(signal1, signal2) - ax.plot([], [], ' ', label=f'Estimated similarity: {similarity_factor:.1f}% ({unpaired_peak_count} unpaired peaks)') + ax2.plot([], [], ' ', label=f'Estimated similarity: {similarity_factor:.1f}%') + ax2.plot([], [], ' ', label=f'Number of unpaired peaks: {unpaired_peak_count}') print(f"Belts estimated similarity: {similarity_factor:.1f}%") # Setting axis parameters, grid and graph title @@ -380,13 +410,12 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq): ax.grid(which='minor', color='lightgrey') fontP = matplotlib.font_manager.FontProperties() fontP.set_size('small') - ax.set_title('Belts Frequency Profiles (estimated similarity: {:.1f}%)'.format(similarity_factor), fontsize=14) - ax.legend(loc='best', prop=fontP) + ax.set_title('Belts Frequency Profiles (estimated similarity: {:.1f}%)'.format(similarity_factor), fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold') # Print the table of offsets ontop of the graph below the original legend (upper right) if len(offsets_table_data) > 0: columns = ["", "Frequency delta", "Amplitude delta", ] - offset_table = ax.table(cellText=offsets_table_data, colLabels=columns, bbox=[0.67, 0.60, 0.3, 0.2], loc='upper right', cellLoc='center') + 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.auto_set_font_size(False) offset_table.set_fontsize(8) offset_table.auto_set_column_width([0, 1, 2]) @@ -396,6 +425,9 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq): offset_table[cell].set_facecolor('white') offset_table[cell].set_alpha(0.6) + ax.legend(loc='upper left', prop=fontP) + ax2.legend(loc='upper right', prop=fontP) + return similarity_factor, unpaired_peak_count @@ -407,7 +439,7 @@ def plot_difference_spectrogram(ax, data1, data2, signal1, signal2, similarity_f # 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)) print(f"Chances of mechanical issues: {comi:.1f}%") - ax.set_title(f"Differential Spectrogram (COMI: {comi:.1f}%)", fontsize=14) + ax.set_title(f"Differential Spectrogram (COMI: {comi:.1f}%)", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold') ax.plot([], [], ' ', label=f'Chances of mechanical issues (COMI): {comi:.1f}%') # 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) @@ -443,11 +475,11 @@ def plot_difference_spectrogram(ax, data1, data2, signal1, signal2, similarity_f label = ALPHABET[idx] x_min = min(peak1[1], peak2[1]) x_max = max(peak1[1], peak2[1]) - ax.axvline(x_min, color='blue', linestyle='dotted', linewidth=1.5) - ax.axvline(x_max, color='blue', linestyle='dotted', linewidth=1.5) - ax.fill_between([x_min, x_max], 0, np.max(combined_data), color='blue', alpha=0.3) + ax.axvline(x_min, color=KLIPPAIN_COLORS['purple'], linestyle='dotted', linewidth=1.5) + ax.axvline(x_max, color=KLIPPAIN_COLORS['purple'], linestyle='dotted', linewidth=1.5) + ax.fill_between([x_min, x_max], 0, np.max(combined_data), color=KLIPPAIN_COLORS['purple'], alpha=0.3) ax.annotate(f"Peaks {label}", (x_min, t[-1]*0.05), - textcoords="data", color='blue', rotation=90, fontsize=10, + textcoords="data", color=KLIPPAIN_COLORS['purple'], rotation=90, fontsize=10, verticalalignment='bottom', horizontalalignment='right') return @@ -517,14 +549,20 @@ def belts_calibration(lognames, klipperdir="~/klipper", max_freq=200.): gs = matplotlib.gridspec.GridSpec(2, 1, height_ratios=[4, 3]) ax1 = fig.add_subplot(gs[0]) ax2 = fig.add_subplot(gs[1]) - fig.suptitle("CoreXY relative belt calibration tool", fontsize=16) + + filename = lognames[0].split('/')[-1] + dt = datetime.strptime(f"{filename.split('_')[1]} {filename.split('_')[2]}", "%Y%m%d %H%M%S") + title_line1 = "RELATIVE BELT CALIBRATION TOOL" + 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.88, 0.957, title_line2, ha='right', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple']) 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) - fig.set_size_inches(10, 12) + fig.set_size_inches(10, 13) fig.tight_layout() - fig.subplots_adjust(top=0.93) + fig.subplots_adjust(top=0.90) return fig @@ -546,6 +584,12 @@ def main(): opts.error("You must specify an output file.png to use the script (option -o)") 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) diff --git a/K-ShakeTune/scripts/graph_shaper.py b/K-ShakeTune/scripts/graph_shaper.py index 583e142..465b114 100755 --- a/K-ShakeTune/scripts/graph_shaper.py +++ b/K-ShakeTune/scripts/graph_shaper.py @@ -30,13 +30,27 @@ from textwrap import wrap import numpy as np import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager import matplotlib.ticker, matplotlib.gridspec +import locale +from datetime import datetime matplotlib.use('Agg') +try: + locale.setlocale(locale.LC_TIME, locale.getdefaultlocale()) +except locale.Error: + locale.setlocale(locale.LC_TIME, 'C') -MAX_TITLE_LENGTH=65 -PEAKS_DETECTION_THRESHOLD=0.05 -PEAKS_EFFECT_THRESHOLD=0.12 -SPECTROGRAM_LOW_PERCENTILE_FILTER=5 + +MAX_TITLE_LENGTH = 65 + +PEAKS_DETECTION_THRESHOLD = 0.05 +PEAKS_EFFECT_THRESHOLD = 0.12 +SPECTROGRAM_LOW_PERCENTILE_FILTER = 5 + +KLIPPAIN_COLORS = { + "purple": "#70088C", + "dark_purple": "#150140", + "dark_orange": "#F24130" +} ###################################################################### @@ -219,7 +233,7 @@ def plot_freq_response_with_damping(ax, calibration_data, shapers, selected_shap ax2.plot([], [], ' ', label="Estimated damping ratio (ζ): %.3f" % (zeta)) # Add the main resonant frequency and damping ratio of the axis to the graph title - ax.set_title("Axis Frequency Profile (ω0=%.1fHz, ζ=%.3f)" % (fr, zeta), fontsize=14) + ax.set_title("Axis Frequency Profile (ω0=%.1fHz, ζ=%.3f)" % (fr, zeta), fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold') ax.legend(loc='upper left', prop=fontP) ax2.legend(loc='upper right', prop=fontP) @@ -237,7 +251,7 @@ def plot_spectrogram(ax, data, peaks, max_freq): # So we need to filter out the lower part of the data (ie. find the proper vmin for LogNorm) vmin_value = np.percentile(pdata, SPECTROGRAM_LOW_PERCENTILE_FILTER) - ax.set_title("Time-Frequency Spectrogram", fontsize=14) + ax.set_title("Time-Frequency Spectrogram", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold') ax.pcolormesh(bins, t, pdata.T, norm=matplotlib.colors.LogNorm(vmin=vmin_value), cmap='inferno', shading='gouraud') @@ -294,20 +308,20 @@ def shaper_calibration(lognames, klipperdir="~/klipper", max_smoothing=None, max gs = matplotlib.gridspec.GridSpec(2, 1, height_ratios=[4, 3]) ax1 = fig.add_subplot(gs[0]) ax2 = fig.add_subplot(gs[1]) - # fig.suptitle("\n".join(wrap( - # "Input Shaper calibration (%s)" % (', '.join(lognames)), MAX_TITLE_LENGTH)), fontsize=16) - title_line1 = "Input Shaper calibration" - title_line2 = ', '.join(lognames) - fig.text(0.5, 0.975, title_line1, ha='center', va='bottom', fontsize=16) - fig.text(0.5, 0.975, title_line2, ha='center', va='top', fontsize=12) + 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") + title_line1 = "INPUT SHAPER CALIBRATION TOOL" + title_line2 = filename_parts[2].upper() + ' axis - ' + 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.88, 0.957, title_line2, ha='right', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple']) peaks = plot_freq_response_with_damping(ax1, calibration_data, shapers, selected_shaper, fr, zeta, max_freq) plot_spectrogram(ax2, datas[0], peaks, max_freq) - fig.set_size_inches(10, 12) + fig.set_size_inches(10, 13) fig.tight_layout() - fig.subplots_adjust(top=0.93) + fig.subplots_adjust(top=0.90) return fig @@ -333,6 +347,12 @@ def main(): 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) + + # 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) diff --git a/K-ShakeTune/scripts/klippain.png b/K-ShakeTune/scripts/klippain.png new file mode 100644 index 0000000..7b8ce84 Binary files /dev/null and b/K-ShakeTune/scripts/klippain.png differ diff --git a/docs/is_tuning_generalities.md b/docs/is_tuning_generalities.md new file mode 100644 index 0000000..f682d4e --- /dev/null +++ b/docs/is_tuning_generalities.md @@ -0,0 +1,58 @@ +# 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. + + +## 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. + +![](./images/IS_docs/ghosting.png) + + +## Generalities on the graphs + +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. 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. 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. 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. 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 +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. + +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. + +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. + + +## Theory behind it + +### 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: + +| Schematics | Undamped resonnant frequency
(natural frequency) | Damping ratio ζ | +| --- | --- | --- | +| ![](./images/IS_docs/harmonic_oscillator.png) | $$\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
`m` [g]: moving mass | `c` [N·s/m]: viscous damping coefficient
`k` [N/m]: spring constant
`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. + +On the other hand, the damping ratio (ζ) is a dimensionless measure describing how oscillations in a system decay after a perturbation. It can vary from underdamped (ζ < 1), through critically damped (ζ = 1) to overdamped (ζ > 1). + +In 3D printers, it's quite challenging to measure the spring constant `k` and even more challenging to measure the viscous damping coefficient `c`, as they are affected by various factors such as belts, plastic parts, frame rigidity, rails, friction, grease, and motor control. Furthermore, a 3D printer is made up of many subsystems, each with its own behavior. Some subsystems, such as the toolhead/belts system, have a bigger impact on ringing than others, such as the motor shaft resonance for example. + +### How Input Shaping helps +The rapid movement of machines is a challenging control problem because it often results in high levels of vibration. As a result, machines are typically moved relatively slowly. Input shaping is an open-loop control method that allows for higher speeds of motion by limiting vibration induced by the reference command. It can also improve the reliability of the stealthChop mode of Trinamic stepper drivers. + +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. + +![](./images/IS_docs/how_IS_works.png) + +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. diff --git a/docs/k_shaketune.md b/docs/k_shaketune.md index 7aa1fe2..e9e0472 100644 --- a/docs/k_shaketune.md +++ b/docs/k_shaketune.md @@ -1,66 +1,9 @@ -# Tuning Klipper's Input Shaper system - -As more and more people use my macros, questions about interpreting the resonnance testing results 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 -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. - -![](./images/IS_docs/ghosting.png) - - -## Reading the graphs - -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. 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 belt tension can change with temperature. - 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 can introduce some vibrations, feel free to use the test to diagnose an unbalanced fan as seen in the [Examples of Input Shaper graphs](#examples-of-input-shaper-graphs) 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 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 issues. - 1. Remember why you're running these tests (clean prints) and don't become too obsessive over perfect graphs. +# Klippain Shake&Tune module documentation ### Detailed documentation + 1. [Input Shaping and tuning generalities](./is_tuning_generalities.md) 1. [Belt graphs](./macros/belts_tuning.md) - 1. [Input Shaper graphs](./macros/axis_tuning.md) + 1. [Axis Input Shaper graphs](./macros/axis_tuning.md) 1. [Klippain vibrations graphs](./macros/vibrations_tuning.md) - - -### 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. - -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. - -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. - - - -## Theory behind it - -### 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: - -| Schematics | Undamped resonnant frequency
(natural frequency) | Damping ratio ζ | -| --- | --- | --- | -| ![](./images/IS_docs/harmonic_oscillator.png) | $$\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
`m` [g]: moving mass | `c` [N·s/m]: viscous damping coefficient
`k` [N/m]: spring constant
`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. - -On the other hand, the damping ratio (ζ) is a dimensionless measure describing how oscillations in a system decay after a perturbation. It can vary from underdamped (ζ < 1), through critically damped (ζ = 1) to overdamped (ζ > 1). - -In 3D printers, it's quite challenging to measure the spring constant `k` and even more challenging to measure the viscous damping coefficient `c`, as they are affected by various factors such as belts, plastic parts, frame rigidity, rails, friction, grease, and motor control. Furthermore, a 3D printer is made up of many subsystems, each with its own behavior. Some subsystems, such as the toolhead/belts system, have a bigger impact on ringing than others, such as the motor shaft resonance for example. - -### How Input Shaping helps -The rapid movement of machines is a challenging control problem because it often results in high levels of vibration. As a result, machines are typically moved relatively slowly. Input shaping is an open-loop control method that allows for higher speeds of motion by limiting vibration induced by the reference command. It can also improve the reliability of the stealthChop mode of Trinamic stepper drivers. - -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. - -![](./images/IS_docs/how_IS_works.png) - -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. diff --git a/docs/macros/axis_tuning.md b/docs/macros/axis_tuning.md index 92f9c89..9bd8838 100644 --- a/docs/macros/axis_tuning.md +++ b/docs/macros/axis_tuning.md @@ -25,7 +25,7 @@ Then, call the `AXES_SHAPER_CALIBRATION` macro and look for the graphs in the re #### 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: - - 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 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 under the printer through the frame of the printer to 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. 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. diff --git a/docs/macros/belts_tuning.md b/docs/macros/belts_tuning.md index 792caba..31d901f 100644 --- a/docs/macros/belts_tuning.md +++ b/docs/macros/belts_tuning.md @@ -21,12 +21,12 @@ Then, call the `BELTS_SHAPER_CALIBRATION` macro and look for the graphs in the r ## 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. It's acceptable to have "noise" around the main peak, but it should be present on both curves with a comparable amplitude. Keep in mind that when you tighten a belt, its main peak 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 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. -The resonant frequency/amplitude of the curves depends primarily on three parameters (and the actual tension): - - the *mass of the toolhead*, which is identical for both belts and has no effect here +The resonant frequency/amplitude of the curves depends primarily on three parameters (and the actual belt tension): + - the *mass of the toolhead*, which is identical on CoreXY machines for both belts and has no 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 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 + - 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.