Browse Source

Implement MIXING_EXTRUDER and SWITCHING_EXTRUDER

Scott Lahteine 8 years ago
parent
commit
05da02f0a2

+ 55
- 25
Marlin/Marlin.h View File

@@ -167,37 +167,63 @@ void manage_inactivity(bool ignore_stepper_queue = false);
167 167
   #define disable_z() NOOP
168 168
 #endif
169 169
 
170
-#if HAS_E0_ENABLE
171
-  #define  enable_e0() E0_ENABLE_WRITE( E_ENABLE_ON)
172
-  #define disable_e0() E0_ENABLE_WRITE(!E_ENABLE_ON)
173
-#else
174
-  #define  enable_e0() NOOP
175
-  #define disable_e0() NOOP
176
-#endif
177
-
178
-#if (EXTRUDERS > 1) && HAS_E1_ENABLE
179
-  #define  enable_e1() E1_ENABLE_WRITE( E_ENABLE_ON)
180
-  #define disable_e1() E1_ENABLE_WRITE(!E_ENABLE_ON)
181
-#else
170
+#if ENABLED(MIXING_EXTRUDER)
171
+
172
+  /**
173
+   * Mixing steppers synchronize their enable (and direction) together
174
+   */
175
+  #if MIXING_STEPPERS > 3
176
+    #define  enable_e0() { E0_ENABLE_WRITE( E_ENABLE_ON); E1_ENABLE_WRITE( E_ENABLE_ON); E2_ENABLE_WRITE( E_ENABLE_ON); E3_ENABLE_WRITE( E_ENABLE_ON); }
177
+    #define disable_e0() { E0_ENABLE_WRITE(!E_ENABLE_ON); E1_ENABLE_WRITE(!E_ENABLE_ON); E2_ENABLE_WRITE(!E_ENABLE_ON); E3_ENABLE_WRITE(!E_ENABLE_ON); }
178
+  #elif MIXING_STEPPERS > 2
179
+    #define  enable_e0() { E0_ENABLE_WRITE( E_ENABLE_ON); E1_ENABLE_WRITE( E_ENABLE_ON); E2_ENABLE_WRITE( E_ENABLE_ON); }
180
+    #define disable_e0() { E0_ENABLE_WRITE(!E_ENABLE_ON); E1_ENABLE_WRITE(!E_ENABLE_ON); E2_ENABLE_WRITE(!E_ENABLE_ON); }
181
+  #else
182
+    #define  enable_e0() { E0_ENABLE_WRITE( E_ENABLE_ON); E1_ENABLE_WRITE( E_ENABLE_ON); }
183
+    #define disable_e0() { E0_ENABLE_WRITE(!E_ENABLE_ON); E1_ENABLE_WRITE(!E_ENABLE_ON); }
184
+  #endif
182 185
   #define  enable_e1() NOOP
183 186
   #define disable_e1() NOOP
184
-#endif
185
-
186
-#if (EXTRUDERS > 2) && HAS_E2_ENABLE
187
-  #define  enable_e2() E2_ENABLE_WRITE( E_ENABLE_ON)
188
-  #define disable_e2() E2_ENABLE_WRITE(!E_ENABLE_ON)
189
-#else
190 187
   #define  enable_e2() NOOP
191 188
   #define disable_e2() NOOP
192
-#endif
193
-
194
-#if (EXTRUDERS > 3) && HAS_E3_ENABLE
195
-  #define  enable_e3() E3_ENABLE_WRITE( E_ENABLE_ON)
196
-  #define disable_e3() E3_ENABLE_WRITE(!E_ENABLE_ON)
197
-#else
198 189
   #define  enable_e3() NOOP
199 190
   #define disable_e3() NOOP
200
-#endif
191
+
192
+#else // !MIXING_EXTRUDER
193
+
194
+  #if HAS_E0_ENABLE
195
+    #define  enable_e0() E0_ENABLE_WRITE( E_ENABLE_ON)
196
+    #define disable_e0() E0_ENABLE_WRITE(!E_ENABLE_ON)
197
+  #else
198
+    #define  enable_e0() NOOP
199
+    #define disable_e0() NOOP
200
+  #endif
201
+
202
+  #if E_STEPPERS > 1 && HAS_E1_ENABLE
203
+    #define  enable_e1() E1_ENABLE_WRITE( E_ENABLE_ON)
204
+    #define disable_e1() E1_ENABLE_WRITE(!E_ENABLE_ON)
205
+  #else
206
+    #define  enable_e1() NOOP
207
+    #define disable_e1() NOOP
208
+  #endif
209
+
210
+  #if E_STEPPERS > 2 && HAS_E2_ENABLE
211
+    #define  enable_e2() E2_ENABLE_WRITE( E_ENABLE_ON)
212
+    #define disable_e2() E2_ENABLE_WRITE(!E_ENABLE_ON)
213
+  #else
214
+    #define  enable_e2() NOOP
215
+    #define disable_e2() NOOP
216
+  #endif
217
+
218
+  #if E_STEPPERS > 3 && HAS_E3_ENABLE
219
+    #define  enable_e3() E3_ENABLE_WRITE( E_ENABLE_ON)
220
+    #define disable_e3() E3_ENABLE_WRITE(!E_ENABLE_ON)
221
+  #else
222
+    #define  enable_e3() NOOP
223
+    #define disable_e3() NOOP
224
+  #endif
225
+
226
+#endif // !MIXING_EXTRUDER
201 227
 
202 228
 /**
203 229
  * The axis order in all axis related arrays is X, Y, Z, E
@@ -376,6 +402,10 @@ extern uint8_t active_extruder;
376 402
   void print_heaterstates();
377 403
 #endif
378 404
 
405
+#if ENABLED(MIXING_EXTRUDER)
406
+  extern float mixing_factor[MIXING_STEPPERS];
407
+#endif
408
+
379 409
 void calculate_volumetric_multipliers();
380 410
 
381 411
 // Buzzer

+ 444
- 247
Marlin/Marlin_main.cpp View File

@@ -183,6 +183,9 @@
183 183
  * M145 - Set the heatup state H<hotend> B<bed> F<fan speed> for S<material> (0=PLA, 1=ABS)
184 184
  * M149 - Set temperature units
185 185
  * M150 - Set BlinkM Color Output R: Red<0-255> U(!): Green<0-255> B: Blue<0-255> over i2c, G for green does not work.
186
+ * M163 - Set a single proportion for a mixing extruder. Requires MIXING_EXTRUDER.
187
+ * M164 - Save the mix as a virtual extruder. Requires MIXING_EXTRUDER and MIXING_VIRTUAL_TOOLS.
188
+ * M165 - Set the proportions for a mixing extruder. Use parameters ABCDHI to set the mixing factors. Requires MIXING_EXTRUDER.
186 189
  * M190 - Sxxx Wait for bed current temp to reach target temp. Waits only when heating
187 190
  *        Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling
188 191
  * M200 - Set filament diameter, D<diameter>, setting E axis units to cubic. (Use S0 to revert to linear units.)
@@ -397,17 +400,11 @@ static uint8_t target_extruder;
397 400
 
398 401
 // Extruder offsets
399 402
 #if HOTENDS > 1
400
-  #ifndef HOTEND_OFFSET_X
401
-    #define HOTEND_OFFSET_X { 0 } // X offsets for each extruder
402
-  #endif
403
-  #ifndef HOTEND_OFFSET_Y
404
-    #define HOTEND_OFFSET_Y { 0 } // Y offsets for each extruder
405
-  #endif
406 403
   float hotend_offset[][HOTENDS] = {
407 404
     HOTEND_OFFSET_X,
408 405
     HOTEND_OFFSET_Y
409
-    #if ENABLED(DUAL_X_CARRIAGE)
410
-      , { 0 } // Z offsets for each extruder
406
+    #ifdef HOTEND_OFFSET_Z
407
+      , HOTEND_OFFSET_Z
411 408
     #endif
412 409
   };
413 410
 #endif
@@ -507,6 +504,13 @@ static uint8_t target_extruder;
507 504
   FilamentChangeMenuResponse filament_change_menu_response;
508 505
 #endif
509 506
 
507
+#if ENABLED(MIXING_EXTRUDER)
508
+  float mixing_factor[MIXING_STEPPERS];
509
+  #if MIXING_VIRTUAL_TOOLS > 1
510
+    float mixing_virtual_tool_mix[MIXING_VIRTUAL_TOOLS][MIXING_STEPPERS];
511
+  #endif
512
+#endif
513
+
510 514
 static bool send_ok[BUFSIZE];
511 515
 
512 516
 #if HAS_SERVOS
@@ -952,6 +956,15 @@ void setup() {
952 956
       lcd_init();
953 957
     #endif
954 958
   #endif
959
+
960
+  #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1
961
+    // Initialize mixing to 100% color 1
962
+    for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
963
+      mixing_factor[i] = (i == 0) ? 1 : 0;
964
+    for (uint8_t t = 0; t < MIXING_VIRTUAL_TOOLS; t++)
965
+      for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
966
+        mixing_virtual_tool_mix[t][i] = mixing_factor[i];
967
+  #endif
955 968
 }
956 969
 
957 970
 /**
@@ -2544,6 +2557,39 @@ static void homeaxis(AxisEnum axis) {
2544 2557
 
2545 2558
 #endif // FWRETRACT
2546 2559
 
2560
+#if ENABLED(MIXING_EXTRUDER)
2561
+
2562
+  void normalize_mix() {
2563
+    float mix_total = 0.0;
2564
+    for (int i = 0; i < MIXING_STEPPERS; i++) {
2565
+      float v = mixing_factor[i];
2566
+      if (v < 0) v = mixing_factor[i] = 0;
2567
+      mix_total += v;
2568
+    }
2569
+    // Scale all values if they don't add up to ~1.0
2570
+    if (mix_total < 0.9999 || mix_total > 1.0001) {
2571
+      SERIAL_PROTOCOLLNPGM("Warning: Mix factors must add up to 1.0. Scaling.");
2572
+      float mix_scale = 1.0 / mix_total;
2573
+      for (int i = 0; i < MIXING_STEPPERS; i++)
2574
+        mixing_factor[i] *= mix_scale;
2575
+    }
2576
+  }
2577
+
2578
+  #if ENABLED(DIRECT_MIXING_IN_G1)
2579
+    // Get mixing parameters from the GCode
2580
+    // Factors that are left out are set to 0
2581
+    // The total "must" be 1.0 (but it will be normalized)
2582
+    void gcode_get_mix() {
2583
+      const char* mixing_codes = "ABCDHI";
2584
+      for (int i = 0; i < MIXING_STEPPERS; i++)
2585
+        mixing_factor[i] = code_seen(mixing_codes[i]) ? code_value_float() : 0;
2586
+
2587
+      normalize_mix();
2588
+    }
2589
+  #endif
2590
+
2591
+#endif
2592
+
2547 2593
 /**
2548 2594
  * ***************************************************************************
2549 2595
  * ***************************** G-CODE HANDLING *****************************
@@ -2572,6 +2618,11 @@ void gcode_get_destination() {
2572 2618
     if(!DEBUGGING(DRYRUN))
2573 2619
       print_job_timer.incFilamentUsed(destination[E_AXIS] - current_position[E_AXIS]);
2574 2620
   #endif
2621
+
2622
+  // Get ABCDHI mixing factors
2623
+  #if ENABLED(MIXING_EXTRUDER) && ENABLED(DIRECT_MIXING_IN_G1)
2624
+    gcode_get_mix();
2625
+  #endif
2575 2626
 }
2576 2627
 
2577 2628
 void unknown_command_error() {
@@ -4733,6 +4784,8 @@ inline void gcode_M109() {
4733 4784
 
4734 4785
     KEEPALIVE_STATE(NOT_BUSY);
4735 4786
 
4787
+    target_extruder = active_extruder; // for print_heaterstates
4788
+
4736 4789
     do {
4737 4790
       // Target temperature might be changed during the loop
4738 4791
       if (theTarget != thermalManager.degTargetBed()) {
@@ -5258,7 +5311,7 @@ inline void gcode_M200() {
5258 5311
     if (volumetric_enabled) {
5259 5312
       filament_size[target_extruder] = code_value_linear_units();
5260 5313
       // make sure all extruders have some sane value for the filament size
5261
-      for (int i = 0; i < EXTRUDERS; i++)
5314
+      for (int i = 0; i < COUNT(filament_size); i++)
5262 5315
         if (! filament_size[i]) filament_size[i] = DEFAULT_NOMINAL_FILAMENT_DIA;
5263 5316
     }
5264 5317
   }
@@ -5496,7 +5549,7 @@ inline void gcode_M206() {
5496 5549
    *   T<tool>
5497 5550
    *   X<xoffset>
5498 5551
    *   Y<yoffset>
5499
-   *   Z<zoffset> - Available with DUAL_X_CARRIAGE
5552
+   *   Z<zoffset> - Available with DUAL_X_CARRIAGE and SWITCHING_EXTRUDER
5500 5553
    */
