From 05da02f0a29fdd3a36c4883ef8ac591b4b9dcbf9 Mon Sep 17 00:00:00 2001
From: Scott Lahteine <sourcetree@thinkyhead.com>
Date: Tue, 28 Jun 2016 15:06:56 -0700
Subject: [PATCH] Implement MIXING_EXTRUDER and SWITCHING_EXTRUDER

---
 Marlin/Marlin.h                |  78 ++--
 Marlin/Marlin_main.cpp         | 711 +++++++++++++++++++++------------
 Marlin/configuration_store.cpp |   4 +-
 Marlin/language_en.h           |   3 +
 Marlin/pins.h                  |  21 +-
 Marlin/pins_MEGACONTROLLER.h   |   4 +-
 Marlin/pins_RUMBA.h            |   4 +-
 Marlin/planner.cpp             |   6 +
 Marlin/planner.h               |   4 +
 Marlin/stepper.cpp             | 207 +++++++---
 Marlin/stepper.h               |  35 +-
 Marlin/stepper_indirection.h   |  48 ++-
 Marlin/ultralcd.cpp            |  41 +-
 13 files changed, 788 insertions(+), 378 deletions(-)

diff --git a/Marlin/Marlin.h b/Marlin/Marlin.h
index d7c14d29a8f..bc1f567fe4b 100644
--- a/Marlin/Marlin.h
+++ b/Marlin/Marlin.h
@@ -167,37 +167,63 @@ void manage_inactivity(bool ignore_stepper_queue = false);
   #define disable_z() NOOP
 #endif
 
-#if HAS_E0_ENABLE
-  #define  enable_e0() E0_ENABLE_WRITE( E_ENABLE_ON)
-  #define disable_e0() E0_ENABLE_WRITE(!E_ENABLE_ON)
-#else
-  #define  enable_e0() NOOP
-  #define disable_e0() NOOP
-#endif
+#if ENABLED(MIXING_EXTRUDER)
 
-#if (EXTRUDERS > 1) && HAS_E1_ENABLE
-  #define  enable_e1() E1_ENABLE_WRITE( E_ENABLE_ON)
-  #define disable_e1() E1_ENABLE_WRITE(!E_ENABLE_ON)
-#else
+  /**
+   * Mixing steppers synchronize their enable (and direction) together
+   */
+  #if MIXING_STEPPERS > 3
+    #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); }
+    #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); }
+  #elif MIXING_STEPPERS > 2
+    #define  enable_e0() { E0_ENABLE_WRITE( E_ENABLE_ON); E1_ENABLE_WRITE( E_ENABLE_ON); E2_ENABLE_WRITE( E_ENABLE_ON); }
+    #define disable_e0() { E0_ENABLE_WRITE(!E_ENABLE_ON); E1_ENABLE_WRITE(!E_ENABLE_ON); E2_ENABLE_WRITE(!E_ENABLE_ON); }
+  #else
+    #define  enable_e0() { E0_ENABLE_WRITE( E_ENABLE_ON); E1_ENABLE_WRITE( E_ENABLE_ON); }
+    #define disable_e0() { E0_ENABLE_WRITE(!E_ENABLE_ON); E1_ENABLE_WRITE(!E_ENABLE_ON); }
+  #endif
   #define  enable_e1() NOOP
   #define disable_e1() NOOP
-#endif
-
-#if (EXTRUDERS > 2) && HAS_E2_ENABLE
-  #define  enable_e2() E2_ENABLE_WRITE( E_ENABLE_ON)
-  #define disable_e2() E2_ENABLE_WRITE(!E_ENABLE_ON)
-#else
   #define  enable_e2() NOOP
   #define disable_e2() NOOP
-#endif
-
-#if (EXTRUDERS > 3) && HAS_E3_ENABLE
-  #define  enable_e3() E3_ENABLE_WRITE( E_ENABLE_ON)
-  #define disable_e3() E3_ENABLE_WRITE(!E_ENABLE_ON)
-#else
   #define  enable_e3() NOOP
   #define disable_e3() NOOP
