Skip to content

Commit

Permalink
✨ Fixed-Time Motion with Input Shaping by Ulendo (MarlinFirmware#25394)
Browse files Browse the repository at this point in the history
Co-authored-by: Ulendo Alex <alex@ulendo.io>
  • Loading branch information
2 people authored and EvilGremlin committed Apr 8, 2023
1 parent 4f966b9 commit 353ae54
Show file tree
Hide file tree
Showing 15 changed files with 1,772 additions and 50 deletions.
48 changes: 47 additions & 1 deletion Marlin/Configuration_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,51 @@

#endif

// @section motion
// @section motion control

/**
* Fixed-time-based Motion Control -- EXPERIMENTAL
* Enable/disable and set parameters with G-code M493.
*/
//#define FT_MOTION
#if ENABLED(FT_MOTION)
#define FTM_DEFAULT_MODE ftMotionMode_ENABLED // Default mode of fixed time control. (Enums in ft_types.h)
#define FTM_DEFAULT_DYNFREQ_MODE dynFreqMode_DISABLED // Default mode of dynamic frequency calculation. (Enums in ft_types.h)
#define FTM_SHAPING_DEFAULT_X_FREQ 37.0f // (Hz) Default peak frequency used by input shapers.
#define FTM_SHAPING_DEFAULT_Y_FREQ 37.0f // (Hz) Default peak frequency used by input shapers.
#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.
#define FTM_SHAPING_ZETA 0.1f // Zeta used by input shapers.
#define FTM_SHAPING_V_TOL 0.05f // Vibration tolerance used by EI input shapers.

/**
* Advanced configuration
*/
#define FTM_BATCH_SIZE 100 // Batch size for trajectory generation;
// half the window size for Ulendo FBS.
#define FTM_FS 1000 // (Hz) Frequency for trajectory generation. (1 / FTM_TS)
#define FTM_TS 0.001f // (s) Time step for trajectory generation. (1 / FTM_FS)
#define FTM_STEPPER_FS 20000 // (Hz) Frequency for stepper I/O update.
#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_ZMAX 100 // Maximum delays for shaping functions (even numbers only!).
// Calculate as:
// 1/2 * (FTM_FS / FTM_MIN_SHAPE_FREQ) for ZV.
// (FTM_FS / FTM_MIN_SHAPE_FREQ) for ZVD, MZV.
// 3/2 * (FTM_FS / FTM_MIN_SHAPE_FREQ) for 2HEI.
// 2 * (FTM_FS / FTM_MIN_SHAPE_FREQ) for 3HEI.
#define FTM_STEPS_PER_UNIT_TIME 20 // Interpolated stepper commands per unit time.
// Calculate as (FTM_STEPPER_FS / FTM_FS).
#define FTM_CTS_COMPARE_VAL 10 // Comparison value used in interpolation algorithm.
// Calculate as (FTM_STEPS_PER_UNIT_TIME / 2).
// These values may be configured to adjust duration of loop().
#define FTM_STEPS_PER_LOOP 60 // Number of stepper commands to generate each loop().
#define FTM_POINTS_PER_LOOP 100 // Number of trajectory points to generate each loop().

// This value may be configured to adjust duration to consume the command buffer.
// Try increasing this value if stepper motion is not smooth.
#define FTM_STEPPERCMD_BUFF_SIZE 1000 // Size of the stepper command buffers.
#endif

/**
* Input Shaping -- EXPERIMENTAL
Expand Down Expand Up @@ -1125,6 +1169,8 @@
//#define SHAPING_MENU // Add a menu to the LCD to set shaping parameters.
#endif

// @section motion

#define AXIS_RELATIVE_MODES { false, false, false, false }

// Add a Duplicate option for well-separated conjoined nozzles
Expand Down
7 changes: 7 additions & 0 deletions Marlin/src/MarlinCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
#include "module/settings.h"
#include "module/stepper.h"
#include "module/temperature.h"
#if ENABLED(FT_MOTION)
#include "module/ft_motion.h"
#endif

#include "gcode/gcode.h"
#include "gcode/parser.h"
Expand Down Expand Up @@ -885,8 +888,12 @@ void idle(bool no_stepper_sleep/*=false*/) {
// Update the LVGL interface
TERN_(HAS_TFT_LVGL_UI, LV_TASK_HANDLER());

// Manage Fixed-time Motion Control
TERN_(FT_MOTION, fxdTiCtrl.loop());

IDLE_DONE:
TERN_(MARLIN_DEV_MODE, idle_depth--);

return;
}

