From dc9e57464fbd89c851524f2282bfd9e5711cdbf4 Mon Sep 17 00:00:00 2001 From: David Buezas Date: Sun, 28 Sep 2025 02:42:33 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20FTMotion=20Z=20shaping,=20axis=20sy?= =?UTF-8?q?nc,=20axis=20smoothing=20(#28055)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Marlin/Configuration_adv.h | 74 ++-- .../src/HAL/LPC1768/inc/Conditionals_post.h | 2 +- Marlin/src/HAL/STM32/inc/Conditionals_post.h | 2 +- Marlin/src/HAL/STM32F1/MinSerial.cpp | 2 +- Marlin/src/core/language.h | 15 + Marlin/src/gcode/feature/ft_motion/M493.cpp | 343 +++++++++++++----- Marlin/src/gcode/feature/ft_motion/M494.cpp | 133 +++++++ Marlin/src/gcode/gcode.cpp | 3 + Marlin/src/gcode/gcode.h | 4 + Marlin/src/inc/Conditionals-1-axes.h | 1 + Marlin/src/inc/Conditionals-4-adv.h | 10 + Marlin/src/inc/Conditionals-5-post.h | 16 + Marlin/src/inc/SanityCheck.h | 6 + .../lcd/dogm/fontdata/fontdata_ISO10646_1.h | 2 +- Marlin/src/lcd/e3v2/proui/dwin.cpp | 4 +- Marlin/src/lcd/language/language_en.h | 2 + Marlin/src/lcd/menu/menu_motion.cpp | 130 ++++--- Marlin/src/module/ft_motion.cpp | 188 ++++++++-- Marlin/src/module/ft_motion.h | 106 ++++-- Marlin/src/module/ft_types.h | 59 ++- buildroot/tests/rambo | 2 +- 21 files changed, 845 insertions(+), 259 deletions(-) create mode 100644 Marlin/src/gcode/feature/ft_motion/M494.cpp diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 215f1f8026..722daf49c9 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1143,49 +1143,73 @@ /** * Fixed-time-based Motion Control -- BETA FEATURE - * Enable/disable and set parameters with G-code M493. + * Enable/disable and set parameters with G-code M493 and M494. * See ft_types.h for named values used by FTM options. */ //#define FT_MOTION #if ENABLED(FT_MOTION) - //#define FTM_IS_DEFAULT_MOTION // Use FT Motion as the factory default? + //#define FTM_IS_DEFAULT_MOTION // Use FT Motion as the factory default? + //#define FT_MOTION_MENU // Provide a MarlinUI menu to set M493 and M494 parameters + #define FTM_DEFAULT_DYNFREQ_MODE dynFreqMode_DISABLED // Default mode of dynamic frequency calculation. (DISABLED, Z_BASED, MASS_BASED) - #define FTM_LINEAR_ADV_DEFAULT_ENA false // Default linear advance enable (true) or disable (false) - #define FTM_LINEAR_ADV_DEFAULT_K 0.0f // Default linear advance gain. (Acceleration-based scaling factor.) + #define FTM_LINEAR_ADV_DEFAULT_ENA false // Default linear advance enable (true) or disable (false) + #define FTM_LINEAR_ADV_DEFAULT_K 0.0f // Default linear advance gain. (Acceleration-based scaling factor.) #define FTM_DEFAULT_SHAPER_X ftMotionShaper_NONE // Default shaper mode on X axis (NONE, ZV, ZVD, ZVDD, ZVDDD, EI, 2HEI, 3HEI, MZV) - #define FTM_SHAPING_DEFAULT_FREQ_X 37.0f // (Hz) Default peak frequency used by input shapers - #define FTM_SHAPING_ZETA_X 0.1f // Zeta used by input shapers for X axis - #define FTM_SHAPING_V_TOL_X 0.05f // Vibration tolerance used by EI input shapers for X axis + #define FTM_SHAPING_DEFAULT_FREQ_X 37.0f // (Hz) Default peak frequency used by input shapers + #define FTM_SHAPING_ZETA_X 0.1f // Zeta used by input shapers for X axis + #define FTM_SHAPING_V_TOL_X 0.05f // Vibration tolerance used by EI input shapers for X axis #define FTM_DEFAULT_SHAPER_Y ftMotionShaper_NONE // Default shaper mode on Y axis - #define FTM_SHAPING_DEFAULT_FREQ_Y 37.0f // (Hz) Default peak frequency used by input shapers - #define FTM_SHAPING_ZETA_Y 0.1f // Zeta used by input shapers for Y axis - #define FTM_SHAPING_V_TOL_Y 0.05f // Vibration tolerance used by EI input shapers for Y axis + #define FTM_SHAPING_DEFAULT_FREQ_Y 37.0f // (Hz) Default peak frequency used by input shapers + #define FTM_SHAPING_ZETA_Y 0.1f // Zeta used by input shapers for Y axis + #define FTM_SHAPING_V_TOL_Y 0.05f // Vibration tolerance used by EI input shapers for Y axis - //#define FT_MOTION_MENU // Provide a MarlinUI menu to set M493 parameters + //#define FTM_SHAPER_Z // Include Z shaping support + #define FTM_DEFAULT_SHAPER_Z ftMotionShaper_NONE // Default shaper mode on Z axis + #define FTM_SHAPING_DEFAULT_FREQ_Z 21.0f // (Hz) Default peak frequency used by input shapers + #define FTM_SHAPING_ZETA_Z 0.03f // Zeta used by input shapers for Z axis + #define FTM_SHAPING_V_TOL_Z 0.05f // Vibration tolerance used by EI input shapers for Z axis + + //#define FTM_SHAPER_E // Include E shaping support + // Required to synchronise extruder with XYZ (better quality) + #define FTM_DEFAULT_SHAPER_E ftMotionShaper_NONE // Default shaper mode on Extruder axis + #define FTM_SHAPING_DEFAULT_FREQ_E 21.0f // (Hz) Default peak frequency used by input shapers + #define FTM_SHAPING_ZETA_E 0.03f // Zeta used by input shapers for E axis + #define FTM_SHAPING_V_TOL_E 0.05f // Vibration tolerance used by EI input shapers for E axis + + //#define FTM_SMOOTHING // Smoothing can reduce artifacts and make steppers quieter + // on sharp corners, but too much will round corners. + #if ENABLED(FTM_SMOOTHING) + #define FTM_MAX_SMOOTHING_TIME 0.10f // Maximum smoothing time (seconds), higher consumes more RAM. + // Increase smoothing time to reduce jerky motion, ghosting and noises. + #define FTM_SMOOTHING_TIME_X 0.00f // (s) Smoothing time for X axis. Zero means disabled. + #define FTM_SMOOTHING_TIME_Y 0.00f // (s) Smoothing time for Y axis + #define FTM_SMOOTHING_TIME_Z 0.00f // (s) Smoothing time for Z axis + #define FTM_SMOOTHING_TIME_E 0.02f // (s) Smoothing time for E axis. Prevents noise/skipping from LA by + // smoothing acceleration peaks, which may also smooth curved surfaces. + #endif /** * Advanced configuration */ - #define FTM_UNIFIED_BWS // DON'T DISABLE unless you use Ulendo FBS (not implemented) + #define FTM_UNIFIED_BWS // DON'T DISABLE unless you use Ulendo FBS (not implemented) #if ENABLED(FTM_UNIFIED_BWS) - #define FTM_BW_SIZE 100 // Unified Window and Batch size with a ratio of 2 + #define FTM_BW_SIZE 100 // Unified Window and Batch size with a ratio of 2 #else - #define FTM_WINDOW_SIZE 200 // Custom Window size for trajectory generation needed by Ulendo FBS - #define FTM_BATCH_SIZE 100 // Custom Batch size for trajectory generation needed by Ulendo FBS + #define FTM_WINDOW_SIZE 200 // Custom Window size for trajectory generation needed by Ulendo FBS + #define FTM_BATCH_SIZE 100 // Custom Batch size for trajectory generation needed by Ulendo FBS #endif - #define FTM_FS 1000 // (Hz) Frequency for trajectory generation. (Reciprocal of FTM_TS) - #define FTM_TS 0.001f // (s) Time step for trajectory generation. (Reciprocal of FTM_FS) + #define FTM_FS 1000 // (Hz) Frequency for trajectory generation. (Reciprocal of FTM_TS) #if DISABLED(COREXY) - #define FTM_STEPPER_FS 20000 // (Hz) Frequency for stepper I/O update + #define FTM_STEPPER_FS 20000 // (Hz) Frequency for stepper I/O update // Use this to adjust the time required to consume the command buffer. // Try increasing this value if stepper motion is choppy. - #define FTM_STEPPERCMD_BUFF_SIZE 3000 // Size of the stepper command buffers + #define FTM_STEPPERCMD_BUFF_SIZE 3000 // Size of the stepper command buffers #else // CoreXY motion needs a larger buffer size. These values are based on our testing. @@ -1193,17 +1217,7 @@ #define FTM_STEPPERCMD_BUFF_SIZE 6000 #endif - #define FTM_STEPS_PER_UNIT_TIME (FTM_STEPPER_FS / FTM_FS) // Interpolated stepper commands per unit time - #define FTM_MIN_TICKS ((STEPPER_TIMER_RATE) / (FTM_STEPPER_FS)) // Minimum stepper ticks between steps - - #define FTM_MIN_SHAPE_FREQ 10 // Minimum shaping frequency - #define FTM_RATIO (FTM_FS / FTM_MIN_SHAPE_FREQ) // Factor for use in FTM_ZMAX. DON'T CHANGE. - #define FTM_ZMAX (FTM_RATIO * 2) // Maximum delays for shaping functions (even numbers only!) - // Calculate as: - // ZV : FTM_RATIO / 2 - // ZVD, MZV : FTM_RATIO - // 2HEI : FTM_RATIO * 3 / 2 - // 3HEI : FTM_RATIO * 2 + #define FTM_MIN_SHAPE_FREQ 10 // (Hz) Minimum shaping frequency, lower consumes more RAM #endif // FT_MOTION /** diff --git a/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h b/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h index 0b03cb2aea..a1b4dd5099 100644 --- a/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h +++ b/Marlin/src/HAL/LPC1768/inc/Conditionals_post.h @@ -29,6 +29,6 @@ // LPC1768 boards seem to lose steps when saving to EEPROM during print (issue #20785) // TODO: Which other boards are incompatible? -#if defined(MCU_LPC1768) && ENABLED(FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 +#if ALL(MCU_LPC1768, FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 #define PRINTCOUNTER_SYNC #endif diff --git a/Marlin/src/HAL/STM32/inc/Conditionals_post.h b/Marlin/src/HAL/STM32/inc/Conditionals_post.h index 6c97a635b3..8d72e720c1 100644 --- a/Marlin/src/HAL/STM32/inc/Conditionals_post.h +++ b/Marlin/src/HAL/STM32/inc/Conditionals_post.h @@ -29,6 +29,6 @@ #endif // Some STM32F4 boards may lose steps when saving to EEPROM during print (PR #17946) -#if defined(STM32F4xx) && ENABLED(FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 +#if ALL(STM32F4xx, FLASH_EEPROM_EMULATION) && PRINTCOUNTER_SAVE_INTERVAL > 0 #define PRINTCOUNTER_SYNC #endif diff --git a/Marlin/src/HAL/STM32F1/MinSerial.cpp b/Marlin/src/HAL/STM32F1/MinSerial.cpp index 8fb9133254..0d9a611d7e 100644 --- a/Marlin/src/HAL/STM32F1/MinSerial.cpp +++ b/Marlin/src/HAL/STM32F1/MinSerial.cpp @@ -92,7 +92,7 @@ void install_min_serial() { HAL_min_serial_out = &TX; } -#if DISABLED(DYNAMIC_VECTORTABLE) && DISABLED(STM32F0xx) // Cortex M0 can't branch to a symbol that's too far, so we have a specific hack for them +#if NONE(DYNAMIC_VECTORTABLE, STM32F0xx) // Cortex M0 can't branch to a symbol that's too far, so we have a specific hack for them extern "C" { __attribute__((naked)) void JumpHandler_ASM() { __asm__ __volatile__ ( diff --git a/Marlin/src/core/language.h b/Marlin/src/core/language.h index 3a50fcdc6b..f2860ae7d4 100644 --- a/Marlin/src/core/language.h +++ b/Marlin/src/core/language.h @@ -358,6 +358,21 @@ #define STR_Z2 STR_C "2" #define STR_Z3 STR_C "3" #define STR_Z4 STR_C "4" +#if CORE_IS_XY || CORE_IS_XZ + #define STEPPER_A_NAME 'A' +#else + #define STEPPER_A_NAME 'X' +#endif +#if CORE_IS_XY || CORE_IS_YZ + #define STEPPER_B_NAME 'B' +#else + #define STEPPER_B_NAME 'Y' +#endif +#if CORE_IS_XZ || CORE_IS_YZ + #define STEPPER_C_NAME 'C' +#else + #define STEPPER_C_NAME 'Z' +#endif // // Endstop Names used by Endstops::report_states diff --git a/Marlin/src/gcode/feature/ft_motion/M493.cpp b/Marlin/src/gcode/feature/ft_motion/M493.cpp index 606d23179d..158ca27706 100644 --- a/Marlin/src/gcode/feature/ft_motion/M493.cpp +++ b/Marlin/src/gcode/feature/ft_motion/M493.cpp @@ -28,8 +28,9 @@ #include "../../../module/ft_motion.h" #include "../../../module/stepper.h" -void say_shaper_type(const AxisEnum a) { - SERIAL_ECHOPGM(" axis "); +void say_shaper_type(const AxisEnum a, bool &sep, const char axis_name) { + if (sep) SERIAL_ECHOPGM(" ; "); + SERIAL_CHAR(axis_name, '='); switch (ftMotion.cfg.shaper[a]) { default: break; case ftMotionShaper_ZV: SERIAL_ECHOPGM("ZV"); break; @@ -41,46 +42,36 @@ void say_shaper_type(const AxisEnum a) { case ftMotionShaper_3HEI: SERIAL_ECHOPGM("3 Hump EI"); break; case ftMotionShaper_MZV: SERIAL_ECHOPGM("MZV"); break; } - SERIAL_ECHOPGM(" shaping"); + sep = true; } -#if CORE_IS_XY || CORE_IS_XZ - #define AXIS_0_NAME "A" -#else - #define AXIS_0_NAME "X" -#endif -#if CORE_IS_XY || CORE_IS_YZ - #define AXIS_1_NAME "B" -#else - #define AXIS_1_NAME "Y" -#endif - void say_shaping() { // FT Enabled SERIAL_ECHO_TERNARY(ftMotion.cfg.active, "Fixed-Time Motion ", "en", "dis", "abled"); // FT Shaping + bool sep = false; + SERIAL_ECHOPGM(" ("); #if HAS_X_AXIS - if (AXIS_IS_SHAPING(X)) { - SERIAL_ECHOPGM(" with " AXIS_0_NAME); - say_shaper_type(X_AXIS); - } + if (AXIS_IS_SHAPING(X)) say_shaper_type(X_AXIS, sep, STEPPER_A_NAME); #endif #if HAS_Y_AXIS - if (AXIS_IS_SHAPING(Y)) { - SERIAL_ECHOPGM(" and with " AXIS_1_NAME); - say_shaper_type(Y_AXIS); - } + if (AXIS_IS_SHAPING(Y)) say_shaper_type(Y_AXIS, sep, STEPPER_B_NAME); #endif - - SERIAL_ECHOLNPGM("."); + #if ENABLED(FTM_SHAPER_Z) + if (AXIS_IS_SHAPING(Z)) say_shaper_type(Z_AXIS, sep, STEPPER_C_NAME); + #endif + #if ENABLED(FTM_SHAPER_E) + if (AXIS_IS_SHAPING(E)) say_shaper_type(E_AXIS, sep, 'E'); + #endif + SERIAL_ECHOLNPGM(")"); const bool z_based = TERN0(HAS_DYNAMIC_FREQ_MM, ftMotion.cfg.dynFreqMode == dynFreqMode_Z_BASED), g_based = TERN0(HAS_DYNAMIC_FREQ_G, ftMotion.cfg.dynFreqMode == dynFreqMode_MASS_BASED), dynamic = z_based || g_based; // FT Dynamic Frequency Mode - if (AXIS_IS_SHAPING(X) || AXIS_IS_SHAPING(Y)) { + if (AXIS_IS_SHAPING(X) || AXIS_IS_SHAPING(Y) || AXIS_IS_SHAPING(Z) || AXIS_IS_SHAPING(E)) { #if HAS_DYNAMIC_FREQ SERIAL_ECHOPGM("Dynamic Frequency Mode "); switch (ftMotion.cfg.dynFreqMode) { @@ -97,7 +88,8 @@ void say_shaping() { #endif #if HAS_X_AXIS - SERIAL_ECHO_TERNARY(dynamic, AXIS_0_NAME " ", "base dynamic", "static", " shaper frequency: "); + SERIAL_CHAR(STEPPER_A_NAME); + SERIAL_ECHO_TERNARY(dynamic, " ", "base dynamic", "static", " shaper frequency: "); SERIAL_ECHO(p_float_t(ftMotion.cfg.baseFreq.x, 2), F("Hz")); #if HAS_DYNAMIC_FREQ if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(ftMotion.cfg.dynFreqK.x, 2), F("Hz/"), z_based ? F("mm") : F("g")); @@ -106,13 +98,24 @@ void say_shaping() { #endif #if HAS_Y_AXIS - SERIAL_ECHO_TERNARY(dynamic, AXIS_1_NAME " ", "base dynamic", "static", " shaper frequency: "); + SERIAL_CHAR(STEPPER_B_NAME); + SERIAL_ECHO_TERNARY(dynamic, " ", "base dynamic", "static", " shaper frequency: "); SERIAL_ECHO(p_float_t(ftMotion.cfg.baseFreq.y, 2), F(" Hz")); #if HAS_DYNAMIC_FREQ if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(ftMotion.cfg.dynFreqK.y, 2), F("Hz/"), z_based ? F("mm") : F("g")); #endif SERIAL_EOL(); #endif + + #if ENABLED(FTM_SHAPER_Z) + SERIAL_CHAR(STEPPER_C_NAME); + SERIAL_ECHO_TERNARY(dynamic, " ", "base dynamic", "static", " shaper frequency: "); + SERIAL_ECHO(p_float_t(ftMotion.cfg.baseFreq.z, 2), F(" Hz")); + #if HAS_DYNAMIC_FREQ + if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(ftMotion.cfg.dynFreqK.z, 2), F("Hz/"), z_based ? F("mm") : F("g")); + #endif + SERIAL_EOL(); + #endif } #if HAS_EXTRUDERS @@ -129,26 +132,44 @@ void GcodeSuite::M493_report(const bool forReplay/*=true*/) { report_heading_etc(forReplay, F(STR_FT_MOTION)); const ft_config_t &c = ftMotion.cfg; - SERIAL_ECHOPGM(" M493 S", c.active); - #if HAS_X_AXIS - SERIAL_ECHOPGM(" A", c.baseFreq.x); - #if HAS_Y_AXIS - SERIAL_ECHOPGM(" B", c.baseFreq.y); - #endif - #endif - #if HAS_DYNAMIC_FREQ - SERIAL_ECHOPGM(" D", c.dynFreqMode); + SERIAL_ECHOLNPGM( + " M493 S", c.active #if HAS_X_AXIS - SERIAL_ECHOPGM(" F", c.dynFreqK.x); + , " A", c.baseFreq.x + #endif + #if HAS_Y_AXIS + , " B", c.baseFreq.y + #endif + #if ENABLED(FTM_SHAPER_Z) + , " C", c.baseFreq.z + #endif + #if ENABLED(FTM_SHAPER_E) + , " E", c.baseFreq.e + #endif + + #if HAS_DYNAMIC_FREQ + , " D", c.dynFreqMode + #if HAS_X_AXIS + , " F", c.dynFreqK.x + #endif #if HAS_Y_AXIS - SERIAL_ECHOPGM(" H", c.dynFreqK.y); + , " H", c.dynFreqK.y + #endif + #if ENABLED(FTM_SHAPER_Z) + , " L", c.dynFreqK.z + #endif + #if ENABLED(FTM_SHAPER_E) + , " O", c.dynFreqK.e #endif #endif - #endif - #if HAS_EXTRUDERS - SERIAL_ECHOPGM(" P", c.linearAdvEna, " K", c.linearAdvK); - #endif - SERIAL_EOL(); + + , " G", c.axis_sync_enabled + + #if HAS_EXTRUDERS + , " P", c.linearAdvEna, " K", c.linearAdvK + #endif + + ); } /** @@ -158,8 +179,8 @@ void GcodeSuite::M493_report(const bool forReplay/*=true*/) { * 0: Fixed-Time Motion OFF (Standard Motion) * 1: Fixed-Time Motion ON * - * X/Y Set the vibration compensator [input shaper] mode for X / Y axis. - * Users / slicers must remember to set the mode for both axes! + * X/Y/Z/E Set the vibration compensator [input shaper] mode for an axis. + * Users / slicers must remember to set the mode for all relevant axes! * 0: NONE : No input shaper * 1: ZV : Zero Vibration * 2: ZVD : Zero Vibration and Derivative @@ -174,20 +195,35 @@ void GcodeSuite::M493_report(const bool forReplay/*=true*/) { * * K Set Linear Advance gain * + * G Enable (1) or Disable (0) axis synchronization. + * * D Set Dynamic Frequency mode * 0: DISABLED * 1: Z-based (Requires a Z axis) * 2: Mass-based (Requires X and E axes) * - * A Set static/base frequency for the X axis - * F Set frequency scaling for the X axis - * I 0.0 Set damping ratio for the X axis - * Q 0.00 Set the vibration tolerance for the X axis + * A Set X static/base frequency + * F Set X frequency scaling + * I Set X damping ratio + * Q Set X vibration tolerance + * + * B Set Y static/base frequency + * H Set Y frequency scaling + * J Set Y damping ratio + * R Set Y vibration tolerance + * + * With FTM_SHAPING_Z: + * C Set Z static/base frequency + * L Set Z frequency scaling + * O Set Z damping ratio + * M Set Z vibration tolerance + * + * With FTM_SHAPING_E: + * W Set E static/base frequency + * O Set E frequency scaling + * U Set E damping ratio + * V Set E vibration tolerance * - * B Set static/base frequency for the Y axis - * H Set frequency scaling for the Y axis - * J 0.0 Set damping ratio for the Y axis - * R 0.00 Set the vibration tolerance for the Y axis */ void GcodeSuite::M493() { struct { bool update:1, report:1; } flag = { false }; @@ -205,7 +241,8 @@ void GcodeSuite::M493() { } } - #if HAS_X_AXIS + #if ANY(HAS_X_AXIS, HAS_Y_AXIS, FTM_SHAPER_Z, FTM_SHAPER_E) + auto set_shaper = [&](const AxisEnum axis, const char c) { const ftMotionShaper_t newsh = (ftMotionShaper_t)parser.value_byte(); if (newsh != ftMotion.cfg.shaper[axis]) { @@ -228,13 +265,20 @@ void GcodeSuite::M493() { return false; }; - if (parser.seenval('X') && set_shaper(X_AXIS, 'X')) return; // Parse 'X' mode parameter - + #if HAS_X_AXIS + if (parser.seenval('X') && set_shaper(X_AXIS, 'X')) return; // Parse 'X' mode parameter + #endif #if HAS_Y_AXIS if (parser.seenval('Y') && set_shaper(Y_AXIS, 'Y')) return; // Parse 'Y' mode parameter #endif + #if ENABLED(FTM_SHAPER_Z) + if (parser.seenval('Z') && set_shaper(Z_AXIS, 'Z')) return; // Parse 'Z' mode parameter + #endif + #if ENABLED(FTM_SHAPER_E) + if (parser.seenval('E') && set_shaper(E_AXIS, 'E')) return; // Parse 'E' mode parameter + #endif - #endif // HAS_X_AXIS + #endif // HAS_X_AXIS || HAS_Y_AXIS || FTM_SHAPER_Z || FTM_SHAPER_E #if HAS_EXTRUDERS @@ -259,11 +303,20 @@ void GcodeSuite::M493() { #endif // HAS_EXTRUDERS + // Parse '?' axis synchronization parameter. + if (parser.seen('?')) { + const bool enabled = parser.value_bool(); + if (enabled != ftMotion.cfg.axis_sync_enabled) { + ftMotion.cfg.axis_sync_enabled = enabled; + flag.report = true; + } + } + #if HAS_DYNAMIC_FREQ // Dynamic frequency mode parameter. if (parser.seenval('D')) { - if (AXIS_IS_SHAPING(X) || AXIS_IS_SHAPING(Y)) { + if (AXIS_IS_SHAPING(X) || AXIS_IS_SHAPING(Y) || AXIS_IS_SHAPING(Z) || AXIS_IS_SHAPING(E)) { const dynFreqMode_t val = dynFreqMode_t(parser.value_byte()); switch (val) { #if HAS_DYNAMIC_FREQ_MM @@ -295,7 +348,7 @@ void GcodeSuite::M493() { #if HAS_X_AXIS - // Parse frequency parameter (X axis). + // Parse X frequency parameter if (parser.seenval('A')) { if (AXIS_IS_SHAPING(X)) { const float val = parser.value_float(); @@ -305,59 +358,59 @@ void GcodeSuite::M493() { flag.update = flag.report = true; } else // Frequency out of range. - SERIAL_ECHOLNPGM("Invalid [", C('A'), "] frequency value."); + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_A_NAME), " [", C('A'), "] frequency value."); } else // Mode doesn't use frequency. - SERIAL_ECHOLNPGM("Wrong mode for [", C('A'), "] frequency."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " [", C('A'), "] frequency."); } #if HAS_DYNAMIC_FREQ - // Parse frequency scaling parameter (X axis). + // Parse X frequency scaling parameter if (parser.seenval('F')) { if (modeUsesDynFreq) { ftMotion.cfg.dynFreqK.x = parser.value_float(); flag.report = true; } else - SERIAL_ECHOLNPGM("Wrong mode for [", C('F'), "] frequency scaling."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " [", C('F'), "] frequency scaling."); } #endif - // Parse zeta parameter (X axis). + // Parse X zeta parameter if (parser.seenval('I')) { const float val = parser.value_float(); if (AXIS_IS_SHAPING(X)) { if (WITHIN(val, 0.01f, 1.0f)) { - ftMotion.cfg.zeta[0] = val; + ftMotion.cfg.zeta.x = val; flag.update = true; } else - SERIAL_ECHOLNPGM("Invalid X zeta [", C('I'), "] value."); // Zeta out of range. + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_A_NAME), " zeta [", C('I'), "] value."); // Zeta out of range } else - SERIAL_ECHOLNPGM("Wrong mode for zeta parameter."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " zeta parameter."); } - // Parse vtol parameter (X axis). + // Parse X vtol parameter if (parser.seenval('Q')) { const float val = parser.value_float(); if (AXIS_IS_EISHAPING(X)) { if (WITHIN(val, 0.00f, 1.0f)) { - ftMotion.cfg.vtol[0] = val; + ftMotion.cfg.vtol.x = val; flag.update = true; } else - SERIAL_ECHOLNPGM("Invalid X vtol [", C('Q'), "] value."); // VTol out of range. + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_A_NAME), " vtol [", C('Q'), "] value."); // VTol out of range. } else - SERIAL_ECHOLNPGM("Wrong mode for vtol parameter."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " vtol parameter."); } #endif // HAS_X_AXIS #if HAS_Y_AXIS - // Parse frequency parameter (Y axis). + // Parse Y frequency parameter if (parser.seenval('B')) { if (AXIS_IS_SHAPING(Y)) { const float val = parser.value_float(); @@ -366,56 +419,178 @@ void GcodeSuite::M493() { flag.update = flag.report = true; } else // Frequency out of range. - SERIAL_ECHOLNPGM("Invalid frequency [", C('B'), "] value."); + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_B_NAME), " frequency [", C('B'), "] value."); } else // Mode doesn't use frequency. - SERIAL_ECHOLNPGM("Wrong mode for [", C('B'), "] frequency."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " [", C('B'), "] frequency."); } #if HAS_DYNAMIC_FREQ - // Parse frequency scaling parameter (Y axis). + // Parse Y frequency scaling parameter if (parser.seenval('H')) { if (modeUsesDynFreq) { ftMotion.cfg.dynFreqK.y = parser.value_float(); flag.report = true; } else - SERIAL_ECHOLNPGM("Wrong mode for [", C('H'), "] frequency scaling."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " [", C('H'), "] frequency scaling."); } #endif - // Parse zeta parameter (Y axis). + // Parse Y zeta parameter if (parser.seenval('J')) { const float val = parser.value_float(); if (AXIS_IS_SHAPING(Y)) { if (WITHIN(val, 0.01f, 1.0f)) { - ftMotion.cfg.zeta[1] = val; + ftMotion.cfg.zeta.y = val; flag.update = true; } else - SERIAL_ECHOLNPGM("Invalid Y zeta [", C('J'), "] value."); // Zeta Out of range + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_B_NAME), " zeta [", C('J'), "] value."); // Zeta out of range } else - SERIAL_ECHOLNPGM("Wrong mode for zeta parameter."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " zeta parameter."); } - // Parse vtol parameter (Y axis). + // Parse Y vtol parameter if (parser.seenval('R')) { const float val = parser.value_float(); if (AXIS_IS_EISHAPING(Y)) { if (WITHIN(val, 0.00f, 1.0f)) { - ftMotion.cfg.vtol[1] = val; + ftMotion.cfg.vtol.y = val; flag.update = true; } else - SERIAL_ECHOLNPGM("Invalid Y vtol [", C('R'), "] value."); // VTol out of range. + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_B_NAME), " vtol [", C('R'), "] value."); // VTol out of range. } else - SERIAL_ECHOLNPGM("Wrong mode for vtol parameter."); + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " vtol parameter."); } #endif // HAS_Y_AXIS + #if ENABLED(FTM_SHAPER_Z) + + // Parse Z frequency parameter + if (parser.seenval('C')) { + if (AXIS_IS_SHAPING(Z)) { + const float val = parser.value_float(); + if (WITHIN(val, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2)) { + ftMotion.cfg.baseFreq.z = val; + flag.update = flag.report = true; + } + else // Frequency out of range. + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_C_NAME), " frequency [", C('C'), "] value."); + } + else // Mode doesn't use frequency. + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " [", C('C'), "] frequency."); + } + + #if HAS_DYNAMIC_FREQ + // Parse Z frequency scaling parameter + if (parser.seenval('L')) { + if (modeUsesDynFreq) { + ftMotion.cfg.dynFreqK.z = parser.value_float(); + flag.report = true; + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " [", C('L'), "] frequency scaling."); + } + #endif + + // Parse Z zeta parameter + if (parser.seenval('O')) { + const float val = parser.value_float(); + if (AXIS_IS_SHAPING(Z)) { + if (WITHIN(val, 0.01f, 1.0f)) { + ftMotion.cfg.zeta.z = val; + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_C_NAME), " zeta [", C('O'), "] value."); // Zeta out of range + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " zeta parameter."); + } + + // Parse Z vtol parameter + if (parser.seenval('M')) { + const float val = parser.value_float(); + if (AXIS_IS_EISHAPING(Z)) { + if (WITHIN(val, 0.00f, 1.0f)) { + ftMotion.cfg.vtol.z = val; + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_C_NAME), " vtol [", C('M'), "] value."); // VTol out of range. + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " vtol parameter."); + } + + #endif // FTM_SHAPER_Z + + #if ENABLED(FTM_SHAPER_E) + + // Parse E frequency parameter + if (parser.seenval('W')) { + if (AXIS_IS_SHAPING(E)) { + const float val = parser.value_float(); + if (WITHIN(val, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2)) { + ftMotion.cfg.baseFreq.e = val; + flag.update = flag.report = true; + } + else // Frequency out of range. + SERIAL_ECHOLNPGM("?Invalid ", C('E'), " frequency [", C('W'), "] value."); + } + else // Mode doesn't use frequency. + SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " [", C('W'), "] frequency."); + } + + #if HAS_DYNAMIC_FREQ + // Parse E frequency scaling parameter + if (parser.seenval('O')) { + if (modeUsesDynFreq) { + ftMotion.cfg.dynFreqK.e = parser.value_float(); + flag.report = true; + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " [", C('O'), "] frequency scaling."); + } + #endif + + // Parse E zeta parameter + if (parser.seenval('U')) { + const float val = parser.value_float(); + if (AXIS_IS_SHAPING(E)) { + if (WITHIN(val, 0.01f, 1.0f)) { + ftMotion.cfg.zeta.e = val; + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Invalid ", C('E'), " zeta [", C('U'), "] value."); // Zeta out of range + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " zeta parameter."); + } + + // Parse E vtol parameter + if (parser.seenval('V')) { + const float val = parser.value_float(); + if (AXIS_IS_EISHAPING(E)) { + if (WITHIN(val, 0.00f, 1.0f)) { + ftMotion.cfg.vtol.e = val; + flag.update = true; + } + else + SERIAL_ECHOLNPGM("?Invalid ", C('E'), " vtol [", C('V'), "] value."); // VTol out of range. + } + else + SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " vtol parameter."); + } + + #endif // FTM_SHAPER_E + if (flag.update) ftMotion.update_shaping_params(); if (flag.report) say_shaping(); diff --git a/Marlin/src/gcode/feature/ft_motion/M494.cpp b/Marlin/src/gcode/feature/ft_motion/M494.cpp new file mode 100644 index 0000000000..119a0c8dfc --- /dev/null +++ b/Marlin/src/gcode/feature/ft_motion/M494.cpp @@ -0,0 +1,133 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "../../../inc/MarlinConfigPre.h" + +#if ENABLED(FTM_SMOOTHING) + +#include "../../gcode.h" +#include "../../../module/ft_motion.h" +#include "../../../module/stepper.h" + +void say_smoothing() { + #if HAS_X_AXIS + SERIAL_ECHOLN(F(" "), C('X'), F(" smoothing time: "), p_float_t(ftMotion.cfg.smoothingTime.x, 3), C('s')); + #endif + #if HAS_Y_AXIS + SERIAL_ECHOLN(F(" "), C('Y'), F(" smoothing time: "), p_float_t(ftMotion.cfg.smoothingTime.y, 3), C('s')); + #endif + #if HAS_Z_AXIS + SERIAL_ECHOLN(F(" "), C('Z'), F(" smoothing time: "), p_float_t(ftMotion.cfg.smoothingTime.z, 3), C('s')); + #endif + #if HAS_EXTRUDERS + SERIAL_ECHOLN(F(" "), C('E'), F(" smoothing time: "), p_float_t(ftMotion.cfg.smoothingTime.e, 3), C('s')); + #endif +} + +void GcodeSuite::M494_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + + report_heading_etc(forReplay, F("FTM Smoothing")); + const ft_config_t &c = ftMotion.cfg; + SERIAL_ECHOLN(F(" M494") + #if HAS_X_AXIS + , F(" X"), c.smoothingTime.x + #endif + #if HAS_Y_AXIS + , F(" Y"), c.smoothingTime.y + #endif + #if HAS_Z_AXIS + , F(" Z"), c.smoothingTime.z + #endif + #if HAS_EXTRUDERS + , F(" E"), c.smoothingTime.e + #endif + ); +} + +/** + * M494: Set Fixed-time Motion Control Smoothing parameters + * + * Parameters: + * X