-#endif
+
+#else // !MIXING_EXTRUDER
+
+  #if HAS_E0_ENABLE
+    #define  enable_e0() E0_ENABLE_WRITE( E_ENABLE_ON)
+    #define disable_e0() E0_ENABLE_WRITE(!E_ENABLE_ON)
+  #else
+    #define  enable_e0() NOOP
+    #define disable_e0() NOOP
+  #endif
+
+  #if E_STEPPERS > 1 && HAS_E1_ENABLE
+    #define  enable_e1() E1_ENABLE_WRITE( E_ENABLE_ON)
+    #define disable_e1() E1_ENABLE_WRITE(!E_ENABLE_ON)
+  #else
+    #define  enable_e1() NOOP
+    #define disable_e1() NOOP
+  #endif
+
+  #if E_STEPPERS > 2 && HAS_E2_ENABLE
+    #define  enable_e2() E2_ENABLE_WRITE( E_ENABLE_ON)
+    #define disable_e2() E2_ENABLE_WRITE(!E_ENABLE_ON)
+  #else
+    #define  enable_e2() NOOP
+    #define disable_e2() NOOP
+  #endif
+
+  #if E_STEPPERS > 3 && HAS_E3_ENABLE
+    #define  enable_e3() E3_ENABLE_WRITE( E_ENABLE_ON)
+    #define disable_e3() E3_ENABLE_WRITE(!E_ENABLE_ON)
+  #else
+    #define  enable_e3() NOOP
+    #define disable_e3() NOOP
+  #endif
+
+#endif // !MIXING_EXTRUDER
 
 /**
  * The axis order in all axis related arrays is X, Y, Z, E
@@ -376,6 +402,10 @@ extern uint8_t active_extruder;
   void print_heaterstates();
 #endif
 
+#if ENABLED(MIXING_EXTRUDER)
+  extern float mixing_factor[MIXING_STEPPERS];
+#endif
+
 void calculate_volumetric_multipliers();
 
 // Buzzer
diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp
index 14c427be475..837550e8f17 100644
--- a/Marlin/Marlin_main.cpp
+++ b/Marlin/Marlin_main.cpp
@@ -183,6 +183,9 @@
  * M145 - Set the heatup state H<hotend> B<bed> F<fan speed> for S<material> (0=PLA, 1=ABS)
  * M149 - Set temperature units
  * 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.
+ * M163 - Set a single proportion for a mixing extruder. Requires MIXING_EXTRUDER.
+ * M164 - Save the mix as a virtual extruder. Requires MIXING_EXTRUDER and MIXING_VIRTUAL_TOOLS.
+ * M165 - Set the proportions for a mixing extruder. Use parameters ABCDHI to set the mixing factors. Requires MIXING_EXTRUDER.
  * M190 - Sxxx Wait for bed current temp to reach target temp. Waits only when heating
  *        Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling
  * 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;
 
 // Extruder offsets
 #if HOTENDS > 1
-  #ifndef HOTEND_OFFSET_X
-    #define HOTEND_OFFSET_X { 0 } // X offsets for each extruder
-  #endif
-  #ifndef HOTEND_OFFSET_Y
-    #define HOTEND_OFFSET_Y { 0 } // Y offsets for each extruder
-  #endif
   float hotend_offset[][HOTENDS] = {
     HOTEND_OFFSET_X,
     HOTEND_OFFSET_Y
-    #if ENABLED(DUAL_X_CARRIAGE)
-      , { 0 } // Z offsets for each extruder
+    #ifdef HOTEND_OFFSET_Z
+      , HOTEND_OFFSET_Z
     #endif
   };
 #endif
@@ -507,6 +504,13 @@ static uint8_t target_extruder;
   FilamentChangeMenuResponse filament_change_menu_response;
 #endif
 
+#if ENABLED(MIXING_EXTRUDER)
+  float mixing_factor[MIXING_STEPPERS];
+  #if MIXING_VIRTUAL_TOOLS > 1
+    float mixing_virtual_tool_mix[MIXING_VIRTUAL_TOOLS][MIXING_STEPPERS];
+  #endif
+#endif
+
 static bool send_ok[BUFSIZE];
 
 #if HAS_SERVOS
@@ -952,6 +956,15 @@ void setup() {
       lcd_init();
     #endif
   #endif
+
+  #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1
+    // Initialize mixing to 100% color 1
+    for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
+      mixing_factor[i] = (i == 0) ? 1 : 0;
+    for (uint8_t t = 0; t < MIXING_VIRTUAL_TOOLS; t++)
+      for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
+        mixing_virtual_tool_mix[t][i] = mixing_factor[i];
+  #endif
 }
 
 /**
@@ -2544,6 +2557,39 @@ static void homeaxis(AxisEnum axis) {
 
 #endif // FWRETRACT
 
+#if ENABLED(MIXING_EXTRUDER)
+
+  void normalize_mix() {
+    float mix_total = 0.0;
+    for (int i = 0; i < MIXING_STEPPERS; i++) {
+      float v = mixing_factor[i];
+      if (v < 0) v = mixing_factor[i] = 0;
+      mix_total += v;
+    }
+    // Scale all values if they don't add up to ~1.0
+    if (mix_total < 0.9999 || mix_total > 1.0001) {
+      SERIAL_PROTOCOLLNPGM("Warning: Mix factors must add up to 1.0. Scaling.");
+      float mix_scale = 1.0 / mix_total;
+      for (int i = 0; i < MIXING_STEPPERS; i++)
+        mixing_factor[i] *= mix_scale;
+    }
+  }
+
+  #if ENABLED(DIRECT_MIXING_IN_G1)
+    // Get mixing parameters from the GCode
+    // Factors that are left out are set to 0
+    // The total "must" be 1.0 (but it will be normalized)
+    void gcode_get_mix() {
+      const char* mixing_codes = "ABCDHI";
+      for (int i = 0; i < MIXING_STEPPERS; i++)
+        mixing_factor[i] = code_seen(mixing_codes[i]) ? code_value_float() : 0;
+
+      normalize_mix();
+    }
+  #endif
+
+#endif
+
 /**
  * ***************************************************************************
  * ***************************** G-CODE HANDLING *****************************
@@ -2572,6 +2618,11 @@ void gcode_get_destination() {
     if(!DEBUGGING(DRYRUN))
       print_job_timer.incFilamentUsed(destination[E_AXIS] - current_position[E_AXIS]);
   #endif
+
+  // Get ABCDHI mixing factors
+  #if ENABLED(MIXING_EXTRUDER) && ENABLED(DIRECT_MIXING_IN_G1)
+    gcode_get_mix();
+  #endif
 }
 
 void unknown_command_error() {
@@ -4733,6 +4784,8 @@ inline void gcode_M109() {
 
     KEEPALIVE_STATE(NOT_BUSY);
 
+    target_extruder = active_extruder; // for print_heaterstates
+
     do {
       // Target temperature might be changed during the loop
       if (theTarget != thermalManager.degTargetBed()) {
@@ -5258,7 +5311,7 @@ inline void gcode_M200() {
     if (volumetric_enabled) {
       filament_size[target_extruder] = code_value_linear_units();
       // make sure all extruders have some sane value for the filament size
-      for (int i = 0; i < EXTRUDERS; i++)
+      for (int i = 0; i < COUNT(filament_size); i++)
         if (! filament_size[i]) filament_size[i] = DEFAULT_NOMINAL_FILAMENT_DIA;
     }
   }
@@ -5496,7 +5549,7 @@ inline void gcode_M206() {
    *   T<tool>
    *   X<xoffset>
    *   Y<yoffset>
-   *   Z<zoffset> - Available with DUAL_X_CARRIAGE
+   *   Z<zoffset> - Available with DUAL_X_CARRIAGE and SWITCHING_EXTRUDER
    */
   inline void gcode_M218() {
     if (get_target_extruder_from_command(218)) return;
@@ -5504,7 +5557,7 @@ inline void gcode_M206() {
     if (code_seen('X')) hotend_offset[X_AXIS][target_extruder] = code_value_axis_units(X_AXIS);
     if (code_seen('Y')) hotend_offset[Y_AXIS][target_extruder] = code_value_axis_units(Y_AXIS);
 
-    #if ENABLED(DUAL_X_CARRIAGE)
+    #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_EXTRUDER)
       if (code_seen('Z')) hotend_offset[Z_AXIS][target_extruder] = code_value_axis_units(Z_AXIS);
     #endif
 
@@ -5515,7 +5568,7 @@ inline void gcode_M206() {
       SERIAL_ECHO(hotend_offset[X_AXIS][e]);
       SERIAL_CHAR(',');
       SERIAL_ECHO(hotend_offset[Y_AXIS][e]);
-      #if ENABLED(DUAL_X_CARRIAGE)
+      #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_EXTRUDER)
         SERIAL_CHAR(',');
         SERIAL_ECHO(hotend_offset[Z_AXIS][e]);
       #endif
@@ -6528,6 +6581,60 @@ inline void gcode_M907() {
 
 #endif // HAS_MICROSTEPS
 
+#if ENABLED(MIXING_EXTRUDER)
+
+  /**
+   * M163: Set a single mix factor for a mixing extruder
+   *       This is called "weight" by some systems.
+   *
+   *   S[index]   The channel index to set
+   *   P[float]   The mix value
+   *
+   */
+  inline void gcode_M163() {
+    int mix_index = code_seen('S') ? code_value_int() : 0;
+    float mix_value = code_seen('P') ? code_value_float() : 0.0;
+    if (mix_index < MIXING_STEPPERS) mixing_factor[mix_index] = mix_value;
+  }
+
+  #if MIXING_VIRTUAL_TOOLS > 1
+
+    /**
+     * M164: Store the current mix factors as a virtual tool.
+     *
+     *   S[index]   The virtual tool to store
+     *
+     */
+    inline void gcode_M164() {
+      int tool_index = code_seen('S') ? code_value_int() : 0;
+      if (tool_index < MIXING_VIRTUAL_TOOLS) {
+        normalize_mix();
+        for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
+          mixing_virtual_tool_mix[tool_index][i] = mixing_factor[i];
+      }
+    }
+
+  #endif
+
+  #if ENABLED(DIRECT_MIXING_IN_G1)
+    /**
+     * M165: Set multiple mix factors for a mixing extruder.
+     *       Factors that are left out will be set to 0.
+     *       All factors together must add up to 1.0.
+     *
+     *   A[factor] Mix factor for extruder stepper 1
+     *   B[factor] Mix factor for extruder stepper 2
+     *   C[factor] Mix factor for extruder stepper 3
+     *   D[factor] Mix factor for extruder stepper 4
+     *   H[factor] Mix factor for extruder stepper 5
+     *   I[factor] Mix factor for extruder stepper 6
+     *
+     */
+    inline void gcode_M165() { gcode_get_mix(); }
+  #endif
+
+#endif // MIXING_EXTRUDER
+
 /**
  * M999: Restart after being stopped
  *
@@ -6548,6 +6655,20 @@ inline void gcode_M999() {
   FlushSerialRequestResend();
 }
 
+#if ENABLED(SWITCHING_EXTRUDER)
+  inline void move_extruder_servo(uint8_t e) {
+    const int angles[2] = SWITCHING_EXTRUDER_SERVO_ANGLES;
+    MOVE_SERVO(SWITCHING_EXTRUDER_SERVO_NR, angles[e]);
+  }
+#endif
+
+inline void invalid_extruder_error(const uint8_t &e) {
+  SERIAL_ECHO_START;
+  SERIAL_CHAR('T');
+  SERIAL_PROTOCOL_F(e, DEC);
+  SERIAL_ECHOLN(MSG_INVALID_EXTRUDER);
+}
+
 /**
  * T0-T3: Switch tool, usually switching extruders
  *
@@ -6555,264 +6676,314 @@ inline void gcode_M999() {
  *   S1           Don't move the tool in XY after change
  */
 inline void gcode_T(uint8_t tmp_extruder) {
-  if (tmp_extruder >= EXTRUDERS) {
-    SERIAL_ECHO_START;
-    SERIAL_CHAR('T');
-    SERIAL_PROTOCOL_F(tmp_extruder, DEC);
-    SERIAL_ECHOLN(MSG_INVALID_EXTRUDER);
-    return;
-  }
 
-  #if ENABLED(DEBUG_LEVELING_FEATURE)
-    if (DEBUGGING(LEVELING)) {
-      SERIAL_ECHOLNPGM(">>> gcode_T");
-      DEBUG_POS("BEFORE", current_position);
+  #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1
+
+    if (tmp_extruder >= MIXING_VIRTUAL_TOOLS) {
+      invalid_extruder_error(tmp_extruder);
+      return;
     }
-  #endif
 
-  #if HOTENDS > 1
+    // T0-Tnnn: Switch virtual tool by changing the mix
+    for (uint8_t j = 0; j < MIXING_STEPPERS; j++)
+      mixing_factor[j] = mixing_virtual_tool_mix[tmp_extruder][j];
 
-    float old_feedrate = feedrate;
+  #else //!MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1
 
-    if (code_seen('F')) {
-      float next_feedrate = code_value_axis_units(X_AXIS);
-      if (next_feedrate > 0.0) old_feedrate = feedrate = next_feedrate;
-    }
-    else
-      feedrate = XY_PROBE_FEEDRATE;
+    #if ENABLED(DEBUG_LEVELING_FEATURE)
+      if (DEBUGGING(LEVELING)) {
+        SERIAL_ECHOLNPGM(">>> gcode_T");
+        DEBUG_POS("BEFORE", current_position);
+      }
+    #endif
 
-    if (tmp_extruder != active_extruder) {
-      bool no_move = code_seen('S') && code_value_bool();
-      if (!no_move && axis_unhomed_error(true, true, true)) {
-        SERIAL_ECHOLNPGM("No move on toolchange");
-        no_move = true;
+    #if HOTENDS > 1
+
+      if (tmp_extruder >= EXTRUDERS) {
+        invalid_extruder_error(tmp_extruder);
+        return;
       }
 
-      // Save current position to destination, for use later
-      set_destination_to_current();
+      float old_feedrate = feedrate;
 
-      #if ENABLED(DUAL_X_CARRIAGE)
+      if (code_seen('F')) {
+        float next_feedrate = code_value_axis_units(X_AXIS);
+        if (next_feedrate > 0.0) old_feedrate = feedrate = next_feedrate;
+      }
+      else
+        feedrate = XY_PROBE_FEEDRATE;
 
-        #if ENABLED(DEBUG_LEVELING_FEATURE)
-          if (DEBUGGING(LEVELING)) {
-            SERIAL_ECHOPGM("Dual X Carriage Mode ");
-            switch (dual_x_carriage_mode) {
-              case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break;
-              case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break;
-              case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break;
-            }
-          }
-        #endif
+      if (tmp_extruder != active_extruder) {
+        bool no_move = code_seen('S') && code_value_bool();
 
-        if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE && IsRunning()
-             && (delayed_move_time || current_position[X_AXIS] != x_home_pos(active_extruder))
-           ) {
-          #if ENABLED(DEBUG_LEVELING_FEATURE)
-            if (DEBUGGING(LEVELING)) {
-              SERIAL_ECHOPAIR("Raise to ", current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT); SERIAL_EOL;
-              SERIAL_ECHOPAIR("MoveX to ", x_home_pos(active_extruder)); SERIAL_EOL;
-              SERIAL_ECHOPAIR("Lower to ", current_position[Z_AXIS]); SERIAL_EOL;
-            }
-          #endif
-          // Park old head: 1) raise 2) move to park position 3) lower
-          planner.buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT,
-                           current_position[E_AXIS], planner.max_feedrate[Z_AXIS], active_extruder);
-          planner.buffer_line(x_home_pos(active_extruder), current_position[Y_AXIS], current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT,
-                           current_position[E_AXIS], planner.max_feedrate[X_AXIS], active_extruder);
-          planner.buffer_line(x_home_pos(active_extruder), current_position[Y_AXIS], current_position[Z_AXIS],
-                           current_position[E_AXIS], planner.max_feedrate[Z_AXIS], active_extruder);
-          stepper.synchronize();
+        if (!no_move && axis_unhomed_error(true, true, true)) {
+          SERIAL_ECHOLNPGM("No move on toolchange");
+          no_move = true;
         }
 
-        // apply Y & Z extruder offset (x offset is already used in determining home pos)
-        current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder];
-        current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder];
-        active_extruder = tmp_extruder;
+        // Save current position to destination, for use later
+        set_destination_to_current();
 
-        // This function resets the max/min values - the current position may be overwritten below.
-        set_axis_is_at_home(X_AXIS);
+        #if ENABLED(DUAL_X_CARRIAGE)
 
-        #if ENABLED(DEBUG_LEVELING_FEATURE)
-          if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position);
-        #endif
+          #if ENABLED(DEBUG_LEVELING_FEATURE)
+            if (DEBUGGING(LEVELING)) {
+              SERIAL_ECHOPGM("Dual X Carriage Mode ");
+              switch (dual_x_carriage_mode) {
+                case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break;
+                case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break;
+                case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break;
+              }
+            }
+          #endif
 
-        switch (dual_x_carriage_mode) {
-          case DXC_FULL_CONTROL_MODE:
-            current_position[X_AXIS] = inactive_extruder_x_pos;
-            inactive_extruder_x_pos = destination[X_AXIS];
-            break;
-          case DXC_DUPLICATION_MODE:
-            active_extruder_parked = (active_extruder == 0); // this triggers the second extruder to move into the duplication position
-            if (active_extruder_parked)
+          if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE && IsRunning() &&
+              (delayed_move_time || current_position[X_AXIS] != x_home_pos(active_extruder))
+          ) {
+            #if ENABLED(DEBUG_LEVELING_FEATURE)
+              if (DEBUGGING(LEVELING)) {
+                SERIAL_ECHOPAIR("Raise to ", current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT); SERIAL_EOL;
+                SERIAL_ECHOPAIR("MoveX to ", x_home_pos(active_extruder)); SERIAL_EOL;
+                SERIAL_ECHOPAIR("Lower to ", current_position[Z_AXIS]); SERIAL_EOL;
+              }
+            #endif
+            // Park old head: 1) raise 2) move to park position 3) lower
+            for (uint8_t i = 0; i < 3; i++)
+              planner.buffer_line(
+                i == 0 ? current_position[X_AXIS] : x_home_pos(active_extruder),
+                current_position[Y_AXIS],
+                current_position[Z_AXIS] + (i == 2 ? 0 : TOOLCHANGE_PARK_ZLIFT),
+                current_position[E_AXIS],
+                planner.max_feedrate[i == 1 ? X_AXIS : Z_AXIS],
+                active_extruder
+              );
+            stepper.synchronize();
+          }
+
+          // apply Y & Z extruder offset (x offset is already used in determining home pos)
+          current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder];
+          current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder];
+          active_extruder = tmp_extruder;
+
+          // This function resets the max/min values - the current position may be overwritten below.
+          set_axis_is_at_home(X_AXIS);
+
+          #if ENABLED(DEBUG_LEVELING_FEATURE)
+            if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position);
+          #endif
+
+          switch (dual_x_carriage_mode) {
+            case DXC_FULL_CONTROL_MODE:
               current_position[X_AXIS] = inactive_extruder_x_pos;
-            else
-              current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset;
-            inactive_extruder_x_pos = destination[X_AXIS];
-            extruder_duplication_enabled = false;
-            break;
-          default:
-            // record raised toolhead position for use by unpark
-            memcpy(raised_parked_position, current_position, sizeof(raised_parked_position));
-            raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT;
-            active_extruder_parked = true;
-            delayed_move_time = 0;
-            break;
-        }
-
-        #if ENABLED(DEBUG_LEVELING_FEATURE)
-          if (DEBUGGING(LEVELING)) {
-            SERIAL_ECHOPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no");
-            SERIAL_EOL;
-            DEBUG_POS("New extruder (parked)", current_position);
+              inactive_extruder_x_pos = destination[X_AXIS];
+              break;
+            case DXC_DUPLICATION_MODE:
+              active_extruder_parked = (active_extruder == 0); // this triggers the second extruder to move into the duplication position
+              if (active_extruder_parked)
+                current_position[X_AXIS] = inactive_extruder_x_pos;
+              else
+                current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset;
+              inactive_extruder_x_pos = destination[X_AXIS];
+              extruder_duplication_enabled = false;
+              break;
+            default:
+              // record raised toolhead position for use by unpark
+              memcpy(raised_parked_position, current_position, sizeof(raised_parked_position));
+              raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT;
+              active_extruder_parked = true;
+              delayed_move_time = 0;
+              break;
           }
-        #endif
-
-       // No extra case for AUTO_BED_LEVELING_FEATURE in DUAL_X_CARRIAGE. Does that mean they don't work together?
-      #else // !DUAL_X_CARRIAGE
-
-        /**
-         * Set current_position to the position of the new nozzle.
-         * Offsets are based on linear distance, so we need to get
-         * the resulting position in coordinate space.
-         *
-         * - With grid or 3-point leveling, offset XYZ by a tilted vector
-         * - With mesh leveling, update Z for the new position
-         * - Otherwise, just use the raw linear distance
-         *
-         * Software endstops are altered here too. Consider a case where:
-         *   E0 at X=0 ... E1 at X=10
-         * When we switch to E1 now X=10, but E1 can't move left.
-         * To express this we apply the change in XY to the software endstops.
-         * E1 can move farther right than E0, so the right limit is extended.
-         *
-         * Note that we don't adjust the Z software endstops. Why not?
-         * Consider a case where Z=0 (here) and switching to E1 makes Z=1
-         * because the bed is 1mm lower at the new position. As long as
-         * the first nozzle is out of the way, the carriage should be
-         * allowed to move 1mm lower. This technically "breaks" the
-         * Z software endstop. But this is technically correct (and
-         * there is no viable alternative).
-         */
-        #if ENABLED(AUTO_BED_LEVELING_FEATURE)
-          // Offset extruder, make sure to apply the bed level rotation matrix
-          vector_3 tmp_offset_vec = vector_3(hotend_offset[X_AXIS][tmp_extruder],
-                                             hotend_offset[Y_AXIS][tmp_extruder],
-                                             0),
-                   act_offset_vec = vector_3(hotend_offset[X_AXIS][active_extruder],
-                                             hotend_offset[Y_AXIS][active_extruder],
-                                             0),
-                   offset_vec = tmp_offset_vec - act_offset_vec;
 
           #if ENABLED(DEBUG_LEVELING_FEATURE)
             if (DEBUGGING(LEVELING)) {
-              tmp_offset_vec.debug("tmp_offset_vec");
-              act_offset_vec.debug("act_offset_vec");
-              offset_vec.debug("offset_vec (BEFORE)");
+              SERIAL_ECHOPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no");
+              SERIAL_EOL;
+              DEBUG_POS("New extruder (parked)", current_position);
             }
           #endif
 
-          offset_vec.apply_rotation(planner.bed_level_matrix.transpose(planner.bed_level_matrix));
+          // No extra case for AUTO_BED_LEVELING_FEATURE in DUAL_X_CARRIAGE. Does that mean they don't work together?
+        #else // !DUAL_X_CARRIAGE
+
+          #if ENABLED(SWITCHING_EXTRUDER)
+            // <0 if the new nozzle is higher, >0 if lower. A bigger raise when lower.
+            float z_diff = hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder],
+                  z_raise = 0.3 + (z_diff > 0.0 ? z_diff : 0.0);
+          
+            // Always raise by some amount
+            planner.buffer_line(
+              current_position[X_AXIS],
+              current_position[Y_AXIS],
+              current_position[Z_AXIS] + z_raise,
+              current_position[E_AXIS],
+              planner.max_feedrate[Z_AXIS],
+              active_extruder
+            );
+            stepper.synchronize();
+          
+            move_extruder_servo(active_extruder);
+            delay(500);
+          
+            // Move back down, if needed
+            if (z_raise != z_diff) {
+              planner.buffer_line(
+                current_position[X_AXIS],
+                current_position[Y_AXIS],
+                current_position[Z_AXIS] + z_diff,
+                current_position[E_AXIS],
+                planner.max_feedrate[Z_AXIS],
+                active_extruder
+              );
+              stepper.synchronize();
+            }
+          #endif
+          
+          /**
+           * Set current_position to the position of the new nozzle.
+           * Offsets are based on linear distance, so we need to get
+           * the resulting position in coordinate space.
+           *
+           * - With grid or 3-point leveling, offset XYZ by a tilted vector
+           * - With mesh leveling, update Z for the new position
+           * - Otherwise, just use the raw linear distance
+           *
+           * Software endstops are altered here too. Consider a case where:
+           *   E0 at X=0 ... E1 at X=10
+           * When we switch to E1 now X=10, but E1 can't move left.
+           * To express this we apply the change in XY to the software endstops.
+           * E1 can move farther right than E0, so the right limit is extended.
+           *
+           * Note that we don't adjust the Z software endstops. Why not?
+           * Consider a case where Z=0 (here) and switching to E1 makes Z=1
+           * because the bed is 1mm lower at the new position. As long as
+           * the first nozzle is out of the way, the carriage should be
+           * allowed to move 1mm lower. This technically "breaks" the
+           * Z software endstop. But this is technically correct (and
+           * there is no viable alternative).
+           */
+          #if ENABLED(AUTO_BED_LEVELING_FEATURE)
+            // Offset extruder, make sure to apply the bed level rotation matrix
+            vector_3 tmp_offset_vec = vector_3(hotend_offset[X_AXIS][tmp_extruder],
+                                               hotend_offset[Y_AXIS][tmp_extruder],
+                                               0),
+                     act_offset_vec = vector_3(hotend_offset[X_AXIS][active_extruder],
+                                               hotend_offset[Y_AXIS][active_extruder],
+                                               0),
+                     offset_vec = tmp_offset_vec - act_offset_vec;
+
+            #if ENABLED(DEBUG_LEVELING_FEATURE)
+              if (DEBUGGING(LEVELING)) {
+                tmp_offset_vec.debug("tmp_offset_vec");
+                act_offset_vec.debug("act_offset_vec");
+                offset_vec.debug("offset_vec (BEFORE)");
+              }
+            #endif
+
+            offset_vec.apply_rotation(planner.bed_level_matrix.transpose(planner.bed_level_matrix));
+
+            #if ENABLED(DEBUG_LEVELING_FEATURE)
+              if (DEBUGGING(LEVELING)) offset_vec.debug("offset_vec (AFTER)");
+            #endif
+
+            // Adjustments to the current position
+            float xydiff[2] = { offset_vec.x, offset_vec.y };
+            current_position[Z_AXIS] += offset_vec.z;
+
+          #else // !AUTO_BED_LEVELING_FEATURE
+  
+            float xydiff[2] = {
+              hotend_offset[X_AXIS][tmp_extruder] - hotend_offset[X_AXIS][active_extruder],
+              hotend_offset[Y_AXIS][tmp_extruder] - hotend_offset[Y_AXIS][active_extruder]
+            };
+
+            #if ENABLED(MESH_BED_LEVELING)
+
+              if (mbl.active()) {
+                #if ENABLED(DEBUG_LEVELING_FEATURE)
+                  if (DEBUGGING(LEVELING)) SERIAL_ECHOPAIR("Z before MBL: ", current_position[Z_AXIS]);
+                #endif
+                float xpos = RAW_CURRENT_POSITION(X_AXIS),
+                      ypos = RAW_CURRENT_POSITION(Y_AXIS);
+                current_position[Z_AXIS] += mbl.get_z(xpos + xydiff[X_AXIS], ypos + xydiff[Y_AXIS]) - mbl.get_z(xpos, ypos);
+                #if ENABLED(DEBUG_LEVELING_FEATURE)
+                  if (DEBUGGING(LEVELING)) {
+                    SERIAL_ECHOPAIR(" after: ", current_position[Z_AXIS]);
+                    SERIAL_EOL;
+                  }
+                #endif
+              }
+
+            #endif // MESH_BED_LEVELING
+  
+          #endif // !AUTO_BED_LEVELING_FEATURE
 
           #if ENABLED(DEBUG_LEVELING_FEATURE)
-            if (DEBUGGING(LEVELING)) offset_vec.debug("offset_vec (AFTER)");
+            if (DEBUGGING(LEVELING)) {
+              SERIAL_ECHOPAIR("Offset Tool XY by { ", xydiff[X_AXIS]);
+              SERIAL_ECHOPAIR(", ", xydiff[X_AXIS]);
+              SERIAL_ECHOLNPGM(" }");
+            }
           #endif
 
-          // Adjustments to the current position
-          float xydiff[2] = { offset_vec.x, offset_vec.y };
-          current_position[Z_AXIS] += offset_vec.z;
+          // The newly-selected extruder XY is actually at...
+          current_position[X_AXIS] += xydiff[X_AXIS];
+          current_position[Y_AXIS] += xydiff[Y_AXIS];
+          for (uint8_t i = X_AXIS; i <= Y_AXIS; i++) {
+            position_shift[i] += xydiff[i];
+            update_software_endstops((AxisEnum)i);
+          }
 
-        #else // !AUTO_BED_LEVELING_FEATURE
+          // Set the new active extruder
+          active_extruder = tmp_extruder;
 
-          float xydiff[2] = {
-            hotend_offset[X_AXIS][tmp_extruder] - hotend_offset[X_AXIS][active_extruder],
-            hotend_offset[Y_AXIS][tmp_extruder] - hotend_offset[Y_AXIS][active_extruder]
-          };
-
-          #if ENABLED(MESH_BED_LEVELING)
-
-            if (mbl.active()) {
-              #if ENABLED(DEBUG_LEVELING_FEATURE)
-                if (DEBUGGING(LEVELING)) SERIAL_ECHOPAIR("Z before MBL: ", current_position[Z_AXIS]);
-              #endif
-              float xpos = RAW_CURRENT_POSITION(X_AXIS),
-                    ypos = RAW_CURRENT_POSITION(Y_AXIS);
-              current_position[Z_AXIS] += mbl.get_z(xpos + xydiff[X_AXIS], ypos + xydiff[Y_AXIS]) - mbl.get_z(xpos, ypos);
-              #if ENABLED(DEBUG_LEVELING_FEATURE)
-                if (DEBUGGING(LEVELING)) {
-                  SERIAL_ECHOPAIR(" after: ", current_position[Z_AXIS]);
-                  SERIAL_EOL;
-                }
-              #endif
-            }
-
-          #endif // MESH_BED_LEVELING
-
-        #endif // !AUTO_BED_LEVELING_FEATURE
+        #endif // !DUAL_X_CARRIAGE
 
         #if ENABLED(DEBUG_LEVELING_FEATURE)
-          if (DEBUGGING(LEVELING)) {
-            SERIAL_ECHOPAIR("Offset Tool XY by { ", xydiff[X_AXIS]);
-            SERIAL_ECHOPAIR(", ", xydiff[X_AXIS]);
-            SERIAL_ECHOLNPGM(" }");
-          }
+          if (DEBUGGING(LEVELING)) DEBUG_POS("Sync After Toolchange", current_position);
         #endif
 
-        // The newly-selected extruder XY is actually at...
-        current_position[X_AXIS] += xydiff[X_AXIS];
-        current_position[Y_AXIS] += xydiff[Y_AXIS];
-        for (uint8_t i = X_AXIS; i <= Y_AXIS; i++) {
-          position_shift[i] += xydiff[i];
-          update_software_endstops((AxisEnum)i);
+        // Tell the planner the new "current position"
+        SYNC_PLAN_POSITION_KINEMATIC();
+
+        // Move to the "old position" (move the extruder into place)
+        if (!no_move && IsRunning()) {
+          #if ENABLED(DEBUG_LEVELING_FEATURE)
+            if (DEBUGGING(LEVELING)) DEBUG_POS("Move back", destination);
+          #endif
+          prepare_move_to_destination();
         }
 
-        // Set the new active extruder
-        active_extruder = tmp_extruder;
+      } // (tmp_extruder != active_extruder)
 
-      #endif // !DUAL_X_CARRIAGE
+      stepper.synchronize();
 
-      #if ENABLED(DEBUG_LEVELING_FEATURE)
-        if (DEBUGGING(LEVELING)) DEBUG_POS("Sync After Toolchange", current_position);
-      #endif
+      #if ENABLED(EXT_SOLENOID)
+        disable_all_solenoids();
+        enable_solenoid_on_active_extruder();
+      #endif // EXT_SOLENOID
 
-      // Tell the planner the new "current position"
-      SYNC_PLAN_POSITION_KINEMATIC();
+      feedrate = old_feedrate;
 
-      // Move to the "old position" (move the extruder into place)
-      if (!no_move && IsRunning()) {
-        #if ENABLED(DEBUG_LEVELING_FEATURE)
-          if (DEBUGGING(LEVELING)) DEBUG_POS("Move back", destination);
-        #endif
-        prepare_move_to_destination();
+    #else // HOTENDS <= 1
+
+      // Set the new active extruder
+      active_extruder = tmp_extruder;
+
+    #endif // HOTENDS <= 1
+
+    #if ENABLED(DEBUG_LEVELING_FEATURE)
+      if (DEBUGGING(LEVELING)) {
+        DEBUG_POS("AFTER", current_position);
+        SERIAL_ECHOLNPGM("<<< gcode_T");
       }
+    #endif
+  
+    SERIAL_ECHO_START;
+    SERIAL_ECHOPGM(MSG_ACTIVE_EXTRUDER);
+    SERIAL_PROTOCOLLN((int)active_extruder);
 
-    } // (tmp_extruder != active_extruder)
-
-    stepper.synchronize();
-
-    #if ENABLED(EXT_SOLENOID)
-      disable_all_solenoids();
-      enable_solenoid_on_active_extruder();
-    #endif // EXT_SOLENOID
-
-    feedrate = old_feedrate;
-
-  #else // !HOTENDS > 1
-
-    // Set the new active extruder
-    active_extruder = tmp_extruder;
-
-  #endif
-
-  #if ENABLED(DEBUG_LEVELING_FEATURE)
-    if (DEBUGGING(LEVELING)) {
-      DEBUG_POS("AFTER", current_position);
-      SERIAL_ECHOLNPGM("<<< gcode_T");
-    }
-  #endif
-
-  SERIAL_ECHO_START;
-  SERIAL_ECHOPGM(MSG_ACTIVE_EXTRUDER);
-  SERIAL_PROTOCOLLN((int)active_extruder);
+  #endif //!MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1
 }
 
 /**
@@ -7219,6 +7390,22 @@ void process_next_command() {
 
       #endif //EXPERIMENTAL_I2CBUS
 
+      #if ENABLED(MIXING_EXTRUDER)
+        case 163: // M163 S<int> P<float> set weight for a mixing extruder
+          gcode_M163();
+          break;
+        #if MIXING_VIRTUAL_TOOLS > 1
+          case 164: // M164 S<int> save current mix as a virtual extruder
+            gcode_M164();
+            break;
+        #endif
+        #if ENABLED(DIRECT_MIXING_IN_G1)
+          case 165: // M165 [ABCDHI]<float> set multiple mix weights
+            gcode_M165();
+            break;
+        #endif
+      #endif
+
       case 200: // M200 D<diameter> Set filament diameter and set E axis units to cubic. (Use S0 to revert to linear units.)
         gcode_M200();
         break;
@@ -8033,14 +8220,14 @@ void prepare_move_to_destination() {
       nextMotorCheck = ms + 2500UL; // Not a time critical function, so only check every 2.5s
       if (X_ENABLE_READ == X_ENABLE_ON || Y_ENABLE_READ == Y_ENABLE_ON || Z_ENABLE_READ == Z_ENABLE_ON || thermalManager.soft_pwm_bed > 0
           || E0_ENABLE_READ == E_ENABLE_ON // If any of the drivers are enabled...
-          #if EXTRUDERS > 1
+          #if E_STEPPERS > 1
             || E1_ENABLE_READ == E_ENABLE_ON
             #if HAS_X2_ENABLE
               || X2_ENABLE_READ == X_ENABLE_ON
             #endif
-            #if EXTRUDERS > 2
+            #if E_STEPPERS > 2
               || E2_ENABLE_READ == E_ENABLE_ON
-              #if EXTRUDERS > 3
+              #if E_STEPPERS > 3
                 || E3_ENABLE_READ == E_ENABLE_ON
               #endif
             #endif
@@ -8303,25 +8490,29 @@ void manage_inactivity(bool ignore_stepper_queue/*=false*/) {
   #endif
 
   #if ENABLED(EXTRUDER_RUNOUT_PREVENT)
-    if (ELAPSED(ms, previous_cmd_ms + (EXTRUDER_RUNOUT_SECONDS) * 1000UL))
-      if (thermalManager.degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP) {
+    if (ELAPSED(ms, previous_cmd_ms + (EXTRUDER_RUNOUT_SECONDS) * 1000UL)
+      && thermalManager.degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP) {
+      #if ENABLED(SWITCHING_EXTRUDER)
+        bool oldstatus = E0_ENABLE_READ;
+        enable_e0();
+      #else // !SWITCHING_EXTRUDER
         bool oldstatus;
         switch (active_extruder) {
           case 0:
             oldstatus = E0_ENABLE_READ;
             enable_e0();
             break;
-          #if EXTRUDERS > 1
+          #if E_STEPPERS > 1
             case 1:
               oldstatus = E1_ENABLE_READ;
               enable_e1();
               break;
-            #if EXTRUDERS > 2
+            #if E_STEPPERS > 2
               case 2:
                 oldstatus = E2_ENABLE_READ;
                 enable_e2();
                 break;
-              #if EXTRUDERS > 3
+              #if E_STEPPERS > 3
                 case 3:
                   oldstatus = E3_ENABLE_READ;
                   enable_e3();
@@ -8330,37 +8521,43 @@ void manage_inactivity(bool ignore_stepper_queue/*=false*/) {
             #endif
           #endif
         }
-        float oldepos = current_position[E_AXIS], oldedes = destination[E_AXIS];
-        planner.buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS],
-                         destination[E_AXIS] + (EXTRUDER_RUNOUT_EXTRUDE) * (EXTRUDER_RUNOUT_ESTEPS) / planner.axis_steps_per_mm[E_AXIS],
-                         (EXTRUDER_RUNOUT_SPEED) / 60. * (EXTRUDER_RUNOUT_ESTEPS) / planner.axis_steps_per_mm[E_AXIS], active_extruder);
+      #endif // !SWITCHING_EXTRUDER
+
+      float oldepos = current_position[E_AXIS], oldedes = destination[E_AXIS];
+      planner.buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS],
+                       destination[E_AXIS] + (EXTRUDER_RUNOUT_EXTRUDE) * (EXTRUDER_RUNOUT_ESTEPS) / planner.axis_steps_per_mm[E_AXIS],
+                       (EXTRUDER_RUNOUT_SPEED) / 60. * (EXTRUDER_RUNOUT_ESTEPS) / planner.axis_steps_per_mm[E_AXIS], active_extruder);
       current_position[E_AXIS] = oldepos;
       destination[E_AXIS] = oldedes;
       planner.set_e_position_mm(oldepos);
       previous_cmd_ms = ms; // refresh_cmd_timeout()
       stepper.synchronize();
