Marlin icon indicating copy to clipboard operation
Marlin copied to clipboard

[BUG] S-Curve Acceleration - Velocity Jumps when changing direction

Open xenovacivus opened this issue 5 years ago • 52 comments

Hi,

The S-Curve Acceleration seems to work pretty well, but there's a velocity jump when an axis changes direction (or when an axis stops and starts again, but that's less likely to cause a resonance/missed step problem).

marlin-s-curve-steps

Zooming in shows a significant instantaneous spike in step time (from -2700 steps per second to +250 steps per second).

marlin-s-curve-steps-zoom

I see this with a clean build of the bugfix-2.0.x branch (Nov. 20), and my configuration is a stock Teensy++2.0 target with the following changes: #define DEFAULT_MAX_ACCELERATION { 100, 100, 100, 100 } #define DEFAULT_MAX_FEEDRATE { 300, 300, 300, 25 } #define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 80, 80, 500 } #define S_CURVE_ACCELERATION

xenovacivus avatar Nov 20 '18 09:11 xenovacivus

The strange thing is that on the position trace, no discontinuity is shown. Make sure it is not a numeric instability (finite precision) on the either capture process or differenciation process...

ejtagle avatar Nov 20 '18 16:11 ejtagle

The data is captured with a logic analyzer watching the step and direction pins. The position is incremented on step with dir=1 and decremented on step with dir=0. So we wouldn't expect a discontinuity. The velocity curve is created from the difference in position, then applying a low-pass filter.

The number of steps is correct; it's the dramatic change in step period that's the problem.

xenovacivus avatar Nov 21 '18 01:11 xenovacivus

Is this an unexpected interaction between s-curve acceleration and adaptive step smoothing?

thinkyhead avatar Nov 21 '18 01:11 thinkyhead

No, i really doubt it @thinkyhead . The logic analyzer does not capture Stepper ISR rate - It captures the step and dir pulses created by that ISR. Step smoothing improves positioning, but does not increase speed of step/dir pulses.

The strange thing about this graph is that the discontinuities on velocity and acceleration happen when there is essentially no movement. That is what calls my attention, Such huge jump in speed. This could be caused by the JERK setting, that allows discontinuities in speed when transitioning of movements, or by the fact that this is plotting just one axis, and this could be a compound movement, where what matters is the vectorial velocity and acceleration changes (module of speed and acceleration) and not the change in its components...

ejtagle avatar Nov 21 '18 02:11 ejtagle

Also I don't have step smoothing defined. //#define ADAPTIVE_STEP_SMOOTHING Here's another example:

marlin-x-y-z-wiggle

(Also good news - no steps are lost while changing direction)

The jumps in velocity in this test are +-800 (and +-80 for Z), which correlates with the "DEFAULT_*JERK" settings in Configuration.h. So this looks (almost) like expected behavior - except that "DEFAULT_*JERK" is defined as the maximum instantaneous speed change - and here, when going from a negative velocity to positive velocity, there are two back-to-back instantaneous changes (one from -800 to 0, then another from 0 to 800). So it's twice "DEFAULT_*JERK".

Here's a zoom of the velocity graph (unfiltered): marlin-x-y-z-wiggle-zoom

It's running this gcode:

M17 ; Enable Steppers M121 ; Disable endstop checking G92 X0 Y0 Z0 E0 ; Set Position G1 F6000 ; Speed 100mm/sec

G1 X1 G1 X0 G1 X1 G1 X0 G1 X1 G1 X0 G1 X1 Y.1 Z.2 G1 X0 Y.2 Z.4 G1 X1 Y.3 Z.6 G1 X0 Y.4 Z.8 G1 X1 Y.7 Z.9 G1 X0 Y1 Z1 G1 X1 Y1.4 Z.9 G1 X0 Y.6 Z.8 G1 X1 Y.4 Z.7 G1 X0 Y0 Z0 G1 X1 G1 X0 G1 Y1 G1 Y0 G1 Z1 G1 Z0 G1 Y1 G1 X0 Y0

xenovacivus avatar Nov 21 '18 03:11 xenovacivus

I have the creality cr10 printer and print a model with Marlin 1.1.9 and after 3 hours of printing that model the print starts to shows layers mismatched, change a little the position and print in different part, I do the same print three times and the same problem, I rollback to the original firmware that came with the printer and print again and no problem, maybe a problem with the s-curve? Please help me

