FTMotion Z shaping, axis sync, axis smoothing (#28055)

This commit is contained in:
David Buezas
2025-09-28 02:42:33 +02:00
committed by GitHub
parent 657929740f
commit dc9e57464f
21 changed files with 845 additions and 259 deletions
+44 -30
View File
@@ -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
/**
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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__ (
+15
View File
@@ -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
+259 -84
View File
@@ -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<mode> 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<mode> 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<gain> Set Linear Advance gain
*
* G<bool> Enable (1) or Disable (0) axis synchronization.
*
* D<mode> Set Dynamic Frequency mode
* 0: DISABLED
* 1: Z-based (Requires a Z axis)
* 2: Mass-based (Requires X and E axes)
*
* A<Hz> Set static/base frequency for the X axis
* F<Hz> 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<Hz> Set X static/base frequency
* F<Hz> Set X frequency scaling
* I<flt> Set X damping ratio
* Q<flt> Set X vibration tolerance
*
* B<Hz> Set Y static/base frequency
* H<Hz> Set Y frequency scaling
* J<flt> Set Y damping ratio
* R<flt> Set Y vibration tolerance
*
* With FTM_SHAPING_Z:
* C<Hz> Set Z static/base frequency
* L<Hz> Set Z frequency scaling
* O<flt> Set Z damping ratio
* M<flt> Set Z vibration tolerance
*
* With FTM_SHAPING_E:
* W<Hz> Set E static/base frequency
* O<Hz> Set E frequency scaling
* U<flt> Set E damping ratio
* V<flt> Set E vibration tolerance
*
* B<Hz> Set static/base frequency for the Y axis
* H<Hz> 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();
+133
View File
@@ -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 <https://www.gnu.org/licenses/>.
*
*/
#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<time> Set smoothing time for the X axis
* Y<time> Set smoothing time for the Y axis
* Z<time> Set smoothing time for the Z axis
* E<time> Set smoothing time for the E axis
*/
void GcodeSuite::M494() {
bool report = !parser.seen_any();
#if HAS_X_AXIS
// Parse X axis smoothing time parameter.
if (parser.seenval('X')) {
const float val = parser.value_float();
if (WITHIN(val, 0.0f, FTM_MAX_SMOOTHING_TIME)) {
ftMotion.set_smoothing_time(X_AXIS, val);
report = true;
}
else
SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_A_NAME), " smoothing time [", C('X'), "] value.");
}
#endif
#if HAS_Y_AXIS
// Parse Y axis smoothing time parameter.
if (parser.seenval('Y')) {
const float val = parser.value_float();
if (WITHIN(val, 0.0f, FTM_MAX_SMOOTHING_TIME)) {
ftMotion.set_smoothing_time(Y_AXIS, val);
report = true;
}
else
SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_B_NAME), " smoothing time [", C('Y'), "] value.");
}
#endif
#if HAS_Z_AXIS
// Parse Z axis smoothing time parameter.
if (parser.seenval('Z')) {
const float val = parser.value_float();
if (WITHIN(val, 0.0f, FTM_MAX_SMOOTHING_TIME)) {
ftMotion.set_smoothing_time(Z_AXIS, val);
report = true;
}
else
SERIAL_ECHOLNPGM("?Invalid ", C(STEPPER_C_NAME), " smoothing time [", C('Z'), "] value.");
}
#endif
#if HAS_EXTRUDERS
// Parse E axis smoothing time parameter.
if (parser.seenval('E')) {
const float val = parser.value_float();
if (WITHIN(val, 0.0f, FTM_MAX_SMOOTHING_TIME)) {
ftMotion.set_smoothing_time(E_AXIS, val);
report = true;
}
else
SERIAL_ECHOLNPGM("?Invalid ", C('E'), " smoothing time [", C('E'), "] value.");
}
#endif
if (report) say_smoothing();
}
#endif // FTM_SMOOTHING
+3
View File
@@ -922,6 +922,9 @@ void GcodeSuite::process_parsed_command(bool no_ok/*=false*/) {
#if ENABLED(FT_MOTION)
case 493: M493(); break; // M493: Fixed-Time Motion control
#if ENABLED(FTM_SMOOTHING)
case 494: M494(); break; // M494: Fixed-Time Motion extras
#endif
#endif
case 500: M500(); break; // M500: Store settings in EEPROM
+4
View File
@@ -1105,6 +1105,10 @@ private:
#if ENABLED(FT_MOTION)
static void M493();
static void M493_report(const bool forReplay=true);
#if ENABLED(FTM_SMOOTHING)
static void M494();
static void M494_report(const bool forReplay=true);
#endif
#endif
static void M500();
+1
View File
@@ -45,6 +45,7 @@
*/
#if EXTRUDERS
#define HAS_EXTRUDERS 1
#define HAS_E_AXIS 1
#if EXTRUDERS > 1
#define HAS_MULTI_EXTRUDER 1
#endif
+10
View File
@@ -1522,6 +1522,16 @@
#if ENABLED(FT_MOTION)
#if HAS_X_AXIS
#define HAS_FTM_SHAPING 1
#define FTM_SHAPER_X
#endif
#if HAS_Y_AXIS
#define FTM_SHAPER_Y
#endif
#if !HAS_Z_AXIS
#undef FTM_SHAPER_Z
#endif
#if !HAS_EXTRUDERS
#undef FTM_SHAPER_E
#endif
#if ENABLED(FTM_UNIFIED_BWS)
#define FTM_WINDOW_SIZE FTM_BW_SIZE
+16
View File
@@ -3630,3 +3630,19 @@
#if ALL(SPI_FLASH, HAS_MEDIA, MARLIN_DEV_MODE)
#define SPI_FLASH_BACKUP 1
#endif
// Fixed-Time Motion
#if ENABLED(FT_MOTION)
#define FTM_TS (1.0f / FTM_FS) // (s) Time step for trajectory generation. (Reciprocal of FTM_FS)
#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_RATIO (FTM_FS / FTM_MIN_SHAPE_FREQ) // Factor for use in FTM_ZMAX. DON'T CHANGE.
#define FTM_SMOOTH_MAX_I uint32_t(TERN0(FTM_SMOOTHING, CEIL(FTM_FS * FTM_MAX_SMOOTHING_TIME))) // Max delays for smoothing
#define FTM_ZMAX (FTM_RATIO * 2 + FTM_SMOOTH_MAX_I) // 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_SMOOTHING_ORDER 5 // 3 to 5 is closest to gaussian
#endif
+6
View File
@@ -4594,6 +4594,12 @@ static_assert(_PLUS_TEST(3), "DEFAULT_MAX_ACCELERATION values must be positive."
#if HAS_DYNAMIC_FREQ_G
static_assert(FTM_DEFAULT_DYNFREQ_MODE != dynFreqMode_MASS_BASED, "dynFreqMode_MASS_BASED requires an X axis and an extruder.");
#endif
#if ENABLED(FTM_SMOOTHING)
static_assert(FTM_SMOOTHING_TIME_X <= FTM_MAX_SMOOTHING_TIME, "FTM_SMOOTHING_TIME_X must be <= FTM_MAX_SMOOTHING_TIME.");
static_assert(FTM_SMOOTHING_TIME_Y <= FTM_MAX_SMOOTHING_TIME, "FTM_SMOOTHING_TIME_Y must be <= FTM_MAX_SMOOTHING_TIME.");
static_assert(FTM_SMOOTHING_TIME_Z <= FTM_MAX_SMOOTHING_TIME, "FTM_SMOOTHING_TIME_Z must be <= FTM_MAX_SMOOTHING_TIME.");
static_assert(FTM_SMOOTHING_TIME_E <= FTM_MAX_SMOOTHING_TIME, "FTM_SMOOTHING_TIME_E must be <= FTM_MAX_SMOOTHING_TIME.");
#endif
#endif
// Multi-Stepping Limit
@@ -22,7 +22,7 @@
#include <U8glib-HAL.h>
#if defined(__AVR__) && ENABLED(NOT_EXTENDED_ISO10646_1_5X7)
#if ALL(__AVR__, NOT_EXTENDED_ISO10646_1_5X7)
// reduced font (only symbols 1 - 127) - saves about 1278 bytes of FLASH
/**
+2 -2
View File
@@ -3593,7 +3593,7 @@ void drawTuneMenu() {
}
#if ENABLED(ADAPTIVE_STEP_SMOOTHING_TOGGLE)
void setAdaptiveStepSmoothing() {
void toggleAdaptiveStepSmoothing() {
toggleCheckboxLine(stepper.adaptive_step_smoothing_enabled);
}
#endif
@@ -3706,7 +3706,7 @@ void drawMotionMenu() {
MENU_ITEM(ICON_InputShaping, MSG_INPUT_SHAPING, onDrawSubMenu, drawInputShaping_menu);
#endif
#if ENABLED(ADAPTIVE_STEP_SMOOTHING_TOGGLE)
EDIT_ITEM(ICON_UBLActive, MSG_STEP_SMOOTHING, onDrawChkbMenu, setAdaptiveStepSmoothing, &stepper.adaptive_step_smoothing_enabled);
EDIT_ITEM(ICON_UBLActive, MSG_STEP_SMOOTHING, onDrawChkbMenu, toggleAdaptiveStepSmoothing, &stepper.adaptive_step_smoothing_enabled);
#endif
EDIT_ITEM(ICON_Speed, MSG_SPEED, onDrawSpeedItem, setSpeed, &feedrate_percentage);
EDIT_ITEM(ICON_Flow, MSG_FLOW, onDrawPIntMenu, setFlow, &planner.flow_percentage[0]);
+2
View File
@@ -936,6 +936,7 @@ namespace LanguageNarrow_en {
LSTR MSG_FTM_MZV = _UxGT("MZV");
//LSTR MSG_FTM_ULENDO_FBS = _UxGT("Ulendo FBS");
//LSTR MSG_FTM_DISCTF = _UxGT("DISCTF");
LSTR MSG_FTM_AXIS_SYNC = _UxGT("Axis Sync");
LSTR MSG_FTM_DYN_MODE = _UxGT("DF Mode: $");
LSTR MSG_FTM_Z_BASED = _UxGT("Z-based");
LSTR MSG_FTM_MASS_BASED = _UxGT("Mass-based");
@@ -943,6 +944,7 @@ namespace LanguageNarrow_en {
LSTR MSG_FTM_DFREQ_K_N = _UxGT("@ Dyn. Freq.");
LSTR MSG_FTM_ZETA_N = _UxGT("@ Damping");
LSTR MSG_FTM_VTOL_N = _UxGT("@ Vib. Level");
LSTR MSG_FTM_SMOOTH_TIME_N = _UxGT("@ Smoothing Time");
LSTR MSG_LEVEL_X_AXIS = _UxGT("Level X Axis");
LSTR MSG_AUTO_CALIBRATE = _UxGT("Auto Calibrate");
+71 -59
View File
@@ -338,47 +338,33 @@ void menu_move() {
}
#endif
void ftm_menu_set_shaper(const AxisEnum axis, const ftMotionShaper_t s) {
ftMotion.cfg.shaper[axis] = s;
void ftm_menu_set_shaper(ftMotionShaper_t &outShaper, const ftMotionShaper_t s) {
outShaper = s;
ftMotion.update_shaping_params();
ui.go_back();
}
inline void menu_ftm_shaper_x() {
const ftMotionShaper_t shaper = ftMotion.cfg.shaper.x;
START_MENU();
BACK_ITEM(MSG_FIXED_TIME_MOTION);
#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(); \
}
if (shaper != ftMotionShaper_NONE) ACTION_ITEM(MSG_LCD_OFF, []{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_NONE); });
if (shaper != ftMotionShaper_ZV) ACTION_ITEM(MSG_FTM_ZV, []{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_ZV); });
if (shaper != ftMotionShaper_ZVD) ACTION_ITEM(MSG_FTM_ZVD, []{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_ZVD); });
if (shaper != ftMotionShaper_ZVDD) ACTION_ITEM(MSG_FTM_ZVDD, []{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_ZVDD); });
if (shaper != ftMotionShaper_ZVDDD) ACTION_ITEM(MSG_FTM_ZVDDD,[]{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_ZVDDD); });
if (shaper != ftMotionShaper_EI) ACTION_ITEM(MSG_FTM_EI, []{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_EI); });
if (shaper != ftMotionShaper_2HEI) ACTION_ITEM(MSG_FTM_2HEI, []{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_2HEI); });
if (shaper != ftMotionShaper_3HEI) ACTION_ITEM(MSG_FTM_3HEI, []{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_3HEI); });
if (shaper != ftMotionShaper_MZV) ACTION_ITEM(MSG_FTM_MZV, []{ ftm_menu_set_shaper(X_AXIS, ftMotionShaper_MZV); });
END_MENU();
}
inline void menu_ftm_shaper_y() {
const ftMotionShaper_t shaper = ftMotion.cfg.shaper.y;
START_MENU();
BACK_ITEM(MSG_FIXED_TIME_MOTION);
if (shaper != ftMotionShaper_NONE) ACTION_ITEM(MSG_LCD_OFF, []{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_NONE); });
if (shaper != ftMotionShaper_ZV) ACTION_ITEM(MSG_FTM_ZV, []{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_ZV); });
if (shaper != ftMotionShaper_ZVD) ACTION_ITEM(MSG_FTM_ZVD, []{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_ZVD); });
if (shaper != ftMotionShaper_ZVDD) ACTION_ITEM(MSG_FTM_ZVDD, []{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_ZVDD); });
if (shaper != ftMotionShaper_ZVDDD) ACTION_ITEM(MSG_FTM_ZVDDD,[]{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_ZVDDD); });
if (shaper != ftMotionShaper_EI) ACTION_ITEM(MSG_FTM_EI, []{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_EI); });
if (shaper != ftMotionShaper_2HEI) ACTION_ITEM(MSG_FTM_2HEI, []{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_2HEI); });
if (shaper != ftMotionShaper_3HEI) ACTION_ITEM(MSG_FTM_3HEI, []{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_3HEI); });
if (shaper != ftMotionShaper_MZV) ACTION_ITEM(MSG_FTM_MZV, []{ ftm_menu_set_shaper(Y_AXIS, ftMotionShaper_MZV); });
END_MENU();
}
MENU_FTM_SHAPER(X);
MENU_FTM_SHAPER(Y);
TERN_(FTM_SHAPER_Z, MENU_FTM_SHAPER(Z));
TERN_(FTM_SHAPER_E, MENU_FTM_SHAPER(E));
#if HAS_DYNAMIC_FREQ
@@ -454,26 +440,19 @@ void menu_move() {
// Show only when FT Motion is active (or optionally always show)
if (c.active || ENABLED(FT_MOTION_NO_MENU_TOGGLE)) {
#if HAS_X_AXIS
SUBMENU_N_S(X_AXIS, _shaper_name(X_AXIS), MSG_FTM_CMPN_MODE, menu_ftm_shaper_x);
if (AXIS_IS_SHAPING(X)) {
EDIT_ITEM_FAST_N(float42_52, X_AXIS, MSG_FTM_BASE_FREQ_N, &c.baseFreq.x, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2, ftMotion.update_shaping_params);
EDIT_ITEM_FAST_N(float42_52, X_AXIS, MSG_FTM_ZETA_N, &c.zeta.x, 0.0f, 1.0f, ftMotion.update_shaping_params);
if (AXIS_IS_EISHAPING(X))
EDIT_ITEM_FAST_N(float42_52, X_AXIS, MSG_FTM_VTOL_N, &c.vtol.x, 0.0f, 1.0f, ftMotion.update_shaping_params);
#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); \
}
#endif
#if HAS_Y_AXIS
SUBMENU_N_S(Y_AXIS, _shaper_name(Y_AXIS), MSG_FTM_CMPN_MODE, menu_ftm_shaper_y);
if (AXIS_IS_SHAPING(Y)) {
EDIT_ITEM_FAST_N(float42_52, Y_AXIS, MSG_FTM_BASE_FREQ_N, &c.baseFreq.y, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2, ftMotion.update_shaping_params);
EDIT_ITEM_FAST_N(float42_52, Y_AXIS, MSG_FTM_ZETA_N, &c.zeta.y, 0.0f, 1.0f, ftMotion.update_shaping_params);
if (AXIS_IS_EISHAPING(Y))
EDIT_ITEM_FAST_N(float42_52, Y_AXIS, MSG_FTM_VTOL_N, &c.vtol.y, 0.0f, 1.0f, ftMotion.update_shaping_params);
}
#endif
TERN_(HAS_X_AXIS, SHAPER_MENU_ITEM(X));
TERN_(HAS_Y_AXIS, SHAPER_MENU_ITEM(Y));
TERN_(FTM_SHAPER_Z, SHAPER_MENU_ITEM(Z));
TERN_(FTM_SHAPER_E, SHAPER_MENU_ITEM(E));
#if HAS_DYNAMIC_FREQ
SUBMENU_S(_dmode(), MSG_FTM_DYN_MODE, menu_ftm_dyn_mode);
@@ -484,6 +463,12 @@ void menu_move() {
#if HAS_Y_AXIS
EDIT_ITEM_FAST_N(float42_52, Y_AXIS, MSG_FTM_DFREQ_K_N, &c.dynFreqK.y, 0.0f, 20.0f);
#endif
#if ENABLED(FTM_SHAPER_Z)
EDIT_ITEM_FAST_N(float42_52, Z_AXIS, MSG_FTM_DFREQ_K_N, &c.dynFreqK.z, 0.0f, 20.0f);
#endif
#if ENABLED(FTM_SHAPER_E)
EDIT_ITEM_FAST_N(float42_52, E_AXIS, MSG_FTM_DFREQ_K_N, &c.dynFreqK.e, 0.0f, 20.0f);
#endif
}
#endif
@@ -492,9 +477,30 @@ void menu_move() {
if (c.linearAdvEna || ENABLED(FT_MOTION_NO_MENU_TOGGLE))
EDIT_ITEM(float42_52, MSG_ADVANCE_K, &c.linearAdvK, 0.0f, 10.0f);
#endif
EDIT_ITEM(bool, MSG_FTM_AXIS_SYNC, &c.axis_sync_enabled);
#if ENABLED(FTM_SMOOTHING)
#if HAS_X_AXIS
editable.decimal = c.smoothingTime.x;
EDIT_ITEM_FAST_N(float43, X_AXIS, MSG_FTM_SMOOTH_TIME_N, &editable.decimal, 0.0f, FTM_MAX_SMOOTHING_TIME, []{ ftMotion.set_smoothing_time(X_AXIS, editable.decimal); });
#endif
#if HAS_Y_AXIS
editable.decimal = c.smoothingTime.y;
EDIT_ITEM_FAST_N(float43, Y_AXIS, MSG_FTM_SMOOTH_TIME_N, &editable.decimal, 0.0f, FTM_MAX_SMOOTHING_TIME, []{ ftMotion.set_smoothing_time(Y_AXIS, editable.decimal); });
#endif
#if HAS_Z_AXIS
editable.decimal = c.smoothingTime.z;
EDIT_ITEM_FAST_N(float43, Z_AXIS, MSG_FTM_SMOOTH_TIME_N, &editable.decimal, 0.0f, FTM_MAX_SMOOTHING_TIME, []{ ftMotion.set_smoothing_time(Z_AXIS, editable.decimal); });
#endif
#if HAS_EXTRUDERS
editable.decimal = c.smoothingTime.e;
EDIT_ITEM_FAST_N(float43, E_AXIS, MSG_FTM_SMOOTH_TIME_N, &editable.decimal, 0.0f, FTM_MAX_SMOOTHING_TIME, []{ ftMotion.set_smoothing_time(E_AXIS, editable.decimal); });
#endif
#endif
}
END_MENU();
}
} // menu_ft_motion
void menu_tune_ft_motion() {
// Define stuff ahead of the menu loop
@@ -536,10 +542,16 @@ void menu_move() {
BACK_ITEM(MSG_TUNE);
#if HAS_X_AXIS
SUBMENU_N_S(X_AXIS, _shaper_name(X_AXIS), MSG_FTM_CMPN_MODE, menu_ftm_shaper_x);
SUBMENU_N_S(X_AXIS, _shaper_name(X_AXIS), MSG_FTM_CMPN_MODE, menu_ftm_shaper_X);
#endif
#if HAS_Y_AXIS
SUBMENU_N_S(Y_AXIS, _shaper_name(Y_AXIS), MSG_FTM_CMPN_MODE, menu_ftm_shaper_y);
SUBMENU_N_S(Y_AXIS, _shaper_name(Y_AXIS), MSG_FTM_CMPN_MODE, menu_ftm_shaper_Y);
#endif
#if ENABLED(FTM_SHAPER_Z)
SUBMENU_N_S(Z_AXIS, _shaper_name(Z_AXIS), MSG_FTM_CMPN_MODE, menu_ftm_shaper_Z);
#endif
#if ENABLED(FTM_SHAPER_E)
SUBMENU_N_S(E_AXIS, _shaper_name(E_AXIS), MSG_FTM_CMPN_MODE, menu_ftm_shaper_E);
#endif
#if HAS_DYNAMIC_FREQ
SUBMENU_S(_dmode(), MSG_FTM_DYN_MODE, menu_ftm_dyn_mode);
@@ -551,7 +563,7 @@ void menu_move() {
#endif
END_MENU();
}
} // menu_tune_ft_motion
#pragma GCC diagnostic pop
@@ -612,6 +624,6 @@ void menu_motion() {
GCODES_ITEM(MSG_DISABLE_STEPPERS, F("M84"));
END_MENU();
}
} // menu_motion
#endif // HAS_MARLINUI_MENU
+150 -38
View File
@@ -106,6 +106,29 @@ uint32_t FTMotion::interpIdx = 0; // Index of current data point b
#if HAS_Y_AXIS
, y:{ false, { 0.0f }, { 0.0f }, { 0 }, 0 }
#endif
#if ENABLED(FTM_SHAPER_Z)
, z:{ false, { 0.0f }, { 0.0f }, { 0 }, 0 }
#endif
#if ENABLED(FTM_SHAPER_E)
, e:{ false, { 0.0f }, { 0.0f }, { 0 }, 0 }
#endif
};
#endif
#if ENABLED(FTM_SMOOTHING)
FTMotion::smoothing_t FTMotion::smoothing = {
#if HAS_X_AXIS
x:{ { 0.0f }, 0.0f, 0 }, // smoothing_pass[], alpha, delay_samples
#endif
#if HAS_Y_AXIS
y:{ { 0.0f }, 0.0f, 0 },
#endif
#if HAS_Z_AXIS
z:{ { 0.0f }, 0.0f, 0 },
#endif
#if HAS_EXTRUDERS
e:{ { 0.0f }, 0.0f, 0 }
#endif
};
#endif
@@ -306,14 +329,16 @@ void FTMotion::loop() {
}
break;
default:
ZERO(Ai);
case ftMotionShaper_NONE:
max_i = 0;
Ai[0] = 1.0f; // No echoes so the whole impulse is applied in the first tap
break;
}
}
// Refresh the indices used by shaping functions.
// Ai[] must be precomputed (if zeta or vtol change, call set_axis_shaping_A first)
void FTMotion::AxisShaping::set_axis_shaping_N(const ftMotionShaper_t shaper, const float f, const float zeta) {
// Note that protections are omitted for DBZ and for index exceeding array length.
const float df = sqrt ( 1.f - sq(zeta) );
@@ -343,27 +368,73 @@ void FTMotion::loop() {
Ni[1] = round((0.375f / f / df) * (FTM_FS));
Ni[2] = Ni[1] + Ni[1];
break;
default: ZERO(Ni);
case ftMotionShaper_NONE:
// No echoes.
// max_i is set to 0 by set_axis_shaping_A, so delay centroid (Ni[0]) will also correctly be 0
break;
}
// Group delay in samples (i.e., Axis delay caused by shaping): sum(Ai * Ni[i]).
// Skipping i=0 since the uncompensated delay of the first impulse is always zero, so Ai[0] * Ni[0] == 0
float centroid = 0.0f;
for (uint8_t i = 1; i <= max_i; ++i) centroid -= Ai[i] * Ni[i];
Ni[0] = round(centroid);
// The resulting echo index can be negative, this is ok because it will be offset
// by the max delay of all axes before it is used.
for (uint8_t i = 1; i <= max_i; ++i) Ni[i] += Ni[0];
}
#if ENABLED(FTM_SMOOTHING)
// Set smoothing time and recalculate alpha and delay.
void FTMotion::AxisSmoothing::set_smoothing_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;
}
else {
alpha = 0.0f;
delay_samples = 0;
}
}
#endif
void FTMotion::update_shaping_params() {
#if HAS_X_AXIS
if ((shaping.x.ena = AXIS_IS_SHAPING(X))) {
shaping.x.set_axis_shaping_A(cfg.shaper.x, cfg.zeta.x, cfg.vtol.x);
shaping.x.set_axis_shaping_N(cfg.shaper.x, cfg.baseFreq.x, cfg.zeta.x);
}
#endif
#if HAS_Y_AXIS
if ((shaping.y.ena = AXIS_IS_SHAPING(Y))) {
shaping.y.set_axis_shaping_A(cfg.shaper.y, cfg.zeta.y, cfg.vtol.y);
shaping.y.set_axis_shaping_N(cfg.shaper.y, cfg.baseFreq.y, cfg.zeta.y);
}
#endif
#define UPDATE_SHAPER(A) \
shaping.A.ena = ftMotion.cfg.shaper.A != ftMotionShaper_NONE; \
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);
TERN_(HAS_X_AXIS, UPDATE_SHAPER(x));
TERN_(HAS_Y_AXIS, UPDATE_SHAPER(y));
TERN_(FTM_SHAPER_Z, UPDATE_SHAPER(z));
TERN_(FTM_SHAPER_E, UPDATE_SHAPER(e));
}
#endif // HAS_FTM_SHAPING
#if ENABLED(FTM_SMOOTHING)
void FTMotion::update_smoothing_params() {
TERN_(HAS_X_AXIS, smoothing.x.set_smoothing_time(cfg.smoothingTime.x));
TERN_(HAS_Y_AXIS, smoothing.y.set_smoothing_time(cfg.smoothingTime.y));
TERN_(HAS_Z_AXIS, smoothing.z.set_smoothing_time(cfg.smoothingTime.z));
TERN_(HAS_EXTRUDERS, smoothing.e.set_smoothing_time(cfg.smoothingTime.e));
}
void FTMotion::set_smoothing_time(uint8_t axis, const float s_time) {
switch (axis) {
TERN_(HAS_X_AXIS, case X_AXIS: cfg.smoothingTime.x = s_time; break;)
TERN_(HAS_Y_AXIS, case Y_AXIS: cfg.smoothingTime.y = s_time; break;)
TERN_(HAS_Z_AXIS, case Z_AXIS: cfg.smoothingTime.z = s_time; break;)
TERN_(HAS_EXTRUDERS, case E_AXIS: cfg.smoothingTime.e = s_time; break;)
}
update_smoothing_params();
}
#endif // FTM_SMOOTHING
// Reset all trajectory processing variables.
void FTMotion::reset() {
@@ -383,8 +454,10 @@ void FTMotion::reset() {
interpIdx = 0;
#if HAS_FTM_SHAPING
TERN_(HAS_X_AXIS, ZERO(shaping.x.d_zi));
TERN_(HAS_Y_AXIS, ZERO(shaping.y.d_zi));
TERN_(HAS_X_AXIS, ZERO(shaping.x.d_zi));
TERN_(HAS_Y_AXIS, ZERO(shaping.y.d_zi));
TERN_(FTM_SHAPER_Z, ZERO(shaping.z.d_zi));
TERN_(FTM_SHAPER_E, ZERO(shaping.e.d_zi));
shaping.zi_idx = 0;
#endif
@@ -443,6 +516,7 @@ int32_t FTMotion::stepperCmdBuffItems() {
// Initializes storage variables before startup.
void FTMotion::init() {
update_shaping_params();
TERN_(FTM_SMOOTHING, update_smoothing_params());
reset(); // Precautionary.
}
@@ -627,30 +701,68 @@ void FTMotion::generateTrajectoryPointsFromBlock() {
default: break;
}
uint32_t max_total_delay = 0;
#if ENABLED(FTM_SMOOTHING)
#define SMOOTHEN(A) /* Approximate gaussian smoothing via chained EMAs */ \
if (smoothing.A.alpha > 0.0f) { \
float smooth_val = traj.A[traj_idx_set]; \
for (uint8_t _i = 0; _i < FTM_SMOOTHING_ORDER; ++_i) { \
smoothing.A.smoothing_pass[_i] += (smooth_val - smoothing.A.smoothing_pass[_i]) * smoothing.A.alpha; \
smooth_val = smoothing.A.smoothing_pass[_i]; \
} \
traj.A[traj_idx_set] = smooth_val; \
}
TERN_(HAS_X_AXIS, SMOOTHEN(x));
TERN_(HAS_Y_AXIS, SMOOTHEN(y));
TERN_(HAS_Z_AXIS, SMOOTHEN(z));
TERN_(HAS_EXTRUDERS, SMOOTHEN(e));
max_total_delay += _MAX(
TERN0(HAS_X_AXIS, smoothing.x.delay_samples),
TERN0(HAS_Y_AXIS, smoothing.y.delay_samples),
TERN0(HAS_Z_AXIS, smoothing.z.delay_samples),
TERN0(HAS_EXTRUDERS, smoothing.e.delay_samples)
);
#endif // FTM_SMOOTHING
// Apply shaping if active on each axis
#if HAS_FTM_SHAPING
#if HAS_X_AXIS
if (shaping.x.ena) {
shaping.x.d_zi[shaping.zi_idx] = traj.x[traj_idx_set];
traj.x[traj_idx_set] *= shaping.x.Ai[0];
for (uint32_t i = 1U; i <= shaping.x.max_i; i++) {
const uint32_t udiffx = shaping.zi_idx - shaping.x.Ni[i];
traj.x[traj_idx_set] += shaping.x.Ai[i] * shaping.x.d_zi[shaping.x.Ni[i] > shaping.zi_idx ? (FTM_ZMAX) + udiffx : udiffx];
}
}
#endif
#if HAS_Y_AXIS
if (shaping.y.ena) {
shaping.y.d_zi[shaping.zi_idx] = traj.y[traj_idx_set];
traj.y[traj_idx_set] *= shaping.y.Ai[0];
for (uint32_t i = 1U; i <= shaping.y.max_i; i++) {
const uint32_t udiffy = shaping.zi_idx - shaping.y.Ni[i];
traj.y[traj_idx_set] += shaping.y.Ai[i] * shaping.y.d_zi[shaping.y.Ni[i] > shaping.zi_idx ? (FTM_ZMAX) + udiffy : udiffy];
}
}
#endif
if (ftMotion.cfg.axis_sync_enabled) {
max_total_delay -= _MIN(
TERN0(HAS_X_AXIS, shaping.x.Ni[0]),
TERN0(HAS_Y_AXIS, shaping.y.Ni[0]),
TERN0(FTM_SHAPER_Z, shaping.z.Ni[0]),
TERN0(FTM_SHAPER_E, shaping.e.Ni[0])
);
}
// Apply shaping if active on each axis
#define SHAPE(A) \
do { \
const uint32_t group_delay = ftMotion.cfg.axis_sync_enabled \
? max_total_delay - TERN0(FTM_SMOOTHING, smoothing.A.delay_samples) \
: -shaping.A.Ni[0]; \
/* α=1exp((dt / (τ / order))) */ \
shaping.A.d_zi[shaping.zi_idx] = traj.A[traj_idx_set]; \
traj.A[traj_idx_set] = 0; \
for (uint32_t i = 0; i <= shaping.A.max_i; i++) { \
/* echo_delay is always positive since Ni[i] = echo_relative_delay - group_delay + max_total_delay */ \
/* where echo_relative_delay > 0 and group_delay ≤ max_total_delay */ \
const uint32_t echo_delay = group_delay + shaping.A.Ni[i]; \
int32_t udiff = shaping.zi_idx - echo_delay; \
if (udiff < 0) udiff += FTM_ZMAX; \
traj.A[traj_idx_set] += shaping.A.Ai[i] * shaping.A.d_zi[udiff]; \
} \
} while (0)
TERN_(HAS_X_AXIS, SHAPE(x));
TERN_(HAS_Y_AXIS, SHAPE(y));
TERN_(FTM_SHAPER_Z, SHAPE(z));
TERN_(FTM_SHAPER_E, SHAPE(e));
if (++shaping.zi_idx == (FTM_ZMAX)) shaping.zi_idx = 0;
#endif // HAS_FTM_SHAPING
+74 -32
View File
@@ -39,16 +39,21 @@
typedef struct FTConfig {
bool active = ENABLED(FTM_IS_DEFAULT_MOTION); // Active (else standard motion)
bool axis_sync_enabled = true; // Axis synchronization enabled
#if HAS_FTM_SHAPING
ft_shaped_shaper_t shaper = // Shaper type
{ SHAPED_ELEM(FTM_DEFAULT_SHAPER_X, FTM_DEFAULT_SHAPER_Y) };
{ SHAPED_ELEM(FTM_DEFAULT_SHAPER_X, FTM_DEFAULT_SHAPER_Y, FTM_DEFAULT_SHAPER_Z, FTM_DEFAULT_SHAPER_E) };
ft_shaped_float_t baseFreq = // Base frequency. [Hz]
{ SHAPED_ELEM(FTM_SHAPING_DEFAULT_FREQ_X, FTM_SHAPING_DEFAULT_FREQ_Y) };
{ SHAPED_ELEM(FTM_SHAPING_DEFAULT_FREQ_X, FTM_SHAPING_DEFAULT_FREQ_Y, FTM_SHAPING_DEFAULT_FREQ_Z, FTM_SHAPING_DEFAULT_FREQ_E) };
ft_shaped_float_t zeta = // Damping factor
{ SHAPED_ELEM(FTM_SHAPING_ZETA_X, FTM_SHAPING_ZETA_Y) };
{ SHAPED_ELEM(FTM_SHAPING_ZETA_X, FTM_SHAPING_ZETA_Y, FTM_SHAPING_ZETA_Z, FTM_SHAPING_ZETA_E) };
ft_shaped_float_t vtol = // Vibration Level
{ SHAPED_ELEM(FTM_SHAPING_V_TOL_X, FTM_SHAPING_V_TOL_Y) };
{ SHAPED_ELEM(FTM_SHAPING_V_TOL_X, FTM_SHAPING_V_TOL_Y, FTM_SHAPING_V_TOL_Z, FTM_SHAPING_V_TOL_E) };
#if ENABLED(FTM_SMOOTHING)
ft_smoothed_float_t smoothingTime; // Smoothing time. [s]
#endif
#if HAS_DYNAMIC_FREQ
dynFreqMode_t dynFreqMode = FTM_DEFAULT_DYNFREQ_MODE; // Dynamic frequency mode configuration.
@@ -79,30 +84,37 @@ class FTMotion {
#if HAS_FTM_SHAPING
#if HAS_X_AXIS
cfg.shaper.x = FTM_DEFAULT_SHAPER_X;
cfg.baseFreq.x = FTM_SHAPING_DEFAULT_FREQ_X;
cfg.zeta.x = FTM_SHAPING_ZETA_X;
cfg.vtol.x = FTM_SHAPING_V_TOL_X;
#endif
#define SET_CFG_DEFAULTS(A) \
cfg.shaper.A = FTM_DEFAULT_SHAPER_##A; \
cfg.baseFreq.A = FTM_SHAPING_DEFAULT_FREQ_##A; \
cfg.zeta.A = FTM_SHAPING_ZETA_##A; \
cfg.vtol.A = FTM_SHAPING_V_TOL_##A;
#if HAS_Y_AXIS
cfg.shaper.y = FTM_DEFAULT_SHAPER_Y;
cfg.baseFreq.y = FTM_SHAPING_DEFAULT_FREQ_Y;
cfg.zeta.y = FTM_SHAPING_ZETA_Y;
cfg.vtol.y = FTM_SHAPING_V_TOL_Y;
#endif
TERN_(HAS_X_AXIS, SET_CFG_DEFAULTS(X));
TERN_(HAS_Y_AXIS, SET_CFG_DEFAULTS(Y));
TERN_(FTM_SHAPER_Z, SET_CFG_DEFAULTS(Z));
TERN_(FTM_SHAPER_E, SET_CFG_DEFAULTS(E));
#if HAS_DYNAMIC_FREQ
cfg.dynFreqMode = FTM_DEFAULT_DYNFREQ_MODE;
TERN_(HAS_X_AXIS, cfg.dynFreqK.x = 0.0f);
TERN_(HAS_Y_AXIS, cfg.dynFreqK.y = 0.0f);
//ZERO(cfg.dynFreqK);
TERN_(HAS_X_AXIS, cfg.dynFreqK.x = 0.0f);
TERN_(HAS_Y_AXIS, cfg.dynFreqK.y = 0.0f);
TERN_(FTM_SHAPER_Z, cfg.dynFreqK.z = 0.0f);
TERN_(FTM_SHAPER_E, cfg.dynFreqK.e = 0.0f);
#endif
update_shaping_params();
#endif // HAS_FTM_SHAPING
#if ENABLED(FTM_SMOOTHING)
TERN_(HAS_X_AXIS, set_smoothing_time(X_AXIS, FTM_SMOOTHING_TIME_X));
TERN_(HAS_Y_AXIS, set_smoothing_time(Y_AXIS, FTM_SMOOTHING_TIME_Y));
TERN_(HAS_Z_AXIS, set_smoothing_time(Z_AXIS, FTM_SMOOTHING_TIME_Z));
TERN_(HAS_EXTRUDERS, set_smoothing_time(E_AXIS, FTM_SMOOTHING_TIME_E));
#endif
#if HAS_EXTRUDERS
cfg.linearAdvEna = FTM_LINEAR_ADV_DEFAULT_ENA;
cfg.linearAdvK = FTM_LINEAR_ADV_DEFAULT_K;
@@ -129,6 +141,13 @@ class FTMotion {
static void update_shaping_params(void);
#endif
#if ENABLED(FTM_SMOOTHING)
// 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);
#endif
static void reset(); // Reset all states of the fixed time conversion to defaults.
FORCE_INLINE static bool axis_is_moving(const AxisEnum axis) {
@@ -179,15 +198,14 @@ class FTMotion {
static bool use_advance_lead;
#endif
// Shaping variables.
#if HAS_FTM_SHAPING
// Shaping data
typedef struct AxisShaping {
bool ena = false; // Enabled indication.
float d_zi[FTM_ZMAX] = { 0.0f }; // Data point delay vector.
float Ai[5]; // Shaping gain vector.
uint32_t Ni[5]; // Shaping time index vector.
uint32_t max_i; // Vector length for the selected shaper.
bool ena = false; // Enabled indication
float d_zi[FTM_ZMAX] = { 0.0f }; // Data point delay vector
float Ai[5]; // Shaping gain vector
int32_t Ni[5]; // Shaping time index vector
uint32_t max_i; // Vector length for the selected shaper
void set_axis_shaping_N(const ftMotionShaper_t shaper, const float f, const float zeta); // Sets the gains used by shaping functions.
void set_axis_shaping_A(const ftMotionShaper_t shaper, const float zeta, const float vtol); // Sets the indices used by shaping functions.
@@ -196,18 +214,35 @@ class FTMotion {
typedef struct Shaping {
uint32_t zi_idx; // Index of storage in the data point delay vectors.
#if HAS_X_AXIS
axis_shaping_t x;
#endif
#if HAS_Y_AXIS
axis_shaping_t y;
#endif
OPTCODE(HAS_X_AXIS, axis_shaping_t x)
OPTCODE(HAS_Y_AXIS, axis_shaping_t y)
OPTCODE(FTM_SHAPER_Z, axis_shaping_t z)
OPTCODE(FTM_SHAPER_E, axis_shaping_t e)
} shaping_t;
static shaping_t shaping; // Shaping data
#endif // HAS_FTM_SHAPING
#if ENABLED(FTM_SMOOTHING)
// Smoothing data for each axis
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.
} axis_smoothing_t;
// Smoothing data for XYZE axes
typedef struct Smoothing {
OPTCODE(HAS_X_AXIS, axis_smoothing_t x)
OPTCODE(HAS_Y_AXIS, axis_smoothing_t y)
OPTCODE(HAS_Z_AXIS, axis_smoothing_t z)
OPTCODE(HAS_EXTRUDERS, axis_smoothing_t e)
} smoothing_t;
static smoothing_t smoothing; // Smoothing data
#endif
// Linear advance variables.
#if HAS_EXTRUDERS
static float prev_traj_e;
@@ -221,7 +256,14 @@ class FTMotion {
static void generateTrajectoryPointsFromBlock();
static void generateStepsFromTrajectory(const uint32_t idx);
FORCE_INLINE static int32_t num_samples_shaper_settle() { return ( shaping.x.ena || shaping.y.ena ) ? FTM_ZMAX : 0; }
FORCE_INLINE static int32_t num_samples_shaper_settle() {
return (
TERN0(HAS_X_AXIS, shaping.x.ena)
|| TERN0(HAS_Y_AXIS, shaping.y.ena)
|| TERN0(FTM_SHAPER_Z, shaping.z.ena)
|| TERN0(FTM_SHAPER_E, shaping.e.ena)
) ? FTM_ZMAX : 0;
}
}; // class FTMotion
+50 -9
View File
@@ -41,8 +41,8 @@ enum dynFreqMode_t : uint8_t {
dynFreqMode_MASS_BASED = 2
};
#define AXIS_IS_SHAPING(A) (ftMotion.cfg.shaper[_AXIS(A)] != ftMotionShaper_NONE)
#define AXIS_IS_EISHAPING(A) WITHIN(ftMotion.cfg.shaper[_AXIS(A)], ftMotionShaper_EI, ftMotionShaper_3HEI)
#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))
typedef struct XYZEarray<float, FTM_WINDOW_SIZE> xyze_trajectory_t;
typedef struct XYZEarray<float, FTM_BATCH_SIZE> xyze_trajectoryMod_t;
@@ -58,26 +58,67 @@ enum {
FT_BIT_COUNT
};
typedef bits_t(FT_BIT_COUNT) ft_command_t;
#if HAS_FTM_SHAPING
#define NUM_AXES_SHAPED TERN(HAS_Y_AXIS, 2, 1)
#define SHAPED_ELEM(A, B) A OPTARG(HAS_Y_AXIS, B)
#define NUM_AXES_SHAPED COUNT_ENABLED(HAS_X_AXIS, HAS_Y_AXIS, FTM_SHAPER_Z, FTM_SHAPER_E)
#define SHAPED_ELEM(A,B,C,D) A OPTARG(HAS_Y_AXIS, B) OPTARG(FTM_SHAPER_Z, C) OPTARG(FTM_SHAPER_E, D)
#else
#define NUM_AXES_SHAPED 0
#define SHAPED_ELEM(A, B)
#define SHAPED_ELEM(A,B,C,D)
#endif
template<typename T>
struct FTShapedAxes {
union {
struct { T SHAPED_ELEM(X, Y); };
struct { T SHAPED_ELEM(x, y); };
struct { T SHAPED_ELEM(X, Y, Z, E); };
struct { T SHAPED_ELEM(x, y, z, e); };
T val[NUM_AXES_SHAPED];
};
T& operator[](int i) { return val[i]; }
T& operator[](const int axis) {
return val[axis_to_index(axis)];
}
private:
static constexpr int axis_to_index(int axis) {
int idx = 0;
#if HAS_X_AXIS
if (axis == X_AXIS) return idx;
idx++;
#endif
#if HAS_Y_AXIS
if (axis == Y_AXIS) return idx;
idx++;
#endif
#if ENABLED(FTM_SHAPER_Z)
if (axis == Z_AXIS) return idx;
idx++;
#endif
#if ENABLED(FTM_SHAPER_E)
if (axis == E_AXIS) return idx;
idx++;
#endif
return -1; // Invalid axis
}
};
typedef FTShapedAxes<float> ft_shaped_float_t;
typedef FTShapedAxes<ftMotionShaper_t> ft_shaped_shaper_t;
typedef FTShapedAxes<dynFreqMode_t> ft_shaped_dfm_t;
typedef bits_t(FT_BIT_COUNT) ft_command_t;
#if ENABLED(FTM_SMOOTHING)
typedef struct FTSmoothedAxes {
#if HAS_X_AXIS
float x;
#endif
#if HAS_Y_AXIS
float y;
#endif
#if HAS_Z_AXIS
float z;
#endif
#if HAS_EXTRUDERS
float e;
#endif
} ft_smoothed_float_t;
#endif
+1 -1
View File
@@ -71,7 +71,7 @@ opt_set MOTHERBOARD BOARD_RAMBO EXTRUDERS 0 TEMP_SENSOR_BED 1 TEMP_SENSOR_PROBE
DEFAULT_MAX_ACCELERATION '{ 3000, 3000, 100 }' \
MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \
AXIS_RELATIVE_MODES '{ false, false, false }'
opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER FIX_MOUNTED_PROBE Z_SAFE_HOMING FT_MOTION
opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER FIX_MOUNTED_PROBE Z_SAFE_HOMING FT_MOTION FTM_SMOOTHING
exec_test $1 $2 "Rambo with ZERO EXTRUDERS, heated bed, FT_MOTION" "$3"
#