FTM_RESONANCE_TEST (#28158)

This commit is contained in:
narno2202
2025-11-08 22:16:06 +01:00
committed by GitHub
parent f48aa53d7c
commit 667aa15b87
19 changed files with 512 additions and 16 deletions
+1
View File
@@ -1177,6 +1177,7 @@
#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_RESONANCE_TEST // Sine sweep motion for resonance study
//#define FTM_SMOOTHING // Smoothing can reduce artifacts and make steppers quieter
// on sharp corners, but too much will round corners.
#if ENABLED(FTM_SMOOTHING)
+1 -1
View File
@@ -867,7 +867,7 @@ struct XYZEval {
FI void set(const XYZval<T> &pxyz, const T pe) { set(pxyz); e = pe; }
FI void set(LOGICAL_AXIS_ARGS_LC(const T)) { LOGICAL_AXIS_CODE(_e = e, a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
#if DISTINCT_AXES > LOGICAL_AXES
FI void set(const T (&arr)[DISTINCT_AXES]) { LOGICAL_AXIS_CODE(e = arr[LOGICAL_AXES-1], x = arr[0], y = arr[1], z = arr[2], i = arr[3], j = arr[4], k = arr[5], u = arr[6], v = arr[7], w = arr[8]); }
FI void set(const T (&arr)[DISTINCT_AXES], const uint8_t eindex) { LOGICAL_AXIS_CODE(e = arr[LOGICAL_AXES-1 + eindex], x = arr[0], y = arr[1], z = arr[2], i = arr[3], j = arr[4], k = arr[5], u = arr[6], v = arr[7], w = arr[8]); }
#endif
#endif
+20 -1
View File
@@ -33,6 +33,9 @@
// Static data members
bool EmergencyParser::killed_by_M112, // = false
EmergencyParser::quickstop_by_M410,
#if ENABLED(FTM_RESONANCE_TEST)
EmergencyParser::rt_stop_by_M496, // = false
#endif
#if HAS_MEDIA
EmergencyParser::sd_abort_by_M524,
#endif
@@ -147,9 +150,22 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) {
case EP_M10: state = (c == '8') ? EP_M108 : EP_IGNORE; break;
case EP_M11: state = (c == '2') ? EP_M112 : EP_IGNORE; break;
case EP_M4: state = (c == '1') ? EP_M41 : EP_IGNORE; break;
case EP_M4:
switch (c) {
case '1' :state = EP_M41; break;
#if ENABLED(FT_MOTION_RESONANCE_TEST)
case '9': state = EP_M49; break;
#endif
default: state = EP_IGNORE;
}
break;
case EP_M41: state = (c == '0') ? EP_M410 : EP_IGNORE; break;
#if ENABLED(FTM_RESONANCE_TEST)
case EP_M49: state = (c == '6') ? EP_M496 : EP_IGNORE; break;
#endif
#if HAS_MEDIA
case EP_M5: state = (c == '2') ? EP_M52 : EP_IGNORE; break;
case EP_M52: state = (c == '4') ? EP_M524 : EP_IGNORE; break;
@@ -195,6 +211,9 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) {
case EP_M108: wait_for_user = wait_for_heatup = false; break;
case EP_M112: killed_by_M112 = true; break;
case EP_M410: quickstop_by_M410 = true; break;
#if ENABLED(FTM_RESONANCE_TEST)
case EP_M496: rt_stop_by_M496 = true; break;
#endif
#if ENABLED(EP_BABYSTEPPING)
case EP_M293: babystep.ep_babysteps++; break;
case EP_M294: babystep.ep_babysteps--; break;
+7
View File
@@ -43,6 +43,9 @@ public:
#if HAS_MEDIA
EP_M5, EP_M52, EP_M524,
#endif
#if ENABLED(FTM_RESONANCE_TEST)
EP_M49, EP_M496,
#endif
#if ENABLED(EP_BABYSTEPPING)
EP_M2, EP_M29, EP_M293, EP_M294,
#endif
@@ -64,6 +67,10 @@ public:
static bool killed_by_M112;
static bool quickstop_by_M410;
#if ENABLED(FTM_RESONANCE_TEST)
static bool rt_stop_by_M496;
#endif
#if HAS_MEDIA
static bool sd_abort_by_M524;
#endif
@@ -0,0 +1,186 @@
/**
* 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/MarlinConfig.h"
#if ENABLED(FTM_RESONANCE_TEST)
#include "../../gcode.h"
#include "../../../module/ft_motion.h"
#include "../../../module/ft_motion/resonance_generator.h"
void say_resonance_test() {
const ftm_resonance_test_params_t &p = ftMotion.rtg.rt_params;
SERIAL_ECHO_START();
SERIAL_ECHOLN(F("M495 "), F("Resonance Test"));
SERIAL_ECHOLNPGM(" Axis: ", p.axis == NO_AXIS_ENUM ? C('-') : C(AXIS_CHAR(p.axis)));
SERIAL_ECHOLNPGM(" Freq Range (F..T): ", p.min_freq, " .. ", p.max_freq, " Hz");
SERIAL_ECHOLNPGM(" Octave Duration (O): ", p.octave_duration, " s");
SERIAL_ECHOLNPGM(" Accel/Hz (A): ", p.accel_per_hz);
}
/**
* M495: Configure and run the resonance test.
* With no parameters report the current settings.
*
* Parameters:
* A<accel/Hz> Accel per Hz. (Default 60)
* F<Hz> Start frequency. (Default 5.0)
* S Start the test.
* T<Hz> End frequency. (Default 100.0f)
* O<float> Octave duration for logarithmic progression
* C<int> Amplitude correction factor. (Default 5)
* X Flag to select the X axis.
* Y Flag to select the Y axis.
* Z Flag to select the Z axis.
* H<float> Get the Resonance Frequency from Timeline value. (Default 0)
*
* Examples:
* M495 S : Start the test with default or last-used parameters
* M495 X S : Start the test on the X axis with default or last-used parameters
* M495 H<val> : Get Resonance Frequency from Timeline value
*
*/
void GcodeSuite::M495() {
if (!parser.seen_any()) return say_resonance_test();
ftm_resonance_test_params_t &p = ftMotion.rtg.rt_params;
const bool seenX = parser.seen_test('X'), seenY = parser.seen_test('Y'), seenZ = parser.seen_test('Z');
if (seenX + seenY + seenZ == 1) {
const AxisEnum a = seenX ? X_AXIS : seenY ? Y_AXIS : Z_AXIS;
p.axis = a;
SERIAL_ECHOLN(C(AXIS_CHAR(a)), F("-axis selected"), F(" for "), F("Resonance Test"));
}
else if (seenX + seenY + seenZ > 1) {
SERIAL_ECHOLN(F("?Specify X, Y, or Z axis"), F(" for "), F("Resonance Test"));
return;
}
if (parser.seenval('A')) {
const float val = parser.value_float();
if (p.axis == Z_AXIS && val > 15.0f) {
p.accel_per_hz = 15.0f;
SERIAL_ECHOLNPGM("Accel/Hz set to max 15 mm/s for Z Axis");
}
else {
p.accel_per_hz = val;
SERIAL_ECHOLNPGM("Accel/Hz set to ", p.accel_per_hz);
}
}
if (parser.seenval('F')) {
const float val = parser.value_float();
if (val >= 5.0f) {
p.min_freq = val;
SERIAL_ECHOLNPGM("Start Frequency set to ", p.min_freq, " Hz");
}
else {
SERIAL_ECHOLN(F("?Invalid "), F("Start [F]requency. (minimum 5.0 Hz)"));
}
}
if (parser.seenval('T')) {
const float val = parser.value_float();
if (val > p.min_freq && val <= 200.0f) {
p.max_freq = val;
SERIAL_ECHOLNPGM("End Frequency set to ", p.max_freq, " Hz");
}
else {
SERIAL_ECHOLN(F("?Invalid "), F("End Frequency [T]. (StartFreq .. 200 Hz)"));
}
}
if (parser.seenval('O')) {
const float val = parser.value_float();
if (WITHIN(val, 20, 60)) {
p.octave_duration = val;
SERIAL_ECHOLNPGM("Octave Duration set to ",p.octave_duration, " s");
}
else {
SERIAL_ECHOLN(F("?Invalid "), F("octave duration [O]. (20..60 s)"));
}
}
if (parser.seenval('C')) {
const int val = parser.value_int();
if (WITHIN(val, 1, 8)) {
p.amplitude_correction = val;
SERIAL_ECHOLNPGM("Amplitude Correction Factor set to ", p.amplitude_correction);
}
else {
SERIAL_ECHOLN(F("?Invalid "), F("Amplitude [C]orrection Factor. (1..8)"));
}
}
if (parser.seenval('G')) {
const float val = parser.value_float();
if (WITHIN(val, 0, 100)) {
ftMotion.rtg.timeline = val;
SERIAL_ECHOLNPGM("Resonance Frequency set to ", ftMotion.rtg.getFrequencyFromTimeline(), " Hz");
}
else {
SERIAL_ECHOLN(F("?Invalid "), F("Timeline value (0..100 s)"));
}
}
if (parser.seen_test('S')) {
if (ftMotion.cfg.active) {
if (p.axis != NO_AXIS_ENUM) {
if (p.max_freq > p.min_freq) {
SERIAL_ECHOLN(F("Starting "), F("Resonance Test"));
ftMotion.start_resonance_test();
// The function returns immediately, the test runs in the background.
}
else {
SERIAL_ECHOLNPGM("?End Frequency must be greater than Start Frequency");
}
}
else {
SERIAL_ECHOLN(F("?Specify X, Y, or Z axis"), F(" first"));
}
}
else {
SERIAL_ECHOLN(F("?Activate FT Motion to run the "), F("Resonance Test"));
}
}
}
/**
* M496: Abort the resonance test (via Emergency Parser)
*/
void GcodeSuite::M496() {
if (ftMotion.rtg.isActive()) {
ftMotion.rtg.abort();
EmergencyParser::rt_stop_by_M496 = false;
#if DISABLED(MARLIN_SMALL_BUILD)
SERIAL_ECHOLN(F("Resonance Test"), F(" aborted."));
#endif
return;
}
#if DISABLED(MARLIN_SMALL_BUILD)
SERIAL_ECHOLN(F("No active "), F("Resonance Test"), F(" to abort."));
#endif
}
#endif // FTM_RESONANCE_TEST
+4
View File
@@ -926,6 +926,10 @@ void GcodeSuite::process_parsed_command(bool no_ok/*=false*/) {
#if ENABLED(FTM_SMOOTHING)
case 494: M494(); break; // M494: Fixed-Time Motion extras
#endif
#if ENABLED(FTM_RESONANCE_TEST)
case 495: M495(); break; // M495: Resonance test for Input Shaping
case 496: M496(); break; // M496: Abort resonance test
#endif
#endif
case 500: M500(); break; // M500: Store settings in EEPROM
+7
View File
@@ -252,6 +252,8 @@
* M485 - Send RS485 packets (Requires RS485_SERIAL_PORT)
* M486 - Identify and cancel objects. (Requires CANCEL_OBJECTS)
* M493 - Set / Report input FT Motion/Shaping parameters. (Requires FT_MOTION)
* M495 - Set / Start resonance test. (Requires FTM_RESONANCE_TEST)
* M496 - Abort resonance test. (Requires FTM_RESONANCE_TEST)
* M500 - Store parameters in EEPROM. (Requires EEPROM_SETTINGS)
* M501 - Restore parameters from EEPROM. (Requires EEPROM_SETTINGS)
* M502 - Revert to the default "factory settings". ** Does not write them to EEPROM! **
@@ -1115,6 +1117,11 @@ private:
static void M493_report(const bool forReplay=true);
static void M494();
static void M494_report(const bool forReplay=true);
#if ENABLED(FTM_RESONANCE_TEST)
static void M495();
static void M495_report(const bool forReplay=true);
static void M496();
#endif
#endif
static void M500();
+3
View File
@@ -4510,6 +4510,9 @@ static_assert(_PLUS_TEST(3), "DEFAULT_MAX_ACCELERATION values must be positive."
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
#if ENABLED(FTM_RESONANCE_TEST) && DISABLED(EMERGENCY_PARSER)
#error "EMERGENCY_PARSER is required with FTM_RESONANCE_TEST (to cancel the test)."
#endif
#endif
// Multi-Stepping Limit
+10
View File
@@ -951,6 +951,14 @@ namespace LanguageNarrow_en {
LSTR MSG_FTM_SMOOTH_TIME_N = _UxGT("@ Smoothing Time");
LSTR MSG_FTM_POLY6_OVERSHOOT = _UxGT("@ Poly6 Overshoot");
LSTR MSG_FTM_RESONANCE_TEST = _UxGT("Resonance Test");
LSTR MSG_FTM_RT_RUNNING = _UxGT("Res. Test Running...");
LSTR MSG_FTM_RT_START_N = _UxGT("Start @ Axis Test");
LSTR MSG_FTM_RT_STOP = _UxGT("Abort Test");
LSTR MSG_FTM_RETRIEVE_FREQ = _UxGT("Calc. Res. Freq.");
LSTR MSG_FTM_RESONANCE_FREQ = _UxGT("Resonance Freq.");
LSTR MSG_FTM_TIMELINE_FREQ = _UxGT("Timeline (s)");
LSTR MSG_LEVEL_X_AXIS = _UxGT("Level X Axis");
LSTR MSG_AUTO_CALIBRATE = _UxGT("Auto Calibrate");
LSTR MSG_FTDI_HEATER_TIMEOUT = _UxGT("Idle timeout, temperature decreased. Press Okay to reheat and again to resume.");
@@ -1158,6 +1166,8 @@ namespace LanguageWide_en {
LSTR MSG_EEPROM_INITIALIZED = _UxGT("Default Settings Restored");
LSTR MSG_PREHEAT_M_CHAMBER = _UxGT("Preheat $ Chamber");
LSTR MSG_PREHEAT_M_SETTINGS = _UxGT("Preheat $ Config");
LSTR MSG_FTM_RT_RUNNING = _UxGT("Resonance Test Running...");
LSTR MSG_FTM_RESONANCE_FREQ = _UxGT("Resonance frequency");
#endif
}
+36
View File
@@ -382,6 +382,38 @@ void menu_move() {
END_MENU();
}
#if ENABLED(FTM_RESONANCE_TEST)
void menu_ftm_resonance_freq() {
START_MENU();
BACK_ITEM(MSG_FTM_RESONANCE_TEST);
STATIC_ITEM(MSG_FTM_RETRIEVE_FREQ);
EDIT_ITEM(float62, MSG_FTM_TIMELINE_FREQ, &ftMotion.rtg.timeline, 0.0f, 600.0f);
PSTRING_ITEM(MSG_FTM_RESONANCE_FREQ, ftostr53_63(ftMotion.rtg.getFrequencyFromTimeline()), SS_FULL);
END_MENU();
}
void menu_ftm_resonance_test() {
START_MENU();
BACK_ITEM(MSG_FIXED_TIME_MOTION);
if (ftMotion.rtg.isActive() && !ftMotion.rtg.isDone()) {
STATIC_ITEM(MSG_FTM_RT_RUNNING);
ACTION_ITEM(MSG_FTM_RT_STOP, []{ ftMotion.rtg.abort(); ui.go_back(); });
}
else {
GCODES_ITEM_N(X_AXIS, MSG_FTM_RT_START_N, F("M495 X S"));
GCODES_ITEM_N(Y_AXIS, MSG_FTM_RT_START_N, F("M495 Y S"));
GCODES_ITEM_N(Z_AXIS, MSG_FTM_RT_START_N, F("M495 Z S"));
SUBMENU(MSG_FTM_RETRIEVE_FREQ, menu_ftm_resonance_freq);
}
END_MENU();
}
#endif // FTM_RESONANCE_TEST
#if HAS_DYNAMIC_FREQ
void menu_ftm_dyn_mode() {
@@ -504,6 +536,10 @@ void menu_move() {
#if ENABLED(FTM_SMOOTHING)
CARTES_MAP(_SMOO_MENU_ITEM);
#endif
#if ENABLED(FTM_RESONANCE_TEST)
SUBMENU(MSG_FTM_RESONANCE_TEST, menu_ftm_resonance_test);
#endif
}
END_MENU();
} // menu_ft_motion
+54 -7
View File
@@ -35,6 +35,11 @@
#include "ft_motion/trajectory_trapezoidal.h"
#include "ft_motion/trajectory_poly5.h"
#include "ft_motion/trajectory_poly6.h"
#if ENABLED(FTM_RESONANCE_TEST)
#include "ft_motion/resonance_generator.h"
#include "../gcode/gcode.h" // for home_all_axes
#endif
#include "stepper.h" // Access stepper block queue function and abort status.
#include "endstops.h"
@@ -71,6 +76,9 @@ Poly6TrajectoryGenerator FTMotion::poly6Generator;
TrajectoryGenerator* FTMotion::currentGenerator = &FTMotion::trapezoidalGenerator;
TrajectoryType FTMotion::trajectoryType = TrajectoryType::FTM_TRAJECTORY_TYPE;
// Resonance Test
TERN_(FTM_RESONANCE_TEST,ResonanceGenerator FTMotion::rtg;) // Resonance trajectory generator instance
// Compact plan buffer
stepper_plan_t FTMotion::stepper_plan_buff[FTM_BUFFER_SIZE];
XYZEval<int64_t> FTMotion::curr_steps_q32_32 = {0};
@@ -150,14 +158,32 @@ void FTMotion::loop() {
* 3. Reset all the states / memory.
* 4. Signal ready for new block.
*/
if (stepper.abort_current_block) {
discard_planner_block_protected();
reset();
currentGenerator->planRunout(0.0f); // Reset generator state
stepper.abort_current_block = false; // Abort finished.
}
fill_stepper_plan_buffer();
const bool using_resonance = TERN(FTM_RESONANCE_TEST, rtg.isActive(), false);
#if ENABLED(FTM_RESONANCE_TEST)
if (using_resonance) {
// Resonance Test has priority over normal ft_motion operation.
// Process resonance test if active. When it's done, generate the last data points for a clean ending.
if (rtg.isActive()) {
if (rtg.isDone()) {
rtg.abort();
return;
}
rtg.fill_stepper_plan_buffer();
}
}
#endif
if (!using_resonance) {
if (stepper.abort_current_block) {
discard_planner_block_protected();
reset();
currentGenerator->planRunout(0.0f); // Reset generator state
stepper.abort_current_block = false; // Abort finished.
}
fill_stepper_plan_buffer();
}
// Set busy status for use by planner.busy()
const bool oldBusy = busy;
@@ -557,4 +583,25 @@ void FTMotion::fill_stepper_plan_buffer() {
}
}
#if ENABLED(FTM_RESONANCE_TEST)
// Start Resonance Testing
void FTMotion::start_resonance_test() {
home_if_needed(); // Ensure known axes first
ftm_resonance_test_params_t &p = rtg.rt_params;
// Safe Acceleration per Hz for Z axis
if (p.axis == Z_AXIS && p.accel_per_hz > 15.0f)
p.accel_per_hz = 15.0f;
// Always move to the center of the bed
do_blocking_move_to_xy(X_CENTER, Y_CENTER, Z_CLEARANCE_FOR_HOMING);
// Start test at the current position with the configured time-step
rtg.start(current_position, FTM_TS);
}
#endif // FTM_RESONANCE_TEST
#endif // FT_MOTION
+11
View File
@@ -28,6 +28,9 @@
#include "ft_motion/trajectory_trapezoidal.h"
#include "ft_motion/trajectory_poly5.h"
#include "ft_motion/trajectory_poly6.h"
#if ENABLED(FTM_RESONANCE_TEST)
#include "ft_motion/resonance_generator.h"
#endif
#if HAS_FTM_SHAPING
#include "ft_motion/shaping.h"
@@ -88,6 +91,10 @@ typedef struct FTConfig {
*/
class FTMotion {
#if ENABLED(FTM_RESONANCE_TEST)
friend void ResonanceGenerator::fill_stepper_plan_buffer();
#endif
public:
// Public variables
@@ -140,6 +147,10 @@ class FTMotion {
// Public methods
static void init();
static void loop(); // Controller main, to be invoked from non-isr task.
#if ENABLED(FTM_RESONANCE_TEST)
static void start_resonance_test(); // Start a resonance test with given parameters
static ResonanceGenerator rtg; // Resonance trajectory generator instance
#endif
#if HAS_FTM_SHAPING
// Refresh gains and indices used by shaping functions.
@@ -0,0 +1,88 @@
/**
* 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_RESONANCE_TEST)
#include "../ft_motion.h"
#include "resonance_generator.h"
#include <math.h>
ftm_resonance_test_params_t ResonanceGenerator::rt_params; // Resonance test parameters
bool ResonanceGenerator::active = false; // Resonance test active
bool ResonanceGenerator::done = false; // Resonance test done
float ResonanceGenerator::rt_time = FTM_TS; // Resonance test timer
float ResonanceGenerator::timeline = 0.0f;
ResonanceGenerator::ResonanceGenerator() {}
void ResonanceGenerator::abort() {
reset();
ftMotion.reset();
}
void ResonanceGenerator::reset() {
rt_params = ftm_resonance_test_params_t();
rt_time = FTM_TS;
active = false;
done = false;
}
void ResonanceGenerator::fill_stepper_plan_buffer() {
xyze_float_t traj_coords = {};
while (!ftMotion.stepper_plan_is_full()) {
// Calculate current frequency
// Logarithmic approach with duration per octave
const float freq = rt_params.min_freq * powf(2.0f, rt_time / rt_params.octave_duration);
if (freq > rt_params.max_freq) {
done = true;
return;
}
// Amplitude based on a sinusoidal wave : A = accel / (4 * PI^2 * f^2)
//const float accel_magnitude = rt_params.accel_per_hz * freq;
//const float amplitude = rt_params.amplitude_correction * accel_magnitude / (4.0f * sq(M_PI) * sq(freq));
const float amplitude = rt_params.amplitude_correction * rt_params.accel_per_hz * 0.25f / (sq(M_PI) * freq);
// Phase in radians
const float phase = 2.0f * M_PI * freq * rt_time;
// Position Offset : between -A and +A
const float pos_offset = amplitude * sinf(phase);
// Set base position and apply offset to the test axis in one step for all axes
#define _SET_TRAJ(A) traj_coords.A = rt_params.start_pos.A + (rt_params.axis == A##_AXIS ? pos_offset : 0.0f);
LOGICAL_AXIS_MAP(_SET_TRAJ);
stepper_plan_t plan = ftMotion.calc_stepper_plan(traj_coords);
// Store in buffer
ftMotion.enqueue_stepper_plan(plan);
// Increment time for the next point
rt_time += FTM_TS;
}
}
#endif // FTM_RESONANCE_TEST
@@ -0,0 +1,73 @@
/**
* 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/>.
*
*/
#pragma once
#include "../../inc/MarlinConfigPre.h"
#include <math.h>
typedef struct FTMResonanceTestParams {
AxisEnum axis = NO_AXIS_ENUM; // Axis to test
float min_freq = 5.0f; // Minimum frequency [Hz]
float max_freq = 100.0f; // Maximum frequency [Hz]
float octave_duration = 40.0f; // Octave duration for logarithmic progression
float accel_per_hz = 60.0f; // Acceleration per Hz [mm/sec/Hz] or [g/Hz]
int16_t amplitude_correction = 5; // Amplitude correction factor
xyze_pos_t start_pos; // Initial stepper position
} ftm_resonance_test_params_t;
class ResonanceGenerator {
public:
static ftm_resonance_test_params_t rt_params; // Resonance test parameters
static float timeline; // Timeline Value to calculate resonance frequency
ResonanceGenerator();
void planRunout(const float duration);
void reset();
void start(const xyze_pos_t &spos, const float t) {
rt_params.start_pos = spos;
rt_time = t;
active = true;
done = false;
}
float getFrequencyFromTimeline() {
return (rt_params.min_freq * powf(2.0f, timeline / rt_params.octave_duration)); // Return frequency based on timeline
}
void fill_stepper_plan_buffer(); // Fill stepper plan buffer with trajectory points
bool isActive() const { return active; }
bool isDone() const { return done; }
void setActive(bool state) { active = state; }
void setDone(bool state) { done = state; }
void abort(); // Abort resonance test
private:
static float rt_time; // Test timer
static bool active; // Resonance test active
static bool done; // Resonance test done
};
-1
View File
@@ -29,7 +29,6 @@ typedef struct stepper_plan {
void reset() { advance_dividend_q0_32.reset(); }
} stepper_plan_t;
// Stepping plan handles steps for a while frame (trajectory point delta)
typedef struct Stepping {
stepper_plan_t stepper_plan;
@@ -21,7 +21,7 @@
*/
#pragma once
#include <stdint.h>
#include "../../inc/MarlinConfigPre.h"
/**
* Base class for trajectory generators.
@@ -72,4 +72,7 @@ protected:
/**
* Trajectory generator types for runtime selection
*/
enum class TrajectoryType : uint8_t { TRAPEZOIDAL, POLY5, POLY6 };
enum class TrajectoryType : uint8_t {
TRAPEZOIDAL, POLY5, POLY6
OPTARG(FTM_RESONANCE_TEST, RESONANCE)
};
@@ -25,8 +25,8 @@
#if ENABLED(FT_MOTION)
#include "trajectory_poly6.h"
#include <math.h>
#include "../ft_motion.h"
#include <math.h>
Poly6TrajectoryGenerator::Poly6TrajectoryGenerator() {}
+3 -2
View File
@@ -14,6 +14,7 @@ opt_set MOTHERBOARD BOARD_BTT_SKR_MINI_E3_V1_0 SERIAL_PORT 1 SERIAL_PORT_2 -1 \
X_DRIVER_TYPE TMC2209 Y_DRIVER_TYPE TMC2209 Z_DRIVER_TYPE TMC2209 E0_DRIVER_TYPE TMC2209 \
X_CURRENT_HOME X_CURRENT/2 Y_CURRENT_HOME Y_CURRENT/2 Z_CURRENT_HOME Y_CURRENT/2
opt_enable CR10_STOCKDISPLAY PINS_DEBUGGING Z_IDLE_HEIGHT EDITABLE_HOMING_CURRENT \
FT_MOTION FT_MOTION_MENU BIQU_MICROPROBE_V1 PROBE_ENABLE_DISABLE Z_SAFE_HOMING AUTO_BED_LEVELING_BILINEAR \
ADAPTIVE_STEP_SMOOTHING LIN_ADVANCE SMOOTH_LIN_ADVANCE NONLINEAR_EXTRUSION INPUT_SHAPING_X INPUT_SHAPING_Y
INPUT_SHAPING_X INPUT_SHAPING_Y FT_MOTION FT_MOTION_MENU FTM_RESONANCE_TEST EMERGENCY_PARSER \
BIQU_MICROPROBE_V1 PROBE_ENABLE_DISABLE Z_SAFE_HOMING AUTO_BED_LEVELING_BILINEAR \
ADAPTIVE_STEP_SMOOTHING LIN_ADVANCE SMOOTH_LIN_ADVANCE NONLINEAR_EXTRUSION
exec_test $1 $2 "BigTreeTech SKR Mini E3 1.0 - TMC2209 HW Serial, FT_MOTION" "$3"
+2 -1
View File
@@ -311,8 +311,9 @@ HAS_DUPLICATION_MODE = build_src_filter=+<src/gcode/control/M6
SPI_FLASH_BACKUP = build_src_filter=+<src/gcode/control/M993_M994.cpp>
PLATFORM_M997_SUPPORT = build_src_filter=+<src/gcode/control/M997.cpp>
HAS_TOOLCHANGE = build_src_filter=+<src/gcode/control/T.cpp>
FT_MOTION = build_src_filter=+<src/module/ft_motion.cpp> +<src/module/ft_motion> -<src/module/ft_motion/smoothing.cpp> +<src/gcode/feature/ft_motion>
FT_MOTION = build_src_filter=+<src/module/ft_motion.cpp> +<src/module/ft_motion> -<src/module/ft_motion/smoothing.cpp> +<src/gcode/feature/ft_motion> -<src/gcode/feature/ft_motion/M495_M496.cpp>
FTM_SMOOTHING = build_src_filter=+<src/module/ft_motion/smoothing.cpp>
FTM_RESONANCE_TEST = build_src_filter=+<src/gcode/feature/ft_motion/M495_M496.cpp>
HAS_LIN_ADVANCE_K = build_src_filter=+<src/gcode/feature/advance>
PHOTO_GCODE = build_src_filter=+<src/gcode/feature/camera>
CONTROLLER_FAN_EDITABLE = build_src_filter=+<src/gcode/feature/controllerfan>