5501 5554
   inline void gcode_M218() {
5502 5555
     if (get_target_extruder_from_command(218)) return;
@@ -5504,7 +5557,7 @@ inline void gcode_M206() {
5504 5557
     if (code_seen('X')) hotend_offset[X_AXIS][target_extruder] = code_value_axis_units(X_AXIS);
5505 5558
     if (code_seen('Y')) hotend_offset[Y_AXIS][target_extruder] = code_value_axis_units(Y_AXIS);
5506 5559
 
5507
-    #if ENABLED(DUAL_X_CARRIAGE)
5560
+    #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_EXTRUDER)
5508 5561
       if (code_seen('Z')) hotend_offset[Z_AXIS][target_extruder] = code_value_axis_units(Z_AXIS);
5509 5562
     #endif
5510 5563
 
@@ -5515,7 +5568,7 @@ inline void gcode_M206() {
5515 5568
       SERIAL_ECHO(hotend_offset[X_AXIS][e]);
5516 5569
       SERIAL_CHAR(',');
5517 5570
       SERIAL_ECHO(hotend_offset[Y_AXIS][e]);
5518
-      #if ENABLED(DUAL_X_CARRIAGE)
5571
+      #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_EXTRUDER)
5519 5572
         SERIAL_CHAR(',');
5520 5573
         SERIAL_ECHO(hotend_offset[Z_AXIS][e]);
5521 5574
       #endif
@@ -6528,6 +6581,60 @@ inline void gcode_M907() {
6528 6581
 
6529 6582
 #endif // HAS_MICROSTEPS
6530 6583
 
6584
+#if ENABLED(MIXING_EXTRUDER)
6585
+
6586
+  /**
6587
+   * M163: Set a single mix factor for a mixing extruder
6588
+   *       This is called "weight" by some systems.
6589
+   *
6590
+   *   S[index]   The channel index to set
6591
+   *   P[float]   The mix value
6592
+   *
6593
+   */
6594
+  inline void gcode_M163() {
6595
+    int mix_index = code_seen('S') ? code_value_int() : 0;
6596
+    float mix_value = code_seen('P') ? code_value_float() : 0.0;
6597
+    if (mix_index < MIXING_STEPPERS) mixing_factor[mix_index] = mix_value;
6598
+  }
6599
+
6600
+  #if MIXING_VIRTUAL_TOOLS > 1
6601
+
6602
+    /**
6603
+     * M164: Store the current mix factors as a virtual tool.
6604
+     *
6605
+     *   S[index]   The virtual tool to store
6606
+     *
6607
+     */
6608
+    inline void gcode_M164() {
6609
+      int tool_index = code_seen('S') ? code_value_int() : 0;
6610
+      if (tool_index < MIXING_VIRTUAL_TOOLS) {
6611
+        normalize_mix();
6612
+        for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
6613
+          mixing_virtual_tool_mix[tool_index][i] = mixing_factor[i];
6614
+      }
6615
+    }
6616
+
6617
+  #endif
6618
+
6619
+  #if ENABLED(DIRECT_MIXING_IN_G1)
6620
+    /**
6621
+     * M165: Set multiple mix factors for a mixing extruder.
6622
+     *       Factors that are left out will be set to 0.
6623
+     *       All factors together must add up to 1.0.
6624
+     *
6625
+     *   A[factor] Mix factor for extruder stepper 1
6626
+     *   B[factor] Mix factor for extruder stepper 2
6627
+     *   C[factor] Mix factor for extruder stepper 3
6628
+     *   D[factor] Mix factor for extruder stepper 4
6629
+     *   H[factor] Mix factor for extruder stepper 5
6630
+     *   I[factor] Mix factor for extruder stepper 6
6631
+     *
6632
+     */
6633
+    inline void gcode_M165() { gcode_get_mix(); }
6634
+  #endif
6635
+
6636
+#endif // MIXING_EXTRUDER
6637
+
6531 6638
 /**
6532 6639
  * M999: Restart after being stopped
6533 6640
  *
@@ -6548,6 +6655,20 @@ inline void gcode_M999() {
6548 6655
   FlushSerialRequestResend();
6549 6656
 }
6550 6657
 
6658
+#if ENABLED(SWITCHING_EXTRUDER)
6659
+  inline void move_extruder_servo(uint8_t e) {
6660
+    const int angles[2] = SWITCHING_EXTRUDER_SERVO_ANGLES;
6661
+    MOVE_SERVO(SWITCHING_EXTRUDER_SERVO_NR, angles[e]);
6662
+  }
6663
+#endif
6664
+
6665
+inline void invalid_extruder_error(const uint8_t &e) {
6666
+  SERIAL_ECHO_START;
6667
+  SERIAL_CHAR('T');
6668
+  SERIAL_PROTOCOL_F(e, DEC);
6669
+  SERIAL_ECHOLN(MSG_INVALID_EXTRUDER);
6670
+}
6671
+
6551 6672
 /**
6552 6673
  * T0-T3: Switch tool, usually switching extruders
6553 6674
  *
@@ -6555,264 +6676,314 @@ inline void gcode_M999() {
6555 6676
  *   S1           Don't move the tool in XY after change
6556 6677
  */
6557 6678
 inline void gcode_T(uint8_t tmp_extruder) {
6558
-  if (tmp_extruder >= EXTRUDERS) {
6559
-    SERIAL_ECHO_START;
6560
-    SERIAL_CHAR('T');
6561
-    SERIAL_PROTOCOL_F(tmp_extruder, DEC);
6562
-    SERIAL_ECHOLN(MSG_INVALID_EXTRUDER);
6563
-    return;
6564
-  }
6565 6679
 
6566
-  #if ENABLED(DEBUG_LEVELING_FEATURE)
6567
-    if (DEBUGGING(LEVELING)) {
6568
-      SERIAL_ECHOLNPGM(">>> gcode_T");
6569
-      DEBUG_POS("BEFORE", current_position);
6680
+  #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1
6681
+
6682
+    if (tmp_extruder >= MIXING_VIRTUAL_TOOLS) {
6683
+      invalid_extruder_error(tmp_extruder);
6684
+      return;
6570 6685
     }
6571
-  #endif
6572 6686
 
6573
-  #if HOTENDS > 1
6687
+    // T0-Tnnn: Switch virtual tool by changing the mix
6688
+    for (uint8_t j = 0; j < MIXING_STEPPERS; j++)
6689
+      mixing_factor[j] = mixing_virtual_tool_mix[tmp_extruder][j];
6574 6690
 
6575
-    float old_feedrate = feedrate;
6691
+  #else //!MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1
6576 6692
 
6577
-    if (code_seen('F')) {
6578
-      float next_feedrate = code_value_axis_units(X_AXIS);
6579
-      if (next_feedrate > 0.0) old_feedrate = feedrate = next_feedrate;
6580
-    }
6581
-    else
6582
-      feedrate = XY_PROBE_FEEDRATE;
6693
+    #if ENABLED(DEBUG_LEVELING_FEATURE)
6694
+      if (DEBUGGING(LEVELING)) {
6695
+        SERIAL_ECHOLNPGM(">>> gcode_T");
6696
+        DEBUG_POS("BEFORE", current_position);
6697
+      }
6698
+    #endif
6583 6699
 
6584
-    if (tmp_extruder != active_extruder) {
6585
-      bool no_move = code_seen('S') && code_value_bool();
6586
-      if (!no_move && axis_unhomed_error(true, true, true)) {
6587
-        SERIAL_ECHOLNPGM("No move on toolchange");
6588
-        no_move = true;
6700
+    #if HOTENDS > 1
6701
+
6702
+      if (tmp_extruder >= EXTRUDERS) {
6703
+        invalid_extruder_error(tmp_extruder);
6704
+        return;
6589 6705
       }
6590 6706
 
6591
-      // Save current position to destination, for use later
6592
-      set_destination_to_current();
6707
+      float old_feedrate = feedrate;
6593 6708
 
6594
-      #if ENABLED(DUAL_X_CARRIAGE)
6709
+      if (code_seen('F')) {
6710
+        float next_feedrate = code_value_axis_units(X_AXIS);
6711
+        if (next_feedrate > 0.0) old_feedrate = feedrate = next_feedrate;
6712
+      }
6713
+      else
6714
+        feedrate = XY_PROBE_FEEDRATE;
6595 6715
 
6596
-        #if ENABLED(DEBUG_LEVELING_FEATURE)
6597
-          if (DEBUGGING(LEVELING)) {
6598
-            SERIAL_ECHOPGM("Dual X Carriage Mode ");
6599
-            switch (dual_x_carriage_mode) {
6600
-              case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break;
6601
-              case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break;
6602
-              case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break;
6603
-            }
6604
-          }
6605
-        #endif
6716
+      if (tmp_extruder != active_extruder) {
6717
+        bool no_move = code_seen('S') && code_value_bool();
6718
+
6719
+        if (!no_move && axis_unhomed_error(true, true, true)) {
6720
+          SERIAL_ECHOLNPGM("No move on toolchange");
6721
+          no_move = true;
6722
+        }
6723
+
6724
+        // Save current position to destination, for use later
6725
+        set_destination_to_current();
6726
+
6727
+        #if ENABLED(DUAL_X_CARRIAGE)
6606 6728
 
6607
-        if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE && IsRunning()
6608
-             && (delayed_move_time || current_position[X_AXIS] != x_home_pos(active_extruder))
6609
-           ) {
6610 6729
           #if ENABLED(DEBUG_LEVELING_FEATURE)
6611 6730
             if (DEBUGGING(LEVELING)) {
6612
-              SERIAL_ECHOPAIR("Raise to ", current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT); SERIAL_EOL;
6613
-              SERIAL_ECHOPAIR("MoveX to ", x_home_pos(active_extruder)); SERIAL_EOL;
6614
-              SERIAL_ECHOPAIR("Lower to ", current_position[Z_AXIS]); SERIAL_EOL;
6731
+              SERIAL_ECHOPGM("Dual X Carriage Mode ");
6732
+              switch (dual_x_carriage_mode) {
6733
+                case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break;
6734
+                case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break;
6735
+                case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break;
6736
+              }
6615 6737
             }
6616 6738
           #endif
6617
-          // Park old head: 1) raise 2) move to park position 3) lower
6618
-          planner.buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT,
6619
-                           current_position[E_AXIS], planner.max_feedrate[Z_AXIS], active_extruder);
6620
-          planner.buffer_line(x_home_pos(active_extruder), current_position[Y_AXIS], current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT,
6621
-                           current_position[E_AXIS], planner.max_feedrate[X_AXIS], active_extruder);
6622
-          planner.buffer_line(x_home_pos(active_extruder), current_position[Y_AXIS], current_position[Z_AXIS],
6623
-                           current_position[E_AXIS], planner.max_feedrate[Z_AXIS], active_extruder);
6624
-          stepper.synchronize();
6625
-        }
6626 6739
 
6627
-        // apply Y & Z extruder offset (x offset is already used in determining home pos)
6628
-        current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder];
6629
-        current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder];
6630
-        active_extruder = tmp_extruder;
6740
+          if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE && IsRunning() &&
6741
+              (delayed_move_time || current_position[X_AXIS] != x_home_pos(active_extruder))
6742
+          ) {
6743
+            #if ENABLED(DEBUG_LEVELING_FEATURE)
6744
+              if (DEBUGGING(LEVELING)) {
6745
+                SERIAL_ECHOPAIR("Raise to ", current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT); SERIAL_EOL;
6746
+                SERIAL_ECHOPAIR("MoveX to ", x_home_pos(active_extruder)); SERIAL_EOL;
6747
+                SERIAL_ECHOPAIR("Lower to ", current_position[Z_AXIS]); SERIAL_EOL;
6748
+              }
6749
+            #endif
6750
+            // Park old head: 1) raise 2) move to park position 3) lower
6751
+            for (uint8_t i = 0; i < 3; i++)
6752
+              planner.buffer_line(
6753
+                i == 0 ? current_position[X_AXIS] : x_home_pos(active_extruder),
6754
+                current_position[Y_AXIS],
6755
+                current_position[Z_AXIS] + (i == 2 ? 0 : TOOLCHANGE_PARK_ZLIFT),
6756
+                current_position[E_AXIS],
6757
+                planner.max_feedrate[i == 1 ? X_AXIS : Z_AXIS],
6758
+                active_extruder
6759
+              );
6760
+            stepper.synchronize();
6761
+          }
6631 6762
 
6632
-        // This function resets the max/min values - the current position may be overwritten below.
6633
-        set_axis_is_at_home(X_AXIS);
6763
+          // apply Y & Z extruder offset (x offset is already used in determining home pos)
6764
+          current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder];
6765
+          current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder];
6766
+          active_extruder = tmp_extruder;
6634 6767
 
6635
-        #if ENABLED(DEBUG_LEVELING_FEATURE)
6636
-          if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position);
6637
-        #endif
6768
+          // This function resets the max/min values - the current position may be overwritten below.
6769
+          set_axis_is_at_home(X_AXIS);
6638 6770
 
6639
-        switch (dual_x_carriage_mode) {
6640
-          case DXC_FULL_CONTROL_MODE:
6641
-            current_position[X_AXIS] = inactive_extruder_x_pos;
6642
-            inactive_extruder_x_pos = destination[X_AXIS];
6643
-            break;
6644
-          case DXC_DUPLICATION_MODE:
6645
-            active_extruder_parked = (active_extruder == 0); // this triggers the second extruder to move into the duplication position
6646
-            if (active_extruder_parked)
6647
-              current_position[X_AXIS] = inactive_extruder_x_pos;
6648
-            else
6649
-              current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset;
6650
-            inactive_extruder_x_pos = destination[X_AXIS];
6651
-            extruder_duplication_enabled = false;
6652
-            break;
6653
-          default:
6654
-            // record raised toolhead position for use by unpark
6655
-            memcpy(raised_parked_position, current_position, sizeof(raised_parked_position));
6656
-            raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT;
6657
-            active_extruder_parked = true;
6658
-            delayed_move_time = 0;
6659
-            break;
6660
-        }
6771
+          #if ENABLED(DEBUG_LEVELING_FEATURE)
6772
+            if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position);
6773
+          #endif
6661 6774
 
