Marlin icon indicating copy to clipboard operation
Marlin copied to clipboard

FTMotion: Call planner.synchronize() before modifying ftmotion configurations

Open dbuezas opened this issue 1 month ago • 22 comments

Any changes to shaping frequency or shaper, and also changes to smoothing time produce layer shifts. This is because axis synchronisation calculates how much to delay each axis based on those properties, and when they are changed mid print, the printer needs to instantly jump to a different position.

Description

Requirements

Benefits

Configurations

Related Issues

dbuezas avatar Nov 24 '25 14:11 dbuezas

In M493.cpp, the call to Stepper::ftMotion_syncPosition() was removed with the refactoring. Just set the update flag to true and call stepper.ftMotion_syncposition() at if (flag.update) .... Just a precision prefer stepper.ftMotion_syncPosition(), if you print a resonance test gcode (change shaping frequency on every layer change) , planner.synchronize() is not enough to avoid layer shift.

narno2202 avatar Nov 24 '25 16:11 narno2202

It isn't? I think one would need a runout block too then

dbuezas avatar Nov 24 '25 16:11 dbuezas

We could call ftmotion.plan_runout_block() after synchronising there, that would add enough time for all smoothing and all shaping to finish stepping.

dbuezas avatar Nov 24 '25 18:11 dbuezas

In my mind, 2 things are important: wait for the "planner to synchronize" and set the correct position. With stepper.ftMotion_syncPosition(), before all the changes, no runout_block was necessary. Just test it in the simulator.

narno2202 avatar Nov 24 '25 20:11 narno2202

I will soon do a pass on this to add setters, and have those setters do the synchronization, because we also have to duplicate this behavior for menu items, and setters will ensure everything inherits the appropriate amount of synchronization.

thinkyhead avatar Nov 24 '25 20:11 thinkyhead

Why is setting the position important at all? That position is not even used inside ftmotion. I think it is all about adding a runout block so all echoes and smoothing settles

dbuezas avatar Nov 24 '25 20:11 dbuezas

You can also switch from standard to FT_MOTION in an ongoing print. If you only call planner_synchronize , you will have a layer shift; that's why I added stepper.ftMotion_synchronize function a long time ago.

narno2202 avatar Nov 24 '25 20:11 narno2202

You can also switch from standard to FT_MOTION in an ongoing print. If you only call planner_synchronize , you will have a layer shift; that's why I added stepper.ftMotion_synchronize function a long time ago.

Should planner.synchronize itself include a call to stepper.ftMotion_synchronize as needed, so that all things that want to introduce a pause also ping FT Motion?

thinkyhead avatar Nov 24 '25 21:11 thinkyhead

planner_synchronize is already in stepper.ftMotion_synchronize, but stepper.ftMotion_synchronize is only useful for FT_MOTION. We could just add position sync if FT_MOTION is active in planner_synchronize. One function to rule them all.

narno2202 avatar Nov 24 '25 21:11 narno2202

One more remark, calling planner.synchronize is only useful when FT_MOTION is active and there is an ongoing print.

narno2202 avatar Nov 25 '25 10:11 narno2202

The 2 functions could be :

#if ENABLED(FT_MOTION)

  void Stepper::ftMotion_syncPosition() {

    // Update stepper positions from the planner
    AVR_ATOMIC_SECTION_START();
    count_position = planner.position;
    AVR_ATOMIC_SECTION_END();
  }

#endif // FT_MOTION

in stepper.cpp and

/**
 * Block until the planner is finished processing
 */
void Planner::synchronize() {
  TERN0(FT_MOTION, const bool ft_busy = ftMotion.busy); 
  while (busy()) idle(); 
  #if ENABLED(FT_MOTION)
    if (ftMotion.cfg.active && ft_busy) 
      // Update stepper positions from the planner
      stepper.ftMotion_syncPosition();
  #endif
}

in planner.cpp.

Position is synchronized only if the printer is busy. Keep Stepper::ftMotion_syncPosition() to deal with private variable and function.

narno2202 avatar Nov 25 '25 13:11 narno2202

I don't get why this would make any difference:

   AVR_ATOMIC_SECTION_START();
    count_position = planner.position;
    AVR_ATOMIC_SECTION_END();

The layer shifts come from axis synchronisation suddenly changing how much it delays axes, which then see an instantaneous jump in position

dbuezas avatar Nov 25 '25 13:11 dbuezas

I think what we could do is scheduling a runout block after synchronising the planner, and then waiting for it to finish.

dbuezas avatar Nov 25 '25 13:11 dbuezas

It's the only way to avoid layer shift when you switch between standard motion and FT_MOTION during an ongoing print. Currently, in the simulator, there is no layer shift when you change shaping frequencies or generator type, but it's present when we change smooting value, and it's not solved by adding planner.synchronize.

narno2202 avatar Nov 25 '25 17:11 narno2202

I see. The layer shifts when changing frequency, shaper or smoothing won't be visible in the simulator. FTMotion doesn't loose track of current position, it kind of travels back or forward in time, so the steppers skip

dbuezas avatar Nov 25 '25 18:11 dbuezas

When I change smooting time when printing an input shaping test file in the simulator, the layer shift is obvious if the value is enough. Nothing happens with frequency or generator change. It's not a good idea to change parameters on the fly, but it's mandatory for calibration.

narno2202 avatar Nov 25 '25 18:11 narno2202