Expand Down
282 changes: 282 additions & 0 deletions Marlin/src/gcode/feature/ft_motion/M493.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2023 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/MarlinConfig.h"

#if ENABLED(FT_MOTION)

#include "../../gcode.h"
#include "../../../module/ft_motion.h"

void say_shaping() {
SERIAL_ECHO_TERNARY(fxdTiCtrl.cfg_mode, "Fixed time controller ", "en", "dis", "abled");
if (fxdTiCtrl.cfg_mode == ftMotionMode_DISABLED || fxdTiCtrl.cfg_mode == ftMotionMode_ENABLED) {
SERIAL_ECHOLNPGM(".");
return;
}
#if HAS_X_AXIS
SERIAL_ECHOPGM(" with ");
switch (fxdTiCtrl.cfg_mode) {
default: break;
//case ftMotionMode_ULENDO_FBS: SERIAL_ECHOLNPGM("Ulendo FBS."); return;
case ftMotionMode_ZV: SERIAL_ECHOLNPGM("ZV"); break;
case ftMotionMode_ZVD: SERIAL_ECHOLNPGM("ZVD"); break;
case ftMotionMode_EI: SERIAL_ECHOLNPGM("EI"); break;
case ftMotionMode_2HEI: SERIAL_ECHOLNPGM("2 Hump EI"); break;
case ftMotionMode_3HEI: SERIAL_ECHOLNPGM("3 Hump EI"); break;
case ftMotionMode_MZV: SERIAL_ECHOLNPGM("MZV"); break;
//case ftMotionMode_DISCTF: SERIAL_ECHOLNPGM("discrete transfer functions"); break;
}
SERIAL_ECHOLNPGM(" shaping.");
#endif
}

