Skip to content

Commit d9ed6f0

Browse files
authored
🩹 Ensure safe change of XYZ smoothing (MarlinFirmware#28247)
1 parent c45354a commit d9ed6f0

6 files changed

Lines changed: 44 additions & 20 deletions

File tree

Marlin/src/module/ft_motion.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ AxisBits FTMotion::moving_axis_flags, // These axes are moving in the
7070

7171
// Block data variables.
7272
xyze_pos_t FTMotion::startPos, // (mm) Start position of block
73-
FTMotion::endPos_prevBlock = { 0.0f }; // (mm) End position of previous block
73+
FTMotion::endPos_prevBlock = { 0.0f }, // (mm) End position of previous block
74+
FTMotion::last_target_traj = { 0.0f }; // (mm) Last target position after shaping and smoothing
7475
xyze_float_t FTMotion::ratio; // (ratio) Axis move ratio of block
7576
float FTMotion::tau = 0.0f; // (s) Time since start of block
7677
bool FTMotion::fastForwardUntilMotion = false; // Fast forward time if there is no motion
@@ -524,13 +525,14 @@ xyze_float_t FTMotion::calc_traj_point(const float dist) {
524525

525526
// Approximate Gaussian smoothing via chained EMAs
526527
auto _smoothen = [&](const AxisEnum axis, axis_smoothing_t &smoo) {
527-
if (smoo.alpha <= 0.0f) return;
528-
float smooth_val = traj_coords[axis];
529-
for (uint8_t _i = 0; _i < FTM_SMOOTHING_ORDER; ++_i) {
530-
smoo.smoothing_pass[_i] += (smooth_val - smoo.smoothing_pass[_i]) * smoo.alpha;
531-
smooth_val = smoo.smoothing_pass[_i];
528+
if (smoo.alpha != 1.0f) {
529+
float smooth_val = traj_coords[axis];
530+
for (uint8_t _i = 0; _i < FTM_SMOOTHING_ORDER; ++_i) {
531+
smoo.smoothing_pass[_i] += (smooth_val - smoo.smoothing_pass[_i]) * smoo.alpha;
532+
smooth_val = smoo.smoothing_pass[_i];
533+
}
534+
traj_coords[axis] = smooth_val;
532535
}
533-
traj_coords[axis] = smooth_val;
534536
};
535537

536538
#define _SMOOTHEN(A) _smoothen(_AXIS(A), smoothing.A);
@@ -604,7 +606,7 @@ void FTMotion::fill_stepper_plan_buffer() {
604606

605607
// Get distance from trajectory generator
606608
xyze_float_t traj_coords = calc_traj_point(currentGenerator->getDistanceAtTime(tau));
607-
if (fastForwardUntilMotion && traj_coords == startPos) {
609+
if (fastForwardUntilMotion && traj_coords == last_target_traj) {
608610
// Axis synchronization delays all axes. When coming from a reset, there is a ramp up time filling all buffers.
609611
// If the slowest axis doesn't move and it isn't smoothened, this time can be skipped.
610612
// It eliminates idle time when changing smoothing time or shapers and speeds up homing and bed leveling.
@@ -614,6 +616,7 @@ void FTMotion::fill_stepper_plan_buffer() {
614616
// Calculate and store stepper plan in buffer
615617
stepping_enqueue(traj_coords);
616618
}
619+
last_target_traj = traj_coords;
617620
}
618621
}
619622

Marlin/src/module/ft_motion.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,8 @@ class FTMotion {
326326
private:
327327
// Block data variables.
328328
static xyze_pos_t startPos, // (mm) Start position of block
329-
endPos_prevBlock; // (mm) End position of previous block
329+
endPos_prevBlock, // (mm) End position of previous block
330+
last_target_traj; // (mm) Last target position after shaping and smoothing
330331
static xyze_float_t ratio; // (ratio) Axis move ratio of block
331332
static float tau; // (s) Time since start of block
332333
static bool fastForwardUntilMotion; // Fast forward time if there is no motion
@@ -375,8 +376,19 @@ class FTMotion {
375376
// Synchronize and reset motion prior to parameter changes
376377
friend void ft_config_t::prep_for_shaper_change();
377378
static void prep_for_shaper_change() {
379+
// planner.synchronize guarantees that motion reached a standstill with no echoes pending execution (including a runout block)
378380
planner.synchronize();
379-
reset();
381+
// Due to smoothing, the end position may not have been reached exactly.
382+
// This is normally fine, but if smoothing time changes, and we assume it was reached,
383+
// it may cause discontinuities.
384+
// Therefore, set the next starting position to the exact reached position.
385+
endPos_prevBlock = last_target_traj;
386+
// We now know that we are not moving and there are no pending echoes,
387+
// so set all shaping buffers to current position in case the new smoothing/shaping
388+
// parameters force input shaping to look in a past position for echoes.
389+
shaping.fill(endPos_prevBlock);
390+
TERN_(FTM_SMOOTHING, smoothing.fill(endPos_prevBlock));
391+
fastForwardUntilMotion = true;
380392
}
381393

382394
// Buffers

Marlin/src/module/ft_motion/shaping.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,9 @@ typedef struct Shaping {
151151
SHAPED_MAP(_RESET_ZI);
152152
zi_idx = 0;
153153
}
154+
void fill(const xyze_float_t pos) {
155+
#define _FILL_ZI(A) for (uint32_t i = 0; i < ftm_zmax; i++) A.d_zi[i] = pos.A;
156+
SHAPED_MAP(_FILL_ZI);
157+
#undef _FILL_ZI
158+
}
154159
} shaping_t;

Marlin/src/module/ft_motion/smoothing.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828

2929
// Set smoothing time and recalculate alpha and delay.
3030
void AxisSmoothing::set_time(const float s_time) {
31-
if (s_time > 0.001f) {
32-
alpha = 1.0f - expf(-(FTM_TS) * (FTM_SMOOTHING_ORDER) / s_time );
31+
if (s_time >= 0.0001f) {
32+
alpha = 1.0f - expf(-(FTM_TS) * (FTM_SMOOTHING_ORDER) / s_time);
3333
delay_samples = s_time * FTM_FS;
3434
}
3535
else {
36-
alpha = 0.0f;
36+
alpha = 1.0f;
3737
delay_samples = 0;
3838
}
3939
}

Marlin/src/module/ft_motion/smoothing.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,9 @@ typedef struct Smoothing {
5555
LOGICAL_AXIS_MAP(_CLEAR);
5656
#undef _CLEAR
5757
}
58+
void fill(const xyze_float_t pos) {
59+
#define _FILL_SMO(A) for (uint32_t i = 0; i < FTM_SMOOTHING_ORDER; i++) A.smoothing_pass[i] = pos.A;
60+
LOGICAL_AXIS_MAP(_FILL_SMO);
61+
#undef _FILL_SMO
62+
}
5863
} smoothing_t;

Marlin/src/module/ft_motion/stepping.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,24 @@ FORCE_INLINE constexpr uint32_t a_times_b_shift_16(const uint32_t a, const uint3
3535
constexpr int CLZ32(const uint32_t v, const int c=0) {
3636
return v ? (TEST32(v, 31)) ? c : CLZ32(v << 1, c + 1) : 32;
3737
}
38-
#define FTM_NEVER uint32_t(UINT16_MAX) // Reserved number to indicate "no ticks in this frame" (FRAME_TICKS_FP+1 would work too)
3938
constexpr uint32_t FRAME_TICKS = STEPPER_TIMER_RATE / FTM_FS; // Timer ticks per frame
4039
constexpr uint32_t FTM_Q_INT = 32u - CLZ32(FRAME_TICKS + 1U); // Bits to represent the integer part of the max value (duration of a frame, +1 one for FTM_NEVER).
4140
constexpr uint32_t FTM_Q = 16u - FTM_Q_INT; // uint16 interval fractional bits.
4241
// Intervals buffer has fixed point numbers with the point on this position
4342

44-
static_assert(FRAME_TICKS < FTM_NEVER, "(STEPPER_TIMER_RATE / FTM_FS) (" STRINGIFY(STEPPER_TIMER_RATE) " / " STRINGIFY(FTM_FS) ") must be < " STRINGIFY(FTM_NEVER) " to fit 16-bit fixed-point numbers.");
45-
46-
// Sanity check
47-
static_assert(POW(2, 16 - FTM_Q) > FRAME_TICKS, "FRAME_TICKS in Q format should fit in a uint16");
48-
static_assert(POW(2, 16 - FTM_Q - 1) <= FRAME_TICKS, "A smaller FTM_Q would still alow a FRAME_TICKS in Q format to fit in a uint16");
49-
5043
// The _FP and _fp suffixes mean the number is in fixed point format with the point at the FTM_Q position.
5144
// See: https://en.wikipedia.org/wiki/Fixed-point_arithmetic
5245
// e.g., number_fp = number << FTM_Q
5346
// number == (number_fp >> FTM_Q)
5447
constexpr uint32_t ONE_FP = 1UL << FTM_Q; // Number 1 in fixed point format
5548
constexpr uint32_t FP_FLOOR_MASK = ~(ONE_FP - 1); // Bit mask to do FLOOR in fixed point
5649
constexpr uint32_t FRAME_TICKS_FP = FRAME_TICKS << FTM_Q; // Ticks in a frame in fixed point
50+
constexpr uint32_t FTM_NEVER = FRAME_TICKS_FP + 1; // Reserved number to indicate "no ticks in this frame", also max isr wait on empty stepper buffer
51+
52+
// Sanity check
53+
static_assert(FRAME_TICKS < FTM_NEVER, "(STEPPER_TIMER_RATE / FTM_FS) (" STRINGIFY(STEPPER_TIMER_RATE) " / " STRINGIFY(FTM_FS) ") must be < " STRINGIFY(FTM_NEVER) " to fit 16-bit fixed-point numbers.");
54+
static_assert(POW(2, 16 - FTM_Q) > FRAME_TICKS, "FRAME_TICKS in Q format should fit in a uint16");
55+
static_assert(POW(2, 16 - FTM_Q - 1) <= FRAME_TICKS, "A smaller FTM_Q would still alow a FRAME_TICKS in Q format to fit in a uint16");
5756

5857
typedef struct stepper_plan {
5958
AxisBits dir_bits;

0 commit comments

Comments
 (0)