johanmanuelle avatar Dec 01 '18 19:12 johanmanuelle

@johanmga, I've discovered the velocity jumps here come from using "classic jerk" computations, and happen whether or not s-curve acceleration is enabled.

Try uncommenting //#define JUNCTION_DEVIATION in Configuration_adv.h; that will disable classic jerk and use the junction deviation math instead.

Also for reference, issue #12403 is actively tracking the layer shift issue (please comment there too if you have more information on problems/solutions), and #12540 is has more information on classic jerk vs. junction deviation.

xenovacivus avatar Dec 01 '18 22:12 xenovacivus

@johanmga, I've discovered the velocity jumps here come from using "classic jerk" computations, and happen whether or not s-curve acceleration is enabled.

Try uncommenting //#define JUNCTION_DEVIATION in Configuration_adv.h; that will disable classic jerk and use the junction deviation math instead.

Also for reference, issue #12403 is actively tracking the layer shift issue (please comment there too if you have more information on problems/solutions), and #12540 is has more information on classic jerk vs. junction deviation. thanks a lot!! i will try and comment in the near future the results, have a nice day :)

johanmanuelle avatar Dec 02 '18 00:12 johanmanuelle

@xenovacivus I replaced the traditional jerk for junction deviation and the problem was solved!! , Additionally I put a little Pieces of foam on each corner of the printer in order to reduce the vibration :)

johanmanuelle avatar Dec 03 '18 13:12 johanmanuelle

@xenovacivus, my morbid curiosity wonders what your captures look like with JUNCTION_DEVIATION defined :)

fiveangle avatar Dec 04 '18 18:12 fiveangle

They're pretty interesting, actually!

With the same configuration as the first post but JUNCTION_DEVIATION on and set to 0.02, I get this (S_CURVE_ACCELERATION on): marlin-s-curve-400mm-junction-deviation0 02

And here's the same input, but with S_CURVE_ACCELERATION turned off: marlin-trapezoid-400mm-junction-deviation0 02

In the trapezoidal acceleration case, the first move (from 0 to 1600 steps) accelerates for roughly 1 second, coasts for a second, and then decelerates for a second. But in the s-curve acceleration case, it coasts for about 1.2 seconds and doesn't have enough time (travel length) to decelerate completely, resulting in the jump to 0 velocity. So either the s-curve is taking longer to accelerate/decelerate than the trapezoidal curve, or the time to begin decelerating is further into the block than with the trapezoidal profile.

Also gcode for reference:

M17 ; Enable Steppers
M121 ; Disable endstop checking
G92 X0 Y0 Z0 E0 ; Set Position
G1 F6000
G1 X-200
G1 Y50
G1 X200
G1 X-200
G1 Y50
G1 X0 Y0

xenovacivus avatar Dec 05 '18 00:12 xenovacivus

Here's a bit more!

I aligned the s-curve and trapezoidal profiles with the same time scale and used the ms-paint analytical tool to compare. It looks like the s-curve for deceleration begins a little late, meaning the extra length traveled (area underneath the velocity curve) needs to be chopped off of the end, or the axis will travel too far. Because of the whole area-under-the curve thing, a little time spent at high velocity translates into a LOT of time spent at a low velocity (especially because the tail end of the s-curve is very near to zero). The green color tries to highlight this (the two areas are roughly equal; in fact, the tall skinny one should probably be a little skinnier).

Note: if the s-curve begins a little early, it results in a profile which stops short of the intended location. This is even less ideal than a profile that must be cut off, because the axis decelerates to zero and must accelerate again to complete the move (and that would require a lot more logic than the current "We're at the end of the travel, shut 'er down!"). So, if there must be some error, it's probably better to bias it to starting the deceleration curve a bit late.

A more optimal solution might re-evaluate the tail end of the s-curve on the fly and dynamically adjust, or just replace the last portion of the curve with a constant acceleration portion. Or, ideally, the point at which deceleration begins is precise.

comparison2

xenovacivus avatar Dec 05 '18 01:12 xenovacivus

@xenovacivus What I gather from your last two comments: this is an issue with S-CURVE acceleration that is NOT fixed by enabling Junction Deviation, correct? S-CURVE acceleration profile either accelerates too slowly or decelerates too slowly, leaving an abrupt deceleration at the tail end of the S-Curve.

Earlier in the thread, there was some indication this problem was due to traditional JERK and resolved by enabling Junction Deviation. Am I correct in assuming that theory is no longer valid?

gururise avatar Dec 05 '18 17:12 gururise

It would give more insight if we could measure the timing of step pulses at the place where the discontinuity in acceleration happens... 👍

ejtagle avatar Dec 05 '18 17:12 ejtagle

@gururise: yep, this is a problem with s-curve acceleration which is not solved by enabling JUNCTION_DEVIATION. Classic jerk computations add another velocity spike (dependent on MAX_JERK) but this one is independent of that.

Here's a zoomed in view of the steps and velocity at the first velocity jump: acc-spike-detail

Steps/s is exactly 1/(time between steps), so you can invert again to get the actual time. In this case, the last step took 457.62us.

There's a standalone plotly .html file with all the detail here, you can zoom and pan into whatever section is interesting: https://drive.google.com/open?id=1ctZzgsAGRkxphIrj3MbXOTcP7zb2B6AN

xenovacivus avatar Dec 05 '18 19:12 xenovacivus

I am trying to figure out the problem: The S-Curve implementation just fits the SCurve to the trapezoidal curve (that internally is still used to do planning) The first thing i thought was that the delay in the deceleration of the SCurve was caused by some problems on the calculations themselves, but, as you pointed out, probably this is not the case.

My suspicion, right now, is rounding errors. And maybe the solution to them are not so complex. Let me explain how the stepper routines work:

  1. The count of steps to apply is independant of the acceleration profile. The idea is just to compute the delay between steps to create the proper acceleration profile.
  2. So, even if the acceleration profile is not completely evaluated (as you have seen, it is truncated at the end), there should never be missing steps or extra steps.
  3. Each time a "step" is output, a delay (in clock cycles) to the next step is computed, and that delay is used to program a timer that will delay the next ISR that will create the next step pulse.
  4. The bezier curve is a function of time, and time is just the accumulated "clock cycles" from the previous pulses.

This is the trapezoidal algorithm

acceleration_time = 0
acceleration_steps_to_do = 1000; // Count of steps to do for acceleration
while (acceleration_steps_to_do  > 0 ) (
 create_step_pulse();
 step_rate= acceleration_time * acceleration_rate + initial_rate; // in steps per second
 delta_cycles = CLOCK_SPEED_HZ/step_rate
 acceleration_time = acceleration_time + delta_cycles
 delay_cycles(delta);
 acceleration_steps_to_do  = acceleration_steps_to_do - 1;
}

cruise_steps_to_do = 1000; // Count of steps to do for cruise
while (cruise_steps_to_do  > 0 ) (
 create_step_pulse();
 delay_cycles(delta); /* we reuse the last delay here */
 cruise_steps_to_do  = cruise_steps_to_do - 1;
}

deceleration_time = 0
deceleration_steps_to_do = 1000; // Count of steps to do for deceleration
while (deceleration_steps_to_do  > 0 ) (
 create_step_pulse();
 step_rate= cruise_rate - deceleration_time * deceleleration_rate; // in steps per second
 delta_cycles = CLOCK_SPEED_HZ/step_rate
 deceleration_time = deceleration_time + delta_cycles
 delay_cycles(delta);
 deceleration_steps_to_do  = deceleration_steps_to_do - 1;
}

This is the SCurve algorithm

acceleration_time = 0
acceleration_steps_to_do = 1000; // Count of steps to do for acceleration
calculate_scurve_coeffs(initial_rate, cruise_rate, acceleration_time);
while (acceleration_steps_to_do  > 0 ) (
 create_step_pulse();
 step_rate= evaluate_scurve(acceleration_time); // in steps per second
 delta_cycles = CLOCK_SPEED_HZ/step_rate
 acceleration_time = acceleration_time + delta_cycles
 delay_cycles(delta);
 acceleration_steps_to_do  = acceleration_steps_to_do - 1;
}

cruise_steps_to_do = 1000; // Count of steps to do for cruise
while (cruise_steps_to_do  > 0 ) (
 create_step_pulse();
 delay_cycles(delta); /* we reuse the last delay here */
 cruise_steps_to_do  = cruise_steps_to_do - 1;
}