/**
* M493: Set Fixed-time Motion Control parameters
*
* S<mode> Set the motion / shaping mode. Shaping requires an X axis, at the minimum.
* 0: NORMAL
* 1: FIXED-TIME
* 10: ZV
* 11: ZVD
* 12: EI
* 13: 2HEI
* 14: 3HEI
* 15: MZV
*
* P<bool> Enable (1) or Disable (0) Linear Advance pressure control
*
* K<gain> Set Linear Advance gain
*
* 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
*
* B<Hz> Set static/base frequency for the Y axis
* H<Hz> Set frequency scaling for the Y axis
*/
void GcodeSuite::M493() {
// Parse 'S' mode parameter.
if (parser.seenval('S')) {
const ftMotionMode_t val = (ftMotionMode_t)parser.value_byte();
switch (val) {
case ftMotionMode_DISABLED:
case ftMotionMode_ENABLED:
#if HAS_X_AXIS
case ftMotionMode_ZVD:
case ftMotionMode_2HEI:
case ftMotionMode_3HEI:
case ftMotionMode_MZV:
//case ftMotionMode_ULENDO_FBS:
//case ftMotionMode_DISCTF:
fxdTiCtrl.cfg_mode = val;
say_shaping();
break;
#endif
default:
SERIAL_ECHOLNPGM("?Invalid control mode [M] value.");
return;
}

switch (val) {
case ftMotionMode_ENABLED: fxdTiCtrl.reset(); break;
#if HAS_X_AXIS
case ftMotionMode_ZV:
case ftMotionMode_ZVD:
case ftMotionMode_EI:
case ftMotionMode_2HEI:
case ftMotionMode_3HEI:
case ftMotionMode_MZV:
fxdTiCtrl.updateShapingN(fxdTiCtrl.cfg_baseFreq[0] OPTARG(HAS_Y_AXIS, fxdTiCtrl.cfg_baseFreq[1]));
fxdTiCtrl.updateShapingA();
fxdTiCtrl.reset();
break;
//case ftMotionMode_ULENDO_FBS:
//case ftMotionMode_DISCTF:
#endif
default: break;
}
}

#if HAS_EXTRUDERS

// Pressure control (linear advance) parameter.
if (parser.seen('P')) {
const bool val = parser.value_bool();
fxdTiCtrl.cfg_linearAdvEna = val;
SERIAL_ECHO_TERNARY(val, "Pressure control: Linear Advance ", "en", "dis", "abled.\n");
}

// Pressure control (linear advance) gain parameter.
if (parser.seenval('K')) {
const float val = parser.value_float();
if (val >= 0.0f) {
fxdTiCtrl.cfg_linearAdvK = val;
SERIAL_ECHOPGM("Pressure control: Linear Advance gain set to: ");
SERIAL_ECHO_F(val, 5);
SERIAL_ECHOLNPGM(".");
}
else { // Value out of range.
SERIAL_ECHOLNPGM("Pressure control: Linear Advance gain out of range.");
}
}

#endif // HAS_EXTRUDERS

#if HAS_Z_AXIS || HAS_EXTRUDERS

// Dynamic frequency mode parameter.
if (parser.seenval('D')) {
if (WITHIN(fxdTiCtrl.cfg_mode, 10U, 19U)) {
const dynFreqMode_t val = dynFreqMode_t(parser.value_byte());
switch (val) {
case dynFreqMode_DISABLED:
fxdTiCtrl.cfg_dynFreqMode = val;
SERIAL_ECHOLNPGM("Dynamic frequency mode disabled.");
break;
#if HAS_Z_AXIS
case dynFreqMode_Z_BASED:
fxdTiCtrl.cfg_dynFreqMode = val;
SERIAL_ECHOLNPGM("Z-based Dynamic Frequency Mode.");
break;
#endif
#if HAS_EXTRUDERS
case dynFreqMode_MASS_BASED:
fxdTiCtrl.cfg_dynFreqMode = val;
SERIAL_ECHOLNPGM("Mass-based Dynamic Frequency Mode.");
break;
#endif
default:
SERIAL_ECHOLNPGM("?Invalid Dynamic Frequency Mode [D] value.");
break;
}
}
else {
SERIAL_ECHOLNPGM("Incompatible shaper for [D] Dynamic Frequency mode.");
}
}

#endif // HAS_Z_AXIS || HAS_EXTRUDERS

#if HAS_X_AXIS

// Parse frequency parameter (X axis).
if (parser.seenval('A')) {
if (WITHIN(fxdTiCtrl.cfg_mode, 10U, 19U)) {
const float val = parser.value_float();
const bool frequencyInRange = WITHIN(val, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2);
// TODO: Frequency minimum is dependent on the shaper used; the above check isn't always correct.
if (frequencyInRange) {
fxdTiCtrl.cfg_baseFreq[0] = val;
fxdTiCtrl.updateShapingN(fxdTiCtrl.cfg_baseFreq[0] OPTARG(HAS_Y_AXIS, fxdTiCtrl.cfg_baseFreq[1]));
fxdTiCtrl.reset();
if (fxdTiCtrl.cfg_dynFreqMode) { SERIAL_ECHOPGM("Compensator base dynamic frequency (X/A axis) set to:"); }
else { SERIAL_ECHOPGM("Compensator static frequency (X/A axis) set to: "); }
SERIAL_ECHO_F( fxdTiCtrl.cfg_baseFreq[0], 2 );
SERIAL_ECHOLNPGM(".");
}
else { // Frequency out of range.
SERIAL_ECHOLNPGM("Invalid [A] frequency value.");
}
}
else { // Mode doesn't use frequency.
SERIAL_ECHOLNPGM("Incompatible mode for [A] frequency.");
}
}

#if HAS_Z_AXIS || HAS_EXTRUDERS
// Parse frequency scaling parameter (X axis).
if (parser.seenval('F')) {
const bool modeUsesDynFreq = (
TERN0(HAS_Z_AXIS, fxdTiCtrl.cfg_dynFreqMode == dynFreqMode_Z_BASED)
|| TERN0(HAS_EXTRUDERS, fxdTiCtrl.cfg_dynFreqMode == dynFreqMode_MASS_BASED)
);

if (modeUsesDynFreq) {
const float val = parser.value_float();
fxdTiCtrl.cfg_dynFreqK[0] = val;
SERIAL_ECHOPGM("Frequency scaling (X/A axis) set to: ");
SERIAL_ECHO_F(fxdTiCtrl.cfg_dynFreqK[0], 8);
SERIAL_ECHOLNPGM(".");
}
else {
SERIAL_ECHOLNPGM("Incompatible mode for [F] frequency scaling.");
}
}
#endif // HAS_Z_AXIS || HAS_EXTRUDERS

#endif // HAS_X_AXIS

#if HAS_Y_AXIS

// Parse frequency parameter (Y axis).
if (parser.seenval('B')) {
if (WITHIN(fxdTiCtrl.cfg_mode, 10U, 19U)) {
const float val = parser.value_float();
const bool frequencyInRange = WITHIN(val, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2);
if (frequencyInRange) {
fxdTiCtrl.cfg_baseFreq[1] = val;
fxdTiCtrl.updateShapingN(fxdTiCtrl.cfg_baseFreq[0] OPTARG(HAS_Y_AXIS, fxdTiCtrl.cfg_baseFreq[1]));
fxdTiCtrl.reset();
if (fxdTiCtrl.cfg_dynFreqMode) { SERIAL_ECHOPGM("Compensator base dynamic frequency (Y/B axis) set to:"); }
else { SERIAL_ECHOPGM("Compensator static frequency (Y/B axis) set to: "); }
SERIAL_ECHO_F( fxdTiCtrl.cfg_baseFreq[1], 2 );
SERIAL_ECHOLNPGM(".");
}
else { // Frequency out of range.
SERIAL_ECHOLNPGM("Invalid frequency [B] value.");
}
}
else { // Mode doesn't use frequency.
SERIAL_ECHOLNPGM("Incompatible mode for [B] frequency.");
}
}

#if HAS_Z_AXIS || HAS_EXTRUDERS
// Parse frequency scaling parameter (Y axis).
if (parser.seenval('H')) {
const bool modeUsesDynFreq = (
TERN0(HAS_Z_AXIS, fxdTiCtrl.cfg_dynFreqMode == dynFreqMode_Z_BASED)
|| TERN0(HAS_EXTRUDERS, fxdTiCtrl.cfg_dynFreqMode == dynFreqMode_MASS_BASED)
);

if (modeUsesDynFreq) {
const float val = parser.value_float();
fxdTiCtrl.cfg_dynFreqK[1] = val;
SERIAL_ECHOPGM("Frequency scaling (Y/B axis) set to: ");
SERIAL_ECHO_F(val, 8);
SERIAL_ECHOLNPGM(".");
}
else {
SERIAL_ECHOLNPGM("Incompatible mode for [H] frequency scaling.");
}
}
#endif // HAS_Z_AXIS || HAS_EXTRUDERS

#endif // HAS_Y_AXIS
}

#endif // FT_MOTION
6 changes: 5 additions & 1 deletion Marlin/src/gcode/gcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
case 486: M486(); break; // M486: Identify and cancel objects
#endif

#if ENABLED(FT_MOTION)
case 493: M493(); break; // M493: Fixed-Time Motion control
#endif

case 500: M500(); break; // M500: Store settings in EEPROM
case 501: M501(); break; // M501: Read settings from EEPROM
case 502: M502(); break; // M502: Revert to default settings
Expand Down Expand Up @@ -934,7 +938,7 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
#endif

#if HAS_ZV_SHAPING
case 593: M593(); break; // M593: Set Input Shaping parameters
case 593: M593(); break; // M593: Input Shaping control
#endif

#if ENABLED(ADVANCED_PAUSE_FEATURE)
Expand Down
4 changes: 4 additions & 0 deletions Marlin/src/gcode/gcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,10 @@ class GcodeSuite {
static void M486();
#endif

#if ENABLED(FT_MOTION)
static void M493();
#endif

static void M500();
static void M501();
static void M502();
Expand Down
Loading

0 comments on commit 353ae54

Please sign in to comment.