Skip to content

Commit 22cf836

Browse files
⛙ Merge w/Marlin
2 parents e8a3541 + f44cb94 commit 22cf836

134 files changed

Lines changed: 4788 additions & 27952 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,4 @@ tags
181181
.env
182182
desktop.ini
183183
!MarlinSimulator.exe
184+
schema.json

Marlin/Configuration.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@
570570
* ================================================================
571571
* Analog Thermocouple Boards
572572
* ================================================================
573+
* -18 : ADS1118 with Thermocouple, e.g., Mightyboard rev G/H
573574
* -4 : AD8495 with Thermocouple
574575
* -1 : AD595 with Thermocouple
575576
*

Marlin/Configuration_adv.h

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,25 @@
586586

587587
// @section fans
588588

589+
/**
590+
* Part Cooling Fan Pins
591+
* Override the default part cooling fan pins for each fan index.
592+
* Allows remapping which physical fan pin is used for part cooling.
593+
* By default: FAN0 -> FAN0_PIN, FAN1 -> FAN1_PIN, etc.
594+
*/
595+
//#define PART_COOLING_FAN0_PIN FAN0_PIN
596+
//#define PART_COOLING_FAN1_PIN FAN1_PIN
597+
//#define PART_COOLING_FAN2_PIN FAN2_PIN
598+
//#define PART_COOLING_FAN3_PIN FAN3_PIN
599+
//#define PART_COOLING_FAN4_PIN FAN4_PIN
600+
//#define PART_COOLING_FAN5_PIN FAN5_PIN
601+
//#define PART_COOLING_FAN6_PIN FAN6_PIN
602+
//#define PART_COOLING_FAN7_PIN FAN7_PIN
603+
//#define PART_COOLING_FAN8_PIN FAN8_PIN
604+
//#define PART_COOLING_FAN9_PIN FAN9_PIN
605+
//#define PART_COOLING_FAN10_PIN FAN10_PIN
606+
//#define PART_COOLING_FAN11_PIN FAN11_PIN
607+
589608
/**
590609
* Controller Fan
591610
* To cool down the stepper drivers and MOSFETs.
@@ -1210,8 +1229,6 @@
12101229
#define FTM_SHAPING_ZETA_E 0.03f // Zeta used by input shapers for E axis
12111230
#define FTM_SHAPING_V_TOL_E 0.05f // Vibration tolerance used by EI input shapers for E axis
12121231

1213-
//#define FTM_RESONANCE_TEST // Sine sweep motion for resonance study
1214-
12151232
//#define FTM_SMOOTHING // Smoothing can reduce artifacts and make steppers quieter
12161233
// on sharp corners, but too much will round corners.
12171234
#if ENABLED(FTM_SMOOTHING)
@@ -1226,16 +1243,29 @@
12261243

12271244
#define FTM_POLYS // Disable POLY5/6 to save ~3k of Flash. Preserves TRAPEZOIDAL.
12281245
#if ENABLED(FTM_POLYS)
1229-
#define FTM_TRAJECTORY_TYPE TRAPEZOIDAL // Block acceleration profile (TRAPEZOIDAL, POLY5, POLY6)
1230-
// TRAPEZOIDAL: Continuous Velocity. Max acceleration is respected.
1231-
// POLY5: Like POLY6 with 1.5x but uses less CPU.
1232-
// POLY6: Continuous Acceleration (aka S_CURVE).
1233-
// POLY trajectories not only reduce resonances without rounding corners, but also
1234-
// reduce extruder strain due to linear advance.
1235-
12361246
#define FTM_POLY6_ACCELERATION_OVERSHOOT 1.875f // Max acceleration overshoot factor for POLY6 (1.25 to 1.875)
12371247
#endif
12381248

1249+
/**
1250+
* FTM Constant-Jolt Trajectory (7-phase S-curve).
1251+
* Jolt is the rate of change of acceleration, not related to Marlin's "classic jerk."
1252+
* Ramps acceleration gradually so max acceleration is limited by max speed and distance traveled.
1253+
*/
1254+
//#define FTM_CONSTANT_JOLT
1255+
#if ENABLED(FTM_CONSTANT_JOLT)
1256+
#define FTM_DEFAULT_JOLT 250.0f // (m/s³) Default jolt for constant-jolt trajectory.
1257+
// Higher values print faster at the cost of increased resonance and extruder stress
1258+
#endif
1259+
1260+
// Block acceleration profile
1261+
// :[ 'TRAPEZOIDAL', 'POLY5', 'POLY6', 'CONSTANT_JOLT' ]
1262+
#define FTM_TRAJECTORY_TYPE TRAPEZOIDAL // TRAPEZOIDAL: Continuous Velocity. Max acceleration is respected.
1263+
// POLY5: Like POLY6 with 1.5x but uses less CPU. Requires FTM_POLYS.
1264+
// POLY6: Continuous Acceleration (aka S_CURVE). Requires FTM_POLYS.
1265+
// CONSTANT_JOLT: 7-phase S-curve. Requires FTM_CONSTANT_JOLT.
1266+
// POLY trajectories not only reduce resonances without rounding corners, but
1267+
// also reduce extruder strain due to linear advance.
1268+
12391269
/**
12401270
* Advanced configuration
12411271
*/
@@ -1301,6 +1331,10 @@
13011331
#define SHAPING_MENU // Add a menu to the LCD to set shaping parameters.
13021332
#endif
13031333

