diff --git a/docs/Kinematics.md b/docs/Kinematics.md index 548076d7..f4b84550 100644 --- a/docs/Kinematics.md +++ b/docs/Kinematics.md @@ -264,35 +264,36 @@ through the nozzle orifice (as in key idea is that the relationship between filament, pressure, and flow rate can be modeled using a linear coefficient: ``` -stepper_position = requested_e_position + pressure_advance_coefficient * nominal_extruder_velocity +pa_position = nominal_position + pressure_advance_coefficient * nominal_velocity ``` See the [pressure advance](Pressure_Advance.md) document for information on how to find this pressure advance coefficient. -Once configured, Klipper will push in an additional amount of filament -during acceleration. The higher the desired filament flow rate, the -more filament must be pushed in during acceleration to account for -pressure. During head deceleration the extra filament is retracted -(the extruder will have a negative velocity). +The basic pressure advance formula can cause the extruder motor to +make sudden velocity changes. Klipper implements "smoothing" of the +extruder movement to avoid this. -![pressure-advance](img/pressure-advance.svg.png) +![pressure-advance](img/pressure-velocity.png) -One may notice that the pressure advance algorithm can cause the -extruder motor to make sudden velocity changes. This is tolerated -based on the idea that the majority of the inertia in the system is in -changing the extruder pressure. As long as the extruder pressure does -not change rapidly the sudden changes in extruder motor velocity are -tolerated. +The above graph shows an example of two extrusion moves with a +non-zero cornering velocity between them. Note that the pressure +advance system causes additional filament to be pushed into the +extruder during acceleration. The higher the desired filament flow +rate, the more filament must be pushed in during acceleration to +account for pressure. During head deceleration the extra filament is +retracted (the extruder will have a negative velocity). -One area where sudden velocity changes become problematic is during -small changes in head speed due to cornering. +The "smoothing" is implemented by averaging the extruder position over +a small time period (as specified by the +`pressure_advance_smooth_time` config parameter). This averaging can +span multiple g-code moves. Note how the extruder motor will start +moving prior to the nominal start of the first extrusion move and will +continue to move after the nominal end of the last extrusion move. -![pressure-cornering](img/pressure-cornering.svg.png) - -To prevent this, the Klipper pressure advance code utilizes the move -look-ahead queue to detect intermittent speed changes. During a -deceleration event the code finds the maximum upcoming head speed -within a configurable time window. The pressure is then only adjusted -to this found maximum. This can greatly reduce (or even completely -eliminate) pressure changes during cornering. +Key formula for "smoothed pressure advance": +``` +smooth_pa_position(t) = + ( definitive_integral(pa_position, from=t-smooth_time/2, to=t+smooth_time/2) + / smooth_time ) +``` diff --git a/docs/img/pressure-advance.svg b/docs/img/pressure-advance.svg deleted file mode 100644 index 79bd4674..00000000 --- a/docs/img/pressure-advance.svg +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - extrudervelocity - - - - head velocity - - - - extrude move - non-extrude move - - extruderpressure - - - time - - diff --git a/docs/img/pressure-advance.svg.png b/docs/img/pressure-advance.svg.png deleted file mode 100644 index fa2c6667..00000000 Binary files a/docs/img/pressure-advance.svg.png and /dev/null differ diff --git a/docs/img/pressure-cornering.svg b/docs/img/pressure-cornering.svg deleted file mode 100644 index 908d7eb7..00000000 --- a/docs/img/pressure-cornering.svg +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - extrudervelocity - - - - head velocity - - - - first extrude - second extrude - - time - - diff --git a/docs/img/pressure-cornering.svg.png b/docs/img/pressure-cornering.svg.png deleted file mode 100644 index 183411d5..00000000 Binary files a/docs/img/pressure-cornering.svg.png and /dev/null differ diff --git a/docs/img/pressure-velocity.png b/docs/img/pressure-velocity.png new file mode 100644 index 00000000..89e91ab4 Binary files /dev/null and b/docs/img/pressure-velocity.png differ diff --git a/scripts/graph_extruder.py b/scripts/graph_extruder.py new file mode 100755 index 00000000..6223bb11 --- /dev/null +++ b/scripts/graph_extruder.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python2 +# Generate extruder pressure advance motion graphs +# +# Copyright (C) 2019 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import math, optparse, datetime +import matplotlib + +SEG_TIME = .000100 +INV_SEG_TIME = 1. / SEG_TIME + + +###################################################################### +# Basic trapezoid motion +###################################################################### + +# List of moves: [(start_v, end_v, move_t), ...] +Moves = [ + (0., 0., .200), + (0., 100., None), (100., 100., .200), (100., 60., None), + (60., 100., None), (100., 100., .200), (100., 0., None), + (0., 0., .300) +] +EXTRUDE_R = (.4 * .4 * .75) / (math.pi * (1.75 / 2.)**2) +ACCEL = 3000. * EXTRUDE_R + +def gen_positions(): + out = [] + start_d = start_t = t = 0. + for start_v, end_v, move_t in Moves: + start_v *= EXTRUDE_R + end_v *= EXTRUDE_R + if move_t is None: + move_t = abs(end_v - start_v) / ACCEL + half_accel = 0. + if end_v > start_v: + half_accel = .5 * ACCEL + elif start_v > end_v: + half_accel = -.5 * ACCEL + end_t = start_t + move_t + while t <= end_t: + rel_t = t - start_t + out.append(start_d + (start_v + half_accel * rel_t) * rel_t) + t += SEG_TIME + start_d += (start_v + half_accel * move_t) * move_t + start_t = end_t + return out + +def gen_deriv(data): + return [0.] + [(data[i+1] - data[i]) * INV_SEG_TIME + for i in range(len(data)-1)] + +def time_to_index(t): + return int(t * INV_SEG_TIME + .5) + + +###################################################################### +# Pressure advance +###################################################################### + +PA_HALF_SMOOTH_T = .040 / 2. +PRESSURE_ADVANCE = .045 + +def calc_pa_raw(t, positions): + pa = PRESSURE_ADVANCE * INV_SEG_TIME + i = time_to_index(t) + return positions[i] + pa * (positions[i+1] - positions[i]) + +def calc_pa_smooth(t, positions): + start_index = time_to_index(t - PA_HALF_SMOOTH_T) + 1 + end_index = time_to_index(t + PA_HALF_SMOOTH_T) + pa = PRESSURE_ADVANCE * INV_SEG_TIME + pa_data = [positions[i] + pa * (positions[i+1] - positions[i]) + for i in range(start_index, end_index)] + return sum(pa_data) / (end_index - start_index) + + +###################################################################### +# Plotting and startup +###################################################################### + +MARGIN_TIME = 0.100 + +def plot_motion(): + # Nominal motion + positions = gen_positions() + drop = int(MARGIN_TIME * INV_SEG_TIME) + times = [SEG_TIME * t for t in range(len(positions))][drop:-drop] + velocities = gen_deriv(positions[drop:-drop]) + # Motion with pressure advance + pa_positions = [calc_pa_raw(t, positions) for t in times] + pa_velocities = gen_deriv(pa_positions) + # Smoothed motion + sm_positions = [calc_pa_smooth(t, positions) for t in times] + sm_velocities = gen_deriv(sm_positions) + # Build plot + shift_times = [t - MARGIN_TIME for t in times] + fig, ax1 = matplotlib.pyplot.subplots(nrows=1, sharex=True) + ax1.set_title("Extruder Velocity") + ax1.set_ylabel('Velocity (mm/s)') + pa_plot, = ax1.plot(shift_times, pa_velocities, 'r', + label='Pressure Advance', alpha=0.3) + nom_plot, = ax1.plot(shift_times, velocities, 'black', label='Nominal') + sm_plot, = ax1.plot(shift_times, sm_velocities, 'g', label='Smooth PA', + alpha=0.9) + fontP = matplotlib.font_manager.FontProperties() + fontP.set_size('x-small') + ax1.legend(handles=[nom_plot, pa_plot, sm_plot], loc='best', prop=fontP) + ax1.set_xlabel('Time (s)') + ax1.grid(True) + fig.tight_layout() + return fig + +def setup_matplotlib(output_to_file): + global matplotlib + if output_to_file: + matplotlib.rcParams.update({'figure.autolayout': True}) + matplotlib.use('Agg') + import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager + import matplotlib.ticker + +def main(): + # Parse command-line arguments + usage = "%prog [options]" + opts = optparse.OptionParser(usage) + opts.add_option("-o", "--output", type="string", dest="output", + default=None, help="filename of output graph") + options, args = opts.parse_args() + if len(args) != 0: + opts.error("Incorrect number of arguments") + + # Draw graph + setup_matplotlib(options.output is not None) + fig = plot_motion() + + # Show graph + if options.output is None: + matplotlib.pyplot.show() + else: + fig.set_size_inches(6, 2.5) + fig.savefig(options.output) + +if __name__ == '__main__': + main()