Skip to content

Commit f0bc427

Browse files
πŸ§‘β€πŸ’» FT Motion: Individual axis shaping, new buffer management (#26848)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
1 parent 20a704b commit f0bc427

File tree

13 files changed

+502
-652
lines changed

13 files changed

+502
-652
lines changed

β€ŽMarlin/Configuration_adv.h

+6-8
Original file line numberDiff line numberDiff line change
@@ -1118,11 +1118,14 @@
11181118
/**
11191119
* Fixed-time-based Motion Control -- EXPERIMENTAL
11201120
* Enable/disable and set parameters with G-code M493.
1121+
* See ft_types.h for named values used by FTM options.
11211122
*/
11221123
//#define FT_MOTION
11231124
#if ENABLED(FT_MOTION)
1124-
#define FTM_DEFAULT_MODE ftMotionMode_DISABLED // Default mode of fixed time control. (Enums in ft_types.h)
1125-
#define FTM_DEFAULT_DYNFREQ_MODE dynFreqMode_DISABLED // Default mode of dynamic frequency calculation. (Enums in ft_types.h)
1125+
//#define FTM_IS_DEFAULT_MOTION // Use FT Motion as the factory default?
1126+
#define FTM_DEFAULT_DYNFREQ_MODE dynFreqMode_DISABLED // Default mode of dynamic frequency calculation. (DISABLED, Z_BASED, MASS_BASED)
1127+
#define FTM_DEFAULT_SHAPER_X ftMotionShaper_NONE // Default shaper mode on X axis (NONE, ZV, ZVD, ZVDD, ZVDDD, EI, 2HEI, 3HEI, MZV)
1128+
#define FTM_DEFAULT_SHAPER_Y ftMotionShaper_NONE // Default shaper mode on Y axis
11261129
#define FTM_SHAPING_DEFAULT_X_FREQ 37.0f // (Hz) Default peak frequency used by input shapers
11271130
#define FTM_SHAPING_DEFAULT_Y_FREQ 37.0f // (Hz) Default peak frequency used by input shapers
11281131
#define FTM_LINEAR_ADV_DEFAULT_ENA false // Default linear advance enable (true) or disable (false)
@@ -1149,18 +1152,13 @@
11491152
#define FTM_FS 1000 // (Hz) Frequency for trajectory generation. (Reciprocal of FTM_TS)
11501153
#define FTM_TS 0.001f // (s) Time step for trajectory generation. (Reciprocal of FTM_FS)
11511154

1152-
// These values may be configured to adjust the duration of loop().
1153-
#define FTM_STEPS_PER_LOOP 60 // Number of stepper commands to generate each loop()
1154-
#define FTM_POINTS_PER_LOOP 100 // Number of trajectory points to generate each loop()
1155-
11561155
#if DISABLED(COREXY)
11571156
#define FTM_STEPPER_FS 20000 // (Hz) Frequency for stepper I/O update
11581157

11591158
// Use this to adjust the time required to consume the command buffer.
11601159
// Try increasing this value if stepper motion is choppy.
11611160
#define FTM_STEPPERCMD_BUFF_SIZE 3000 // Size of the stepper command buffers
1162-
// (FTM_STEPS_PER_LOOP * FTM_POINTS_PER_LOOP) is a good start
1163-
// If you run out of memory, fall back to 3000 and increase progressively
1161+
11641162
#else
11651163
// CoreXY motion needs a larger buffer size. These values are based on our testing.
11661164
#define FTM_STEPPER_FS 30000

β€ŽMarlin/src/HAL/ESP32/i2s.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ void stepperTask(void *parameter) {
152152
xQueueReceive(dma.queue, &dma.current, portMAX_DELAY);
153153
dma.rw_pos = 0;
154154

155-
const bool using_ftMotion = TERN0(FT_MOTION, ftMotion.cfg.mode);
155+
const bool using_ftMotion = TERN0(FT_MOTION, ftMotion.cfg.active);
156156

157157
while (dma.rw_pos < DMA_SAMPLE_COUNT) {
158158

β€ŽMarlin/src/gcode/feature/ft_motion/M493.cpp

+110-72
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,59 @@
2828
#include "../../../module/ft_motion.h"
2929
#include "../../../module/stepper.h"
3030

31+
void say_shaper_type(const AxisEnum a) {
32+
SERIAL_ECHOPGM(" axis ");
33+
switch (ftMotion.cfg.shaper[a]) {
34+
default: break;
35+
case ftMotionShaper_ZV: SERIAL_ECHOPGM("ZV"); break;
36+
case ftMotionShaper_ZVD: SERIAL_ECHOPGM("ZVD"); break;
37+
case ftMotionShaper_ZVDD: SERIAL_ECHOPGM("ZVDD"); break;
38+
case ftMotionShaper_ZVDDD: SERIAL_ECHOPGM("ZVDDD"); break;
39+
case ftMotionShaper_EI: SERIAL_ECHOPGM("EI"); break;
40+
case ftMotionShaper_2HEI: SERIAL_ECHOPGM("2 Hump EI"); break;
41+
case ftMotionShaper_3HEI: SERIAL_ECHOPGM("3 Hump EI"); break;
42+
case ftMotionShaper_MZV: SERIAL_ECHOPGM("MZV"); break;
43+
}
44+
SERIAL_ECHOPGM(" shaping");
45+
}
46+
47+
#if CORE_IS_XY || CORE_IS_XZ
48+
#define AXIS_0_NAME "A"
49+
#else
50+
#define AXIS_0_NAME "X"
51+
#endif
52+
#if CORE_IS_XY || CORE_IS_YZ
53+
#define AXIS_1_NAME "B"
54+
#else
55+
#define AXIS_1_NAME "Y"
56+
#endif
57+
3158
void say_shaping() {
3259
// FT Enabled
33-
SERIAL_ECHO_TERNARY(ftMotion.cfg.mode, "Fixed-Time Motion ", "en", "dis", "abled");
60+
SERIAL_ECHO_TERNARY(ftMotion.cfg.active, "Fixed-Time Motion ", "en", "dis", "abled");
3461

3562
// FT Shaping
3663
#if HAS_X_AXIS
37-
if (ftMotion.cfg.mode > ftMotionMode_ENABLED) {
38-
SERIAL_ECHOPGM(" with ");
39-
switch (ftMotion.cfg.mode) {
40-
default: break;
41-
case ftMotionMode_ZV: SERIAL_ECHOPGM("ZV"); break;
42-
case ftMotionMode_ZVD: SERIAL_ECHOPGM("ZVD"); break;
43-
case ftMotionMode_ZVDD: SERIAL_ECHOPGM("ZVDD"); break;
44-
case ftMotionMode_ZVDDD: SERIAL_ECHOPGM("ZVDDD"); break;
45-
case ftMotionMode_EI: SERIAL_ECHOPGM("EI"); break;
46-
case ftMotionMode_2HEI: SERIAL_ECHOPGM("2 Hump EI"); break;
47-
case ftMotionMode_3HEI: SERIAL_ECHOPGM("3 Hump EI"); break;
48-
case ftMotionMode_MZV: SERIAL_ECHOPGM("MZV"); break;
49-
//case ftMotionMode_DISCTF: SERIAL_ECHOPGM("discrete transfer functions"); break;
50-
//case ftMotionMode_ULENDO_FBS: SERIAL_ECHOPGM("Ulendo FBS."); return;
51-
}
52-
SERIAL_ECHOPGM(" shaping");
64+
if (CMPNSTR_HAS_SHAPER(X_AXIS)) {
65+
SERIAL_ECHOPGM(" with " AXIS_0_NAME);
66+
say_shaper_type(X_AXIS);
67+
}
68+
#endif
69+
#if HAS_Y_AXIS
70+
if (CMPNSTR_HAS_SHAPER(Y_AXIS)) {
71+
SERIAL_ECHOPGM(" and with " AXIS_1_NAME);
72+
say_shaper_type(Y_AXIS);
5373
}
5474
#endif
75+
5576
SERIAL_ECHOLNPGM(".");
5677

5778
const bool z_based = TERN0(HAS_DYNAMIC_FREQ_MM, ftMotion.cfg.dynFreqMode == dynFreqMode_Z_BASED),
5879
g_based = TERN0(HAS_DYNAMIC_FREQ_G, ftMotion.cfg.dynFreqMode == dynFreqMode_MASS_BASED),
5980
dynamic = z_based || g_based;
6081

6182
// FT Dynamic Frequency Mode
62-
if (ftMotion.cfg.modeHasShaper()) {
83+
if (CMPNSTR_HAS_SHAPER(X_AXIS) || CMPNSTR_HAS_SHAPER(Y_AXIS)) {
6384
#if HAS_DYNAMIC_FREQ
6485
SERIAL_ECHOPGM("Dynamic Frequency Mode ");
6586
switch (ftMotion.cfg.dynFreqMode) {
@@ -76,7 +97,7 @@ void say_shaping() {
7697
#endif
7798

7899
#if HAS_X_AXIS
79-
SERIAL_ECHO_TERNARY(dynamic, "X/A ", "base dynamic", "static", " compensator frequency: ");
100+
SERIAL_ECHO_TERNARY(dynamic, AXIS_0_NAME " ", "base dynamic", "static", " shaper frequency: ");
80101
SERIAL_ECHO(p_float_t(ftMotion.cfg.baseFreq[X_AXIS], 2), F("Hz"));
81102
#if HAS_DYNAMIC_FREQ
82103
if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(ftMotion.cfg.dynFreqK[X_AXIS], 2), F("Hz/"), z_based ? F("mm") : F("g"));
@@ -85,7 +106,7 @@ void say_shaping() {
85106
#endif
86107

87108
#if HAS_Y_AXIS
88-
SERIAL_ECHO_TERNARY(dynamic, "Y/B ", "base dynamic", "static", " compensator frequency: ");
109+
SERIAL_ECHO_TERNARY(dynamic, AXIS_1_NAME " ", "base dynamic", "static", " shaper frequency: ");
89110
SERIAL_ECHO(p_float_t(ftMotion.cfg.baseFreq[Y_AXIS], 2), F(" Hz"));
90111
#if HAS_DYNAMIC_FREQ
91112
if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(ftMotion.cfg.dynFreqK[Y_AXIS], 2), F("Hz/"), z_based ? F("mm") : F("g"));
@@ -108,7 +129,7 @@ void GcodeSuite::M493_report(const bool forReplay/*=true*/) {
108129

109130
report_heading_etc(forReplay, F(STR_FT_MOTION));
110131
const ft_config_t &c = ftMotion.cfg;
111-
SERIAL_ECHOPGM(" M493 S", c.mode);
132+
SERIAL_ECHOPGM(" M493 S", c.active);
112133
#if HAS_X_AXIS
113134
SERIAL_ECHOPGM(" A", c.baseFreq[X_AXIS]);
114135
#if HAS_Y_AXIS
@@ -133,18 +154,21 @@ void GcodeSuite::M493_report(const bool forReplay/*=true*/) {
133154
/**
134155
* M493: Set Fixed-time Motion Control parameters
135156
*
136-
* S<mode> Set the motion / shaping mode. Shaping requires an X axis, at the minimum.
157+
* S<bool> Set Fixed-Time motion mode on or off.
158+
* 0: Fixed-Time Motion OFF (Standard Motion)
159+
* 1: Fixed-Time Motion ON
137160
*
138-
* 0: Standard Motion
139-
* 1: Fixed-Time Motion
140-
* 10: ZV : Zero Vibration
141-
* 11: ZVD : Zero Vibration and Derivative
142-
* 12: ZVDD : Zero Vibration, Derivative, and Double Derivative
143-
* 13: ZVDDD : Zero Vibration, Derivative, Double Derivative, and Triple Derivative
144-
* 14: EI : Extra-Intensive
145-
* 15: 2HEI : 2-Hump Extra-Intensive
146-
* 16: 3HEI : 3-Hump Extra-Intensive
147-
* 17: MZV : Mass-based Zero Vibration
161+
* X/Y<mode> Set the vibration compensator [input shaper] mode for X / Y axis.
162+
* Users / slicers must remember to set the mode for both axes!
163+
* 0: NONE : No input shaper
164+
* 1: ZV : Zero Vibration
165+
* 2: ZVD : Zero Vibration and Derivative
166+
* 3: ZVDD : Zero Vibration, Derivative, and Double Derivative
167+
* 4: ZVDDD : Zero Vibration, Derivative, Double Derivative, and Triple Derivative
168+
* 5: EI : Extra-Intensive
169+
* 6: 2HEI : 2-Hump Extra-Intensive
170+
* 7: 3HEI : 3-Hump Extra-Intensive
171+
* 8: MZV : Mass-based Zero Vibration
148172
*
149173
* P<bool> Enable (1) or Disable (0) Linear Advance pressure control
150174
*
@@ -166,40 +190,56 @@ void GcodeSuite::M493_report(const bool forReplay/*=true*/) {
166190
* R 0.00 Set the vibration tolerance for the Y axis
167191
*/
168192
void GcodeSuite::M493() {
169-
struct { bool update_n:1, update_a:1, reset_ft:1, report_h:1; } flag = { false };
193+
struct { bool update:1, reset_ft:1, report_h:1; } flag = { false };
170194

171195
if (!parser.seen_any())
172196
flag.report_h = true;
173197

174198
// Parse 'S' mode parameter.
175-
if (parser.seenval('S')) {
176-
const ftMotionMode_t newmm = (ftMotionMode_t)parser.value_byte();
177-
178-
if (newmm != ftMotion.cfg.mode) {
179-
switch (newmm) {
180-
default: SERIAL_ECHOLNPGM("?Invalid control mode [S] value."); return;
181-
#if HAS_X_AXIS
182-
case ftMotionMode_ZV:
183-
case ftMotionMode_ZVD:
184-
case ftMotionMode_ZVDD:
185-
case ftMotionMode_ZVDDD:
186-
case ftMotionMode_EI:
187-
case ftMotionMode_2HEI:
188-
case ftMotionMode_3HEI:
189-
case ftMotionMode_MZV:
190-
//case ftMotionMode_ULENDO_FBS:
191-
//case ftMotionMode_DISCTF:
192-
flag.update_n = flag.update_a = true;
193-
#endif
194-
case ftMotionMode_DISABLED: flag.reset_ft = true;
195-
case ftMotionMode_ENABLED:
196-
ftMotion.cfg.mode = newmm;
197-
flag.report_h = true;
199+
if (parser.seen('S')) {
200+
const bool active = parser.value_bool();
201+
202+
if (active != ftMotion.cfg.active) {
203+
switch (active) {
204+
case false: flag.reset_ft = true;
205+
case true: flag.report_h = true;
206+
ftMotion.cfg.active = active;
198207
break;
199208
}
200209
}
201210
}
202211

212+
#if HAS_X_AXIS
213+
auto set_shaper = [&](const AxisEnum axis, const char c) {
214+
const ftMotionShaper_t newsh = (ftMotionShaper_t)parser.value_byte();
215+
if (newsh != ftMotion.cfg.shaper[axis]) {
216+
switch (newsh) {
217+
default: SERIAL_ECHOLNPGM("?Invalid [", C(c), "] shaper."); return true;
218+
case ftMotionShaper_NONE:
219+
case ftMotionShaper_ZV:
220+
case ftMotionShaper_ZVD:
221+
case ftMotionShaper_ZVDD:
222+
case ftMotionShaper_ZVDDD:
223+
case ftMotionShaper_EI:
224+
case ftMotionShaper_2HEI:
225+
case ftMotionShaper_3HEI:
226+
case ftMotionShaper_MZV:
227+
ftMotion.cfg.shaper[axis] = newsh;
228+
flag.update = flag.report_h = true;
229+
break;
230+
}
231+
}
232+
return false;
233+
};
234+
235+
if (parser.seenval('X') && set_shaper(X_AXIS, 'X')) return; // Parse 'X' mode parameter
236+
237+
#if HAS_Y_AXIS
238+
if (parser.seenval('Y') && set_shaper(Y_AXIS, 'Y')) return; // Parse 'Y' mode parameter
239+
#endif
240+
241+
#endif // HAS_X_AXIS
242+
203243
#if HAS_EXTRUDERS
204244

205245
// Pressure control (linear advance) parameter.
@@ -227,7 +267,7 @@ void GcodeSuite::M493() {
227267

228268
// Dynamic frequency mode parameter.
229269
if (parser.seenval('D')) {
230-
if (ftMotion.cfg.modeHasShaper()) {
270+
if (CMPNSTR_HAS_SHAPER(X_AXIS) || CMPNSTR_HAS_SHAPER(Y_AXIS)) {
231271
const dynFreqMode_t val = dynFreqMode_t(parser.value_byte());
232272
switch (val) {
233273
#if HAS_DYNAMIC_FREQ_MM
@@ -261,12 +301,12 @@ void GcodeSuite::M493() {
261301

262302
// Parse frequency parameter (X axis).
263303
if (parser.seenval('A')) {
264-
if (ftMotion.cfg.modeHasShaper()) {
304+
if (CMPNSTR_HAS_SHAPER(X_AXIS)) {
265305
const float val = parser.value_float();
266306
// TODO: Frequency minimum is dependent on the shaper used; the above check isn't always correct.
267307
if (WITHIN(val, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2)) {
268308
ftMotion.cfg.baseFreq[X_AXIS] = val;
269-
flag.update_n = flag.reset_ft = flag.report_h = true;
309+
flag.update = flag.reset_ft = flag.report_h = true;
270310
}
271311
else // Frequency out of range.
272312
SERIAL_ECHOLNPGM("Invalid [", C('A'), "] frequency value.");
@@ -290,10 +330,10 @@ void GcodeSuite::M493() {
290330
// Parse zeta parameter (X axis).
291331
if (parser.seenval('I')) {
292332
const float val = parser.value_float();
293-
if (ftMotion.cfg.modeHasShaper()) {
333+
if (CMPNSTR_HAS_SHAPER(X_AXIS)) {
294334
if (WITHIN(val, 0.01f, 1.0f)) {
295335
ftMotion.cfg.zeta[0] = val;
296-
flag.update_n = flag.update_a = true;
336+
flag.update = true;
297337
}
298338
else
299339
SERIAL_ECHOLNPGM("Invalid X zeta [", C('I'), "] value."); // Zeta out of range.
@@ -305,10 +345,10 @@ void GcodeSuite::M493() {
305345
// Parse vtol parameter (X axis).
306346
if (parser.seenval('Q')) {
307347
const float val = parser.value_float();
308-
if (ftMotion.cfg.modeHasShaper() && IS_EI_MODE(ftMotion.cfg.mode)) {
348+
if (CMPNSTR_IS_EISHAPER(X_AXIS)) {
309349
if (WITHIN(val, 0.00f, 1.0f)) {
310350
ftMotion.cfg.vtol[0] = val;
311-
flag.update_a = true;
351+
flag.update = true;
312352
}
313353
else
314354
SERIAL_ECHOLNPGM("Invalid X vtol [", C('Q'), "] value."); // VTol out of range.
@@ -323,11 +363,11 @@ void GcodeSuite::M493() {
323363

324364
// Parse frequency parameter (Y axis).
325365
if (parser.seenval('B')) {
326-
if (ftMotion.cfg.modeHasShaper()) {
366+
if (CMPNSTR_HAS_SHAPER(Y_AXIS)) {
327367
const float val = parser.value_float();
328368
if (WITHIN(val, FTM_MIN_SHAPE_FREQ, (FTM_FS) / 2)) {
329369
ftMotion.cfg.baseFreq[Y_AXIS] = val;
330-
flag.update_n = flag.reset_ft = flag.report_h = true;
370+
flag.update = flag.reset_ft = flag.report_h = true;
331371
}
332372
else // Frequency out of range.
333373
SERIAL_ECHOLNPGM("Invalid frequency [", C('B'), "] value.");
@@ -351,10 +391,10 @@ void GcodeSuite::M493() {
351391
// Parse zeta parameter (Y axis).
352392
if (parser.seenval('J')) {
353393
const float val = parser.value_float();
354-
if (ftMotion.cfg.modeHasShaper()) {
394+
if (CMPNSTR_HAS_SHAPER(Y_AXIS)) {
355395
if (WITHIN(val, 0.01f, 1.0f)) {
356396
ftMotion.cfg.zeta[1] = val;
357-
flag.update_n = flag.update_a = true;
397+
flag.update = true;
358398
}
359399
else
360400
SERIAL_ECHOLNPGM("Invalid Y zeta [", C('J'), "] value."); // Zeta Out of range
@@ -366,10 +406,10 @@ void GcodeSuite::M493() {
366406
// Parse vtol parameter (Y axis).
367407
if (parser.seenval('R')) {
368408
const float val = parser.value_float();
369-
if (ftMotion.cfg.modeHasShaper() && IS_EI_MODE(ftMotion.cfg.mode)) {
409+
if (CMPNSTR_IS_EISHAPER(Y_AXIS)) {
370410
if (WITHIN(val, 0.00f, 1.0f)) {
371411
ftMotion.cfg.vtol[1] = val;
372-
flag.update_a = true;
412+
flag.update = true;
373413
}
374414
else
375415
SERIAL_ECHOLNPGM("Invalid Y vtol [", C('R'), "] value."); // VTol out of range.
@@ -382,9 +422,7 @@ void GcodeSuite::M493() {
382422

383423
planner.synchronize();
384424

385-
if (flag.update_n) ftMotion.refreshShapingN();
386-
387-
if (flag.update_a) ftMotion.updateShapingA();
425+
if (flag.update) ftMotion.update_shaping_params();
388426

389427
if (flag.reset_ft) {
390428
stepper.ftMotion_syncPosition();

β€ŽMarlin/src/inc/SanityCheck.h

+9
Original file line numberDiff line numberDiff line change
@@ -4350,6 +4350,15 @@ static_assert(_PLUS_TEST(3), "DEFAULT_MAX_ACCELERATION values must be positive."
43504350
#elif DISABLED(FTM_UNIFIED_BWS)
43514351
#error "FT_MOTION requires FTM_UNIFIED_BWS to be enabled because FBS is not yet implemented."
43524352
#endif
4353+
#if !HAS_X_AXIS
4354+
static_assert(FTM_DEFAULT_X_COMPENSATOR != ftMotionShaper_NONE, "Without any linear axes FTM_DEFAULT_X_COMPENSATOR must be ftMotionShaper_NONE.");
4355+
#endif
4356+
#if HAS_DYNAMIC_FREQ_MM
4357+
static_assert(FTM_DEFAULT_DYNFREQ_MODE != dynFreqMode_Z_BASED, "dynFreqMode_Z_BASED requires a Z axis.");
4358+
#endif
4359+
#if HAS_DYNAMIC_FREQ_G
4360+
static_assert(FTM_DEFAULT_DYNFREQ_MODE != dynFreqMode_MASS_BASED, "dynFreqMode_MASS_BASED requires an X axis and an extruder.");
4361+
#endif
43534362
#endif
43544363

43554364
// Multi-Stepping Limit

β€ŽMarlin/src/lcd/language/language_en.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ namespace LanguageNarrow_en {
814814
LSTR MSG_BACKLASH_SMOOTHING = _UxGT("Smoothing");
815815

816816
LSTR MSG_FIXED_TIME_MOTION = _UxGT("Fixed-Time Motion");
817-
LSTR MSG_FTM_MODE = _UxGT("Motion Mode:");
817+
LSTR MSG_FTM_CMPN_MODE = _UxGT("@ Comp. Mode:");
818818
LSTR MSG_FTM_ZV = _UxGT("ZV");
819819
LSTR MSG_FTM_ZVD = _UxGT("ZVD");
820820
LSTR MSG_FTM_ZVDD = _UxGT("ZVDD");

0 commit comments

Comments
Β (0)