-      switch (active_extruder) {
-        case 0:
-          E0_ENABLE_WRITE(oldstatus);
-          break;
-        #if EXTRUDERS > 1
-          case 1:
-            E1_ENABLE_WRITE(oldstatus);
+      #if ENABLED(SWITCHING_EXTRUDER)
+        E0_ENABLE_WRITE(oldstatus);
+      #else
+        switch (active_extruder) {
+          case 0:
+            E0_ENABLE_WRITE(oldstatus);
             break;
-          #if EXTRUDERS > 2
-            case 2:
-              E2_ENABLE_WRITE(oldstatus);
+          #if E_STEPPERS > 1
+            case 1:
+              E1_ENABLE_WRITE(oldstatus);
               break;
-            #if EXTRUDERS > 3
-              case 3:
-                E3_ENABLE_WRITE(oldstatus);
+            #if E_STEPPERS > 2
+              case 2:
+                E2_ENABLE_WRITE(oldstatus);
                 break;
+              #if E_STEPPERS > 3
+                case 3:
+                  E3_ENABLE_WRITE(oldstatus);
+                  break;
+              #endif
             #endif
           #endif
-        #endif
-      }
+        }
+      #endif // !SWITCHING_EXTRUDER
     }
-  #endif
+  #endif // EXTRUDER_RUNOUT_PREVENT
 
   #if ENABLED(DUAL_X_CARRIAGE)
     // handle delayed move timeout