6662
-        #if ENABLED(DEBUG_LEVELING_FEATURE)
6663
-          if (DEBUGGING(LEVELING)) {
6664
-            SERIAL_ECHOPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no");
6665
-            SERIAL_EOL;
6666
-            DEBUG_POS("New extruder (parked)", current_position);
6775
+          switch (dual_x_carriage_mode) {
6776
+            case DXC_FULL_CONTROL_MODE:
6777
+              current_position[X_AXIS] = inactive_extruder_x_pos;
6778
+              inactive_extruder_x_pos = destination[X_AXIS];
6779
+              break;
6780
+            case DXC_DUPLICATION_MODE:
6781
+              active_extruder_parked = (active_extruder == 0); // this triggers the second extruder to move into the duplication position
6782
+              if (active_extruder_parked)
6783
+                current_position[X_AXIS] = inactive_extruder_x_pos;
6784
+              else
6785
+                current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset;
6786
+              inactive_extruder_x_pos = destination[X_AXIS];
6787
+              extruder_duplication_enabled = false;
6788
+              break;
6789
+            default:
6790
+              // record raised toolhead position for use by unpark
6791
+              memcpy(raised_parked_position, current_position, sizeof(raised_parked_position));
6792
+              raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT;
6793
+              active_extruder_parked = true;
6794
+              delayed_move_time = 0;
6795
+              break;
6667 6796
           }
6668
-        #endif
6669
-
6670
-       // No extra case for AUTO_BED_LEVELING_FEATURE in DUAL_X_CARRIAGE. Does that mean they don't work together?
6671
-      #else // !DUAL_X_CARRIAGE
6672
-
6673
-        /**
6674
-         * Set current_position to the position of the new nozzle.
6675
-         * Offsets are based on linear distance, so we need to get
6676
-         * the resulting position in coordinate space.
6677
-         *
6678
-         * - With grid or 3-point leveling, offset XYZ by a tilted vector
6679
-         * - With mesh leveling, update Z for the new position
6680
-         * - Otherwise, just use the raw linear distance
6681
-         *
6682
-         * Software endstops are altered here too. Consider a case where:
6683
-         *   E0 at X=0 ... E1 at X=10
6684
-         * When we switch to E1 now X=10, but E1 can't move left.
6685
-         * To express this we apply the change in XY to the software endstops.
6686
-         * E1 can move farther right than E0, so the right limit is extended.
6687
-         *
6688
-         * Note that we don't adjust the Z software endstops. Why not?
6689
-         * Consider a case where Z=0 (here) and switching to E1 makes Z=1
6690
-         * because the bed is 1mm lower at the new position. As long as
6691
-         * the first nozzle is out of the way, the carriage should be
6692
-         * allowed to move 1mm lower. This technically "breaks" the
6693
-         * Z software endstop. But this is technically correct (and
6694
-         * there is no viable alternative).
6695
-         */
6696
-        #if ENABLED(AUTO_BED_LEVELING_FEATURE)
6697
-          // Offset extruder, make sure to apply the bed level rotation matrix
6698
-          vector_3 tmp_offset_vec = vector_3(hotend_offset[X_AXIS][tmp_extruder],
6699
-                                             hotend_offset[Y_AXIS][tmp_extruder],
6700
-                                             0),
6701
-                   act_offset_vec = vector_3(hotend_offset[X_AXIS][active_extruder],
6702
-                                             hotend_offset[Y_AXIS][active_extruder],
6703
-                                             0),
6704
-                   offset_vec = tmp_offset_vec - act_offset_vec;
6705 6797
 
6706 6798
           #if ENABLED(DEBUG_LEVELING_FEATURE)
6707 6799
             if (DEBUGGING(LEVELING)) {
6708
-              tmp_offset_vec.debug("tmp_offset_vec");
6709
-              act_offset_vec.debug("act_offset_vec");
6710
-              offset_vec.debug("offset_vec (BEFORE)");
6800
+              SERIAL_ECHOPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no");
6801
+              SERIAL_EOL;
6802
+              DEBUG_POS("New extruder (parked)", current_position);
6711 6803
             }
6712 6804
           #endif
6713 6805
 
6714
-          offset_vec.apply_rotation(planner.bed_level_matrix.transpose(planner.bed_level_matrix));
6715
-
6716
-          #if ENABLED(DEBUG_LEVELING_FEATURE)
6717
-            if (DEBUGGING(LEVELING)) offset_vec.debug("offset_vec (AFTER)");
6806
+          // No extra case for AUTO_BED_LEVELING_FEATURE in DUAL_X_CARRIAGE. Does that mean they don't work together?
6807
+        #else // !DUAL_X_CARRIAGE
6808
+
6809
+          #if ENABLED(SWITCHING_EXTRUDER)
6810
+            // <0 if the new nozzle is higher, >0 if lower. A bigger raise when lower.
6811
+            float z_diff = hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder],
6812
+                  z_raise = 0.3 + (z_diff > 0.0 ? z_diff : 0.0);
6813
+          
6814
+            // Always raise by some amount
6815
+            planner.buffer_line(
6816
+              current_position[X_AXIS],
6817
+              current_position[Y_AXIS],
6818
+              current_position[Z_AXIS] + z_raise,
6819
+              current_position[E_AXIS],
6820
+              planner.max_feedrate[Z_AXIS],
6821
+              active_extruder
6822
+            );
6823
+            stepper.synchronize();
6824
+          
6825
+            move_extruder_servo(active_extruder);
6826
+            delay(500);
6827
+          
6828
+            // Move back down, if needed
6829
+            if (z_raise != z_diff) {
6830
+              planner.buffer_line(
6831
+                current_position[X_AXIS],
6832
+                current_position[Y_AXIS],
6833
+                current_position[Z_AXIS] + z_diff,
6834
+                current_position[E_AXIS],
6835
+                planner.max_feedrate[Z_AXIS],
6836
+                active_extruder
6837
+              );
6838
+              stepper.synchronize();
6839
+            }
6718 6840
           #endif
6841
+          
6842
+          /**
6843
+           * Set current_position to the position of the new nozzle.
6844
+           * Offsets are based on linear distance, so we need to get
6845
+           * the resulting position in coordinate space.
6846
+           *
6847
+           * - With grid or 3-point leveling, offset XYZ by a tilted vector
6848
+           * - With mesh leveling, update Z for the new position
6849
+           * - Otherwise, just use the raw linear distance
6850
+           *
6851
+           * Software endstops are altered here too. Consider a case where:
6852
+           *   E0 at X=0 ... E1 at X=10
6853
+           * When we switch to E1 now X=10, but E1 can't move left.
6854
+           * To express this we apply the change in XY to the software endstops.
6855
+           * E1 can move farther right than E0, so the right limit is extended.
6856
+           *
6857
+           * Note that we don't adjust the Z software endstops. Why not?
6858
+           * Consider a case where Z=0 (here) and switching to E1 makes Z=1
6859
+           * because the bed is 1mm lower at the new position. As long as
6860
+           * the first nozzle is out of the way, the carriage should be
6861
+           * allowed to move 1mm lower. This technically "breaks" the
6862
+           * Z software endstop. But this is technically correct (and
6863
+           * there is no viable alternative).
6864
+           */
6865
+          #if ENABLED(AUTO_BED_LEVELING_FEATURE)
6866
+            // Offset extruder, make sure to apply the bed level rotation matrix
6867
+            vector_3 tmp_offset_vec = vector_3(hotend_offset[X_AXIS][tmp_extruder],
6868
+                                               hotend_offset[Y_AXIS][tmp_extruder],
6869
+                                               0),
6870
+                     act_offset_vec = vector_3(hotend_offset[X_AXIS][active_extruder],
6871
+                                               hotend_offset[Y_AXIS][active_extruder],
6872
+                                               0),
6873
+                     offset_vec = tmp_offset_vec - act_offset_vec;
6719 6874
 
6720
-          // Adjustments to the current position
6721
-          float xydiff[2] = { offset_vec.x, offset_vec.y };
6722
-          current_position[Z_AXIS] += offset_vec.z;
6875
+            #if ENABLED(DEBUG_LEVELING_FEATURE)
6876
+              if (DEBUGGING(LEVELING)) {
6877
+                tmp_offset_vec.debug("tmp_offset_vec");
6878
+                act_offset_vec.debug("act_offset_vec");
6879
+                offset_vec.debug("offset_vec (BEFORE)");
6880
+              }
6881
+            #endif
6723 6882
 
6724
-        #else // !AUTO_BED_LEVELING_FEATURE
6883
+            offset_vec.apply_rotation(planner.bed_level_matrix.transpose(planner.bed_level_matrix));
6725 6884
 
6726
-          float xydiff[2] = {
6727
-            hotend_offset[X_AXIS][tmp_extruder] - hotend_offset[X_AXIS][active_extruder],
6728
-            hotend_offset[Y_AXIS][tmp_extruder] - hotend_offset[Y_AXIS][active_extruder]
6729
-          };
6885
+            #if ENABLED(DEBUG_LEVELING_FEATURE)
6886
+              if (DEBUGGING(LEVELING)) offset_vec.debug("offset_vec (AFTER)");
6887
+            #endif
6730 6888
 
