🧑‍💻 FT Motion accessors, G-code style
Some checks are pending
CI - Build Tests / Build Test (push) Waiting to run
CI - Unit Tests / Unit Test (push) Waiting to run
CI - Validate Source Files / Validate Source Files (push) Waiting to run

This commit is contained in:
Scott Lahteine 2025-12-02 16:10:49 -06:00
parent 291a90ace4
commit 3ee18bc667
6 changed files with 79 additions and 65 deletions

View file

@ -129,15 +129,14 @@
#if ENABLED(Z_SAFE_HOMING)
inline void home_z_safely() {
// Potentially disable Fixed-Time Motion for homing
TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE());
DEBUG_SECTION(log_G28, "home_z_safely", DEBUGGING(LEVELING));
// Disallow Z homing if X or Y homing is needed
if (homing_needed_error(_BV(X_AXIS) | _BV(Y_AXIS))) return;
// Potentially disable Fixed-Time Motion for homing
TERN_(FT_MOTION, FTM_DISABLE_IN_SCOPE());
sync_plan_position();
/**

View file

@ -208,12 +208,14 @@ void GcodeSuite::M493() {
if (!parser.seen_any())
flag.report = true;
ft_config_t &c = ftMotion.cfg;
// Parse 'S' mode parameter.
if (parser.seen('S')) {
const bool active = parser.value_bool();
if (active != ftMotion.cfg.active) {
if (active != c.active) {
stepper.ftMotion_syncPosition();
ftMotion.cfg.active = active;
c.active = active;
flag.report = true;
}
}
@ -228,8 +230,8 @@ void GcodeSuite::M493() {
return;
}
auto set_shaper = [&](const AxisEnum axis, ftMotionShaper_t newsh) {
if (newsh != ftMotion.cfg.shaper[axis]) {
ftMotion.cfg.shaper[axis] = newsh;
if (newsh != c.shaper[axis]) {
c.shaper[axis] = newsh;
flag.update = flag.report = true;
}
};
@ -243,8 +245,8 @@ void GcodeSuite::M493() {
// Parse 'H' Axis Synchronization parameter.
if (parser.seenval('H')) {
const bool enabled = parser.value_bool();
if (enabled != ftMotion.cfg.axis_sync_enabled) {
ftMotion.cfg.axis_sync_enabled = enabled;
if (enabled != c.axis_sync_enabled) {
c.axis_sync_enabled = enabled;
flag.report = true;
}
}
@ -263,7 +265,7 @@ void GcodeSuite::M493() {
case dynFreqMode_MASS_BASED:
#endif
case dynFreqMode_DISABLED:
ftMotion.cfg.dynFreqMode = val;
c.dynFreqMode = val;
flag.report = true;
break;
default:
@ -271,22 +273,18 @@ void GcodeSuite::M493() {
break;
}
}
else {
SERIAL_ECHOLNPGM("?Wrong shaper for (D)ynamic Frequency Mode ", ftMotion.cfg.dynFreqMode, ".");
}
else
SERIAL_ECHOLNPGM("?Shaper required for (D)ynamic Frequency Mode ", c.dynFreqMode, ".");
}
const bool modeUsesDynFreq = (
TERN0(HAS_DYNAMIC_FREQ_MM, ftMotion.cfg.dynFreqMode == dynFreqMode_Z_BASED)
|| TERN0(HAS_DYNAMIC_FREQ_G, ftMotion.cfg.dynFreqMode == dynFreqMode_MASS_BASED)
);
const bool modeUsesDynFreq = c.modeUsesDynFreq();
#endif // HAS_DYNAMIC_FREQ
// Frequency parameter
const bool seenA = parser.seenval('A');
const float baseFreqVal = seenA ? parser.value_float() : 0.0f;
const bool goodBaseFreq = seenA && WITHIN(baseFreqVal, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2);
const bool goodBaseFreq = seenA && c.goodBaseFreq(baseFreqVal);
if (seenA && !goodBaseFreq)
SERIAL_ECHOLN(F("?Invalid "), F("(A) Base Frequency value. ("), int(FTM_MIN_SHAPE_FREQ), C('-'), int((FTM_FS) / 2), C(')'));
@ -301,14 +299,14 @@ void GcodeSuite::M493() {
// Zeta parameter
const bool seenI = parser.seenval('I');
const float zetaVal = seenI ? parser.value_float() : 0.0f;
const bool goodZeta = seenI && WITHIN(zetaVal, 0.01f, 1.0f);
const bool goodZeta = seenI && c.goodZeta(zetaVal);
if (seenI && !goodZeta)
SERIAL_ECHOLN(F("?Invalid "), F("(I) Zeta value. (0.01-1.0)")); // Zeta out of range
// Vibration Tolerance parameter
const bool seenQ = parser.seenval('Q');
const float vtolVal = seenQ ? parser.value_float() : 0.0f;
const bool goodVtol = seenQ && WITHIN(vtolVal, 0.00f, 1.0f);
const bool goodVtol = seenQ && c.goodVtol(vtolVal);
if (seenQ && !goodVtol)
SERIAL_ECHOLN(F("?Invalid "), F("(Q) Vibration Tolerance value. (0.0-1.0)")); // VTol out of range
@ -323,18 +321,18 @@ void GcodeSuite::M493() {
if (AXIS_IS_SHAPING(X)) {
// TODO: Frequency minimum is dependent on the shaper used; the above check isn't always correct.
if (goodBaseFreq) {
ftMotion.cfg.baseFreq.x = baseFreqVal;
c.baseFreq.x = baseFreqVal;
flag.update = flag.report = true;
}
}
else // Mode doesn't use frequency.
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " [A] frequency.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " (A) frequency.");
}
#if HAS_DYNAMIC_FREQ
// Parse X frequency scaling parameter
if (seenF && modeUsesDynFreq) {
ftMotion.cfg.dynFreqK.x = baseDynFreqVal;
c.dynFreqK.x = baseDynFreqVal;
flag.report = true;
}
#endif
@ -343,24 +341,24 @@ void GcodeSuite::M493() {
if (seenI) {
if (AXIS_IS_SHAPING(X)) {
if (goodZeta) {
ftMotion.cfg.zeta.x = zetaVal;
c.zeta.x = zetaVal;
flag.update = true;
}
}
else
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " zeta parameter.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " (I) zeta parameter.");
}
// Parse X vtol parameter
if (seenQ) {
if (AXIS_IS_EISHAPING(X)) {
if (goodVtol) {
ftMotion.cfg.vtol.x = vtolVal;
c.vtol.x = vtolVal;
flag.update = true;
}
}
else
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " vtol parameter.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_A_NAME), " (Q) vtol parameter.");
}
}
@ -374,18 +372,18 @@ void GcodeSuite::M493() {
if (seenA) {
if (AXIS_IS_SHAPING(Y)) {
if (goodBaseFreq) {
ftMotion.cfg.baseFreq.y = baseFreqVal;
c.baseFreq.y = baseFreqVal;
flag.update = flag.report = true;
}
}
else // Mode doesn't use frequency.
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " [A] frequency.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " (A) frequency.");
}
#if HAS_DYNAMIC_FREQ
// Parse Y frequency scaling parameter
if (seenF && modeUsesDynFreq) {
ftMotion.cfg.dynFreqK.y = baseDynFreqVal;
c.dynFreqK.y = baseDynFreqVal;
flag.report = true;
}
#endif
@ -394,24 +392,24 @@ void GcodeSuite::M493() {
if (seenI) {
if (AXIS_IS_SHAPING(Y)) {
if (goodZeta) {
ftMotion.cfg.zeta.y = zetaVal;
c.zeta.y = zetaVal;
flag.update = true;
}
}
else
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " zeta parameter.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " (I) zeta parameter.");
}
// Parse Y vtol parameter
if (seenQ) {
if (AXIS_IS_EISHAPING(Y)) {
if (goodVtol) {
ftMotion.cfg.vtol.y = vtolVal;
c.vtol.y = vtolVal;
flag.update = true;
}
}
else
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " vtol parameter.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_B_NAME), " (Q) vtol parameter.");
}
}
@ -425,18 +423,18 @@ void GcodeSuite::M493() {
if (seenA) {
if (AXIS_IS_SHAPING(Z)) {
if (goodBaseFreq) {
ftMotion.cfg.baseFreq.z = baseFreqVal;
c.baseFreq.z = baseFreqVal;
flag.update = flag.report = true;
}
}
else // Mode doesn't use frequency.
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " [A] frequency.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " (A) frequency.");
}
#if HAS_DYNAMIC_FREQ
// Parse Z frequency scaling parameter
if (seenF && modeUsesDynFreq) {
ftMotion.cfg.dynFreqK.z = baseDynFreqVal;
c.dynFreqK.z = baseDynFreqVal;
flag.report = true;
}
#endif
@ -445,24 +443,24 @@ void GcodeSuite::M493() {
if (seenI) {
if (AXIS_IS_SHAPING(Z)) {
if (goodZeta) {
ftMotion.cfg.zeta.z = zetaVal;
c.zeta.z = zetaVal;
flag.update = true;
}
}
else
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " zeta parameter.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " (I) zeta parameter.");
}
// Parse Z vtol parameter
if (seenQ) {
if (AXIS_IS_EISHAPING(Z)) {
if (goodVtol) {
ftMotion.cfg.vtol.z = vtolVal;
c.vtol.z = vtolVal;
flag.update = true;
}
}
else
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " vtol parameter.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C(STEPPER_C_NAME), " (Q) vtol parameter.");
}
}
@ -476,18 +474,18 @@ void GcodeSuite::M493() {
if (seenA) {
if (AXIS_IS_SHAPING(E)) {
if (goodBaseFreq) {
ftMotion.cfg.baseFreq.e = baseFreqVal;
c.baseFreq.e = baseFreqVal;
flag.update = flag.report = true;
}
}
else // Mode doesn't use frequency.
SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " [A] frequency.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " (A) frequency.");
}
#if HAS_DYNAMIC_FREQ
// Parse E frequency scaling parameter
if (seenF && modeUsesDynFreq) {
ftMotion.cfg.dynFreqK.e = baseDynFreqVal;
c.dynFreqK.e = baseDynFreqVal;
flag.report = true;
}
#endif
@ -496,24 +494,24 @@ void GcodeSuite::M493() {
if (seenI) {
if (AXIS_IS_SHAPING(E)) {
if (goodZeta) {
ftMotion.cfg.zeta.e = zetaVal;
c.zeta.e = zetaVal;
flag.update = true;
}
}
else
SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " zeta parameter.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " (I) zeta parameter.");
}
// Parse E vtol parameter
if (seenQ) {
if (AXIS_IS_EISHAPING(E)) {
if (goodVtol) {
ftMotion.cfg.vtol.e = vtolVal;
c.vtol.e = vtolVal;
flag.update = true;
}
}
else
SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " vtol parameter.");
SERIAL_ECHOLNPGM("?Wrong mode for ", C('E'), " (Q) vtol parameter.");
}
}

View file

@ -55,19 +55,18 @@ void GcodeSuite::M494_report(const bool forReplay/*=true*/) {
SERIAL_ECHOPGM(" M494 T", (uint8_t)ftMotion.getTrajectoryType());
#if ENABLED(FTM_SMOOTHING)
SERIAL_ECHOPGM(
CARTES_PAIRED_LIST(
" X", c.smoothingTime.X, " Y", c.smoothingTime.Y,
" Z", c.smoothingTime.Z, " E", c.smoothingTime.E
)
);
#endif // FTM_SMOOTHING
SERIAL_ECHOPGM(CARTES_PAIRED_LIST(
" X", c.smoothingTime.X,
" Y", c.smoothingTime.Y,
" Z", c.smoothingTime.Z,
" E", c.smoothingTime.E
));
#endif
#if ENABLED(FTM_POLYS)
if (ftMotion.getTrajectoryType() == TrajectoryType::POLY6)
SERIAL_ECHOPGM(" O", c.poly6_acceleration_overshoot);
#endif // FTM_POLYS
#endif
SERIAL_EOL();
}
@ -97,8 +96,9 @@ void GcodeSuite::M494() {
report = true;
}
else
SERIAL_ECHOLN(F("?Invalid "), F("trajectory type [T] value. Use 0=TRAPEZOIDAL, 1=POLY5, 2=POLY6"));
SERIAL_ECHOLN(F("?Invalid "), F("(T)rajectory type value. Use 0=TRAPEZOIDAL, 1=POLY5, 2=POLY6"));
}
// Parse overshoot parameter.
if (parser.seenval('O')) {
const float val = parser.value_float();
@ -107,7 +107,7 @@ void GcodeSuite::M494() {
report = true;
}
else
SERIAL_ECHOLN(F("?Invalid "), F("overshoot [O] value. Range 1.25-1.875"));
SERIAL_ECHOLN(F("?Invalid "), F("(O)vershoot value. Range 1.25-1.875"));
}
#endif // FTM_POLYS
@ -122,7 +122,7 @@ void GcodeSuite::M494() {
report = true; \
} \
else \
SERIAL_ECHOLNPGM("?Invalid ", C(N), " smoothing time [", C(CHARIFY(A)), "] value."); \
SERIAL_ECHOLNPGM("?Invalid ", C(N), " smoothing time (", C(CHARIFY(A)), ") value."); \
}
CARTES_GANG(

View file

@ -542,6 +542,7 @@ void menu_move() {
} // menu_ft_motion
void menu_tune_ft_motion() {
// Define stuff ahead of the menu loop
ft_config_t &c = ftMotion.cfg;
@ -551,10 +552,10 @@ void menu_move() {
// 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
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);
@ -565,10 +566,10 @@ void menu_move() {
#endif
#if HAS_DYNAMIC_FREQ
MString<20> dmode;
#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);
@ -579,10 +580,10 @@ void menu_move() {
#endif
#if ENABLED(FTM_POLYS)
MString<20> traj_name;
#if CACHE_FOR_SPEED
bool got_t = false;
#endif
MString<20> traj_name;
auto _traj_name = [&]{
if (TERN1(CACHE_FOR_SPEED, !got_t)) {
TERN_(CACHE_FOR_SPEED, got_t = true);

View file

@ -81,7 +81,9 @@ TrapezoidalTrajectoryGenerator FTMotion::trapezoidalGenerator;
#endif
// Resonance Test
TERN_(FTM_RESONANCE_TEST,ResonanceGenerator FTMotion::rtg;) // Resonance trajectory generator instance
#if ENABLED(FTM_RESONANCE_TEST)
ResonanceGenerator FTMotion::rtg; // Resonance trajectory generator instance
#endif
#if FTM_HAS_LIN_ADVANCE
bool FTMotion::use_advance_lead;

View file

@ -90,6 +90,20 @@ typedef struct FTConfig {
#else
static constexpr TrajectoryType trajectory_type = TrajectoryType::TRAPEZOIDAL;
#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
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_FTM_SHAPING
constexpr bool goodBaseFreq(const float f) { return WITHIN(f, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2); }
} ft_config_t;
/**