1334+
#if ANY(INPUT_SHAPING_X, INPUT_SHAPING_Y, INPUT_SHAPING_Z, FTM_SHAPER_ZV, FTM_SHAPER_ZVD, FTM_SHAPER_ZVDD, FTM_SHAPER_ZVDDD, FTM_SHAPER_EI, FTM_SHAPER_2HEI, FTM_SHAPER_3HEI, FTM_SHAPER_MZV)
1335+
//#define RESONANCE_TEST // Sine sweep motion for resonance study
1336+
#endif
1337+
13041338
// @section motion
13051339

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

Marlin/Version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
* here we define this default string as the date where the latest release
4242
* version was tagged.
4343
*/
44-
//#define STRING_DISTRIBUTION_DATE "2026-06-03"
44+
//#define STRING_DISTRIBUTION_DATE "2026-07-08"
4545

4646
#define STRING_DISTRIBUTION_DATE __DATE__
4747
#define STRING_DISTRIBUTION_TIME __TIME__

Marlin/src/feature/bedlevel/abl/bbl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ void LevelingBilinear::print_leveling_grid(const bed_mesh_t* _z_values/*=nullptr
190190
// The requested point requires extrapolating two points beyond the mesh.
191191
// These values are only requested for the edges of the mesh, which are always an actual mesh point,
192192
// and do not require interpolation. When interpolation is not needed, this "Mesh + 2" point is
193-
// cancelled out in virt_cmr and does not impact the result. Return 0.0 rather than
193+
// canceled out in virt_cmr and does not impact the result. Return 0.0 rather than
194194
// making this function more complex by extrapolating two points.
195195
return 0.0;
196196
}

Marlin/src/feature/e_parser.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
// Static data members
3434
bool EmergencyParser::killed_by_M112, // = false
3535
EmergencyParser::quickstop_by_M410,
36-
#if ENABLED(FTM_RESONANCE_TEST)
36+
#if ENABLED(RESONANCE_TEST)
3737
EmergencyParser::rt_stop_by_M496, // = false
3838
#endif
3939
#if HAS_MEDIA
@@ -151,7 +151,7 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) {
151151
case EP_M4:
152152
switch (c) {
153153
case '1' :state = EP_M41; break;
154-
#if ENABLED(FTM_RESONANCE_TEST)
154+
#if ENABLED(RESONANCE_TEST)
155155
case '9': state = EP_M49; break;
156156
#endif
157157
default: state = EP_IGNORE;
@@ -160,7 +160,7 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) {
160160

161161
case EP_M41: state = (c == '0') ? EP_M410 : EP_IGNORE; break;
162162

163-
#if ENABLED(FTM_RESONANCE_TEST)
163+
#if ENABLED(RESONANCE_TEST)
164164
case EP_M49: state = (c == '6') ? EP_M496 : EP_IGNORE; break;
165165
#endif
166166

@@ -209,7 +209,7 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) {
209209
case EP_M108: marlin.end_waiting(); break;
210210
case EP_M112: killed_by_M112 = true; break;
211211
case EP_M410: quickstop_by_M410 = true; break;
212-
#if ENABLED(FTM_RESONANCE_TEST)
212+
#if ENABLED(RESONANCE_TEST)
213213
case EP_M496: rt_stop_by_M496 = true; break;
214214
#endif
215215
#if ENABLED(EP_BABYSTEPPING)

Marlin/src/feature/e_parser.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class EmergencyParser {
4343
#if HAS_MEDIA
4444
EP_M5, EP_M52, EP_M524,
4545
#endif
46-
#if ENABLED(FTM_RESONANCE_TEST)
46+
#if ENABLED(RESONANCE_TEST)
4747
EP_M49, EP_M496,
4848
#endif
4949
#if ENABLED(EP_BABYSTEPPING)
@@ -67,7 +67,7 @@ class EmergencyParser {
6767
static bool killed_by_M112;
6868
static bool quickstop_by_M410;
6969

70-
#if ENABLED(FTM_RESONANCE_TEST)
70+
#if ENABLED(RESONANCE_TEST)
7171
static bool rt_stop_by_M496;
7272
#endif
7373

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Marlin 3D Printer Firmware
3+
* Copyright (c) 2026 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4+
*
5+
* Based on Sprinter and grbl.
6+
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU General Public License as published by
10+
* the Free Software Foundation, either version 3 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
20+
*
21+
*/
22+
23+
#include "../../inc/MarlinConfigPre.h"
24+
25+
#if ENABLED(RESONANCE_TEST)
26+
27+
#include "resonance_generator.h"
28+
29+
#if ENABLED(FT_MOTION)
30+
#include "../../module/ft_motion.h"
31+
#endif
32+
33+
#include "../../gcode/gcode.h" // for home_all_axes
34+
35+
resonance_test_params_t ResonanceGenerator::rt_params; // Resonance test parameters
36+
float ResonanceGenerator::timeline;
37+
38+
bool ResonanceGenerator::active = false; // Resonance test active
39+
bool ResonanceGenerator::done = false; // Resonance test done
40+
int32_t ResonanceGenerator::freq_to_phase_fp;
41+
int32_t ResonanceGenerator::max_freq_fp;
42+
int32_t ResonanceGenerator::phase_fp;
43+
int32_t ResonanceGenerator::current_freq_fp;
44+
int32_t ResonanceGenerator::amplitude_precalc_fp;
45+
46+
47+
#if HAS_STANDARD_MOTION
48+
block_t ResonanceGenerator::block;
49+
#endif
50+
51+
ResonanceGenerator rtg;
52+
53+
ResonanceGenerator::ResonanceGenerator() {}
54+
55+
void ResonanceGenerator::start() {
56+
gcode.home_all_axes(); // Always home axes first
57+
motion.blocking_move_xy(X_CENTER, Y_CENTER, Z_CLEARANCE_FOR_HOMING);
58+
59+
rt_params.start_pos = motion.position;
60+
active = true;
61+
done = false;
62+
63+
// Clamp Z-axis acceleration for safety
64+
if (rt_params.axis == Z_AXIS)
65+
NOMORE(rt_params.accel_per_hz, 15.0f);
66+
67+
// Calculate time constant for sine sweep
68+
const float rt_time = rt_params.octave_duration * (logf(RATIO) / logf(2.0f));
69+
70+
#if HAS_STANDARD_MOTION
71+
if (TERN1(FT_MOTION, !ftMotion.cfg.active)) {
72+
block.reset();
73+
block.initial_rate = (rt_params.axis == Z_AXIS) ? 2000 : 8000;
74+
}
75+
#endif
76+
77+
// Precompute fixed-point sine sweep parameters
78+
amplitude_precalc_fp = F2FP((rt_params.amplitude_correction * rt_params.accel_per_hz * 0.25f) / sq(M_PI));
79+
current_freq_fp = F2FP(rt_params.min_freq);
80+
freq_to_phase_fp = F2FP(2.0f * M_PI * rt_time);
81+
max_freq_fp = F2FP(rt_params.max_freq);
82+
phase_fp = 0;
83+
}
84+
85+
void ResonanceGenerator::abort() {
86+
reset();
87+
#if HAS_STANDARD_MOTION
88+
if (!TERN0(FT_MOTION, ftMotion.cfg.active))
89+
return;
90+
#endif
91+
TERN_(FT_MOTION, ftMotion.reset());
92+
}
93+
94+
void ResonanceGenerator::reset() {
95+
rt_params = resonance_test_params_t();
96+
#if HAS_STANDARD_MOTION
97+
if (!TERN0(FT_MOTION, ftMotion.cfg.active))
98+
block.reset();
99+
#endif
100+
active = false;
101+
done = false;
102+
}
103+
104+
float ResonanceGenerator::calc_next_pos() {
105+
// Phase accumulation and wrapping within [0, 2π)
106+
phase_fp += (int32_t)(((int64_t)current_freq_fp * freq_to_phase_fp) >> FP_BITS);
107+
if (phase_fp >= M_TAU_FP) phase_fp -= M_TAU_FP;
108+
else if (phase_fp < 0) phase_fp += M_TAU_FP;
109+
110+
// -π <= r_fp <= π
111+
const int32_t r_fp = (phase_fp > M_PI_FP) ? phase_fp - M_TAU_FP : phase_fp;
112+
113+
// Calculate windowing polynomial: 1.0 - 0.101321184 * r²
114+
const int32_t poly_fp = FP_ONE - ((C0101321184_FP * ((r_fp * r_fp) >> FP_BITS)) >> FP_BITS);
115+
116+
// Combine amplitude, phase, and polynomial and return new position
117+
const int32_t amplitude_fp = (int32_t)(((int64_t)amplitude_precalc_fp * FP_ONE) / current_freq_fp);
118+
const int32_t pos_fp = (int32_t)((((int64_t)amplitude_fp * r_fp) >> FP_BITS) * poly_fp) >> FP_BITS;
119+
120+
return FP2F(pos_fp);
121+
}
122+
123+
#if ENABLED(FT_MOTION)
124+
125+
void ResonanceGenerator::fill_stepper_plan_buffer() {
126+
#if HAS_FTM_DIR_CHANGE_HOLD
127+
xyze_float_t traj_coords = ftMotion.get_last_target_traj();
128+
traj_coords[rt_params.axis] = rt_params.start_pos[rt_params.axis];
129+
#else
130+
xyze_float_t traj_coords = rt_params.start_pos;
131+
#endif
132+
// Save starting position, avoid cumulative errors
133+
const float start_pos = rt_params.start_pos[rt_params.axis];
134+
135+
while (!ftMotion.stepping.is_full()) {
136+
// Calculate current frequency with exponential sweep
137+
current_freq_fp += current_freq_fp >> FP_BITS;
138+
if (current_freq_fp > max_freq_fp) {
139+
done = true;
140+
return;
141+
}
142+
143+
// Resonate the axis being tested
144+
traj_coords[rt_params.axis] = start_pos + calc_next_pos();
145+
146+
TERN_(HAS_FTM_DIR_CHANGE_HOLD, traj_coords = ftMotion.ftm_hold_frames(traj_coords));
147+
148+
// Store in buffer
149+
ftMotion.stepping_enqueue(traj_coords);
150+
}
151+
}
152+
153+
#endif // FT_MOTION
154+
155+
#if HAS_STANDARD_MOTION
156+
157+
block_t* ResonanceGenerator::generate_resonance_block() {
158+
// Static variables to retain state between calls and avoid cumulative errors
159+
static float prev_pos = 0.0f, step_accumulator = 0.0f;
160+
161+
const float step_mm = planner.settings.axis_steps_per_mm[rt_params.axis];
162+
const uint8_t axis_bit = 1 << rt_params.axis;
163+
164+
// Calculate current frequency with exponential sweep
165+
current_freq_fp += current_freq_fp >> FP_BITS;
166+
if (current_freq_fp > max_freq_fp) {
167+
done = true;
168+
return nullptr;
169+
}
170+
171+
// Calculate position and accumulate steps
172+
const float current_pos = calc_next_pos();
173+
step_accumulator += (current_pos - prev_pos) * step_mm;
174+
prev_pos = current_pos;
175+
176+
// Extract steps
177+
const int32_t delta_steps = (int32_t)floor(step_accumulator);
178+
step_accumulator -= delta_steps;
179+
const uint32_t abs_steps = abs(delta_steps);
180+
181+
// Update block
182+
block.steps[rt_params.axis] = abs_steps;
183+
block.step_event_count = abs_steps;
184+
if (delta_steps < 0)
185+
block.direction_bits |= axis_bit;
186+
else
187+
block.direction_bits &= ~axis_bit;
188+
189+
return &block;
190+
}
191+
192+
#endif // HAS_STANDARD_MOTION
193+
194+
#endif // RESONANCE_TEST

0 commit comments

Comments
 (0)