6731
-          #if ENABLED(MESH_BED_LEVELING)
6889
+            // Adjustments to the current position
6890
+            float xydiff[2] = { offset_vec.x, offset_vec.y };
6891
+            current_position[Z_AXIS] += offset_vec.z;
6732 6892
 
6733
-            if (mbl.active()) {
6734
-              #if ENABLED(DEBUG_LEVELING_FEATURE)
6735
-                if (DEBUGGING(LEVELING)) SERIAL_ECHOPAIR("Z before MBL: ", current_position[Z_AXIS]);
6736
-              #endif
6737
-              float xpos = RAW_CURRENT_POSITION(X_AXIS),
6738
-                    ypos = RAW_CURRENT_POSITION(Y_AXIS);
6739
-              current_position[Z_AXIS] += mbl.get_z(xpos + xydiff[X_AXIS], ypos + xydiff[Y_AXIS]) - mbl.get_z(xpos, ypos);
6740
-              #if ENABLED(DEBUG_LEVELING_FEATURE)
6741
-                if (DEBUGGING(LEVELING)) {
6742
-                  SERIAL_ECHOPAIR(" after: ", current_position[Z_AXIS]);
6743
-                  SERIAL_EOL;
6744
-                }
6745
-              #endif
6746
-            }
6893
+          #else // !AUTO_BED_LEVELING_FEATURE
6894
+  
6895
+            float xydiff[2] = {
6896
+              hotend_offset[X_AXIS][tmp_extruder] - hotend_offset[X_AXIS][active_extruder],
6897
+              hotend_offset[Y_AXIS][tmp_extruder] - hotend_offset[Y_AXIS][active_extruder]
6898
+            };
6747 6899
 
6748
-          #endif // MESH_BED_LEVELING
6900
+            #if ENABLED(MESH_BED_LEVELING)
6749 6901
 
6750
-        #endif // !AUTO_BED_LEVELING_FEATURE
6902
+              if (mbl.active()) {
6903
+                #if ENABLED(DEBUG_LEVELING_FEATURE)
6904
+                  if (DEBUGGING(LEVELING)) SERIAL_ECHOPAIR("Z before MBL: ", current_position[Z_AXIS]);
6905
+                #endif
6906
+                float xpos = RAW_CURRENT_POSITION(X_AXIS),
6907
+                      ypos = RAW_CURRENT_POSITION(Y_AXIS);
6908
+                current_position[Z_AXIS] += mbl.get_z(xpos + xydiff[X_AXIS], ypos + xydiff[Y_AXIS]) - mbl.get_z(xpos, ypos);
6909
+                #if ENABLED(DEBUG_LEVELING_FEATURE)
6910
+                  if (DEBUGGING(LEVELING)) {
6911
+                    SERIAL_ECHOPAIR(" after: ", current_position[Z_AXIS]);
6912
+                    SERIAL_EOL;
6913
+                  }
6914
+                #endif
6915
+              }
6751 6916
 
6752
-        #if ENABLED(DEBUG_LEVELING_FEATURE)
6753
-          if (DEBUGGING(LEVELING)) {
6754
-            SERIAL_ECHOPAIR("Offset Tool XY by { ", xydiff[X_AXIS]);
6755
-            SERIAL_ECHOPAIR(", ", xydiff[X_AXIS]);
6756
-            SERIAL_ECHOLNPGM(" }");
6757
-          }
6758
-        #endif
6917
+            #endif // MESH_BED_LEVELING
6918
+  
6919
+          #endif // !AUTO_BED_LEVELING_FEATURE
6759 6920
 
6760
-        // The newly-selected extruder XY is actually at...
6761
-        current_position[X_AXIS] += xydiff[X_AXIS];
6762
-        current_position[Y_AXIS] += xydiff[Y_AXIS];
6763
-        for (uint8_t i = X_AXIS; i <= Y_AXIS; i++) {
6764
-          position_shift[i] += xydiff[i];
6765
-          update_software_endstops((AxisEnum)i);
6766
-        }
6921
+          #if ENABLED(DEBUG_LEVELING_FEATURE)
6922
+            if (DEBUGGING(LEVELING)) {
6923
+              SERIAL_ECHOPAIR("Offset Tool XY by { ", xydiff[X_AXIS]);
6924
+              SERIAL_ECHOPAIR(", ", xydiff[X_AXIS]);
6925
+              SERIAL_ECHOLNPGM(" }");
6926
+            }
6927
+          #endif
6767 6928
 
6768
-        // Set the new active extruder
6769
-        active_extruder = tmp_extruder;
6929
+          // The newly-selected extruder XY is actually at...
6930
+          current_position[X_AXIS] += xydiff[X_AXIS];
6931
+          current_position[Y_AXIS] += xydiff[Y_AXIS];
6932
+          for (uint8_t i = X_AXIS; i <= Y_AXIS; i++) {
6933
+            position_shift[i] += xydiff[i];
6934
+            update_software_endstops((AxisEnum)i);
6935
+          }
6770 6936
 
6771
-      #endif // !DUAL_X_CARRIAGE
6937
+          // Set the new active extruder
6938
+          active_extruder = tmp_extruder;
6772 6939
 
6773
-      #if ENABLED(DEBUG_LEVELING_FEATURE)
6774
-        if (DEBUGGING(LEVELING)) DEBUG_POS("Sync After Toolchange", current_position);
6775
-      #endif
6776
-
6777
-      // Tell the planner the new "current position"
6778
-      SYNC_PLAN_POSITION_KINEMATIC();
6940
+        #endif // !DUAL_X_CARRIAGE
6779 6941
 
6780
-      // Move to the "old position" (move the extruder into place)
6781
-      if (!no_move && IsRunning()) {
6782 6942
         #if ENABLED(DEBUG_LEVELING_FEATURE)
6783
-          if (DEBUGGING(LEVELING)) DEBUG_POS("Move back", destination);
6943
+          if (DEBUGGING(LEVELING)) DEBUG_POS("Sync After Toolchange", current_position);
6784 6944
         #endif
6785
-        prepare_move_to_destination();
6786
-      }
6787 6945
 
6788
-    } // (tmp_extruder != active_extruder)
6946
+        // Tell the planner the new "current position"
6947
+        SYNC_PLAN_POSITION_KINEMATIC();
6789 6948
 
6790
-    stepper.synchronize();
6949
+        // Move to the "old position" (move the extruder into place)
6950
+        if (!no_move && IsRunning()) {
6951
+          #if ENABLED(DEBUG_LEVELING_FEATURE)
6952
+            if (DEBUGGING(LEVELING)) DEBUG_POS("Move back", destination);
6953
+          #endif
6954
+          prepare_move_to_destination();
6955
+        }
6791 6956
 
6792
-    #if ENABLED(EXT_SOLENOID)
6793
-      disable_all_solenoids();
6794
-      enable_solenoid_on_active_extruder();
6795
-    #endif // EXT_SOLENOID
6957
+      } // (tmp_extruder != active_extruder)
6796 6958
 
6797
-    feedrate = old_feedrate;
6959
+      stepper.synchronize();
6798 6960
 
6799
-  #else // !HOTENDS > 1
6961
+      #if ENABLED(EXT_SOLENOID)
6962
+        disable_all_solenoids();
6963
+        enable_solenoid_on_active_extruder();
6964
+      #endif // EXT_SOLENOID
6800 6965
 
6801
-    // Set the new active extruder
6802
-    active_extruder = tmp_extruder;
6966
+      feedrate = old_feedrate;
6803 6967
 
6804
-  #endif
6968
+    #else // HOTENDS <= 1
6805 6969
 
6806
-  #if ENABLED(DEBUG_LEVELING_FEATURE)
6807
-    if (DEBUGGING(LEVELING)) {
6808
-      DEBUG_POS("AFTER", current_position);
6809
-      SERIAL_ECHOLNPGM("<<< gcode_T");
6810
-    }
6811
-  #endif
6970
+      // Set the new active extruder
6971
+      active_extruder = tmp_extruder;
6812 6972
 
6813
-  SERIAL_ECHO_START;
6814
-  SERIAL_ECHOPGM(MSG_ACTIVE_EXTRUDER);
6815
-  SERIAL_PROTOCOLLN((int)active_extruder);
6973
+    #endif // HOTENDS <= 1
6974
+
6975
+    #if ENABLED(DEBUG_LEVELING_FEATURE)
6976
+      if (DEBUGGING(LEVELING)) {
6977
+        DEBUG_POS("AFTER", current_position);
6978
+        SERIAL_ECHOLNPGM("<<< gcode_T");
6979
+      }
6980
+    #endif
6981
+  
6982
+    SERIAL_ECHO_START;
6983
+    SERIAL_ECHOPGM(MSG_ACTIVE_EXTRUDER);
6984
+    SERIAL_PROTOCOLLN((int)active_extruder);
6985
+
6986
+  #endif //!MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1
6816 6987
 }
6817 6988
 
6818 6989
 /**
@@ -7219,6 +7390,22 @@ void process_next_command() {
7219 7390
 
7220 7391
       #endif //EXPERIMENTAL_I2CBUS
7221 7392
 
7393
+      #if ENABLED(MIXING_EXTRUDER)
7394
+        case 163: // M163 S<int> P<float> set weight for a mixing extruder
7395
+          gcode_M163();
7396
+          break;
7397
+        #if MIXING_VIRTUAL_TOOLS > 1
7398
+          case 164: // M164 S<int> save current mix as a virtual extruder
7399
+            gcode_M164();
7400
+            break;
7401
+        #endif
7402
+        #if ENABLED(DIRECT_MIXING_IN_G1)
7403
+          case 165: // M165 [ABCDHI]<float> set multiple mix weights
7404
+            gcode_M165();
7405
+            break;
7406
+        #endif
7407
+      #endif
7408
+
7222 7409
       case 200: // M200 D<diameter> Set filament diameter and set E axis units to cubic. (Use S0 to revert to linear units.)
7223 7410
         gcode_M200();
7224 7411
         break;
@@ -8033,14 +8220,14 @@ void prepare_move_to_destination() {
8033 8220
       nextMotorCheck = ms + 2500UL; // Not a time critical function, so only check every 2.5s
8034 8221
       if (X_ENABLE_READ == X_ENABLE_ON || Y_ENABLE_READ == Y_ENABLE_ON || Z_ENABLE_READ == Z_ENABLE_ON || thermalManager.soft_pwm_bed > 0
8035 8222
           || E0_ENABLE_READ == E_ENABLE_ON // If any of the drivers are enabled...
8036
-          #if EXTRUDERS > 1
8223
+          #if E_STEPPERS > 1
8037 8224
             || E1_ENABLE_READ == E_ENABLE_ON
8038 8225
             #if HAS_X2_ENABLE
8039 8226
               || X2_ENABLE_READ == X_ENABLE_ON
8040 8227
             #endif
8041
-            #if EXTRUDERS > 2
8228
+            #if E_STEPPERS > 2
8042 8229
               || E2_ENABLE_READ == E_ENABLE_ON
8043
-              #if EXTRUDERS > 3
8230
+              #if E_STEPPERS > 3
8044 8231
                 || E3_ENABLE_READ == E_ENABLE_ON
8045 8232
               #endif
8046 8233
             #endif
@@ -8303,25 +8490,29 @@ void manage_inactivity(bool ignore_stepper_queue/*=false*/) {
8303 8490
   #endif
8304 8491
 
8305 8492
   #if ENABLED(EXTRUDER_RUNOUT_PREVENT)
8306
-    if (ELAPSED(ms, previous_cmd_ms + (EXTRUDER_RUNOUT_SECONDS) * 1000UL))
8307
-      if (thermalManager.degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP) {
8493
+    if (ELAPSED(ms, previous_cmd_ms + (EXTRUDER_RUNOUT_SECONDS) * 1000UL)
8494
+      && thermalManager.degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP) {
8495
+      #if ENABLED(SWITCHING_EXTRUDER)
8496
+        bool oldstatus = E0_ENABLE_READ;
8497
+        enable_e0();
8498
+      #else // !SWITCHING_EXTRUDER
8308 8499
         bool oldstatus;
8309 8500
         switch (active_extruder) {
8310 8501
           case 0:
8311 8502
             oldstatus = E0_ENABLE_READ;
8312 8503
             enable_e0();
8313 8504
             break;
8314
-          #if EXTRUDERS > 1
8505
+          #if E_STEPPERS > 1
8315 8506
             case 1:
8316 8507
               oldstatus = E1_ENABLE_READ;
8317 8508
               enable_e1();
8318 8509
               break;
8319
-            #if EXTRUDERS > 2
8510
+            #if E_STEPPERS > 2
8320 8511
               case 2:
8321 8512
                 oldstatus = E2_ENABLE_READ;
8322 8513
                 enable_e2();
8323 8514
                 break;
8324
-              #if EXTRUDERS > 3
8515
+              #if E_STEPPERS > 3
8325 8516
                 case 3:
8326 8517
                   oldstatus = E3_ENABLE_READ;
8327 8518
                   enable_e3();
@@ -8330,37 +8521,43 @@ void manage_inactivity(bool ignore_stepper_queue/*=false*/) {
8330 8521
             #endif
8331 8522
           #endif
8332 8523
         }
8333
-        float oldepos = current_position[E_AXIS], oldedes = destination[E_AXIS];
8334
-        planner.buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS],
8335
-                         destination[E_AXIS] + (EXTRUDER_RUNOUT_EXTRUDE) * (EXTRUDER_RUNOUT_ESTEPS) / planner.axis_steps_per_mm[E_AXIS],
8336
-                         (EXTRUDER_RUNOUT_SPEED) / 60. * (EXTRUDER_RUNOUT_ESTEPS) / planner.axis_steps_per_mm[E_AXIS], active_extruder);
8524
+      #endif // !SWITCHING_EXTRUDER
8525
+
8526
+      float oldepos = current_position[E_AXIS], oldedes = destination[E_AXIS];
8527
+      planner.buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS],
8528
+                       destination[E_AXIS] + (EXTRUDER_RUNOUT_EXTRUDE) * (EXTRUDER_RUNOUT_ESTEPS) / planner.axis_steps_per_mm[E_AXIS],
8529
+                       (EXTRUDER_RUNOUT_SPEED) / 60. * (EXTRUDER_RUNOUT_ESTEPS) / planner.axis_steps_per_mm[E_AXIS], active_extruder);
8337 8530
       current_position[E_AXIS] = oldepos;
