From f2ac2e7cadcab6753a81e4a4d8372badab7ead7b Mon Sep 17 00:00:00 2001 From: narno2202 <130909513+narno2202@users.noreply.github.com> Date: Thu, 11 Dec 2025 23:15:49 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8=20Optimize=20FTM=20menu=20code,=20?= =?UTF-8?q?use=20some=20setters=20(#28170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Scott Lahteine --- Marlin/src/core/types.h | 1 + Marlin/src/gcode/feature/ft_motion/M493.cpp | 22 +- Marlin/src/gcode/feature/ft_motion/M494.cpp | 11 +- Marlin/src/inc/SanityCheck.h | 2 +- Marlin/src/lcd/language/language_en.h | 1 + Marlin/src/lcd/menu/menu.h | 4 +- Marlin/src/lcd/menu/menu_item.h | 2 + Marlin/src/lcd/menu/menu_motion.cpp | 250 +++++++------------- Marlin/src/module/ft_motion.cpp | 31 ++- Marlin/src/module/ft_motion.h | 49 +++- Marlin/src/module/ft_motion/shaping.h | 6 +- Marlin/src/module/ft_motion/smoothing.cpp | 2 +- Marlin/src/module/ft_motion/smoothing.h | 10 +- Marlin/src/module/ft_motion/stepping.h | 20 +- 14 files changed, 187 insertions(+), 224 deletions(-) diff --git a/Marlin/src/core/types.h b/Marlin/src/core/types.h index c72f02558f..7008d04c53 100644 --- a/Marlin/src/core/types.h +++ b/Marlin/src/core/types.h @@ -177,6 +177,7 @@ template struct IF { typedef L type; }; #define CARTES_CODE(x,y,z,e) XYZ_CODE(x,y,z) CODE_ITEM_E(e) #define CARTES_GANG(x,y,z,e) XYZ_GANG(x,y,z) GANG_ITEM_E(e) #define CARTES_AXIS_NAMES CARTES_LIST(X,Y,Z,E) +#define CARTES_AXIS_NAMES_LC CARTES_LIST(x,y,z,e) #define CARTES_MAP(F) MAP(F, CARTES_AXIS_NAMES) #if CARTES_COUNT #define CARTES_COMMA , diff --git a/Marlin/src/gcode/feature/ft_motion/M493.cpp b/Marlin/src/gcode/feature/ft_motion/M493.cpp index 528102c868..905bddf124 100644 --- a/Marlin/src/gcode/feature/ft_motion/M493.cpp +++ b/Marlin/src/gcode/feature/ft_motion/M493.cpp @@ -215,8 +215,7 @@ void GcodeSuite::M493() { if (parser.seen('S')) { const bool active = parser.value_bool(); if (active != c.active) { - stepper.ftMotion_syncPosition(); - c.active = active; + ftMotion.toggle(); flag.report = true; } } @@ -258,21 +257,10 @@ void GcodeSuite::M493() { // Dynamic frequency mode parameter. if (parser.seenval('D')) { 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 - case dynFreqMode_Z_BASED: - #endif - #if HAS_DYNAMIC_FREQ_G - case dynFreqMode_MASS_BASED: - #endif - case dynFreqMode_DISABLED: - c.dynFreqMode = val; - flag.report = true; - break; - default: - SERIAL_ECHOLN(F("?Invalid "), F("(D)ynamic Frequency Mode value.")); - break; + switch (c.setDynFreqMode(parser.value_byte())) { + case 0: break; // Same value, no update + case 1: flag.report = true; break; // New value, updated + default: SERIAL_ECHOLN(F("?Invalid "), F("(D)ynamic Frequency Mode value.")); break; } } else diff --git a/Marlin/src/gcode/feature/ft_motion/M494.cpp b/Marlin/src/gcode/feature/ft_motion/M494.cpp index 26d2b7d2b8..6f90badb49 100644 --- a/Marlin/src/gcode/feature/ft_motion/M494.cpp +++ b/Marlin/src/gcode/feature/ft_motion/M494.cpp @@ -89,12 +89,8 @@ void GcodeSuite::M494() { // Parse trajectory type parameter. if (parser.seenval('T')) { - const int val = parser.value_int(); - if (WITHIN(val, 0, 2)) { - planner.synchronize(); - ftMotion.setTrajectoryType((TrajectoryType)val); + if (ftMotion.updateTrajectoryType(TrajectoryType(parser.value_int()))) report = true; - } else SERIAL_ECHOLN(F("?Invalid "), F("(T)rajectory type value. Use 0=TRAPEZOIDAL, 1=POLY5, 2=POLY6")); } @@ -116,11 +112,8 @@ void GcodeSuite::M494() { #define SMOOTH_SET(A,N) \ if (parser.seenval(CHARIFY(A))) { \ - const float val = parser.value_float(); \ - if (WITHIN(val, 0.0f, FTM_MAX_SMOOTHING_TIME)) { \ - ftMotion.set_smoothing_time(_AXIS(A), val); \ + if (ftMotion.set_smoothing_time(_AXIS(A), parser.value_float())) \ report = true; \ - } \ else \ SERIAL_ECHOLNPGM("?Invalid ", C(N), " smoothing time (", C(CHARIFY(A)), ") value."); \ } diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 88d177cde3..5a55671fc1 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -4483,7 +4483,7 @@ static_assert(_PLUS_TEST(3), "DEFAULT_MAX_ACCELERATION values must be positive." #error "FT_MOTION does not currently support MIXING_EXTRUDER." #endif #if !HAS_X_AXIS - static_assert(FTM_DEFAULT_SHAPER_X != ftMotionShaper_NONE, "Without any linear axes FTM_DEFAULT_SHAPER_X must be ftMotionShaper_NONE."); + static_assert(FTM_DEFAULT_SHAPER_X == ftMotionShaper_NONE, "Without any linear axes FTM_DEFAULT_SHAPER_X must be ftMotionShaper_NONE."); #endif #if HAS_DYNAMIC_FREQ_MM static_assert(FTM_DEFAULT_DYNFREQ_MODE != dynFreqMode_Z_BASED, "dynFreqMode_Z_BASED requires a Z axis."); diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h index 5e34a47723..559b3e339d 100644 --- a/Marlin/src/lcd/language/language_en.h +++ b/Marlin/src/lcd/language/language_en.h @@ -947,6 +947,7 @@ namespace LanguageNarrow_en { LSTR MSG_FTM_VTOL_N = _UxGT("@ Vib. Level"); LSTR MSG_FTM_SMOOTH_TIME_N = _UxGT("@ Smoothing Time"); LSTR MSG_FTM_POLY6_OVERSHOOT = _UxGT("@ Poly6 Overshoot"); + LSTR MSG_FTM_CONFIGURE_AXIS_N = _UxGT("Configure @ Axis"); LSTR MSG_FTM_RESONANCE_TEST = _UxGT("Resonance Test"); LSTR MSG_FTM_RT_RUNNING = _UxGT("Res. Test Running..."); diff --git a/Marlin/src/lcd/menu/menu.h b/Marlin/src/lcd/menu/menu.h index 869b06828c..7ef7894f7b 100644 --- a/Marlin/src/lcd/menu/menu.h +++ b/Marlin/src/lcd/menu/menu.h @@ -59,8 +59,8 @@ class MenuItemBase { static const char* itemStringC; // Store an index and string for later substitution - FORCE_INLINE static void init(const int8_t ind=0, FSTR_P const fstr=nullptr) { itemIndex = ind; itemStringF = fstr; itemStringC = nullptr; } - FORCE_INLINE static void init(const int8_t ind, const char * const cstr) { itemIndex = ind; itemStringC = cstr; itemStringF = nullptr; } + FORCE_INLINE static void init(const int8_t ind=-1, FSTR_P const fstr=nullptr) { itemStringF = fstr; itemStringC = nullptr; if (ind >= 0) itemIndex = ind; } + FORCE_INLINE static void init(const int8_t ind, const char * const cstr) { itemStringC = cstr; itemStringF = nullptr; if (ind >= 0) itemIndex = ind; } // Implementation-specific: // Draw an item either selected (pre_char) or not (space) with post_char diff --git a/Marlin/src/lcd/menu/menu_item.h b/Marlin/src/lcd/menu/menu_item.h index ec680665bf..8b23ab1b9c 100644 --- a/Marlin/src/lcd/menu/menu_item.h +++ b/Marlin/src/lcd/menu/menu_item.h @@ -462,9 +462,11 @@ class MenuItem_bool : public MenuEditItemBase { #elif ENABLED(GENERIC_BACK_MENU_ITEM) #define BACK_ITEM_F(V...) MENU_ITEM_F(back, GET_TEXT_F(MSG_BACK)) #define BACK_ITEM(V...) MENU_ITEM(back, MSG_BACK) + #define BACK_ITEM_N BACK_ITEM #else #define BACK_ITEM_F(FLABEL) MENU_ITEM_F(back, FLABEL) #define BACK_ITEM(LABEL) MENU_ITEM(back, LABEL) + #define BACK_ITEM_N(N, LABEL) MENU_ITEM_N(back, N, LABEL) #endif #define ACTION_ITEM_N_S_F(N, S, FLABEL, ACTION) MENU_ITEM_N_S_F(function, N, S, FLABEL, ACTION) diff --git a/Marlin/src/lcd/menu/menu_motion.cpp b/Marlin/src/lcd/menu/menu_motion.cpp index bbd6b5beff..5b3d72a6ee 100644 --- a/Marlin/src/lcd/menu/menu_motion.cpp +++ b/Marlin/src/lcd/menu/menu_motion.cpp @@ -331,47 +331,57 @@ void menu_move() { FSTR_P get_dyn_freq_mode_name() { switch (ftMotion.cfg.dynFreqMode) { default: - case dynFreqMode_DISABLED: return GET_TEXT_F(MSG_LCD_OFF); - case dynFreqMode_Z_BASED: return GET_TEXT_F(MSG_FTM_Z_BASED); - case dynFreqMode_MASS_BASED: return GET_TEXT_F(MSG_FTM_MASS_BASED); + case dynFreqMode_DISABLED: return GET_TEXT_F(MSG_LCD_OFF); + #if HAS_DYNAMIC_FREQ_MM + case dynFreqMode_Z_BASED: return GET_TEXT_F(MSG_FTM_Z_BASED); + #endif + #if HAS_DYNAMIC_FREQ_G + case dynFreqMode_MASS_BASED: return GET_TEXT_F(MSG_FTM_MASS_BASED); + #endif } } #endif - void ftm_menu_set_shaper(ftMotionShaper_t &outShaper, const ftMotionShaper_t s) { - outShaper = s; + void ftm_menu_set_shaper(const ftMotionShaper_t s) { + ftMotion.cfg.shaper[MenuItemBase::itemIndex] = s; ftMotion.update_shaping_params(); ui.go_back(); } - #define MENU_FTM_SHAPER(A) \ - inline void menu_ftm_shaper_##A() { \ - const ftMotionShaper_t shaper = ftMotion.cfg.shaper.A; \ - START_MENU(); \ - BACK_ITEM(MSG_FIXED_TIME_MOTION); \ - if (shaper != ftMotionShaper_NONE) ACTION_ITEM(MSG_LCD_OFF, []{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_NONE ); }); \ - if (shaper != ftMotionShaper_ZV) ACTION_ITEM(MSG_FTM_ZV, []{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_ZV ); }); \ - if (shaper != ftMotionShaper_ZVD) ACTION_ITEM(MSG_FTM_ZVD, []{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_ZVD ); }); \ - if (shaper != ftMotionShaper_ZVDD) ACTION_ITEM(MSG_FTM_ZVDD, []{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_ZVDD ); }); \ - if (shaper != ftMotionShaper_ZVDDD) ACTION_ITEM(MSG_FTM_ZVDDD,[]{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_ZVDDD ); }); \ - if (shaper != ftMotionShaper_EI) ACTION_ITEM(MSG_FTM_EI, []{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_EI ); }); \ - if (shaper != ftMotionShaper_2HEI) ACTION_ITEM(MSG_FTM_2HEI, []{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_2HEI ); }); \ - if (shaper != ftMotionShaper_3HEI) ACTION_ITEM(MSG_FTM_3HEI, []{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_3HEI ); }); \ - if (shaper != ftMotionShaper_MZV) ACTION_ITEM(MSG_FTM_MZV, []{ ftm_menu_set_shaper(ftMotion.cfg.shaper.A, ftMotionShaper_MZV ); }); \ - END_MENU(); \ - } + void menu_ftm_shaper() { + const int8_t axis = MenuItemBase::itemIndex; + const ftMotionShaper_t shaper = ftMotion.cfg.shaper[axis]; + + START_MENU(); + BACK_ITEM_N(axis, MSG_FTM_CONFIGURE_AXIS_N); + + if (shaper != ftMotionShaper_NONE) ACTION_ITEM_N(axis, MSG_LCD_OFF, []{ ftm_menu_set_shaper(ftMotionShaper_NONE) ; }); + if (shaper != ftMotionShaper_ZV) ACTION_ITEM_N(axis, MSG_FTM_ZV, []{ ftm_menu_set_shaper(ftMotionShaper_ZV) ; }); + if (shaper != ftMotionShaper_ZVD) ACTION_ITEM_N(axis, MSG_FTM_ZVD, []{ ftm_menu_set_shaper(ftMotionShaper_ZVD) ; }); + if (shaper != ftMotionShaper_ZVDD) ACTION_ITEM_N(axis, MSG_FTM_ZVDD, []{ ftm_menu_set_shaper(ftMotionShaper_ZVDD) ; }); + if (shaper != ftMotionShaper_ZVDDD) ACTION_ITEM_N(axis, MSG_FTM_ZVDDD,[]{ ftm_menu_set_shaper(ftMotionShaper_ZVDDD) ; }); + if (shaper != ftMotionShaper_EI) ACTION_ITEM_N(axis, MSG_FTM_EI, []{ ftm_menu_set_shaper(ftMotionShaper_EI) ; }); + if (shaper != ftMotionShaper_2HEI) ACTION_ITEM_N(axis, MSG_FTM_2HEI, []{ ftm_menu_set_shaper(ftMotionShaper_2HEI) ; }); + if (shaper != ftMotionShaper_3HEI) ACTION_ITEM_N(axis, MSG_FTM_3HEI, []{ ftm_menu_set_shaper(ftMotionShaper_3HEI) ; }); + if (shaper != ftMotionShaper_MZV) ACTION_ITEM_N(axis, MSG_FTM_MZV, []{ ftm_menu_set_shaper(ftMotionShaper_MZV) ; }); - SHAPED_MAP(MENU_FTM_SHAPER); - #if ENABLED(FTM_POLYS) - void menu_ftm_trajectory_generator() { - const TrajectoryType current_type = ftMotion.getTrajectoryType(); - START_MENU(); - BACK_ITEM(MSG_FIXED_TIME_MOTION); - if (current_type != TrajectoryType::TRAPEZOIDAL) ACTION_ITEM(MSG_FTM_TRAPEZOIDAL, []{ planner.synchronize(); ftMotion.setTrajectoryType(TrajectoryType::TRAPEZOIDAL); ui.go_back(); }); - if (current_type != TrajectoryType::POLY5) ACTION_ITEM(MSG_FTM_POLY5, []{ planner.synchronize(); ftMotion.setTrajectoryType(TrajectoryType::POLY5); ui.go_back(); }); - if (current_type != TrajectoryType::POLY6) ACTION_ITEM(MSG_FTM_POLY6, []{ planner.synchronize(); ftMotion.setTrajectoryType(TrajectoryType::POLY6); ui.go_back(); }); END_MENU(); } + + #if ENABLED(FTM_POLYS) + + void menu_ftm_trajectory_generator() { + const TrajectoryType traj_type = ftMotion.getTrajectoryType(); + START_MENU(); + BACK_ITEM(MSG_FIXED_TIME_MOTION); + + if (traj_type != TrajectoryType::TRAPEZOIDAL) ACTION_ITEM(MSG_FTM_TRAPEZOIDAL, []{ ftMotion.updateTrajectoryType(TrajectoryType::TRAPEZOIDAL); ui.go_back(); }); + if (traj_type != TrajectoryType::POLY5) ACTION_ITEM(MSG_FTM_POLY5, []{ ftMotion.updateTrajectoryType(TrajectoryType::POLY5); ui.go_back(); }); + if (traj_type != TrajectoryType::POLY6) ACTION_ITEM(MSG_FTM_POLY6, []{ ftMotion.updateTrajectoryType(TrajectoryType::POLY6); ui.go_back(); }); + + END_MENU(); + } + #endif // FTM_POLYS #if ENABLED(FTM_RESONANCE_TEST) @@ -401,6 +411,7 @@ void menu_move() { GCODES_ITEM_N(Z_AXIS, MSG_FTM_RT_START_N, F("M495 Z S")); SUBMENU(MSG_FTM_RETRIEVE_FREQ, menu_ftm_resonance_freq); } + END_MENU(); } @@ -412,14 +423,14 @@ void menu_move() { const dynFreqMode_t dmode = ftMotion.cfg.dynFreqMode; START_MENU(); - BACK_ITEM(MSG_FIXED_TIME_MOTION); + BACK_ITEM_N(MenuItemBase::itemIndex, MSG_FTM_CONFIGURE_AXIS_N); - if (dmode != dynFreqMode_DISABLED) ACTION_ITEM(MSG_LCD_OFF, []{ ftMotion.cfg.dynFreqMode = dynFreqMode_DISABLED; ui.go_back(); }); + if (dmode != dynFreqMode_DISABLED) ACTION_ITEM(MSG_LCD_OFF, []{ (void)ftMotion.cfg.setDynFreqMode(dynFreqMode_DISABLED); ui.go_back(); }); #if HAS_DYNAMIC_FREQ_MM - if (dmode != dynFreqMode_Z_BASED) ACTION_ITEM(MSG_FTM_Z_BASED, []{ ftMotion.cfg.dynFreqMode = dynFreqMode_Z_BASED; ui.go_back(); }); + if (dmode != dynFreqMode_Z_BASED) ACTION_ITEM(MSG_FTM_Z_BASED, []{ (void)ftMotion.cfg.setDynFreqMode(dynFreqMode_Z_BASED); ui.go_back(); }); #endif #if HAS_DYNAMIC_FREQ_G - if (dmode != dynFreqMode_MASS_BASED) ACTION_ITEM(MSG_FTM_MASS_BASED, []{ ftMotion.cfg.dynFreqMode = dynFreqMode_MASS_BASED; ui.go_back(); }); + if (dmode != dynFreqMode_MASS_BASED) ACTION_ITEM(MSG_FTM_MASS_BASED, []{ (void)ftMotion.cfg.setDynFreqMode(dynFreqMode_MASS_BASED); ui.go_back(); }); #endif END_MENU(); @@ -427,75 +438,43 @@ void menu_move() { #endif // HAS_DYNAMIC_FREQ - // Suppress warning about storing a stack address in a static string pointer - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdangling-pointer" - - #if ALL(__AVR__, HAS_MARLINUI_U8GLIB) && DISABLED(OPTIMIZE_FT_MOTION_FOR_SIZE) - #define CACHE_FOR_SPEED 1 - #endif - - #if ENABLED(FTM_SMOOTHING) - #define _SMOO_MENU_ITEM(A) do{ \ - editable.decimal = c.smoothingTime.A; \ - EDIT_ITEM_FAST_N(float43, _AXIS(A), MSG_FTM_SMOOTH_TIME_N, &editable.decimal, 0.0f, FTM_MAX_SMOOTHING_TIME, []{ ftMotion.set_smoothing_time(_AXIS(A), editable.decimal); }); \ - }while(0); - #endif - - void menu_ft_motion() { - // Define stuff ahead of the menu loop + void menu_ftm_axis(const AxisEnum axis) { ft_config_t &c = ftMotion.cfg; - #ifdef __AVR__ - // Copy Flash strings to RAM for C-string substitution - // For U8G paged rendering check and skip extra string copy - #if HAS_X_AXIS - MString<20> shaper_name; - #if CACHE_FOR_SPEED - int8_t prev_a = -1; - #endif - auto _shaper_name = [&](const AxisEnum a) { - if (TERN1(CACHE_FOR_SPEED, a != prev_a)) { - TERN_(CACHE_FOR_SPEED, prev_a = a); - shaper_name = get_shaper_name(a); - } - return shaper_name; - }; - #endif - #if HAS_DYNAMIC_FREQ - MString<20> dmode; - #if CACHE_FOR_SPEED - bool got_d = false; - #endif - auto _dmode = [&]{ - if (TERN1(CACHE_FOR_SPEED, !got_d)) { - TERN_(CACHE_FOR_SPEED, got_d = true); - dmode = get_dyn_freq_mode_name(); - } - return dmode; - }; - #endif - MString<20> traj_name; - #if CACHE_FOR_SPEED - bool got_t = false; - #endif - auto _traj_name = [&]{ - if (TERN1(CACHE_FOR_SPEED, !got_t)) { - TERN_(CACHE_FOR_SPEED, got_t = true); - traj_name = ftMotion.getTrajectoryName(); - } - return traj_name; - }; - #else - auto _shaper_name = [](const AxisEnum a) { return get_shaper_name(a); }; - #if HAS_DYNAMIC_FREQ - auto _dmode = []{ return get_dyn_freq_mode_name(); }; - #endif - #if ENABLED(FTM_POLYS) - auto _traj_name = []{ return ftMotion.getTrajectoryName(); }; - #endif + START_MENU(); + BACK_ITEM(MSG_FIXED_TIME_MOTION); + + if (axis == X_AXIS || axis == Y_AXIS || TERN0(FTM_SHAPER_Z, axis == Z_AXIS) || TERN0(FTM_SHAPER_E, axis == E_AXIS)) { + SUBMENU_N_S(axis, get_shaper_name(axis), MSG_FTM_CMPN_MODE, menu_ftm_shaper); + if (IS_SHAPING(c.shaper[axis])) { + EDIT_ITEM_FAST_N(float42_52, axis, MSG_FTM_BASE_FREQ_N, &c.baseFreq[axis], FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2, ftMotion.update_shaping_params); + EDIT_ITEM_FAST_N(float42_52, axis, MSG_FTM_ZETA_N, &c.zeta[axis], 0.0f, 1.0f, ftMotion.update_shaping_params); + if (IS_EISHAPING(c.shaper[axis])) + EDIT_ITEM_FAST_N(float42_52, axis, MSG_FTM_VTOL_N, &c.vtol[axis], 0.0f, 1.0f, ftMotion.update_shaping_params); + } + } + + #if ENABLED(FTM_SMOOTHING) + editable.decimal = c.smoothingTime[axis]; + EDIT_ITEM_FAST_N(float43, axis, MSG_FTM_SMOOTH_TIME_N, &editable.decimal, 0.0f, FTM_MAX_SMOOTHING_TIME, []{ (void)ftMotion.set_smoothing_time(AxisEnum(MenuItemBase::itemIndex), editable.decimal); }); #endif + #if HAS_DYNAMIC_FREQ + if (axis == X_AXIS || axis == Y_AXIS) { + SUBMENU_N_S(axis, get_dyn_freq_mode_name(), MSG_FTM_DYN_MODE, menu_ftm_dyn_mode); + if (c.dynFreqMode != dynFreqMode_DISABLED) + EDIT_ITEM_FAST_N(float42_52, axis, MSG_FTM_DFREQ_K_N, &c.dynFreqK[axis], 0.0f, 20.0f); + } + #endif + + END_MENU(); + } // menu_ftm_axis + + #define _FTM_AXIS_SUBMENU(A) SUBMENU_N(_AXIS(A), MSG_FTM_CONFIGURE_AXIS_N, []{ menu_ftm_axis(_AXIS(A)); }); + + void menu_ft_motion() { + ft_config_t &c = ftMotion.cfg; + START_MENU(); BACK_ITEM(MSG_MOTION); @@ -505,41 +484,23 @@ void menu_move() { #endif // Show only when FT Motion is active (or optionally always show) - if (c.active || ENABLED(FT_MOTION_NO_MENU_TOGGLE)) { + if (TERN(FT_MOTION_NO_MENU_TOGGLE, true, c.active)) { + #if ENABLED(FTM_POLYS) - SUBMENU_S(_traj_name(), MSG_FTM_TRAJECTORY, menu_ftm_trajectory_generator); + SUBMENU_S(ftMotion.getTrajectoryName(), MSG_FTM_TRAJECTORY, menu_ftm_trajectory_generator); if (ftMotion.getTrajectoryType() == TrajectoryType::POLY6) EDIT_ITEM(float42_52, MSG_FTM_POLY6_OVERSHOOT, &c.poly6_acceleration_overshoot, 1.25f, 1.875f); #endif - #define SHAPER_MENU_ITEM(A) \ - SUBMENU_N_S(_AXIS(A), _shaper_name(_AXIS(A)), MSG_FTM_CMPN_MODE, menu_ftm_shaper_##A); \ - if (AXIS_IS_SHAPING(A)) { \ - EDIT_ITEM_FAST_N(float42_52, _AXIS(A), MSG_FTM_BASE_FREQ_N, &c.baseFreq.A, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2, ftMotion.update_shaping_params); \ - EDIT_ITEM_FAST_N(float42_52, _AXIS(A), MSG_FTM_ZETA_N, &c.zeta.A, 0.0f, 1.0f, ftMotion.update_shaping_params); \ - if (AXIS_IS_EISHAPING(A)) \ - EDIT_ITEM_FAST_N(float42_52, _AXIS(A), MSG_FTM_VTOL_N, &c.vtol.A, 0.0f, 1.0f, ftMotion.update_shaping_params); \ - } - SHAPED_MAP(SHAPER_MENU_ITEM); - - #if HAS_DYNAMIC_FREQ - SUBMENU_S(_dmode(), MSG_FTM_DYN_MODE, menu_ftm_dyn_mode); - if (c.dynFreqMode != dynFreqMode_DISABLED) { - #define _DYN_MENU_ITEM(A) EDIT_ITEM_FAST_N(float42_52, _AXIS(A), MSG_FTM_DFREQ_K_N, &c.dynFreqK.A, 0.0f, 20.0f); - SHAPED_MAP(_DYN_MENU_ITEM); - } - #endif + CARTES_MAP(_FTM_AXIS_SUBMENU); EDIT_ITEM(bool, MSG_FTM_AXIS_SYNC, &c.axis_sync_enabled); - #if ENABLED(FTM_SMOOTHING) - CARTES_MAP(_SMOO_MENU_ITEM); - #endif - #if ENABLED(FTM_RESONANCE_TEST) SUBMENU(MSG_FTM_RESONANCE_TEST, menu_ftm_resonance_test); #endif } + END_MENU(); } // menu_ft_motion @@ -553,34 +514,6 @@ void menu_move() { // Copy Flash strings to RAM for C-string substitution // For U8G paged rendering check and skip extra string copy - #if HAS_X_AXIS - #if CACHE_FOR_SPEED - int8_t prev_a = -1; - #endif - MString<20> shaper_name; - auto _shaper_name = [&](const AxisEnum a) { - if (TERN1(CACHE_FOR_SPEED, a != prev_a)) { - TERN_(CACHE_FOR_SPEED, prev_a = a); - shaper_name = get_shaper_name(a); - } - return shaper_name; - }; - #endif - - #if HAS_DYNAMIC_FREQ - #if CACHE_FOR_SPEED - bool got_d = false; - #endif - MString<20> dmode; - auto _dmode = [&]{ - if (TERN1(CACHE_FOR_SPEED, !got_d)) { - TERN_(CACHE_FOR_SPEED, got_d = true); - dmode = get_dyn_freq_mode_name(); - } - return dmode; - }; - #endif - #if ENABLED(FTM_POLYS) #if CACHE_FOR_SPEED bool got_t = false; @@ -597,10 +530,6 @@ void menu_move() { #else // !__AVR__ - auto _shaper_name = [](const AxisEnum a) { return get_shaper_name(a); }; - #if HAS_DYNAMIC_FREQ - auto _dmode = []{ return get_dyn_freq_mode_name(); }; - #endif #if ENABLED(FTM_POLYS) auto _traj_name = []{ return ftMotion.getTrajectoryName(); }; #endif @@ -616,22 +545,11 @@ void menu_move() { EDIT_ITEM(float42_52, MSG_FTM_POLY6_OVERSHOOT, &c.poly6_acceleration_overshoot, 1.25f, 1.875f); #endif - #define _CMPM_MENU_ITEM(A) SUBMENU_N_S(_AXIS(A), _shaper_name(_AXIS(A)), MSG_FTM_CMPN_MODE, menu_ftm_shaper_##A); - SHAPED_MAP(_CMPM_MENU_ITEM); - - #if HAS_DYNAMIC_FREQ - SUBMENU_S(_dmode(), MSG_FTM_DYN_MODE, menu_ftm_dyn_mode); - #endif - - #if ENABLED(FTM_SMOOTHING) - CARTES_MAP(_SMOO_MENU_ITEM); - #endif + SHAPED_MAP(_FTM_AXIS_SUBMENU); END_MENU(); } // menu_tune_ft_motion - #pragma GCC diagnostic pop - #endif // FT_MOTION_MENU void menu_motion() { diff --git a/Marlin/src/module/ft_motion.cpp b/Marlin/src/module/ft_motion.cpp index df9eb2bd14..e35ccba95e 100644 --- a/Marlin/src/module/ft_motion.cpp +++ b/Marlin/src/module/ft_motion.cpp @@ -192,7 +192,7 @@ void FTMotion::loop() { void FTMotion::update_shaping_params() { #define UPDATE_SHAPER(A) \ - shaping.A.ena = ftMotion.cfg.shaper.A != ftMotionShaper_NONE; \ + shaping.A.ena = IS_SHAPING(ftMotion.cfg.shaper.A); \ shaping.A.set_axis_shaping_A(cfg.shaper.A, cfg.zeta.A, cfg.vtol.A); \ shaping.A.set_axis_shaping_N(cfg.shaper.A, cfg.baseFreq.A, cfg.zeta.A); @@ -204,19 +204,19 @@ void FTMotion::loop() { #if ENABLED(FTM_SMOOTHING) + #include "planner.h" + void FTMotion::update_smoothing_params() { - #define _SMOOTH_PARAM(A) smoothing.A.set_smoothing_time(cfg.smoothingTime.A); + #define _SMOOTH_PARAM(A) smoothing.A.set_time(cfg.smoothingTime.A); CARTES_MAP(_SMOOTH_PARAM); smoothing.refresh_largest_delay_samples(); } - void FTMotion::set_smoothing_time(uint8_t axis, const float s_time) { - #define _SMOOTH_CASE(A) case _AXIS(A): cfg.smoothingTime.A = s_time; break; - switch (axis) { - default: - CARTES_MAP(_SMOOTH_CASE); - } + bool FTMotion::set_smoothing_time(const AxisEnum axis, const float s_time) { + if (!WITHIN(s_time, 0.0f, FTM_MAX_SMOOTHING_TIME)) return false; + cfg.smoothingTime[axis] = s_time; update_smoothing_params(); + return true; } #endif // FTM_SMOOTHING @@ -304,6 +304,21 @@ void FTMotion::init() { } } + // Update trajectory generator type from G-code or UI + bool FTMotion::updateTrajectoryType(const TrajectoryType type) { + if (type == trajectoryType) return false; + switch (type) { + default: return false; + case TrajectoryType::TRAPEZOIDAL: + case TrajectoryType::POLY5: + case TrajectoryType::POLY6: + break; + } + planner.synchronize(); + setTrajectoryType(type); + return true; + } + #endif // FTM_POLYS FSTR_P FTMotion::getTrajectoryName() { diff --git a/Marlin/src/module/ft_motion.h b/Marlin/src/module/ft_motion.h index 7af440b163..79dce3c8e1 100644 --- a/Marlin/src/module/ft_motion.h +++ b/Marlin/src/module/ft_motion.h @@ -95,15 +95,43 @@ typedef struct FTConfig { static constexpr TrajectoryType trajectory_type = TrajectoryType::TRAPEZOIDAL; #endif + #if HAS_STANDARD_MOTION + bool setActive(const bool a) { + if (a == active) return false; + stepper.ftMotion_syncPosition(); + active = a; + return true; + } + #endif + #if HAS_FTM_SHAPING + constexpr bool goodZeta(const float z) { return WITHIN(z, 0.01f, 1.0f); } constexpr bool goodVtol(const float v) { return WITHIN(v, 0.00f, 1.0f); } + #if HAS_DYNAMIC_FREQ + + uint8_t setDynFreqMode(const uint8_t m) { + if (dynFreqMode_t(m) == dynFreqMode) return 0; + switch (dynFreqMode_t(m)) { + default: return 2; + TERN_(HAS_DYNAMIC_FREQ_MM, case dynFreqMode_Z_BASED:) + TERN_(HAS_DYNAMIC_FREQ_G, case dynFreqMode_MASS_BASED:) + case dynFreqMode_DISABLED: + planner.synchronize(); + dynFreqMode = dynFreqMode_t(m); + break; + } + return 1; + } + bool modeUsesDynFreq() const { return (TERN0(HAS_DYNAMIC_FREQ_MM, dynFreqMode == dynFreqMode_Z_BASED) || TERN0(HAS_DYNAMIC_FREQ_G, dynFreqMode == dynFreqMode_MASS_BASED)); } - #endif + + #endif // HAS_DYNAMIC_FREQ + #endif // HAS_FTM_SHAPING constexpr bool goodBaseFreq(const float f) { return WITHIN(f, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2); } @@ -158,9 +186,9 @@ class FTMotion { TERN_(HAS_FTM_SHAPING, update_shaping_params()); #if ENABLED(FTM_SMOOTHING) - #define _SET_SMOOTH(A) set_smoothing_time(_AXIS(A), FTM_SMOOTHING_TIME_##A); - CARTES_MAP(_SET_SMOOTH); - #undef _SET_SMOOTH + #define _RESET_SMOOTH(A) (void)set_smoothing_time(_AXIS(A), FTM_SMOOTHING_TIME_##A); + CARTES_MAP(_RESET_SMOOTH); + #undef _RESET_SMOOTH #endif TERN_(FTM_POLYS, setTrajectoryType(TrajectoryType::FTM_TRAJECTORY_TYPE)); @@ -188,7 +216,7 @@ class FTMotion { // Refresh alpha and delay samples used by smoothing functions. static void update_smoothing_params(); // Setters for smoothingTime that update alpha and delay - static void set_smoothing_time(uint8_t axis, const float s_time); + static bool set_smoothing_time(const AxisEnum axis, const float s_time); #endif static void reset(); // Reset all states of the fixed time conversion to defaults. @@ -197,14 +225,17 @@ class FTMotion { #if ALL(FT_MOTION, HAS_STANDARD_MOTION) static bool toggle() { stepper.ftMotion_syncPosition(); - FLIP(cfg.active); + cfg.setActive(!cfg.active); update_shaping_params(); return cfg.active; } #endif // Trajectory generator selection - static void setTrajectoryType(const TrajectoryType type); + #if ENABLED(FTM_POLYS) + static void setTrajectoryType(const TrajectoryType type); + static bool updateTrajectoryType(const TrajectoryType type); + #endif static TrajectoryType getTrajectoryType() { return TERN(FTM_POLYS, trajectoryType, TrajectoryType::TRAPEZOIDAL); } static FSTR_P getTrajectoryName(); @@ -295,10 +326,10 @@ extern FTMotion ftMotion; // Use ftMotion.thing, not FTMotion::thing. bool isactive; FTMotionDisableInScope() { isactive = ftMotion.cfg.active; - ftMotion.cfg.active = false; + ftMotion.cfg.setActive(false); } ~FTMotionDisableInScope() { - ftMotion.cfg.active = isactive; + ftMotion.cfg.setActive(isactive); if (isactive) ftMotion.init(); } } FTMotionDisableInScope_t; diff --git a/Marlin/src/module/ft_motion/shaping.h b/Marlin/src/module/ft_motion/shaping.h index dacd0454fb..671bdeea88 100644 --- a/Marlin/src/module/ft_motion/shaping.h +++ b/Marlin/src/module/ft_motion/shaping.h @@ -41,8 +41,10 @@ enum dynFreqMode_t : uint8_t { dynFreqMode_MASS_BASED = 2 }; -#define AXIS_IS_SHAPING(A) TERN0(FTM_SHAPER_##A, (ftMotion.cfg.shaper.A != ftMotionShaper_NONE)) -#define AXIS_IS_EISHAPING(A) TERN0(FTM_SHAPER_##A, WITHIN(ftMotion.cfg.shaper.A, ftMotionShaper_EI, ftMotionShaper_3HEI)) +#define IS_SHAPING(S) (S != ftMotionShaper_NONE) +#define IS_EISHAPING(S) WITHIN(S, ftMotionShaper_EI, ftMotionShaper_3HEI) +#define AXIS_IS_SHAPING(A) TERN0(FTM_SHAPER_##A, IS_SHAPING(ftMotion.cfg.shaper.A)) +#define AXIS_IS_EISHAPING(A) TERN0(FTM_SHAPER_##A, IS_EISHAPING(ftMotion.cfg.shaper.A)) // Emitters for code that only cares about shaped XYZE #if HAS_FTM_SHAPING diff --git a/Marlin/src/module/ft_motion/smoothing.cpp b/Marlin/src/module/ft_motion/smoothing.cpp index f0016391b3..f40bd828aa 100644 --- a/Marlin/src/module/ft_motion/smoothing.cpp +++ b/Marlin/src/module/ft_motion/smoothing.cpp @@ -27,7 +27,7 @@ #include "smoothing.h" // Set smoothing time and recalculate alpha and delay. -void AxisSmoothing::set_smoothing_time(const float s_time) { +void AxisSmoothing::set_time(const float s_time) { if (s_time > 0.001f) { alpha = 1.0f - expf(-(FTM_TS) * (FTM_SMOOTHING_ORDER) / s_time ); delay_samples = s_time * FTM_FS; diff --git a/Marlin/src/module/ft_motion/smoothing.h b/Marlin/src/module/ft_motion/smoothing.h index 9d4043c028..3e32b2b917 100644 --- a/Marlin/src/module/ft_motion/smoothing.h +++ b/Marlin/src/module/ft_motion/smoothing.h @@ -24,7 +24,13 @@ #include "../../inc/MarlinConfig.h" typedef struct FTSmoothedAxes { - float CARTES_AXIS_NAMES; + union { + struct { float CARTES_AXIS_NAMES; }; + struct { float CARTES_AXIS_NAMES_LC; }; + float data[CARTES_COUNT]; + }; + float& operator[](const int n) { return data[n]; } + const float& operator[](const int n) const { return data[n]; } } ft_smoothed_float_t; // Smoothing data for each axis @@ -34,7 +40,7 @@ typedef struct AxisSmoothing { float smoothing_pass[FTM_SMOOTHING_ORDER] = { 0.0f }; // Last value of each of the exponential smoothing passes float alpha = 0.0f; // Pre-calculated alpha for smoothing. uint32_t delay_samples = 0; // Pre-calculated delay in samples for smoothing. - void set_smoothing_time(const float s_time); // Set smoothing time, recalculate alpha and delay. + void set_time(const float s_time); // Set smoothing time, recalculate alpha and delay. } axis_smoothing_t; typedef struct Smoothing { diff --git a/Marlin/src/module/ft_motion/stepping.h b/Marlin/src/module/ft_motion/stepping.h index a845e78e3d..474918ec75 100644 --- a/Marlin/src/module/ft_motion/stepping.h +++ b/Marlin/src/module/ft_motion/stepping.h @@ -31,18 +31,24 @@ FORCE_INLINE constexpr uint32_t a_times_b_shift_16(const uint32_t a, const uint3 return (hi * b) + ((lo * b) >> 16); } #define FTM_NEVER uint32_t(UINT16_MAX) // Reserved number to indicate "no ticks in this frame" (FRAME_TICKS_FP+1 would work too) -constexpr uint32_t FRAME_TICKS = STEPPER_TIMER_RATE / FTM_FS; // Timer ticks in a frame -static_assert(FRAME_TICKS < FTM_NEVER, "(STEPPER_TIMER_RATE / FTM_FS) must be < 2^16 (otherwise fixed-point numbers exceed uint16 vars)."); -constexpr uint32_t FTM_Q_INT = 32u - __builtin_clz(FRAME_TICKS + 1); // Bits to represent the max value (duration of a frame, +1 one for FTM_NEVER). +constexpr uint32_t FRAME_TICKS = STEPPER_TIMER_RATE / FTM_FS; // Timer ticks per frame (by default, 1kHz) +constexpr uint32_t TICKS_BITS = __builtin_clzl(FRAME_TICKS + 1UL); // Bits to represent the max value (duration of a frame, +1 one for FTM_NEVER). +constexpr uint32_t FTM_Q_INT = 32u - TICKS_BITS; // Bits remaining // "clz" counts leading zeroes. -constexpr uint32_t FTM_Q = 16 - FTM_Q_INT; // uint16 interval fractional bits. +constexpr uint32_t FTM_Q = 16u - FTM_Q_INT; // uint16 interval fractional bits. // Intervals buffer has fixed point numbers with the point on this position +static_assert(FRAME_TICKS < FTM_NEVER, "(STEPPER_TIMER_RATE / FTM_FS) must be < " STRINGIFY(FTM_NEVER) " to fit 16-bit fixed-point numbers."); +static_assert(FRAME_TICKS != 2000 || FTM_Q_INT == 11, "FTM_Q_INT should be 11"); +static_assert(FRAME_TICKS != 2000 || FTM_Q == 5, "FTM_Q should be 5"); +static_assert(FRAME_TICKS != 25000 || FTM_Q_INT == 15, "FTM_Q_INT should be 15"); +static_assert(FRAME_TICKS != 25000 || FTM_Q == 1, "FTM_Q should be 1"); + // The _FP and _fp suffixes mean the number is in fixed point format with the point at the FTM_Q position. // See: https://en.wikipedia.org/wiki/Fixed-point_arithmetic -// E.g number_fp = number << FTM_Q -// number == (number_fp >> FTM_Q) -constexpr uint32_t ONE_FP = 1 << FTM_Q; // Number 1 in fixed point format +// e.g., number_fp = number << FTM_Q +// number == (number_fp >> FTM_Q) +constexpr uint32_t ONE_FP = 1UL << FTM_Q; // Number 1 in fixed point format constexpr uint32_t FP_FLOOR_MASK = ~(ONE_FP - 1); // Bit mask to do FLOOR in fixed point constexpr uint32_t FRAME_TICKS_FP = FRAME_TICKS << FTM_Q; // Ticks in a frame in fixed point