From 001d1fd7cb86306cf6e25c11a91d0b2e478007bd Mon Sep 17 00:00:00 2001 From: Thomas Niccolo Reyes Date: Fri, 5 May 2023 07:09:36 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8=20Improve=20MMU2=20unload=20(like?= =?UTF-8?q?=20original=20MMU2S)=20(#20147)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Scott Lahteine --- Marlin/Configuration_adv.h | 47 +++--- Marlin/src/feature/mmu/mmu2.cpp | 147 ++++++++++--------- Marlin/src/feature/mmu/mmu2.h | 12 +- Marlin/src/gcode/feature/pause/M701_M702.cpp | 2 +- Marlin/src/lcd/menu/menu_mmu2.cpp | 23 ++- buildroot/tests/rambo | 8 + 6 files changed, 125 insertions(+), 114 deletions(-) diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 34f5f38ce3..4567d5b4c1 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -4250,30 +4250,29 @@ // Add an LCD menu for MMU2 //#define MMU2_MENUS - #if EITHER(MMU2_MENUS, HAS_PRUSA_MMU2S) - // Settings for filament load / unload from the LCD menu. - // This is for Průša MK3-style extruders. Customize for your hardware. - #define MMU2_FILAMENTCHANGE_EJECT_FEED 80.0 - #define MMU2_LOAD_TO_NOZZLE_SEQUENCE \ - { 7.2, 1145 }, \ - { 14.4, 871 }, \ - { 36.0, 1393 }, \ - { 14.4, 871 }, \ - { 50.0, 198 } - #define MMU2_RAMMING_SEQUENCE \ - { 1.0, 1000 }, \ - { 1.0, 1500 }, \ - { 2.0, 2000 }, \ - { 1.5, 3000 }, \ - { 2.5, 4000 }, \ - { -15.0, 5000 }, \ - { -14.0, 1200 }, \ - { -6.0, 600 }, \ - { 10.0, 700 }, \ - { -10.0, 400 }, \ - { -50.0, 2000 } - #endif + // Settings for filament load / unload from the LCD menu. + // This is for Průša MK3-style extruders. Customize for your hardware. + #define MMU2_FILAMENTCHANGE_EJECT_FEED 80.0 + #define MMU2_LOAD_TO_NOZZLE_SEQUENCE \ + { 7.2, 1145 }, \ + { 14.4, 871 }, \ + { 36.0, 1393 }, \ + { 14.4, 871 }, \ + { 50.0, 198 } + + #define MMU2_RAMMING_SEQUENCE \ + { 1.0, 1000 }, \ + { 1.0, 1500 }, \ + { 2.0, 2000 }, \ + { 1.5, 3000 }, \ + { 2.5, 4000 }, \ + { -15.0, 5000 }, \ + { -14.0, 1200 }, \ + { -6.0, 600 }, \ + { 10.0, 700 }, \ + { -10.0, 400 }, \ + { -50.0, 2000 } /** * Using a sensor like the MMU2S @@ -4296,6 +4295,8 @@ #define MMU2_CAN_LOAD_INCREMENT_SEQUENCE \ { -MMU2_CAN_LOAD_INCREMENT, MMU2_CAN_LOAD_FEEDRATE } + // Continue unloading if sensor detects filament after the initial unload move + //#define MMU_IR_UNLOAD_MOVE #else /** diff --git a/Marlin/src/feature/mmu/mmu2.cpp b/Marlin/src/feature/mmu/mmu2.cpp index ea58c2859b..8aec1dc1db 100644 --- a/Marlin/src/feature/mmu/mmu2.cpp +++ b/Marlin/src/feature/mmu/mmu2.cpp @@ -84,7 +84,7 @@ uint8_t MMU2::cmd, MMU2::cmd_arg, MMU2::last_cmd, MMU2::extruder; int8_t MMU2::state = 0; volatile int8_t MMU2::finda = 1; volatile bool MMU2::finda_runout_valid; -int16_t MMU2::version = -1, MMU2::buildnr = -1; +uint16_t MMU2::version = 0, MMU2::buildnr = 0; millis_t MMU2::prev_request, MMU2::prev_P0_request; char MMU2::rx_buffer[MMU_RX_SIZE], MMU2::tx_buffer[MMU_TX_SIZE]; @@ -93,14 +93,11 @@ struct E_Step { feedRate_t feedRate; //!< feed rate in mm/s }; -static constexpr E_Step - ramming_sequence[] PROGMEM = { MMU2_RAMMING_SEQUENCE } - , load_to_nozzle_sequence[] PROGMEM = { MMU2_LOAD_TO_NOZZLE_SEQUENCE } - #if HAS_PRUSA_MMU2S - , can_load_sequence[] PROGMEM = { MMU2_CAN_LOAD_SEQUENCE } - , can_load_increment_sequence[] PROGMEM = { MMU2_CAN_LOAD_INCREMENT_SEQUENCE } - #endif -; +inline void unscaled_mmu2_e_move(const float &dist, const feedRate_t fr_mm_s, const bool sync=true) { + current_position.e += dist / planner.e_factor[active_extruder]; + line_to_current_position(fr_mm_s); + if (sync) planner.synchronize(); +} MMU2::MMU2() { rx_buffer[0] = '\0'; @@ -136,12 +133,12 @@ void MMU2::reset() { #endif } -uint8_t MMU2::get_current_tool() { - return extruder == MMU2_NO_TOOL ? -1 : extruder; -} +int8_t MMU2::get_current_tool() { return extruder == MMU2_NO_TOOL ? -1 : extruder; } #if EITHER(HAS_PRUSA_MMU2S, MMU_EXTRUDER_SENSOR) #define FILAMENT_PRESENT() (READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE) +#else + #define FILAMENT_PRESENT() true #endif void mmu2_attn_buzz(const bool two=false) { @@ -200,15 +197,15 @@ void MMU2::mmu_loop() { break; #if ENABLED(MMU2_MODE_12V) - case -5: - // response to M1 - if (rx_ok()) { - DEBUG_ECHOLNPGM("MMU => ok"); - DEBUG_ECHOLNPGM("MMU <= 'P0'"); - MMU2_SEND("P0"); // Read FINDA - state = -4; - } - break; + case -5: + // response to M1 + if (rx_ok()) { + DEBUG_ECHOLNPGM("MMU => ok"); + DEBUG_ECHOLNPGM("MMU <= 'P0'"); + MMU2_SEND("P0"); // Read FINDA + state = -4; + } + break; #endif case -4: @@ -458,8 +455,15 @@ static void mmu2_not_responding() { BUZZ(100, 659); } +inline void beep_bad_cmd() { BUZZ(400, 40); } + #if HAS_PRUSA_MMU2S + /** + * Load filament until the sensor at the gears is triggered + * and give up after a number of attempts set with MMU2_C0_RETRY. + * Each try has a timeout before returning a fail state. + */ bool MMU2::load_to_gears() { command(MMU_CMD_C0); manage_response(true, true); @@ -484,6 +488,11 @@ static void mmu2_not_responding() { set_runout_valid(false); if (index != extruder) { + if (ENABLED(MMU_IR_UNLOAD_MOVE) && FILAMENT_PRESENT()) { + DEBUG_ECHOLNPGM("Unloading\n"); + while (FILAMENT_PRESENT()) // Filament present? Keep unloading. + unscaled_mmu2_e_move(-0.25, MMM_TO_MMS(120)); // 0.25mm is a guessed value. Adjust to preference. + } stepper.disable_extruder(); ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1)); @@ -520,9 +529,9 @@ static void mmu2_not_responding() { #if ENABLED(MMU2_MENUS) const uint8_t index = mmu2_choose_filament(); while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - load_filament_to_nozzle(index); + load_to_nozzle(index); #else - ERR_BUZZ(); + beep_bad_cmd(); #endif } break; @@ -541,13 +550,13 @@ static void mmu2_not_responding() { active_extruder = 0; } #else - ERR_BUZZ(); + beep_bad_cmd(); #endif } break; case 'c': { while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - load_to_nozzle(); + load_to_nozzle_sequence(); } break; } @@ -608,9 +617,9 @@ static void mmu2_not_responding() { #if ENABLED(MMU2_MENUS) uint8_t index = mmu2_choose_filament(); while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - load_filament_to_nozzle(index); + load_to_nozzle(index); #else - ERR_BUZZ(); + beep_bad_cmd(); #endif } break; @@ -630,14 +639,14 @@ static void mmu2_not_responding() { extruder = index; active_extruder = 0; #else - ERR_BUZZ(); + beep_bad_cmd(); #endif } break; case 'c': { DEBUG_ECHOLNPGM("case c\n"); while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, COUNT(load_to_nozzle_sequence)); + load_to_nozzle_sequence(); } break; } @@ -723,9 +732,9 @@ static void mmu2_not_responding() { #if ENABLED(MMU2_MENUS) uint8_t index = mmu2_choose_filament(); while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - load_filament_to_nozzle(index); + load_to_nozzle(index); #else - ERR_BUZZ(); + beep_bad_cmd(); #endif } break; @@ -744,14 +753,14 @@ static void mmu2_not_responding() { extruder = index; active_extruder = 0; #else - ERR_BUZZ(); + beep_bad_cmd(); #endif } break; case 'c': { DEBUG_ECHOLNPGM("case c\n"); while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100); - execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, COUNT(load_to_nozzle_sequence)); + load_to_nozzle_sequence(); } break; } @@ -823,13 +832,12 @@ void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { } } else if (mmu_print_saved) { - SERIAL_ECHOLNPGM("MMU starts responding\n"); + SERIAL_ECHOLNPGM("\nMMU starts responding"); if (turn_off_nozzle && resume_hotend_temp) { thermalManager.setTargetHotend(resume_hotend_temp, active_extruder); LCD_MESSAGE(MSG_HEATING); ERR_BUZZ(); - while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(1000); } @@ -842,7 +850,6 @@ void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { if (move_axes && all_axes_homed()) { // Move XY to starting position, then Z do_blocking_move_to_xy(resume_position, feedRate_t(NOZZLE_PARK_XY_FEEDRATE)); - // Move Z_AXIS to saved position do_blocking_move_to_z(resume_position.z, feedRate_t(NOZZLE_PARK_Z_FEEDRATE)); } @@ -877,23 +884,24 @@ void MMU2::filament_runout() { } // Slowly spin the extruder during C0 else { - while (planner.movesplanned() < 3) { - current_position.e += 0.25; - line_to_current_position(MMM_TO_MMS(120)); - } + while (planner.movesplanned() < 3) + unscaled_mmu2_e_move(0.25, MMM_TO_MMS(120), false); } } mmu2s_triggered = present; } bool MMU2::can_load() { - execute_extruder_sequence((const E_Step *)can_load_sequence, COUNT(can_load_sequence)); + static const E_Step can_load_sequence[] PROGMEM = { MMU2_CAN_LOAD_SEQUENCE }, + can_load_increment_sequence[] PROGMEM = { MMU2_CAN_LOAD_INCREMENT_SEQUENCE }; + + execute_extruder_sequence(can_load_sequence, COUNT(can_load_sequence)); int filament_detected_count = 0; const int steps = (MMU2_CAN_LOAD_RETRACT) / (MMU2_CAN_LOAD_INCREMENT); DEBUG_ECHOLNPGM("MMU can_load:"); LOOP_L_N(i, steps) { - execute_extruder_sequence((const E_Step *)can_load_increment_sequence, COUNT(can_load_increment_sequence)); + execute_extruder_sequence(can_load_increment_sequence, COUNT(can_load_increment_sequence)); check_filament(); // Don't trust the idle function DEBUG_CHAR(mmu2s_triggered ? 'O' : 'o'); if (mmu2s_triggered) ++filament_detected_count; @@ -911,7 +919,7 @@ void MMU2::filament_runout() { #endif // Load filament into MMU2 -void MMU2::load_filament(const uint8_t index) { +void MMU2::load_to_feeder(const uint8_t index) { if (!_enabled) return; command(MMU_CMD_L0 + index); @@ -922,8 +930,7 @@ void MMU2::load_filament(const uint8_t index) { /** * Switch material and load to nozzle */ -bool MMU2::load_filament_to_nozzle(const uint8_t index) { - +bool MMU2::load_to_nozzle(const uint8_t index) { if (!_enabled) return false; if (thermalManager.tooColdToExtrude(active_extruder)) { @@ -932,6 +939,13 @@ bool MMU2::load_filament_to_nozzle(const uint8_t index) { return false; } + if (TERN0(MMU_IR_UNLOAD_MOVE, index != extruder) && FILAMENT_PRESENT()) { + DEBUG_ECHOLNPGM("Unloading\n"); + ramming_sequence(); // Unloading instructions from printer side when operating LCD + while (FILAMENT_PRESENT()) // Filament present? Keep unloading. + unscaled_mmu2_e_move(-0.25, MMM_TO_MMS(120)); // 0.25mm is a guessed value. Adjust to preference. + } + stepper.disable_extruder(); command(MMU_CMD_T0 + index); manage_response(true, true); @@ -941,23 +955,12 @@ bool MMU2::load_filament_to_nozzle(const uint8_t index) { mmu_loop(); extruder = index; active_extruder = 0; - load_to_nozzle(); + load_to_nozzle_sequence(); mmu2_attn_buzz(); } return success; } -/** - * Load filament to nozzle of multimaterial printer - * - * This function is used only after T? (user select filament) and M600 (change filament). - * It is not used after T0 .. T4 command (select filament), in such case, G-code is responsible for loading - * filament to nozzle. - */ -void MMU2::load_to_nozzle() { - execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, COUNT(load_to_nozzle_sequence)); -} - bool MMU2::eject_filament(const uint8_t index, const bool recover) { if (!_enabled) return false; @@ -970,10 +973,7 @@ bool MMU2::eject_filament(const uint8_t index, const bool recover) { LCD_MESSAGE(MSG_MMU2_EJECTING_FILAMENT); - stepper.enable_extruder(); - current_position.e -= MMU2_FILAMENTCHANGE_EJECT_FEED; - line_to_current_position(MMM_TO_MMS(2500)); - planner.synchronize(); + unscaled_mmu2_e_move(-(MMU2_FILAMENTCHANGE_EJECT_FEED), MMM_TO_MMS(2500)); command(MMU_CMD_E0 + index); manage_response(false, false); @@ -983,7 +983,7 @@ bool MMU2::eject_filament(const uint8_t index, const bool recover) { TERN_(HOST_PROMPT_SUPPORT, hostui.continue_prompt(GET_TEXT_F(MSG_MMU2_EJECT_RECOVER))); TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired(GET_TEXT_F(MSG_MMU2_EJECT_RECOVER))); TERN_(HAS_RESUME_CONTINUE, wait_for_user_response()); - mmu2_attn_buzz(true); + mmu2_attn_buzz(); command(MMU_CMD_R0); manage_response(false, false); @@ -1017,7 +1017,7 @@ bool MMU2::unload() { } // Unload sequence to optimize shape of the tip of the unloaded filament - execute_extruder_sequence((const E_Step *)ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step)); + ramming_sequence(); command(MMU_CMD_U0); manage_response(false, true); @@ -1032,23 +1032,26 @@ bool MMU2::unload() { return true; } +void MMU2::ramming_sequence() { + static const E_Step sequence[] PROGMEM = { MMU2_RAMMING_SEQUENCE }; + execute_extruder_sequence(sequence, COUNT(sequence)); +} + +void MMU2::load_to_nozzle_sequence() { + static const E_Step sequence[] PROGMEM = { MMU2_LOAD_TO_NOZZLE_SEQUENCE }; + execute_extruder_sequence(sequence, COUNT(sequence)); +} + void MMU2::execute_extruder_sequence(const E_Step * sequence, int steps) { - planner.synchronize(); - stepper.enable_extruder(); - const E_Step* step = sequence; + const E_Step *step = sequence; LOOP_L_N(i, steps) { const float es = pgm_read_float(&(step->extrude)); const feedRate_t fr_mm_m = pgm_read_float(&(step->feedRate)); - DEBUG_ECHO_MSG("E step ", es, "/", fr_mm_m); - - current_position.e += es; - line_to_current_position(MMM_TO_MMS(fr_mm_m)); - planner.synchronize(); - + unscaled_mmu2_e_move(es, MMM_TO_MMS(fr_mm_m)); step++; } diff --git a/Marlin/src/feature/mmu/mmu2.h b/Marlin/src/feature/mmu/mmu2.h index 18d6d38a35..bebbae667e 100644 --- a/Marlin/src/feature/mmu/mmu2.h +++ b/Marlin/src/feature/mmu/mmu2.h @@ -47,13 +47,12 @@ public: static void mmu_loop(); static void tool_change(const uint8_t index); static void tool_change(const char *special); - static uint8_t get_current_tool(); + static int8_t get_current_tool(); static void set_filament_type(const uint8_t index, const uint8_t type); static bool unload(); - static void load_filament(uint8_t); - static void load_all(); - static bool load_filament_to_nozzle(const uint8_t index); + static void load_to_feeder(const uint8_t index); + static bool load_to_nozzle(const uint8_t index); static bool eject_filament(const uint8_t index, const bool recover); private: @@ -71,8 +70,9 @@ private: static bool get_response(); static void manage_response(const bool move_axes, const bool turn_off_nozzle); - static void load_to_nozzle(); static void execute_extruder_sequence(const E_Step * sequence, int steps); + static void ramming_sequence(); + static void load_to_nozzle_sequence(); static void filament_runout(); @@ -96,7 +96,7 @@ private: static int8_t state; static volatile int8_t finda; static volatile bool finda_runout_valid; - static int16_t version, buildnr; + static uint16_t version, buildnr; static millis_t prev_request, prev_P0_request; static char rx_buffer[MMU_RX_SIZE], tx_buffer[MMU_TX_SIZE]; diff --git a/Marlin/src/gcode/feature/pause/M701_M702.cpp b/Marlin/src/gcode/feature/pause/M701_M702.cpp index 2afc5c36a0..6ec560f5c6 100644 --- a/Marlin/src/gcode/feature/pause/M701_M702.cpp +++ b/Marlin/src/gcode/feature/pause/M701_M702.cpp @@ -102,7 +102,7 @@ void GcodeSuite::M701() { // Load filament #if HAS_PRUSA_MMU2 - mmu2.load_filament_to_nozzle(target_extruder); + mmu2.load_to_nozzle(target_extruder); #else constexpr float purge_length = ADVANCED_PAUSE_PURGE_LENGTH, slow_load_length = FILAMENT_CHANGE_SLOW_LOAD_LENGTH; diff --git a/Marlin/src/lcd/menu/menu_mmu2.cpp b/Marlin/src/lcd/menu/menu_mmu2.cpp index 22230687e4..a6ce147024 100644 --- a/Marlin/src/lcd/menu/menu_mmu2.cpp +++ b/Marlin/src/lcd/menu/menu_mmu2.cpp @@ -33,20 +33,19 @@ // Load Filament // -inline void action_mmu2_load_filament_to_nozzle(const uint8_t tool) { - ui.reset_status(); +inline void action_mmu2_load_to_nozzle(const uint8_t tool) { ui.return_to_status(); ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(tool + 1)); - if (mmu2.load_filament_to_nozzle(tool)) ui.reset_status(); - ui.return_to_status(); + if (mmu2.load_to_nozzle(tool)) ui.reset_status(); } -void _mmu2_load_filament(uint8_t index) { +void _mmu2_load_to_feeder(const uint8_t index) { ui.return_to_status(); ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1)); - mmu2.load_filament(index); + mmu2.load_to_feeder(index); ui.reset_status(); } + void action_mmu2_load_all() { EXTRUDER_LOOP() _mmu2_load_filament(e); ui.return_to_status(); @@ -56,14 +55,14 @@ void menu_mmu2_load_filament() { START_MENU(); BACK_ITEM(MSG_MMU2_MENU); ACTION_ITEM(MSG_MMU2_ALL, action_mmu2_load_all); - EXTRUDER_LOOP() ACTION_ITEM_N(e, MSG_MMU2_FILAMENT_N, []{ _mmu2_load_filament(MenuItemBase::itemIndex); }); + EXTRUDER_LOOP() ACTION_ITEM_N(e, MSG_MMU2_FILAMENT_N, []{ _mmu2_load_to_feeder(MenuItemBase::itemIndex); }); END_MENU(); } void menu_mmu2_load_to_nozzle() { START_MENU(); BACK_ITEM(MSG_MMU2_MENU); - EXTRUDER_LOOP() ACTION_ITEM_N(e, MSG_MMU2_FILAMENT_N, []{ action_mmu2_load_filament_to_nozzle(MenuItemBase::itemIndex); }); + EXTRUDER_LOOP() ACTION_ITEM_N(e, MSG_MMU2_FILAMENT_N, []{ action_mmu2_load_to_nozzle(MenuItemBase::itemIndex); }); END_MENU(); } @@ -117,7 +116,7 @@ void menu_mmu2() { // T* Choose Filament // -uint8_t feeder_index; +int8_t feeder_index; bool wait_for_mmu_menu; inline void action_mmu2_chosen(const uint8_t index) { @@ -144,10 +143,10 @@ void menu_mmu2_pause() { #if LCD_HEIGHT > 2 STATIC_ITEM(MSG_FILAMENT_CHANGE_HEADER, SS_DEFAULT|SS_INVERT); #endif - ACTION_ITEM(MSG_MMU2_RESUME, []{ wait_for_mmu_menu = false; }); + ACTION_ITEM(MSG_MMU2_RESUME, []{ wait_for_mmu_menu = false; }); ACTION_ITEM(MSG_MMU2_UNLOAD_FILAMENT, []{ mmu2.unload(); }); - ACTION_ITEM(MSG_MMU2_LOAD_FILAMENT, []{ mmu2.load_filament(feeder_index); }); - ACTION_ITEM(MSG_MMU2_LOAD_TO_NOZZLE, []{ mmu2.load_filament_to_nozzle(feeder_index); }); + ACTION_ITEM(MSG_MMU2_LOAD_FILAMENT, []{ mmu2.load_to_feeder(feeder_index); }); + ACTION_ITEM(MSG_MMU2_LOAD_TO_NOZZLE, []{ mmu2.load_to_nozzle(feeder_index); }); END_MENU(); } diff --git a/buildroot/tests/rambo b/buildroot/tests/rambo index 475b2daf87..c0784b95a0 100755 --- a/buildroot/tests/rambo +++ b/buildroot/tests/rambo @@ -73,6 +73,14 @@ opt_set MOTHERBOARD BOARD_RAMBO EXTRUDERS 0 TEMP_SENSOR_BED 1 TEMP_SENSOR_PROBE opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER FIX_MOUNTED_PROBE Z_SAFE_HOMING exec_test $1 $2 "Rambo heated bed only" "$3" +# +# Rambo with MMU2 +# +restore_configs +opt_set MOTHERBOARD BOARD_RAMBO EXTRUDERS 5 MMU_MODEL PRUSA_MMU2 +opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE EMERGENCY_PARSER MMU2_DEBUG +exec_test $1 $2 "Rambo with PRUSA_MMU2 " "$3" + # # Build with the default configurations #