E Smoothing in particularly is something i want to change on the fly if the extruder doesn't sound great with a particular filament.

dbuezas avatar Nov 25 '25 19:11 dbuezas

Another option would be to update delays incrementally

dbuezas avatar Nov 29 '25 13:11 dbuezas

Thinking if i should close this pr since it will require more careful thought

dbuezas avatar Dec 02 '25 20:12 dbuezas

Thinking if i should close this pr since it will require more careful thought

Some of it makes sense, like having more standardized setters that are shared by G-code and UI. I can cherry-pick other adjustments from this PR and merge them separately, whether you want to close it or not. But, we can work out the details on the Discord of what synchronization is needed, and where, when changing settings.

thinkyhead avatar Dec 02 '25 21:12 thinkyhead

Right, I think what you did is great, but my naive calls to synchronize don't fix what I intended to fix

dbuezas avatar Dec 03 '25 06:12 dbuezas

While tracking moving_axis_flags while trying to fix setting smoothing time during an ongoing print (the goal was to change smoothing time when the axis doesn't move), I realize that once the flag is set to true for an axis, thisd is for all the print. The probability of if (oldBusy && !busy) moving_axis_flags.reset(); is near zero. If the goal is to set a valid motion flag for each block,

// Set busy status for use by planner.busy()
  const bool oldBusy = busy;
  busy = stepping.is_busy();
  if (oldBusy && !busy) moving_axis_flags.reset();

should be

// Set busy status for use by planner.busy()
  busy = stepping.is_busy();

and

#define _SET_MOVE_END(A) do{ \
   if (moveDist.A) { \
     moving_axis_flags.A = true; \
     axis_move_dir.A = moveDist.A > 0; \
   } \
 }while(0);

 LOGICAL_AXIS_MAP(_SET_MOVE_END);

should be

#define _SET_MOVE_END(A) do{ \
   if (moveDist.A) { \
     moving_axis_flags.A = true; \
     axis_move_dir.A = moveDist.A > 0; \
   } \
  else \
     moving_axis_flags.A = false; \
 }while(0);

 LOGICAL_AXIS_MAP(_SET_MOVE_END);

For sure calling planner.synchronize doesn't fix changing smoothing time during a print, nor all my various tries.

narno2202 avatar Dec 06 '25 22:12 narno2202

@narno2202 — I've applied your PR #28214 but I'm not sure it's entirely correct, per your comment above. We want the moving_axis_flags and axis_move_dir flags to be set based on the fact that a block has motion in certain axes. We don't want to set it based on whether or not there is a STEP command in the ftm-command queue because these STEP commands can be very sparse for any given axis and we would miss the flag most of the time.

So, we still need to set these flags at the start of the processing of each block, and we need to clear them when each block has completed, (or at least, when the ftm-command queue has reached its end).

So let us continue to assess the way endstops and probes function with FTM and make sure we are getting the same kind of behavior that we get from standard motion when it comes to checking endstops and probes. Keep in mind that we need to be able to check these during printing too, and not just in very controlled situations.

thinkyhead avatar Dec 11 '25 22:12 thinkyhead

@thinkyhead , with the previous code, once moving_axis_flag is set, it stays set for the entire print (tested many times with the same result). @dbuezas's code set axis_moving_flag when a block is loaded the PR does the same. As I understand, axis_moving_flag is only important if there is a machine dysfunction and for aborting print from SD card if an endstop is triggered.

For homing only one block is loaded so the flag is always correct as the direction. axis_moving_flag could be reset when a block is discarded as it was the case for axis_did_move a long time ago, but buffer management was different and discard_planner_block_protected() is called just before a new block is loaded so it's not the good place.

Other option is to fall back to the old code which set the moving flag in the stepper ISR and assume that temperature ISR is enough to catch a triggered end stop. I can't test these because I'm using sensorless homing.

narno2202 avatar Dec 11 '25 23:12 narno2202

with the previous code, once moving_axis_flag is set, it stays set for the entire print

I don't doubt your changes are an improvement but I suspect there are still going to be some issues.

As I understand, axis_moving_flag is only important if there is a machine dysfunction…

Maybe the case for FT Motion, but with standard motion axis_did_move is tested any time endstops are enabled, so it is a necessary element of homing, probing, and abort-on-trigger.

For homing only one block is loaded so the flag is always correct as the direction.

That is currently true, but a homing or side-probing move could theoretically be done with leveling enabled, or there may be kinematics that require segmentation even for homing moves. So the endstop logic is agnostic about that, and all it cares about is whether the axis is currently moving towards the endstop.


I'll dive into the simulator and mess around for a while to get a better understanding of how these motion/direction flags are behaving. I'll see whether they are in sync with actual motion, persistent through the entire move, and so on.

thinkyhead avatar Dec 12 '25 00:12 thinkyhead

@thinkyhead , I've made changes to use axis_did_move which is set in stepper.stepper::ftMotion_stepper(). The value is updated on every stepper ISR. Homing and probing is fine with my machine (remember I use sensorless homing). Could you have a look https://github.com/narno2202/Marlin/tree/FTM_Move_Dir_Test

narno2202 avatar Dec 12 '25 10:12 narno2202

ups deleted branch by accident

dbuezas avatar Dec 16 '25 10:12 dbuezas

ups deleted branch by accident

DHL is also prone to this error.

thinkyhead avatar Dec 16 '25 10:12 thinkyhead