Repository: MarlinFirmware/Marlin
Pull request #27861: SMOOTH_LIN_ADVANCE + NONLINEAR_EXTRUSION
Author: login: vovodroid
Labels: name: C: Motion
name: C: Optimization
Created: 2025-05-16T06:49:57Z | Merged: 2025-05-20T19:52:51Z

Implement SMOOTH_LIN_ADVANCE + NONLINEAR_EXTRUSION ( See **Forbid SMOOTH_LIN_ADVANCE + NONLINEAR_EXTRUSION #27817**)


Diff:
diff --git a/Marlin/src/HAL/GD32_MFL/timers.h b/Marlin/src/HAL/GD32_MFL/timers.h
index 49d005b8cd22..a5d36d9eca4d 100644
--- a/Marlin/src/HAL/GD32_MFL/timers.h
+++ b/Marlin/src/HAL/GD32_MFL/timers.h
@@ -44,9 +44,9 @@
 extern uint32_t GetStepperTimerClkFreq();
 
 // Timer prescaler calculations
-#define STEPPER_TIMER_PRESCALE      (GetStepperTimerClkFreq() / STEPPER_TIMER_RATE)	// Prescaler = 30
+#define STEPPER_TIMER_PRESCALE      (GetStepperTimerClkFreq() / STEPPER_TIMER_RATE) // Prescaler = 30
 #define PULSE_TIMER_PRESCALE        STEPPER_TIMER_PRESCALE
-#define STEPPER_TIMER_TICKS_PER_US  ((STEPPER_TIMER_RATE) / 1000000)				        // Stepper timer ticks per µs
+#define STEPPER_TIMER_TICKS_PER_US  ((STEPPER_TIMER_RATE) / 1000000)                // Stepper timer ticks per µs
 #define PULSE_TIMER_RATE            STEPPER_TIMER_RATE
 #define PULSE_TIMER_TICKS_PER_US    STEPPER_TIMER_TICKS_PER_US
 
@@ -57,7 +57,7 @@ extern uint32_t GetStepperTimerClkFreq();
 #define ENABLE_STEPPER_DRIVER_INTERRUPT()   HAL_timer_enable_interrupt(MF_TIMER_STEP)
 #define DISABLE_STEPPER_DRIVER_INTERRUPT()  HAL_timer_disable_interrupt(MF_TIMER_STEP)
 #define STEPPER_ISR_ENABLED()               HAL_timer_interrupt_enabled(MF_TIMER_STEP)
-#define ENABLE_TEMPERATURE_INTERRUPT()		  HAL_timer_enable_interrupt(MF_TIMER_TEMP)
+#define ENABLE_TEMPERATURE_INTERRUPT()      HAL_timer_enable_interrupt(MF_TIMER_TEMP)
 #define DISABLE_TEMPERATURE_INTERRUPT()     HAL_timer_disable_interrupt(MF_TIMER_TEMP)
 
 extern void Step_Handler();
diff --git a/Marlin/src/gcode/feature/nonlinear/M592.cpp b/Marlin/src/gcode/feature/nonlinear/M592.cpp
index 77a6258ddc94..78c15443f86c 100644
--- a/Marlin/src/gcode/feature/nonlinear/M592.cpp
+++ b/Marlin/src/gcode/feature/nonlinear/M592.cpp
@@ -49,6 +49,12 @@ void GcodeSuite::M592() {
   if (parser.seenval('A')) stepper.ne.A = parser.value_float();
   if (parser.seenval('B')) stepper.ne.B = parser.value_float();
   if (parser.seenval('C')) stepper.ne.C = parser.value_float();
+
+  #if ENABLED(SMOOTH_LIN_ADVANCE)
+    stepper.ne_q30.A = _BV32(30) * (stepper.ne.A * planner.mm_per_step[E_AXIS_N(0)] * planner.mm_per_step[E_AXIS_N(0)]);
+    stepper.ne_q30.B = _BV32(30) * (stepper.ne.B * planner.mm_per_step[E_AXIS_N(0)]);
+    stepper.ne_q30.C = _BV32(30) * stepper.ne.C;
+  #endif
 }
 
 #endif // NONLINEAR_EXTRUSION
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 97cc2ed19819..c70c5ce0ec15 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -868,8 +868,6 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
       #error "SMOOTH_LIN_ADVANCE requires a 32-bit CPU."
     #elif ENABLED(S_CURVE_ACCELERATION)
       #error "SMOOTH_LIN_ADVANCE is not compatible with S_CURVE_ACCELERATION."
-    #elif ENABLED(NONLINEAR_EXTRUSION)
-      #error "SMOOTH_LIN_ADVANCE doesn't currently support NONLINEAR_EXTRUSION."
     #elif ENABLED(INPUT_SHAPING_E_SYNC) && NONE(INPUT_SHAPING_X, INPUT_SHAPING_Y)
       #error "INPUT_SHAPING_E_SYNC requires INPUT_SHAPING_X or INPUT_SHAPING_Y."
     #endif
diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp
index fe497058d922..5e1c08f86345 100644
--- a/Marlin/src/module/planner.cpp
+++ b/Marlin/src/module/planner.cpp
@@ -3252,6 +3252,10 @@ void Planner::refresh_acceleration_rates() {
 void Planner::refresh_positioning() {
   #if ENABLED(EDITABLE_STEPS_PER_UNIT)
     LOOP_DISTINCT_AXES(i) mm_per_step[i] = 1.0f / settings.axis_steps_per_mm[i];
+    #if ALL(NONLINEAR_EXTRUSION, SMOOTH_LIN_ADVANCE)
+      stepper.ne_q30.A = _BV32(30) * (stepper.ne.A * mm_per_step[E_AXIS_N(0)] * mm_per_step[E_AXIS_N(0)]);
+      stepper.ne_q30.B = _BV32(30) * (stepper.ne.B * mm_per_step[E_AXIS_N(0)]);
+    #endif
   #endif
   set_position_mm(current_position);
   refresh_acceleration_rates();
diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h
index 0a3a35d92290..c71a73c5be02 100644
--- a/Marlin/src/module/planner.h
+++ b/Marlin/src/module/planner.h
@@ -531,9 +531,7 @@ class Planner {
       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
+        TERN_(SMOOTH_LIN_ADVANCE, extruder_advance_K_q27[E_INDEX_N(e)] = k * _BV32(27));
       }
       static float get_advance_k(const uint8_t e=active_extruder) {
         UNUSED(e);
diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp
index 04c796acb341..a6931c8d0d10 100644
--- a/Marlin/src/module/stepper.cpp
+++ b/Marlin/src/module/stepper.cpp
@@ -257,9 +257,16 @@ uint32_t Stepper::advance_divisor = 0,
 
 #if ENABLED(NONLINEAR_EXTRUSION)
   ne_coeff_t Stepper::ne;
-  ne_fix_t Stepper::ne_fix;
-  int32_t Stepper::ne_edividend;
-  uint32_t Stepper::ne_scale;
+  #if NONLINEAR_EXTRUSION_Q24
+    ne_q24_t Stepper::ne_q24;
+  #else
+    ne_q30_t Stepper::ne_q30;
+  #endif
+  // private:
+  #if NONLINEAR_EXTRUSION_Q24
+    int32_t Stepper::ne_edividend;
+    uint32_t Stepper::ne_scale_q24;
+  #endif
 #endif
 
 #if HAS_ZV_SHAPING
@@ -2241,13 +2248,13 @@ hal_timer_t Stepper::calc_timer_interval(uint32_t step_rate) {
   #endif // !CPU_32_BIT
 }
 
-#if ENABLED(NONLINEAR_EXTRUSION)
-  void Stepper::calc_nonlinear_e(uint32_t step_rate) {
-    const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
-    int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
-    NOLESS(vd, 0);
+#if NONLINEAR_EXTRUSION_Q24
+  void Stepper::calc_nonlinear_e(const uint32_t step_rate) {
+    const uint32_t velocity_q24 = ne_scale_q24 * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+    int32_t vd_q24 = (((((int64_t)ne_q24.A * velocity_q24) >> 24) * velocity_q24) >> 24) + (((int64_t)ne_q24.B * velocity_q24) >> 24);
+    NOLESS(vd_q24, 0);
 
-    advance_dividend.e = (uint64_t(ne_fix.C + vd) * ne_edividend) >> 24;
+    advance_dividend.e = (uint64_t(ne_q24.C + vd_q24) * ne_edividend) >> 24;
   }
 #endif
 
@@ -2463,9 +2470,7 @@ hal_timer_t Stepper::block_phase_isr() {
         acceleration_time += interval;
         deceleration_time = 0; // Reset since we're doing acceleration first.
 
-        #if ENABLED(NONLINEAR_EXTRUSION)
-          calc_nonlinear_e(acc_step_rate << oversampling_factor);
-        #endif
+        calc_nonlinear_e(acc_step_rate << oversampling_factor);
 
         #if HAS_ROUGH_LIN_ADVANCE
           if (la_active) {
@@ -2529,9 +2534,7 @@ hal_timer_t Stepper::block_phase_isr() {
         interval = calc_multistep_timer_interval(step_rate << oversampling_factor);
         deceleration_time += interval;
 
-        #if ENABLED(NONLINEAR_EXTRUSION)
-          calc_nonlinear_e(step_rate << oversampling_factor);
-        #endif
+        calc_nonlinear_e(step_rate << oversampling_factor);
 
         #if HAS_ROUGH_LIN_ADVANCE
           if (la_active) {
@@ -2584,9 +2587,7 @@ hal_timer_t Stepper::block_phase_isr() {
           TERN_(SMOOTH_LIN_ADVANCE, curr_step_rate = current_block->nominal_rate;)
           deceleration_time = ticks_nominal / 2;
 
-          #if ENABLED(NONLINEAR_EXTRUSION)
-            calc_nonlinear_e(current_block->nominal_rate << oversampling_factor);
-          #endif
+          calc_nonlinear_e(current_block->nominal_rate << oversampling_factor);
 
           #if HAS_ROUGH_LIN_ADVANCE
             if (la_active)
@@ -2836,18 +2837,18 @@ hal_timer_t Stepper::block_phase_isr() {
         acc_step_rate = current_block->initial_rate;
       #endif
 
-      #if ENABLED(NONLINEAR_EXTRUSION)
+      #if NONLINEAR_EXTRUSION_Q24
         ne_edividend = advance_dividend.e;
         const float scale = (float(ne_edividend) / advance_divisor) * planner.mm_per_step[E_AXIS_N(current_block->extruder)];
-        ne_scale = (1L << 24) * scale;
+        ne_scale_q24 = _BV32(24) * scale;
         if (current_block->direction_bits.e && ANY_AXIS_MOVES(current_block)) {
-          ne_fix.A = (1L << 24) * ne.A;
-          ne_fix.B = (1L << 24) * ne.B;
-          ne_fix.C = (1L << 24) * ne.C;
+          ne_q24.A = _BV32(24) * ne.A;
+          ne_q24.B = _BV32(24) * ne.B;
+          ne_q24.C = _BV32(24) * ne.C;
         }
         else {
-          ne_fix.A = ne_fix.B = 0;
-          ne_fix.C = (1L << 24);
+          ne_q24.A = ne_q24.B = 0;
+          ne_q24.C = _BV32(24);
         }
       #endif
 
@@ -2856,9 +2857,7 @@ hal_timer_t Stepper::block_phase_isr() {
       // Initialize ac/deceleration time as if half the time passed.
       acceleration_time = deceleration_time = interval / 2;
 
-      #if ENABLED(NONLINEAR_EXTRUSION)
-        calc_nonlinear_e(current_block->initial_rate << oversampling_factor);
-      #endif
+      calc_nonlinear_e(current_block->initial_rate << oversampling_factor);
 
       #if ENABLED(LIN_ADVANCE)
         #if ENABLED(SMOOTH_LIN_ADVANCE)
@@ -2885,13 +2884,23 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e && ANY_AXIS_MOVES(current_block)) {
+            // Maximum polynomial value is just above 1, like 1.05..1.2, less than 2 anyway, so we can use 30 bits for fractional part
+            int32_t vd_q30 = ne_q30.A*step_rate*step_rate + ne_q30.B*step_rate;
+            NOLESS(vd_q30, 0);
+            step_rate = (int64_t(step_rate) * (ne_q30.C + vd_q30)) >> 30;
+          }
+        #endif
+
+        la_interval = calc_timer_interval(uint32_t(ABS(step_rate)));
         if (forward_e != motor_direction(E_AXIS)) {
           last_direction_bits.toggle(E_AXIS);
           count_direction.e = -count_direction.e;
diff --git a/Marlin/src/module/stepper.h b/Marlin/src/module/stepper.h
index 3fb0b44884f8..9495a1813959 100644
--- a/Marlin/src/module/stepper.h
+++ b/Marlin/src/module/stepper.h
@@ -285,7 +285,12 @@ constexpr ena_mask_t enable_overlap[] = {
 
 #if ENABLED(NONLINEAR_EXTRUSION)
   typedef struct { float A, B, C; void reset() { A = B = 0.0f; C = 1.0f; } } ne_coeff_t;
-  typedef struct { int32_t A, B, C; } ne_fix_t;
+  #if DISABLED(SMOOTH_LIN_ADVANCE)
+    #define NONLINEAR_EXTRUSION_Q24 1
+    typedef struct { int32_t A, B, C; } ne_q24_t;
+  #else
+    typedef struct { int32_t A, B, C; } ne_q30_t;
+  #endif
 #endif
 
 //
@@ -343,6 +348,11 @@ class Stepper {
 
     #if ENABLED(NONLINEAR_EXTRUSION)
       static ne_coeff_t ne;
+      #if NONLINEAR_EXTRUSION_Q24
+        static ne_q24_t ne_q24;
+      #else
+        static ne_q30_t ne_q30;
+      #endif
     #endif
 
     #if ENABLED(ADAPTIVE_STEP_SMOOTHING_TOGGLE)
@@ -467,10 +477,9 @@ class Stepper {
       #endif
     #endif
 
-    #if ENABLED(NONLINEAR_EXTRUSION)
+    #if NONLINEAR_EXTRUSION_Q24
       static int32_t ne_edividend;
-      static uint32_t ne_scale;
-      static ne_fix_t ne_fix;
+      static uint32_t ne_scale_q24;
     #endif
 
     #if ENABLED(BABYSTEPPING)
@@ -531,7 +540,7 @@ class Stepper {
       // The Linear advance ISR phase
       static void advance_isr();
       #if ENABLED(SMOOTH_LIN_ADVANCE)
-        static void set_la_interval(const int32_t rate);
+        static void set_la_interval(int32_t step_rate);
         static hal_timer_t smooth_lin_adv_isr();
       #endif
     #endif
@@ -738,8 +747,10 @@ class Stepper {
     // Evaluate axis motions and set bits in axis_did_move
     static void set_axis_moved_for_current_block();
 
-    #if ENABLED(NONLINEAR_EXTRUSION)
-      static void calc_nonlinear_e(uint32_t step_rate);
+    #if NONLINEAR_EXTRUSION_Q24
+      static void calc_nonlinear_e(const uint32_t step_rate);
+    #else
+      static void calc_nonlinear_e(const uint32_t) {}
     #endif
 
     #if ENABLED(S_CURVE_ACCELERATION)
diff --git a/README-PT-BR.md b/README-PT-BR.md
index 8d8b18828ffa..3c730967ae49 100644
--- a/README-PT-BR.md
+++ b/README-PT-BR.md
@@ -14,7 +14,7 @@
     <a href="https://fosstodon.org/@marlinfirmware"><img alt="Siga MarlinFirmware no Mastodon" src="https://img.shields.io/mastodon/follow/109450200866020466?domain=https%3A%2F%2Ffosstodon.org&logoColor=%2300B&style=social"></a>
 </p>
 
-Documentação adicional pode ser encontrada na [Página Inicial do Marlin](//marlinfw.org/).  
+Documentação adicional pode ser encontrada na [Página Inicial do Marlin](//marlinfw.org/).
 Por favor, teste este firmware e nos avise se encontrar algum problema. Voluntários estão prontos para ajudar!
 
 ## Branch de Correções do Marlin 2.1


Reviews:
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:25:27Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:26:07Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:26:19Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:26:46Z
user: login: vovodroid
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:32:06Z
user: login: vovodroid
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:33:58Z
user: login: vovodroid
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:34:42Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:44:09Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:45:02Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:47:04Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:52:19Z
user: login: vovodroid
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:55:02Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T15:59:51Z
user: login: dbuezas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T10:25:17Z
user: login: thinkyhead
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T01:21:58Z
user: login: thinkyhead
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T01:24:19Z
user: login: vovodroid
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T06:18:18Z

Review comments:
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: Divisions are really, really expensive. Maybe this can be done per block:

α = mm_per_sec / ·steps_per_sec (may already be available somewhere

a′ = a · α²
b′ = b · α
c′ = c

Then f(steps_per_sec) can be done directly:
f(steps_per_sec) = a′·steps_per_sec² + b′·steps_per_sec + c′
created_at: 2025-05-16T15:25:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: note step rate can be negative
created_at: 2025-05-16T15:26:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: (i.e velocity too)
created_at: 2025-05-16T15:26:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: can also be negative (needs to be signed)
created_at: 2025-05-16T15:26:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: vovodroid
body: Ok, let's get code working (I hope my 500MHz SKR-3 can handle it), and then optimize it. Another issue that mm_per_sec can be changed during print with M92 or menu, so coefficients should be recalculated on each update.
created_at: 2025-05-16T15:32:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
path: Marlin/src/module/stepper.cpp
user: login: vovodroid
body: Not for NLE, it's active only for extrusion, but not for retract/unrectract:

`if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block))`
created_at: 2025-05-16T15:33:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: vovodroid
body: Also not for NLE.
created_at: 2025-05-16T15:34:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: Ok, that's a problem then. During the deacceleration phase of the trapezoid, linear advance will result in negative extrusion
created_at: 2025-05-16T15:44:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: the thing is that if NLE is only applied when step_rate+advance_rate is positive, then the asymmetry will result in the wrong pressure being in the nozzle when the next block starts
created_at: 2025-05-16T15:45:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: then it would need to either be calculated once every time a new block is picked by the ISR, or in the planer and stored inside the block. Smooth LA is for 32 bit machines, so the extra couple of bytes per block shouldn't be that bad either.
created_at: 2025-05-16T15:47:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: both vd and  ne_scale are in q24, right? (<<24), right? 
I suggest adding a _q24 postfix to the var names to keep track of that. I had a lot of issues with smooth advance until I did that
created_at: 2025-05-16T15:52:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: vovodroid
body: Good idea!
created_at: 2025-05-16T15:55:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2885,13 +2887,27 @@ hal_timer_t Stepper::block_phase_isr() {
     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) {
+    void Stepper::set_la_interval(int32_t step_rate) {
+      if (step_rate == 0) {
         la_interval = LA_ADV_NEVER;
       }
       else {
-        const bool forward_e = rate > 0;
-        la_interval = calc_timer_interval(uint32_t(ABS(rate)));
+        const bool forward_e = step_rate > 0;
+        
+        #if ENABLED(NONLINEAR_EXTRUSION)
+          if (forward_e /*is forward_e the same as current_block->direction_bits.e?*/ && ANY_AXIS_MOVES(current_block)) {
+            const float scale = planner.mm_per_step[E_AXIS_N(current_block->extruder)];
+            uint32_t ne_scale = (1L << 24) * scale;
+
+            const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
+            int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
+            NOLESS(vd, 0);
+
+            step_rate = uint64_t(ne_fix.C + vd) / ne_scale;
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: Maybe it would be then best to only apply NLE to the "planned_step_rate" inside the smooth LA isr (and not to the total). Or otherwise only apply it when `block->use_advance_lead` is `true`. That is false during (un)retractions
created_at: 2025-05-16T15:59:51Z
author_association: CONTRIBUTOR
path: Marlin/src/module/stepper.cpp
user: login: dbuezas
body: I think it reads a lot better!
I think eventually we should move the other fixed point ops in smooth LA to q24 and remove the extra param (24)
created_at: 2025-05-17T10:25:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3252,6 +3252,10 @@ void Planner::refresh_acceleration_rates() {
 void Planner::refresh_positioning() {
   #if ENABLED(EDITABLE_STEPS_PER_UNIT)
     LOOP_DISTINCT_AXES(i) mm_per_step[i] = 1.0f / settings.axis_steps_per_mm[i];
+    #if ALL(NONLINEAR_EXTRUSION, SMOOTH_LIN_ADVANCE)
+      stepper.ne_q30.A = (1L << 30) * (stepper.ne.A * mm_per_step[E_AXIS_N(0)] * mm_per_step[E_AXIS_N(0)]);
+      stepper.ne_q30.B = (1L << 30) * (stepper.ne.B * mm_per_step[E_AXIS_N(0)]);
+    #endif
path: Marlin/src/module/planner.cpp
user: login: thinkyhead
body: Seems like there should be a 24-bit version here when `SMOOTH_LIN_ADVANCE` is disabled…?
created_at: 2025-05-20T01:21:58Z
author_association: MEMBER
diff_hunk: @@ -2241,13 +2248,13 @@ hal_timer_t Stepper::calc_timer_interval(uint32_t step_rate) {
   #endif // !CPU_32_BIT
 }
 
-#if ENABLED(NONLINEAR_EXTRUSION)
+#if ENABLED(NONLINEAR_EXTRUSION) and DISABLED(SMOOTH_LIN_ADVANCE)
path: Marlin/src/module/stepper.cpp
user: login: thinkyhead
body: What is this? Please review code for left behind "concepts" before and after submission. Did AI generate this code??
created_at: 2025-05-20T01:24:19Z
author_association: MEMBER
diff_hunk: @@ -3252,6 +3252,10 @@ void Planner::refresh_acceleration_rates() {
 void Planner::refresh_positioning() {
   #if ENABLED(EDITABLE_STEPS_PER_UNIT)
     LOOP_DISTINCT_AXES(i) mm_per_step[i] = 1.0f / settings.axis_steps_per_mm[i];
+    #if ALL(NONLINEAR_EXTRUSION, SMOOTH_LIN_ADVANCE)
+      stepper.ne_q30.A = (1L << 30) * (stepper.ne.A * mm_per_step[E_AXIS_N(0)] * mm_per_step[E_AXIS_N(0)]);
+      stepper.ne_q30.B = (1L << 30) * (stepper.ne.B * mm_per_step[E_AXIS_N(0)]);
+    #endif
path: Marlin/src/module/planner.cpp
user: login: vovodroid
body: Without SMOOTH_LIN_ADVANCE coefficients are calculated in Stepper here https://github.com/MarlinFirmware/Marlin/pull/27861/files#diff-ddacdfeac95e2a001322f957bfdfe0a702d54d6728dd9aebdcb8b7a279d89260R2845
created_at: 2025-05-20T06:18:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: vovodroid
created_at: 2025-05-16T10:03:49Z
body: @dbuezas , could you take a look? Did I put the code in the correct place in  `set_la_interval`? (polynomial is still for velocity now, and scale is calculated every time)

@abortz, your thoughts, as the author of NONLINEAR_EXTRUSION, also are very appreciated.
author_association: CONTRIBUTOR
user: login: vovodroid
created_at: 2025-05-16T15:58:13Z
body: >linear advance will result in negative extrusion

Do you mean that LA could result not just in slowing or even stopping extrusion but in kind of unretract during printing move?
Anyway, NLE should compensate nozzle resistance, so there is no necessity to compensate negative extrusion.


>if NLE is only applied when step_rate+advance_rate is positive, then the asymmetry will result in the wrong pressure being in the nozzle when the next block starts

May you clarify it? As I mentioned above, NLE only for forward extrusions (with moves, not for unretracts).
author_association: CONTRIBUTOR
user: login: dbuezas
created_at: 2025-05-16T16:02:07Z
body: > > linear advance will result in negative extrusion
> 
> Do you mean that LA could result not just in slowing or even stopping extrusion but in kind of unretract during printing move? Anyway, NLE should compensate nozzle resistance, so there is no necessity to compensate negative extrusion.
> 
> > if NLE is only applied when step_rate+advance_rate is positive, then the asymmetry will result in the wrong pressure being in the nozzle when the next block starts
> 
> May you clarify it? As I mentioned above, NLE only for forward extrusions (with moves, not for unretracts).

See comment in the thread above. And yes, extrusion goes negative at the end of deceleration to stand still
author_association: CONTRIBUTOR
user: login: vovodroid
created_at: 2025-05-16T16:05:58Z
body: > extrusion goes negative at the end of deceleration to stand still

Well, so no NLE should be applied to it.
author_association: CONTRIBUTOR
user: login: dbuezas
created_at: 2025-05-16T16:34:21Z
body: > Well, so no NLE should be applied to it.

Ok, I think I see the logic. I guess it's a matter of testing later and confirming it makes pretty boats 
author_association: CONTRIBUTOR
user: login: vovodroid
created_at: 2025-05-16T17:01:02Z
body: @abortz probably I miss something, here is
```
  ne_scale = (1L << 24) * scale;
  ..............................
  void Stepper::calc_nonlinear_e(uint32_t step_rate) {
    const uint32_t velocity = ne_scale * step_rate; // Scale step_rate first so all intermediate values stay in range of 8.24 fixed point math
    int32_t vd =  (((((int64_t)ne_fix.A * velocity) >> 24) * velocity) >> 24) + (((int64_t)ne_fix.B * velocity) >> 24);
    NOLESS(vd, 0);

    advance_dividend.e = (uint64_t(ne_fix.C + vd) * ne_edividend) >> 24;
  }
```
So `velocity` is 8.24, ne_fix members as well. Why there are back conversions in `vd ` calculation, and then again in `advance_dividend.e`? I never worked before with 8.24 format, could you please explain this?
author_association: CONTRIBUTOR
user: login: dbuezas
created_at: 2025-05-16T17:22:45Z
body: It's just to make the decimals storageable in an integer. Instead of `x`, one stores `x*2^q`.

So if you want to calculate `a * x`
You do `a * x_q24` which is `a*(x*2^24)`, then you need to get rid of the `*2^24`, which is just bit shifting (`>>24`)

It's easier to see if e.g `x=0.1`. Since it was stored with the comma moved 24 binary positions, it is not just zero.

The castings to `int64` you see are because the intermediate result (before removing the `*2^24` by bit shifting to the right), will likely be too large to fit in 32 bits.

The backs and fourths are to ensure that on each multiplication:
* only one side is in q24. Otherwise the result of the multiplication would end up 48 bits shifted and overflow even a 64 bit int)
* No decimals are lost
author_association: CONTRIBUTOR
user: login: vovodroid
created_at: 2025-05-16T17:38:36Z
body: So my `step_rate = uint64_t(ne_fix.C + vd) / ne_scale;` without >>24 is correct, as ne_scale is <<24, right?

I'll rename variables as you suggested.
author_association: CONTRIBUTOR
user: login: dbuezas
created_at: 2025-05-16T17:59:25Z
body: If all vars 3 vars are in in q24 then yes. Division is extremely expensive even for ints so one trick is to computes its inverse and then multiply 
author_association: CONTRIBUTOR
user: login: vovodroid
created_at: 2025-05-16T18:55:32Z
body: What is `total_step_rate` format? Is it plain integer step per seconds value?
author_association: CONTRIBUTOR
user: login: dbuezas
created_at: 2025-05-16T19:10:29Z
body: Yes

> What is `total_step_rate` format? Is it plane integer step per seconds value?

Yes
author_association: CONTRIBUTOR
user: login: vovodroid
created_at: 2025-05-17T18:39:43Z
body: Well, due to small polynomial coefficients I use 30 bits for fractional part, as polynomial value is no more than two anyway.
author_association: CONTRIBUTOR
user: login: dbuezas
created_at: 2025-05-17T19:02:12Z
body: > Well, due to small polynomial coefficients I use 30 bits for fractional part, as polynomial value is no more than two anyway.

Ok, still more than enough precision with 24bits, but I did the same as you hehe.

Watch out, of the 32 bits, one is the sign so it goes between -2 and 1.999999... (almost but not +2). So it it can be positive 2, then go for 29bits
author_association: CONTRIBUTOR
user: login: vovodroid
created_at: 2025-05-17T20:05:38Z
body: 24 bits is not enough for A or B divided by steps_per_mm, that could be 400 for geared extruders, probably even more. I spent some time investigating why vd is zero.
Also meaning of polynomial is factor for gear slippage compensation. I don't believe it could rise above 1.2 at the worst case, but by no means it goes out of -2..2 range.


author_association: CONTRIBUTOR