diff --git a/Firmware/Marlin_main.cpp b/Firmware/Marlin_main.cpp index d8d022ff9..8bb82b565 100644 --- a/Firmware/Marlin_main.cpp +++ b/Firmware/Marlin_main.cpp @@ -3414,18 +3414,40 @@ void gcode_M123() } #endif //FANCHECK and TACH_0 or TACH_1 -static void mmu_M600_wait_and_beep() { - // Beep and wait for user to remove old filament and prepare new filament for load - KEEPALIVE_STATE(PAUSED_FOR_USER); - - lcd_display_message_fullscreen_P(_i("Remove old filament and press the knob to start loading new filament.")); ////MSG_REMOVE_OLD_FILAMENT c=20 r=4 - - while (!lcd_clicked()) { +/// @brief Re-use the MMU error screen UI to present choices for filament change +/// There are two button actions, Load and Eject +/// Load will exit the screen and continue as normally by asking the user which slot to load from +/// Eject will eject the depleted filament, very useful after FINDA runout events. +/// @param eject_slot the MMU slot to eject if the user selects the Eject button choice +static void mmu_M600_filament_change_screen(uint8_t eject_slot) { + MMU2::Buttons btn; + for(;;) + { manage_heater(); manage_inactivity(true); - sound_wait_for_user(); + + btn = MMU2::mmu2.getPrinterButtonOperation(); + if (btn != MMU2::Buttons::NoButton) + { + MMU2::mmu2.clearPrinterButtonOperation(); + + if (btn == MMU2::Buttons::Eject) { + MMU2::mmu2.eject_filament(eject_slot, true); + // The MMU will raise FILAMENT_EJECTED screen, and ask the user to confirm + // the operation is done. We must be careful to not raise FILAMENT_CHANGE + // screen too quickly + continue; + } + else if (btn == MMU2::Buttons::Load) + { + // External caller is expected to load the filament + // This event is used to exit the endless loop + return; + } + } + + MMU2::mmu2.InvokeErrorScreen(ErrorCode::FILAMENT_CHANGE); } - sound_wait_for_user_reset(); } /** @@ -3471,8 +3493,13 @@ static void gcode_M600(const bool automatic, const float x_position, const float st_synchronize(); float lastpos[4]; - prusa_statistics(22); - + // When using an MMU, save the currently use slot number + // so the firmware can know which slot to eject after the filament + // is unloaded. + uint8_t eject_slot = 0; + + prusa_statistics(22); + //First backup current position and settings int feedmultiplyBckp = feedmultiply; float HotendTempBckp = degTargetHotend(active_extruder); @@ -3499,6 +3526,7 @@ static void gcode_M600(const bool automatic, const float x_position, const float // Unload filament if (MMU2::mmu2.Enabled()) { + eject_slot = MMU2::mmu2.get_current_tool(); mmu_M600_unload_filament(); } else { // Beep, manage nozzle heater and wait for user to start unload filament @@ -3526,13 +3554,7 @@ static void gcode_M600(const bool automatic, const float x_position, const float } else // MMU is enabled { - if (!automatic) { - if (saved_printing){ - // if M600 was invoked by filament senzor (FINDA) eject filament so user can easily remove it - MMU2::mmu2.eject_filament(MMU2::mmu2.get_current_tool(), false); - } - mmu_M600_wait_and_beep(); - } + if (!automatic) mmu_M600_filament_change_screen(eject_slot); mmu_M600_load_filament(automatic, HotendTempBckp); } if (!automatic) @@ -10820,12 +10842,14 @@ void M600_check_state(float nozzle_temp) { // Filament failed to load so load it again case 2: - if (MMU2::mmu2.Enabled()){ + if (MMU2::mmu2.Enabled()) { + uint8_t eject_slot = MMU2::mmu2.get_current_tool(); + // Unload filament mmu_M600_unload_filament(); // Ask to remove any old filament and load new - mmu_M600_wait_and_beep(); + mmu_M600_filament_change_screen(eject_slot); // After user clicks knob, MMU will load the filament mmu_M600_load_filament(false, nozzle_temp); diff --git a/Firmware/mmu2.cpp b/Firmware/mmu2.cpp index 79c631b0b..b6ead0d74 100644 --- a/Firmware/mmu2.cpp +++ b/Firmware/mmu2.cpp @@ -721,24 +721,23 @@ void MMU2::CheckUserInput() { lastButton = Buttons::NoButton; // Clear it. } + if (mmu2.MMULastErrorSource() == MMU2::ErrorSourcePrinter && btn != Buttons::NoButton) + { + // When the printer has raised an error screen, and a button was selected + // the error screen should always be dismissed. + ClearPrinterError(); + // A horrible hack - clear the explicit printer error allowing manage_response to recover on MMU's Finished state + // Moreover - if the MMU is currently doing something (like the LoadFilament - see comment above) + // we'll actually wait for it automagically in manage_response and after it finishes correctly, + // we'll issue another command (like toolchange) + } + switch (btn) { case Left: case Middle: case Right: SERIAL_ECHOPGM("CheckUserInput-btnLMR "); SERIAL_ECHOLN(btn); - - // clear the explicit printer error as soon as possible so that the MMU error screens + reporting doesn't get too confused - if (lastErrorCode == ErrorCode::LOAD_TO_EXTRUDER_FAILED) { - // A horrible hack - clear the explicit printer error allowing manage_response to recover on MMU's Finished state - // Moreover - if the MMU is currently doing something (like the LoadFilament - see comment above) - // we'll actually wait for it automagically in manage_response and after it finishes correctly, - // we'll issue another command (like toolchange) - logic.ClearPrinterError(); - lastErrorCode = ErrorCode::OK; - lastErrorSource = ErrorSourceNone; // this seems to help clearing the error screen - } - ResumeHotendTemp(); // Recover the hotend temp before we attempt to do anything else... if (mmu2.MMULastErrorSource() == MMU2::ErrorSourceMMU) { @@ -761,6 +760,11 @@ void MMU2::CheckUserInput() { case TuneMMU: Tune(); break; + case Load: + case Eject: + // High level operation + setPrinterButtonOperation(btn); + break; case ResetMMU: Reset(ResetPin); // we cannot do power cycle on the MK3 // ... but mmu2_power.cpp knows this and triggers a soft-reset instead. diff --git a/Firmware/mmu2.h b/Firmware/mmu2.h index eb7a0a591..0c4df18e1 100644 --- a/Firmware/mmu2.h +++ b/Firmware/mmu2.h @@ -199,6 +199,37 @@ public: inline uint16_t GetLastReadRegisterValue() const { return lastReadRegisterValue; }; + inline void InvokeErrorScreen(ErrorCode ec) { + // The printer may not raise an error when the MMU is busy + if ( !logic.CommandInProgress() // MMU must not be busy + && MMUCurrentErrorCode() == ErrorCode::OK // The protocol must not be in error state + && lastErrorCode != ec) // The error code is not a duplicate + { + ReportError(ec, ErrorSource::ErrorSourcePrinter); + } + } + + void ClearPrinterError() { + logic.ClearPrinterError(); + lastErrorCode = ErrorCode::OK; + lastErrorSource = ErrorSource::ErrorSourceNone; + } + + /// @brief Queue a button operation which the printer can act upon + /// @param btn Button operation + inline void setPrinterButtonOperation(Buttons btn) { + printerButtonOperation = btn; + } + + /// @brief Get the printer button operation + /// @return currently set printer button operation, it can be NoButton if nothing is queued + inline Buttons getPrinterButtonOperation() { + return printerButtonOperation; + } + + inline void clearPrinterButtonOperation() { + printerButtonOperation = Buttons::NoButton; + } private: /// Perform software self-reset of the MMU (sends an X0 command) @@ -308,6 +339,7 @@ private: ErrorSource lastErrorSource = ErrorSource::ErrorSourceNone; Buttons lastButton = Buttons::NoButton; uint16_t lastReadRegisterValue = 0; + Buttons printerButtonOperation = Buttons::NoButton; StepStatus logicStepLastStatus; diff --git a/Firmware/mmu2/buttons.h b/Firmware/mmu2/buttons.h index 81bd0d6ab..4941513a0 100644 --- a/Firmware/mmu2/buttons.h +++ b/Firmware/mmu2/buttons.h @@ -13,11 +13,13 @@ enum class ButtonOperations : uint8_t { NoOperation = 0, Retry = 1, Continue = 2, - ResetMMU = 3, + ResetMMU = 3, Unload = 4, - StopPrint = 5, - DisableMMU = 6, - Tune = 7, + Load = 5, + Eject = 6, + Tune = 7, + StopPrint = 8, + DisableMMU = 9, }; /// Button codes + extended actions performed on the printer's side @@ -28,6 +30,8 @@ enum Buttons : uint8_t { // performed on the printer's side ResetMMU, + Load, + Eject, StopPrint, DisableMMU, TuneMMU, // Printer changes MMU register value diff --git a/Firmware/mmu2/error_codes.h b/Firmware/mmu2/error_codes.h index 971f36bb8..9e0b5339c 100644 --- a/Firmware/mmu2/error_codes.h +++ b/Firmware/mmu2/error_codes.h @@ -58,7 +58,8 @@ enum class ErrorCode : uint_fast16_t { MCU_UNDERVOLTAGE_VCC = 0x800d, ///< MCU VCC rail undervoltage. - LOAD_TO_EXTRUDER_FAILED = 0x802a, ///< E32811 internal error of the printer - try-load-unload sequence detected missing filament -> failed load into the nozzle + FILAMENT_CHANGE = 0x8029, ///< E32809 internal error of the printer - try-load-unload sequence detected missing filament -> failed load into the nozzle + LOAD_TO_EXTRUDER_FAILED = 0x802a, ///< E32810 internal error of the printer - try-load-unload sequence detected missing filament -> failed load into the nozzle QUEUE_FULL = 0x802b, ///< E32811 internal logic error - attempt to move with a full queue VERSION_MISMATCH = 0x802c, ///< E32812 internal error of the printer - incompatible version of the MMU FW PROTOCOL_ERROR = 0x802d, ///< E32813 internal error of the printer - communication with the MMU got garbled - protocol decoder couldn't decode the incoming messages diff --git a/Firmware/mmu2/errors_list.h b/Firmware/mmu2/errors_list.h index 4bc713591..507de36d5 100644 --- a/Firmware/mmu2/errors_list.h +++ b/Firmware/mmu2/errors_list.h @@ -77,6 +77,7 @@ typedef enum : uint16_t { ERR_SYSTEM_FW_RUNTIME_ERROR = 505, ERR_SYSTEM_UNLOAD_MANUALLY = 506, ERR_SYSTEM_FILAMENT_EJECTED = 507, + ERR_SYSTEM_FILAMENT_CHANGE = 508, ERR_OTHER_UNKNOWN_ERROR = 900 } err_num_t; @@ -129,6 +130,7 @@ static const constexpr uint16_t errorCodes[] PROGMEM = { ERR_SYSTEM_FW_RUNTIME_ERROR, ERR_SYSTEM_UNLOAD_MANUALLY, ERR_SYSTEM_FILAMENT_EJECTED, + ERR_SYSTEM_FILAMENT_CHANGE, ERR_OTHER_UNKNOWN_ERROR }; @@ -183,6 +185,7 @@ static const char MSG_TITLE_FW_UPDATE_NEEDED[] PROGMEM_I1 = ISTR("MMU FW static const char MSG_TITLE_FW_RUNTIME_ERROR[] PROGMEM_I1 = ISTR("FW RUNTIME ERROR"); ////MSG_TITLE_FW_RUNTIME_ERROR c=20 static const char MSG_TITLE_UNLOAD_MANUALLY[] PROGMEM_I1 = ISTR("UNLOAD MANUALLY"); ////MSG_TITLE_UNLOAD_MANUALLY c=20 static const char MSG_TITLE_FILAMENT_EJECTED[] PROGMEM_I1 = ISTR("FILAMENT EJECTED"); ////MSG_TITLE_FILAMENT_EJECTED c=20 +static const char MSG_TITLE_FILAMENT_CHANGE[] PROGMEM_I1 = ISTR("FILAMENT CHANGE"); ////MSG_TITLE_FILAMENT_CHANGE c=20 static const char MSG_TITLE_UNKNOWN_ERROR[] PROGMEM_I1 = ISTR("UNKNOWN ERROR"); ////MSG_TITLE_UNKNOWN_ERROR c=20 static const char * const errorTitles [] PROGMEM = { @@ -229,6 +232,7 @@ static const char * const errorTitles [] PROGMEM = { _R(MSG_TITLE_FW_RUNTIME_ERROR), _R(MSG_TITLE_UNLOAD_MANUALLY), _R(MSG_TITLE_FILAMENT_EJECTED), + _R(MSG_TITLE_FILAMENT_CHANGE), _R(MSG_TITLE_UNKNOWN_ERROR) }; @@ -278,6 +282,7 @@ static const char MSG_DESC_QUEUE_FULL[] PROGMEM_I1 = ISTR("MMU Firmware internal static const char MSG_DESC_FW_RUNTIME_ERROR[] PROGMEM_I1 = ISTR("Internal runtime error. Try resetting the MMU or updating the firmware."); ////MSG_DESC_FW_RUNTIME_ERROR c=20 r=8 static const char MSG_DESC_UNLOAD_MANUALLY[] PROGMEM_I1 = ISTR("Filament detected unexpectedly. Ensure no filament is loaded. Check the sensors and wiring."); ////MSG_DESC_UNLOAD_MANUALLY c=20 r=8 static const char MSG_DESC_FILAMENT_EJECTED[] PROGMEM_I1 = ISTR("Remove the ejected filament from the front of the MMU."); ////MSG_DESC_FILAMENT_EJECTED c=20 r=8 +static const char MSG_DESC_FILAMENT_CHANGE[] PROGMEM_I1 = ISTR("Printer is running M600 command"); ////MSG_DESC_FILAMENT_CHANGE c=20 r=8 static const char MSG_DESC_UNKNOWN_ERROR[] PROGMEM_I1 = ISTR("Unexpected error occurred."); ////MSG_DESC_UNKNOWN_ERROR c=20 r=8 // Read explanation in mmu2_protocol_logic.cpp -> supportedMmuFWVersion @@ -332,6 +337,7 @@ static const char * const errorDescs[] PROGMEM = { _R(MSG_DESC_FW_RUNTIME_ERROR), _R(MSG_DESC_UNLOAD_MANUALLY), _R(MSG_DESC_FILAMENT_EJECTED), + _R(MSG_DESC_FILAMENT_CHANGE), _R(MSG_DESC_UNKNOWN_ERROR) }; @@ -347,6 +353,8 @@ static const char MSG_BTN_RETRY[] PROGMEM_I1 = ISTR("Retry"); ////MSG_BTN_RETRY static const char MSG_BTN_CONTINUE[] PROGMEM_I1 = ISTR("Done"); ////MSG_BTN_CONTINUE c=8 static const char MSG_BTN_RESET_MMU[] PROGMEM_I1 = ISTR("ResetMMU"); ////MSG_BTN_RESET_MMU c=8 static const char MSG_BTN_UNLOAD[] PROGMEM_I1 = ISTR("Unload"); ////MSG_BTN_UNLOAD c=8 +static const char MSG_BTN_LOAD[] PROGMEM_I1 = ISTR("Load"); ////MSG_BTN_LOAD c=8 +static const char MSG_BTN_EJECT[] PROGMEM_I1 = ISTR("Eject"); ////MSG_BTN_EJECT c=8 static const char MSG_BTN_STOP[] PROGMEM_I1 = ISTR("Stop"); ////MSG_BTN_STOP c=8 static const char MSG_BTN_DISABLE_MMU[] PROGMEM_I1 = ISTR("Disable"); ////MSG_BTN_DISABLE_MMU c=8 static const char MSG_BTN_TUNE_MMU[] PROGMEM_I1 = ISTR("Tune"); ////MSG_BTN_TUNE_MMU c=8 @@ -358,6 +366,8 @@ static const char * const btnOperation[] PROGMEM = { _R(MSG_BTN_CONTINUE), _R(MSG_BTN_RESET_MMU), _R(MSG_BTN_UNLOAD), + _R(MSG_BTN_LOAD), + _R(MSG_BTN_EJECT), _R(MSG_BTN_STOP), _R(MSG_BTN_DISABLE_MMU), _R(MSG_BTN_TUNE_MMU), @@ -418,6 +428,7 @@ static const uint8_t errorButtons[] PROGMEM = { Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation),//FW_RUNTIME_ERROR Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//UNLOAD_MANUALLY Btns(ButtonOperations::Continue, ButtonOperations::NoOperation),//FILAMENT_EJECTED + Btns(ButtonOperations::Load, ButtonOperations::Eject),//FILAMENT_CHANGE Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation),//UNKOWN_ERROR }; diff --git a/Firmware/mmu2_error_converter.cpp b/Firmware/mmu2_error_converter.cpp index 44b265331..0acc3d778 100644 --- a/Firmware/mmu2_error_converter.cpp +++ b/Firmware/mmu2_error_converter.cpp @@ -53,6 +53,8 @@ uint8_t PrusaErrorCodeIndex(uint16_t ec) { return FindErrorIndex(ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED); case (uint16_t)ErrorCode::FILAMENT_EJECTED: return FindErrorIndex(ERR_SYSTEM_FILAMENT_EJECTED); + case (uint16_t)ErrorCode::FILAMENT_CHANGE: + return FindErrorIndex(ERR_SYSTEM_FILAMENT_CHANGE); case (uint16_t)ErrorCode::STALLED_PULLEY: case (uint16_t)ErrorCode::MOVE_PULLEY_FAILED: @@ -238,6 +240,16 @@ Buttons ButtonAvailable(uint16_t ec) { break; } break; + case ERR_SYSTEM_FILAMENT_CHANGE: + switch (buttonSelectedOperation) { + case ButtonOperations::Load: + return Load; + case ButtonOperations::Eject: + return Eject; + default: + break; + } + break; case ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT: case ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT: case ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT: