diff --git a/Firmware/mmu2.cpp b/Firmware/mmu2.cpp index 79f8518ae..9be0f2724 100644 --- a/Firmware/mmu2.cpp +++ b/Firmware/mmu2.cpp @@ -3,45 +3,43 @@ #include "mmu2_error_converter.h" #include "mmu2_fsensor.h" #include "mmu2_log.h" +#include "mmu2_marlin.h" +#include "mmu2_marlin_macros.h" #include "mmu2_power.h" #include "mmu2_progress_converter.h" #include "mmu2_reporting.h" -#include "cardreader.h" // for IS_SD_PRINTING -#include "Marlin.h" -#include "language.h" -#include "messages.h" -#include "sound.h" -#include "stepper.h" #include "strlen_cx.h" -#include "temperature.h" -#include "ultralcd.h" #include "SpoolJoin.h" +#ifdef __AVR__ // As of FW 3.12 we only support building the FW with only one extruder, all the multi-extruder infrastructure will be removed. // Saves at least 800B of code size static_assert(EXTRUDERS==1); +constexpr float MMM_TO_MMS(float MM_M){ return MM_M / 60.0f; } +#endif + namespace MMU2 { template void waitForHotendTargetTemp(uint16_t delay, F f){ - while (((degTargetHotend(active_extruder) - degHotend(active_extruder)) > 5)) { + while (((thermal_degTargetHotend() - thermal_degHotend()) > 5)) { f(); - delay_keep_alive(delay); + safe_delay_keep_alive(delay); } } void WaitForHotendTargetTempBeep(){ waitForHotendTargetTemp(3000, []{ }); - Sound_MakeSound(e_SOUND_TYPE_StandardPrompt); + MakeSound(Prompt); } MMU2 mmu2; MMU2::MMU2() : is_mmu_error_monitor_active(false) - , logic(&mmu2Serial, MMU2_TOOL_CHANGE_LOAD_LENGTH) + , logic(&mmu2Serial, MMU2_TOOL_CHANGE_LOAD_LENGTH, MMU2_LOAD_TO_NOZZLE_FEED_RATE) , extruder(MMU2_NO_TOOL) , tool_change_extruder(MMU2_NO_TOOL) , resume_position() @@ -52,24 +50,15 @@ MMU2::MMU2() , loadFilamentStarted(false) , unloadFilamentStarted(false) , loadingToNozzle(false) - , inAutoRetry(false) - , retryAttempts(MAX_RETRIES) , toolchange_counter(0) , tmcFailures(0) { } void MMU2::Start() { -#ifdef MMU_HWRESET - WRITE(MMU_RST_PIN, 1); - SET_OUTPUT(MMU_RST_PIN); // setup reset pin -#endif //MMU_HWRESET - mmu2Serial.begin(MMU_BAUD); PowerOn(); // I repurposed this to serve as our EEPROM disable toggle. - Reset(ResetForm::ResetPin); - mmu2Serial.flush(); // make sure the UART buffer is clear before starting communication extruder = MMU2_NO_TOOL; @@ -78,7 +67,7 @@ void MMU2::Start() { // start the communication logic.Start(); - ResetRetryAttempts(); + logic.ResetRetryAttempts(); } void MMU2::Stop() { @@ -94,10 +83,17 @@ void MMU2::StopKeepPowered(){ void MMU2::Reset(ResetForm level){ switch (level) { - case Software: ResetX0(); break; - case ResetPin: TriggerResetPin(); break; - case CutThePower: PowerCycle(); break; - default: break; + case Software: + ResetX0(); + break; + case ResetPin: + TriggerResetPin(); + break; + case CutThePower: + PowerCycle(); + break; + default: + break; } } @@ -115,7 +111,7 @@ void MMU2::PowerCycle(){ // NOTE: the below will toggle the EEPROM var. Should we // assert this function is never called in the MK3 FW? Do we even care? PowerOff(); - delay_keep_alive(1000); + safe_delay_keep_alive(1000); PowerOn(); } @@ -173,7 +169,7 @@ void __attribute__((noinline)) MMU2::mmu_loop_inner(bool reportErrors) { if (is_mmu_error_monitor_active) { // Call this every iteration to keep the knob rotation responsive // This includes when mmu_loop is called within manage_response - ReportErrorHook((uint16_t)lastErrorCode); + ReportErrorHook((CommandInProgress)logic.CommandInProgress(), (uint16_t)lastErrorCode, uint8_t(lastErrorSource)); } } @@ -193,7 +189,8 @@ void MMU2::CheckFINDARunout() { struct ReportingRAII { CommandInProgress cip; - inline ReportingRAII(CommandInProgress cip):cip(cip){ + explicit inline ReportingRAII(CommandInProgress cip) + : cip(cip) { BeginReport(cip, (uint16_t)ProgressCode::EngagingIdler); } inline ~ReportingRAII(){ @@ -214,54 +211,40 @@ bool MMU2::WaitForMMUReady(){ } bool MMU2::RetryIfPossible(uint16_t ec){ - if( retryAttempts ){ + if (logic.RetryAttempts()) { SetButtonResponse(ButtonOperations::Retry); // check, that Retry is actually allowed on that operation if( ButtonAvailable(ec) != NoButton ){ - inAutoRetry = true; + logic.SetInAutoRetry(true); SERIAL_ECHOLNPGM("RetryButtonPressed"); // We don't decrement until the button is acknowledged by the MMU. //--retryAttempts; // "used" one retry attempt return true; } } - inAutoRetry = false; + logic.SetInAutoRetry(false); return false; } -void MMU2::ResetRetryAttempts(){ - SERIAL_ECHOLNPGM("ResetRetryAttempts"); - retryAttempts = MAX_RETRIES; -} - -void MMU2::DecrementRetryAttempts() { - if (inAutoRetry && retryAttempts) { - SERIAL_ECHOLNPGM("DecrementRetryAttempts"); - retryAttempts--; - } -} - bool MMU2::VerifyFilamentEnteredPTFE() { - st_synchronize(); + planner_synchronize(); - if (!fsensor.getFilamentPresent()) return false; + if (WhereIsFilament() == FilamentState::NOT_PRESENT) return false; uint8_t fsensorState = 0; // MMU has finished its load, push the filament further by some defined constant length // If the filament sensor reads 0 at any moment, then report FAILURE - current_position[E_AXIS] += MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION); - plan_buffer_line_curposXYZE(MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE); - current_position[E_AXIS] -= (MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION)); - plan_buffer_line_curposXYZE(MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE); + MoveE(MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION), MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE); + MoveE( - (MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION)), MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE); - while(blocks_queued()) + while(planner_any_moves()) { // Wait for move to finish and monitor the fsensor the entire time // A single 0 reading will set the bit. - fsensorState |= !fsensor.getFilamentPresent(); - manage_heater(); - manage_inactivity(true); + fsensorState |= (WhereIsFilament() == FilamentState::NOT_PRESENT); + marlin_manage_heater(); + marlin_manage_inactivity(true); } if (fsensorState) @@ -278,7 +261,7 @@ bool MMU2::ToolChangeCommonOnce(uint8_t slot){ static_assert(MAX_RETRIES > 1); // need >1 retries to do the cut in the last attempt for(uint8_t retries = MAX_RETRIES; retries; --retries){ for(;;) { - disable_e0(); // it may seem counterintuitive to disable the E-motor, but it gets enabled in the planner whenever the E-motor is to move + Disable_E0(); // it may seem counterintuitive to disable the E-motor, but it gets enabled in the planner whenever the E-motor is to move tool_change_extruder = slot; logic.ToolChange(slot); // let the MMU pull the filament out and push a new one in if( manage_response(true, true) ) @@ -297,12 +280,12 @@ bool MMU2::ToolChangeCommonOnce(uint8_t slot){ // something else is seriously broken and stopping a print is probably our best option. } // reset current position to whatever the planner thinks it is - plan_set_e_position(current_position[E_AXIS]); + planner_set_current_position_E( planner_get_current_position_E() ); if (VerifyFilamentEnteredPTFE()){ return true; // success } else { // Prepare a retry attempt unload(); - if( retries == 2 && eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED) == EEPROM_MMU_CUTTER_ENABLED_enabled){ + if( retries == 2 && cutter_enabled()){ cut_filament(slot, false); // try cutting filament tip at the last attempt } } @@ -325,9 +308,6 @@ void MMU2::ToolChangeCommon(uint8_t slot){ extruder = slot; //filament change is finished SpoolJoin::spooljoin.setSlot(slot); - // @@TODO really report onto the serial? May be for the Octoprint? Not important now - // SERIAL_ECHO_START(); - // SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, int(extruder)); ++toolchange_counter; } @@ -338,8 +318,7 @@ bool MMU2::tool_change(uint8_t slot) { if (slot != extruder) { if (/*FindaDetectsFilament()*/ /*!IS_SD_PRINTING && !usb_timer.running()*/ - ! printer_active() - ) { + !marlin_printingIsActive()) { // If Tcodes are used manually through the serial // we need to unload manually as well -- but only if FINDA detects filament unload(); @@ -347,7 +326,7 @@ bool MMU2::tool_change(uint8_t slot) { ReportingRAII rep(CommandInProgress::ToolChange); FSensorBlockRunout blockRunout; - st_synchronize(); + planner_synchronize(); ToolChangeCommon(slot); } return true; @@ -371,10 +350,10 @@ bool MMU2::tool_change(char code, uint8_t slot) { } break; case 'x': { - set_extrude_min_temp(0); // Allow cold extrusion since Tx only loads to the gears not nozzle - st_synchronize(); + thermal_setExtrudeMintemp(0); // Allow cold extrusion since Tx only loads to the gears not nozzle + planner_synchronize(); ToolChangeCommon(slot); // the only difference was manage_response(false, false), but probably good enough - set_extrude_min_temp(EXTRUDE_MINTEMP); + thermal_setExtrudeMintemp(EXTRUDE_MINTEMP); } break; case 'c': { @@ -398,13 +377,13 @@ uint8_t MMU2::get_tool_change_tool() const { return tool_change_extruder == MMU2_NO_TOOL ? (uint8_t)FILAMENT_UNKNOWN : tool_change_extruder; } -bool MMU2::set_filament_type(uint8_t slot, uint8_t type) { +bool MMU2::set_filament_type(uint8_t /*slot*/, uint8_t /*type*/) { if( ! WaitForMMUReady()) return false; // @@TODO - this is not supported in the new MMU yet - slot = slot; // @@TODO - type = type; // @@TODO +// slot = slot; // @@TODO +// type = type; // @@TODO // cmd_arg = filamentType; // command(MMU_CMD_F0 + index); @@ -430,14 +409,13 @@ bool MMU2::unload() { // we assume the printer managed to relieve filament tip from the gears, // so repeating that part in case of an MMU restart is not necessary for(;;) { - disable_e0(); + Disable_E0(); logic.UnloadFilament(); if( manage_response(false, true) ) break; IncrementMMUFails(); } - - Sound_MakeSound(e_SOUND_TYPE_StandardConfirm); + MakeSound(Confirm); // no active tool extruder = MMU2_NO_TOOL; @@ -446,20 +424,12 @@ bool MMU2::unload() { return true; } -void FullScreenMsg(const char *pgmS, uint8_t slot){ - lcd_update_enable(false); - lcd_clear(); - lcd_puts_at_P(0, 1, pgmS); - lcd_print(' '); - lcd_print(slot + 1); -} - bool MMU2::cut_filament(uint8_t slot, bool enableFullScreenMsg /* = true */){ if( ! WaitForMMUReady()) return false; if( enableFullScreenMsg ){ - FullScreenMsg(_T(MSG_CUT_FILAMENT), slot); + FullScreenMsgCut(slot); } { if( FindaDetectsFilament() ){ @@ -468,7 +438,7 @@ bool MMU2::cut_filament(uint8_t slot, bool enableFullScreenMsg /* = true */){ ReportingRAII rep(CommandInProgress::CutFilament); for(;;){ - disable_e0(); + Disable_E0(); logic.CutFilament(slot); if( manage_response(false, true) ) break; @@ -477,16 +447,16 @@ bool MMU2::cut_filament(uint8_t slot, bool enableFullScreenMsg /* = true */){ } extruder = MMU2_NO_TOOL; tool_change_extruder = MMU2_NO_TOOL; - Sound_MakeSound(e_SOUND_TYPE_StandardConfirm); + MakeSound(SoundType::Confirm); return true; } bool MMU2::loading_test(uint8_t slot){ - FullScreenMsg(_T(MSG_TESTING_FILAMENT), slot); + FullScreenMsgTest(slot); tool_change(slot); - st_synchronize(); + planner_synchronize(); unload(); - lcd_update_enable(true); + ScreenUpdateEnable(); return true; } @@ -494,20 +464,20 @@ bool MMU2::load_filament(uint8_t slot) { if( ! WaitForMMUReady()) return false; - FullScreenMsg(_T(MSG_LOADING_FILAMENT), slot); + FullScreenMsgLoad(slot); ReportingRAII rep(CommandInProgress::LoadFilament); for(;;) { - disable_e0(); + Disable_E0(); logic.LoadFilament(slot); if( manage_response(false, false) ) break; IncrementMMUFails(); } - Sound_MakeSound(e_SOUND_TYPE_StandardConfirm); + MakeSound(SoundType::Confirm); - lcd_update_enable(true); + ScreenUpdateEnable(); return true; } @@ -530,7 +500,7 @@ bool MMU2::load_filament_to_nozzle(uint8_t slot) { WaitForHotendTargetTempBeep(); - FullScreenMsg(_T(MSG_LOADING_FILAMENT), slot); + FullScreenMsgLoad(slot); { // used for MMU-menu operation "Load to Nozzle" ReportingRAII rep(CommandInProgress::ToolChange); @@ -544,9 +514,9 @@ bool MMU2::load_filament_to_nozzle(uint8_t slot) { // Finish loading to the nozzle with finely tuned steps. execute_load_to_nozzle_sequence(); - Sound_MakeSound(e_SOUND_TYPE_StandardConfirm); + MakeSound(Confirm); } - lcd_update_enable(true); + ScreenUpdateEnable(); return true; } @@ -555,7 +525,7 @@ bool MMU2::eject_filament(uint8_t slot, bool enableFullScreenMsg /* = true */) { return false; if( enableFullScreenMsg ){ - FullScreenMsg(_T(MSG_EJECT_FILAMENT), slot); + FullScreenMsgEject(slot); } { if( FindaDetectsFilament() ){ @@ -564,7 +534,7 @@ bool MMU2::eject_filament(uint8_t slot, bool enableFullScreenMsg /* = true */) { ReportingRAII rep(CommandInProgress::EjectFilament); for(;;) { - disable_e0(); + Disable_E0(); logic.EjectFilament(slot); if( manage_response(false, true) ) break; @@ -573,7 +543,7 @@ bool MMU2::eject_filament(uint8_t slot, bool enableFullScreenMsg /* = true */) { } extruder = MMU2_NO_TOOL; tool_change_extruder = MMU2_NO_TOOL; - Sound_MakeSound(e_SOUND_TYPE_StandardConfirm); + MakeSound(Confirm); return true; } @@ -590,8 +560,8 @@ void MMU2::SaveHotendTemp(bool turn_off_nozzle) { if (mmu_print_saved & SavedState::Cooldown) return; if (turn_off_nozzle && !(mmu_print_saved & SavedState::CooldownPending)){ - disable_e0(); - resume_hotend_temp = degTargetHotend(active_extruder); + Disable_E0(); + resume_hotend_temp = thermal_degTargetHotend(); mmu_print_saved |= SavedState::CooldownPending; LogEchoEvent_P(PSTR("Heater cooldown pending")); } @@ -600,58 +570,50 @@ void MMU2::SaveHotendTemp(bool turn_off_nozzle) { void MMU2::SaveAndPark(bool move_axes) { if (mmu_print_saved == SavedState::None) { // First occurrence. Save current position, park print head, disable nozzle heater. LogEchoEvent_P(PSTR("Saving and parking")); - disable_e0(); - st_synchronize(); + Disable_E0(); + planner_synchronize(); if (move_axes){ mmu_print_saved |= SavedState::ParkExtruder; - // save current pos - for(uint8_t i = 0; i < 3; ++i){ - resume_position.xyz[i] = current_position[i]; - } + resume_position = planner_current_position(); // save current pos // lift Z raise_z(MMU_ERR_Z_PAUSE_LIFT); // move XY aside - if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]) - { - current_position[X_AXIS] = MMU_ERR_X_PAUSE_POS; - current_position[Y_AXIS] = MMU_ERR_Y_PAUSE_POS; - plan_buffer_line_curposXYZE(NOZZLE_PARK_XY_FEEDRATE); - st_synchronize(); + if (all_axes_homed()) { + nozzle_park(); } } } // keep the motors powered forever (until some other strategy is chosen) // @@TODO do we need that in 8bit? - // gcode.reset_stepper_timeout(); + gcode_reset_stepper_timeout(); } void MMU2::ResumeHotendTemp() { - if ((mmu_print_saved & SavedState::CooldownPending)) - { + if ((mmu_print_saved & SavedState::CooldownPending)) { // Clear the "pending" flag if we haven't cooled yet. mmu_print_saved &= ~(SavedState::CooldownPending); LogEchoEvent_P(PSTR("Cooldown flag cleared")); } if ((mmu_print_saved & SavedState::Cooldown) && resume_hotend_temp) { LogEchoEvent_P(PSTR("Resuming Temp")); - MMU2_ECHO_MSGRPGM(PSTR("Restoring hotend temperature ")); +// @@TODO MMU2_ECHO_MSGRPGM(PSTR("Restoring hotend temperature ")); SERIAL_ECHOLN(resume_hotend_temp); mmu_print_saved &= ~(SavedState::Cooldown); - setTargetHotend(resume_hotend_temp, active_extruder); - lcd_display_message_fullscreen_P(_i("MMU Retry: Restoring temperature...")); ////MSG_MMU_RESTORE_TEMP c=20 r=4 + thermal_setTargetHotend(resume_hotend_temp); + FullScreenMsgRestoringTemperature(); //@todo better report the event and let the GUI do its work somewhere else ReportErrorHookSensorLineRender(); waitForHotendTargetTemp(100, []{ - manage_inactivity(true); + marlin_manage_inactivity(true); mmu2.mmu_loop_inner(false); ReportErrorHookDynamicRender(); }); - lcd_update_enable(true); // temporary hack to stop this locking the printer... + ScreenUpdateEnable(); // temporary hack to stop this locking the printer... LogEchoEvent_P(PSTR("Hotend temperature reached")); - lcd_clear(); + ScreenClear(); } } @@ -659,14 +621,12 @@ void MMU2::ResumeUnpark(){ if (mmu_print_saved & SavedState::ParkExtruder) { LogEchoEvent_P(PSTR("Resuming XYZ")); - current_position[X_AXIS] = resume_position.xyz[X_AXIS]; - current_position[Y_AXIS] = resume_position.xyz[Y_AXIS]; - plan_buffer_line_curposXYZE(NOZZLE_PARK_XY_FEEDRATE); - st_synchronize(); + // Move XY to starting position, then Z + motion_do_blocking_move_to_xy(resume_position.xyz[0], resume_position.xyz[1], feedRate_t(NOZZLE_PARK_XY_FEEDRATE)); - current_position[Z_AXIS] = resume_position.xyz[Z_AXIS]; - plan_buffer_line_curposXYZE(NOZZLE_PARK_Z_FEEDRATE); - st_synchronize(); + // Move Z_AXIS to saved position + motion_do_blocking_move_to_z(resume_position.xyz[2], feedRate_t(NOZZLE_PARK_Z_FEEDRATE)); + mmu_print_saved &= ~(SavedState::ParkExtruder); } } @@ -742,7 +702,7 @@ void MMU2::CheckUserInput(){ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { mmu_print_saved = SavedState::None; - KEEPALIVE_STATE(IN_PROCESS); + MARLIN_KEEPALIVE_STATE_IN_PROCESS; LongTimer nozzleTimeout; @@ -752,7 +712,7 @@ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { // - still running -> wait normally in idle() // - failed -> then do the safety moves on the printer like before // - finished ok -> proceed with reading other commands - delay_keep_alive(0); // calls LogicStep() and remembers its return status + safe_delay_keep_alive(0); // calls LogicStep() and remembers its return status if (mmu_print_saved & SavedState::CooldownPending){ if (!nozzleTimeout.running()){ @@ -761,7 +721,7 @@ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { } else if (nozzleTimeout.expired(DEFAULT_SAFETYTIMER_TIME_MINS*60*1000ul)){ // mins->msec. mmu_print_saved &= ~(SavedState::CooldownPending); mmu_print_saved |= SavedState::Cooldown; - setAllTargetHotends(0); + thermal_setTargetHotend(0); LogEchoEvent_P(PSTR("Heater cooldown")); } } else if (nozzleTimeout.running()) { @@ -775,8 +735,8 @@ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { // the E may have some more moves to finish - wait for them ResumeHotendTemp(); ResumeUnpark(); // We can now travel back to the tower or wherever we were when we saved. - ResetRetryAttempts(); // Reset the retry counter. - st_synchronize(); + logic.ResetRetryAttempts(); // Reset the retry counter. + planner_synchronize(); return true; case Interrupted: // now what :D ... big bad ... ramming, unload, retry the whole command originally issued @@ -795,7 +755,7 @@ bool MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) { case CommunicationTimeout: case ProtocolError: case ButtonPushed: - if (!inAutoRetry){ + if (! logic.InAutoRetry()){ // Don't proceed to the park/save if we are doing an autoretry. SaveAndPark(move_axes); SaveHotendTemp(turn_off_nozzle); @@ -868,25 +828,32 @@ StepStatus MMU2::LogicStep(bool reportErrors) { } void MMU2::filament_ramming() { - execute_extruder_sequence((const E_Step *)ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step)); + execute_extruder_sequence(ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step)); } void MMU2::execute_extruder_sequence(const E_Step *sequence, uint8_t steps) { - st_synchronize(); + planner_synchronize(); + Enable_E0(); + const E_Step *step = sequence; for (uint8_t i = 0; i < steps; i++) { - current_position[E_AXIS] += pgm_read_float(&(step->extrude)); - plan_buffer_line_curposXYZE(pgm_read_float(&(step->feedRate))); - st_synchronize(); + const float es = pgm_read_float(&(step->extrude)); + const feedRate_t fr_mm_m = pgm_read_float(&(step->feedRate)); + planner_set_current_position_E(planner_get_current_position_E() + es); + planner_line_to_current_position(MMM_TO_MMS(fr_mm_m)); + step++; } + planner_synchronize(); // it looks like it's better to sync the moves at the end - smoother move (if the sequence is not too long). + + Disable_E0(); } void MMU2::execute_load_to_nozzle_sequence() { - st_synchronize(); + planner_synchronize(); // Compensate for configurable Extra Loading Distance - current_position[E_AXIS] -= (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION); - execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof (load_to_nozzle_sequence[0])); + planner_set_current_position_E( planner_get_current_position_E() - (logic.ExtraLoadDistance() - MMU2_FILAMENT_SENSOR_POSITION) ); + execute_extruder_sequence(load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof (load_to_nozzle_sequence[0])); } void MMU2::ReportError(ErrorCode ec, ErrorSource res) { @@ -945,7 +912,7 @@ void MMU2::ReportError(ErrorCode ec, ErrorSource res) { // If retry attempts are all used up // or if 'Retry' operation is not available // raise the MMU error sceen and wait for user input - ReportErrorHook((uint16_t)ec); + ReportErrorHook((CommandInProgress)logic.CommandInProgress(), (uint16_t)ec, uint8_t(lastErrorSource)); } static_assert(mmu2Magic[0] == 'M' @@ -977,22 +944,21 @@ void MMU2::OnMMUProgressMsgChanged(ProgressCode pc){ switch (pc) { case ProgressCode::UnloadingToFinda: if ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::UnloadFilament - || ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::ToolChange)) - { + || ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::ToolChange)) { // If MK3S sent U0 command, ramming sequence takes care of releasing the filament. // If Toolchange is done while printing, PrusaSlicer takes care of releasing the filament // If printing is not in progress, ToolChange will issue a U0 command. break; } else { // We're likely recovering from an MMU error - st_synchronize(); + planner_synchronize(); unloadFilamentStarted = true; HelpUnloadToFinda(); } break; case ProgressCode::FeedingToFSensor: // prepare for the movement of the E-motor - st_synchronize(); + planner_synchronize(); loadFilamentStarted = true; break; default: @@ -1002,17 +968,20 @@ void MMU2::OnMMUProgressMsgChanged(ProgressCode pc){ } void __attribute__((noinline)) MMU2::HelpUnloadToFinda(){ - current_position[E_AXIS] -= MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH; - plan_buffer_line_curposXYZE(MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE); + MoveE(- MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH, MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE); } void MMU2::OnMMUProgressMsgSame(ProgressCode pc){ switch (pc) { case ProgressCode::UnloadingToFinda: - if (unloadFilamentStarted && !blocks_queued()) { // Only plan a move if there is no move ongoing - if (fsensor.getFilamentPresent()) { + if (unloadFilamentStarted && !planner_any_moves()) { // Only plan a move if there is no move ongoing + switch (WhereIsFilament()) { + case FilamentState::AT_FSENSOR: + case FilamentState::IN_NOZZLE: + case FilamentState::UNAVAILABLE: // actually Unavailable makes sense as well to start the E-move to release the filament from the gears HelpUnloadToFinda(); - } else { + break; + default: unloadFilamentStarted = false; } } @@ -1026,14 +995,12 @@ void MMU2::OnMMUProgressMsgSame(ProgressCode pc){ // After the MMU knows the FSENSOR is triggered it will: // 1. Push the filament by additional 30mm (see fsensorToNozzle) // 2. Disengage the idler and push another 2mm. - current_position[E_AXIS] += logic.ExtraLoadDistance() + 2; - plan_buffer_line_curposXYZE(MMU2_LOAD_TO_NOZZLE_FEED_RATE); + MoveE(logic.ExtraLoadDistance() + 2, MMU2_LOAD_TO_NOZZLE_FEED_RATE); break; case FilamentState::NOT_PRESENT: // fsensor not triggered, continue moving extruder - if (!blocks_queued()) { // Only plan a move if there is no move ongoing - current_position[E_AXIS] += 2.0f; - plan_buffer_line_curposXYZE(MMU2_LOAD_TO_NOZZLE_FEED_RATE); + if (!planner_any_moves()) { // Only plan a move if there is no move ongoing + MoveE(2.0f, MMU2_LOAD_TO_NOZZLE_FEED_RATE); } break; default: @@ -1048,4 +1015,24 @@ void MMU2::OnMMUProgressMsgSame(ProgressCode pc){ } } +//void MMU2::LogErrorEvent(const char *msg) { +// MMU2_ERROR_MSG(msg); +// SERIAL_ECHOLN(); +//} + +void MMU2::LogErrorEvent_P(const char *msg_P) { + MMU2_ERROR_MSGRPGM(msg_P); + SERIAL_ECHOLN(); +} + +//void MMU2::LogEchoEvent(const char *msg) { +// MMU2_ECHO_MSG(msg); +// SERIAL_ECHOLN(); +//} + +void MMU2::LogEchoEvent_P(const char *msg_P) { + MMU2_ECHO_MSGRPGM(msg_P); + SERIAL_ECHOLN(); +} + } // namespace MMU2 diff --git a/Firmware/mmu2.h b/Firmware/mmu2.h index bd29437e1..77ea54880 100644 --- a/Firmware/mmu2.h +++ b/Firmware/mmu2.h @@ -1,19 +1,24 @@ /// @file #pragma once + +#include "mmu2_state.h" +#include "mmu2_marlin.h" + +#ifdef __AVR__ #include "mmu2_protocol_logic.h" +typedef float feedRate_t; +#else + +#include "protocol_logic.h" +#include "../../Marlin/src/core/macros.h" +#include "../../Marlin/src/core/types.h" +#include +#endif struct E_Step; namespace MMU2 { -static constexpr uint8_t MAX_RETRIES = 3U; - -/// @@TODO hmmm, 12 bytes... may be we can reduce that -struct xyz_pos_t { - float xyz[3]; - xyz_pos_t()=default; -}; - // general MMU setup for MK3 enum : uint8_t { FILAMENT_UNKNOWN = 0xffU @@ -36,22 +41,8 @@ public: /// Stops the protocol logic, closes the UART, powers OFF the MMU void Stop(); - /// States of a printer with the MMU: - /// - Active - /// - Connecting - /// - Stopped - /// - /// When the printer's FW starts, the MMU2 mode is either Stopped or NotResponding (based on user's preference). - /// When the MMU successfully establishes communication, the state changes to Active. - enum class xState : uint_fast8_t { - Active, ///< MMU has been detected, connected, communicates and is ready to be worked with. - Connecting, ///< MMU is connected but it doesn't communicate (yet). The user wants the MMU, but it is not ready to be worked with. - Stopped ///< The user doesn't want the printer to work with the MMU. The MMU itself is not powered and does not work at all. - }; - inline xState State() const { return state; } - // @@TODO temporary wrappers to make old gcc survive the code inline bool Enabled()const { return State() == xState::Active; } /// Different levels of resetting the MMU @@ -97,7 +88,6 @@ public: /// @returns true upon success bool WriteRegister(uint8_t address, uint16_t data); - /// The main loop of MMU processing. /// Doesn't loop (block) inside, performs just one step of logic state machines. /// Also, internally it prevents recursive entries. @@ -184,17 +174,13 @@ public: bool is_mmu_error_monitor_active; /// Method to read-only mmu_print_saved - inline bool MMU_PRINT_SAVED() const { return mmu_print_saved != SavedState::None; } + bool MMU_PRINT_SAVED() const { return mmu_print_saved != SavedState::None; } /// Automagically "press" a Retry button if we have any retry attempts left /// @param ec ErrorCode enum value /// @returns true if auto-retry is ongoing, false when retry is unavailable or retry attempts are all used up bool RetryIfPossible(uint16_t ec); - /// Decrement the retry attempts, if in a retry. - // Called by the MMU protocol when a sent button is acknowledged. - void DecrementRetryAttempts(); - /// @return count for toolchange in current print inline uint16_t ToolChangeCounter() const { return toolchange_counter; }; @@ -206,8 +192,6 @@ public: inline void ClearTMCFailures() { tmcFailures = 0; } private: - /// Reset the retryAttempts back to the default value - void ResetRetryAttempts(); /// Perform software self-reset of the MMU (sends an X0 command) void ResetX0(); @@ -258,6 +242,20 @@ private: /// Repeated calls when progress code remains the same void OnMMUProgressMsgSame(ProgressCode pc); + /// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff) + // void LogErrorEvent(const char *msg); + /// Report the msg into the general logging subsystem. + /// On the AVR platform this variant reads the input string from PROGMEM. + /// On the ARM platform it calls LogErrorEvent directly (silently expecting the compiler to optimize it away) + void LogErrorEvent_P(const char *msg_P); + + /// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff) + // void LogEchoEvent(const char *msg); + /// Report the msg into the general logging subsystem. + /// On the AVR platform this variant reads the input string from PROGMEM. + /// On the ARM platform it calls LogEchoEvent directly (silently expecting the compiler to optimize it away) + void LogEchoEvent_P(const char *msg_P); + /// @brief Save hotend temperature and set flag to cooldown hotend after 60 minutes /// @param turn_off_nozzle if true, the hotend temperature will be set to 0degC after 60 minutes void SaveHotendTemp(bool turn_off_nozzle); @@ -302,7 +300,7 @@ private: uint8_t extruder; ///< currently active slot in the MMU ... somewhat... not sure where to get it from yet uint8_t tool_change_extruder; ///< only used for UI purposes - xyz_pos_t resume_position; + pos3d resume_position; int16_t resume_hotend_temp; ProgressCode lastProgressCode = ProgressCode::OK; @@ -322,9 +320,6 @@ private: /// true in case we are doing the LoadToNozzle operation - that means the filament shall be loaded all the way down to the nozzle /// unlike the mid-print ToolChange commands, which only load the first ~30mm and then the G-code takes over. bool loadingToNozzle; - - bool inAutoRetry; - uint8_t retryAttempts; uint16_t toolchange_counter; uint16_t tmcFailures; }; diff --git a/Firmware/mmu2_fsensor.h b/Firmware/mmu2_fsensor.h index 5135aedf1..c30f578a7 100644 --- a/Firmware/mmu2_fsensor.h +++ b/Firmware/mmu2_fsensor.h @@ -9,7 +9,8 @@ namespace MMU2 { enum class FilamentState : uint_fast8_t { NOT_PRESENT = 0, ///< filament sensor doesn't see the filament AT_FSENSOR = 1, ///< filament detected by the filament sensor, but the nozzle has not detected the filament yet - IN_NOZZLE = 2 ///< filament detected by the filament sensor and also loaded in the nozzle + IN_NOZZLE = 2, ///< filament detected by the filament sensor and also loaded in the nozzle + UNAVAILABLE = 3 ///< sensor not available (likely not connected due broken cable) }; FilamentState WhereIsFilament(); diff --git a/Firmware/mmu2_marlin.h b/Firmware/mmu2_marlin.h new file mode 100644 index 000000000..1e70b73d1 --- /dev/null +++ b/Firmware/mmu2_marlin.h @@ -0,0 +1,63 @@ +/// @file +/// The sole purpose of this interface is to separate Marlin1/Marlin2 from the MMU2 top logic layer. +/// Why? +/// - unify implementation among MK3 and Buddy FW +/// - enable unit testing of MMU2 top layer +#pragma once +#include + +namespace MMU2 { + +/// @@TODO hmmm, 12 bytes... may be we can reduce that +struct pos3d { + float xyz[3]; + pos3d() = default; + inline constexpr pos3d(float x, float y, float z) + : xyz { x, y, z } {} + pos3d operator=(const float *newP){ + for(uint8_t i = 0; i < 3; ++i){ + xyz[i] = newP[i]; + } + return *this; + } +}; + +void MoveE(float delta, float feedRate); + +float raise_z(float delta); + +void planner_synchronize(); +bool planner_any_moves(); +float planner_get_machine_position_E_mm(); +float planner_get_current_position_E(); +void planner_set_current_position_E(float e); +void planner_line_to_current_position(float feedRate_mm_s); +pos3d planner_current_position(); + +void motion_do_blocking_move_to_xy(float rx, float ry, float feedRate_mm_s); +void motion_do_blocking_move_to_z(float z, float feedRate_mm_s); + +void nozzle_park(); + +bool marlin_printingIsActive(); +void marlin_manage_heater(); +void marlin_manage_inactivity(bool b); +void marlin_idle(bool b); + +int16_t thermal_degTargetHotend(); +int16_t thermal_degHotend(); +void thermal_setExtrudeMintemp(int16_t t); +void thermal_setTargetHotend(int16_t t); + +void safe_delay_keep_alive(uint16_t t); + +void Enable_E0(); +void Disable_E0(); + +bool all_axes_homed(); + +void gcode_reset_stepper_timeout(); + +bool cutter_enabled(); + +} // namespace MMU2 diff --git a/Firmware/mmu2_marlin1.cpp b/Firmware/mmu2_marlin1.cpp new file mode 100644 index 000000000..803ca8c50 --- /dev/null +++ b/Firmware/mmu2_marlin1.cpp @@ -0,0 +1,123 @@ +/// @file +/// MK3 / Marlin1 implementation of support routines for the MMU2 +#include "Marlin.h" +#include "stepper.h" +#include "planner.h" +#include "mmu2_config.h" +#include "temperature.h" + +namespace MMU2 { + +void MoveE(float delta, float feedRate) { + current_position[E_AXIS] += delta; + plan_buffer_line_curposXYZE(feedRate); +} + +float raise_z(float delta) { + // @@TODO + return 0.0F; +} + +void planner_synchronize() { + st_synchronize(); +} + +bool planner_any_moves() { + return blocks_queued(); +} + +float planner_get_machine_position_E_mm(){ +// @@TODO return Planner::get_machine_position_mm()[3]; +} + +float planner_get_current_position_E(){ + return current_position[E_AXIS]; +} + +void planner_set_current_position_E(float e){ + current_position[E_AXIS] = e; +} + +void planner_line_to_current_position(float feedRate_mm_s){ + plan_buffer_line_curposXYZE(feedRate_mm_s); + st_synchronize(); +} + +pos3d planner_current_position(){ + return pos3d(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]); +} + +void motion_do_blocking_move_to_xy(float rx, float ry, float feedRate_mm_s){ + current_position[X_AXIS] = rx; + current_position[Y_AXIS] = ry; + plan_buffer_line_curposXYZE(feedRate_mm_s); + st_synchronize(); +} + +void motion_do_blocking_move_to_z(float z, float feedRate_mm_s){ + current_position[Z_AXIS] = z; + plan_buffer_line_curposXYZE(feedRate_mm_s); + st_synchronize(); +} + +void nozzle_park() { + current_position[X_AXIS] = MMU_ERR_X_PAUSE_POS; + current_position[Y_AXIS] = MMU_ERR_Y_PAUSE_POS; + planner_line_to_current_position(NOZZLE_PARK_XY_FEEDRATE); + st_synchronize(); +} + +bool marlin_printingIsActive() { + // return IS_SD_PRINTING || usb_timer_running(); + return printer_active(); +} + +void marlin_manage_heater(){ + manage_heater(); +} + +void marlin_manage_inactivity(bool b){ + manage_inactivity(b); +} + +void marlin_idle(bool b){ + manage_heater(); + manage_inactivity(true); +} + +int16_t thermal_degTargetHotend() { + return degTargetHotend(0); +} + +int16_t thermal_degHotend() { + return degHotend(0); +} + +void thermal_setExtrudeMintemp(int16_t t) { + set_extrude_min_temp(t); +} + +void thermal_setTargetHotend(int16_t t) { + setTargetHotend(t, 0); +} + +void safe_delay_keep_alive(uint16_t t) { + delay_keep_alive(t); +} + +void gcode_reset_stepper_timeout(){ + // empty +} + +void Enable_E0(){ enable_e0(); } +void Disable_E0(){ disable_e0(); } + +bool all_axes_homed(){ + return axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]; +} + +bool cutter_enabled(){ + return eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED) == EEPROM_MMU_CUTTER_ENABLED_enabled; +} + +} // namespace MMU2 diff --git a/Firmware/mmu2_marlin_macros.h b/Firmware/mmu2_marlin_macros.h new file mode 100644 index 000000000..af2f79504 --- /dev/null +++ b/Firmware/mmu2_marlin_macros.h @@ -0,0 +1,16 @@ +/// @file +/// This file will not be the same on Marlin1 and Marlin2. +/// Its purpose is to unify different macros in either of Marlin incarnations. +#pragma once + +#ifdef __AVR__ + #include "Marlin.h" + // brings _O and _T macros into MMU + #include "language.h" + #define MARLIN_KEEPALIVE_STATE_IN_PROCESS KEEPALIVE_STATE(IN_PROCESS) +#else + #include "../../gcode/gcode.h" + #define _O(x) x + #define _T(x) x + #define MARLIN_KEEPALIVE_STATE_IN_PROCESS KEEPALIVE_STATE(IN_PROCESS) +#endif diff --git a/Firmware/mmu2_power.cpp b/Firmware/mmu2_power.cpp index ed9f8adb7..14f119156 100644 --- a/Firmware/mmu2_power.cpp +++ b/Firmware/mmu2_power.cpp @@ -10,13 +10,18 @@ namespace MMU2 { // sadly, on MK3 we cannot do actual power cycle on HW... // so we just block the MMU via EEPROM var instead. -void power_on() -{ +void power_on() { +#ifdef MMU_HWRESET + WRITE(MMU_RST_PIN, 1); + SET_OUTPUT(MMU_RST_PIN); // setup reset pin +#endif //MMU_HWRESET + eeprom_update_byte((uint8_t *)EEPROM_MMU_ENABLED, true); + + reset(); } -void power_off() -{ +void power_off() { eeprom_update_byte((uint8_t *)EEPROM_MMU_ENABLED, false); } diff --git a/Firmware/mmu2_protocol_logic.cpp b/Firmware/mmu2_protocol_logic.cpp index f6fea96a9..1b6d54381 100644 --- a/Firmware/mmu2_protocol_logic.cpp +++ b/Firmware/mmu2_protocol_logic.cpp @@ -1,7 +1,16 @@ #include "mmu2_protocol_logic.h" #include "mmu2_log.h" #include "mmu2_fsensor.h" + +#ifdef __AVR__ +// on MK3/S/+ we shuffle the timers a bit, thus "_millis" may not equal "millis" #include "system_timer.h" +#else +// irrelevant on Buddy FW, just keep "_millis" as "millis" +#include +#define _millis millis +#endif + #include namespace MMU2 { @@ -20,7 +29,8 @@ const uint8_t ProtocolLogic::regs16Addrs[ProtocolLogic::regs16Count] PROGMEM = { }; const uint8_t ProtocolLogic::initRegs8Addrs[ProtocolLogic::initRegs8Count] PROGMEM = { - 0x0b, // extra load distance + 0x0b, // extra load distance [mm] + 0x14, // pulley slow feedrate [mm/s] }; void ProtocolLogic::CheckAndReportAsyncEvents() { @@ -112,9 +122,12 @@ void ProtocolLogic::SendWriteRegister(uint8_t index, uint16_t value, ScopeState // searches for "ok\n" in the incoming serial data (that's the usual response of the old MMU FW) struct OldMMUFWDetector { uint8_t ok; - inline constexpr OldMMUFWDetector():ok(0) { } + inline constexpr OldMMUFWDetector() + : ok(0) {} - enum class State : uint8_t { MatchingPart, SomethingElse, Matched }; + enum class State : uint8_t { MatchingPart, + SomethingElse, + Matched }; /// @returns true when "ok\n" gets detected State Detect(uint8_t c){ @@ -144,6 +157,7 @@ StepStatus ProtocolLogic::ExpectingMessage() { case DecodeStatus::MessageCompleted: rsp = protocol.GetResponseMsg(); LogResponse(); + // @@TODO reset direction of communication RecordUARTActivity(); // something has happened on the UART, update the timeout record return MessageReady; case DecodeStatus::NeedMoreData: @@ -386,7 +400,7 @@ StepStatus ProtocolLogic::CommandStep() { case ScopeState::ButtonSent: if (rsp.paramCode == ResponseMsgParamCodes::Accepted) { // Button was accepted, decrement the retry. - mmu2.DecrementRetryAttempts(); + DecrementRetryAttempts(); } SendAndUpdateFilamentSensor(); break; @@ -467,7 +481,7 @@ StepStatus ProtocolLogic::IdleStep() { case ScopeState::ButtonSent: if (rsp.paramCode == ResponseMsgParamCodes::Accepted) { // Button was accepted, decrement the retry. - mmu2.DecrementRetryAttempts(); + DecrementRetryAttempts(); } StartReading8bitRegisters(); return Processing; @@ -495,7 +509,7 @@ StepStatus ProtocolLogic::IdleStep() { return Finished; } -ProtocolLogic::ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance) +ProtocolLogic::ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance, uint8_t pulleySlowFeedrate) : explicitPrinterError(ErrorCode::OK) , currentScope(Scope::Stopped) , scopeState(ScopeState::Ready) @@ -510,12 +524,16 @@ ProtocolLogic::ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance) , progressCode(ProgressCode::OK) , buttonCode(NoButton) , lastFSensor((uint8_t)WhereIsFilament()) - , regs8 { 0, 0, 0 } - , regs16 { 0, 0 } - , initRegs8 { extraLoadDistance } , regIndex(0) - , mmuFwVersion { 0, 0, 0 } -{} + , retryAttempts(MAX_RETRIES) + , inAutoRetry(false) +{ + // @@TODO currently, I don't see a way of writing the initialization better :( + // I'd like to write something like: initRegs8 { extraLoadDistance, pulleySlowFeedrate } + // avr-gcc seems to like such a syntax, ARM gcc doesn't + initRegs8[0] = extraLoadDistance; + initRegs8[1] = pulleySlowFeedrate; +} void ProtocolLogic::Start() { state = State::InitSequence; @@ -811,6 +829,18 @@ uint8_t ProtocolLogic::CommandInProgress() const { return (uint8_t)ReqMsg().code; } +void ProtocolLogic::DecrementRetryAttempts() { + if (inAutoRetry && retryAttempts) { + SERIAL_ECHOLNPGM("DecrementRetryAttempts"); + retryAttempts--; + } +} + +void ProtocolLogic::ResetRetryAttempts() { + SERIAL_ECHOLNPGM("ResetRetryAttempts"); + retryAttempts = MAX_RETRIES; +} + bool DropOutFilter::Record(StepStatus ss) { if (occurrences == maxOccurrences) { cause = ss; diff --git a/Firmware/mmu2_protocol_logic.h b/Firmware/mmu2_protocol_logic.h index 7a10125d0..f96e91f40 100644 --- a/Firmware/mmu2_protocol_logic.h +++ b/Firmware/mmu2_protocol_logic.h @@ -1,7 +1,15 @@ #pragma once #include #include -// #include //@@TODO Don't we have STL for AVR somewhere? + +#ifdef __AVR__ +#include "mmu2/error_codes.h" +#include "mmu2/progress_codes.h" +#include "mmu2/buttons.h" +#include "mmu2_protocol.h" + +// #include std array is not available on AVR ... we need to "fake" it +namespace std { template class array { T data[N]; @@ -14,17 +22,26 @@ public: return data[i]; } }; +} +#else -#include "mmu2/error_codes.h" -#include "mmu2/progress_codes.h" -#include "mmu2/buttons.h" -#include "mmu2_protocol.h" +#include +#include "../../../../../../Prusa-Firmware-MMU/src/logic/error_codes.h" +#include "../../../../../../Prusa-Firmware-MMU/src/logic/progress_codes.h" + +// prevent ARM HAL macros from breaking our code +#undef CRC +#include "../../../../../../Prusa-Firmware-MMU/src/modules/protocol.h" +#include "buttons.h" +#endif #include "mmu2_serial.h" /// New MMU2 protocol logic namespace MMU2 { +static constexpr uint8_t MAX_RETRIES = 3U; + using namespace modules::protocol; class ProtocolLogic; @@ -73,7 +90,7 @@ public: /// Logic layer of the MMU vs. printer communication protocol class ProtocolLogic { public: - ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance); + ProtocolLogic(MMU2Serial *uart, uint8_t extraLoadDistance, uint8_t pulleySlowFeedrate); /// Start/Enable communication with the MMU void Start(); @@ -143,6 +160,21 @@ public: return mmuFwVersion[2]; } + /// Current number of retry attempts left + constexpr uint8_t RetryAttempts() const { return retryAttempts; } + + /// Decrement the retry attempts, if in a retry. + /// Called by the MMU protocol when a sent button is acknowledged. + void DecrementRetryAttempts(); + + /// Reset the retryAttempts back to the default value + void ResetRetryAttempts(); + + constexpr bool InAutoRetry() const { return inAutoRetry; } + void SetInAutoRetry(bool iar) { + inAutoRetry = iar; + } + inline void SetPrinterError(ErrorCode ec){ explicitPrinterError = ec; } @@ -315,7 +347,7 @@ private: Protocol protocol; ///< protocol codec - array lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes + std::array lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes uint8_t lrb; MMU2Serial *uart; ///< UART interface @@ -330,31 +362,27 @@ private: static constexpr uint8_t regs8Count = 3; static_assert(regs8Count > 0); // code is not ready for empty lists of registers static const uint8_t regs8Addrs[regs8Count] PROGMEM; - uint8_t regs8[regs8Count]; + uint8_t regs8[regs8Count] = { 0, 0, 0 }; // 16bit registers static constexpr uint8_t regs16Count = 2; static_assert(regs16Count > 0); // code is not ready for empty lists of registers static const uint8_t regs16Addrs[regs16Count] PROGMEM; - uint16_t regs16[regs16Count]; + uint16_t regs16[regs16Count] = { 0, 0 }; // 8bit init values to be sent to the MMU after line up - static constexpr uint8_t initRegs8Count = 1; + static constexpr uint8_t initRegs8Count = 2; static_assert(initRegs8Count > 0); // code is not ready for empty lists of registers static const uint8_t initRegs8Addrs[initRegs8Count] PROGMEM; uint8_t initRegs8[initRegs8Count]; uint8_t regIndex; - uint8_t mmuFwVersion[3]; + uint8_t mmuFwVersion[3] = { 0, 0, 0 }; uint16_t mmuFwVersionBuild; - friend class ProtocolLogicPartBase; - friend class Stopped; - friend class Command; - friend class Idle; - friend class StartSeq; - friend class DelayedRestart; + uint8_t retryAttempts; + bool inAutoRetry; friend class MMU2; }; diff --git a/Firmware/mmu2_reporting.cpp b/Firmware/mmu2_reporting.cpp index cd04da7bb..579172429 100644 --- a/Firmware/mmu2_reporting.cpp +++ b/Firmware/mmu2_reporting.cpp @@ -217,7 +217,7 @@ enum class ReportErrorHookStates : uint8_t { enum ReportErrorHookStates ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN; -void ReportErrorHook(uint16_t ec) { +void ReportErrorHook(CommandInProgress /*cip*/, uint16_t ec, uint8_t /*es*/) { if (mmu2.MMUCurrentErrorCode() == ErrorCode::OK && mmu2.MMULastErrorSource() == MMU2::ErrorSourceMMU) { // If the error code suddenly changes to OK, that means // a button was pushed on the MMU and the LCD should @@ -292,4 +292,44 @@ void IncrementMMUFails(){ eeprom_increment_word((uint16_t *)EEPROM_MMU_FAIL_TOT); } +void MakeSound(SoundType s){ + Sound_MakeSound( (eSOUND_TYPE)s); +} + +static void FullScreenMsg(const char *pgmS, uint8_t slot){ + lcd_update_enable(false); + lcd_clear(); + lcd_puts_at_P(0, 1, pgmS); + lcd_print(' '); + lcd_print(slot + 1); +} + +void FullScreenMsgCut(uint8_t slot){ + FullScreenMsg(_T(MSG_CUT_FILAMENT), slot); +} + +void FullScreenMsgEject(uint8_t slot){ + FullScreenMsg(_T(MSG_EJECT_FILAMENT), slot); +} + +void FullScreenMsgTest(uint8_t slot){ + FullScreenMsg(_T(MSG_TESTING_FILAMENT), slot); +} + +void FullScreenMsgLoad(uint8_t slot){ + FullScreenMsg(_T(MSG_LOADING_FILAMENT), slot); +} + +void FullScreenMsgRestoringTemperature(){ + lcd_display_message_fullscreen_P(_i("MMU Retry: Restoring temperature...")); ////MSG_MMU_RESTORE_TEMP c=20 r=4 +} + +void ScreenUpdateEnable(){ + lcd_update_enable(true); +} + +void ScreenClear(){ + lcd_clear(); +} + } // namespace MMU2 diff --git a/Firmware/mmu2_reporting.h b/Firmware/mmu2_reporting.h index 1b76b920f..a5fd49aa0 100644 --- a/Firmware/mmu2_reporting.h +++ b/Firmware/mmu2_reporting.h @@ -22,13 +22,12 @@ void BeginReport(CommandInProgress cip, uint16_t ec); /// Called at the end of every MMU operation void EndReport(CommandInProgress cip, uint16_t ec); -/** - * @brief Called when the MMU or MK3S sends operation error (even repeatedly). - * Render MMU error screen on the LCD. This must be non-blocking - * and allow the MMU and printer to communicate with each other. - * @param[in] ec error code - */ -void ReportErrorHook(uint16_t ec); +/// @brief Called when the MMU or MK3S sends operation error (even repeatedly). +/// Render MMU error screen on the LCD. This must be non-blocking +/// and allow the MMU and printer to communicate with each other. +/// @param[in] ec error code +/// @param[in] es error source +void ReportErrorHook(CommandInProgress cip, uint16_t ec, uint8_t es); /// Called when the MMU sends operation progress update void ReportProgressHook(CommandInProgress cip, uint16_t ec); @@ -53,4 +52,21 @@ void IncrementLoadFails(); /// Increments EEPROM cell - number of MMU errors void IncrementMMUFails(); +// Beware: enum values intentionally chosen to match the 8bit FW to save code size +enum SoundType { + Prompt = 2, + Confirm = 3 +}; + +void MakeSound(SoundType s); + +void FullScreenMsgCut(uint8_t slot); +void FullScreenMsgEject(uint8_t slot); +void FullScreenMsgTest(uint8_t slot); +void FullScreenMsgLoad(uint8_t slot); +void FullScreenMsgRestoringTemperature(); + +void ScreenUpdateEnable(); +void ScreenClear(); + } // namespace diff --git a/Firmware/mmu2_state.h b/Firmware/mmu2_state.h new file mode 100644 index 000000000..85e6be686 --- /dev/null +++ b/Firmware/mmu2_state.h @@ -0,0 +1,21 @@ +/** + * @file mmu_state.h + * @brief status of mmu + */ +#pragma once +#include +namespace MMU2 { +/// States of a printer with the MMU: +/// - Active +/// - Connecting +/// - Stopped +/// +/// When the printer's FW starts, the MMU2 mode is either Stopped or NotResponding (based on user's preference). +/// When the MMU successfully establishes communication, the state changes to Active. +enum class xState : uint_fast8_t { + Active, ///< MMU has been detected, connected, communicates and is ready to be worked with. + Connecting, ///< MMU is connected but it doesn't communicate (yet). The user wants the MMU, but it is not ready to be worked with. + Stopped ///< The user doesn't want the printer to work with the MMU. The MMU itself is not powered and does not work at all. +}; + +} // namespace MMU2