@@ -8498,6 +8695,6 @@ float calculate_volumetric_multiplier(float diameter) {
 }
 
 void calculate_volumetric_multipliers() {
-  for (int i = 0; i < EXTRUDERS; i++)
+  for (int i = 0; i < COUNT(filament_size); i++)
     volumetric_multiplier[i] = calculate_volumetric_multiplier(filament_size[i]);
 }
diff --git a/Marlin/configuration_store.cpp b/Marlin/configuration_store.cpp
index 54c96b6e880..4cf9f5d3676 100644
--- a/Marlin/configuration_store.cpp
+++ b/Marlin/configuration_store.cpp
@@ -349,7 +349,7 @@ void Config_StoreSettings()  {
 
   // Save filament sizes
   for (uint8_t q = 0; q < MAX_EXTRUDERS; q++) {
-    if (q < EXTRUDERS) dummy = filament_size[q];
+    if (q < COUNT(filament_size)) dummy = filament_size[q];
     EEPROM_WRITE_VAR(i, dummy);
   }
 
@@ -531,7 +531,7 @@ void Config_RetrieveSettings() {
 
     for (uint8_t q = 0; q < MAX_EXTRUDERS; q++) {
       EEPROM_READ_VAR(i, dummy);
-      if (q < EXTRUDERS) filament_size[q] = dummy;
+      if (q < COUNT(filament_size)) filament_size[q] = dummy;
     }
 
     if (eeprom_checksum == stored_checksum) {
diff --git a/Marlin/language_en.h b/Marlin/language_en.h
index efb48f55312..eba87d3df47 100644
--- a/Marlin/language_en.h
+++ b/Marlin/language_en.h
@@ -251,6 +251,9 @@
 #ifndef MSG_PID_C
   #define MSG_PID_C                           "PID-C"
 #endif
+#ifndef MSG_SELECT
+  #define MSG_SELECT                          "Select"
+#endif
 #ifndef MSG_E1
   #define MSG_E1                              " E1"
 #endif
diff --git a/Marlin/pins.h b/Marlin/pins.h
index 8800845888a..cc18289bde1 100644
--- a/Marlin/pins.h
+++ b/Marlin/pins.h
@@ -285,6 +285,17 @@
       #define _H3_PINS HEATER_3_PIN, EXTRUDER_3_AUTO_FAN_PIN, marlinAnalogInputToDigitalPin(TEMP_3_PIN),
     #endif
   #endif
+#elif ENABLED(MIXING_EXTRUDER)
+  #undef _E1_PINS
+  #define _E1_PINS E1_STEP_PIN, E1_DIR_PIN, E1_ENABLE_PIN,
+  #if MIXING_STEPPERS > 2
+    #undef _E2_PINS
+    #define _E2_PINS E2_STEP_PIN, E2_DIR_PIN, E2_ENABLE_PIN,
+    #if MIXING_STEPPERS > 3
+      #undef _E3_PINS
+      #define _E3_PINS E3_STEP_PIN, E3_DIR_PIN, E3_ENABLE_PIN,
+    #endif
+  #endif
 #endif
 
 #define BED_PINS HEATER_BED_PIN, marlinAnalogInputToDigitalPin(TEMP_BED_PIN),
@@ -374,15 +385,15 @@
 // The X2 axis, if any, should be the next open extruder port
 #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(X_DUAL_STEPPER_DRIVERS)
   #ifndef X2_STEP_PIN
-    #define X2_STEP_PIN   _EPIN(EXTRUDERS, STEP)
-    #define X2_DIR_PIN    _EPIN(EXTRUDERS, DIR)
-    #define X2_ENABLE_PIN _EPIN(EXTRUDERS, ENABLE)
+    #define X2_STEP_PIN   _EPIN(E_STEPPERS, STEP)
+    #define X2_DIR_PIN    _EPIN(E_STEPPERS, DIR)
+    #define X2_ENABLE_PIN _EPIN(E_STEPPERS, ENABLE)
   #endif
   #undef _X2_PINS
   #define _X2_PINS X2_STEP_PIN, X2_DIR_PIN, X2_ENABLE_PIN,
-  #define Y2_E_INDEX INCREMENT(EXTRUDERS)
+  #define Y2_E_INDEX INCREMENT(E_STEPPERS)
 #else
-  #define Y2_E_INDEX EXTRUDERS
+  #define Y2_E_INDEX E_STEPPERS
 #endif
 
 // The Y2 axis, if any, should be the next open extruder port
diff --git a/Marlin/pins_MEGACONTROLLER.h b/Marlin/pins_MEGACONTROLLER.h
index 600f2f528d2..f21a805187b 100644
--- a/Marlin/pins_MEGACONTROLLER.h
+++ b/Marlin/pins_MEGACONTROLLER.h
@@ -28,8 +28,8 @@
   #error "Oops!  Make sure you have 'Arduino Mega' selected from the 'Tools -> Boards' menu."
 #endif
 
-#if EXTRUDERS > 2 || HOTENDS > 2
-  #error "Mega Controller supports up to 2 extruders. Comment this line to keep going."
+#if E_STEPPERS > 2 || HOTENDS > 2
+  #error "Mega Controller supports up to 2 hotends / E-steppers. Comment this line to keep going."
 #endif
 
 #define BOARD_NAME "Mega Controller"
diff --git a/Marlin/pins_RUMBA.h b/Marlin/pins_RUMBA.h
index a0fe01360ee..642deb2923d 100644
--- a/Marlin/pins_RUMBA.h
+++ b/Marlin/pins_RUMBA.h
@@ -28,8 +28,8 @@
   #error "Oops!  Make sure you have 'Arduino Mega' selected from the 'Tools -> Boards' menu."
 #endif
 
-#if EXTRUDERS > 3 || HOTENDS > 3
-  #error "RUMBA supports up to 3 extruders. Comment this line to keep going."
+#if E_STEPPERS > 3 || HOTENDS > 3
+  #error "RUMBA supports up to 3 hotends / E-steppers. Comment this line to keep going."
 #endif
 
 #define DEFAULT_MACHINE_NAME "Rumba"
diff --git a/Marlin/planner.cpp b/Marlin/planner.cpp
index aacc041ef08..900866e63a4 100644
--- a/Marlin/planner.cpp
+++ b/Marlin/planner.cpp
@@ -629,6 +629,12 @@ void Planner::check_axes_activity() {
   // Bail if this is a zero-length block
   if (block->step_event_count <= dropsegments) return;
 
+  // For a mixing extruder, get a magnified step_event_count for each
+  #if ENABLED(MIXING_EXTRUDER)
+    for (uint8_t i = 0; i < MIXING_STEPPERS; i++)
+      block->mix_event_count[i] = (mixing_factor[i] < 0.0001) ? 0 : block->step_event_count / mixing_factor[i];
+  #endif
+
   #if FAN_COUNT > 0
     for (uint8_t i = 0; i < FAN_COUNT; i++) block->fan_speed[i] = fanSpeeds[i];
   #endif
diff --git a/Marlin/planner.h b/Marlin/planner.h
index 71e6b81196b..d963fba4c43 100644
--- a/Marlin/planner.h
+++ b/Marlin/planner.h
@@ -58,6 +58,10 @@ typedef struct {
   long steps[NUM_AXIS];                     // Step count along each axis
   unsigned long step_event_count;           // The number of step events required to complete this block
 
+  #if ENABLED(MIXING_EXTRUDER)
+    unsigned long mix_event_count[MIXING_STEPPERS]; // Scaled step_event_count for the mixing steppers
+  #endif
+
   long accelerate_until,                    // The index of the step event on which to stop acceleration
        decelerate_after,                    // The index of the step event on which to start decelerating
        acceleration_rate;                   // The acceleration rate used for acceleration calculation
diff --git a/Marlin/stepper.cpp b/Marlin/stepper.cpp
index 57fa3b5b72e..63159a3046f 100644
--- a/Marlin/stepper.cpp
+++ b/Marlin/stepper.cpp
@@ -95,13 +95,13 @@ volatile unsigned long Stepper::step_events_completed = 0; // The number of step
   volatile unsigned char Stepper::eISR_Rate = 200; // Keep the ISR at a low rate until needed
 
   #if ENABLED(LIN_ADVANCE)
-    volatile int Stepper::e_steps[EXTRUDERS];
+    volatile int Stepper::e_steps[E_STEPPERS];
     int Stepper::extruder_advance_k = LIN_ADVANCE_K,
         Stepper::final_estep_rate,
-        Stepper::current_estep_rate[EXTRUDERS],
-        Stepper::current_adv_steps[EXTRUDERS];
+        Stepper::current_estep_rate[E_STEPPERS],
+        Stepper::current_adv_steps[E_STEPPERS];
   #else
-    long  Stepper::e_steps[EXTRUDERS],
+    long  Stepper::e_steps[E_STEPPERS],
           Stepper::final_advance = 0,
           Stepper::old_advance = 0,
           Stepper::advance_rate,
@@ -114,6 +114,10 @@ long Stepper::acceleration_time, Stepper::deceleration_time;
 volatile long Stepper::count_position[NUM_AXIS] = { 0 };
 volatile signed char Stepper::count_direction[NUM_AXIS] = { 1, 1, 1, 1 };
 
+#if ENABLED(MIXING_EXTRUDER)
+  long Stepper::counter_M[MIXING_STEPPERS];
+#endif
+
 unsigned short Stepper::acc_step_rate; // needed for deceleration start point
 uint8_t Stepper::step_loops, Stepper::step_loops_nominal;
 unsigned short Stepper::OCR1A_nominal;
@@ -179,7 +183,9 @@ volatile long Stepper::endstops_trigsteps[3];
   #define Z_APPLY_STEP(v,Q) Z_STEP_WRITE(v)
 #endif
 
-#define E_APPLY_STEP(v,Q) E_STEP_WRITE(v)
+#if DISABLED(MIXING_EXTRUDER)
+  #define E_APPLY_STEP(v,Q) E_STEP_WRITE(v)
+#endif
 
 // intRes = longIn1 * longIn2 >> 24
 // uses:
@@ -322,8 +328,15 @@ void Stepper::isr() {
     if (current_block) {
       current_block->busy = true;
       trapezoid_generator_reset();
-      counter_X = -(current_block->step_event_count >> 1);
-      counter_Y = counter_Z = counter_E = counter_X;
+
+      // Initialize Bresenham counters to 1/2 the ceiling
+      counter_X = counter_Y = counter_Z = counter_E = -(current_block->step_event_count >> 1);
+
+      #if ENABLED(MIXING_EXTRUDER)
+        MIXING_STEPPERS_LOOP(i)
+          counter_M[i] = -(current_block->mix_event_count[i] >> 1);
+      #endif
+
       step_events_completed = 0;
 
       #if ENABLED(Z_LATE_ENABLE)
@@ -335,7 +348,7 @@ void Stepper::isr() {
       #endif
 
       // #if ENABLED(ADVANCE)
-      //   e_steps[current_block->active_extruder] = 0;
+      //   e_steps[TOOL_E_INDEX] = 0;
       // #endif
     }
     else {
@@ -343,7 +356,7 @@ void Stepper::isr() {
     }
   }
 
-  if (current_block != NULL) {
+  if (current_block) {
 
     // Update endstops state, if enabled
     #if HAS_BED_PROBE
@@ -363,25 +376,67 @@ void Stepper::isr() {
         counter_E += current_block->steps[E_AXIS];
         if (counter_E > 0) {
           counter_E -= current_block->step_event_count;
-          count_position[E_AXIS] += count_direction[E_AXIS];
-          e_steps[current_block->active_extruder] += motor_direction(E_AXIS) ? -1 : 1;
+          #if DISABLED(MIXING_EXTRUDER)
+            // Don't step E here for mixing extruder
+            count_position[E_AXIS] += count_direction[E_AXIS];
+            e_steps[TOOL_E_INDEX] += motor_direction(E_AXIS) ? -1 : 1;
+          #endif
         }
 
+        #if ENABLED(MIXING_EXTRUDER)
+          // Step mixing steppers proportionally
+          long dir = motor_direction(E_AXIS) ? -1 : 1;
+          MIXING_STEPPERS_LOOP(j) {
+            counter_m[j] += current_block->steps[E_AXIS];
+            if (counter_m[j] > 0) {
+              counter_m[j] -= current_block->mix_event_count[j];
+              e_steps[j] += dir;
+            }
+          }
+        #endif
+
         if (current_block->use_advance_lead) {
-          int delta_adv_steps; //Maybe a char would be enough?
-          delta_adv_steps = (((long)extruder_advance_k * current_estep_rate[current_block->active_extruder]) >> 9) - current_adv_steps[current_block->active_extruder];
-          e_steps[current_block->active_extruder] += delta_adv_steps;
-          current_adv_steps[current_block->active_extruder] += delta_adv_steps;
+          int delta_adv_steps = (((long)extruder_advance_k * current_estep_rate[TOOL_E_INDEX]) >> 9) - current_adv_steps[TOOL_E_INDEX];
+          #if ENABLED(MIXING_EXTRUDER)
+            // Mixing extruders apply advance lead proportionally
+            MIXING_STEPPERS_LOOP(j) {
+              int steps = delta_adv_steps * current_block->step_event_count / current_block->mix_event_count[j];
+              e_steps[j] += steps;
+              current_adv_steps[j] += steps;
+            }
+          #else
+            // For most extruders, advance the single E stepper
+            e_steps[TOOL_E_INDEX] += delta_adv_steps;
+            current_adv_steps[TOOL_E_INDEX] += delta_adv_steps;
+          #endif
         }
 
       #elif ENABLED(ADVANCE)
 
+        // Always count the unified E axis
         counter_E += current_block->steps[E_AXIS];
         if (counter_E > 0) {
           counter_E -= current_block->step_event_count;
-          e_steps[current_block->active_extruder] += motor_direction(E_AXIS) ? -1 : 1;
+          #if DISABLED(MIXING_EXTRUDER)
+            // Don't step E here for mixing extruder
+            e_steps[TOOL_E_INDEX] += motor_direction(E_AXIS) ? -1 : 1;
+          #endif
         }
 
+        #if ENABLED(MIXING_EXTRUDER)
+
+          // Step mixing steppers proportionally
+          long dir = motor_direction(E_AXIS) ? -1 : 1;
+          MIXING_STEPPERS_LOOP(j) {
+            counter_m[j] += current_block->steps[E_AXIS];
+            if (counter_m[j] > 0) {
+              counter_m[j] -= current_block->mix_event_count[j];
+              e_steps[j] += dir;
+            }
+          }
+
+        #endif // MIXING_EXTRUDER
+
       #endif // ADVANCE or LIN_ADVANCE
 
       #define _COUNTER(AXIS) counter_## AXIS
@@ -395,9 +450,22 @@ void Stepper::isr() {
       STEP_ADD(X);
       STEP_ADD(Y);
       STEP_ADD(Z);
+
       #if DISABLED(ADVANCE) && DISABLED(LIN_ADVANCE)
-        STEP_ADD(E);
-      #endif
+        #if ENABLED(MIXING_EXTRUDER)
+          // Keep updating the single E axis
+          counter_E += current_block->steps[E_AXIS];
+          // Tick the counters used for this mix
+          MIXING_STEPPERS_LOOP(j) {
+            // Step mixing steppers (proportionally)
+            counter_M[j] += current_block->steps[E_AXIS];
+            // Step when the counter goes over zero
+            if (counter_M[j] > 0) En_STEP_WRITE(j, !INVERT_E_STEP_PIN);
+          }
+        #else // !MIXING_EXTRUDER
+          STEP_ADD(E);
+        #endif
+      #endif // !ADVANCE && !LIN_ADVANCE
 
       #define STEP_IF_COUNTER(AXIS) \
         if (_COUNTER(AXIS) > 0) { \
@@ -409,17 +477,32 @@ void Stepper::isr() {
       STEP_IF_COUNTER(X);
       STEP_IF_COUNTER(Y);
       STEP_IF_COUNTER(Z);
+
       #if DISABLED(ADVANCE) && DISABLED(LIN_ADVANCE)
-        STEP_IF_COUNTER(E);
-      #endif
+        #if ENABLED(MIXING_EXTRUDER)
+          // Always step the single E axis
+          if (counter_E > 0) {
+            counter_E -= current_block->step_event_count;
+            count_position[E_AXIS] += count_direction[E_AXIS];
+          }
+          MIXING_STEPPERS_LOOP(j) {
+            if (counter_M[j] > 0) {
+              counter_M[j] -= current_block->mix_event_count[j];
+              En_STEP_WRITE(j, INVERT_E_STEP_PIN);
+            }
+          }
+        #else // !MIXING_EXTRUDER
+          STEP_IF_COUNTER(E);
+        #endif
+      #endif // !ADVANCE && !LIN_ADVANCE
 
       step_events_completed++;
       if (step_events_completed >= current_block->step_event_count) break;
     }
 
-    #if ENABLED(LIN_ADVANCE)
+    #if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
       // If we have esteps to execute, fire the next ISR "now"
-      if (e_steps[current_block->active_extruder]) OCR0A = TCNT0 + 2;
+      if (e_steps[TOOL_E_INDEX]) OCR0A = TCNT0 + 2;
     #endif
 
     // Calculate new timer value
@@ -440,21 +523,41 @@ void Stepper::isr() {
       #if ENABLED(LIN_ADVANCE)
 
         if (current_block->use_advance_lead)
-          current_estep_rate[current_block->active_extruder] = ((unsigned long)acc_step_rate * current_block->e_speed_multiplier8) >> 8;
+          current_estep_rate[TOOL_E_INDEX] = ((unsigned long)acc_step_rate * current_block->e_speed_multiplier8) >> 8;
+
+        if (current_block->use_advance_lead) {
+          #if ENABLED(MIXING_EXTRUDER)
+            MIXING_STEPPERS_LOOP(j)
+              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;
+          #else
+            current_estep_rate[TOOL_E_INDEX] = ((unsigned long)acc_step_rate * current_block->e_speed_multiplier8) >> 8;
+          #endif
+        }
 
       #elif ENABLED(ADVANCE)
 
         advance += advance_rate * step_loops;
         //NOLESS(advance, current_block->advance);
 
+        long advance_whole = advance >> 8,
+             advance_factor = advance_whole - old_advance;
+
         // Do E steps + advance steps
-        e_steps[current_block->active_extruder] += ((advance >> 8) - old_advance);
-        old_advance = advance >> 8;
+        #if ENABLED(MIXING_EXTRUDER)
+          // ...for mixing steppers proportionally
+          MIXING_STEPPERS_LOOP(j)
+            e_steps[j] += advance_factor * current_block->step_event_count / current_block->mix_event_count[j];
+        #else
+          // ...for the active extruder
+          e_steps[TOOL_E_INDEX] += advance_factor;
+        #endif
+
+        old_advance = advance_whole;
 
       #endif // ADVANCE or LIN_ADVANCE
 
       #if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
-        eISR_Rate = (timer >> 2) * step_loops / abs(e_steps[current_block->active_extruder]);
+        eISR_Rate = (timer >> 2) * step_loops / abs(e_steps[TOOL_E_INDEX]);
       #endif
     }
     else if (step_events_completed > (unsigned long)current_block->decelerate_after) {
@@ -474,8 +577,14 @@ void Stepper::isr() {
 
       #if ENABLED(LIN_ADVANCE)
 
-        if (current_block->use_advance_lead)
-          current_estep_rate[current_block->active_extruder] = ((unsigned long)step_rate * current_block->e_speed_multiplier8) >> 8;
+        if (current_block->use_advance_lead) {
+          #if ENABLED(MIXING_EXTRUDER)
+            MIXING_STEPPERS_LOOP(j)
+              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;
+          #else
+            current_estep_rate[TOOL_E_INDEX] = ((unsigned long)step_rate * current_block->e_speed_multiplier8) >> 8;
+          #endif
+        }
 
       #elif ENABLED(ADVANCE)
 
@@ -483,14 +592,22 @@ void Stepper::isr() {
         NOLESS(advance, final_advance);
 
         // Do E steps + advance steps
-        uint32_t advance_whole = advance >> 8;
-        e_steps[current_block->active_extruder] += advance_whole - old_advance;
+        long advance_whole = advance >> 8,
+             advance_factor = advance_whole - old_advance;
+
+        #if ENABLED(MIXING_EXTRUDER)
+          MIXING_STEPPERS_LOOP(j)
+            e_steps[j] += advance_factor * current_block->step_event_count / current_block->mix_event_count[j];
+        #else
+          e_steps[TOOL_E_INDEX] += advance_factor;
+        #endif
+
         old_advance = advance_whole;
 
       #endif // ADVANCE or LIN_ADVANCE
 
       #if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
-        eISR_Rate = (timer >> 2) * step_loops / abs(e_steps[current_block->active_extruder]);
+        eISR_Rate = (timer >> 2) * step_loops / abs(e_steps[TOOL_E_INDEX]);
       #endif
     }
     else {
@@ -498,9 +615,9 @@ void Stepper::isr() {
       #if ENABLED(LIN_ADVANCE)
 
         if (current_block->use_advance_lead)
-          current_estep_rate[current_block->active_extruder] = final_estep_rate;
+          current_estep_rate[TOOL_E_INDEX] = final_estep_rate;
 
-        eISR_Rate = (OCR1A_nominal >> 2) * step_loops_nominal / abs(e_steps[current_block->active_extruder]);
+        eISR_Rate = (OCR1A_nominal >> 2) * step_loops_nominal / abs(e_steps[TOOL_E_INDEX]);
 
       #endif
 
@@ -537,7 +654,7 @@ void Stepper::isr() {
           E## INDEX ##_DIR_WRITE(INVERT_E## INDEX ##_DIR); \
           e_steps[INDEX]++; \
         } \
-        else if (e_steps[INDEX] > 0) { \
+        else { \
           E## INDEX ##_DIR_WRITE(!INVERT_E## INDEX ##_DIR); \
           e_steps[INDEX]--; \
         } \
@@ -547,11 +664,11 @@ void Stepper::isr() {
     // Step all E steppers that have steps
     for (uint8_t i = 0; i < step_loops; i++) {
       STEP_E_ONCE(0);
-      #if EXTRUDERS > 1
+      #if E_STEPPERS > 1
         STEP_E_ONCE(1);
-        #if EXTRUDERS > 2
+        #if E_STEPPERS > 2
           STEP_E_ONCE(2);
-          #if EXTRUDERS > 3
+          #if E_STEPPERS > 3
             STEP_E_ONCE(3);
           #endif
         #endif
@@ -730,18 +847,12 @@ void Stepper::init() {
 
   #if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
 
-    #if ENABLED(LIN_ADVANCE)
-
-      for (int i = 0; i < EXTRUDERS; i++) {
-        e_steps[i] = 0;
+    for (int i = 0; i < E_STEPPERS; i++) {
+      e_steps[i] = 0;
+      #if ENABLED(LIN_ADVANCE)
         current_adv_steps[i] = 0;
-      }
-
-    #elif ENABLED(ADVANCE)
-
-      for (uint8_t i = 0; i < EXTRUDERS; i++) e_steps[i] = 0;
-
-    #endif
+      #endif
+    }
 
     #if defined(TCCR0A) && defined(WGM01)
       CBI(TCCR0A, WGM01);
diff --git a/Marlin/stepper.h b/Marlin/stepper.h
index 6ad55216d5c..45c8753aa78 100644
--- a/Marlin/stepper.h
+++ b/Marlin/stepper.h
@@ -107,15 +107,15 @@ class Stepper {
       static unsigned char old_OCR0A;
       static volatile unsigned char eISR_Rate;
       #if ENABLED(LIN_ADVANCE)
-        static volatile int e_steps[EXTRUDERS];
+        static volatile int e_steps[E_STEPPERS];
         static int extruder_advance_k;
         static int final_estep_rate;
-        static int current_estep_rate[EXTRUDERS]; // Actual extruder speed [steps/s]
-        static int current_adv_steps[EXTRUDERS];  // The amount of current added esteps due to advance.
+        static int current_estep_rate[E_STEPPERS]; // Actual extruder speed [steps/s]
+        static int current_adv_steps[E_STEPPERS];  // The amount of current added esteps due to advance.
                                                   // i.e., the current amount of pressure applied
                                                   // to the spring (=filament).
       #else
-        static long e_steps[EXTRUDERS];
+        static long e_steps[E_STEPPERS];
         static long advance_rate, advance, final_advance;
         static long old_advance;
       #endif
@@ -147,6 +147,16 @@ class Stepper {
     //
     static volatile signed char count_direction[NUM_AXIS];
 
+    //
+    // Mixing extruder mix counters
+    //
+    #if ENABLED(MIXING_EXTRUDER)
+      static long counter_M[MIXING_STEPPERS];
+      #define MIXING_STEPPERS_LOOP(VAR) \
+        for (uint8_t VAR = 0; VAR < MIXING_STEPPERS; VAR++) \
+          if (current_block->mix_event_count[VAR])
+    #endif
+
   public:
 
     //
@@ -315,12 +325,25 @@ class Stepper {
       }
 
       #if ENABLED(ADVANCE)
+
         advance = current_block->initial_advance;
         final_advance = current_block->final_advance;
+
         // Do E steps + advance steps
-        e_steps[current_block->active_extruder] += ((advance >>8) - old_advance);
-        old_advance = advance >>8;
+        #if ENABLED(MIXING_EXTRUDER)
+          long advance_factor = (advance >> 8) - old_advance;
+          // ...for mixing steppers proportionally
+          MIXING_STEPPERS_LOOP(j)
+            e_steps[j] += advance_factor * current_block->step_event_count / current_block->mix_event_count[j];
+        #else
+          // ...for the active extruder
+          e_steps[TOOL_E_INDEX] += ((advance >> 8) - old_advance);
+        #endif
+
+        old_advance = advance >> 8;
+
       #endif
+
       deceleration_time = 0;
       // step_rate to timer interval
       OCR1A_nominal = calc_timer(current_block->nominal_rate);
diff --git a/Marlin/stepper_indirection.h b/Marlin/stepper_indirection.h
index 56c15060ec7..65fde23c168 100644
--- a/Marlin/stepper_indirection.h
+++ b/Marlin/stepper_indirection.h
@@ -182,26 +182,42 @@
 #define E3_ENABLE_WRITE(STATE) WRITE(E3_ENABLE_PIN,STATE)
 #define E3_ENABLE_READ READ(E3_ENABLE_PIN)
 
-#if EXTRUDERS > 3
-  #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);}}
-  #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);}}
-  #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);}}
+#if ENABLED(SWITCHING_EXTRUDER)
+  #define E_STEP_WRITE(v) E0_STEP_WRITE(v)
+  #define NORM_E_DIR() E0_DIR_WRITE(current_block->active_extruder ?  INVERT_E0_DIR : !INVERT_E0_DIR)
+  #define  REV_E_DIR() E0_DIR_WRITE(current_block->active_extruder ? !INVERT_E0_DIR :  INVERT_E0_DIR)
+#elif EXTRUDERS > 3
+  #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); } }
+  #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); } }
+  #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); } }
 #elif EXTRUDERS > 2
-  #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);}}
-  #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);}}
-  #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);}}
+  #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); } }
+  #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); } }
+  #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); } }
 #elif EXTRUDERS > 1
-  #define _E_STEP_WRITE(v) {if(current_block->active_extruder==1){E1_STEP_WRITE(v);}else{E0_STEP_WRITE(v);}}
-  #define _NORM_E_DIR() {if(current_block->active_extruder==1){E1_DIR_WRITE(!INVERT_E1_DIR);}else{E0_DIR_WRITE(!INVERT_E0_DIR);}}
-  #define _REV_E_DIR() {if(current_block->active_extruder==1){E1_DIR_WRITE(INVERT_E1_DIR);}else{E0_DIR_WRITE(INVERT_E0_DIR);}}
   #if DISABLED(DUAL_X_CARRIAGE)
-    #define E_STEP_WRITE(v) _E_STEP_WRITE(v)
-    #define NORM_E_DIR() _NORM_E_DIR()
-    #define REV_E_DIR() _REV_E_DIR()
+    #define E_STEP_WRITE(v) { if (current_block->active_extruder == 0) { E0_STEP_WRITE(v); } else { E1_STEP_WRITE(v); } }
+    #define NORM_E_DIR() { if (current_block->active_extruder == 0) { E0_DIR_WRITE(!INVERT_E0_DIR); } else { E1_DIR_WRITE(!INVERT_E1_DIR); } }
+    #define REV_E_DIR() { if (current_block->active_extruder == 0) { E0_DIR_WRITE(INVERT_E0_DIR); } else { E1_DIR_WRITE(INVERT_E1_DIR); } }
   #else
-    #define E_STEP_WRITE(v) {if(extruder_duplication_enabled){E0_STEP_WRITE(v);E1_STEP_WRITE(v);}else _E_STEP_WRITE(v);}
-    #define NORM_E_DIR() {if(extruder_duplication_enabled){E0_DIR_WRITE(!INVERT_E0_DIR);E1_DIR_WRITE(!INVERT_E1_DIR);}else _NORM_E_DIR();}
-    #define REV_E_DIR() {if(extruder_duplication_enabled){E0_DIR_WRITE(INVERT_E0_DIR);E1_DIR_WRITE(INVERT_E1_DIR);}else _REV_E_DIR();}
+    #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); } }
+    #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); } }
+    #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); } }
+  #endif
+#elif ENABLED(MIXING_EXTRUDER)
+  #define E_STEP_WRITE(v) NOOP /* not used for mixing extruders! */
+  #if MIXING_STEPPERS > 3
+    #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); } }
+    #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); }
+    #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); }
+  #elif MIXING_STEPPERS > 2
+    #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); } }
+    #define NORM_E_DIR() { E0_DIR_WRITE(!INVERT_E0_DIR); E1_DIR_WRITE(!INVERT_E1_DIR); E2_DIR_WRITE(!INVERT_E2_DIR); }
+    #define REV_E_DIR()  { E0_DIR_WRITE( INVERT_E0_DIR); E1_DIR_WRITE( INVERT_E1_DIR); E2_DIR_WRITE( INVERT_E2_DIR); }
+  #else
+    #define En_STEP_WRITE(n,v) { switch (n) { case 0: E0_STEP_WRITE(v); break; case 1: E1_STEP_WRITE(v); } }
+    #define NORM_E_DIR() { E0_DIR_WRITE(!INVERT_E0_DIR); E1_DIR_WRITE(!INVERT_E1_DIR); }
+    #define REV_E_DIR()  { E0_DIR_WRITE( INVERT_E0_DIR); E1_DIR_WRITE( INVERT_E1_DIR); }
   #endif
 #else
   #define E_STEP_WRITE(v) E0_STEP_WRITE(v)
diff --git a/Marlin/ultralcd.cpp b/Marlin/ultralcd.cpp
index 543724814ab..bd8d6c3936c 100755
--- a/Marlin/ultralcd.cpp
+++ b/Marlin/ultralcd.cpp
@@ -1365,7 +1365,7 @@ void kill_screen(const char* lcd_msg) {
   #endif
   static void lcd_move_z() { _lcd_move_xyz(PSTR(MSG_MOVE_Z), Z_AXIS, sw_endstop_min[Z_AXIS], sw_endstop_max[Z_AXIS]); }
   static void lcd_move_e(
-    #if EXTRUDERS > 1
+    #if E_STEPPERS > 1
       int8_t eindex = -1
     #endif
   ) {
@@ -1375,7 +1375,7 @@ void kill_screen(const char* lcd_msg) {
       current_position[E_AXIS] += float((int32_t)encoderPosition) * move_menu_scale;
       encoderPosition = 0;
       manual_move_to_current(E_AXIS
-        #if EXTRUDERS > 1
+        #if E_STEPPERS > 1
           , eindex
         #endif
       );
@@ -1383,34 +1383,34 @@ void kill_screen(const char* lcd_msg) {
     }
     if (lcdDrawUpdate) {
       PGM_P pos_label;
-      #if EXTRUDERS == 1
+      #if E_STEPPERS == 1
         pos_label = PSTR(MSG_MOVE_E);
       #else
         switch (eindex) {
           default: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E1); break;
           case 1: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E2); break;
-          #if EXTRUDERS > 2
+          #if E_STEPPERS > 2
             case 2: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E3); break;
-            #if EXTRUDERS > 3
+            #if E_STEPPERS > 3
               case 3: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E4); break;
-            #endif //EXTRUDERS > 3
-          #endif //EXTRUDERS > 2
+            #endif
+          #endif
         }
-      #endif //EXTRUDERS > 1
+      #endif
       lcd_implementation_drawedit(pos_label, ftostr41sign(current_position[E_AXIS]));
     }
   }
 
-  #if EXTRUDERS > 1
+  #if E_STEPPERS > 1
     static void lcd_move_e0() { lcd_move_e(0); }
     static void lcd_move_e1() { lcd_move_e(1); }
-    #if EXTRUDERS > 2
+    #if E_STEPPERS > 2
       static void lcd_move_e2() { lcd_move_e(2); }
-      #if EXTRUDERS > 3
+      #if E_STEPPERS > 3
         static void lcd_move_e3() { lcd_move_e(3); }
       #endif
     #endif
-  #endif // EXTRUDERS > 1
+  #endif
 
   /**
    *
@@ -1432,20 +1432,29 @@ void kill_screen(const char* lcd_msg) {
       MENU_ITEM(submenu, MSG_MOVE_X, lcd_move_x);
       MENU_ITEM(submenu, MSG_MOVE_Y, lcd_move_y);
     }
+
     if (move_menu_scale < 10.0) {
       if (_MOVE_XYZ_ALLOWED) MENU_ITEM(submenu, MSG_MOVE_Z, lcd_move_z);
-      #if EXTRUDERS == 1
+
+      #if ENABLED(SWITCHING_EXTRUDER)
+        if (active_extruder)
+          MENU_ITEM(gcode, MSG_SELECT MSG_E1, PSTR("T0"));
+        else
+          MENU_ITEM(gcode, MSG_SELECT MSG_E2, PSTR("T1"));
+      #endif
+
+      #if E_STEPPERS == 1
         MENU_ITEM(submenu, MSG_MOVE_E, lcd_move_e);
       #else
         MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E1, lcd_move_e0);
         MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E2, lcd_move_e1);
-        #if EXTRUDERS > 2
+        #if E_STEPPERS > 2
           MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E3, lcd_move_e2);
-          #if EXTRUDERS > 3
+          #if E_STEPPERS > 3
             MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E4, lcd_move_e3);
           #endif
         #endif
-      #endif // EXTRUDERS > 1
+      #endif
     }
     END_MENU();
   }