️ Optimize Smooth Linear Advance (via fixed-point) (#27818)

This commit is contained in:
David Buezas 2025-05-13 23:14:04 +02:00 committed by GitHub
parent 4de6d655ac
commit 12fdde24d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 265 additions and 201 deletions

View file

@ -2366,11 +2366,11 @@
* Higher k and higher XY acceleration may require larger ADVANCE_TAU to avoid skipping steps.
*/
#if ENABLED(DISTINCT_E_FACTORS)
#define ADVANCE_TAU { 0.01 } // (s) Smoothing time to reduce extruder acceleration, per extruder
#define ADVANCE_TAU { 0.02 } // (s) Smoothing time to reduce extruder acceleration, per extruder
#else
#define ADVANCE_TAU 0.01 // (s) Smoothing time to reduce extruder acceleration
#define ADVANCE_TAU 0.02 // (s) Smoothing time to reduce extruder acceleration
#endif
#define SMOOTH_LIN_ADV_HZ 5000 // (Hz) How often to update extruder speed
#define SMOOTH_LIN_ADV_HZ 1000 // (Hz) How often to update extruder speed
#define INPUT_SHAPING_E_SYNC // Synchronize the extruder-shaped XY axes (to increase precision)
#endif
#endif

View file

@ -29,7 +29,7 @@
#include "../../../module/stepper.h"
#if ENABLED(ADVANCE_K_EXTRA)
float other_extruder_advance_K[DISTINCT_E];
float other_extruder_advance_K[EXTRUDERS];
uint8_t lin_adv_slot = 0;
#endif
@ -62,17 +62,17 @@ void GcodeSuite::M900() {
}
#endif
float &kref = planner.extruder_advance_K[E_INDEX_N(tool_index)], newK = kref;
const float oldK = newK;
const float oldK = planner.get_advance_k(tool_index);
float newK = oldK;
#if ENABLED(SMOOTH_LIN_ADVANCE)
const float oldU = stepper.get_advance_tau(E_INDEX_N(tool_index));
const float oldU = stepper.get_advance_tau(tool_index);
float newU = oldU;
#endif
#if ENABLED(ADVANCE_K_EXTRA)
float &lref = other_extruder_advance_K[E_INDEX_N(tool_index)];
float &lref = other_extruder_advance_K[tool_index];
const bool old_slot = TEST(lin_adv_slot, tool_index), // Each tool uses 1 bit to store its current slot (0 or 1)
new_slot = parser.boolval('S', old_slot); // The new slot (0 or 1) to set for the tool (default = no change)
@ -125,9 +125,9 @@ void GcodeSuite::M900() {
if (newK != oldK || TERN0(SMOOTH_LIN_ADVANCE, newU != oldU)) {
planner.synchronize();
if (newK != oldK) kref = newK;
if (newK != oldK) planner.set_advance_k(newK, tool_index);
#if ENABLED(SMOOTH_LIN_ADVANCE)
if (newU != oldU) stepper.set_advance_tau(newU);
if (newU != oldU) stepper.set_advance_tau(newU, tool_index);
#endif
}
@ -136,11 +136,11 @@ void GcodeSuite::M900() {
#if ENABLED(ADVANCE_K_EXTRA)
#if DISABLED(DISTINCT_E_FACTORS)
SERIAL_ECHOLNPGM("Advance S", new_slot, " K", kref, "(S", !new_slot, " K", lref, ")");
SERIAL_ECHOLNPGM("Advance S", new_slot, " K", newK, "(S", !new_slot, " K", lref, ")");
#else
EXTRUDER_LOOP() {
const bool slot = TEST(lin_adv_slot, e);
SERIAL_ECHOLNPGM("Advance T", e, " S", slot, " K", planner.extruder_advance_K[e],
SERIAL_ECHOLNPGM("Advance T", e, " S", slot, " K", planner.get_advance_k(e),
"(S", !slot, " K", other_extruder_advance_K[e], ")");
}
#endif
@ -149,14 +149,14 @@ void GcodeSuite::M900() {
SERIAL_ECHO_START();
#if DISABLED(DISTINCT_E_FACTORS)
SERIAL_ECHOPGM("Advance K=", planner.extruder_advance_K[0]);
SERIAL_ECHOPGM("Advance K=", planner.get_advance_k());
#if ENABLED(SMOOTH_LIN_ADVANCE)
SERIAL_ECHOPGM(" TAU=", stepper.get_advance_tau());
#endif
SERIAL_EOL();
#else
SERIAL_ECHOPGM("Advance K");
EXTRUDER_LOOP() SERIAL_ECHO(C(' '), C('0' + e), C(':'), planner.extruder_advance_K[e]);
EXTRUDER_LOOP() SERIAL_ECHO(C(' '), C('0' + e), C(':'), planner.get_advance_k(e));
SERIAL_EOL();
#if ENABLED(SMOOTH_LIN_ADVANCE)
SERIAL_ECHOPGM("Advance TAU");
@ -174,23 +174,21 @@ void GcodeSuite::M900_report(const bool forReplay/*=true*/) {
TERN_(MARLIN_SMALL_BUILD, return);
report_heading(forReplay, F(STR_LINEAR_ADVANCE));
#if DISABLED(DISTINCT_E_FACTORS)
DISTINCT_E_LOOP() {
report_echo_start(forReplay);
SERIAL_ECHOPGM(" M900 K", planner.extruder_advance_K[0]);
SERIAL_ECHOPGM(
#if ENABLED(DISTINCT_E_FACTORS)
" M900 T", e, " K"
#else
" M900 K"
#endif
);
SERIAL_ECHO(planner.get_advance_k(e));
#if ENABLED(SMOOTH_LIN_ADVANCE)
SERIAL_ECHOPGM(" M900 U", stepper.get_advance_tau());
SERIAL_ECHOPGM(" U", stepper.get_advance_tau(e));
#endif
SERIAL_EOL();
#else
EXTRUDER_LOOP() {
report_echo_start(forReplay);
SERIAL_ECHOPGM(" M900 T", e, " K", planner.extruder_advance_K[e]);
#if ENABLED(SMOOTH_LIN_ADVANCE)
SERIAL_ECHOPGM(" U", stepper.get_advance_tau(e));
#endif
SERIAL_EOL();
}
#endif
}
}
#endif // LIN_ADVANCE

View file

@ -3725,7 +3725,7 @@ static_assert(NUM_SERVOS <= NUM_SERVO_PLUGS, "NUM_SERVOS (or some servo index) i
* Check per-axis initializers for errors
*/
#define __PLUS_TEST(I,A) && (sanity_arr_##A[_MIN(I,signed(COUNT(sanity_arr_##A)-1))] > 0)
#define __PLUS_TEST(I,A) && (sanity_arr_##A[ALIM(I,sanity_arr_##A)] > 0)
#define _PLUS_TEST(A) (1 REPEAT2(14,__PLUS_TEST,A))
#if HAS_MULTI_EXTRUDER
#define _EXTRA_NOTE " (Did you forget to enable DISTINCT_E_FACTORS?)"

View file

@ -2398,10 +2398,13 @@ void JyersDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool dra
case MOTION_LA:
if (draw) {
drawMenuItem(row, ICON_MaxAccelerated, GET_TEXT_F(MSG_ADVANCE_K));
drawFloat(planner.extruder_advance_K[0], row, false, 100);
drawFloat(planner.get_advance_k(), row, false, 100);
}
else
modifyValue(planner.extruder_advance_K[0], 0, 10, 100);
else {
static float k = planner.get_advance_k();
modifyValue(k, 0, 10, 100, []{ planner.set_advance_k(k); });
}
break;
#endif
}
@ -2914,10 +2917,12 @@ void JyersDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool dra
case ADVANCED_LA:
if (draw) {
drawMenuItem(row, ICON_MaxAccelerated, GET_TEXT_F(MSG_ADVANCE_K));
drawFloat(planner.extruder_advance_K[0], row, false, 100);
drawFloat(planner.get_advance_k(), row, false, 100);
}
else {
static float k = planner.get_advance_k();
modifyValue(k, 0, 10, 100, []{ planner.set_advance_k(k); });
}
else
modifyValue(planner.extruder_advance_K[0], 0, 10, 100);
break;
#endif
@ -3925,10 +3930,12 @@ void JyersDWIN::menuItemHandler(const uint8_t menu, const uint8_t item, bool dra
case TUNE_LA:
if (draw) {
drawMenuItem(row, ICON_MaxAccelerated, GET_TEXT_F(MSG_ADVANCE_K));
drawFloat(planner.extruder_advance_K[0], row, false, 100);
drawFloat(planner.get_advance_k(), row, false, 100);
}
else {
static float k = planner.get_advance_k();
modifyValue(k, 0, 10, 100, []{ planner.set_advance_k(k); });
}
else
modifyValue(planner.extruder_advance_K[0], 0, 10, 100);
break;
#endif

View file

@ -2686,7 +2686,8 @@ void applyMaxAccel() { planner.set_max_acceleration(hmiValue.axis, menuData.valu
#endif
#if ENABLED(LIN_ADVANCE)
void setLA_K() { setPFloatOnClick(0, 10, 3); }
void applyLA_K() { planner.set_advance_k(menuData.value / MINUNITMULT); }
void setLA_K() { setPFloatOnClick(0, 10, 3, applyLA_K); }
#endif
#if HAS_X_AXIS
@ -3545,7 +3546,7 @@ void drawTuneMenu() {
EDIT_ITEM(ICON_JDmm, MSG_JUNCTION_DEVIATION, onDrawPFloat3Menu, setJDmm, &planner.junction_deviation_mm);
#endif
#if ENABLED(PROUI_ITEM_ADVK)
EDIT_ITEM(ICON_MaxAccelerated, MSG_ADVANCE_K, onDrawPFloat3Menu, setLA_K, &planner.extruder_advance_K[0]);
EDIT_ITEM(ICON_MaxAccelerated, MSG_ADVANCE_K, onDrawPFloat3Menu, setLA_K, &planner.get_advance_k());
#endif
#if HAS_LOCKSCREEN
MENU_ITEM(ICON_Lock, MSG_LOCKSCREEN, onDrawMenuItem, dwinLockScreen);
@ -3683,7 +3684,7 @@ void drawMotionMenu() {
MENU_ITEM(ICON_Homing, MSG_HOMING_FEEDRATE, onDrawSubMenu, drawHomingFRMenu);
#endif
#if ENABLED(LIN_ADVANCE)
EDIT_ITEM(ICON_MaxAccelerated, MSG_ADVANCE_K, onDrawPFloat3Menu, setLA_K, &planner.extruder_advance_K[0]);
EDIT_ITEM(ICON_MaxAccelerated, MSG_ADVANCE_K, onDrawPFloat3Menu, setLA_K, &planner.get_advance_k());
#endif
#if ENABLED(SHAPING_MENU)
MENU_ITEM(ICON_InputShaping, MSG_INPUT_SHAPING, onDrawSubMenu, drawInputShaping_menu);

View file

@ -643,12 +643,12 @@ namespace ExtUI {
#if ENABLED(LIN_ADVANCE)
float getLinearAdvance_mm_mm_s(const extruder_t extruder) {
return (extruder < EXTRUDERS) ? planner.extruder_advance_K[E_INDEX_N(extruder - E0)] : 0;
return (extruder < EXTRUDERS) ? planner.get_advance_k(E_INDEX_N(extruder - E0)) : 0;
}
void setLinearAdvance_mm_mm_s(const_float_t value, const extruder_t extruder) {
if (extruder < EXTRUDERS)
planner.extruder_advance_K[E_INDEX_N(extruder - E0)] = constrain(value, 0, 10);
planner.set_advance_k(constrain(value, 0, 10), E_INDEX_N(extruder - E0));
}
#endif

View file

@ -117,10 +117,13 @@ void menu_backlash();
#if ENABLED(LIN_ADVANCE)
#if DISABLED(DISTINCT_E_FACTORS)
EDIT_ITEM(float42_52, MSG_ADVANCE_K, &planner.extruder_advance_K[0], 0, 10);
editable.decimal = planner.get_advance_k();
EDIT_ITEM(float42_52, MSG_ADVANCE_K, &editable.decimal, 0.0f, 10.0f, []{ planner.set_advance_k(editable.decimal); });
#else
EXTRUDER_LOOP()
EDIT_ITEM_N(float42_52, e, MSG_ADVANCE_K_E, &planner.extruder_advance_K[e], 0, 10);
EXTRUDER_LOOP() {
editable.decimal = planner.get_advance_k(e);
EDIT_ITEM_N(float42_52, e, MSG_ADVANCE_K_E, &editable.decimal, 0.0f, 10.0f, []{ planner.set_advance_k(editable.decimal, MenuItemBase::itemIndex); });
}
#endif
#if ENABLED(SMOOTH_LIN_ADVANCE)
#if DISABLED(DISTINCT_E_FACTORS)
@ -745,15 +748,19 @@ void menu_advanced_settings() {
#endif
#if HAS_ADV_FILAMENT_MENU
SUBMENU(MSG_FILAMENT, menu_advanced_filament);
#endif
#if ENABLED(LIN_ADVANCE) && DISABLED(HAS_ADV_FILAMENT_MENU)
SUBMENU(MSG_FILAMENT, menu_advanced_filament);
#elif ENABLED(LIN_ADVANCE)
#if DISABLED(DISTINCT_E_FACTORS)
EDIT_ITEM(float42_52, MSG_ADVANCE_K, &planner.extruder_advance_K[0], 0, 10);
editable.decimal = planner.get_advance_k();
EDIT_ITEM(float42_52, MSG_ADVANCE_K, &editable.decimal, 0.0f, 10.0f, []{ planner.set_advance_k(editable.decimal); });
#else
EXTRUDER_LOOP()
EDIT_ITEM_N(float42_52, e, MSG_ADVANCE_K_E, &planner.extruder_advance_K[e], 0, 10);
EXTRUDER_LOOP() {
editable.decimal = planner.get_advance_k(e);
EDIT_ITEM_N(float42_52, e, MSG_ADVANCE_K_E, &editable.decimal, 0.0f, 10.0f, []{ planner.set_advance_k(editable.decimal, MenuItemBase::itemIndex); });
}
#endif
#if ENABLED(SMOOTH_LIN_ADVANCE)
#if DISABLED(DISTINCT_E_FACTORS)
@ -766,7 +773,8 @@ void menu_advanced_settings() {
}
#endif
#endif
#endif
#endif // LIN_ADVANCE && !HAS_ADV_FILAMENT_MENU
// M540 S - Abort on endstop hit when SD printing
#if ENABLED(SD_ABORT_ON_ENDSTOP_HIT)

View file

@ -216,10 +216,13 @@ void menu_tune() {
//
#if ENABLED(LIN_ADVANCE) && DISABLED(SLIM_LCD_MENUS)
#if DISABLED(DISTINCT_E_FACTORS)
EDIT_ITEM(float42_52, MSG_ADVANCE_K, &planner.extruder_advance_K[0], 0, 10);
editable.decimal = planner.get_advance_k();
EDIT_ITEM(float42_52, MSG_ADVANCE_K, &editable.decimal, 0.0f, 10.0f, []{ planner.set_advance_k(editable.decimal); });
#else
EXTRUDER_LOOP()
EDIT_ITEM_N(float42_52, e, MSG_ADVANCE_K_E, &planner.extruder_advance_K[e], 0, 10);
EXTRUDER_LOOP() {
editable.decimal = planner.get_advance_k(e);
EDIT_ITEM_N(float42_52, e, MSG_ADVANCE_K_E, &editable.decimal, 0.0f, 10.0f, []{ planner.set_advance_k(editable.decimal, MenuItemBase::itemIndex); });
}
#endif
#if ENABLED(SMOOTH_LIN_ADVANCE)
#if DISABLED(DISTINCT_E_FACTORS)

View file

@ -894,7 +894,7 @@ void RTS::handleData() {
break;
case 4: // Go to Advanced Settings
TERN_(LIN_ADVANCE, sendData(planner.extruder_advance_K[0] * 100, Advance_K_VP));
TERN_(LIN_ADVANCE, sendData(planner.get_advance_k() * 100, Advance_K_VP));
gotoPage(ID_AdvWarn_L, ID_AdvWarn_D);
break;
@ -1292,7 +1292,7 @@ void RTS::handleData() {
#if ENABLED(LIN_ADVANCE)
case 7: // Confirm
sendData(planner.extruder_advance_K[0] * 100, Advance_K_VP);
sendData(planner.get_advance_k() * 100, Advance_K_VP);
gotoPage(ID_Advanced_L, ID_Advanced_D);
break;
#endif
@ -1350,7 +1350,7 @@ void RTS::handleData() {
#endif
case A_Retract: planner.settings.retract_acceleration = recdat.data[0]; break;
#if ENABLED(LIN_ADVANCE)
case Advance_K: planner.extruder_advance_K[0] = float(recdat.data[0]) / 100.0f; break;
case Advance_K: planner.set_advance_k(float(recdat.data[0]) / 100.0f); break;
#endif
#endif
case Accel: planner.settings.acceleration = recdat.data[0]; break;

View file

@ -181,44 +181,51 @@ void Touch::touch(touch_control_t *control) {
case SLIDER: hold(control); ui.encoderPosition = (x - control->x) * control->data / control->width; break;
case INCREASE: hold(control, repeat_delay - 5); TERN(AUTO_BED_LEVELING_UBL, ui.external_control ? bedlevel.encoder_diff++ : ui.encoderPosition++, ui.encoderPosition++); break;
case DECREASE: hold(control, repeat_delay - 5); TERN(AUTO_BED_LEVELING_UBL, ui.external_control ? bedlevel.encoder_diff-- : ui.encoderPosition--, ui.encoderPosition--); break;
case HEATER:
int8_t heater;
heater = control->data;
case HEATER: {
ui.clear_for_drawing();
#if HAS_HOTEND
if (heater >= 0) { // HotEnd
#if HOTENDS == 1
MenuItem_int3::action(GET_TEXT_F(MSG_NOZZLE), &thermalManager.temp_hotend[0].target, 0, thermalManager.hotend_max_target(0), []{ thermalManager.start_watching_hotend(0); });
#else
MenuItemBase::itemIndex = heater;
MenuItem_int3::action(GET_TEXT_F(MSG_NOZZLE_N), &thermalManager.temp_hotend[heater].target, 0, thermalManager.hotend_max_target(heater), []{ thermalManager.start_watching_hotend(MenuItemBase::itemIndex); });
const int8_t heater = control->data;
switch (heater) {
default: // Hotend
#if HAS_HOTEND
#define HOTEND_HEATER(N) TERN0(HAS_MULTI_HOTEND, N)
TERN_(HAS_MULTI_HOTEND, MenuItemBase::itemIndex = heater);
MenuItem_int3::action(GET_TEXT_F(TERN(HAS_MULTI_HOTEND, MSG_NOZZLE_N, MSG_NOZZLE)),
&thermalManager.temp_hotend[HOTEND_HEATER(heater)].target, 0, thermalManager.hotend_max_target(HOTEND_HEATER(heater)),
[]{ thermalManager.start_watching_hotend(HOTEND_HEATER(MenuItemBase::itemIndex)); }
);
#endif
}
#endif
#if HAS_HEATED_BED
else if (heater == H_BED) {
MenuItem_int3::action(GET_TEXT_F(MSG_BED), &thermalManager.temp_bed.target, 0, BED_MAX_TARGET, thermalManager.start_watching_bed);
}
#endif
#if HAS_HEATED_CHAMBER
else if (heater == H_CHAMBER) {
MenuItem_int3::action(GET_TEXT_F(MSG_CHAMBER), &thermalManager.temp_chamber.target, 0, CHAMBER_MAX_TARGET, thermalManager.start_watching_chamber);
}
#endif
#if HAS_COOLER
else if (heater == H_COOLER) {
MenuItem_int3::action(GET_TEXT_F(MSG_COOLER), &thermalManager.temp_cooler.target, 0, COOLER_MAX_TARGET, thermalManager.start_watching_cooler);
}
#endif
break;
break;
case FAN:
#if HAS_HEATED_BED
case H_BED:
MenuItem_int3::action(GET_TEXT_F(MSG_BED), &thermalManager.temp_bed.target, 0, BED_MAX_TARGET, thermalManager.start_watching_bed);
break;
#endif
#if HAS_HEATED_CHAMBER
case H_CHAMBER:
MenuItem_int3::action(GET_TEXT_F(MSG_CHAMBER), &thermalManager.temp_chamber.target, 0, CHAMBER_MAX_TARGET, thermalManager.start_watching_chamber);
break;
#endif
#if HAS_COOLER
case H_COOLER:
MenuItem_int3::action(GET_TEXT_F(MSG_COOLER), &thermalManager.temp_cooler.target, 0, COOLER_MAX_TARGET, thermalManager.start_watching_cooler);
break;
#endif
} // switch
} break;
case FAN: {
ui.clear_for_drawing();
static uint8_t fan, fan_speed;
fan = 0;
fan_speed = thermalManager.fan_speed[fan];
MenuItem_percent::action(GET_TEXT_F(MSG_FIRST_FAN_SPEED), &fan_speed, 0, 255, []{ thermalManager.set_fan_speed(fan, fan_speed); TERN_(LASER_SYNCHRONOUS_M106_M107, planner.buffer_sync_block(BLOCK_BIT_SYNC_FANS));});
break;
} break;
case FEEDRATE:
ui.clear_for_drawing();
MenuItem_int3::action(GET_TEXT_F(MSG_SPEED), &feedrate_percentage, SPEED_EDIT_MIN, SPEED_EDIT_MAX);
@ -228,11 +235,10 @@ void Touch::touch(touch_control_t *control) {
case FLOWRATE:
ui.clear_for_drawing();
MenuItemBase::itemIndex = control->data;
#if EXTRUDERS == 1
MenuItem_int3::action(GET_TEXT_F(MSG_FLOW), &planner.flow_percentage[MenuItemBase::itemIndex], FLOW_EDIT_MIN, FLOW_EDIT_MAX, []{ planner.refresh_e_factor(MenuItemBase::itemIndex); });
#else
MenuItem_int3::action(GET_TEXT_F(MSG_FLOW_N), &planner.flow_percentage[MenuItemBase::itemIndex], FLOW_EDIT_MIN, FLOW_EDIT_MAX, []{ planner.refresh_e_factor(MenuItemBase::itemIndex); });
#endif
MenuItem_int3::action(GET_TEXT_F(TERN(HAS_MULTI_EXTRUDER, MSG_FLOW_N, MSG_FLOW)),
&planner.flow_percentage[MenuItemBase::itemIndex], FLOW_EDIT_MIN, FLOW_EDIT_MAX,
[]{ planner.refresh_e_factor(MenuItemBase::itemIndex); }
);
break;
#endif

View file

@ -236,6 +236,9 @@ float Planner::previous_nominal_speed;
#if ENABLED(LIN_ADVANCE)
float Planner::extruder_advance_K[DISTINCT_E]; // Initialized by settings.load
#if ENABLED(SMOOTH_LIN_ADVANCE)
uint32_t Planner::extruder_advance_K_q27[DISTINCT_E];
#endif
#endif
#if HAS_POSITION_FLOAT
@ -2457,7 +2460,7 @@ bool Planner::_populate_block(
block->acceleration_steps_per_s2 = accel;
block->acceleration = accel / steps_per_mm;
#if DISABLED(S_CURVE_ACCELERATION)
block->acceleration_rate = uint32_t(accel * (float(1UL << 24) / (STEPPER_TIMER_RATE)));
block->acceleration_rate = uint32_t(accel * (float(_BV32(24)) / (STEPPER_TIMER_RATE)));
#endif
#if HAS_ROUGH_LIN_ADVANCE
@ -2478,7 +2481,13 @@ bool Planner::_populate_block(
}
#elif ENABLED(SMOOTH_LIN_ADVANCE)
block->use_advance_lead = use_advance_lead;
block->e_step_ratio = (block->direction_bits.e ? 1 : -1) * float(block->steps.e) / block->step_event_count;
const uint32_t ratio = (uint64_t(block->steps.e) * _BV32(30)) / block->step_event_count;
block->e_step_ratio_q30 = block->direction_bits.e ? ratio : -ratio;
#if ENABLED(INPUT_SHAPING_E_SYNC)
const uint32_t xy_steps = TERN0(INPUT_SHAPING_X, block->steps.x) + TERN0(INPUT_SHAPING_Y, block->steps.y);
block->xy_length_inv_q30 = xy_steps ? (_BV32(30) / xy_steps) : 0;
#endif
#endif
// Formula for the average speed over a 1 step worth of distance if starting from zero and

View file

@ -247,7 +247,10 @@ typedef struct PlannerBlock {
#if ENABLED(SMOOTH_LIN_ADVANCE)
uint32_t cruise_time; // Cruise time in STEP timer counts
float e_step_ratio;
int32_t e_step_ratio_q30; // Ratio of e steps to block steps.
#if ENABLED(INPUT_SHAPING_E_SYNC)
uint32_t xy_length_inv_q30; // inverse of block->steps.x + block.steps.y
#endif
#endif
#if ANY(S_CURVE_ACCELERATION, SMOOTH_LIN_ADVANCE)
uint32_t cruise_rate, // The actual cruise rate to use, between end of the acceleration phase and start of deceleration phase
@ -359,9 +362,8 @@ typedef struct PlannerSettings {
#if ENABLED(EDITABLE_STEPS_PER_UNIT)
float axis_steps_per_mm[DISTINCT_AXES];
#else
#define _DLIM(I) ALIM(I, _dasu)
#define _DASU(N) _dasu[_DLIM(N)],
#define _EASU(N) _dasu[_DLIM(E_AXIS + N)],
#define _DASU(N) _dasu[ALIM(N, _dasu)],
#define _EASU(N) _dasu[ALIM(E_AXIS + N, _dasu)],
static constexpr float axis_steps_per_mm[DISTINCT_AXES] = {
REPEAT(NUM_AXES, _DASU)
TERN_(HAS_EXTRUDERS, REPEAT(DISTINCT_E, _EASU))
@ -526,6 +528,23 @@ class Planner {
#if ENABLED(LIN_ADVANCE)
static float extruder_advance_K[DISTINCT_E];
static void set_advance_k(const_float_t k, const uint8_t e=active_extruder) {
UNUSED(e);
extruder_advance_K[E_INDEX_N(e)] = k;
#if ENABLED(SMOOTH_LIN_ADVANCE)
extruder_advance_K_q27[E_INDEX_N(e)] = k * (1UL << 27);
#endif
}
static float get_advance_k(const uint8_t e=active_extruder) {
UNUSED(e);
return extruder_advance_K[E_INDEX_N(e)];
}
#if ENABLED(SMOOTH_LIN_ADVANCE)
static uint32_t get_advance_k_q27(const uint8_t e=active_extruder) {
UNUSED(e);
return extruder_advance_K_q27[E_INDEX_N(e)];
}
#endif
#endif
/**
@ -602,6 +621,10 @@ class Planner {
volatile static uint32_t block_buffer_runtime_us; // Theoretical block buffer runtime in µs
#endif
#if ENABLED(SMOOTH_LIN_ADVANCE)
static uint32_t extruder_advance_K_q27[DISTINCT_E];
#endif
public:
/**

View file

@ -126,7 +126,7 @@
#endif
#if ENABLED(ADVANCE_K_EXTRA)
extern float other_extruder_advance_K[DISTINCT_E];
extern float other_extruder_advance_K[EXTRUDERS];
#endif
#if HAS_MULTI_EXTRUDER
@ -2641,18 +2641,14 @@ void MarlinSettings::postprocess() {
_FIELD_TEST(planner_extruder_advance_K);
EEPROM_READ(extruder_advance_K);
if (!validating)
COPY(planner.extruder_advance_K, extruder_advance_K);
DISTINCT_E_LOOP() planner.set_advance_k(extruder_advance_K[e], e);
#if ENABLED(SMOOTH_LIN_ADVANCE)
_FIELD_TEST(stepper_extruder_advance_tau);
float tau[DISTINCT_E];
EEPROM_READ(tau);
if (!validating) {
#if ENABLED(DISTINCT_E_FACTORS)
EXTRUDER_LOOP() stepper.set_advance_tau(tau[e], e);
#else
stepper.set_advance_tau(tau[0]);
#endif
}
if (!validating)
DISTINCT_E_LOOP() stepper.set_advance_tau(tau[e], e);
#endif
}
#endif
@ -3615,21 +3611,23 @@ void MarlinSettings::reset() {
#if ENABLED(DISTINCT_E_FACTORS)
constexpr float linAdvanceK[] = ADVANCE_K;
EXTRUDER_LOOP() {
const float a = linAdvanceK[ALIM(e, linAdvanceK)];
planner.extruder_advance_K[e] = a;
TERN_(ADVANCE_K_EXTRA, other_extruder_advance_K[e] = a);
}
#else
planner.extruder_advance_K[0] = ADVANCE_K;
#endif
#if ENABLED(SMOOTH_LIN_ADVANCE)
#if ENABLED(DISTINCT_E_FACTORS)
#if ENABLED(SMOOTH_LIN_ADVANCE)
constexpr float linAdvanceTau[] = ADVANCE_TAU;
EXTRUDER_LOOP()
stepper.set_advance_tau(linAdvanceTau[ALIM(e, linAdvanceTau)], e);
#else
stepper.set_advance_tau(ADVANCE_TAU);
#endif
EXTRUDER_LOOP() {
const float k = linAdvanceK[ALIM(e, linAdvanceK)];
planner.set_advance_k(k, e);
TERN_(SMOOTH_LIN_ADVANCE, stepper.set_advance_tau(linAdvanceTau[ALIM(e, linAdvanceTau)], e));
TERN_(ADVANCE_K_EXTRA, other_extruder_advance_K[e] = k);
}
#else // !DISTINCT_E_FACTORS
planner.set_advance_k(ADVANCE_K);
TERN_(SMOOTH_LIN_ADVANCE, stepper.set_advance_tau(ADVANCE_TAU));
#if ENABLED(ADVANCE_K_EXTRA)
EXTRUDER_LOOP() other_extruder_advance_K[e] = ADVANCE_K;
#endif
#endif

View file

@ -1510,6 +1510,10 @@ HAL_STEP_TIMER_ISR() {
#define STEP_MULTIPLY(A,B) MultiU24X32toH16(A, B)
#endif
#if ENABLED(SMOOTH_LIN_ADVANCE)
FORCE_INLINE static constexpr int32_t MULT_Q(uint8_t q, int32_t x, int32_t y) { return (int64_t(x) * y) >> q; }
#endif
void Stepper::isr() {
static hal_timer_t nextMainISR = 0; // Interval until the next main Stepper Pulse phase (0 = Now)
@ -2877,9 +2881,9 @@ hal_timer_t Stepper::block_phase_isr() {
#if ENABLED(SMOOTH_LIN_ADVANCE)
float Stepper::extruder_advance_tau[DISTINCT_E],
Stepper::extruder_advance_tau_ticks[DISTINCT_E],
Stepper::extruder_advance_alpha[DISTINCT_E];
float Stepper::extruder_advance_tau[DISTINCT_E];
uint32_t Stepper::extruder_advance_tau_ticks[DISTINCT_E],
Stepper::extruder_advance_alpha_q30[DISTINCT_E];
void Stepper::set_la_interval(const int32_t rate) {
if (rate == 0) {
@ -2904,117 +2908,119 @@ hal_timer_t Stepper::block_phase_isr() {
constexpr uint16_t IS_COMPENSATION_BUFFER_SIZE = uint16_t(float(SMOOTH_LIN_ADV_HZ) / float(SHAPING_MIN_FREQ) / 2.0f + 0.5f);
typedef struct {
xy_float_t buffer[IS_COMPENSATION_BUFFER_SIZE];
xy_long_t buffer[IS_COMPENSATION_BUFFER_SIZE];
uint16_t index;
FORCE_INLINE void add(const xy_long_t &input) {
buffer[index] = input;
if (++index == IS_COMPENSATION_BUFFER_SIZE) index = 0;
}
FORCE_INLINE xy_long_t past_item(const uint16_t n) {
const int16_t i = int16_t(index) - n;
return buffer[i >= 0 ? i : i + IS_COMPENSATION_BUFFER_SIZE];
}
} DelayBuffer;
DelayBuffer delayBuffer;
void add_to_buffer(xy_float_t input) {
delayBuffer.buffer[delayBuffer.index++] = input;
if (delayBuffer.index == IS_COMPENSATION_BUFFER_SIZE)
delayBuffer.index = 0;
}
xy_float_t lookback(shaping_time_t t /* in stepper timer ticks */) {
constexpr float ADV_TICKS_PER_STEPPER_TICKS = float(SMOOTH_LIN_ADV_HZ) / (STEPPER_TIMER_RATE);
uint32_t delay_steps = t * ADV_TICKS_PER_STEPPER_TICKS + 0.5f; // Convert time to steps
uint16_t past_i;
if (delay_steps>= IS_COMPENSATION_BUFFER_SIZE) {
// this means the buffer is too small. TODO: how to inform user?
past_i = delayBuffer.index;
}
else {
past_i = (delayBuffer.index + IS_COMPENSATION_BUFFER_SIZE - delay_steps) % IS_COMPENSATION_BUFFER_SIZE;
}
return delayBuffer.buffer[past_i];
xy_long_t smooth_lin_adv_lookback(const shaping_time_t stepper_ticks) {
constexpr uint32_t ADV_TICKS_PER_STEPPER_TICKS_Q30 = (uint64_t(SMOOTH_LIN_ADV_HZ) * _BV32(30)) / STEPPER_TIMER_RATE;
const uint16_t delay_steps = MULT_Q(30, stepper_ticks, ADV_TICKS_PER_STEPPER_TICKS_Q30);
return delayBuffer.past_item(delay_steps);
}
#endif // INPUT_SHAPING_E_SYNC
float lookahead(uint32_t t) {
int32_t smooth_lin_adv_lookahead(uint32_t stepper_ticks) {
for (uint8_t i = 0; block_t *block = planner.get_future_block(i); i++) {
if (block->is_sync()) continue;
if (t <= block->acceleration_time) {
if (!block->use_advance_lead) return 0.0f;
uint32_t rate = STEP_MULTIPLY(t, block->acceleration_rate) + block->initial_rate;
if (stepper_ticks <= block->acceleration_time) {
if (!block->use_advance_lead) return 0;
uint32_t rate = STEP_MULTIPLY(stepper_ticks, block->acceleration_rate) + block->initial_rate;
NOMORE(rate, block->nominal_rate);
return rate * block->e_step_ratio;
return MULT_Q(30, rate, block->e_step_ratio_q30);
}
t -= block->acceleration_time;
stepper_ticks -= block->acceleration_time;
if (t <= block->cruise_time) {
if (!block->use_advance_lead) return 0.0f;
return block->cruise_rate * block->e_step_ratio;
if (stepper_ticks <= block->cruise_time) {
if (!block->use_advance_lead) return 0;
return MULT_Q(30, block->cruise_rate, block->e_step_ratio_q30);
}
t -= block->cruise_time;
stepper_ticks -= block->cruise_time;
if (t <= block->deceleration_time) {
if (!block->use_advance_lead) return 0.0f;
uint32_t rate = STEP_MULTIPLY(t, block->acceleration_rate);
if (stepper_ticks <= block->deceleration_time) {
if (!block->use_advance_lead) return 0;
uint32_t rate = STEP_MULTIPLY(stepper_ticks, block->acceleration_rate);
if (rate < block->cruise_rate) {
rate = block->cruise_rate - rate;
NOLESS(rate, block->final_rate);
}
else
rate = block->final_rate;
return rate * block->e_step_ratio;
return MULT_Q(30, rate, block->e_step_ratio_q30);
}
t -= block->deceleration_time;
stepper_ticks -= block->deceleration_time;
}
return 0.0f;
return 0;
}
hal_timer_t Stepper::smooth_lin_adv_isr() {
float target_adv_steps = 0;
int32_t target_adv_steps = 0;
if (current_block) {
const uint32_t t = extruder_advance_tau_ticks[0] + curr_timer_tick;
target_adv_steps = lookahead(t) * planner.extruder_advance_K[0];
const uint32_t stepper_ticks = extruder_advance_tau_ticks[E_INDEX_N(active_extruder)] + curr_timer_tick;
target_adv_steps = MULT_Q(27, smooth_lin_adv_lookahead(stepper_ticks), planner.get_advance_k_q27());
}
else {
curr_step_rate = 0;
}
static float last_target_adv_steps = 0;
constexpr float dt_inv = SMOOTH_LIN_ADV_HZ;
float la_step_rate = (target_adv_steps - last_target_adv_steps) * dt_inv;
static int32_t last_target_adv_steps = 0;
constexpr uint16_t dt_inv = SMOOTH_LIN_ADV_HZ;
int32_t la_step_rate = (target_adv_steps - last_target_adv_steps) * dt_inv;
last_target_adv_steps = target_adv_steps;
static float smoothed_vals[SMOOTH_LIN_ADV_EXP_ORDER] = {0};
static int32_t smoothed_vals[SMOOTH_LIN_ADV_EXP_ORDER] = {0};
for (uint8_t i = 0; i < SMOOTH_LIN_ADV_EXP_ORDER; i++) {
// Approximate gaussian smoothing via higher order exponential smoothing
la_step_rate = extruder_advance_alpha[0] * la_step_rate + (1 - extruder_advance_alpha[0]) * smoothed_vals[i];
smoothed_vals[i] = la_step_rate;
smoothed_vals[i] += MULT_Q(30, la_step_rate - smoothed_vals[i], extruder_advance_alpha_q30[E_INDEX_N(active_extruder)]);
la_step_rate = smoothed_vals[i];
}
const float planned_step_rate = current_block ? curr_step_rate * current_block->e_step_ratio : 0;
float total_step_rate = la_step_rate + planned_step_rate;
const int32_t planned_step_rate = current_block
? MULT_Q(30, curr_step_rate, current_block->e_step_ratio_q30)
: 0;
int32_t total_step_rate = la_step_rate + planned_step_rate;
#if ENABLED(INPUT_SHAPING_E_SYNC)
xy_float_t pre_shaping_rate = xy_float_t({0, 0}),
first_pulse_rate = xy_float_t({0, 0});
float unshaped_rate_e = total_step_rate;
xy_long_t pre_shaping_rate = xy_long_t({0, 0}),
first_pulse_rate = xy_long_t({0, 0});
int32_t unshaped_rate_e = total_step_rate;
if (current_block) {
const float xy_length = TERN0(INPUT_SHAPING_X, current_block->steps.x) + TERN0(INPUT_SHAPING_Y, current_block->steps.y);
if (xy_length > 0) {
if (current_block->xy_length_inv_q30 > 0) {
unshaped_rate_e = 0;
pre_shaping_rate = xy_float_t({
TERN0(INPUT_SHAPING_X, total_step_rate * current_block->steps.x / xy_length),
TERN0(INPUT_SHAPING_Y, total_step_rate * current_block->steps.y / xy_length)
pre_shaping_rate = xy_long_t({
TERN0(INPUT_SHAPING_X, MULT_Q(30, total_step_rate * current_block->steps.x, current_block->xy_length_inv_q30)),
TERN0(INPUT_SHAPING_Y, MULT_Q(30, total_step_rate * current_block->steps.y, current_block->xy_length_inv_q30))
});
first_pulse_rate = xy_float_t({
TERN0(INPUT_SHAPING_X, pre_shaping_rate.x * Stepper::shaping_x.factor1 / 128.0f),
TERN0(INPUT_SHAPING_Y, pre_shaping_rate.y * Stepper::shaping_y.factor1 / 128.0f)
first_pulse_rate = xy_long_t({
TERN0(INPUT_SHAPING_X, (pre_shaping_rate.x * Stepper::shaping_x.factor1) >> 7),
TERN0(INPUT_SHAPING_Y, (pre_shaping_rate.y * Stepper::shaping_y.factor1) >> 7)
});
}
}
const xy_float_t second_pulse_rate = {
TERN0(INPUT_SHAPING_X, lookback(ShapingQueue::get_delay_x()).x * Stepper::shaping_x.factor2 / 128.0f),
TERN0(INPUT_SHAPING_Y, lookback(ShapingQueue::get_delay_y()).y * Stepper::shaping_y.factor2 / 128.0f)
};
add_to_buffer(pre_shaping_rate);
const float x = TERN0(INPUT_SHAPING_X, first_pulse_rate.x + second_pulse_rate.x),
y = TERN0(INPUT_SHAPING_Y, first_pulse_rate.y + second_pulse_rate.y);
const xy_long_t second_pulse_rate = {
TERN0(INPUT_SHAPING_X, (smooth_lin_adv_lookback(ShapingQueue::get_delay_x()).x * Stepper::shaping_x.factor2)) >> 7,
TERN0(INPUT_SHAPING_Y, (smooth_lin_adv_lookback(ShapingQueue::get_delay_y()).y * Stepper::shaping_y.factor2)) >> 7
};
delayBuffer.add(pre_shaping_rate);
const int32_t x = TERN0(INPUT_SHAPING_X, first_pulse_rate.x + second_pulse_rate.x),
y = TERN0(INPUT_SHAPING_Y, first_pulse_rate.y + second_pulse_rate.y);
total_step_rate = unshaped_rate_e + x + y;
@ -3025,6 +3031,7 @@ hal_timer_t Stepper::block_phase_isr() {
curr_timer_tick += SMOOTH_LIN_ADV_INTERVAL;
return SMOOTH_LIN_ADV_INTERVAL;
}
#endif // SMOOTH_LIN_ADVANCE
// Timer interrupt for E. LA_steps is set in the main routine

View file

@ -352,13 +352,18 @@ class Stepper {
#endif
#if ENABLED(SMOOTH_LIN_ADVANCE)
static void set_advance_tau(const_float_t tau, const uint8_t e=E_INDEX_N(active_extruder)) {
extruder_advance_tau[e] = tau;
extruder_advance_tau_ticks[e] = tau * (STEPPER_TIMER_RATE); // i.e., <= STEPPER_TIMER_RATE / 2
static float extruder_advance_tau[DISTINCT_E]; // Smoothing time; also the lookahead time of the smoother
static void set_advance_tau(const_float_t tau, const uint8_t e=active_extruder) {
const uint8_t i = E_INDEX_N(e);
extruder_advance_tau[i] = tau;
extruder_advance_tau_ticks[i] = tau * STEPPER_TIMER_RATE;
// α=1exp(dt/τ)
extruder_advance_alpha[e] = 1.0f - expf(-(SMOOTH_LIN_ADV_INTERVAL) * (SMOOTH_LIN_ADV_EXP_ORDER) / extruder_advance_tau_ticks[e]);
const float alpha_float = 1.0f - expf(-float(SMOOTH_LIN_ADV_INTERVAL) * (SMOOTH_LIN_ADV_EXP_ORDER) / extruder_advance_tau_ticks[i]);
extruder_advance_alpha_q30[i] = int32_t(alpha_float * _BV32(30));
}
static float get_advance_tau(const uint8_t e=active_extruder) {
return extruder_advance_tau[E_INDEX_N(e)];
}
static float get_advance_tau(const uint8_t e=E_INDEX_N(active_extruder)) { return extruder_advance_tau[e]; }
#endif
private:
@ -449,12 +454,11 @@ class Stepper {
static hal_timer_t nextAdvanceISR,
la_interval; // Interval between ISR calls for LA
#if ENABLED(SMOOTH_LIN_ADVANCE)
static uint32_t curr_timer_tick, // Current tick relative to block start
curr_step_rate; // Current motion step rate
static float extruder_advance_tau[DISTINCT_E], // Smoothing time; also the lookahead time of the smoother
extruder_advance_tau_ticks[DISTINCT_E], // Same as extruder_advance_tau but in in stepper timer ticks
extruder_advance_alpha[DISTINCT_E]; // The smoothing factor of each stage of the high-order exponential
// smoothing filter (calculated from tau)
static uint32_t curr_timer_tick, // Current tick relative to block start
curr_step_rate; // Current motion step rate
static uint32_t extruder_advance_tau_ticks[DISTINCT_E], // Same as extruder_advance_tau but in in stepper timer ticks
extruder_advance_alpha_q30[DISTINCT_E]; // The smoothing factor of each stage of the high-order exponential
// smoothing filter (calculated from tau)
#else
static int32_t la_delta_error, // Analogue of delta_error.e for E steps in LA ISR
la_dividend, // Analogue of advance_dividend.e for E steps in LA ISR

View file

@ -1587,7 +1587,7 @@ void tool_change(const uint8_t new_tool, bool no_move/*=false*/) {
#endif
// Migrate Linear Advance K factor to the new extruder
TERN_(LIN_ADVANCE, planner.extruder_advance_K[active_extruder] = planner.extruder_advance_K[migration_extruder]);
TERN_(LIN_ADVANCE, planner.set_advance_k(planner.get_advance_k(migration_extruder), active_extruder));
// Temporary migration toolchange_settings restored on exit. i.e., before next tool_change().
#if defined(MIGRATION_FS_EXTRA_PRIME) \