deceleration_time = 0
deceleration_steps_to_do = 1000; // Count of steps to do for deceleration
calculate_scurve_coeffs(cruise_rate, end_rate, deceleration_time);
while (deceleration_steps_to_do  > 0 ) (
 create_step_pulse();
 step_rate= evaluate_scurve(deceleration_time); // in steps per second
 delta_cycles = CLOCK_SPEED_HZ/step_rate
 deceleration_time = deceleration_time + delta_cycles
 delay_cycles(delta);
 deceleration_steps_to_do  = deceleration_steps_to_do - 1;
}

The main problem with that algorithm is that all variables are integers, so errors accumulate along movements.

What calls my attention is that there seems to be an extra delay on the cruise phase, or perhaps the problem is a rounding error on the start of the deceleration phase, as the faster the movement is, the less precise the step_rate variable becomes, as that variable is an integer... And that could lead to deceleration_time increasing at an slower pace than required, and thus the "delay" in the start of the scurve deceleration profile

ejtagle avatar Dec 05 '18 20:12 ejtagle

I just tried S-curve and junction deviation on with 0.02, and got some missed steps/skipping belts/noise etc. I'm using a Rumba+ and DRV8825 drivers. Rather large printer though so the mass of the carriage doesn't help. having s-curve really working would be awesome! Great work on the above graphs, very nice.

Could you use a round or floor or ceil type function, or split up the section of travel that needs the s-curve computation into more sections, and then set a requirement for the position, vel, accel to line up at the intersection of those sections? Then you could apply a numerical adjustment in each section to ensure accel at end of movement goes to zero? Or maybe do the calculation in reverse and let the starting point have the error rather than the end of the travel?

Just some ideas...

DroneMang

DroneMang avatar Dec 06 '18 03:12 DroneMang

@DroneMang : There is a known problem with DRV8825 and 32bit boards. Please, increase the minimum pulse width to 3 or 4us and your skipped steps will be solved.

ejtagle avatar Dec 06 '18 07:12 ejtagle

@ejtagle, good observation, accumulating into an integer (always with floor behavior) will result in a lower acceleration_time than should actually be supplied to the curve function.

In the worst case scenario on a travel with 16,000 steps, it could be short by up to 16,000 "units". With 80 steps/mm and 100mm/s^2 acceleration, I get 1970000 for the "acceleration_time" (time duration for the acceleration/deceleration curve). 16,000 is ~0.8% of 1970000, so while that contributes, I don't think it's the only culprit (though, with all the details thrown in, perhaps it can be short by more than 1 unit per step).

Another culprit is the "get_period_inverse" function in planner.cpp, which returns 0x1000000 / d as an integer (not rounding, just using floor it looks like). So if it's fed the acceleration_time of 1970000, it returns 8, but the real ratio is 8.52. The value returned is used to compute the s-curve duration, so if it's low, the s-curve will effectively be computed for an acceleration_time of 2097152. If target speed is higher, the potential error is even worse.

For example, with the default acceleration of 100mm/s^2 and 80 steps/mm, this gcode produces a s-curve that's chopped off at nearly 6000steps/s (or 75mm/s):

; Reaches ~11K steps/second
; With ACC = 300mm/s^2 and 80 mm/step,
; s-curve deceleration stops at ~5745 steps/sec.
M17 ; Enable Steppers
M121 ; Disable endstop checking
G92 X0 Y0 Z0 E0 ; Set Position
G1 F18000
G1 X200
G1 X0

marlin-s-curve-200mm-5745-vel-spike

So maybe a heavy handed solution would be increasing the resolution of the "*time_inverse" variables (which probably means a re-write of the s-curve assembly code...).

xenovacivus avatar Dec 06 '18 09:12 xenovacivus

I wonder if a 32bit system would produce any insights into this problem? Does this same problem exist on 32bit boards? Has anyone been able to create the same traces for a 32bit system? Would it be easier to test any solution on a 32bit system?

gloomyandy avatar Dec 06 '18 09:12 gloomyandy

@gloomyandy : On 32bit systems, the accumulated error is less, just because the timer runs at a higher frequency. But i did never test if the acceleration curve is chopped...

@xenovacivus : Perhaps it would be desirable to round up... ? - Having a faster Scurve, besides pushing a little bit the maximum speed of the motors, would not get any discontinuity.

