🚸 Optimize FTM menu code, use some setters (#28170)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
narno2202
2025-12-11 23:15:49 +01:00
committed by GitHub
parent 4cd972c3bb
commit f2ac2e7cad
14 changed files with 187 additions and 224 deletions

View File

@@ -177,6 +177,7 @@ template <class L, class R> struct IF<true, L, R> { 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 ,

View File

@@ -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

View File

@@ -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."); \
}

View File

@@ -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.");

View File

@@ -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...");

View File

@@ -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

View File

@@ -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)

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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