8338 8531
       destination[E_AXIS] = oldedes;
8339 8532
       planner.set_e_position_mm(oldepos);
8340 8533
       previous_cmd_ms = ms; // refresh_cmd_timeout()
8341 8534
       stepper.synchronize();
8342
-      switch (active_extruder) {
8343
-        case 0:
8344
-          E0_ENABLE_WRITE(oldstatus);
8345
-          break;
8346
-        #if EXTRUDERS > 1
8347
-          case 1:
8348
-            E1_ENABLE_WRITE(oldstatus);
8535
+      #if ENABLED(SWITCHING_EXTRUDER)
8536
+        E0_ENABLE_WRITE(oldstatus);
8537
+      #else
8538
+        switch (active_extruder) {
8539
+          case 0:
8540
+            E0_ENABLE_WRITE(oldstatus);
8349 8541
             break;
8350
-          #if EXTRUDERS > 2
8351
-            case 2:
8352
-              E2_ENABLE_WRITE(oldstatus);
8542
+          #if E_STEPPERS > 1
8543
+            case 1:
8544
+              E1_ENABLE_WRITE(oldstatus);
8353 8545
               break;
8354
-            #if EXTRUDERS > 3
8355
-              case 3:
8356
-                E3_ENABLE_WRITE(oldstatus);
8546
+            #if E_STEPPERS > 2
8547
+              case 2:
8548
+                E2_ENABLE_WRITE(oldstatus);
8357 8549
                 break;
8550
+              #if E_STEPPERS > 3
8551
+                case 3:
8552
+                  E3_ENABLE_WRITE(oldstatus);
8553
+                  break;
8554
+              #endif
8358 8555
             #endif
8359 8556
           #endif
8360
-        #endif
8361
-      }
8557
+        }
8558
+      #endif // !SWITCHING_EXTRUDER
8362 8559
     }
8363
-  #endif
8560
+  #endif // EXTRUDER_RUNOUT_PREVENT
8364 8561
 
8365 8562
   #if ENABLED(DUAL_X_CARRIAGE)
8366 8563
     // handle delayed move timeout
@@ -8498,6 +8695,6 @@ float calculate_volumetric_multiplier(float diameter) {
8498 8695
 }
8499 8696
 
8500 8697
 void calculate_volumetric_multipliers() {
8501
-  for (int i = 0; i < EXTRUDERS; i++)
8698
+  for (int i = 0; i < COUNT(filament_size); i++)
8502 8699
     volumetric_multiplier[i] = calculate_volumetric_multiplier(filament_size[i]);
8503 8700
 }

+ 2
- 2
Marlin/configuration_store.cpp View File