I am not a fan of increasing the resolution of the calculations. AVR is struggling to get the SCurve computed in realtime, and, in fact, when i wrote those routines, i had to reduce precision as much as possible to get it to run in realtime.

But rounding UP the result of get_period_inverse() is perfectly doable. Also rounding up the Scurve computation should be no problem... Perhaps something along that idea could work...

ejtagle avatar Dec 06 '18 17:12 ejtagle

@DroneMang : There is a known problem with DRV8825 and 32bit boards. Please, increase the minimum pulse width to 3 or 4us and your skipped steps will be solved.

The Rumba+ is 8-bit, but Arduino IDE says:

Sketch uses 184080 bytes (72%) of program storage space. Maximum is 253952 bytes. Global variables use 5311 bytes (64%) of dynamic memory, leaving 2881 bytes for local variables. Maximum is 8192 bytes.

So I think it should be able to run it. Maybe junction deviation and s-curve actually is too much though.

I'm waiting for the Revolve and Smoothieboard V2 Kickstarters to step up to 32-bit.

Linear Advance works great, and produces S-curve like movements when extruding, how does s-curve mesh with linear advance nowadays? S-curve would be nice for non-printing moves, perhaps an option could be introduced to use s-curve only for non printing moves, then you can skip the linear advance calculations for those moves?

DroneMang

DroneMang avatar Dec 06 '18 17:12 DroneMang

@xenovacivus problem solved?

boelle avatar Feb 21 '19 19:02 boelle

I don't think there's been any update to the related code (if there is, I'd be happy to test it!).

xenovacivus avatar Feb 26 '19 02:02 xenovacivus

bugfix 2.0 is updated almost every 2nd day so i think it has been

boelle avatar Feb 26 '19 06:02 boelle

Based on the later comments, I believe I was able to reproduce this on a week old bugfix-2.0.x. I essentially did M204 T50 to get a very low acceleration when moving the bed around with wobbly parts and when doing a fast G1 move across the whole bed travel distance of ~300mm, it accelerates fine, moves fine, starts slowing down as expected and, in the middle of deceleration, stops suddenly. This happened on RAMBo (8bit) @ 24V, disabling S_CURVE_ACCELERATION "fixed" it.

comps avatar Feb 26 '19 17:02 comps

You may just be pushing the processor too hard. S_CURVE_ACCELERATION is hand-tuned assembler on AVR and yet still does use up a lot of time in processing. Do you ever see issues with lower movement rates? You may also want to try ADAPTIVE_STEP_SMOOTHING to see if it keeps the load down.

thinkyhead avatar Mar 03 '19 06:03 thinkyhead

You may just be pushing the processor too hard. S_CURVE_ACCELERATION is hand-tuned assembler on AVR and yet still does use up a lot of time in processing. Do you ever see issues with lower movement rates? You may also want to try ADAPTIVE_STEP_SMOOTHING to see if it keeps the load down.

If you're referring to my issue, then I don't think it's a CPU issue - I do have those, but that usually results in stutter in movement, the decelleration here is part of a single-gcode move and is cut short prematurely and never finished (though the position seems to be correct according to the DRO on an LCD display, so no steps lost). The effect is less noticeable on slower feedrates (default F3000, noticeable even on F1000), but is still there. I run with ADAPTIVE_STEP_SMOOTHING enabled. If it seems unrelated to this issue, I can create a new one.

comps avatar Mar 03 '19 21:03 comps

Could it be that the issue stems from these lines:

// Steps required for acceleration, deceleration to/from nominal rate
uint32_t accelerate_steps = CEIL(estimate_acceleration_distance(initial_rate, block->nominal_rate, accel)),
decelerate_steps = FLOOR(estimate_acceleration_distance(block->nominal_rate, final_rate, -accel));
// Steps between acceleration and deceleration, if any
int32_t plateau_steps = block->step_event_count - accelerate_steps - decelerate_steps;

The steps required for acceleration and deceleration are calculated on a trapezoidal movement, but s-curve inherently needs more time for acceleration and deceleration, doesn't it?

microtronics avatar Aug 06 '19 16:08 microtronics

@xenovacivus did you retest with latest bugfix 2.0.x ?

boelle avatar Aug 28 '19 08:08 boelle