@@ -349,7 +349,7 @@ void Config_StoreSettings()  {
349 349
 
350 350
   // Save filament sizes
351 351
   for (uint8_t q = 0; q < MAX_EXTRUDERS; q++) {
352
-    if (q < EXTRUDERS) dummy = filament_size[q];
352
+    if (q < COUNT(filament_size)) dummy = filament_size[q];
353 353
     EEPROM_WRITE_VAR(i, dummy);
354 354
   }
355 355
 
@@ -531,7 +531,7 @@ void Config_RetrieveSettings() {
531 531
 
532 532
     for (uint8_t q = 0; q < MAX_EXTRUDERS; q++) {
533 533
       EEPROM_READ_VAR(i, dummy);
534
-      if (q < EXTRUDERS) filament_size[q] = dummy;
534
+      if (q < COUNT(filament_size)) filament_size[q] = dummy;
535 535
     }
536 536
 
537 537
     if (eeprom_checksum == stored_checksum) {

+ 3
- 0
Marlin/language_en.h View File

@@ -251,6 +251,9 @@
251 251
 #ifndef MSG_PID_C
252 252
   #define MSG_PID_C                           "PID-C"
253 253
 #endif
254
+#ifndef MSG_SELECT
255
+  #define MSG_SELECT                          "Select"
256
+#endif
254 257
 #ifndef MSG_E1
255 258
   #define MSG_E1                              " E1"
256 259
 #endif

+ 16
- 5
Marlin/pins.h View File

@@ -285,6 +285,17 @@
285 285
       #define _H3_PINS HEATER_3_PIN, EXTRUDER_3_AUTO_FAN_PIN, marlinAnalogInputToDigitalPin(TEMP_3_PIN),
286 286
     #endif
287 287
   #endif
288
+#elif ENABLED(MIXING_EXTRUDER)
289
+  #undef _E1_PINS
290
+  #define _E1_PINS E1_STEP_PIN, E1_DIR_PIN, E1_ENABLE_PIN,
291
+  #if MIXING_STEPPERS > 2
292
+    #undef _E2_PINS
293
+    #define _E2_PINS E2_STEP_PIN, E2_DIR_PIN, E2_ENABLE_PIN,
294
+    #if MIXING_STEPPERS > 3
295
+      #undef _E3_PINS
296
+      #define _E3_PINS E3_STEP_PIN, E3_DIR_PIN, E3_ENABLE_PIN,
297
+    #endif
298
+  #endif
288 299
 #endif
289 300
 
290 301
 #define BED_PINS HEATER_BED_PIN, marlinAnalogInputToDigitalPin(TEMP_BED_PIN),
@@ -374,15 +385,15 @@
374 385
 // The X2 axis, if any, should be the next open extruder port
375 386
 #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(X_DUAL_STEPPER_DRIVERS)
376 387
   #ifndef X2_STEP_PIN
377
-    #define X2_STEP_PIN   _EPIN(EXTRUDERS, STEP)
378
-    #define X2_DIR_PIN    _EPIN(EXTRUDERS, DIR)
379
-    #define X2_ENABLE_PIN _EPIN(EXTRUDERS, ENABLE)
388
+    #define X2_STEP_PIN   _EPIN(E_STEPPERS, STEP)
389
+    #define X2_DIR_PIN    _EPIN(E_STEPPERS, DIR)
390
+    #define X2_ENABLE_PIN _EPIN(E_STEPPERS, ENABLE)
380 391
   #endif
381 392
   #undef _X2_PINS
382 393
   #define _X2_PINS X2_STEP_PIN, X2_DIR_PIN, X2_ENABLE_PIN,
383
-  #define Y2_E_INDEX INCREMENT(EXTRUDERS)
394
+  #define Y2_E_INDEX INCREMENT(E_STEPPERS)
384 395
 #else
385
-  #define Y2_E_INDEX EXTRUDERS
396
+  #define Y2_E_INDEX E_STEPPERS
386 397
 #endif
387 398
 
388 399
 // The Y2 axis, if any, should be the next open extruder port

+ 2
- 2
Marlin/pins_MEGACONTROLLER.h View File

@@ -28,8 +28,8 @@
28 28
   #error "Oops!  Make sure you have 'Arduino Mega' selected from the 'Tools -> Boards' menu."
29 29
 #endif
30 30
 
31
-#if EXTRUDERS > 2 || HOTENDS > 2
32
-  #error "Mega Controller supports up to 2 extruders. Comment this line to keep going."
31
+#if E_STEPPERS > 2 || HOTENDS > 2
32
+  #error "Mega Controller supports up to 2 hotends / E-steppers. Comment this line to keep going."
33 33
 #endif
34 34
 
35 35
 #define BOARD_NAME "Mega Controller"

+ 2
- 2
Marlin/pins_RUMBA.h View File

@@ -28,8 +28,8 @@
28 28
   #error "Oops!  Make sure you have 'Arduino Mega' selected from the 'Tools -> Boards' menu."
29 29
 #endif
30 30
 
31
-#if EXTRUDERS > 3 || HOTENDS > 3
32
-  #error "RUMBA supports up to 3 extruders. Comment this line to keep going."
31
+#if E_STEPPERS > 3 || HOTENDS > 3
32
+  #error "RUMBA supports up to 3 hotends / E-steppers. Comment this line to keep going."
33 33
 #endif
34 34
 
35 35
 #define DEFAULT_MACHINE_NAME "Rumba"

+ 6
- 0
Marlin/planner.cpp View File

@@ -629,6 +629,12 @@ void Planner::check_axes_activity() {
629 629
   // Bail if this is a zero-length block
630 630
   if (block->step_event_count <= dropsegments) return;
631 631
 
632
+  // For a mixing extruder, get a magnified step_event_count for each
633
+  #if ENABLED(MIXING_EXTRUDER)
634
+    for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
635
+      block->mix_event_count[i] = (mixing_factor[i] < 0.0001) ? 0 : block->step_event_count / mixing_factor[i];
636
+  #endif
637
+
632 638
   #if FAN_COUNT > 0
633 639
     for (uint8_t i = 0; i < FAN_COUNT; i++) block->fan_speed[i] = fanSpeeds[i];
634 640
   #endif

+ 4
- 0
Marlin/planner.h View File

@@ -58,6 +58,10 @@ typedef struct {
58 58
   long steps[NUM_AXIS];                     // Step count along each axis
59 59
   unsigned long step_event_count;           // The number of step events required to complete this block
60 60
 
61
+  #if ENABLED(MIXING_EXTRUDER)
62
+    unsigned long mix_event_count[MIXING_STEPPERS]; // Scaled step_event_count for the mixing steppers
63
+  #endif
64
+
61 65
   long accelerate_until,                    // The index of the step event on which to stop acceleration
62 66
        decelerate_after,                    // The index of the step event on which to start decelerating
63 67
        acceleration_rate;                   // The acceleration rate used for acceleration calculation

+ 159
- 48
Marlin/stepper.cpp View File

@@ -95,13 +95,13 @@ volatile unsigned long Stepper::step_events_completed = 0; // The number of step
95 95
   volatile unsigned char Stepper::eISR_Rate = 200; // Keep the ISR at a low rate until needed
96 96
 
97 97
   #if ENABLED(LIN_ADVANCE)
98
-    volatile int Stepper::e_steps[EXTRUDERS];
98
+    volatile int Stepper::e_steps[E_STEPPERS];
99 99
     int Stepper::extruder_advance_k = LIN_ADVANCE_K,
100 100
         Stepper::final_estep_rate,
101
-        Stepper::current_estep_rate[EXTRUDERS],
102
-        Stepper::current_adv_steps[EXTRUDERS];
101
+        Stepper::current_estep_rate[E_STEPPERS],
102
+        Stepper::current_adv_steps[E_STEPPERS];
103 103
   #else
104
-    long  Stepper::e_steps[EXTRUDERS],
104
+    long  Stepper::e_steps[E_STEPPERS],
105 105
           Stepper::final_advance = 0,
106 106
           Stepper::old_advance = 0,
107 107
           Stepper::advance_rate,
@@ -114,6 +114,10 @@ long Stepper::acceleration_time, Stepper::deceleration_time;
114 114
 volatile long Stepper::count_position[NUM_AXIS] = { 0 };
115 115
 volatile signed char Stepper::count_direction[NUM_AXIS] = { 1, 1, 1, 1 };
116 116
 
117
+#if ENABLED(MIXING_EXTRUDER)
118
+  long Stepper::counter_M[MIXING_STEPPERS];
119
+#endif
120
+
117 121
 unsigned short Stepper::acc_step_rate; // needed for deceleration start point
118 122
 uint8_t Stepper::step_loops, Stepper::step_loops_nominal;
119 123
 unsigned short Stepper::OCR1A_nominal;
@@ -179,7 +183,9 @@ volatile long Stepper::endstops_trigsteps[3];
179 183
   #define Z_APPLY_STEP(v,Q) Z_STEP_WRITE(v)
180 184
 #endif
181 185
 
182
-#define E_APPLY_STEP(v,Q) E_STEP_WRITE(v)
186
+#if DISABLED(MIXING_EXTRUDER)
187
+  #define E_APPLY_STEP(v,Q) E_STEP_WRITE(v)
188
+#endif
183 189
 
184 190
 // intRes = longIn1 * longIn2 >> 24
185 191
 // uses:
@@ -322,8 +328,15 @@ void Stepper::isr() {
322 328
     if (current_block) {
323 329
       current_block->busy = true;
324 330
       trapezoid_generator_reset();
325
-      counter_X = -(current_block->step_event_count >> 1);
326
-      counter_Y = counter_Z = counter_E = counter_X;
331
+
332
+      // Initialize Bresenham counters to 1/2 the ceiling
333
+      counter_X = counter_Y = counter_Z = counter_E = -(current_block->step_event_count >> 1);
334
+
335
+      #if ENABLED(MIXING_EXTRUDER)
336
+        MIXING_STEPPERS_LOOP(i)
337
+          counter_M[i] = -(current_block->mix_event_count[i] >> 1);
338
+      #endif
339
+
327 340
       step_events_completed = 0;
328 341
 
329 342
       #if ENABLED(Z_LATE_ENABLE)
@@ -335,7 +348,7 @@ void Stepper::isr() {
335 348
       #endif
336 349
 
337 350
       // #if ENABLED(ADVANCE)
338
-      //   e_steps[current_block->active_extruder] = 0;
351
+      //   e_steps[TOOL_E_INDEX] = 0;
339 352
       // #endif
340 353
     }
341 354
     else {
@@ -343,7 +356,7 @@ void Stepper::isr() {
343 356
     }
344 357
   }
345 358
 
346
-  if (current_block != NULL) {
359
+  if (current_block) {
347 360
 
348 361
     // Update endstops state, if enabled
349 362
     #if HAS_BED_PROBE
@@ -363,25 +376,67 @@ void Stepper::isr() {
363 376
         counter_E += current_block->steps[E_AXIS];
364 377
         if (counter_E > 0) {
365 378
           counter_E -= current_block->step_event_count;
366
-          count_position[E_AXIS] += count_direction[E_AXIS];
367
-          e_steps[current_block->active_extruder] += motor_direction(E_AXIS) ? -1 : 1;
379
+          #if DISABLED(MIXING_EXTRUDER)
380
+            // Don't step E here for mixing extruder
381
+            count_position[E_AXIS] += count_direction[E_AXIS];
382
+            e_steps[TOOL_E_INDEX] += motor_direction(E_AXIS) ? -1 : 1;
383
+          #endif
368 384
         }
369 385
 
386
+        #if ENABLED(MIXING_EXTRUDER)
387
+          // Step mixing steppers proportionally
388
+          long dir = motor_direction(E_AXIS) ? -1 : 1;
389
+          MIXING_STEPPERS_LOOP(j) {
390
+            counter_m[j] += current_block->steps[E_AXIS];
391
+            if (counter_m[j] > 0) {
392
+              counter_m[j] -= current_block->mix_event_count[j];
393
+              e_steps[j] += dir;
394
+            }
395
+          }
396
+        #endif
397
+
370 398
         if (current_block->use_advance_lead) {
371
-          int delta_adv_steps; //Maybe a char would be enough?
372
-          delta_adv_steps = (((long)extruder_advance_k * current_estep_rate[current_block->active_extruder]) >> 9) - current_adv_steps[current_block->active_extruder];
373
-          e_steps[current_block->active_extruder] += delta_adv_steps;
374
-          current_adv_steps[current_block->active_extruder] += delta_adv_steps;
399
+          int delta_adv_steps = (((long)extruder_advance_k * current_estep_rate[TOOL_E_INDEX]) >> 9) - current_adv_steps[TOOL_E_INDEX];
400
+          #if ENABLED(MIXING_EXTRUDER)
401
+            // Mixing extruders apply advance lead proportionally
402
+            MIXING_STEPPERS_LOOP(j) {
403
+              int steps = delta_adv_steps * current_block->step_event_count / current_block->mix_event_count[j];
404
+              e_steps[j] += steps;
405
+              current_adv_steps[j] += steps;
406
+            }
407
+          #else
408
+            // For most extruders, advance the single E stepper
409
+            e_steps[TOOL_E_INDEX] += delta_adv_steps;
410
+            current_adv_steps[TOOL_E_INDEX] += delta_adv_steps;
411
+          #endif
375 412
         }
376 413
 
377 414
       #elif ENABLED(ADVANCE)
378 415
 
416
+        // Always count the unified E axis
379 417
         counter_E += current_block->steps[E_AXIS];
380 418
         if (counter_E > 0) {
381 419
           counter_E -= current_block->step_event_count;
382
-          e_steps[current_block->active_extruder] += motor_direction(E_AXIS) ? -1 : 1;
420
+          #if DISABLED(MIXING_EXTRUDER)
421
+            // Don't step E here for mixing extruder
422
+            e_steps[TOOL_E_INDEX] += motor_direction(E_AXIS) ? -1 : 1;
423
+          #endif
383 424
         }
384 425
 
426
+        #if ENABLED(MIXING_EXTRUDER)
427
+
428
+          // Step mixing steppers proportionally
429
+          long dir = motor_direction(E_AXIS) ? -1 : 1;
430
+          MIXING_STEPPERS_LOOP(j) {
431
+            counter_m[j] += current_block->steps[E_AXIS];
432
+            if (counter_m[j] > 0) {
433
+              counter_m[j] -= current_block->mix_event_count[j];
434
+              e_steps[j] += dir;
435
+            }
436
+          }
437
+
438
+        #endif // MIXING_EXTRUDER
439
+
385 440
       #endif // ADVANCE or LIN_ADVANCE
386 441
 
387 442
       #define _COUNTER(AXIS) counter_## AXIS
@@ -395,9 +450,22 @@ void Stepper::isr() {
395 450
       STEP_ADD(X);
396 451
       STEP_ADD(Y);
397 452
       STEP_ADD(Z);
453
+
398 454
       #if DISABLED(ADVANCE) && DISABLED(LIN_ADVANCE)
399
-        STEP_ADD(E);
400
-      #endif
455
+        #if ENABLED(MIXING_EXTRUDER)
456
+          // Keep updating the single E axis
457
+          counter_E += current_block->steps[E_AXIS];
458
+          // Tick the counters used for this mix
459
+          MIXING_STEPPERS_LOOP(j) {
460
+            // Step mixing steppers (proportionally)
461
+            counter_M[j] += current_block->steps[E_AXIS];
462
+            // Step when the counter goes over zero
463
+            if (counter_M[j] > 0) En_STEP_WRITE(j, !INVERT_E_STEP_PIN);
464
+          }
465
+        #else // !MIXING_EXTRUDER
466
+          STEP_ADD(E);
467
+        #endif
468
+      #endif // !ADVANCE && !LIN_ADVANCE
401 469
 
402 470
       #define STEP_IF_COUNTER(AXIS) \
403 471
         if (_COUNTER(AXIS) > 0) { \
@@ -409,17 +477,32 @@ void Stepper::isr() {
409 477
       STEP_IF_COUNTER(X);
410 478
       STEP_IF_COUNTER(Y);
411 479
       STEP_IF_COUNTER(Z);
480
+
412 481
       #if DISABLED(ADVANCE) && DISABLED(LIN_ADVANCE)
413
-        STEP_IF_COUNTER(E);
414
-      #endif
482
+        #if ENABLED(MIXING_EXTRUDER)
483
+          // Always step the single E axis
484
+          if (counter_E > 0) {
485
+            counter_E -= current_block->step_event_count;
486
+            count_position[E_AXIS] += count_direction[E_AXIS];
487
+          }
488
+          MIXING_STEPPERS_LOOP(j) {
489
+            if (counter_M[j] > 0) {
490
+              counter_M[j] -= current_block->mix_event_count[j];
491
+              En_STEP_WRITE(j, INVERT_E_STEP_PIN);
492
+            }
493
+          }
494
+        #else // !MIXING_EXTRUDER
495
+          STEP_IF_COUNTER(E);
496
+        #endif
497
+      #endif // !ADVANCE && !LIN_ADVANCE
415 498
 
416 499
       step_events_completed++;
417 500
       if (step_events_completed >= current_block->step_event_count) break;
418 501
     }
419 502
 
420
-    #if ENABLED(LIN_ADVANCE)
503
+    #if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
421 504
       // If we have esteps to execute, fire the next ISR "now"
422
-      if (e_steps[current_block->active_extruder]) OCR0A = TCNT0 + 2;
505
+      if (e_steps[TOOL_E_INDEX]) OCR0A = TCNT0 + 2;
423 506
     #endif
424 507
 
425 508
     // Calculate new timer value
@@ -440,21 +523,41 @@ void Stepper::isr() {
440 523
       #if ENABLED(LIN_ADVANCE)
441 524
 
442 525
         if (current_block->use_advance_lead)
443
-          current_estep_rate[current_block->active_extruder] = ((unsigned long)acc_step_rate * current_block->e_speed_multiplier8) >> 8;
526
+          current_estep_rate[TOOL_E_INDEX] = ((unsigned long)acc_step_rate * current_block->e_speed_multiplier8) >> 8;
527
+
528
+        if (current_block->use_advance_lead) {
529
+          #if ENABLED(MIXING_EXTRUDER)
530
+            MIXING_STEPPERS_LOOP(j)
531
+              current_estep_rate[j] = ((unsigned long)acc_step_rate * current_block->e_speed_multiplier8 * current_block->step_event_count / current_block->mix_event_count[j]) >> 8;
532
+          #else
533
+            current_estep_rate[TOOL_E_INDEX] = ((unsigned long)acc_step_rate * current_block->e_speed_multiplier8) >> 8;
534
+          #endif
535
+        }
444 536
 
445 537
       #elif ENABLED(ADVANCE)
446 538
 
447 539
         advance += advance_rate * step_loops;
448 540
         //NOLESS(advance, current_block->advance);
449 541
 
542
+        long advance_whole = advance >> 8,
543
+             advance_factor = advance_whole - old_advance;
544
+
450 545
         // Do E steps + advance steps
451
-        e_steps[current_block->active_extruder] += ((advance >> 8) - old_advance);
452
-        old_advance = advance >> 8;
546
+        #if ENABLED(MIXING_EXTRUDER)
547
+          // ...for mixing steppers proportionally
548
+          MIXING_STEPPERS_LOOP(j)
549
+            e_steps[j] += advance_factor * current_block->step_event_count / current_block->mix_event_count[j];
550
+        #else
551
+          // ...for the active extruder
552
+          e_steps[TOOL_E_INDEX] += advance_factor;
553
+        #endif
554
+
555
+        old_advance = advance_whole;
453 556
 
454 557
       #endif // ADVANCE or LIN_ADVANCE
455 558
 
456 559
       #if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
457
-        eISR_Rate = (timer >> 2) * step_loops / abs(e_steps[current_block->active_extruder]);
560
+        eISR_Rate = (timer >> 2) * step_loops / abs(e_steps[TOOL_E_INDEX]);
458 561
       #endif
459 562
     }
460 563
     else if (step_events_completed > (unsigned long)current_block->decelerate_after) {
@@ -474,8 +577,14 @@ void Stepper::isr() {
474 577
 
475 578
       #if ENABLED(LIN_ADVANCE)
476 579
 
477
-        if (current_block->use_advance_lead)
478
-          current_estep_rate[current_block->active_extruder] = ((unsigned long)step_rate * current_block->e_speed_multiplier8) >> 8;
580
+        if (current_block->use_advance_lead) {
581
+          #if ENABLED(MIXING_EXTRUDER)
582
+            MIXING_STEPPERS_LOOP(j)
583
+              current_estep_rate[j] = ((unsigned long)step_rate * current_block->e_speed_multiplier8 * current_block->step_event_count / current_block->mix_event_count[j]) >> 8;
584
+          #else
585
+            current_estep_rate[TOOL_E_INDEX] = ((unsigned long)step_rate * current_block->e_speed_multiplier8) >> 8;
586
+          #endif
587
+        }
479 588
 
480 589
       #elif ENABLED(ADVANCE)
481 590
 
@@ -483,14 +592,22 @@ void Stepper::isr() {
483 592
         NOLESS(advance, final_advance);
484 593
 
485 594
         // Do E steps + advance steps
486
-        uint32_t advance_whole = advance >> 8;
487
-        e_steps[current_block->active_extruder] += advance_whole - old_advance;
595
+        long advance_whole = advance >> 8,
596
+             advance_factor = advance_whole - old_advance;
597
+
598
+        #if ENABLED(MIXING_EXTRUDER)
599
+          MIXING_STEPPERS_LOOP(j)
600
+            e_steps[j] += advance_factor * current_block->step_event_count / current_block->mix_event_count[j];
601
+        #else
602
+          e_steps[TOOL_E_INDEX] += advance_factor;
603
+        #endif
604
+
488 605
         old_advance = advance_whole;
489 606
 
490 607
       #endif // ADVANCE or LIN_ADVANCE
491 608
 
492 609
       #if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
493
-        eISR_Rate = (timer >> 2) * step_loops / abs(e_steps[current_block->active_extruder]);
610
+        eISR_Rate = (timer >> 2) * step_loops / abs(e_steps[TOOL_E_INDEX]);
494 611
       #endif
495 612
     }
496 613
     else {
@@ -498,9 +615,9 @@ void Stepper::isr() {
498 615
       #if ENABLED(LIN_ADVANCE)
499 616
 
500 617
         if (current_block->use_advance_lead)
501
-          current_estep_rate[current_block->active_extruder] = final_estep_rate;
618
+          current_estep_rate[TOOL_E_INDEX] = final_estep_rate;
502 619
 
503
-        eISR_Rate = (OCR1A_nominal >> 2) * step_loops_nominal / abs(e_steps[current_block->active_extruder]);
620
+        eISR_Rate = (OCR1A_nominal >> 2) * step_loops_nominal / abs(e_steps[TOOL_E_INDEX]);
504 621
 
505 622
       #endif
506 623
 
@@ -537,7 +654,7 @@ void Stepper::isr() {
537 654
           E## INDEX ##_DIR_WRITE(INVERT_E## INDEX ##_DIR); \
538 655
           e_steps[INDEX]++; \
539 656
         } \
540
-        else if (e_steps[INDEX] > 0) { \
657
+        else { \
541 658
           E## INDEX ##_DIR_WRITE(!INVERT_E## INDEX ##_DIR); \
542 659
           e_steps[INDEX]--; \
543 660
         } \
@@ -547,11 +664,11 @@ void Stepper::isr() {
547 664
     // Step all E steppers that have steps
548 665
     for (uint8_t i = 0; i < step_loops; i++) {
549 666
       STEP_E_ONCE(0);
550
-      #if EXTRUDERS > 1
667
+      #if E_STEPPERS > 1
551 668
         STEP_E_ONCE(1);
552
-        #if EXTRUDERS > 2
669
+        #if E_STEPPERS > 2
553 670
           STEP_E_ONCE(2);
554
-          #if EXTRUDERS > 3
671
+          #if E_STEPPERS > 3
555 672
             STEP_E_ONCE(3);
556 673
           #endif
557 674
         #endif
@@ -730,18 +847,12 @@ void Stepper::init() {
730 847
 
731 848
   #if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
732 849
 
733
-    #if ENABLED(LIN_ADVANCE)
734
-
735
-      for (int i = 0; i < EXTRUDERS; i++) {
736
-        e_steps[i] = 0;
850
+    for (int i = 0; i < E_STEPPERS; i++) {
851
+      e_steps[i] = 0;
852
+      #if ENABLED(LIN_ADVANCE)
737 853
         current_adv_steps[i] = 0;
738
-      }
739
-
740
-    #elif ENABLED(ADVANCE)
741
-
742
-      for (uint8_t i = 0; i < EXTRUDERS; i++) e_steps[i] = 0;
743
-
744
-    #endif
854
+      #endif
855
+    }
745 856
 
746 857
     #if defined(TCCR0A) && defined(WGM01)
747 858
       CBI(TCCR0A, WGM01);

+ 29
- 6
Marlin/stepper.h View File

@@ -107,15 +107,15 @@ class Stepper {
107 107
       static unsigned char old_OCR0A;
108 108
       static volatile unsigned char eISR_Rate;
109 109
       #if ENABLED(LIN_ADVANCE)
110
-        static volatile int e_steps[EXTRUDERS];
110
+        static volatile int e_steps[E_STEPPERS];
111 111
         static int extruder_advance_k;
112 112
         static int final_estep_rate;
113
-        static int current_estep_rate[EXTRUDERS]; // Actual extruder speed [steps/s]
114
-        static int current_adv_steps[EXTRUDERS];  // The amount of current added esteps due to advance.
113
+        static int current_estep_rate[E_STEPPERS]; // Actual extruder speed [steps/s]
114
+        static int current_adv_steps[E_STEPPERS];  // The amount of current added esteps due to advance.
115 115
                                                   // i.e., the current amount of pressure applied
116 116
                                                   // to the spring (=filament).
117 117
       #else
118
-        static long e_steps[EXTRUDERS];
118
+        static long e_steps[E_STEPPERS];
119 119
         static long advance_rate, advance, final_advance;
120 120
         static long old_advance;
121 121
       #endif
@@ -147,6 +147,16 @@ class Stepper {
147 147
     //
148 148
     static volatile signed char count_direction[NUM_AXIS];
149 149
 
150
+    //
151
+    // Mixing extruder mix counters
152
+    //
153
+    #if ENABLED(MIXING_EXTRUDER)
154
+      static long counter_M[MIXING_STEPPERS];
155
+      #define MIXING_STEPPERS_LOOP(VAR) \
156
+        for (uint8_t VAR = 0; VAR < MIXING_STEPPERS; VAR++) \
157
+          if (current_block->mix_event_count[VAR])
158
+    #endif
159
+
150 160
   public:
151 161
 
152 162
     //
@@ -315,12 +325,25 @@ class Stepper {
315 325
       }
316 326
 
317 327
       #if ENABLED(ADVANCE)
328
+
318 329
         advance = current_block->initial_advance;
319 330
         final_advance = current_block->final_advance;
331
+
320 332
         // Do E steps + advance steps
321
-        e_steps[current_block->active_extruder] += ((advance >>8) - old_advance);
322
-        old_advance = advance >>8;
333
+        #if ENABLED(MIXING_EXTRUDER)
334
+          long advance_factor = (advance >> 8) - old_advance;
335
+          // ...for mixing steppers proportionally
336
+          MIXING_STEPPERS_LOOP(j)
337
+            e_steps[j] += advance_factor * current_block->step_event_count / current_block->mix_event_count[j];
338
+        #else
339
+          // ...for the active extruder
340
+          e_steps[TOOL_E_INDEX] += ((advance >> 8) - old_advance);
341
+        #endif
342
+
343
+        old_advance = advance >> 8;
344
+
323 345
       #endif
346
+
324 347
       deceleration_time = 0;
325 348
       // step_rate to timer interval
326 349
       OCR1A_nominal = calc_timer(current_block->nominal_rate);

+ 32
- 16
Marlin/stepper_indirection.h View File

@@ -182,26 +182,42 @@
182 182
 #define E3_ENABLE_WRITE(STATE) WRITE(E3_ENABLE_PIN,STATE)
183 183
 #define E3_ENABLE_READ READ(E3_ENABLE_PIN)
184 184
 
185
-#if EXTRUDERS > 3
186
-  #define E_STEP_WRITE(v) {switch(current_block->active_extruder){case 3:E3_STEP_WRITE(v);break;case 2:E2_STEP_WRITE(v);break;case 1:E1_STEP_WRITE(v);break;default:E0_STEP_WRITE(v);}}
187
-  #define NORM_E_DIR() {switch(current_block->active_extruder){case 3:E3_DIR_WRITE(!INVERT_E3_DIR);break;case 2:E2_DIR_WRITE(!INVERT_E2_DIR);break;case 1:E1_DIR_WRITE(!INVERT_E1_DIR);break;default:E0_DIR_WRITE(!INVERT_E0_DIR);}}
188
-  #define REV_E_DIR() {switch(current_block->active_extruder){case 3:E3_DIR_WRITE(INVERT_E3_DIR);break;case 2:E2_DIR_WRITE(INVERT_E2_DIR);break;case 1:E1_DIR_WRITE(INVERT_E1_DIR);break;default:E0_DIR_WRITE(INVERT_E0_DIR);}}
185
+#if ENABLED(SWITCHING_EXTRUDER)
186
+  #define E_STEP_WRITE(v) E0_STEP_WRITE(v)
187
+  #define NORM_E_DIR() E0_DIR_WRITE(current_block->active_extruder ?  INVERT_E0_DIR : !INVERT_E0_DIR)
188
+  #define  REV_E_DIR() E0_DIR_WRITE(current_block->active_extruder ? !INVERT_E0_DIR :  INVERT_E0_DIR)
189
+#elif EXTRUDERS > 3
190
+  #define E_STEP_WRITE(v) { switch (current_block->active_extruder) { case 0: E0_STEP_WRITE(v); break; case 1: E1_STEP_WRITE(v); break; case 2: E2_STEP_WRITE(v); break; case 3: E3_STEP_WRITE(v); } }
191
+  #define NORM_E_DIR() { switch (current_block->active_extruder) { case 0: E0_DIR_WRITE(!INVERT_E0_DIR); break; case 1: E1_DIR_WRITE(!INVERT_E1_DIR); break; case 2: E2_DIR_WRITE(!INVERT_E2_DIR); break; case 3: E3_DIR_WRITE(!INVERT_E3_DIR); } }
192
+  #define REV_E_DIR() { switch (current_block->active_extruder) { case 0: E0_DIR_WRITE(INVERT_E0_DIR); break; case 1: E1_DIR_WRITE(INVERT_E1_DIR); break; case 2: E2_DIR_WRITE(INVERT_E2_DIR); break; case 3: E3_DIR_WRITE(INVERT_E3_DIR); } }
189 193
 #elif EXTRUDERS > 2
190
-  #define E_STEP_WRITE(v) {switch(current_block->active_extruder){case 2:E2_STEP_WRITE(v);break;case 1:E1_STEP_WRITE(v);break;default:E0_STEP_WRITE(v);}}
191
-  #define NORM_E_DIR() {switch(current_block->active_extruder){case 2:E2_DIR_WRITE(!INVERT_E2_DIR);break;case 1:E1_DIR_WRITE(!INVERT_E1_DIR);break;default:E0_DIR_WRITE(!INVERT_E0_DIR);}}
192
-  #define REV_E_DIR() {switch(current_block->active_extruder){case 2:E2_DIR_WRITE(INVERT_E2_DIR);break;case 1:E1_DIR_WRITE(INVERT_E1_DIR);break;default:E0_DIR_WRITE(INVERT_E0_DIR);}}
194
+  #define E_STEP_WRITE(v) { switch (current_block->active_extruder) { case 0: E0_STEP_WRITE(v); break; case 1: E1_STEP_WRITE(v); break; case 2: E2_STEP_WRITE(v); } }
195
+  #define NORM_E_DIR() { switch (current_block->active_extruder) { case 0: E0_DIR_WRITE(!INVERT_E0_DIR); break; case 1: E1_DIR_WRITE(!INVERT_E1_DIR); break; case 2: E2_DIR_WRITE(!INVERT_E2_DIR); } }
196
+  #define REV_E_DIR() { switch (current_block->active_extruder) { case 0: E0_DIR_WRITE(INVERT_E0_DIR); break; case 1: E1_DIR_WRITE(INVERT_E1_DIR); break; case 2: E2_DIR_WRITE(INVERT_E2_DIR); } }
193 197
 #elif EXTRUDERS > 1
194
-  #define _E_STEP_WRITE(v) {if(current_block->active_extruder==1){E1_STEP_WRITE(v);}else{E0_STEP_WRITE(v);}}
195
-  #define _NORM_E_DIR() {if(current_block->active_extruder==1){E1_DIR_WRITE(!INVERT_E1_DIR);}else{E0_DIR_WRITE(!INVERT_E0_DIR);}}
196
-  #define _REV_E_DIR() {if(current_block->active_extruder==1){E1_DIR_WRITE(INVERT_E1_DIR);}else{E0_DIR_WRITE(INVERT_E0_DIR);}}
197 198
   #if DISABLED(DUAL_X_CARRIAGE)
198
-    #define E_STEP_WRITE(v) _E_STEP_WRITE(v)
199
-    #define NORM_E_DIR() _NORM_E_DIR()
200
-    #define REV_E_DIR() _REV_E_DIR()
199
+    #define E_STEP_WRITE(v) { if (current_block->active_extruder == 0) { E0_STEP_WRITE(v); } else { E1_STEP_WRITE(v); } }
200
+    #define NORM_E_DIR() { if (current_block->active_extruder == 0) { E0_DIR_WRITE(!INVERT_E0_DIR); } else { E1_DIR_WRITE(!INVERT_E1_DIR); } }
201
+    #define REV_E_DIR() { if (current_block->active_extruder == 0) { E0_DIR_WRITE(INVERT_E0_DIR); } else { E1_DIR_WRITE(INVERT_E1_DIR); } }
202
+  #else
203
+    #define E_STEP_WRITE(v) { if (extruder_duplication_enabled) { E0_STEP_WRITE(v); E1_STEP_WRITE(v); } else if (current_block->active_extruder == 0) { E0_STEP_WRITE(v); } else { E1_STEP_WRITE(v); } }
204
+    #define NORM_E_DIR() { if (extruder_duplication_enabled) { E0_DIR_WRITE(!INVERT_E0_DIR); E1_DIR_WRITE(!INVERT_E1_DIR); } else if (current_block->active_extruder == 0) { E0_DIR_WRITE(!INVERT_E0_DIR); } else { E1_DIR_WRITE(!INVERT_E1_DIR); } }
205
+    #define REV_E_DIR() { if (extruder_duplication_enabled) { E0_DIR_WRITE(INVERT_E0_DIR); E1_DIR_WRITE(INVERT_E1_DIR); } else if (current_block->active_extruder == 0) { E0_DIR_WRITE(INVERT_E0_DIR); } else { E1_DIR_WRITE(INVERT_E1_DIR); } }
206
+  #endif
207
+#elif ENABLED(MIXING_EXTRUDER)
208
+  #define E_STEP_WRITE(v) NOOP /* not used for mixing extruders! */
209
+  #if MIXING_STEPPERS > 3
210
+    #define En_STEP_WRITE(n,v) { switch (n) { case 0: E0_STEP_WRITE(v); break; case 1: E1_STEP_WRITE(v); break; case 2: E2_STEP_WRITE(v); break; case 3: E3_STEP_WRITE(v); } }
211
+    #define NORM_E_DIR() { E0_DIR_WRITE(!INVERT_E0_DIR); E1_DIR_WRITE(!INVERT_E1_DIR); E2_DIR_WRITE(!INVERT_E2_DIR); E3_DIR_WRITE(!INVERT_E3_DIR); }
212
+    #define REV_E_DIR()  { E0_DIR_WRITE( INVERT_E0_DIR); E1_DIR_WRITE( INVERT_E1_DIR); E2_DIR_WRITE( INVERT_E2_DIR); E3_DIR_WRITE( INVERT_E3_DIR); }
213
+  #elif MIXING_STEPPERS > 2
214
+    #define En_STEP_WRITE(n,v) { switch (n) { case 0: E0_STEP_WRITE(v); break; case 1: E1_STEP_WRITE(v); break; case 2: E2_STEP_WRITE(v); } }
215
+    #define NORM_E_DIR() { E0_DIR_WRITE(!INVERT_E0_DIR); E1_DIR_WRITE(!INVERT_E1_DIR); E2_DIR_WRITE(!INVERT_E2_DIR); }
216
+    #define REV_E_DIR()  { E0_DIR_WRITE( INVERT_E0_DIR); E1_DIR_WRITE( INVERT_E1_DIR); E2_DIR_WRITE( INVERT_E2_DIR); }
201 217
   #else
202
-    #define E_STEP_WRITE(v) {if(extruder_duplication_enabled){E0_STEP_WRITE(v);E1_STEP_WRITE(v);}else _E_STEP_WRITE(v);}
203
-    #define NORM_E_DIR() {if(extruder_duplication_enabled){E0_DIR_WRITE(!INVERT_E0_DIR);E1_DIR_WRITE(!INVERT_E1_DIR);}else _NORM_E_DIR();}
204
-    #define REV_E_DIR() {if(extruder_duplication_enabled){E0_DIR_WRITE(INVERT_E0_DIR);E1_DIR_WRITE(INVERT_E1_DIR);}else _REV_E_DIR();}
218
+    #define En_STEP_WRITE(n,v) { switch (n) { case 0: E0_STEP_WRITE(v); break; case 1: E1_STEP_WRITE(v); } }
219
+    #define NORM_E_DIR() { E0_DIR_WRITE(!INVERT_E0_DIR); E1_DIR_WRITE(!INVERT_E1_DIR); }
220
+    #define REV_E_DIR()  { E0_DIR_WRITE( INVERT_E0_DIR); E1_DIR_WRITE( INVERT_E1_DIR); }
205 221
   #endif
206 222
 #else
207 223
   #define E_STEP_WRITE(v) E0_STEP_WRITE(v)

+ 25
- 16
Marlin/ultralcd.cpp View File

@@ -1365,7 +1365,7 @@ void kill_screen(const char* lcd_msg) {
1365 1365
   #endif
1366 1366
   static void lcd_move_z() { _lcd_move_xyz(PSTR(MSG_MOVE_Z), Z_AXIS, sw_endstop_min[Z_AXIS], sw_endstop_max[Z_AXIS]); }
1367 1367
   static void lcd_move_e(
1368
-    #if EXTRUDERS > 1
1368
+    #if E_STEPPERS > 1
1369 1369
       int8_t eindex = -1
1370 1370
     #endif
1371 1371
   ) {
@@ -1375,7 +1375,7 @@ void kill_screen(const char* lcd_msg) {
1375 1375
       current_position[E_AXIS] += float((int32_t)encoderPosition) * move_menu_scale;
1376 1376
       encoderPosition = 0;
1377 1377
       manual_move_to_current(E_AXIS
1378
-        #if EXTRUDERS > 1
1378
+        #if E_STEPPERS > 1
1379 1379
           , eindex
1380 1380
         #endif
1381 1381
       );
@@ -1383,34 +1383,34 @@ void kill_screen(const char* lcd_msg) {
1383 1383
     }
1384 1384
     if (lcdDrawUpdate) {
1385 1385
       PGM_P pos_label;
1386
-      #if EXTRUDERS == 1
1386
+      #if E_STEPPERS == 1
1387 1387
         pos_label = PSTR(MSG_MOVE_E);
1388 1388
       #else
1389 1389
         switch (eindex) {
1390 1390
           default: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E1); break;
1391 1391
           case 1: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E2); break;
1392
-          #if EXTRUDERS > 2
1392
+          #if E_STEPPERS > 2
1393 1393
             case 2: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E3); break;
1394
-            #if EXTRUDERS > 3
1394
+            #if E_STEPPERS > 3
1395 1395
               case 3: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E4); break;
1396
-            #endif //EXTRUDERS > 3
1397
-          #endif //EXTRUDERS > 2
1396
+            #endif
1397
+          #endif
1398 1398
         }
1399
-      #endif //EXTRUDERS > 1
1399
+      #endif
1400 1400
       lcd_implementation_drawedit(pos_label, ftostr41sign(current_position[E_AXIS]));
1401 1401
     }
1402 1402
   }
1403 1403
 
1404
-  #if EXTRUDERS > 1
1404
+  #if E_STEPPERS > 1
1405 1405
     static void lcd_move_e0() { lcd_move_e(0); }
1406 1406
     static void lcd_move_e1() { lcd_move_e(1); }
1407
-    #if EXTRUDERS > 2
1407
+    #if E_STEPPERS > 2
1408 1408
       static void lcd_move_e2() { lcd_move_e(2); }
1409
-      #if EXTRUDERS > 3
1409
+      #if E_STEPPERS > 3
1410 1410
         static void lcd_move_e3() { lcd_move_e(3); }
1411 1411
       #endif
1412 1412
     #endif
1413
-  #endif // EXTRUDERS > 1
1413
+  #endif
1414 1414
 
1415 1415
   /**
1416 1416
    *
@@ -1432,20 +1432,29 @@ void kill_screen(const char* lcd_msg) {
1432 1432
       MENU_ITEM(submenu, MSG_MOVE_X, lcd_move_x);
1433 1433
       MENU_ITEM(submenu, MSG_MOVE_Y, lcd_move_y);
1434 1434
     }
1435
+
1435 1436
     if (move_menu_scale < 10.0) {
1436 1437
       if (_MOVE_XYZ_ALLOWED) MENU_ITEM(submenu, MSG_MOVE_Z, lcd_move_z);
1437
-      #if EXTRUDERS == 1
1438
+
1439
+      #if ENABLED(SWITCHING_EXTRUDER)
1440
+        if (active_extruder)
1441
+          MENU_ITEM(gcode, MSG_SELECT MSG_E1, PSTR("T0"));
1442
+        else
1443
+          MENU_ITEM(gcode, MSG_SELECT MSG_E2, PSTR("T1"));
1444
+      #endif
1445
+
1446
+      #if E_STEPPERS == 1
1438 1447
         MENU_ITEM(submenu, MSG_MOVE_E, lcd_move_e);
1439 1448
       #else
1440 1449
         MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E1, lcd_move_e0);
1441 1450
         MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E2, lcd_move_e1);
1442
-        #if EXTRUDERS > 2
1451
+        #if E_STEPPERS > 2
1443 1452
           MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E3, lcd_move_e2);
1444
-          #if EXTRUDERS > 3
1453
+          #if E_STEPPERS > 3
1445 1454
             MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E4, lcd_move_e3);
1446 1455
           #endif
1447 1456
         #endif
1448
-      #endif // EXTRUDERS > 1
1457
+      #endif
1449 1458
     }
1450 1459
     END_MENU();
1451 1460
   }

Loading…
Cancel
Save