mirror of
https://github.com/MarlinFirmware/Marlin.git
synced 2025-07-06 22:47:27 -06:00
✨ Prusa MMU3 (#26635)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
parent
a664587219
commit
a9c529f004
70 changed files with 8975 additions and 122 deletions
|
@ -385,14 +385,15 @@
|
|||
* PRUSA_MMU1 : Průša MMU1 (The "multiplexer" version)
|
||||
* PRUSA_MMU2 : Průša MMU2
|
||||
* PRUSA_MMU2S : Průša MMU2S (Requires MK3S extruder with motion sensor, EXTRUDERS = 5)
|
||||
* PRUSA_MMU3 : Průša MMU3 (Requires MK3S extruder with motion sensor and MMU firmware version 3.x.x, EXTRUDERS = 5)
|
||||
* EXTENDABLE_EMU_MMU2 : MMU with configurable number of filaments (ERCF, SMuFF or similar with Průša MMU2 compatible firmware)
|
||||
* EXTENDABLE_EMU_MMU2S : MMUS with configurable number of filaments (ERCF, SMuFF or similar with Průša MMU2 compatible firmware)
|
||||
*
|
||||
* Requires NOZZLE_PARK_FEATURE to park print head in case MMU unit fails.
|
||||
* See additional options in Configuration_adv.h.
|
||||
* :["PRUSA_MMU1", "PRUSA_MMU2", "PRUSA_MMU2S", "EXTENDABLE_EMU_MMU2", "EXTENDABLE_EMU_MMU2S"]
|
||||
* :["PRUSA_MMU1", "PRUSA_MMU2", "PRUSA_MMU2S", "PRUSA_MMU3", "EXTENDABLE_EMU_MMU2", "EXTENDABLE_EMU_MMU2S"]
|
||||
*/
|
||||
//#define MMU_MODEL PRUSA_MMU2
|
||||
//#define MMU_MODEL PRUSA_MMU3
|
||||
|
||||
// @section psu control
|
||||
|
||||
|
|
|
@ -4389,44 +4389,89 @@
|
|||
//#define E_MUX0_PIN 40 // Always Required
|
||||
//#define E_MUX1_PIN 42 // Needed for 3 to 8 inputs
|
||||
//#define E_MUX2_PIN 44 // Needed for 5 to 8 inputs
|
||||
#elif HAS_PRUSA_MMU2
|
||||
// Serial port used for communication with MMU2.
|
||||
#elif HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
// Common settings for MMU2/MMU2S/MMU3
|
||||
// Serial port used for communication with MMU2/MMU2S/MMU3.
|
||||
#define MMU2_SERIAL_PORT 2
|
||||
#define MMU_BAUD 115200
|
||||
|
||||
// Use hardware reset for MMU if a pin is defined for it
|
||||
//#define MMU2_RST_PIN 23
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
// Enable if the MMU2 has 12V stepper motors (MMU2 Firmware 1.0.2 and up)
|
||||
//#define MMU2_MODE_12V
|
||||
|
||||
// G-code to execute when MMU2 F.I.N.D.A. probe detects filament runout
|
||||
#define MMU2_FILAMENT_RUNOUT_SCRIPT "M600"
|
||||
#endif
|
||||
|
||||
// Add an LCD menu for MMU2
|
||||
//#define MMU2_MENUS
|
||||
// Add an LCD menu for MMU2/MMU2S/MMU3
|
||||
//#define MMU_MENUS
|
||||
|
||||
// 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
|
||||
|
||||
/**
|
||||
* ------------
|
||||
* MMU2 / MMU2S
|
||||
* ------------
|
||||
* MMU2 sequences use mm/min. Not compatible with MMU3 (see below).
|
||||
* #define MMU2_LOAD_TO_NOZZLE_SEQUENCE \
|
||||
* { 4.4, 871 }, \
|
||||
* { 10.0, 1393 }, \
|
||||
* { 4.4, 871 }, \
|
||||
* { 10.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 }
|
||||
*/
|
||||
|
||||
/**
|
||||
* ----
|
||||
* MMU3
|
||||
* ----
|
||||
* These values are compatible with MMU3 as they are defined in mm/s
|
||||
*/
|
||||
|
||||
#define MMU2_EXTRUDER_PTFE_LENGTH 42.3 // (mm)
|
||||
#define MMU2_EXTRUDER_HEATBREAK_LENGTH 17.7 // (mm)
|
||||
|
||||
#define MMU2_LOAD_TO_NOZZLE_SEQUENCE \
|
||||
{ 7.2, 1145 }, \
|
||||
{ 14.4, 871 }, \
|
||||
{ 36.0, 1393 }, \
|
||||
{ 14.4, 871 }, \
|
||||
{ 50.0, 198 }
|
||||
{ MMU2_EXTRUDER_PTFE_LENGTH, MMM_TO_MMS(810) }, /* (13.5 mm/s) Fast load ahead of heatbreak */ \
|
||||
{ MMU2_EXTRUDER_HEATBREAK_LENGTH, MMM_TO_MMS(198) } // ( 3.3 mm/s) Slow load after heatbreak
|
||||
|
||||
#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 }
|
||||
{ 0.2816, MMM_TO_MMS(1339.0) }, \
|
||||
{ 0.3051, MMM_TO_MMS(1451.0) }, \
|
||||
{ 0.3453, MMM_TO_MMS(1642.0) }, \
|
||||
{ 0.3990, MMM_TO_MMS(1897.0) }, \
|
||||
{ 0.4761, MMM_TO_MMS(2264.0) }, \
|
||||
{ 0.5767, MMM_TO_MMS(2742.0) }, \
|
||||
{ 0.5691, MMM_TO_MMS(3220.0) }, \
|
||||
{ 0.1081, MMM_TO_MMS(3220.0) }, \
|
||||
{ 0.7644, MMM_TO_MMS(3635.0) }, \
|
||||
{ 0.8248, MMM_TO_MMS(3921.0) }, \
|
||||
{ 0.8483, MMM_TO_MMS(4033.0) }, \
|
||||
{ -15.0, MMM_TO_MMS(6000.0) }, \
|
||||
{ -24.5, MMM_TO_MMS(1200.0) }, \
|
||||
{ -7.0, MMM_TO_MMS( 600.0) }, \
|
||||
{ -3.5, MMM_TO_MMS( 360.0) }, \
|
||||
{ 20.0, MMM_TO_MMS( 454.0) }, \
|
||||
{ -20.0, MMM_TO_MMS( 303.0) }, \
|
||||
{ -35.0, MMM_TO_MMS(2000.0) }
|
||||
|
||||
/**
|
||||
* Using a sensor like the MMU2S
|
||||
|
@ -4436,11 +4481,26 @@
|
|||
#if HAS_PRUSA_MMU2S
|
||||
#define MMU2_C0_RETRY 5 // Number of retries (total time = timeout*retries)
|
||||
|
||||
/**
|
||||
* This is called after the filament runout sensor is triggered to check if
|
||||
* the filament has been loaded properly by moving the filament back and
|
||||
* forth to see if the filament runout sensor is going to get triggered
|
||||
* again, which should not occur if the filament is properly loaded.
|
||||
*
|
||||
* Thus, the MMU2_CAN_LOAD_SEQUENCE should contain some forward and
|
||||
* backward moves. The forward moves should be greater than the backward
|
||||
* moves.
|
||||
*
|
||||
* This is useless if your filament runout sensor is way behind the gears.
|
||||
* In that case use {0, MMU2_CAN_LOAD_FEEDRATE}
|
||||
*
|
||||
* Adjust MMU2_CAN_LOAD_SEQUENCE according to your setup.
|
||||
*/
|
||||
#define MMU2_CAN_LOAD_FEEDRATE 800 // (mm/min)
|
||||
#define MMU2_CAN_LOAD_SEQUENCE \
|
||||
{ 0.1, MMU2_CAN_LOAD_FEEDRATE }, \
|
||||
{ 60.0, MMU2_CAN_LOAD_FEEDRATE }, \
|
||||
{ -52.0, MMU2_CAN_LOAD_FEEDRATE }
|
||||
{ 5.0, MMU2_CAN_LOAD_FEEDRATE }, \
|
||||
{ 15.0, MMU2_CAN_LOAD_FEEDRATE }, \
|
||||
{ -10.0, MMU2_CAN_LOAD_FEEDRATE }
|
||||
|
||||
#define MMU2_CAN_LOAD_RETRACT 6.0 // (mm) Keep under the distance between Load Sequence values
|
||||
#define MMU2_CAN_LOAD_DEVIATION 0.8 // (mm) Acceptable deviation
|
||||
|
@ -4451,6 +4511,68 @@
|
|||
|
||||
// Continue unloading if sensor detects filament after the initial unload move
|
||||
//#define MMU_IR_UNLOAD_MOVE
|
||||
|
||||
#elif HAS_PRUSA_MMU3
|
||||
|
||||
// MMU3 settings
|
||||
|
||||
#define MMU2_MAX_RETRIES 3 // Number of retries (total time = timeout*retries)
|
||||
|
||||
// Nominal distance from the extruder gear to the nozzle tip is 87mm
|
||||
// However, some slipping may occur and we need separate distances for
|
||||
// LoadToNozzle and ToolChange.
|
||||
// - +5mm seemed good for LoadToNozzle,
|
||||
// - but too much (made blobs) for a ToolChange
|
||||
#define MMU2_LOAD_TO_NOZZLE_LENGTH 87.0 + 5.0
|
||||
|
||||
// As discussed with our PrusaSlicer profile specialist
|
||||
// - ToolChange shall not try to push filament into the very tip of the nozzle
|
||||
// to have some space for additional G-code to tune the extruded filament length
|
||||
// in the profile
|
||||
// Beware - this value is used to initialize the MMU logic layer - it will be sent to the MMU upon line up (written into its 8bit register 0x0b)
|
||||
// However - in the G-code we can get a request to set the extra load distance at runtime to something else (M708 A0xb Xsomething).
|
||||
// The printer intercepts such a call and sets its extra load distance to match the new value as well.
|
||||
#define MMU2_FILAMENT_SENSOR_POSITION 0 // (mm)
|
||||
#define MMU2_LOAD_DISTANCE_PAST_GEARS 5 // (mm)
|
||||
#define MMU2_TOOL_CHANGE_LOAD_LENGTH MMU2_FILAMENT_SENSOR_POSITION + MMU2_LOAD_DISTANCE_PAST_GEARS // (mm)
|
||||
|
||||
#define MMU2_LOAD_TO_NOZZLE_FEED_RATE 20.0 // (mm/s)
|
||||
#define MMU2_UNLOAD_TO_FINDA_FEED_RATE 120.0 // (mm/s)
|
||||
|
||||
#define MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE 50.0 // (mm/s)
|
||||
#define MMU2_VERIFY_LOAD_TO_NOZZLE_TWEAK -5.0 // (mm) Amount to adjust the length for verifying load-to-nozzle
|
||||
|
||||
// The first thing the MMU does is initialize its axis.
|
||||
// Meanwhile the E-motor will unload 20mm of filament in about 1 second.
|
||||
#define MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH 80.0 // (mm)
|
||||
#define MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE 80.0 // (mm/s)
|
||||
|
||||
// After loading a new filament, the printer will extrude this length of filament
|
||||
// then retract to the original position. This is used to check if the filament sensor
|
||||
// reading flickers or filament is jammed.
|
||||
#define MMU2_CHECK_FILAMENT_PRESENCE_EXTRUSION_LENGTH (MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH + MMU2_VERIFY_LOAD_TO_NOZZLE_TWEAK + MMU2_FILAMENT_SENSOR_POSITION) // (mm)
|
||||
|
||||
#define MMU_HAS_CUTTER // Enable cutter related functionalities
|
||||
//#define MMU_FORCE_STEALTH_MODE // Force stealth mode and disable menu item
|
||||
|
||||
/**
|
||||
* SpoolJoin Consumes All Filament -- EXPERIMENTAL
|
||||
*
|
||||
* SpoolJoin normally triggers when FINDA sensor untriggers while printing.
|
||||
* This is the default behaviour and it doesn't consume all the filament
|
||||
* before triggering a filament change. This leaves some filament in the
|
||||
* current slot and before switching to the next slot it is unloaded.
|
||||
*
|
||||
* Enabling this option will trigger the filament change when both FINDA
|
||||
* and Filament Runout Sensor triggers during the print and it allows the
|
||||
* filament in the current slot to be completely consumed before doing the
|
||||
* filament change. But this can cause problems as a little bit of filament
|
||||
* will be left between the extruder gears (thinking that the filament
|
||||
* sensor is triggered through the gears) and the end of the PTFE tube and
|
||||
* can cause filament load issues.
|
||||
*/
|
||||
//#define MMU_SPOOL_JOIN_CONSUMES_ALL_FILAMENT
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
|
@ -4473,7 +4595,7 @@
|
|||
|
||||
//#define MMU2_DEBUG // Write debug info to serial output
|
||||
|
||||
#endif // HAS_PRUSA_MMU2
|
||||
#endif // HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
|
||||
/**
|
||||
* Advanced Print Counter settings
|
||||
|
|
|
@ -229,12 +229,14 @@
|
|||
#include "feature/controllerfan.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU1
|
||||
#include "feature/mmu/mmu.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "feature/mmu3/mmu2.h"
|
||||
#include "feature/mmu3/mmu2_reporting.h"
|
||||
#include "feature/mmu3/SpoolJoin.h"
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#include "feature/mmu/mmu2.h"
|
||||
#elif HAS_PRUSA_MMU1
|
||||
#include "feature/mmu/mmu.h"
|
||||
#endif
|
||||
|
||||
#if ENABLED(PASSWORD_FEATURE)
|
||||
|
@ -351,6 +353,7 @@ void startOrResumeJob() {
|
|||
TERN_(CANCEL_OBJECTS, cancelable.reset());
|
||||
TERN_(LCD_SHOW_E_TOTAL, e_move_accumulator = 0);
|
||||
TERN_(SET_REMAINING_TIME, ui.reset_remaining_time());
|
||||
TERN_(HAS_PRUSA_MMU3, MMU3::operation_statistics.reset_per_print_stats());
|
||||
}
|
||||
print_job_timer.start();
|
||||
}
|
||||
|
@ -785,7 +788,7 @@ void idle(const bool no_stepper_sleep/*=false*/) {
|
|||
|
||||
// Handle filament runout sensors
|
||||
#if HAS_FILAMENT_SENSOR
|
||||
if (TERN1(HAS_PRUSA_MMU2, !mmu2.enabled()))
|
||||
if (TERN1(HAS_PRUSA_MMU2, !mmu2.enabled()) && TERN1(HAS_PRUSA_MMU3, !mmu3.enabled()))
|
||||
runout.run();
|
||||
#endif
|
||||
|
||||
|
@ -850,7 +853,11 @@ void idle(const bool no_stepper_sleep/*=false*/) {
|
|||
#endif
|
||||
|
||||
// Update the Průša MMU2
|
||||
TERN_(HAS_PRUSA_MMU2, mmu2.mmu_loop());
|
||||
#if HAS_PRUSA_MMU3
|
||||
mmu3.mmu_loop();
|
||||
#elif HAS_PRUSA_MMU2
|
||||
mmu2.mmu_loop();
|
||||
#endif
|
||||
|
||||
// Handle Joystick jogging
|
||||
TERN_(POLL_JOG, joystick.inject_jog_moves());
|
||||
|
@ -1586,7 +1593,11 @@ void setup() {
|
|||
SETUP_RUN(stepper_driver_backward_report());
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
if (mmu3.mmu_hw_enabled) SETUP_RUN(mmu3.start());
|
||||
SETUP_RUN(mmu3.status());
|
||||
SETUP_RUN(spooljoin.initStatus());
|
||||
#elif HAS_PRUSA_MMU2
|
||||
SETUP_RUN(mmu2.init());
|
||||
#endif
|
||||
|
||||
|
|
|
@ -526,7 +526,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
|||
|
||||
switch (*special) {
|
||||
case '?': {
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
const uint8_t index = mmu2_choose_filament();
|
||||
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
|
||||
load_to_nozzle(index);
|
||||
|
@ -536,7 +536,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
|||
} break;
|
||||
|
||||
case 'x': {
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
planner.synchronize();
|
||||
const uint8_t index = mmu2_choose_filament();
|
||||
stepper.disable_extruder();
|
||||
|
@ -614,7 +614,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
|||
switch (*special) {
|
||||
case '?': {
|
||||
DEBUG_ECHOLNPGM("case ?\n");
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
uint8_t index = mmu2_choose_filament();
|
||||
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
|
||||
load_to_nozzle(index);
|
||||
|
@ -625,7 +625,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
|||
|
||||
case 'x': {
|
||||
DEBUG_ECHOLNPGM("case x\n");
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
planner.synchronize();
|
||||
uint8_t index = mmu2_choose_filament();
|
||||
stepper.disable_extruder();
|
||||
|
@ -729,7 +729,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
|||
switch (*special) {
|
||||
case '?': {
|
||||
DEBUG_ECHOLNPGM("case ?\n");
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
uint8_t index = mmu2_choose_filament();
|
||||
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
|
||||
load_to_nozzle(index);
|
||||
|
@ -740,7 +740,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
|||
|
||||
case 'x': {
|
||||
DEBUG_ECHOLNPGM("case x\n");
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
planner.synchronize();
|
||||
uint8_t index = mmu2_choose_filament();
|
||||
stepper.disable_extruder();
|
||||
|
|
73
Marlin/src/feature/mmu3/SpoolJoin.cpp
Normal file
73
Marlin/src/feature/mmu3/SpoolJoin.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* SpoolJoin.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "SpoolJoin.h"
|
||||
#include "../../module/settings.h"
|
||||
#include "../../core/language.h"
|
||||
|
||||
SpoolJoin spooljoin;
|
||||
|
||||
bool SpoolJoin::enabled; // Initialized by settings.load
|
||||
int SpoolJoin::epprom_addr; // Initialized by settings.load
|
||||
uint8_t SpoolJoin::currentMMUSlot;
|
||||
|
||||
SpoolJoin::SpoolJoin() { setSlot(0); }
|
||||
|
||||
void SpoolJoin::initStatus() {
|
||||
// Useful information to see during bootup
|
||||
SERIAL_ECHOLN(F("SpoolJoin is "), enabled ? F("On") : F("Off"));
|
||||
}
|
||||
|
||||
void SpoolJoin::toggle() {
|
||||
// Toggle enabled value.
|
||||
enabled = !enabled;
|
||||
|
||||
// Following Prusa's implementation let's save the value to the EEPROM
|
||||
// TODO: Move to settings.cpp
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(epprom_addr, enabled);
|
||||
persistentStore.access_finish();
|
||||
settings.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SpoolJoin::isEnabled() { return enabled; }
|
||||
|
||||
void SpoolJoin::setSlot(const uint8_t slot) { currentMMUSlot = slot; }
|
||||
|
||||
uint8_t SpoolJoin::nextSlot() {
|
||||
SERIAL_ECHOPGM("SpoolJoin: ", currentMMUSlot);
|
||||
if (++currentMMUSlot >= 4) currentMMUSlot = 0;
|
||||
SERIAL_ECHOLNPGM(" -> ", currentMMUSlot);
|
||||
return currentMMUSlot;
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
72
Marlin/src/feature/mmu3/SpoolJoin.h
Normal file
72
Marlin/src/feature/mmu3/SpoolJoin.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* SpoolJoin.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// See documentation here: https://help.prusa3d.com/article/spooljoin-mmu2s_134252
|
||||
|
||||
class SpoolJoin {
|
||||
public:
|
||||
SpoolJoin();
|
||||
|
||||
enum class EEPROM : uint8_t {
|
||||
Unknown, //!< SpoolJoin is unknown while printer is booting up
|
||||
Enabled, //!< SpoolJoin is enabled in EEPROM
|
||||
Disabled, //!< SpoolJoin is disabled in EEPROM
|
||||
Empty = 0xFF //!< EEPROM has not been set before and all bits are 1 (0xFF) - either a new printer or user erased the memory
|
||||
};
|
||||
|
||||
// @brief Contrary to Prusa's implementation we store the enabled status in a variable
|
||||
static int epprom_addr;
|
||||
static bool enabled;
|
||||
|
||||
// @brief Called when EEPROM is ready to be read
|
||||
static void initStatus();
|
||||
|
||||
// @brief Toggle SpoolJoin
|
||||
static void toggle();
|
||||
|
||||
// @brief Check if SpoolJoin is enabled
|
||||
// @return true if enabled, false if disabled
|
||||
static bool isEnabled();
|
||||
|
||||
// @brief Update the saved MMU slot number so SpoolJoin can determine the next slot to use
|
||||
// @param slot number of the slot to set
|
||||
static void setSlot(const uint8_t slot);
|
||||
|
||||
// @brief Fetch the next slot number (0 to 4).
|
||||
// When filament slot 4 is depleted, the next slot should be 0.
|
||||
// @return the next slot (0 to 4)
|
||||
static uint8_t nextSlot();
|
||||
|
||||
private:
|
||||
static uint8_t currentMMUSlot; //!< Currently used slot (0 to 4)
|
||||
};
|
||||
|
||||
extern SpoolJoin spooljoin;
|
1185
Marlin/src/feature/mmu3/mmu2.cpp
Normal file
1185
Marlin/src/feature/mmu3/mmu2.cpp
Normal file
File diff suppressed because it is too large
Load diff
419
Marlin/src/feature/mmu3/mmu2.h
Normal file
419
Marlin/src/feature/mmu3/mmu2.h
Normal file
|
@ -0,0 +1,419 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2.h
|
||||
*/
|
||||
|
||||
#include "mmu2_state.h"
|
||||
#include "mmu2_marlin.h"
|
||||
|
||||
#include "mmu2_protocol_logic.h"
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
typedef float feedRate_t;
|
||||
#else
|
||||
//#include <atomic>
|
||||
#endif
|
||||
|
||||
struct E_Step {
|
||||
float extrude; //!< extrude distance in mm
|
||||
float feedRate; //!< feed rate in mm/s
|
||||
};
|
||||
|
||||
static constexpr E_Step ramming_sequence[] PROGMEM = { MMU2_RAMMING_SEQUENCE };
|
||||
static constexpr E_Step load_to_nozzle_sequence[] PROGMEM = { MMU2_LOAD_TO_NOZZLE_SEQUENCE };
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// general MMU setup for MK3
|
||||
enum : uint8_t {
|
||||
FILAMENT_UNKNOWN = 0xFFU
|
||||
};
|
||||
|
||||
struct Version {
|
||||
uint8_t major, minor, build;
|
||||
};
|
||||
|
||||
// Top-level interface between Logic and Marlin.
|
||||
// Intentionally named MMU3 to be (almost) a drop-in replacement for the previous implementation.
|
||||
// Most of the public methods share the original naming convention as well.
|
||||
class MMU3 {
|
||||
public:
|
||||
MMU3();
|
||||
|
||||
// Powers ON the MMU, then initializes the UART and protocol logic
|
||||
void start();
|
||||
|
||||
// Stops the protocol logic, closes the UART, powers OFF the MMU
|
||||
void stop();
|
||||
|
||||
// Serial output of MMU state
|
||||
void status();
|
||||
|
||||
xState state() const { return _state; }
|
||||
|
||||
bool enabled() const { mmu_hw_enabled = state() == xState::Active; return mmu_hw_enabled; }
|
||||
|
||||
// Different levels of resetting the MMU
|
||||
enum ResetForm : uint8_t {
|
||||
Software = 0, //!< sends a X0 command into the MMU, the MMU will watchdog-reset itself
|
||||
ResetPin = 1, //!< trigger the reset pin of the MMU
|
||||
CutThePower = 2, //!< power off and power on (that includes +5V and +24V power lines)
|
||||
EraseEEPROM = 42, //!< erase MMU EEPROM and then perform a software reset
|
||||
};
|
||||
|
||||
// Saved print state on error.
|
||||
enum SavedState : uint8_t {
|
||||
None = 0, // No state saved.
|
||||
ParkExtruder = 1, // The extruder was parked.
|
||||
Cooldown = 2, // The extruder was allowed to cool.
|
||||
CooldownPending = 4,
|
||||
};
|
||||
|
||||
// Source of operation error
|
||||
enum ErrorSource : uint8_t {
|
||||
ErrorSourcePrinter = 0,
|
||||
ErrorSourceMMU = 1,
|
||||
ErrorSourceNone = 0xFF,
|
||||
};
|
||||
|
||||
// Tune value in MMU registers as a way to recover from errors
|
||||
// e.g. Idler Stallguard threshold
|
||||
void tune();
|
||||
|
||||
// Perform a reset of the MMU
|
||||
// @param level physical form of the reset
|
||||
void reset(ResetForm level);
|
||||
|
||||
// Power off the MMU (cut the power)
|
||||
void powerOff();
|
||||
|
||||
// Power on the MMU
|
||||
void powerOn();
|
||||
|
||||
// Read from a MMU register (See gcode M707)
|
||||
// @param address Address of register in hexidecimal
|
||||
// @return true upon success
|
||||
bool readRegister(uint8_t address);
|
||||
|
||||
// Write from a MMU register (See gcode M708)
|
||||
// @param address Address of register in hexidecimal
|
||||
// @param data Data to write to register
|
||||
// @return 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.
|
||||
void mmu_loop();
|
||||
|
||||
// The main MMU command - select a different slot
|
||||
// @param slot of the slot to be selected
|
||||
// @return false if the operation cannot be performed (Stopped)
|
||||
bool tool_change(uint8_t slot);
|
||||
|
||||
// Handling of special Tx, Tc, T? commands
|
||||
bool tool_change(char code, uint8_t slot);
|
||||
|
||||
// Unload of filament in collaboration with the MMU.
|
||||
// That includes rotating the printer's extruder in order to release filament.
|
||||
// @return false if the operation cannot be performed (Stopped or cold extruder)
|
||||
bool unload();
|
||||
|
||||
// Load (insert) filament just into the MMU (not into printer's nozzle)
|
||||
// @return false if the operation cannot be performed (Stopped)
|
||||
bool load_to_feeder(uint8_t slot);
|
||||
|
||||
// Load (push) filament from the MMU into the printer's nozzle
|
||||
// @return false if the operation cannot be performed (Stopped or cold extruder)
|
||||
bool load_to_nozzle(uint8_t slot);
|
||||
|
||||
// Move MMU's selector aside and push the selected filament forward.
|
||||
// Usable for improving filament's tip or pulling the remaining piece of filament out completely.
|
||||
bool eject_filament(uint8_t slot, bool enableFullScreenMsg=true);
|
||||
|
||||
// Issue a Cut command into the MMU
|
||||
// Requires unloaded filament from the printer (obviously)
|
||||
// @return false if the operation cannot be performed (Stopped)
|
||||
bool cut_filament(uint8_t slot, bool enableFullScreenMsg=true);
|
||||
|
||||
// Issue a planned request for statistics data from MMU
|
||||
void get_statistics();
|
||||
|
||||
// Issue a Try-Load command
|
||||
// It behaves very similarly like a ToolChange, but it doesn't load the filament
|
||||
// all the way down to the nozzle. The sole purpose of this operation
|
||||
// is to check, that the filament will be ready for printing.
|
||||
// @param slot index of slot to be tested
|
||||
// @return true
|
||||
bool loading_test(uint8_t slot);
|
||||
|
||||
// @return the active filament slot index (0-4) or 0xff in case of no active tool
|
||||
uint8_t get_current_tool() const;
|
||||
|
||||
// @return The filament slot index (0 to 4) that will be loaded next, 0xff in case of no active tool change
|
||||
uint8_t get_tool_change_tool() const;
|
||||
|
||||
bool set_filament_type(uint8_t slot, uint8_t type);
|
||||
|
||||
// Issue a "button" click into the MMU - to be used from Error screens of the MMU
|
||||
// to select one of the 3 possible options to resolve the issue
|
||||
void button(uint8_t index);
|
||||
|
||||
// Issue an explicit "homing" command into the MMU
|
||||
void home(uint8_t mode);
|
||||
|
||||
// @return current state of FINDA (true=filament present, false=filament not present)
|
||||
bool findaDetectsFilament() const { return logic.findaPressed(); }
|
||||
|
||||
uint16_t totalFailStatistics() const { return logic.FailStatistics(); }
|
||||
|
||||
// @return Current error code
|
||||
ErrorCode mmuCurrentErrorCode() const { return logic.Error(); }
|
||||
|
||||
// @return Command in progress
|
||||
uint8_t getCommandInProgress() const { return logic.CommandInProgress(); }
|
||||
|
||||
// @return Last error source
|
||||
ErrorSource mmuLastErrorSource() const { return lastErrorSource; }
|
||||
|
||||
// @return Last error code
|
||||
ErrorCode getLastErrorCode() const { return lastErrorCode; }
|
||||
|
||||
// @return the version of the connected MMU FW.
|
||||
// In the future we'll return the trully detected FW version
|
||||
Version getMMUFWVersion() const {
|
||||
if (state() == xState::Active) {
|
||||
return { logic.mmuFwVersionMajor(), logic.mmuFwVersionMinor(), logic.mmuFwVersionRevision() };
|
||||
}
|
||||
else {
|
||||
return { 0, 0, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// Method to read-only mmu_print_saved
|
||||
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
|
||||
// @return true if auto-retry is ongoing, false when retry is unavailable or retry attempts are all used up
|
||||
bool retryIfPossible(const ErrorCode ec);
|
||||
|
||||
// @return count for toolchange in current print
|
||||
uint16_t toolChangeCounter() const { return toolchange_counter; }
|
||||
|
||||
// Set toolchange counter to zero
|
||||
void resetToolChangeCounter() { toolchange_counter = 0; }
|
||||
|
||||
uint16_t tmcFailures() const { return _tmcFailures; }
|
||||
void incrementTMCFailures() { ++_tmcFailures; }
|
||||
void resetTMCFailures() { _tmcFailures = 0; }
|
||||
|
||||
// Retrieve cached value parsed from readRegister()
|
||||
// or using M707
|
||||
uint16_t getLastReadRegisterValue() const {
|
||||
return lastReadRegisterValue;
|
||||
}
|
||||
void invokeErrorScreen(const ErrorCode ec) {
|
||||
if (logic.CommandInProgress()) return; // MMU must not be busy
|
||||
if (lastErrorCode == ec) return; // The error code is not a duplicate
|
||||
if (mmuCurrentErrorCode() == ErrorCode::OK) { // The protocol must not be in error state
|
||||
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
|
||||
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
|
||||
Buttons getPrinterButtonOperation() {
|
||||
return printerButtonOperation;
|
||||
}
|
||||
|
||||
void clearPrinterButtonOperation() {
|
||||
printerButtonOperation = Buttons::NoButton;
|
||||
}
|
||||
|
||||
static uint8_t cutter_mode; // mode 0:disabled | 1:enabled | 2:always (EXPERIMENTAL)
|
||||
static int cutter_mode_addr; // EEPROM addr for cutter enabled setting
|
||||
static uint8_t stealth_mode; // stealth mode
|
||||
static int stealth_mode_addr; // EEPROM addr for stealth_mode setting
|
||||
static bool mmu_hw_enabled; // MMU hardware can be Enabled/Disabled
|
||||
// with the M709 S0 or M709 S1 commands
|
||||
// and the last state is stored in the
|
||||
// EEPROM
|
||||
|
||||
static int mmu_hw_enabled_addr; // EEPROM addr for mmu_hw_enabled
|
||||
|
||||
bool e_active();
|
||||
|
||||
#ifndef UNITTEST
|
||||
private:
|
||||
#endif
|
||||
|
||||
// Perform software self-reset of the MMU (sends an X0 command)
|
||||
void resetX0();
|
||||
|
||||
// Perform software self-reset of the MMU + erase its EEPROM (sends X2a command)
|
||||
void resetX42();
|
||||
|
||||
// Trigger reset pin of the MMU
|
||||
void triggerResetPin();
|
||||
|
||||
// Perform power cycle of the MMU (cold boot)
|
||||
// Please note this is a blocking operation (sleeps for some time inside while doing the power cycle)
|
||||
void powerCycle();
|
||||
|
||||
// Stop the communication, but keep the MMU powered on (for scenarios with incorrect FW version)
|
||||
void stopKeepPowered();
|
||||
|
||||
// Along with the mmu_loop method, this loops until a response from the MMU is received and acts upon.
|
||||
// In case of an error, it parks the print head and turns off nozzle heating
|
||||
// @return false if the command could not have been completed (MMU interrupted)
|
||||
[[nodiscard]] bool manage_response(const bool move_axes, const bool turn_off_nozzle);
|
||||
|
||||
// The inner private implementation of mmu_loop()
|
||||
// which is NOT (!!!) recursion-guarded. Use caution - but we do need it during waiting for hotend resume to keep comms alive!
|
||||
// @param reportErrors true if Errors should raise MMU Error screen, false otherwise
|
||||
void mmu_loop_inner(bool reportErrors);
|
||||
|
||||
// Performs one step of the protocol logic state machine
|
||||
// and reports progress and errors if needed to attached ExtUIs.
|
||||
// Updates the global state of MMU (Active/Connecting/Stopped) at runtime, see @ref State
|
||||
// @param reportErrors true if Errors should raise MMU Error screen, false otherwise
|
||||
StepStatus logicStep(bool reportErrors);
|
||||
|
||||
void filament_ramming();
|
||||
void execute_extruder_sequence(const E_Step *sequence, uint8_t steps);
|
||||
void execute_load_to_nozzle_sequence();
|
||||
|
||||
// Reports an error into attached ExtUIs
|
||||
// @param ec error code, see ErrorCode
|
||||
// @param res reporter error source, is either Printer (0) or MMU (1)
|
||||
void reportError(ErrorCode ec, ErrorSource res);
|
||||
|
||||
// Reports progress of operations into attached ExtUIs
|
||||
// @param pc progress code, see ProgressCode
|
||||
void reportProgress(ProgressCode pc);
|
||||
|
||||
// Responds to a change of MMU's progress
|
||||
// - plans additional steps, e.g. starts the E-motor after fsensor trigger
|
||||
// The function is quite complex, because it needs to handle asynchronnous
|
||||
// progress and error reports coming from the MMU without an explicit command
|
||||
// - typically after MMU's start or after some HW issue on the MMU.
|
||||
// It must ensure, that calls to @ref reportProgress and/or @ref reportError are
|
||||
// only executed after @ref BeginReport has been called first.
|
||||
void onMMUProgressMsg(ProgressCode pc);
|
||||
// Progress code changed - act accordingly
|
||||
void onMMUProgressMsgChanged(ProgressCode pc);
|
||||
// Repeated calls when progress code remains the same
|
||||
void onMMUProgressMsgSame(ProgressCode pc);
|
||||
|
||||
// @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);
|
||||
|
||||
// Save print and park the print head
|
||||
void saveAndPark(bool move_axes);
|
||||
|
||||
// Resume hotend temperature, if it was cooled. Safe to call if we aren't saved.
|
||||
void resumeHotendTemp();
|
||||
|
||||
// Resume position, if the extruder was parked. Safe to all if state was not saved.
|
||||
void resumeUnpark();
|
||||
|
||||
// Check for any button/user input coming from the printer's UI
|
||||
void checkUserInput();
|
||||
|
||||
// @brief Check whether to trigger a FINDA runout. If triggered this function will call M600 AUTO
|
||||
// if SpoolJoin is enabled, otherwise M600 is called without AUTO which will prompt the user
|
||||
// for the next filament slot to use
|
||||
void checkFINDARunout();
|
||||
|
||||
// Entry check of all external commands.
|
||||
// It can wait until the MMU becomes ready.
|
||||
// Optionally, it can also emit/display an error screen and the user can decide what to do next.
|
||||
// @return false if the MMU is not ready to perform the command (for whatever reason)
|
||||
bool waitForMMUReady();
|
||||
|
||||
// After MMU completes a tool-change command
|
||||
// the printer will push the filament by a constant distance. If the Fsensor untriggers
|
||||
// at any moment the test fails. Else the test passes, and the E-motor retracts the
|
||||
// filament back to its original position.
|
||||
// @return false if test fails, true otherwise
|
||||
bool verifyFilamentEnteredPTFE();
|
||||
|
||||
// Common processing of pushing filament into the extruder - shared by tool_change, load_to_nozzle and probably others
|
||||
void toolChangeCommon(uint8_t slot);
|
||||
bool toolChangeCommonOnce(uint8_t slot);
|
||||
|
||||
void helpUnloadToFinda();
|
||||
void unloadInner();
|
||||
void cutFilamentInner(uint8_t slot);
|
||||
|
||||
void setCurrentTool(uint8_t ex);
|
||||
|
||||
ProtocolLogic logic; //!< implementation of the protocol logic layer
|
||||
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;
|
||||
int16_t resume_hotend_temp;
|
||||
|
||||
ProgressCode lastProgressCode = ProgressCode::OK;
|
||||
ErrorCode lastErrorCode = ErrorCode::MMU_NOT_RESPONDING;
|
||||
ErrorSource lastErrorSource = ErrorSource::ErrorSourceNone;
|
||||
Buttons lastButton = Buttons::NoButton;
|
||||
uint16_t lastReadRegisterValue = 0;
|
||||
Buttons printerButtonOperation = Buttons::NoButton;
|
||||
|
||||
StepStatus logicStepLastStatus;
|
||||
|
||||
enum xState _state;
|
||||
|
||||
uint8_t mmu_print_saved;
|
||||
bool loadFilamentStarted;
|
||||
bool unloadFilamentStarted;
|
||||
|
||||
uint16_t toolchange_counter;
|
||||
uint16_t _tmcFailures;
|
||||
};
|
||||
|
||||
} // MMU3
|
||||
|
||||
// following Marlin's way of doing stuff - one and only instance of MMU implementation in the code base
|
||||
// + avoiding buggy singletons on the AVR platform
|
||||
extern MMU3::MMU3 mmu3;
|
53
Marlin/src/feature/mmu3/mmu2_crc.cpp
Normal file
53
Marlin/src/feature/mmu3/mmu2_crc.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_crc.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2_crc.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <util/crc16.h>
|
||||
#endif
|
||||
|
||||
namespace modules {
|
||||
|
||||
namespace crc {
|
||||
|
||||
uint8_t CRC8::CCITT_update(uint8_t crc, uint8_t b) {
|
||||
#ifdef __AVR__
|
||||
return _crc8_ccitt_update(crc, b);
|
||||
#else
|
||||
return CCITT_updateCX(crc, b);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace crc
|
||||
|
||||
} // namespace modules
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
73
Marlin/src/feature/mmu3/mmu2_crc.h
Normal file
73
Marlin/src/feature/mmu3/mmu2_crc.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_crc.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace modules {
|
||||
|
||||
// prevent silly indenting of the whole file
|
||||
|
||||
// Contains all the necessary functions for computation of CRC
|
||||
namespace crc {
|
||||
|
||||
class CRC8 {
|
||||
public:
|
||||
// Compute/update CRC8 CCIIT from 8bits.
|
||||
// Details: https://www.nongnu.org/avr-libc/user-manual/group__util__crc.html
|
||||
static uint8_t CCITT_update(uint8_t crc, uint8_t b);
|
||||
|
||||
static constexpr uint8_t CCITT_updateCX(uint8_t crc, uint8_t b) {
|
||||
uint8_t data = crc ^ b;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if ((data & 0x80U) != 0) {
|
||||
data <<= 1U;
|
||||
data ^= 0x07U;
|
||||
}
|
||||
else {
|
||||
data <<= 1U;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Compute/update CRC8 CCIIT from 16bits (convenience wrapper)
|
||||
static constexpr uint8_t CCITT_updateW(uint8_t crc, uint16_t w) {
|
||||
union U {
|
||||
uint8_t b[2];
|
||||
uint16_t w;
|
||||
explicit constexpr inline U(uint16_t w)
|
||||
: w(w) {}
|
||||
}
|
||||
u(w);
|
||||
return CCITT_updateCX(CCITT_updateCX(crc, u.b[0]), u.b[1]);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace crc
|
||||
|
||||
|
||||
} // namespace modules
|
376
Marlin/src/feature/mmu3/mmu2_error_converter.cpp
Normal file
376
Marlin/src/feature/mmu3/mmu2_error_converter.cpp
Normal file
|
@ -0,0 +1,376 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_error_converter.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../core/language.h"
|
||||
#include "mmu2_error_converter.h"
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/errors_list.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
static ButtonOperations buttonSelectedOperation = ButtonOperations::NoOperation;
|
||||
|
||||
// we don't have a constexpr find_if in C++17/STL yet
|
||||
template <class InputIt, class UnaryPredicate>
|
||||
constexpr InputIt find_if_cx(InputIt first, InputIt last, UnaryPredicate p) {
|
||||
for (; first != last; ++first) {
|
||||
if (p(*first)) return first;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
// Making a constexpr FindError should instruct the compiler to optimize the
|
||||
// PrusaErrorCodeIndex in such a way that no searching will ever be done at
|
||||
// runtime. A call to FindError then compiles to a single instruction even on
|
||||
// the AVR.
|
||||
// static constexpr uint8_t FindErrorIndex(uint16_t pec) {
|
||||
static uint8_t FindErrorIndex(uint16_t pec) {
|
||||
constexpr uint16_t errorCodesSize = sizeof(errorCodes) / sizeof(errorCodes[0]);
|
||||
constexpr const auto *errorCodesEnd = errorCodes + errorCodesSize;
|
||||
const auto *i = find_if_cx(errorCodes, errorCodesEnd, [pec](uint16_t ed) {
|
||||
return ed == pec;
|
||||
});
|
||||
return (i != errorCodesEnd) ? (i - errorCodes) : (errorCodesSize - 1);
|
||||
}
|
||||
|
||||
// check that the searching algoritm works
|
||||
// static_assert( FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_TRIGGER) == 0);
|
||||
// static_assert( FindErrorIndex(ERR_MECHANICAL_FINDA_FILAMENT_STUCK) == 1);
|
||||
// static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER) == 2);
|
||||
// static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK) == 3);
|
||||
|
||||
constexpr ErrorCode operator&(ErrorCode a, ErrorCode b) {
|
||||
return (ErrorCode)((uint16_t)a & (uint16_t)b);
|
||||
}
|
||||
|
||||
constexpr bool ContainsBit(ErrorCode ec, ErrorCode mask) {
|
||||
return (uint16_t)ec & (uint16_t)mask;
|
||||
}
|
||||
|
||||
uint8_t PrusaErrorCodeIndex(const ErrorCode ec) {
|
||||
switch (ec) {
|
||||
case ErrorCode::FINDA_DIDNT_SWITCH_ON:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_TRIGGER);
|
||||
case ErrorCode::FINDA_DIDNT_SWITCH_OFF:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FINDA_FILAMENT_STUCK);
|
||||
case ErrorCode::FSENSOR_DIDNT_SWITCH_ON:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER);
|
||||
case ErrorCode::FSENSOR_DIDNT_SWITCH_OFF:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK);
|
||||
case ErrorCode::FSENSOR_TOO_EARLY:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_TOO_EARLY);
|
||||
case ErrorCode::FINDA_FLICKERS:
|
||||
return FindErrorIndex(ERR_MECHANICAL_INSPECT_FINDA);
|
||||
case ErrorCode::LOAD_TO_EXTRUDER_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED);
|
||||
case ErrorCode::FILAMENT_EJECTED:
|
||||
return FindErrorIndex(ERR_SYSTEM_FILAMENT_EJECTED);
|
||||
case ErrorCode::FILAMENT_CHANGE:
|
||||
return FindErrorIndex(ERR_SYSTEM_FILAMENT_CHANGE);
|
||||
|
||||
case ErrorCode::STALLED_PULLEY:
|
||||
case ErrorCode::MOVE_PULLEY_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_PULLEY_CANNOT_MOVE);
|
||||
|
||||
case ErrorCode::HOMING_SELECTOR_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_HOME);
|
||||
case ErrorCode::MOVE_SELECTOR_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_MOVE);
|
||||
|
||||
case ErrorCode::HOMING_IDLER_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_HOME);
|
||||
case ErrorCode::MOVE_IDLER_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_MOVE);
|
||||
|
||||
case ErrorCode::MMU_NOT_RESPONDING:
|
||||
return FindErrorIndex(ERR_CONNECT_MMU_NOT_RESPONDING);
|
||||
case ErrorCode::PROTOCOL_ERROR:
|
||||
return FindErrorIndex(ERR_CONNECT_COMMUNICATION_ERROR);
|
||||
case ErrorCode::FILAMENT_ALREADY_LOADED:
|
||||
return FindErrorIndex(ERR_SYSTEM_FILAMENT_ALREADY_LOADED);
|
||||
case ErrorCode::INVALID_TOOL:
|
||||
return FindErrorIndex(ERR_SYSTEM_INVALID_TOOL);
|
||||
case ErrorCode::QUEUE_FULL:
|
||||
return FindErrorIndex(ERR_SYSTEM_QUEUE_FULL);
|
||||
case ErrorCode::VERSION_MISMATCH:
|
||||
return FindErrorIndex(ERR_SYSTEM_FW_UPDATE_NEEDED);
|
||||
case ErrorCode::INTERNAL:
|
||||
return FindErrorIndex(ERR_SYSTEM_FW_RUNTIME_ERROR);
|
||||
case ErrorCode::FINDA_VS_EEPROM_DISREPANCY:
|
||||
return FindErrorIndex(ERR_SYSTEM_UNLOAD_MANUALLY);
|
||||
case ErrorCode::MCU_UNDERVOLTAGE_VCC:
|
||||
return FindErrorIndex(ERR_ELECTRICAL_MMU_MCU_ERROR);
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Electrical issues which can be detected somehow.
|
||||
// Need to be placed before TMC-related errors in order to process couples of error bits between single ones
|
||||
// and to keep the code size down.
|
||||
if (ContainsBit(ec, ErrorCode::TMC_PULLEY_BIT)) {
|
||||
if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED);
|
||||
}
|
||||
else if (ContainsBit(ec, ErrorCode::TMC_SELECTOR_BIT)) {
|
||||
if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED);
|
||||
}
|
||||
else if (ContainsBit(ec, ErrorCode::TMC_IDLER_BIT)) {
|
||||
if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED);
|
||||
}
|
||||
|
||||
// TMC-related errors - multiple of these can occur at once
|
||||
// - in such a case we report the first which gets found/converted into Prusa-Error-Codes (usually the fact, that one TMC has an issue is serious enough)
|
||||
// By carefully ordering the checks here we can prioritize the errors being reported to the user.
|
||||
if (ContainsBit(ec, ErrorCode::TMC_PULLEY_BIT)) {
|
||||
if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_RESET))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR);
|
||||
}
|
||||
else if (ContainsBit(ec, ErrorCode::TMC_SELECTOR_BIT)) {
|
||||
if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_RESET))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR);
|
||||
}
|
||||
else if (ContainsBit(ec, ErrorCode::TMC_IDLER_BIT)) {
|
||||
if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_RESET))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR);
|
||||
}
|
||||
|
||||
// if nothing got caught, return a generic runtime error
|
||||
return FindErrorIndex(ERR_OTHER_UNKNOWN_ERROR);
|
||||
}
|
||||
|
||||
uint16_t PrusaErrorCode(const uint8_t i) { return (uint16_t)pgm_read_word(&errorCodes[i]); }
|
||||
|
||||
FSTR_P const PrusaErrorTitle(const uint8_t i) { return (FSTR_P const)pgm_read_ptr(&errorTitles[i]); }
|
||||
FSTR_P const PrusaErrorDesc(const uint8_t i) { return (FSTR_P const)pgm_read_ptr(&errorDescs[i]); }
|
||||
|
||||
uint8_t PrusaErrorButtons(const uint8_t i) { return pgm_read_byte(errorButtons + i); }
|
||||
|
||||
FSTR_P const PrusaErrorButtonTitle(const uint8_t bi) {
|
||||
// -1 represents the hidden NoOperation button which is not drawn in any way
|
||||
return (FSTR_P const)pgm_read_ptr(&btnOperation[bi - 1]);
|
||||
}
|
||||
|
||||
Buttons ButtonPressed(const ErrorCode ec) {
|
||||
if (buttonSelectedOperation == ButtonOperations::NoOperation || buttonSelectedOperation == ButtonOperations::MoreInfo)
|
||||
return Buttons::NoButton; // no button
|
||||
|
||||
const auto result = ButtonAvailable(ec);
|
||||
buttonSelectedOperation = ButtonOperations::NoOperation; // Reset operation
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Buttons ButtonAvailable(const ErrorCode ec) {
|
||||
uint8_t ei = PrusaErrorCodeIndex(ec);
|
||||
|
||||
// The list of responses which occur in mmu error dialogs
|
||||
// Return button index or perform some action on the MK3 by itself (like Reset MMU)
|
||||
// Based on Prusa-Error-Codes errors_list.h
|
||||
// So far hardcoded, but should be generated in the future
|
||||
switch (PrusaErrorCode(ei)) {
|
||||
case ERR_MECHANICAL_FINDA_DIDNT_TRIGGER:
|
||||
case ERR_MECHANICAL_FINDA_FILAMENT_STUCK:
|
||||
case ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER:
|
||||
case ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK:
|
||||
case ERR_MECHANICAL_FSENSOR_TOO_EARLY:
|
||||
case ERR_MECHANICAL_INSPECT_FINDA:
|
||||
case ERR_MECHANICAL_SELECTOR_CANNOT_MOVE:
|
||||
case ERR_MECHANICAL_IDLER_CANNOT_MOVE:
|
||||
case ERR_MECHANICAL_PULLEY_CANNOT_MOVE:
|
||||
case ERR_SYSTEM_UNLOAD_MANUALLY:
|
||||
switch (buttonSelectedOperation) {
|
||||
// may be allow move selector right and left in the future
|
||||
case ButtonOperations::Retry: // "Repeat action"
|
||||
return Buttons::Middle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_MECHANICAL_SELECTOR_CANNOT_HOME:
|
||||
case ERR_MECHANICAL_IDLER_CANNOT_HOME:
|
||||
switch (buttonSelectedOperation) {
|
||||
// may be allow move selector right and left in the future
|
||||
case ButtonOperations::Tune: // Tune Stallguard threshold
|
||||
return Buttons::TuneMMU;
|
||||
case ButtonOperations::Retry: // "Repeat action"
|
||||
return Buttons::Middle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED:
|
||||
case ERR_SYSTEM_FILAMENT_EJECTED:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Continue: // User solved the serious mechanical problem by hand - there is no other way around
|
||||
return Buttons::Middle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_SYSTEM_FILAMENT_CHANGE:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Load:
|
||||
return Buttons::Load;
|
||||
case ButtonOperations::Eject:
|
||||
return Buttons::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:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Continue: // "Continue"
|
||||
return Buttons::Left;
|
||||
case ButtonOperations::ResetMMU: // "Reset MMU"
|
||||
return Buttons::ResetMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR:
|
||||
case ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR:
|
||||
case ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR:
|
||||
case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR:
|
||||
case ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET:
|
||||
case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET:
|
||||
case ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET:
|
||||
|
||||
case ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR:
|
||||
case ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR:
|
||||
case ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED:
|
||||
case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED:
|
||||
case ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED:
|
||||
|
||||
case ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED:
|
||||
case ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED:
|
||||
case ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED:
|
||||
|
||||
case ERR_SYSTEM_QUEUE_FULL:
|
||||
case ERR_SYSTEM_FW_RUNTIME_ERROR:
|
||||
case ERR_ELECTRICAL_MMU_MCU_ERROR:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::ResetMMU: // "Reset MMU"
|
||||
return Buttons::ResetMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_CONNECT_MMU_NOT_RESPONDING:
|
||||
case ERR_CONNECT_COMMUNICATION_ERROR:
|
||||
case ERR_SYSTEM_FW_UPDATE_NEEDED:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::DisableMMU: // "Disable"
|
||||
return Buttons::DisableMMU;
|
||||
case ButtonOperations::ResetMMU: // "ResetMMU"
|
||||
return Buttons::ResetMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_SYSTEM_FILAMENT_ALREADY_LOADED:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Unload: // "Unload"
|
||||
return Buttons::Left;
|
||||
case ButtonOperations::Continue: // "Proceed/Continue"
|
||||
return Buttons::Right;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_SYSTEM_INVALID_TOOL:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::StopPrint: // "Stop print"
|
||||
return Buttons::StopPrint;
|
||||
case ButtonOperations::ResetMMU: // "Reset MMU"
|
||||
return Buttons::ResetMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Buttons::NoButton;
|
||||
}
|
||||
|
||||
void SetButtonResponse(ButtonOperations rsp) {
|
||||
buttonSelectedOperation = rsp;
|
||||
}
|
||||
|
||||
ButtonOperations GetButtonResponse() {
|
||||
return buttonSelectedOperation;
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
73
Marlin/src/feature/mmu3/mmu2_error_converter.h
Normal file
73
Marlin/src/feature/mmu3/mmu2_error_converter.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_error_converter.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "mmu_hw/buttons.h"
|
||||
#include "mmu_hw/error_codes.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Translates MMU3::ErrorCode into an index of Prusa-Error-Codes
|
||||
// Basically this is the way to obtain an index into all other functions in this API
|
||||
uint8_t PrusaErrorCodeIndex(const ErrorCode ec);
|
||||
|
||||
// @return pointer to a PROGMEM string representing the Title of the Prusa-Error-Codes error
|
||||
// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
FSTR_P const PrusaErrorTitle(const uint8_t i);
|
||||
|
||||
// @return pointer to a PROGMEM string representing the multi-page Description of the Prusa-Error-Codes error
|
||||
// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
FSTR_P const PrusaErrorDesc(const uint8_t i);
|
||||
|
||||
// @return the actual numerical value of the Prusa-Error-Codes error
|
||||
// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
uint16_t PrusaErrorCode(const uint8_t i);
|
||||
|
||||
// @return Btns pair of buttons for a particular Prusa-Error-Codes error
|
||||
// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
uint8_t PrusaErrorButtons(const uint8_t i);
|
||||
|
||||
// @return pointer to a PROGMEM string representing the Title of a button
|
||||
// @param i index of the error - obtained by calling PrusaErrorButtons + extracting low or high nibble from the Btns pair
|
||||
FSTR_P const PrusaErrorButtonTitle(const uint8_t bi);
|
||||
|
||||
// Sets the selected button for later pick-up by the MMU state machine.
|
||||
// Used to save the GUI selection/decoupling
|
||||
void SetButtonResponse(const ButtonOperations rsp);
|
||||
ButtonOperations GetButtonResponse();
|
||||
|
||||
// @return button index/code based on currently processed error/screen
|
||||
// Clears the "pressed" button upon exit
|
||||
Buttons ButtonPressed(const ErrorCode ec);
|
||||
|
||||
// @return button index/code based on currently processed error/screen
|
||||
// Used as a subfunction of ButtonPressed.
|
||||
// Does not clear the "pressed" button upon exit
|
||||
Buttons ButtonAvailable(const ErrorCode ec);
|
||||
|
||||
} // MMU3
|
65
Marlin/src/feature/mmu3/mmu2_fsensor.cpp
Normal file
65
Marlin/src/feature/mmu3/mmu2_fsensor.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_fsensor.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../feature/runout.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
#if HAS_FILAMENT_SENSOR
|
||||
|
||||
FSensorBlockRunout::FSensorBlockRunout() {
|
||||
runout.enabled = false; // Suppress filament runouts while loading filament.
|
||||
//fsensor.setAutoLoadEnabled(false); //suppress filament autoloads while loading filament.
|
||||
}
|
||||
|
||||
FSensorBlockRunout::~FSensorBlockRunout() {
|
||||
//fsensor.settings_init(); // restore filament runout state.
|
||||
runout.reset();
|
||||
runout.enabled = true;
|
||||
//SERIAL_ECHOLNPGM("FSUnBlockRunout");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
FSensorBlockRunout::FSensorBlockRunout() { }
|
||||
FSensorBlockRunout::~FSensorBlockRunout() { }
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
FilamentState WhereIsFilament() {
|
||||
//return fsensor.getFilamentPresent() ? FilamentState::AT_FSENSOR : FilamentState::NOT_PRESENT;
|
||||
return FILAMENT_PRESENT() ? FilamentState::AT_FSENSOR : FilamentState::NOT_PRESENT;
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
55
Marlin/src/feature/mmu3/mmu2_fsensor.h
Normal file
55
Marlin/src/feature/mmu3/mmu2_fsensor.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_fsensor.h
|
||||
*/
|
||||
|
||||
#include "../../core/macros.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define FILAMENT_PRESENT() (READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE)
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Can be used to block printer's filament sensor handling - to avoid errorneous injecting of M600
|
||||
// while doing a toolchange with the MMU
|
||||
// In case of "no filament sensor" these methods default to an empty implementation
|
||||
class FSensorBlockRunout {
|
||||
public:
|
||||
FSensorBlockRunout();
|
||||
~FSensorBlockRunout();
|
||||
};
|
||||
|
||||
// Possible states of filament from the perspective of presence in various parts of the printer
|
||||
// Beware, the numeric codes are important and sent into the MMU
|
||||
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
|
||||
UNAVAILABLE = 3 //!< Sensor not available (likely not connected due broken cable)
|
||||
};
|
||||
|
||||
FilamentState WhereIsFilament();
|
||||
|
||||
} // MMU3
|
47
Marlin/src/feature/mmu3/mmu2_log.cpp
Normal file
47
Marlin/src/feature/mmu3/mmu2_log.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_log.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2_log.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
void LogEchoEvent_P(PGM_P const pstr) {
|
||||
SERIAL_ECHO_START(); // @@TODO Decide MMU errors on serial line
|
||||
SERIAL_MMU2();
|
||||
SERIAL_ECHOLN_P(pstr);
|
||||
}
|
||||
|
||||
void LogErrorEvent_P(PGM_P const pstr) {
|
||||
LogEchoEvent_P(pstr);
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
82
Marlin/src/feature/mmu3/mmu2_log.h
Normal file
82
Marlin/src/feature/mmu3/mmu2_log.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_log.h
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
|
||||
// @param msg pointer to a string in PROGMEM
|
||||
// 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(PGM_P const pstr);
|
||||
inline void LogErrorEvent(FSTR_P const fstr) { LogErrorEvent_P(FTOP(fstr)); }
|
||||
|
||||
// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
|
||||
// @param msg pointer to a string in PROGMEM
|
||||
// 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 LogEchoEvent_P(PGM_P const pstr);
|
||||
inline void LogEchoEvent(FSTR_P const fstr) { LogEchoEvent_P(FTOP(fstr)); }
|
||||
|
||||
} // MMU3
|
||||
|
||||
#ifndef UNITTEST
|
||||
|
||||
#define SERIAL_MMU2() { SERIAL_ECHO(F("MMU3:")); }
|
||||
|
||||
#define MMU2_ECHO_MSGLN(S) do { \
|
||||
SERIAL_ECHO_START(); \
|
||||
SERIAL_MMU2(); \
|
||||
SERIAL_ECHOLN(S); \
|
||||
}while(0)
|
||||
#define MMU2_ERROR_MSGLN(S) MMU2_ECHO_MSGLN(S) //! @todo Decide MMU errors on serial line
|
||||
#define MMU2_ECHO_MSGRPGM(S) do { \
|
||||
SERIAL_ECHO_START(); \
|
||||
SERIAL_MMU2(); \
|
||||
SERIAL_ECHO_P(S); \
|
||||
}while(0)
|
||||
#define MMU2_ERROR_MSGRPGM(S) MMU2_ECHO_MSGRPGM(S) //! @todo Decide MMU errors on serial line
|
||||
#define MMU2_ECHO_MSG(S) do { \
|
||||
SERIAL_ECHO_START(); \
|
||||
SERIAL_MMU2(); \
|
||||
SERIAL_ECHO(S); \
|
||||
}while(0)
|
||||
#define MMU2_ERROR_MSG(S) MMU2_ECHO_MSG(S) //! @todo Decide MMU errors on serial line
|
||||
|
||||
#else // UNITTEST
|
||||
|
||||
#include "stubs/stub_interfaces.h"
|
||||
#define MMU2_ECHO_MSGLN(S) marlinLogSim.AppendLine(S)
|
||||
#define MMU2_ERROR_MSGLN(S) marlinLogSim.AppendLine(S)
|
||||
#define MMU2_ECHO_MSGRPGM(S) /* marlinLogSim.AppendLine(S) */
|
||||
#define MMU2_ERROR_MSGRPGM(S) /* marlinLogSim.AppendLine(S) */
|
||||
#define SERIAL_ECHOLNPGM(S) /* marlinLogSim.AppendLine(S) */
|
||||
#define SERIAL_ECHOPGM(S) /* */
|
||||
#define SERIAL_ECHOLN(S) /* marlinLogSim.AppendLine(S) */
|
||||
|
||||
#endif // UNITTEST
|
74
Marlin/src/feature/mmu3/mmu2_marlin.h
Normal file
74
Marlin/src/feature/mmu3/mmu2_marlin.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_marlin.h
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// This interface separates Marlin1/Marlin2 from the MMU top logic layer.
|
||||
// - Unify implementation among MK3 and Buddy FW
|
||||
// - Enable unit testing of MMU top layer
|
||||
|
||||
void extruder_move(const_float_t distance, const_float_t feedRate_mm_s, const bool sync=true);
|
||||
void extruder_schedule_turning(const_float_t feedRate_mm_s);
|
||||
|
||||
float move_raise_z(const_float_t delta);
|
||||
|
||||
void planner_abort_queued_moves();
|
||||
void planner_synchronize();
|
||||
bool planner_any_moves();
|
||||
float stepper_get_machine_position_E_mm();
|
||||
float planner_get_current_position_E();
|
||||
void planner_set_current_position_E(float e);
|
||||
xyz_pos_t 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);
|
||||
void marlin_refresh_print_state_in_ram();
|
||||
void marlin_clear_print_state_in_ram();
|
||||
void marlin_stop_and_save_print_to_ram();
|
||||
|
||||
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 xy_are_trusted();
|
||||
|
||||
} // MMU3
|
190
Marlin/src/feature/mmu3/mmu2_marlin1.cpp
Normal file
190
Marlin/src/feature/mmu3/mmu2_marlin1.cpp
Normal file
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_marlin1.cpp
|
||||
* MK3 / Marlin1 implementation of support routines for the MMU3
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
#include "../../module/stepper.h"
|
||||
#include "../../module/planner.h"
|
||||
#include "../../module/temperature.h"
|
||||
|
||||
#include "../../feature/pause.h"
|
||||
#include "../../libs/nozzle.h"
|
||||
#include "mmu2_marlin.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
static void planner_line_to_current_position(float feedRate_mm_s) {
|
||||
line_to_current_position(feedRate_mm_s);
|
||||
}
|
||||
|
||||
static void planner_line_to_current_position_sync(float feedRate_mm_s) {
|
||||
planner_line_to_current_position(feedRate_mm_s);
|
||||
planner_synchronize();
|
||||
}
|
||||
|
||||
void extruder_move(const_float_t delta, const_float_t feedRate_mm_s, const bool sync/*=true*/) {
|
||||
current_position.e += delta / planner.e_factor[active_extruder];
|
||||
planner_line_to_current_position(feedRate_mm_s);
|
||||
if (sync) planner.synchronize();
|
||||
}
|
||||
|
||||
float move_raise_z(const_float_t delta) {
|
||||
//return raise_z(delta);
|
||||
xyze_pos_t current_position_before = current_position;
|
||||
do_z_clearance_by(delta);
|
||||
return (current_position - current_position_before).z;
|
||||
}
|
||||
|
||||
void planner_abort_queued_moves() {
|
||||
//planner_abort_hard();
|
||||
quickstop_stepper();
|
||||
|
||||
// Unblock the planner. This should be safe in the
|
||||
// toolchange context. Currently we are mainly aborting
|
||||
// excess E-moves after detecting filament during toolchange.
|
||||
// If a MMU error is reported, the planner must be unblocked
|
||||
// as well so the extruder can be parked safely.
|
||||
//planner_aborted = false;
|
||||
// eoyilmaz: we don't need this part, the print is not aborted
|
||||
}
|
||||
|
||||
void planner_synchronize() {
|
||||
planner.synchronize();
|
||||
}
|
||||
|
||||
bool planner_any_moves() {
|
||||
return planner.has_blocks_queued();
|
||||
}
|
||||
|
||||
float planner_get_machine_position_E_mm() {
|
||||
return current_position.e;
|
||||
}
|
||||
|
||||
float stepper_get_machine_position_E_mm() {
|
||||
return planner.get_axis_position_mm(E_AXIS);
|
||||
}
|
||||
|
||||
float planner_get_current_position_E() {
|
||||
return current_position.e;
|
||||
}
|
||||
|
||||
void planner_set_current_position_E(float e) {
|
||||
current_position.e = e;
|
||||
}
|
||||
|
||||
xyz_pos_t planner_current_position() {
|
||||
return xyz_pos_t(current_position);
|
||||
}
|
||||
|
||||
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;
|
||||
planner_line_to_current_position_sync(feedRate_mm_s);
|
||||
}
|
||||
|
||||
void motion_do_blocking_move_to_z(float z, float feedRate_mm_s) {
|
||||
current_position[Z_AXIS] = z;
|
||||
planner_line_to_current_position_sync(feedRate_mm_s);
|
||||
}
|
||||
|
||||
void nozzle_park() {
|
||||
#if ANY(NOZZLE_CLEAN_FEATURE, NOZZLE_PARK_FEATURE)
|
||||
#if ALL(ADVANCED_PAUSE_FEATURE)
|
||||
xyz_pos_t park_point = NOZZLE_PARK_POINT;
|
||||
nozzle.park(0, park_point);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool marlin_printingIsActive() { return printingIsActive(); }
|
||||
|
||||
void marlin_manage_heater() { thermalManager.task(); }
|
||||
|
||||
void marlin_manage_inactivity(const bool b) { idle(b); }
|
||||
|
||||
void marlin_idle(bool b) {
|
||||
thermalManager.task();
|
||||
idle(b);
|
||||
}
|
||||
|
||||
void marlin_refresh_print_state_in_ram() {
|
||||
// refresh_print_state_in_ram();
|
||||
// TODO: I don't see a comparable implementation in Marlin.
|
||||
}
|
||||
|
||||
void marlin_clear_print_state_in_ram() {
|
||||
// clear_print_state_in_ram();
|
||||
// TODO: I don't see a comparable implementation in Marlin.
|
||||
}
|
||||
|
||||
void marlin_stop_and_save_print_to_ram() {
|
||||
// stop_and_save_print_to_ram(0,0);
|
||||
#if ENABLED(ADVANCED_PAUSE_FEATURE)
|
||||
constexpr xyz_pos_t park_point = NOZZLE_PARK_POINT;
|
||||
pause_print(0, park_point);
|
||||
#endif
|
||||
}
|
||||
|
||||
int16_t thermal_degTargetHotend() {
|
||||
return thermalManager.degTargetHotend(0);
|
||||
}
|
||||
|
||||
int16_t thermal_degHotend() {
|
||||
return thermalManager.degHotend(0);
|
||||
}
|
||||
|
||||
void thermal_setExtrudeMintemp(int16_t t) {
|
||||
thermalManager.extrude_min_temp = t;
|
||||
}
|
||||
|
||||
void thermal_setTargetHotend(int16_t t) {
|
||||
thermalManager.setTargetHotend(t, 0);
|
||||
}
|
||||
|
||||
void safe_delay_keep_alive(uint16_t t) {
|
||||
idle(true);
|
||||
safe_delay(t);
|
||||
}
|
||||
|
||||
void Enable_E0() {
|
||||
stepper.enable_extruder(TERN_(HAS_EXTRUDERS, 0));
|
||||
}
|
||||
|
||||
void Disable_E0() {
|
||||
stepper.disable_extruder(TERN_(HAS_EXTRUDERS, 0));
|
||||
}
|
||||
|
||||
bool xy_are_trusted() {
|
||||
return axis_is_trusted(X_AXIS) && axis_is_trusted(Y_AXIS);
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
49
Marlin/src/feature/mmu3/mmu2_marlin_macros.h
Normal file
49
Marlin/src/feature/mmu3/mmu2_marlin_macros.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_marlin_macros.h
|
||||
*/
|
||||
|
||||
// This file will not be the same on Marlin1 and Marlin2.
|
||||
// Its purpose is to unify different macros in either of Marlin incarnations.
|
||||
|
||||
#ifdef __AVR__
|
||||
#include "../../MarlinCore.h"
|
||||
// brings _O and _T macros into MMU
|
||||
#include "../../core/language.h"
|
||||
#include "../../gcode/gcode.h"
|
||||
// we don't have these in Marlin 2.x so just define them here again
|
||||
#define _O(x) x
|
||||
#define _T(x) x
|
||||
#define MARLIN_KEEPALIVE_STATE_IN_PROCESS KEEPALIVE_STATE(IN_PROCESS)
|
||||
#elif defined(UNITTEST)
|
||||
#define _O(x) x
|
||||
#define _T(x) x
|
||||
#define MARLIN_KEEPALIVE_STATE_IN_PROCESS /*KEEPALIVE_STATE(IN_PROCESS) TODO*/
|
||||
#else
|
||||
#include "../../gcode/gcode.h"
|
||||
#define _O(x) x
|
||||
#define _T(x) x
|
||||
#define MARLIN_KEEPALIVE_STATE_IN_PROCESS KEEPALIVE_STATE(IN_PROCESS)
|
||||
#endif
|
65
Marlin/src/feature/mmu3/mmu2_power.cpp
Normal file
65
Marlin/src/feature/mmu3/mmu2_power.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_power.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2.h"
|
||||
#include "mmu2_power.h"
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include "../../core/macros.h"
|
||||
#include "../../core/boards.h"
|
||||
#include "../../pins/pins.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// On MK3 we cannot do actual power cycle on HW. Instead trigger a hardware reset.
|
||||
void power_on() {
|
||||
#if PIN_EXISTS(MMU2_RST)
|
||||
OUT_WRITE(MMU2_RST_PIN, HIGH);
|
||||
#endif
|
||||
power_reset();
|
||||
}
|
||||
|
||||
void power_off() {}
|
||||
|
||||
void power_reset() {
|
||||
#if PIN_EXISTS(MMU2_RST) // HW - pulse reset pin
|
||||
WRITE(MMU2_RST_PIN, LOW);
|
||||
safe_delay(100);
|
||||
WRITE(MMU2_RST_PIN, HIGH);
|
||||
#else
|
||||
mmu3.reset(MMU3::Software); // TODO: Needs redesign. This power implementation shouldn't know anything about the MMU itself
|
||||
#endif
|
||||
// otherwise HW reset is not available
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
36
Marlin/src/feature/mmu3/mmu2_power.h
Normal file
36
Marlin/src/feature/mmu3/mmu2_power.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_power.h
|
||||
*/
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
void power_on();
|
||||
|
||||
void power_off();
|
||||
|
||||
void power_reset();
|
||||
|
||||
} // MMU3
|
82
Marlin/src/feature/mmu3/mmu2_progress_converter.cpp
Normal file
82
Marlin/src/feature/mmu3/mmu2_progress_converter.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_progress_converter.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../core/language.h"
|
||||
#include "mmu2_progress_converter.h"
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#endif
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
#include "mmu_hw/errors_list.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
FSTR_P const progressTexts[] PROGMEM = {
|
||||
GET_TEXT_F(MSG_PROGRESS_OK),
|
||||
GET_TEXT_F(MSG_PROGRESS_ENGAGE_IDLER),
|
||||
GET_TEXT_F(MSG_PROGRESS_DISENGAGE_IDLER),
|
||||
GET_TEXT_F(MSG_PROGRESS_UNLOAD_FINDA),
|
||||
GET_TEXT_F(MSG_PROGRESS_UNLOAD_PULLEY),
|
||||
GET_TEXT_F(MSG_PROGRESS_FEED_FINDA),
|
||||
GET_TEXT_F(MSG_PROGRESS_FEED_EXTRUDER),
|
||||
GET_TEXT_F(MSG_PROGRESS_FEED_NOZZLE),
|
||||
GET_TEXT_F(MSG_PROGRESS_AVOID_GRIND),
|
||||
GET_TEXT_F(MSG_FINISHING_MOVEMENTS), // reuse from messages.cpp
|
||||
GET_TEXT_F(MSG_PROGRESS_DISENGAGE_IDLER), // err disengaging idler is the same text
|
||||
GET_TEXT_F(MSG_PROGRESS_ENGAGE_IDLER), // engage dtto.
|
||||
GET_TEXT_F(MSG_PROGRESS_WAIT_USER),
|
||||
GET_TEXT_F(MSG_PROGRESS_ERR_INTERNAL),
|
||||
GET_TEXT_F(MSG_PROGRESS_ERR_HELP_FIL),
|
||||
GET_TEXT_F(MSG_PROGRESS_ERR_TMC),
|
||||
GET_TEXT_F(MSG_UNLOADING_FILAMENT), // reuse from messages.cpp
|
||||
GET_TEXT_F(MSG_LOADING_FILAMENT), // reuse from messages.cpp
|
||||
GET_TEXT_F(MSG_PROGRESS_SELECT_SLOT),
|
||||
GET_TEXT_F(MSG_PROGRESS_PREPARE_BLADE),
|
||||
GET_TEXT_F(MSG_PROGRESS_PUSH_FILAMENT),
|
||||
GET_TEXT_F(MSG_PROGRESS_PERFORM_CUT),
|
||||
GET_TEXT_F(MSG_PROGRESSPSTRETURN_SELECTOR),
|
||||
GET_TEXT_F(MSG_PROGRESS_PARK_SELECTOR),
|
||||
GET_TEXT_F(MSG_PROGRESS_EJECT_FILAMENT),
|
||||
GET_TEXT_F(MSG_PROGRESSPSTRETRACT_FINDA),
|
||||
GET_TEXT_F(MSG_PROGRESS_HOMING),
|
||||
GET_TEXT_F(MSG_PROGRESS_MOVING_SELECTOR),
|
||||
GET_TEXT_F(MSG_PROGRESS_FEED_FSENSOR)
|
||||
};
|
||||
|
||||
FSTR_P const ProgressCodeToText(const ProgressCode pc) {
|
||||
// @@TODO ?? a better fallback option?
|
||||
return (int(pc) < COUNT(progressTexts))
|
||||
? static_cast<FSTR_P const>(pgm_read_ptr(&progressTexts[(uint16_t)pc]))
|
||||
: static_cast<FSTR_P const>(pgm_read_ptr(&progressTexts[0]));
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
36
Marlin/src/feature/mmu3/mmu2_progress_converter.h
Normal file
36
Marlin/src/feature/mmu3/mmu2_progress_converter.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_progress_converter.h
|
||||
*/
|
||||
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
|
||||
#include "../../HAL/shared/Marduino.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
FSTR_P const ProgressCodeToText(const ProgressCode pc);
|
||||
|
||||
}
|
418
Marlin/src/feature/mmu3/mmu2_protocol.cpp
Normal file
418
Marlin/src/feature/mmu3/mmu2_protocol.cpp
Normal file
|
@ -0,0 +1,418 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_protocol.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2_protocol.h"
|
||||
|
||||
// protocol definition
|
||||
// command: Q0
|
||||
// meaning: query operation status
|
||||
// Query/command: query
|
||||
// Expected reply from the MMU:
|
||||
// any of the running operation statuses: OID: [T|L|U|E|C|W|K][0-4]
|
||||
// <OID> P[0-9] : command being processed i.e. operation running, may contain a state number
|
||||
// <OID> E[0-9][0-9] : error 1-9 while doing a tool change
|
||||
// <OID> F[0-9] : operation finished - will be repeated to "Q" messages until a new command is issued
|
||||
|
||||
namespace modules {
|
||||
namespace protocol {
|
||||
|
||||
// decoding automaton
|
||||
// states: input -> transition into state
|
||||
// Code QTLMUXPSBEWK -> msgcode
|
||||
// \n ->start
|
||||
// * ->error
|
||||
// error \n ->start
|
||||
// * ->error
|
||||
// msgcode 0-9 ->msgvalue
|
||||
// * ->error
|
||||
// msgvalue 0-9 ->msgvalue
|
||||
// \n ->start successfully accepted command
|
||||
|
||||
DecodeStatus Protocol::DecodeRequest(uint8_t c) {
|
||||
switch (rqState) {
|
||||
case RequestStates::Code:
|
||||
switch (c) {
|
||||
case 'Q':
|
||||
case 'T':
|
||||
case 'L':
|
||||
case 'M':
|
||||
case 'U':
|
||||
case 'X':
|
||||
case 'P':
|
||||
case 'S':
|
||||
case 'B':
|
||||
case 'E':
|
||||
case 'W': // write is gonna be a special one
|
||||
case 'K':
|
||||
case 'F':
|
||||
case 'f':
|
||||
case 'H':
|
||||
case 'R':
|
||||
requestMsg.code = (RequestMsgCodes)c;
|
||||
requestMsg.value = 0;
|
||||
requestMsg.value2 = 0;
|
||||
requestMsg.crc8 = 0;
|
||||
rqState = (c == 'W') ? RequestStates::Address : RequestStates::Value; // prepare special automaton path for Write commands
|
||||
return DecodeStatus::NeedMoreData;
|
||||
default:
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case RequestStates::Value:
|
||||
if (IsHexDigit(c)) {
|
||||
requestMsg.value <<= 4U;
|
||||
requestMsg.value |= Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else if (IsCRCSeparator(c)) {
|
||||
rqState = RequestStates::CRC;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case RequestStates::Address:
|
||||
if (IsHexDigit(c)) {
|
||||
requestMsg.value <<= 4U;
|
||||
requestMsg.value |= Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else if (c == ' ') { // end of address, value coming
|
||||
rqState = RequestStates::WriteValue;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case RequestStates::WriteValue:
|
||||
if (IsHexDigit(c)) {
|
||||
requestMsg.value2 <<= 4U;
|
||||
requestMsg.value2 |= Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else if (IsCRCSeparator(c)) {
|
||||
rqState = RequestStates::CRC;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case RequestStates::CRC:
|
||||
if (IsHexDigit(c)) {
|
||||
requestMsg.crc8 <<= 4U;
|
||||
requestMsg.crc8 |= Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else if (IsNewLine(c)) {
|
||||
// check CRC at this spot
|
||||
if (requestMsg.crc8 != requestMsg.ComputeCRC8()) {
|
||||
// CRC mismatch
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
else {
|
||||
rqState = RequestStates::Code;
|
||||
return DecodeStatus::MessageCompleted;
|
||||
}
|
||||
}
|
||||
else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
default: // case error:
|
||||
if (IsNewLine(c)) {
|
||||
rqState = RequestStates::Code;
|
||||
return DecodeStatus::MessageCompleted;
|
||||
}
|
||||
else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeRequest(const RequestMsg &msg, uint8_t *txbuff) {
|
||||
txbuff[0] = (uint8_t)msg.code;
|
||||
uint8_t i = 1 + UInt8ToHex(msg.value, txbuff + 1);
|
||||
|
||||
i += AppendCRC(msg.getCRC(), txbuff + i);
|
||||
|
||||
txbuff[i] = '\n';
|
||||
++i;
|
||||
return i;
|
||||
static_assert(7 <= MaxRequestSize(), "Request message length exceeded the maximum size, increase the magic constant in MaxRequestSize()");
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff) {
|
||||
const RequestMsg msg(RequestMsgCodes::Write, address, value);
|
||||
uint8_t i = BeginEncodeRequest(msg, txbuff);
|
||||
// dump the value
|
||||
i += UInt16ToHex(value, txbuff + i);
|
||||
|
||||
i += AppendCRC(msg.getCRC(), txbuff + i);
|
||||
|
||||
txbuff[i] = '\n';
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
DecodeStatus Protocol::DecodeResponse(uint8_t c) {
|
||||
switch (rspState) {
|
||||
case ResponseStates::RequestCode:
|
||||
switch (c) {
|
||||
case 'Q':
|
||||
case 'T':
|
||||
case 'L':
|
||||
case 'M':
|
||||
case 'U':
|
||||
case 'X':
|
||||
case 'P':
|
||||
case 'S':
|
||||
case 'B':
|
||||
case 'E':
|
||||
case 'W':
|
||||
case 'K':
|
||||
case 'F':
|
||||
case 'f':
|
||||
case 'H':
|
||||
case 'R':
|
||||
responseMsg.request.code = (RequestMsgCodes)c;
|
||||
responseMsg.request.value = 0;
|
||||
responseMsg.request.value2 = 0;
|
||||
responseMsg.request.crc8 = 0;
|
||||
rspState = ResponseStates::RequestValue;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
case 0x0a:
|
||||
case 0x0d:
|
||||
// skip leading whitespace if any (makes integration with other SW easier/tolerant)
|
||||
return DecodeStatus::NeedMoreData;
|
||||
default:
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case ResponseStates::RequestValue:
|
||||
if (IsHexDigit(c)) {
|
||||
responseMsg.request.value <<= 4U;
|
||||
responseMsg.request.value += Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else if (c == ' ') {
|
||||
rspState = ResponseStates::ParamCode;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else {
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case ResponseStates::ParamCode:
|
||||
switch (c) {
|
||||
case 'P':
|
||||
case 'E':
|
||||
case 'F':
|
||||
case 'A':
|
||||
case 'R':
|
||||
case 'B':
|
||||
rspState = ResponseStates::ParamValue;
|
||||
responseMsg.paramCode = (ResponseMsgParamCodes)c;
|
||||
responseMsg.paramValue = 0;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
default:
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case ResponseStates::ParamValue:
|
||||
if (IsHexDigit(c)) {
|
||||
responseMsg.paramValue <<= 4U;
|
||||
responseMsg.paramValue += Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else if (IsCRCSeparator(c)) {
|
||||
rspState = ResponseStates::CRC;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else {
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case ResponseStates::CRC:
|
||||
if (IsHexDigit(c)) {
|
||||
responseMsg.request.crc8 <<= 4U;
|
||||
responseMsg.request.crc8 += Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
}
|
||||
else if (IsNewLine(c)) {
|
||||
// check CRC at this spot
|
||||
if (responseMsg.request.crc8 != responseMsg.ComputeCRC8()) {
|
||||
// CRC mismatch
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
else {
|
||||
rspState = ResponseStates::RequestCode;
|
||||
return DecodeStatus::MessageCompleted;
|
||||
}
|
||||
}
|
||||
else {
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
default: // case error:
|
||||
if (IsNewLine(c)) {
|
||||
rspState = ResponseStates::RequestCode;
|
||||
return DecodeStatus::MessageCompleted;
|
||||
}
|
||||
else {
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff) {
|
||||
// BEWARE:
|
||||
// ResponseMsg rsp(RequestMsg(msg.code, msg.value), ar, 0);
|
||||
// ... is NOT the same as:
|
||||
// ResponseMsg rsp(msg, ar, 0);
|
||||
// ... because of the usually unused parameter value2 (which only comes non-zero in write requests).
|
||||
// It took me a few hours to find out why the CRC from the MMU never matched all the other sides (unit tests and the MK3S)
|
||||
// It is because this was the only place where the original request kept its value2 non-zero.
|
||||
// In the response, we must make sure value2 is actually zero unless being sent along with it (which is not right now)
|
||||
const ResponseMsg rsp(RequestMsg(msg.code, msg.value), ar, 0); // this needs some cleanup @@TODO - check assembly how bad is it
|
||||
uint8_t i = BeginEncodeRequest(rsp.request, txbuff);
|
||||
txbuff[i] = (uint8_t)ar;
|
||||
++i;
|
||||
i += AppendCRC(rsp.getCRC(), txbuff + i);
|
||||
txbuff[i] = '\n';
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff) {
|
||||
return EncodeResponseRead(msg, true, findaValue, txbuff);
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeResponseQueryOperation(const RequestMsg &msg, ResponseCommandStatus rcs, uint8_t *txbuff) {
|
||||
const ResponseMsg rsp(msg, rcs.code, rcs.value);
|
||||
uint8_t i = BeginEncodeRequest(msg, txbuff);
|
||||
txbuff[i] = (uint8_t)rsp.paramCode;
|
||||
++i;
|
||||
i += UInt16ToHex(rsp.paramValue, txbuff + i);
|
||||
i += AppendCRC(rsp.getCRC(), txbuff + i);
|
||||
txbuff[i] = '\n';
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff) {
|
||||
const ResponseMsg rsp(msg,
|
||||
accepted ? ResponseMsgParamCodes::Accepted : ResponseMsgParamCodes::Rejected,
|
||||
accepted ? value2 : 0 // be careful about this value for CRC computation - rejected status doesn't have any meaningful value which could be reconstructed from the textual form of the message
|
||||
);
|
||||
uint8_t i = BeginEncodeRequest(msg, txbuff);
|
||||
txbuff[i] = (uint8_t)rsp.paramCode;
|
||||
++i;
|
||||
if (accepted)
|
||||
// dump the value
|
||||
i += UInt16ToHex(value2, txbuff + i);
|
||||
i += AppendCRC(rsp.getCRC(), txbuff + i);
|
||||
txbuff[i] = '\n';
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
uint8_t Protocol::UInt8ToHex(uint8_t value, uint8_t *dst) {
|
||||
if (value == 0) {
|
||||
*dst = '0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t v = value >> 4U;
|
||||
uint8_t charsOut = 1;
|
||||
if (v != 0) { // skip the first '0' if any
|
||||
*dst = Nibble2Char(v);
|
||||
++dst;
|
||||
charsOut = 2;
|
||||
}
|
||||
v = value & 0xfU;
|
||||
*dst = Nibble2Char(v);
|
||||
return charsOut;
|
||||
}
|
||||
|
||||
uint8_t Protocol::UInt16ToHex(uint16_t value, uint8_t *dst) {
|
||||
constexpr uint16_t topNibbleMask = 0xf000;
|
||||
if (value == 0) {
|
||||
*dst = '0';
|
||||
return 1;
|
||||
}
|
||||
// skip initial zeros
|
||||
uint8_t charsOut = 4;
|
||||
while ((value & topNibbleMask) == 0) {
|
||||
value <<= 4U;
|
||||
--charsOut;
|
||||
}
|
||||
for (uint8_t i = 0; i < charsOut; ++i) {
|
||||
uint8_t n = (value & topNibbleMask) >> (8U + 4U);
|
||||
value <<= 4U;
|
||||
*dst = Nibble2Char(n);
|
||||
++dst;
|
||||
}
|
||||
return charsOut;
|
||||
}
|
||||
|
||||
uint8_t Protocol::BeginEncodeRequest(const RequestMsg &msg, uint8_t *dst) {
|
||||
dst[0] = (uint8_t)msg.code;
|
||||
|
||||
uint8_t i = 1 + UInt8ToHex(msg.value, dst + 1);
|
||||
|
||||
dst[i] = ' ';
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
uint8_t Protocol::AppendCRC(uint8_t crc, uint8_t *dst) {
|
||||
dst[0] = '*'; // reprap-style separator of CRC
|
||||
return 1 + UInt8ToHex(crc, dst + 1);
|
||||
}
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace modules
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
318
Marlin/src/feature/mmu3/mmu2_protocol.h
Normal file
318
Marlin/src/feature/mmu3/mmu2_protocol.h
Normal file
|
@ -0,0 +1,318 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_protocol.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mmu2_crc.h"
|
||||
|
||||
// prevent ARM HAL macros from breaking our code
|
||||
#undef CRC
|
||||
|
||||
namespace modules {
|
||||
|
||||
// @brief The MMU communication protocol implementation and related stuff.
|
||||
//
|
||||
// See description of the new protocol in the MMU 2021 doc
|
||||
namespace protocol {
|
||||
|
||||
// Definition of request message codes
|
||||
enum class RequestMsgCodes : uint8_t {
|
||||
unknown = 0,
|
||||
Query = 'Q',
|
||||
Tool = 'T',
|
||||
Load = 'L',
|
||||
Mode = 'M',
|
||||
Unload = 'U',
|
||||
Reset = 'X',
|
||||
Finda = 'P',
|
||||
Version = 'S',
|
||||
Button = 'B',
|
||||
Eject = 'E',
|
||||
Write = 'W',
|
||||
Cut = 'K',
|
||||
FilamentType = 'F',
|
||||
FilamentSensor = 'f',
|
||||
Home = 'H',
|
||||
Read = 'R'
|
||||
};
|
||||
|
||||
// Definition of response message parameter codes
|
||||
enum class ResponseMsgParamCodes : uint8_t {
|
||||
unknown = 0,
|
||||
Processing = 'P',
|
||||
Error = 'E',
|
||||
Finished = 'F',
|
||||
Accepted = 'A',
|
||||
Rejected = 'R',
|
||||
Button = 'B'// the MMU registered a button press and is sending it to the printer for processing
|
||||
};
|
||||
|
||||
// A request message - requests are being sent by the printer into the MMU.
|
||||
struct RequestMsg {
|
||||
RequestMsgCodes code; //!< code of the request message
|
||||
uint8_t value; //!< value of the request message or address of variable to read/write
|
||||
uint16_t value2; //!< in case or write messages - value to be written into the register
|
||||
|
||||
// CRC8 check - please note we abuse this byte for CRC of ResponseMsgs as well.
|
||||
// The crc8 byte itself is not added into the CRC computation (obviously ;) )
|
||||
// Beware - adding any members of this data structure may need changing the way CRC is being computed!
|
||||
uint8_t crc8;
|
||||
|
||||
constexpr uint8_t ComputeCRC8() const {
|
||||
uint8_t crc = 0;
|
||||
crc = modules::crc::CRC8::CCITT_updateCX(0, (uint8_t)code);
|
||||
crc = modules::crc::CRC8::CCITT_updateCX(crc, value);
|
||||
crc = modules::crc::CRC8::CCITT_updateW(crc, value2);
|
||||
return crc;
|
||||
}
|
||||
|
||||
// @param code of the request message
|
||||
// @param value of the request message
|
||||
inline constexpr RequestMsg(RequestMsgCodes code, uint8_t value)
|
||||
: code(code)
|
||||
, value(value)
|
||||
, value2(0)
|
||||
, crc8(ComputeCRC8()) {
|
||||
}
|
||||
|
||||
// Intended for write requests
|
||||
// @param code of the request message ('W')
|
||||
// @param address of the register
|
||||
// @param value to write into the register
|
||||
inline constexpr RequestMsg(RequestMsgCodes code, uint8_t address, uint16_t value)
|
||||
: code(code)
|
||||
, value(address)
|
||||
, value2(value)
|
||||
, crc8(ComputeCRC8()) {}
|
||||
|
||||
constexpr uint8_t getCRC() const { return crc8; }
|
||||
};
|
||||
|
||||
// A response message - responses are being sent from the MMU into the printer as a response to a request message.
|
||||
struct ResponseMsg {
|
||||
RequestMsg request; //!< response is always preceeded by the request message
|
||||
ResponseMsgParamCodes paramCode; //!< code of the parameter
|
||||
uint16_t paramValue; //!< value of the parameter
|
||||
|
||||
constexpr uint8_t ComputeCRC8() const {
|
||||
uint8_t crc = request.ComputeCRC8();
|
||||
crc = modules::crc::CRC8::CCITT_updateCX(crc, (uint8_t)paramCode);
|
||||
crc = modules::crc::CRC8::CCITT_updateW(crc, paramValue);
|
||||
return crc;
|
||||
}
|
||||
|
||||
// @param request the source request message this response is a reply to
|
||||
// @param paramCode code of the parameter
|
||||
// @param paramValue value of the parameter
|
||||
inline constexpr ResponseMsg(RequestMsg request, ResponseMsgParamCodes paramCode, uint16_t paramValue)
|
||||
: request(request)
|
||||
, paramCode(paramCode)
|
||||
, paramValue(paramValue) {
|
||||
this->request.crc8 = ComputeCRC8();
|
||||
}
|
||||
|
||||
constexpr uint8_t getCRC() const { return request.crc8; }
|
||||
};
|
||||
|
||||
// Combined commandStatus and its value into one data structure (optimization purposes)
|
||||
struct ResponseCommandStatus {
|
||||
ResponseMsgParamCodes code;
|
||||
uint16_t value;
|
||||
inline constexpr ResponseCommandStatus(ResponseMsgParamCodes code, uint16_t value)
|
||||
: code(code)
|
||||
, value(value) {}
|
||||
};
|
||||
|
||||
// Message decoding return values
|
||||
enum class DecodeStatus : uint_fast8_t {
|
||||
MessageCompleted, //!< message completed and successfully lexed
|
||||
NeedMoreData, //!< message incomplete yet, waiting for another byte to come
|
||||
Error, //!< input character broke message decoding
|
||||
};
|
||||
|
||||
// Protocol class is responsible for creating/decoding messages in Rx/Tx buffer
|
||||
//
|
||||
// Beware - in the decoding more, it is meant to be a statefull instance which works through public methods
|
||||
// processing one input byte per call.
|
||||
class Protocol {
|
||||
public:
|
||||
Protocol()
|
||||
: rqState(RequestStates::Code)
|
||||
, requestMsg(RequestMsgCodes::unknown, 0)
|
||||
, rspState(ResponseStates::RequestCode)
|
||||
, responseMsg(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0) {}
|
||||
|
||||
// Takes the input byte c and steps one step through the state machine
|
||||
// @return state of the message being decoded
|
||||
DecodeStatus DecodeRequest(uint8_t c);
|
||||
|
||||
// Decodes response message in rxbuff
|
||||
// @return decoded response message structure
|
||||
DecodeStatus DecodeResponse(uint8_t c);
|
||||
|
||||
// Encodes request message msg into txbuff memory
|
||||
// It is expected the txbuff is large enough to fit the message
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeRequest(const RequestMsg &msg, uint8_t *txbuff);
|
||||
|
||||
// Encodes Write request message msg into txbuff memory
|
||||
// It is expected the txbuff is large enough to fit the message
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff);
|
||||
|
||||
// @return the maximum byte length necessary to encode a request message
|
||||
// Beneficial in case of pre-allocating a buffer for enconding a RequestMsg.
|
||||
static constexpr uint8_t MaxRequestSize() { return 13; }
|
||||
|
||||
// @return the maximum byte length necessary to encode a response message
|
||||
// Beneficial in case of pre-allocating a buffer for enconding a ResponseMsg.
|
||||
static constexpr uint8_t MaxResponseSize() { return 14; }
|
||||
|
||||
// Encode generic response Command Accepted or Rejected
|
||||
// @param msg source request message for this response
|
||||
// @param ar code of response parameter
|
||||
// @param txbuff where to format the message
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff);
|
||||
|
||||
// Encode response to Read FINDA query
|
||||
// @param msg source request message for this response
|
||||
// @param findaValue 1/0 (on/off) status of FINDA
|
||||
// @param txbuff where to format the message
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff);
|
||||
|
||||
// Encode response to Version query
|
||||
// @param msg source request message for this response
|
||||
// @param value version number (0-255)
|
||||
// @param txbuff where to format the message
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseVersion(const RequestMsg &msg, uint16_t value, uint8_t *txbuff);
|
||||
|
||||
// Encode response to Query operation status
|
||||
// @param msg source request message for this response
|
||||
// @param code status of operation (Processing, Error, Finished)
|
||||
// @param value related to status of operation(e.g. error code or progress)
|
||||
// @param txbuff where to format the message
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseQueryOperation(const RequestMsg &msg, ResponseCommandStatus rcs, uint8_t *txbuff);
|
||||
|
||||
// Encode response to Read query
|
||||
// @param msg source request message for this response
|
||||
// @param accepted true if the read query was accepted
|
||||
// @param value2 variable value
|
||||
// @param txbuff where to format the message
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff);
|
||||
|
||||
// @return the most recently lexed request message
|
||||
inline const RequestMsg GetRequestMsg() const { return requestMsg; }
|
||||
|
||||
// @return the most recently lexed response message
|
||||
inline const ResponseMsg GetResponseMsg() const { return responseMsg; }
|
||||
|
||||
// resets the internal request decoding state (typically after an error)
|
||||
void ResetRequestDecoder() {
|
||||
rqState = RequestStates::Code;
|
||||
}
|
||||
|
||||
// resets the internal response decoding state (typically after an error)
|
||||
void ResetResponseDecoder() {
|
||||
rspState = ResponseStates::RequestCode;
|
||||
}
|
||||
|
||||
#ifndef UNITTEST
|
||||
private:
|
||||
#endif
|
||||
|
||||
enum class RequestStates : uint8_t {
|
||||
Code, //!< starting state - expects message code
|
||||
Value, //!< expecting code value
|
||||
Address, //!< expecting address for Write command
|
||||
WriteValue, //!< value to be written (Write command)
|
||||
CRC, //!< CRC
|
||||
Error //!< automaton in error state
|
||||
};
|
||||
|
||||
RequestStates rqState;
|
||||
RequestMsg requestMsg;
|
||||
|
||||
enum class ResponseStates : uint8_t {
|
||||
RequestCode, //!< starting state - expects message code
|
||||
RequestValue, //!< expecting code value
|
||||
ParamCode, //!< expecting param code
|
||||
ParamValue, //!< expecting param value
|
||||
CRC, //!< expecting CRC value
|
||||
Error //!< automaton in error state
|
||||
};
|
||||
|
||||
ResponseStates rspState;
|
||||
ResponseMsg responseMsg;
|
||||
|
||||
static constexpr bool IsNewLine(uint8_t c) {
|
||||
return c == '\n' || c == '\r';
|
||||
}
|
||||
static constexpr bool IsDigit(uint8_t c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
static constexpr bool IsCRCSeparator(uint8_t c) {
|
||||
return c == '*';
|
||||
}
|
||||
static constexpr bool IsHexDigit(uint8_t c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
|
||||
}
|
||||
static constexpr uint8_t Char2Nibble(uint8_t c) {
|
||||
switch (c) {
|
||||
case '0' ... '9': return c - '0';
|
||||
case 'a' ... 'f': return c - 'a' + 10;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint8_t Nibble2Char(uint8_t n) {
|
||||
switch (n) {
|
||||
case 0x0 ... 0x9: return n + '0';
|
||||
case 0xA ... 0xF: return n - 10 + 'a';
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// @return number of characters written
|
||||
static uint8_t UInt8ToHex(uint8_t value, uint8_t *dst);
|
||||
|
||||
// @return number of characters written
|
||||
static uint8_t UInt16ToHex(uint16_t value, uint8_t *dst);
|
||||
|
||||
static uint8_t BeginEncodeRequest(const RequestMsg &msg, uint8_t *dst);
|
||||
|
||||
static uint8_t AppendCRC(uint8_t crc, uint8_t *dst);
|
||||
};
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace modules
|
||||
|
899
Marlin/src/feature/mmu3/mmu2_protocol_logic.cpp
Normal file
899
Marlin/src/feature/mmu3/mmu2_protocol_logic.cpp
Normal file
|
@ -0,0 +1,899 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_protocol_logic.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#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"
|
||||
#define _millis millis
|
||||
#else
|
||||
// irrelevant on Buddy FW, just keep "_millis" as "millis"
|
||||
// #include <wiring_time.h>
|
||||
#define _millis millis
|
||||
#ifdef UNITTEST
|
||||
#define strncmp_P strncmp
|
||||
#else
|
||||
#include "../../core/serial.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include "mmu2_supported_version.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
static constexpr uint8_t supportedMmuFWVersion[] PROGMEM = { mmuVersionMajor, mmuVersionMinor, mmuVersionPatch };
|
||||
|
||||
const Register ProtocolLogic::regs8Addrs[ProtocolLogic::regs8Count] PROGMEM = {
|
||||
Register::FINDA_State, // FINDA state
|
||||
Register::Set_Get_Selector_Slot, // Selector slot
|
||||
Register::Set_Get_Idler_Slot, // Idler slot
|
||||
};
|
||||
|
||||
const Register ProtocolLogic::regs16Addrs[ProtocolLogic::regs16Count] PROGMEM = {
|
||||
Register::MMU_Errors, // MMU errors - aka statistics
|
||||
Register::Get_Pulley_Position, // Pulley position [mm]
|
||||
};
|
||||
|
||||
const Register ProtocolLogic::initRegs8Addrs[ProtocolLogic::initRegs8Count] PROGMEM = {
|
||||
Register::Extra_Load_Distance, // Extra load distance [mm]
|
||||
Register::Pulley_Slow_Feedrate, // Pulley slow feedrate [mm/s]
|
||||
};
|
||||
|
||||
void ProtocolLogic::CheckAndReportAsyncEvents() {
|
||||
// even when waiting for a query period, we need to report a change in filament sensor's state
|
||||
// - it is vital for a precise synchronization of moves of the printer and the MMU
|
||||
uint8_t fs = (uint8_t)WhereIsFilament();
|
||||
if (fs != lastFSensor)
|
||||
SendAndUpdateFilamentSensor();
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendQuery() {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
|
||||
scopeState = ScopeState::QuerySent;
|
||||
}
|
||||
|
||||
void ProtocolLogic::StartReading8bitRegisters() {
|
||||
regIndex = 0;
|
||||
SendReadRegister(pgm_read_byte(regs8Addrs + regIndex), ScopeState::Reading8bitRegisters);
|
||||
}
|
||||
|
||||
void ProtocolLogic::ProcessRead8bitRegister() {
|
||||
regs8[regIndex] = rsp.paramValue;
|
||||
++regIndex;
|
||||
if (regIndex >= regs8Count)
|
||||
// proceed with reading 16bit registers
|
||||
StartReading16bitRegisters();
|
||||
else
|
||||
SendReadRegister(pgm_read_byte(regs8Addrs + regIndex), ScopeState::Reading8bitRegisters);
|
||||
}
|
||||
|
||||
void ProtocolLogic::StartReading16bitRegisters() {
|
||||
regIndex = 0;
|
||||
SendReadRegister(pgm_read_byte(regs16Addrs + regIndex), ScopeState::Reading16bitRegisters);
|
||||
}
|
||||
|
||||
ProtocolLogic::ScopeState __attribute__((noinline)) ProtocolLogic::ProcessRead16bitRegister(ProtocolLogic::ScopeState stateAtEnd) {
|
||||
regs16[regIndex] = rsp.paramValue;
|
||||
++regIndex;
|
||||
if (regIndex >= regs16Count)
|
||||
return stateAtEnd;
|
||||
else
|
||||
SendReadRegister(pgm_read_byte(regs16Addrs + regIndex), ScopeState::Reading16bitRegisters);
|
||||
return ScopeState::Reading16bitRegisters;
|
||||
}
|
||||
|
||||
void ProtocolLogic::StartWritingInitRegisters() {
|
||||
regIndex = 0;
|
||||
SendWriteRegister(pgm_read_byte(initRegs8Addrs + regIndex), initRegs8[regIndex], ScopeState::WritingInitRegisters);
|
||||
}
|
||||
|
||||
bool __attribute__((noinline)) ProtocolLogic::ProcessWritingInitRegister() {
|
||||
++regIndex;
|
||||
if (regIndex >= initRegs8Count)
|
||||
return true;
|
||||
else
|
||||
SendWriteRegister(pgm_read_byte(initRegs8Addrs + regIndex), initRegs8[regIndex], ScopeState::WritingInitRegisters);
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendAndUpdateFilamentSensor() {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, lastFSensor = (uint8_t)WhereIsFilament()));
|
||||
scopeState = ScopeState::FilamentSensorStateSent;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendButton(uint8_t btn) {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
|
||||
scopeState = ScopeState::ButtonSent;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendVersion(uint8_t stage) {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Version, stage));
|
||||
scopeState = (ScopeState)((uint_fast8_t)ScopeState::S0Sent + stage);
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendReadRegister(uint8_t index, ScopeState nextState) {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Read, index));
|
||||
scopeState = nextState;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendWriteRegister(uint8_t index, uint16_t value, ScopeState nextState) {
|
||||
SendWriteMsg(RequestMsg(RequestMsgCodes::Write, index, value));
|
||||
scopeState = nextState;
|
||||
}
|
||||
|
||||
// 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) {}
|
||||
|
||||
enum class State : uint8_t {
|
||||
MatchingPart,
|
||||
SomethingElse,
|
||||
Matched
|
||||
};
|
||||
|
||||
// @return true when "ok\n" gets detected
|
||||
State Detect(uint8_t c) {
|
||||
// consume old MMU FW's data if any -> avoid confusion of protocol decoder
|
||||
if (ok == 0 && c == 'o') {
|
||||
++ok;
|
||||
return State::MatchingPart;
|
||||
}
|
||||
else if (ok == 1 && c == 'k') {
|
||||
++ok;
|
||||
return State::Matched;
|
||||
}
|
||||
return State::SomethingElse;
|
||||
}
|
||||
};
|
||||
|
||||
StepStatus ProtocolLogic::ExpectingMessage() {
|
||||
int bytesConsumed = 0;
|
||||
int c = -1;
|
||||
|
||||
OldMMUFWDetector oldMMUh4x0r; // old MMU FW hacker ;)
|
||||
|
||||
// try to consume as many rx bytes as possible (until a message has been completed)
|
||||
while ((c = MMU2_SERIAL.read()) >= 0) {
|
||||
++bytesConsumed;
|
||||
RecordReceivedByte(c);
|
||||
switch (protocol.DecodeResponse(c)) {
|
||||
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:
|
||||
break;
|
||||
case DecodeStatus::Error: {
|
||||
// consume old MMU FW's data if any -> avoid confusion of protocol decoder
|
||||
auto old = oldMMUh4x0r.Detect(c);
|
||||
if (old == OldMMUFWDetector::State::Matched)
|
||||
// Old MMU FW 1.0.6 detected. Firmwares are incompatible.
|
||||
return VersionMismatch;
|
||||
else if (old == OldMMUFWDetector::State::MatchingPart)
|
||||
break;
|
||||
}
|
||||
// [[fallthrough]]; // otherwise
|
||||
// fall through
|
||||
default:
|
||||
RecordUARTActivity(); // something has happened on the UART, update the timeout record
|
||||
return ProtocolError;
|
||||
}
|
||||
}
|
||||
if (bytesConsumed != 0) {
|
||||
RecordUARTActivity(); // something has happened on the UART, update the timeout record
|
||||
return Processing; // consumed some bytes, but message still not ready
|
||||
}
|
||||
else if (Elapsed(linkLayerTimeout) && currentScope != Scope::Stopped) {
|
||||
return CommunicationTimeout;
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendMsg(RequestMsg rq) {
|
||||
#if defined(__AVR__) || defined(TARGET_LPC1768)
|
||||
// Buddy FW cannot use stack-allocated txbuff - DMA doesn't work with CCMRAM
|
||||
// No restrictions on MK3/S/+ though
|
||||
uint8_t txbuff[Protocol::MaxRequestSize()];
|
||||
#endif
|
||||
uint8_t len = Protocol::EncodeRequest(rq, txbuff);
|
||||
#if defined(__AVR__) || defined(TARGET_LPC1768)
|
||||
// TODO: I'm not sure if this is the correct approach with AVR
|
||||
for ( uint8_t i = 0; i < len; i++) {
|
||||
MMU2_SERIAL.write(txbuff[i]);
|
||||
}
|
||||
#else
|
||||
MMU2_SERIAL.write(txbuff, len);
|
||||
#endif
|
||||
LogRequestMsg(txbuff, len);
|
||||
RecordUARTActivity();
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendWriteMsg(RequestMsg rq) {
|
||||
#if defined(__AVR__) || defined(TARGET_LPC1768)
|
||||
// Buddy FW cannot use stack-allocated txbuff - DMA doesn't work with CCMRAM
|
||||
// No restrictions on MK3/S/+ though
|
||||
uint8_t txbuff[Protocol::MaxRequestSize()];
|
||||
#endif
|
||||
uint8_t len = Protocol::EncodeWriteRequest(rq.value, rq.value2, txbuff);
|
||||
|
||||
#if defined(__AVR__) || defined(TARGET_LPC1768)
|
||||
// TODO: I'm not sure if this is the correct approach with AVR
|
||||
for ( uint8_t i = 0; i < len; i++) {
|
||||
MMU2_SERIAL.write(txbuff[i]);
|
||||
}
|
||||
#else
|
||||
MMU2_SERIAL.write(txbuff, len);
|
||||
#endif
|
||||
LogRequestMsg(txbuff, len);
|
||||
RecordUARTActivity();
|
||||
}
|
||||
|
||||
void ProtocolLogic::StartSeqRestart() {
|
||||
retries = maxRetries;
|
||||
SendVersion(0);
|
||||
}
|
||||
|
||||
void ProtocolLogic::DelayedRestartRestart() {
|
||||
scopeState = ScopeState::RecoveringProtocolError;
|
||||
}
|
||||
|
||||
void ProtocolLogic::CommandRestart() {
|
||||
scopeState = ScopeState::CommandSent;
|
||||
SendMsg(rq);
|
||||
}
|
||||
|
||||
void ProtocolLogic::IdleRestart() {
|
||||
scopeState = ScopeState::Ready;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::ProcessVersionResponse(uint8_t stage) {
|
||||
if (rsp.request.code != RequestMsgCodes::Version || rsp.request.value != stage) {
|
||||
// got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
|
||||
SendVersion(stage);
|
||||
}
|
||||
else {
|
||||
mmuFwVersion[stage] = rsp.paramValue;
|
||||
if (mmuFwVersion[stage] != pgm_read_byte(&supportedMmuFWVersion[stage])) {
|
||||
if (--retries == 0) return VersionMismatch;
|
||||
SendVersion(stage);
|
||||
}
|
||||
else {
|
||||
ResetCommunicationTimeoutAttempts(); // got a meaningful response from the MMU, stop data layer timeout tracking
|
||||
SendVersion(stage + 1);
|
||||
}
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::ScopeStep() {
|
||||
if (!ExpectsResponse()) {
|
||||
// we are waiting for something
|
||||
switch (currentScope) {
|
||||
case Scope::DelayedRestart:
|
||||
return DelayedRestartWait();
|
||||
case Scope::Idle:
|
||||
return IdleWait();
|
||||
case Scope::Command:
|
||||
return CommandWait();
|
||||
case Scope::Stopped:
|
||||
return StoppedStep();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// we are expecting a message
|
||||
auto expmsg = ExpectingMessage();
|
||||
if (expmsg != MessageReady)
|
||||
return expmsg;
|
||||
|
||||
// process message
|
||||
switch (currentScope) {
|
||||
case Scope::StartSeq:
|
||||
return StartSeqStep(); // ~270B
|
||||
case Scope::Idle:
|
||||
return IdleStep(); // ~300B
|
||||
case Scope::Command:
|
||||
return CommandStep(); // ~430B
|
||||
case Scope::Stopped:
|
||||
return StoppedStep();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Finished;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::StartSeqStep() {
|
||||
// solve initial handshake
|
||||
switch (scopeState) {
|
||||
case ScopeState::S0Sent: // received response to S0 - major
|
||||
case ScopeState::S1Sent: // received response to S1 - minor
|
||||
case ScopeState::S2Sent: // received response to S2 - patch
|
||||
return ProcessVersionResponse((uint8_t)scopeState - (uint8_t)ScopeState::S0Sent);
|
||||
case ScopeState::S3Sent: // received response to S3 - revision
|
||||
if (rsp.request.code != RequestMsgCodes::Version || rsp.request.value != 3) {
|
||||
// got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
|
||||
SendVersion(3);
|
||||
}
|
||||
else {
|
||||
mmuFwVersionBuild = rsp.paramValue; // just register the build number
|
||||
// Start General Interrogation after line up - initial parametrization is started
|
||||
StartWritingInitRegisters();
|
||||
}
|
||||
return Processing;
|
||||
case ScopeState::WritingInitRegisters:
|
||||
if (ProcessWritingInitRegister())
|
||||
SendAndUpdateFilamentSensor();
|
||||
return Processing;
|
||||
case ScopeState::FilamentSensorStateSent:
|
||||
SwitchFromStartToIdle();
|
||||
return Processing; // Returning Finished is not a good idea in case of a fast error recovery
|
||||
// - it tells the printer, that the command which experienced a protocol error and recovered successfully actually terminated.
|
||||
// In such a case we must return "Processing" in order to keep the MMU state machine running and prevent the printer from executing next G-codes.
|
||||
default:
|
||||
return VersionMismatch;
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::DelayedRestartWait() {
|
||||
if (Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on
|
||||
while (MMU2_SERIAL.read() != -1); // clear the input buffer
|
||||
// switch to StartSeq
|
||||
start();
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::CommandWait() {
|
||||
if (Elapsed(heartBeatPeriod))
|
||||
SendQuery();
|
||||
else
|
||||
// even when waiting for a query period, we need to report a change in filament sensor's state
|
||||
// - it is vital for a precise synchronization of moves of the printer and the MMU
|
||||
CheckAndReportAsyncEvents();
|
||||
return Processing;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::ProcessCommandQueryResponse() {
|
||||
switch (rsp.paramCode) {
|
||||
case ResponseMsgParamCodes::Processing:
|
||||
progressCode = static_cast<ProgressCode>(rsp.paramValue);
|
||||
errorCode = ErrorCode::OK;
|
||||
SendAndUpdateFilamentSensor(); // keep on reporting the state of fsensor regularly
|
||||
return Processing;
|
||||
case ResponseMsgParamCodes::Error:
|
||||
// in case of an error the progress code remains as it has been before
|
||||
progressCode = ProgressCode::ERRWaitingForUser;
|
||||
errorCode = static_cast<ErrorCode>(rsp.paramValue);
|
||||
// keep on reporting the state of fsensor regularly even in command error state
|
||||
// - the MMU checks FINDA and fsensor even while recovering from errors
|
||||
SendAndUpdateFilamentSensor();
|
||||
return CommandError;
|
||||
case ResponseMsgParamCodes::Button:
|
||||
// The user pushed a button on the MMU. Save it, do what we need to do
|
||||
// to prepare, then pass it back to the MMU so it can work its magic.
|
||||
buttonCode = static_cast<Buttons>(rsp.paramValue);
|
||||
SendAndUpdateFilamentSensor();
|
||||
return ButtonPushed;
|
||||
case ResponseMsgParamCodes::Finished:
|
||||
// We must check whether the "finished" is actually related to the command issued into the MMU
|
||||
// It can also be an X0 F which means MMU just successfully restarted.
|
||||
if (ReqMsg().code == rsp.request.code && ReqMsg().value == rsp.request.value) {
|
||||
progressCode = ProgressCode::OK;
|
||||
errorCode = ErrorCode::OK;
|
||||
scopeState = ScopeState::Ready;
|
||||
rq = RequestMsg(RequestMsgCodes::unknown, 0); // clear the successfully finished request
|
||||
return Finished;
|
||||
}
|
||||
else {
|
||||
// got response to some other command - the originally issued command was interrupted!
|
||||
return Interrupted;
|
||||
}
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::CommandStep() {
|
||||
switch (scopeState) {
|
||||
case ScopeState::CommandSent: {
|
||||
switch (rsp.paramCode) { // the response should be either accepted or rejected
|
||||
case ResponseMsgParamCodes::Accepted:
|
||||
progressCode = ProgressCode::OK;
|
||||
errorCode = ErrorCode::RUNNING;
|
||||
scopeState = ScopeState::Wait;
|
||||
break;
|
||||
case ResponseMsgParamCodes::Rejected:
|
||||
// rejected - should normally not happen, but report the error up
|
||||
progressCode = ProgressCode::OK;
|
||||
errorCode = ErrorCode::PROTOCOL_ERROR;
|
||||
return CommandRejected;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ScopeState::QuerySent:
|
||||
return ProcessCommandQueryResponse();
|
||||
case ScopeState::FilamentSensorStateSent:
|
||||
StartReading8bitRegisters();
|
||||
return Processing;
|
||||
case ScopeState::Reading8bitRegisters:
|
||||
ProcessRead8bitRegister();
|
||||
return Processing;
|
||||
case ScopeState::Reading16bitRegisters:
|
||||
scopeState = ProcessRead16bitRegister(ScopeState::Wait);
|
||||
return Processing;
|
||||
case ScopeState::ButtonSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted)
|
||||
// Button was accepted, decrement the retry.
|
||||
DecrementRetryAttempts();
|
||||
SendAndUpdateFilamentSensor();
|
||||
break;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::IdleWait() {
|
||||
if (scopeState == ScopeState::Ready) { // check timeout
|
||||
if (Elapsed(heartBeatPeriod)) {
|
||||
SendQuery();
|
||||
return Processing;
|
||||
}
|
||||
}
|
||||
return Finished;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::IdleStep() {
|
||||
switch (scopeState) {
|
||||
case ScopeState::QuerySent: // check UART
|
||||
// If we are accidentally in Idle and we receive something like "T0 P1" - that means the communication dropped out while a command was in progress.
|
||||
// That causes no issues here, we just need to switch to Command processing and continue there from now on.
|
||||
// The usual response in this case should be some command and "F" - finished - that confirms we are in an Idle state even on the MMU side.
|
||||
switch (rsp.request.code) {
|
||||
case RequestMsgCodes::Cut:
|
||||
case RequestMsgCodes::Eject:
|
||||
case RequestMsgCodes::Load:
|
||||
case RequestMsgCodes::Mode:
|
||||
case RequestMsgCodes::Tool:
|
||||
case RequestMsgCodes::Unload:
|
||||
if (rsp.paramCode != ResponseMsgParamCodes::Finished)
|
||||
return SwitchFromIdleToCommand();
|
||||
break;
|
||||
case RequestMsgCodes::Reset:
|
||||
// this one is kind of special
|
||||
// we do not transfer to any "running" command (i.e. we stay in Idle),
|
||||
// but in case there is an error reported we must make sure it gets propagated
|
||||
switch (rsp.paramCode) {
|
||||
case ResponseMsgParamCodes::Button:
|
||||
// The user pushed a button on the MMU. Save it, do what we need to do
|
||||
// to prepare, then pass it back to the MMU so it can work its magic.
|
||||
buttonCode = static_cast<Buttons>(rsp.paramValue);
|
||||
StartReading8bitRegisters();
|
||||
return ButtonPushed;
|
||||
case ResponseMsgParamCodes::Finished:
|
||||
if (ReqMsg().code != RequestMsgCodes::unknown) {
|
||||
// got reset while doing some other command - the originally issued command was interrupted!
|
||||
// this must be solved by the upper layer, protocol logic doesn't have all the context (like unload before trying again)
|
||||
IdleRestart();
|
||||
return Interrupted;
|
||||
}
|
||||
// [[fallthrough]];
|
||||
// fall through
|
||||
case ResponseMsgParamCodes::Processing:
|
||||
// @@TODO we may actually use this branch to report progress of manual operation on the MMU
|
||||
// The MMU sends e.g. X0 P27 after its restart when the user presses an MMU button to move the Selector
|
||||
progressCode = static_cast<ProgressCode>(rsp.paramValue);
|
||||
errorCode = ErrorCode::OK;
|
||||
break;
|
||||
default:
|
||||
progressCode = ProgressCode::ERRWaitingForUser;
|
||||
errorCode = static_cast<ErrorCode>(rsp.paramValue);
|
||||
StartReading8bitRegisters(); // continue Idle state without restarting the communication
|
||||
return CommandError;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
StartReading8bitRegisters();
|
||||
return Processing;
|
||||
case ScopeState::Reading8bitRegisters:
|
||||
ProcessRead8bitRegister();
|
||||
return Processing;
|
||||
case ScopeState::Reading16bitRegisters:
|
||||
scopeState = ProcessRead16bitRegister(ScopeState::Ready);
|
||||
return scopeState == ScopeState::Ready ? Finished : Processing;
|
||||
case ScopeState::ButtonSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted)
|
||||
// Button was accepted, decrement the retry.
|
||||
DecrementRetryAttempts();
|
||||
StartReading8bitRegisters();
|
||||
return Processing;
|
||||
case ScopeState::ReadRegisterSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
|
||||
// @@TODO just dump the value onto the serial
|
||||
}
|
||||
return Finished;
|
||||
case ScopeState::WriteRegisterSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
|
||||
// @@TODO do something? Retry if not accepted?
|
||||
}
|
||||
return Finished;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
|
||||
// The "return Finished" in this state machine requires a bit of explanation:
|
||||
// The Idle state either did nothing (still waiting for the heartbeat timeout)
|
||||
// or just successfully received the answer to Q0, whatever that was.
|
||||
// In both cases, it is ready to hand over work to a command or something else,
|
||||
// therefore we are returning Finished (also to exit mmu_loop() and unblock Marlin's loop!).
|
||||
// If there is no work, we'll end up in the Idle state again
|
||||
// and we'll send the heartbeat message after the specified timeout.
|
||||
return Finished;
|
||||
}
|
||||
|
||||
ProtocolLogic::ProtocolLogic(uint8_t extraLoadDistance, uint8_t pulleySlowFeedrate)
|
||||
: explicitPrinterError(ErrorCode::OK)
|
||||
, currentScope(Scope::Stopped)
|
||||
, scopeState(ScopeState::Ready)
|
||||
, plannedRq(RequestMsgCodes::unknown, 0)
|
||||
, lastUARTActivityMs(0)
|
||||
, dataTO()
|
||||
, rsp(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0)
|
||||
, state(State::Stopped)
|
||||
, lrb(0)
|
||||
, errorCode(ErrorCode::OK)
|
||||
, progressCode(ProgressCode::OK)
|
||||
, buttonCode(Buttons::NoButton)
|
||||
, lastFSensor((uint8_t)WhereIsFilament())
|
||||
, regIndex(0)
|
||||
, retryAttempts(MMU2_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;
|
||||
currentScope = Scope::StartSeq;
|
||||
protocol.ResetResponseDecoder(); // important - finished delayed restart relies on this
|
||||
StartSeqRestart();
|
||||
}
|
||||
|
||||
void ProtocolLogic::stop() {
|
||||
state = State::Stopped;
|
||||
currentScope = Scope::Stopped;
|
||||
}
|
||||
|
||||
void ProtocolLogic::ToolChange(uint8_t slot) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Tool, slot));
|
||||
}
|
||||
|
||||
void ProtocolLogic::Statistics() {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Version, 3));
|
||||
}
|
||||
|
||||
void ProtocolLogic::UnloadFilament() {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Unload, 0));
|
||||
}
|
||||
|
||||
void ProtocolLogic::LoadFilament(uint8_t slot) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Load, slot));
|
||||
}
|
||||
|
||||
void ProtocolLogic::EjectFilament(uint8_t slot) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Eject, slot));
|
||||
}
|
||||
|
||||
void ProtocolLogic::CutFilament(uint8_t slot) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Cut, slot));
|
||||
}
|
||||
|
||||
void ProtocolLogic::ResetMMU(uint8_t mode /* = 0 */) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, mode));
|
||||
}
|
||||
|
||||
void ProtocolLogic::button(uint8_t index) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Button, index));
|
||||
}
|
||||
|
||||
void ProtocolLogic::home(uint8_t mode) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Home, mode));
|
||||
}
|
||||
|
||||
void ProtocolLogic::readRegister(uint8_t address) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Read, address));
|
||||
}
|
||||
|
||||
void ProtocolLogic::writeRegister(uint8_t address, uint16_t data) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Write, address, data));
|
||||
}
|
||||
|
||||
void ProtocolLogic::PlanGenericRequest(RequestMsg rq) {
|
||||
plannedRq = rq;
|
||||
if (!ExpectsResponse())
|
||||
ActivatePlannedRequest();
|
||||
// otherwise wait for an empty window to activate the request
|
||||
}
|
||||
|
||||
bool ProtocolLogic::ActivatePlannedRequest() {
|
||||
switch (plannedRq.code) {
|
||||
case RequestMsgCodes::Button:
|
||||
// only issue the button to the MMU and do not restart the state machines
|
||||
SendButton(plannedRq.value);
|
||||
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
return true;
|
||||
case RequestMsgCodes::Read:
|
||||
SendReadRegister(plannedRq.value, ScopeState::ReadRegisterSent);
|
||||
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
return true;
|
||||
case RequestMsgCodes::Write:
|
||||
SendWriteRegister(plannedRq.value, plannedRq.value2, ScopeState::WriteRegisterSent);
|
||||
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
return true;
|
||||
case RequestMsgCodes::unknown:
|
||||
return false;
|
||||
default: // commands
|
||||
currentScope = Scope::Command;
|
||||
SetRequestMsg(plannedRq);
|
||||
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
CommandRestart();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::SwitchFromIdleToCommand() {
|
||||
currentScope = Scope::Command;
|
||||
SetRequestMsg(rsp.request);
|
||||
// we are recovering from a communication drop out, the command is already running
|
||||
// and we have just received a response to a Q0 message about a command progress
|
||||
return ProcessCommandQueryResponse();
|
||||
}
|
||||
|
||||
void ProtocolLogic::SwitchToIdle() {
|
||||
state = State::Running;
|
||||
currentScope = Scope::Idle;
|
||||
IdleRestart();
|
||||
}
|
||||
|
||||
void ProtocolLogic::SwitchFromStartToIdle() {
|
||||
state = State::Running;
|
||||
currentScope = Scope::Idle;
|
||||
IdleRestart();
|
||||
SendQuery(); // force sending Q0 immediately
|
||||
}
|
||||
|
||||
bool ProtocolLogic::Elapsed(uint32_t timeout) const {
|
||||
return _millis() >= (lastUARTActivityMs + timeout);
|
||||
}
|
||||
|
||||
void ProtocolLogic::RecordUARTActivity() {
|
||||
lastUARTActivityMs = _millis();
|
||||
}
|
||||
|
||||
void ProtocolLogic::RecordReceivedByte(uint8_t c) {
|
||||
lastReceivedBytes[lrb] = c;
|
||||
lrb = (lrb + 1) % lastReceivedBytes.size();
|
||||
}
|
||||
|
||||
constexpr char NibbleToChar(uint8_t c) {
|
||||
switch (c) {
|
||||
case 0x0 ... 0x9: return c + '0';
|
||||
case 0xA ... 0xF: return (c - 10) + 'a';
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolLogic::FormatLastReceivedBytes(char *dst) {
|
||||
for (uint8_t i = 0; i < lastReceivedBytes.size(); ++i) {
|
||||
uint8_t b = lastReceivedBytes[(lrb - i - 1) % lastReceivedBytes.size()];
|
||||
dst[i * 3] = NibbleToChar(b >> 4);
|
||||
dst[i * 3 + 1] = NibbleToChar(b & 0xf);
|
||||
dst[i * 3 + 2] = ' ';
|
||||
}
|
||||
dst[(lastReceivedBytes.size() - 1) * 3 + 2] = 0; // terminate properly
|
||||
}
|
||||
|
||||
void ProtocolLogic::FormatLastResponseMsgAndClearLRB(char *dst) {
|
||||
*dst++ = '<';
|
||||
for (uint8_t i = 0; i < lrb; ++i) {
|
||||
uint8_t b = lastReceivedBytes[i];
|
||||
// Check for printable character, including space
|
||||
if (b < 32 || b > 127)
|
||||
b = '.';
|
||||
*dst++ = b;
|
||||
}
|
||||
*dst = 0; // terminate properly
|
||||
lrb = 0; // reset the input buffer index in case of a clean message
|
||||
}
|
||||
|
||||
void ProtocolLogic::LogRequestMsg(const uint8_t *txbuff, uint8_t size) {
|
||||
constexpr uint_fast8_t rqs = modules::protocol::Protocol::MaxRequestSize() + 1;
|
||||
char tmp[rqs] = ">";
|
||||
static char lastMsg[rqs] = "";
|
||||
for (uint8_t i = 0; i < size; ++i) {
|
||||
uint8_t b = txbuff[i];
|
||||
// Check for printable character, including space
|
||||
if (b < 32 || b > 127)
|
||||
b = '.';
|
||||
tmp[i + 1] = b;
|
||||
}
|
||||
tmp[size + 1] = 0;
|
||||
if (!strncmp_P(tmp, PSTR(">S0*c6."), rqs) && !strncmp(lastMsg, tmp, rqs)) {
|
||||
// @@TODO we skip the repeated request msgs for now
|
||||
// to avoid spoiling the whole log just with ">S0" messages
|
||||
// especially when the MMU is not connected.
|
||||
// We'll lose the ability to see if the printer is actually
|
||||
// trying to find the MMU, but since it has been reliable in the past
|
||||
// we can live without it for now.
|
||||
}
|
||||
else {
|
||||
MMU2_ECHO_MSGLN(tmp);
|
||||
}
|
||||
strncpy(lastMsg, tmp, rqs);
|
||||
}
|
||||
|
||||
void ProtocolLogic::LogError(const char *reason_P) {
|
||||
char lrb[lastReceivedBytes.size() * 3];
|
||||
FormatLastReceivedBytes(lrb);
|
||||
|
||||
MMU2_ERROR_MSGRPGM(reason_P);
|
||||
SERIAL_ECHOPGM(", last bytes: ");
|
||||
SERIAL_ECHOLN(lrb);
|
||||
}
|
||||
|
||||
void ProtocolLogic::LogResponse() {
|
||||
char lrb[lastReceivedBytes.size()];
|
||||
FormatLastResponseMsgAndClearLRB(lrb);
|
||||
MMU2_ECHO_MSGLN(lrb);
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::SuppressShortDropOuts(const char *msg_P, StepStatus ss) {
|
||||
if (dataTO.Record(ss)) {
|
||||
LogError(msg_P);
|
||||
ResetCommunicationTimeoutAttempts(); // prepare for another run of consecutive retries before firing an error
|
||||
return dataTO.InitialCause();
|
||||
}
|
||||
else {
|
||||
return Processing; // suppress short drop outs of communication
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::HandleCommunicationTimeout() {
|
||||
MMU2_SERIAL.flush(); // clear the output buffer
|
||||
protocol.ResetResponseDecoder();
|
||||
start();
|
||||
return SuppressShortDropOuts(PSTR("Communication timeout"), CommunicationTimeout);
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::HandleProtocolError() {
|
||||
MMU2_SERIAL.flush(); // clear the output buffer
|
||||
state = State::InitSequence;
|
||||
currentScope = Scope::DelayedRestart;
|
||||
DelayedRestartRestart();
|
||||
return SuppressShortDropOuts(PSTR("Protocol Error"), ProtocolError);
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::Step() {
|
||||
if (!ExpectsResponse()) // if not waiting for a response, activate a planned request immediately
|
||||
ActivatePlannedRequest();
|
||||
auto currentStatus = ScopeStep();
|
||||
switch (currentStatus) {
|
||||
case Processing:
|
||||
// we are ok, the state machine continues correctly
|
||||
break;
|
||||
case Finished: {
|
||||
// We are ok, switching to Idle if there is no potential next request planned.
|
||||
// But the trouble is we must report a finished command if the previous command has just been finished
|
||||
// i.e. only try to find some planned command if we just finished the Idle cycle
|
||||
if (!ActivatePlannedRequest()) { // if nothing is planned, switch to Idle
|
||||
SwitchToIdle();
|
||||
}
|
||||
else if (ExpectsResponse()) {
|
||||
// if the previous cycle was Idle and now we have planned a new command -> avoid returning Finished
|
||||
currentStatus = Processing;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CommandRejected:
|
||||
// we have to repeat it - that's the only thing we can do
|
||||
// no change in state
|
||||
// @@TODO wait until Q0 returns command in progress finished, then we can send this one
|
||||
LogError(PSTR("Command rejected"));
|
||||
CommandRestart();
|
||||
break;
|
||||
case CommandError:
|
||||
LogError(PSTR("Command Error"));
|
||||
// we should probably transfer into the Idle state and await further instructions from the upper layer
|
||||
// Idle state may solve the problem of keeping up the heart beat running
|
||||
break;
|
||||
case VersionMismatch:
|
||||
LogError(PSTR("Version mismatch"));
|
||||
break;
|
||||
case ProtocolError:
|
||||
currentStatus = HandleProtocolError();
|
||||
break;
|
||||
case CommunicationTimeout:
|
||||
currentStatus = HandleCommunicationTimeout();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// special handling of explicit printer errors
|
||||
return IsPrinterError() ? StepStatus::PrinterError : currentStatus;
|
||||
}
|
||||
|
||||
uint8_t ProtocolLogic::CommandInProgress() const {
|
||||
if (currentScope != Scope::Command) return 0;
|
||||
return (uint8_t)ReqMsg().code;
|
||||
}
|
||||
|
||||
void ProtocolLogic::DecrementRetryAttempts() {
|
||||
if (inAutoRetry && retryAttempts) {
|
||||
SERIAL_ECHOLNPGM("DecrementRetryAttempts");
|
||||
retryAttempts--;
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolLogic::ResetRetryAttempts() {
|
||||
SERIAL_ECHOLNPGM("ResetRetryAttempts");
|
||||
retryAttempts = MMU2_MAX_RETRIES;
|
||||
}
|
||||
|
||||
void __attribute__((noinline)) ProtocolLogic::ResetCommunicationTimeoutAttempts() {
|
||||
SERIAL_ECHOLNPGM("RSTCommTimeout");
|
||||
dataTO.reset();
|
||||
}
|
||||
|
||||
bool DropOutFilter::Record(StepStatus ss) {
|
||||
if (occurrences == maxOccurrences) cause = ss;
|
||||
--occurrences;
|
||||
return occurrences == 0;
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
397
Marlin/src/feature/mmu3/mmu2_protocol_logic.h
Normal file
397
Marlin/src/feature/mmu3/mmu2_protocol_logic.h
Normal file
|
@ -0,0 +1,397 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_protocol_logic.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
#include "mmu_hw/buttons.h"
|
||||
#include "mmu_hw/registers.h"
|
||||
#include "mmu2_protocol.h"
|
||||
|
||||
// #include <array> std array is not available on AVR ... we need to "fake" it
|
||||
namespace std {
|
||||
template <typename T, uint8_t N>
|
||||
class array {
|
||||
T data[N];
|
||||
public:
|
||||
array() = default;
|
||||
inline constexpr T *begin() const { return data; }
|
||||
inline constexpr T *end() const { return data + N; }
|
||||
static constexpr uint8_t size() { return N; }
|
||||
inline T &operator[](uint8_t i) { return data[i]; }
|
||||
};
|
||||
} // std
|
||||
|
||||
#else // !__AVR__
|
||||
|
||||
#include <array>
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
|
||||
// Prevent ARM HAL macros from breaking our code
|
||||
#undef CRC
|
||||
#include "mmu2_protocol.h"
|
||||
#include "mmu_hw/buttons.h"
|
||||
#include "registers.h"
|
||||
|
||||
#endif // !__AVR__
|
||||
|
||||
// New MMU3 protocol logic
|
||||
namespace MMU3 {
|
||||
|
||||
using namespace modules::protocol;
|
||||
|
||||
class ProtocolLogic;
|
||||
|
||||
// ProtocolLogic stepping statuses
|
||||
enum StepStatus : uint_fast8_t {
|
||||
Processing = 0,
|
||||
MessageReady, //!< A message has been successfully decoded from the received bytes
|
||||
Finished, //!< Scope finished successfully
|
||||
Interrupted, //!< Received "Finished" message related to a different command than originally issued (most likely the MMU restarted while doing something)
|
||||
CommunicationTimeout, //!< The MMU failed to respond to a request within a specified time frame
|
||||
ProtocolError, //!< Bytes read from the MMU didn't form a valid response
|
||||
CommandRejected, //!< The MMU rejected the command due to some other command in progress, may be the user is operating the MMU locally (button commands)
|
||||
CommandError, //!< The command in progress stopped due to unrecoverable error, user interaction required
|
||||
VersionMismatch, //!< The MMU reports its firmware version incompatible with our implementation
|
||||
PrinterError, //!< Printer's explicit error - MMU is fine, but the printer was unable to complete the requested operation
|
||||
CommunicationRecovered,
|
||||
ButtonPushed //!< The MMU reported the user pushed one of its three buttons.
|
||||
};
|
||||
|
||||
/*inline*/ constexpr uint32_t linkLayerTimeout = 2000; //!< Default link layer communication timeout
|
||||
/*inline*/ constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; //!< Data layer communication timeout
|
||||
/*inline*/ constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; //!< Period of heart beat messages (Q0)
|
||||
|
||||
static_assert(heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts");
|
||||
|
||||
//!< Filter of short consecutive drop outs which are recovered instantly
|
||||
class DropOutFilter {
|
||||
public:
|
||||
static constexpr uint8_t maxOccurrences = 10; // ideally set this to >8 seconds -> 12x heartBeatPeriod
|
||||
static_assert(maxOccurrences > 1, "we should really silently ignore at least 1 comm drop out if recovered immediately afterwards");
|
||||
DropOutFilter() = default;
|
||||
|
||||
// @return true if the error should be reported to higher levels (max. number of consecutive occurrences reached)
|
||||
bool Record(StepStatus ss);
|
||||
|
||||
// @return the initial cause which started this drop out event
|
||||
inline StepStatus InitialCause() const { return cause; }
|
||||
|
||||
// Rearms the object for further processing - basically call this once the MMU responds with something meaningful (e.g. S0 A2)
|
||||
inline void reset() { occurrences = maxOccurrences; }
|
||||
|
||||
private:
|
||||
StepStatus cause;
|
||||
uint8_t occurrences = maxOccurrences;
|
||||
};
|
||||
|
||||
// Logic layer of the MMU vs. printer communication protocol
|
||||
class ProtocolLogic {
|
||||
public:
|
||||
ProtocolLogic(uint8_t extraLoadDistance, uint8_t pulleySlowFeedrate);
|
||||
|
||||
// Start/Enable communication with the MMU
|
||||
void start();
|
||||
|
||||
// Stop/Disable communication with the MMU
|
||||
void stop();
|
||||
|
||||
// Issue commands to the MMU
|
||||
void ToolChange(uint8_t slot);
|
||||
void Statistics();
|
||||
void UnloadFilament();
|
||||
void LoadFilament(uint8_t slot);
|
||||
void EjectFilament(uint8_t slot);
|
||||
void CutFilament(uint8_t slot);
|
||||
void ResetMMU(uint8_t mode=0);
|
||||
void button(uint8_t index);
|
||||
void home(uint8_t mode);
|
||||
void readRegister(uint8_t address);
|
||||
void writeRegister(uint8_t address, uint16_t data);
|
||||
|
||||
// Set the extra load distance to be reported to the MMU.
|
||||
// Beware - this call doesn't send anything to the MMU.
|
||||
// The MMU gets the newly set value either by a communication restart or via an explicit writeRegister call
|
||||
inline void PlanExtraLoadDistance(uint8_t eld_mm) { initRegs8[0] = eld_mm; }
|
||||
// @return the currently preset extra load distance
|
||||
inline uint8_t ExtraLoadDistance() const { return initRegs8[0]; }
|
||||
|
||||
// Sets the Pulley slow feed rate to be reported to the MMU.
|
||||
// Beware - this call doesn't send anything to the MMU.
|
||||
// The MMU gets the newly set value either by a communication restart or via an explicit writeRegister call
|
||||
inline void PlanPulleySlowFeedRate(uint8_t psfr) {
|
||||
initRegs8[1] = psfr;
|
||||
}
|
||||
// @return the currently preset Pulley slow feed rate
|
||||
inline uint8_t PulleySlowFeedRate() const {
|
||||
return initRegs8[1]; // even though MMU register 0x14 is 16bit, reasonable speeds are way below 255mm/s - saving space ;)
|
||||
}
|
||||
|
||||
// Step the state machine
|
||||
StepStatus Step();
|
||||
|
||||
// @return the current/latest error code as reported by the MMU
|
||||
ErrorCode Error() const { return errorCode; }
|
||||
|
||||
// @return the current/latest process code as reported by the MMU
|
||||
ProgressCode Progress() const { return progressCode; }
|
||||
|
||||
// @return the current/latest button code as reported by the MMU
|
||||
Buttons button() const { return buttonCode; }
|
||||
|
||||
uint8_t CommandInProgress() const;
|
||||
|
||||
inline bool Running() const { return state == State::Running; }
|
||||
|
||||
inline bool findaPressed() const { return regs8[0]; }
|
||||
|
||||
inline uint16_t FailStatistics() const { return regs16[0]; }
|
||||
|
||||
inline uint8_t mmuFwVersionMajor() const { return mmuFwVersion[0]; }
|
||||
inline uint8_t mmuFwVersionMinor() const { return mmuFwVersion[1]; }
|
||||
inline uint8_t mmuFwVersionRevision() const { 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();
|
||||
|
||||
void ResetCommunicationTimeoutAttempts();
|
||||
|
||||
constexpr bool InAutoRetry() const { return inAutoRetry; }
|
||||
inline void SetInAutoRetry(const bool iar) { inAutoRetry = iar; }
|
||||
|
||||
inline void SetPrinterError(const ErrorCode ec) { explicitPrinterError = ec; }
|
||||
inline void clearPrinterError() { explicitPrinterError = ErrorCode::OK; }
|
||||
inline bool IsPrinterError() const { return explicitPrinterError != ErrorCode::OK; }
|
||||
inline ErrorCode PrinterError() const { return explicitPrinterError; }
|
||||
|
||||
#ifndef UNITTEST
|
||||
private:
|
||||
#endif
|
||||
|
||||
StepStatus ExpectingMessage();
|
||||
void SendMsg(RequestMsg rq);
|
||||
void SendWriteMsg(RequestMsg rq);
|
||||
void SwitchToIdle();
|
||||
StepStatus SuppressShortDropOuts(const char *msg_P, StepStatus ss);
|
||||
StepStatus HandleCommunicationTimeout();
|
||||
StepStatus HandleProtocolError();
|
||||
bool Elapsed(uint32_t timeout) const;
|
||||
void RecordUARTActivity();
|
||||
void RecordReceivedByte(uint8_t c);
|
||||
void FormatLastReceivedBytes(char *dst);
|
||||
void FormatLastResponseMsgAndClearLRB(char *dst);
|
||||
void LogRequestMsg(const uint8_t *txbuff, uint8_t size);
|
||||
void LogError(const char *reason_P);
|
||||
void LogResponse();
|
||||
StepStatus SwitchFromIdleToCommand();
|
||||
void SwitchFromStartToIdle();
|
||||
|
||||
ErrorCode explicitPrinterError;
|
||||
|
||||
enum class State : uint_fast8_t {
|
||||
Stopped, //!< stopped for whatever reason
|
||||
InitSequence, //!< initial sequence running
|
||||
Running //!< normal operation - Idle + Command processing
|
||||
};
|
||||
|
||||
enum class Scope : uint_fast8_t {
|
||||
Stopped,
|
||||
StartSeq,
|
||||
DelayedRestart,
|
||||
Idle,
|
||||
Command
|
||||
};
|
||||
Scope currentScope;
|
||||
|
||||
// basic scope members
|
||||
// @return true if the state machine is waiting for a response from the MMU
|
||||
bool ExpectsResponse() const { return ((uint8_t)scopeState & (uint8_t)ScopeState::NotExpectsResponse) == 0; }
|
||||
|
||||
// Common internal states of the derived sub-automata
|
||||
// General rule of thumb: *Sent states are waiting for a response from the MMU
|
||||
enum class ScopeState : uint_fast8_t {
|
||||
S0Sent, // beware - due to optimization reasons these SxSent must be kept one after another
|
||||
S1Sent,
|
||||
S2Sent,
|
||||
S3Sent,
|
||||
QuerySent,
|
||||
CommandSent,
|
||||
FilamentSensorStateSent,
|
||||
Reading8bitRegisters,
|
||||
Reading16bitRegisters,
|
||||
WritingInitRegisters,
|
||||
ButtonSent,
|
||||
ReadRegisterSent, // standalone requests for reading registers - from higher layers
|
||||
WriteRegisterSent,
|
||||
|
||||
// States which do not expect a message - MSb set
|
||||
NotExpectsResponse = 0x80,
|
||||
Wait = NotExpectsResponse + 1,
|
||||
Ready = NotExpectsResponse + 2,
|
||||
RecoveringProtocolError = NotExpectsResponse + 3,
|
||||
};
|
||||
|
||||
ScopeState scopeState; //!< internal state of the sub-automaton
|
||||
|
||||
// @return the status of processing of the FINDA query response
|
||||
// @param finishedRV returned value in case the message was successfully received and processed
|
||||
// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
|
||||
// StepStatus ProcessFINDAReqSent(StepStatus finishedRV, State nextState);
|
||||
|
||||
// @return the status of processing of the statistics query response
|
||||
// @param finishedRV returned value in case the message was successfully received and processed
|
||||
// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
|
||||
// StepStatus ProcessStatisticsReqSent(StepStatus finishedRV, State nextState);
|
||||
|
||||
// Called repeatedly while waiting for a query (Q0) period.
|
||||
// All event checks to report immediately from the printer to the MMU should be done in this method.
|
||||
// So far, the only such a case is the filament sensor, but there can be more like this in the future.
|
||||
void CheckAndReportAsyncEvents();
|
||||
void SendQuery();
|
||||
void StartReading8bitRegisters();
|
||||
void ProcessRead8bitRegister();
|
||||
void StartReading16bitRegisters();
|
||||
ScopeState ProcessRead16bitRegister(ProtocolLogic::ScopeState stateAtEnd);
|
||||
void StartWritingInitRegisters();
|
||||
// @return true when all registers have been written into the MMU
|
||||
bool ProcessWritingInitRegister();
|
||||
void SendAndUpdateFilamentSensor();
|
||||
void SendButton(uint8_t btn);
|
||||
void SendVersion(uint8_t stage);
|
||||
void SendReadRegister(uint8_t index, ScopeState nextState);
|
||||
void SendWriteRegister(uint8_t index, uint16_t value, ScopeState nextState);
|
||||
|
||||
StepStatus ProcessVersionResponse(uint8_t stage);
|
||||
|
||||
// Top level split - calls the appropriate step based on current scope
|
||||
StepStatus ScopeStep();
|
||||
|
||||
static constexpr uint8_t maxRetries = 6;
|
||||
uint8_t retries;
|
||||
|
||||
void StartSeqRestart();
|
||||
void DelayedRestartRestart();
|
||||
void IdleRestart();
|
||||
void CommandRestart();
|
||||
|
||||
StepStatus StartSeqStep();
|
||||
StepStatus DelayedRestartWait();
|
||||
StepStatus IdleStep();
|
||||
StepStatus IdleWait();
|
||||
StepStatus CommandStep();
|
||||
StepStatus CommandWait();
|
||||
StepStatus StoppedStep() { return Processing; }
|
||||
|
||||
StepStatus ProcessCommandQueryResponse();
|
||||
|
||||
inline void SetRequestMsg(const RequestMsg msg) { rq = msg; }
|
||||
inline const RequestMsg &ReqMsg() const { return rq; }
|
||||
RequestMsg rq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
|
||||
// Records the next planned state, "unknown" msg code if no command is planned.
|
||||
// This is not intended to be a queue of commands to process, protocol_logic must not queue commands.
|
||||
// It exists solely to prevent breaking the Request-Response protocol handshake -
|
||||
// - during tests it turned out, that the commands from Marlin are coming in such an asynchronnous way, that
|
||||
// we could accidentally send T2 immediately after Q0 without waiting for reception of response to Q0.
|
||||
//
|
||||
// Beware, if Marlin manages to call PlanGenericCommand multiple times before a response comes,
|
||||
// these variables will get overwritten by the last call.
|
||||
// However, that should not happen under normal circumstances as Marlin should wait for the Command to finish,
|
||||
// which includes all responses (and error recovery if any).
|
||||
RequestMsg plannedRq;
|
||||
|
||||
// Plan a command to be processed once the immediate response to a sent request arrives
|
||||
void PlanGenericRequest(RequestMsg rq);
|
||||
// Activate the planned state once the immediate response to a sent request arrived
|
||||
bool ActivatePlannedRequest();
|
||||
|
||||
uint32_t lastUARTActivityMs; //!< timestamp - last ms when something occurred on the UART
|
||||
DropOutFilter dataTO; //!< Filter of short consecutive drop outs which are recovered instantly
|
||||
|
||||
ResponseMsg rsp; //!< decoded response message from the MMU protocol
|
||||
|
||||
State state; //!< internal state of ProtocolLogic
|
||||
|
||||
Protocol protocol; //!< protocol codec
|
||||
|
||||
std::array<uint8_t, 16> lastReceivedBytes; //!< remembers the last few bytes of incoming communication for diagnostic purposes
|
||||
uint8_t lrb;
|
||||
|
||||
ErrorCode errorCode; //!< last received error code from the MMU
|
||||
ProgressCode progressCode; //!< last received progress code from the MMU
|
||||
Buttons buttonCode; //!< Last received button from the MMU.
|
||||
|
||||
uint8_t lastFSensor; //!< last state of filament sensor
|
||||
|
||||
#ifndef __AVR__
|
||||
uint8_t txbuff[Protocol::MaxRequestSize()]; //!< In Buddy FW - a static transmit buffer needs to exist as DMA cannot be used from CCMRAM.
|
||||
//!< On MK3/S/+ the transmit buffer is allocated on the stack without restrictions
|
||||
#endif
|
||||
|
||||
// 8bit registers
|
||||
static constexpr uint8_t regs8Count = 3;
|
||||
static_assert(regs8Count > 0); // code is not ready for empty lists of registers
|
||||
static const Register regs8Addrs[regs8Count] PROGMEM;
|
||||
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 Register regs16Addrs[regs16Count] PROGMEM;
|
||||
uint16_t regs16[regs16Count] = { 0, 0 };
|
||||
|
||||
// 8bit init values to be sent to the MMU after line up
|
||||
static constexpr uint8_t initRegs8Count = 2;
|
||||
static_assert(initRegs8Count > 0); // code is not ready for empty lists of registers
|
||||
static const Register initRegs8Addrs[initRegs8Count] PROGMEM;
|
||||
uint8_t initRegs8[initRegs8Count];
|
||||
|
||||
uint8_t regIndex;
|
||||
|
||||
uint8_t mmuFwVersion[3] = { 0, 0, 0 };
|
||||
uint16_t mmuFwVersionBuild;
|
||||
|
||||
uint8_t retryAttempts;
|
||||
bool inAutoRetry;
|
||||
|
||||
friend class MMU3;
|
||||
};
|
||||
|
||||
} // MMU3
|
704
Marlin/src/feature/mmu3/mmu2_reporting.cpp
Normal file
704
Marlin/src/feature/mmu3/mmu2_reporting.cpp
Normal file
|
@ -0,0 +1,704 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_reporting.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2.h"
|
||||
#include "mmu2_log.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
#include "mmu2_reporting.h"
|
||||
#include "mmu2_error_converter.h"
|
||||
#include "mmu2_marlin_macros.h"
|
||||
#include "mmu2_progress_converter.h"
|
||||
#include "mmu_hw/buttons.h"
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/errors_list.h"
|
||||
#include "ultralcd.h"
|
||||
#include "sound.h"
|
||||
|
||||
#include "../../core/language.h"
|
||||
#include "../../gcode/gcode.h"
|
||||
#include "../../feature/host_actions.h"
|
||||
#include "../../lcd/marlinui.h"
|
||||
#include "../../lcd/menu/menu.h"
|
||||
#include "../../lcd/menu/menu_item.h"
|
||||
#include "../../module/temperature.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
OperationStatistics operation_statistics;
|
||||
|
||||
uint16_t OperationStatistics::fail_total_num; // total failures
|
||||
uint8_t OperationStatistics::fail_num; // fails during print
|
||||
uint16_t OperationStatistics::load_fail_total_num; // total load failures
|
||||
uint8_t OperationStatistics::load_fail_num; // load failures during print
|
||||
uint16_t OperationStatistics::tool_change_counter; // number of tool changes per print
|
||||
uint32_t OperationStatistics::tool_change_total_counter; // number of total tool changes
|
||||
int OperationStatistics::fail_total_num_addr; // total failures EEPROM addr
|
||||
int OperationStatistics::fail_num_addr; // fails during print EEPROM addr
|
||||
int OperationStatistics::load_fail_total_num_addr; // total load failures EEPROM addr
|
||||
int OperationStatistics::load_fail_num_addr; // load failures during print EEPROM addr
|
||||
int OperationStatistics::tool_change_counter_addr; // number of total tool changes EEPROM addr
|
||||
int OperationStatistics::tool_change_total_counter_addr; // number of total tool changes EEPROM addr
|
||||
|
||||
/**
|
||||
* Increment both the total load fails and Per print job load fails.
|
||||
*/
|
||||
void OperationStatistics::increment_load_fails() {
|
||||
load_fail_num += 1;
|
||||
load_fail_total_num += 1;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// save load_fail_num to eeprom
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(load_fail_num_addr, load_fail_num);
|
||||
|
||||
// save load_fail_total_num to eeprom
|
||||
persistentStore.write_data(load_fail_total_num_addr, load_fail_total_num);
|
||||
persistentStore.access_finish();
|
||||
settings.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment both the total fails and the per print job fails.
|
||||
*/
|
||||
void OperationStatistics::increment_mmu_fails() {
|
||||
fail_num += 1;
|
||||
fail_total_num += 1;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// save fail_num to eeprom
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(fail_num_addr, fail_num);
|
||||
// save fail_total_num to eeprom
|
||||
persistentStore.write_data(fail_total_num_addr, fail_total_num);
|
||||
persistentStore.access_finish();
|
||||
settings.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment tool change counter
|
||||
*/
|
||||
void OperationStatistics::increment_tool_change_counter() {
|
||||
tool_change_counter += 1;
|
||||
tool_change_total_counter += 1;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// save tool_change_total_counter to eeprom
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(tool_change_total_counter_addr, tool_change_total_counter);
|
||||
persistentStore.access_finish();
|
||||
settings.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset only per print operation statistics and update EEPROM.
|
||||
*
|
||||
* @return true if everything went okay, false otherwise.
|
||||
*/
|
||||
bool OperationStatistics::reset_per_print_stats() {
|
||||
// Update data
|
||||
load_fail_num = 0;
|
||||
fail_num = 0;
|
||||
tool_change_counter = 0;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// Update EEPROM
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(load_fail_num_addr, load_fail_num);
|
||||
persistentStore.write_data(fail_num_addr, fail_num);
|
||||
persistentStore.write_data(tool_change_counter_addr, tool_change_counter);
|
||||
persistentStore.access_finish();
|
||||
return settings.save();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset fail statistics and update EEPROM.
|
||||
*
|
||||
* This will keep the tool change counter change counters and delete anything
|
||||
* else.
|
||||
*
|
||||
* @return true if everything went okay, false otherwise.
|
||||
*/
|
||||
bool OperationStatistics::reset_fail_stats() {
|
||||
// Update data
|
||||
load_fail_num = 0;
|
||||
load_fail_total_num = 0;
|
||||
fail_num = 0;
|
||||
fail_total_num = 0;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// Update EEPROM
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(load_fail_num_addr, load_fail_num);
|
||||
persistentStore.write_data(load_fail_total_num_addr, load_fail_total_num);
|
||||
persistentStore.write_data(fail_num_addr, fail_num);
|
||||
persistentStore.write_data(fail_total_num_addr, fail_total_num);
|
||||
persistentStore.access_finish();
|
||||
return settings.save();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset all operation statistics and update EEPROM.
|
||||
*
|
||||
* @return true if everything went okay, false otherwise.
|
||||
*/
|
||||
bool OperationStatistics::reset_stats() {
|
||||
// Update data
|
||||
load_fail_num = 0;
|
||||
load_fail_total_num = 0;
|
||||
fail_num = 0;
|
||||
fail_total_num = 0;
|
||||
tool_change_counter = 0;
|
||||
tool_change_total_counter = 0;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// Update EEPROM
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(load_fail_num_addr, load_fail_num);
|
||||
persistentStore.write_data(load_fail_total_num_addr, load_fail_total_num);
|
||||
persistentStore.write_data(fail_num_addr, fail_num);
|
||||
persistentStore.write_data(fail_total_num_addr, fail_total_num);
|
||||
persistentStore.write_data(tool_change_counter_addr, tool_change_counter);
|
||||
persistentStore.write_data(tool_change_total_counter_addr, tool_change_total_counter);
|
||||
persistentStore.access_finish();
|
||||
return settings.save();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void BeginReport(CommandInProgress /*cip*/, ProgressCode ec) {
|
||||
// custom_message_type = CustomMsg::MMUProgress;
|
||||
ui.set_status(ProgressCodeToText(ec));
|
||||
}
|
||||
|
||||
void EndReport(CommandInProgress /*cip*/, ProgressCode /*ec*/) {
|
||||
// clear the status msg line - let the printed filename get visible again
|
||||
if (!printJobOngoing()) ui.reset_status();
|
||||
//custom_message_type = CustomMsg::Status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Renders any characters that will be updated live on the MMU error screen.
|
||||
*Currently, this is FINDA and Filament Sensor status and Extruder temperature.
|
||||
*/
|
||||
extern void ReportErrorHookDynamicRender(void) {
|
||||
#if HAS_WIRED_LCD
|
||||
// beware - this optimization abuses the fact, that findaDetectsFilament returns 0 or 1 and '0' is followed by '1' in the ASCII table
|
||||
lcd_put_int(3, LCD_HEIGHT - 1, mmu3.findaDetectsFilament() + '0');
|
||||
lcd_put_int(8, LCD_HEIGHT - 1, FILAMENT_PRESENT() + '0');
|
||||
|
||||
// print active/changing filament slot
|
||||
lcd_moveto(10, LCD_HEIGHT - 1);
|
||||
lcdui_print_extruder();
|
||||
|
||||
// Print active extruder temperature
|
||||
lcd_put_int(16, LCD_HEIGHT - 1, (int)(thermalManager.degHotend(0) + 0.5));
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool drawing_more_info_screen = false;
|
||||
static bool msg_next_is_consumed = false;
|
||||
static FSTR_P msg_next = nullptr;
|
||||
|
||||
/**
|
||||
* Display more info about the error. If the error message doesn't fit into
|
||||
* the screen, clicking the LCD button will go to the next screen to display
|
||||
* the rest of the message, until no messages left to display and a final
|
||||
* click will return to the previous screen.
|
||||
*
|
||||
* This gets the message data from the "editable.uint8" which is set in the
|
||||
* action item.
|
||||
*/
|
||||
void show_more_info_screen() {
|
||||
#if HAS_WIRED_LCD
|
||||
if (drawing_more_info_screen) return;
|
||||
drawing_more_info_screen = true;
|
||||
FSTR_P fmsg = PrusaErrorDesc(editable.uint8);
|
||||
if (ui.use_click()) {
|
||||
if (msg_next_is_consumed) {
|
||||
msg_next_is_consumed = false;
|
||||
drawing_more_info_screen = false;
|
||||
msg_next = nullptr;
|
||||
// Prevent this function being triggered again...
|
||||
SetButtonResponse(ButtonOperations::NoOperation);
|
||||
return ui.go_back();
|
||||
}
|
||||
fmsg = msg_next;
|
||||
}
|
||||
else if (msg_next_is_consumed) {
|
||||
fmsg = msg_next;
|
||||
}
|
||||
|
||||
FSTR_P const msg_next_int = lcd_display_message_fullscreen(fmsg);
|
||||
msg_next_is_consumed = strlen_P(FTOP(msg_next_int)) == 0;
|
||||
if (!msg_next_is_consumed) msg_next = msg_next_int;
|
||||
// Set the button response to MoreInfo so we keep coming back to this screen until all messages are consumed
|
||||
SetButtonResponse(ButtonOperations::MoreInfo);
|
||||
#else
|
||||
// no lcd, no error display... just break the loop...
|
||||
msg_next_is_consumed = false;
|
||||
msg_next = nullptr;
|
||||
SetButtonResponse(ButtonOperations::NoOperation);
|
||||
#endif // HAS_WIRED_LCD
|
||||
drawing_more_info_screen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Renders any characters that are static on the MMU error screen i.e. they don't change.
|
||||
* @param[in] ei Error code index
|
||||
*/
|
||||
static void ReportErrorHookStaticRender(uint8_t ei) {
|
||||
#if HAS_WIRED_LCD
|
||||
//! Show an error screen
|
||||
//! When an MMU error occurs, the LCD content will look like this:
|
||||
//! |01234567890123456789|
|
||||
//! |MMU FW update needed| <- title/header of the error: max 20 characters
|
||||
//! |prusa.io/04504 | <- URL max 20 characters
|
||||
//! |FI:1 FS:1 5>3 t201°| <- status line, t is thermometer symbol
|
||||
//! |>Retry >Done >W| <- buttons
|
||||
bool two_choices = false;
|
||||
|
||||
// Read and determine what operations should be shown on the menu
|
||||
const uint8_t button_operation = PrusaErrorButtons(ei),
|
||||
button_op_right = BUTTON_OP_RIGHT(button_operation),
|
||||
button_op_middle = BUTTON_OP_MIDDLE(button_operation);
|
||||
|
||||
// Check if the menu should have three or two choices
|
||||
if (button_op_right == (uint8_t)ButtonOperations::NoOperation) {
|
||||
// Two operations not specified, the error menu should only show two choices
|
||||
two_choices = true;
|
||||
}
|
||||
|
||||
START_MENU();
|
||||
#ifndef __AVR__
|
||||
// TODO: I couldn't make this work on AVR
|
||||
STATIC_ITEM_F(PrusaErrorTitle(ei), SS_DEFAULT | SS_INVERT);
|
||||
|
||||
// Write the help page and error code
|
||||
MString<LCD_WIDTH> url("");
|
||||
url.appendf("prusa.io/04%hu", PrusaErrorCode(ei));
|
||||
STATIC_ITEM_F(nullptr, SS_DEFAULT, url.buffer());
|
||||
|
||||
//ReportErrorHookSensorLineRender();
|
||||
|
||||
editable.uint8 = button_op_middle;
|
||||
ACTION_ITEM_F(
|
||||
PrusaErrorButtonTitle(button_op_middle),
|
||||
[]{ SetButtonResponse((ButtonOperations)editable.uint8); }
|
||||
);
|
||||
|
||||
if (!two_choices) {
|
||||
editable.uint8 = button_op_right;
|
||||
ACTION_ITEM_F(
|
||||
PrusaErrorButtonTitle(button_op_right),
|
||||
[]{ SetButtonResponse((ButtonOperations)editable.uint8); }
|
||||
);
|
||||
}
|
||||
|
||||
// Add a More Info option
|
||||
editable.uint8 = ei;
|
||||
ACTION_ITEM_F(
|
||||
GET_TEXT_F(MSG_BTN_MORE),
|
||||
[]{
|
||||
// only when the menu item is used push the current screen back
|
||||
ui.push_current_screen();
|
||||
msg_next_is_consumed = false;
|
||||
msg_next = nullptr;
|
||||
SetButtonResponse(ButtonOperations::MoreInfo);
|
||||
}
|
||||
);
|
||||
|
||||
#endif // !__AVR__
|
||||
|
||||
// Render the choices
|
||||
//if (two_choices) {
|
||||
// lcd_show_choices_prompt_P(
|
||||
// LCD_LEFT_BUTTON_CHOICE,
|
||||
// PrusaErrorButtonTitle(button_op_middle),
|
||||
// GET_TEXT(MSG_BTN_MORE),
|
||||
// 18, nullptr
|
||||
// );
|
||||
//}
|
||||
//else {
|
||||
// lcd_show_choices_prompt_P(LCD_MIDDLE_BUTTON_CHOICE,
|
||||
// PrusaErrorButtonTitle(button_op_middle),
|
||||
// PrusaErrorButtonTitle(button_op_right),
|
||||
// 9, GET_TEXT(MSG_BTN_MORE)
|
||||
// );
|
||||
//}
|
||||
|
||||
END_MENU();
|
||||
//ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
|
||||
#endif // HAS_WIRED_LCD
|
||||
}
|
||||
|
||||
void ReportErrorHookSensorLineRender() {
|
||||
#if HAS_WIRED_LCD
|
||||
// Render static characters in third line
|
||||
lcd_put_u8str(
|
||||
0,
|
||||
LCD_HEIGHT - 1,
|
||||
F("FI: FS: > " LCD_STR_THERMOMETER " " LCD_STR_DEGREE)
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Monitors the LCD button selection without blocking MMU communication
|
||||
* @param[in] ei Error code index
|
||||
* @return 0 if there is no knob click --
|
||||
* 1 if user clicked 'More' and firmware should render
|
||||
* the error screen when ReportErrorHook is called next --
|
||||
* 2 if the user selects an operation and we would like
|
||||
* to exit the error screen. The MMU will raise the menu
|
||||
* again if the error is not solved.
|
||||
*/
|
||||
static uint8_t ReportErrorHookMonitor(uint8_t ei) {
|
||||
uint8_t ret = 0;
|
||||
if (GetButtonResponse() == ButtonOperations::MoreInfo) {
|
||||
SetButtonResponse(ButtonOperations::NoOperation);
|
||||
ret = 1;
|
||||
}
|
||||
else if (GetButtonResponse() != ButtonOperations::NoOperation) {
|
||||
ret = 2;
|
||||
}
|
||||
// Next MMU error screen should reset the choice selection
|
||||
// reset_button_selection = 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum class ReportErrorHookStates : uint8_t {
|
||||
RENDER_ERROR_SCREEN = 0,
|
||||
MONITOR_SELECTION = 1,
|
||||
DISMISS_ERROR_SCREEN = 2,
|
||||
};
|
||||
|
||||
enum ReportErrorHookStates ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
|
||||
// Helper variable to monitor knob in MMU error screen in blocking functions e.g. manage_response
|
||||
static bool is_mmu_error_monitor_active;
|
||||
|
||||
// Helper variable to stop rendering the error screen when the firmware is rendering complementary
|
||||
// UI to resolve the error screen, for example tuning Idler Stallguard Threshold
|
||||
// Set to false to allow the error screen to render again.
|
||||
static bool putErrorScreenToSleep;
|
||||
|
||||
void CheckErrorScreenUserInput() {
|
||||
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((CommandInProgress)mmu3.getCommandInProgress(), mmu3.getLastErrorCode(), mmu3.mmuLastErrorSource());
|
||||
}
|
||||
}
|
||||
|
||||
bool TuneMenuEntered() {
|
||||
return putErrorScreenToSleep;
|
||||
}
|
||||
|
||||
void ReportErrorHook(CommandInProgress /*cip*/, ErrorCode ec, uint8_t /*es*/) {
|
||||
if (putErrorScreenToSleep) return;
|
||||
|
||||
if (mmu3.mmuCurrentErrorCode() == ErrorCode::OK && mmu3.mmuLastErrorSource() == MMU3::ErrorSourceMMU) {
|
||||
// If the error code suddenly changes to OK, that means
|
||||
// a button was pushed on the MMU and the LCD should
|
||||
// dismiss the error screen until MMU raises a new error
|
||||
ReportErrorHookState = ReportErrorHookStates::DISMISS_ERROR_SCREEN;
|
||||
drawing_more_info_screen = false;
|
||||
msg_next_is_consumed = true;
|
||||
}
|
||||
|
||||
const uint8_t ei = PrusaErrorCodeIndex((ErrorCode)ec);
|
||||
|
||||
// This should be the equivelent of the switch..case above...
|
||||
if ((uint8_t)ReportErrorHookState == (uint8_t)ReportErrorHookStates::RENDER_ERROR_SCREEN) {
|
||||
KEEPALIVE_STATE(PAUSED_FOR_USER);
|
||||
#if HAS_WIRED_LCD
|
||||
drawing_more_info_screen = false;
|
||||
msg_next_is_consumed = false;
|
||||
msg_next = nullptr;
|
||||
editable.uint8 = ei;
|
||||
ui.defer_status_screen();
|
||||
ui.goto_screen([]{ ReportErrorHookStaticRender(editable.uint8); });
|
||||
#endif
|
||||
ReportErrorHookState = ReportErrorHookStates::MONITOR_SELECTION;
|
||||
}
|
||||
|
||||
if ((uint8_t)ReportErrorHookState == (uint8_t)ReportErrorHookStates::MONITOR_SELECTION) {
|
||||
is_mmu_error_monitor_active = true;
|
||||
// ReportErrorHookDynamicRender(); // Render dynamic characters
|
||||
sound_wait_for_user();
|
||||
uint8_t result = ReportErrorHookMonitor(ei);
|
||||
if (result == 0) {
|
||||
// No choice selected, return to loop()
|
||||
}
|
||||
else if (result == 1) {
|
||||
// More Info button selected, change state
|
||||
editable.uint8 = ei;
|
||||
//ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
|
||||
ui.goto_screen(show_more_info_screen);
|
||||
ReportErrorHookState = ReportErrorHookStates::MONITOR_SELECTION;
|
||||
}
|
||||
else if (result == 2) {
|
||||
// Exit error screen and enable lcd updates
|
||||
TERN_(HAS_WIRED_LCD, ui.return_to_status());
|
||||
sound_wait_for_user_reset();
|
||||
// Reset the state in case a new error is reported
|
||||
is_mmu_error_monitor_active = false;
|
||||
KEEPALIVE_STATE(IN_HANDLER);
|
||||
ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
}
|
||||
return; // Always return to loop() to let MMU trigger a call to ReportErrorHook again
|
||||
}
|
||||
else if ((uint8_t)ReportErrorHookState == (uint8_t)ReportErrorHookStates::DISMISS_ERROR_SCREEN) {
|
||||
TERN_(HAS_WIRED_LCD, ui.return_to_status());
|
||||
sound_wait_for_user_reset();
|
||||
// Reset the state in case a new error is reported
|
||||
is_mmu_error_monitor_active = false;
|
||||
KEEPALIVE_STATE(IN_HANDLER);
|
||||
ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
}
|
||||
}
|
||||
|
||||
void ReportProgressHook(CommandInProgress cip, ProgressCode ec) {
|
||||
if (cip != CommandInProgress::NoCommand) {
|
||||
// custom_message_type = CustomMsg::MMUProgress;
|
||||
ui.set_status(ProgressCodeToText(ec));
|
||||
}
|
||||
}
|
||||
|
||||
TryLoadUnloadReporter::TryLoadUnloadReporter(float delta_mm)
|
||||
: dpixel0(0), dpixel1(0), lcd_cursor_col(0)
|
||||
, pixel_per_mm(0.5F * float(LCD_WIDTH) / (delta_mm)
|
||||
) {
|
||||
//lcd_clearstatus();
|
||||
ui.reset_status();
|
||||
}
|
||||
|
||||
TryLoadUnloadReporter::~TryLoadUnloadReporter() {
|
||||
#if HAS_WIRED_LCD
|
||||
// Delay the next status message just so
|
||||
// the user can see the results clearly
|
||||
ui.set_status_no_expire(ui.status_message);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TryLoadUnloadReporter::Render(uint8_t col, bool sensorState) {
|
||||
#if HAS_WIRED_LCD
|
||||
// Set the cursor position each time in case some other
|
||||
// part of the firmware changes the cursor position
|
||||
lcd_insert_char_into_status(col, sensorState ? LCD_STR_SOLID_BLOCK[0] : '-');
|
||||
if (ui.lcdDrawUpdate == LCDViewAction::LCDVIEW_NONE)
|
||||
ui.draw_status_message(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TryLoadUnloadReporter::Progress(bool sensorState) {
|
||||
// Always round up, you can only have 'whole' pixels. (floor is also an option)
|
||||
dpixel1 = ceil((stepper_get_machine_position_E_mm() - planner_get_current_position_E()) * pixel_per_mm);
|
||||
if (dpixel1 - dpixel0) {
|
||||
dpixel0 = dpixel1;
|
||||
if (lcd_cursor_col > (LCD_WIDTH - 1)) lcd_cursor_col = LCD_WIDTH - 1;
|
||||
Render(lcd_cursor_col++, sensorState);
|
||||
}
|
||||
}
|
||||
|
||||
void TryLoadUnloadReporter::DumpToSerial() {
|
||||
char buf[LCD_WIDTH + 1];
|
||||
TERN_(HAS_WIRED_LCD, ui.status_message.copyto(buf));
|
||||
for (uint8_t i = 0; i < sizeof(buf); i++) {
|
||||
// 0xFF is -1 when converting from unsigned to signed char
|
||||
// If the number is negative, that means filament is present
|
||||
buf[i] = (buf[i] < 0) ? '1' : '0';
|
||||
}
|
||||
buf[LCD_WIDTH] = 0;
|
||||
MMU2_ECHO_MSGLN(buf);
|
||||
}
|
||||
|
||||
void IncrementLoadFails() {
|
||||
operation_statistics.increment_load_fails();
|
||||
}
|
||||
|
||||
void IncrementMMUFails() {
|
||||
operation_statistics.increment_mmu_fails();
|
||||
}
|
||||
|
||||
bool cutter_enabled() {
|
||||
return mmu3.cutter_mode > 0;
|
||||
}
|
||||
|
||||
void MakeSound(SoundType s) {
|
||||
Sound_MakeSound((eSOUND_TYPE)s);
|
||||
}
|
||||
|
||||
static void fullScreenMsg(FSTR_P const fstr, uint8_t slot) {
|
||||
#if HAS_WIRED_LCD
|
||||
ui.clear_lcd();
|
||||
#ifndef __AVR__
|
||||
SETCURSOR(0, 1);
|
||||
lcd_put_u8str(fstr);
|
||||
lcd_put_lchar(' ');
|
||||
lcd_put_int(slot + 1);
|
||||
#else
|
||||
UNUSED(fstr);
|
||||
#endif
|
||||
ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
|
||||
ui.screen_changed = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void fullScreenMsgCut(uint8_t slot) { fullScreenMsg(GET_TEXT_F(MSG_CUT_FILAMENT), slot); }
|
||||
void fullScreenMsgEject(uint8_t slot) { fullScreenMsg(GET_TEXT_F(MSG_EJECT_FROM_MMU), slot); }
|
||||
void fullScreenMsgTest(uint8_t slot) { fullScreenMsg(GET_TEXT_F(MSG_TESTING_FILAMENT), slot); }
|
||||
void fullScreenMsgLoad(uint8_t slot) { fullScreenMsg(GET_TEXT_F(MSG_LOADING_FILAMENT), slot); }
|
||||
|
||||
void fullScreenMsgRestoringTemperature() {
|
||||
#if HAS_WIRED_LCD
|
||||
lcd_display_message_fullscreen(F("MMU Retry: Restoring temperature..."));
|
||||
#endif
|
||||
}
|
||||
|
||||
void ScreenUpdateEnable() {
|
||||
TERN_(HAS_WIRED_LCD, ui.refresh(LCDVIEW_CALL_REDRAW_NEXT));
|
||||
}
|
||||
|
||||
void ScreenClear() { ui.clear_lcd(); }
|
||||
|
||||
struct TuneItem {
|
||||
uint8_t address;
|
||||
uint8_t minValue;
|
||||
uint8_t maxValue;
|
||||
}
|
||||
__attribute__((packed));
|
||||
|
||||
static const TuneItem TuneItems[] PROGMEM = {
|
||||
{ (uint8_t)Register::Selector_sg_thrs_R, 1, 4 },
|
||||
{ (uint8_t)Register::Idler_sg_thrs_R, 2, 10 },
|
||||
};
|
||||
|
||||
static_assert(COUNT(TuneItems) == 2);
|
||||
|
||||
typedef struct {
|
||||
// Variables used when editing values.
|
||||
const char* editLabel;
|
||||
uint8_t editValueBits; // 8 or 16
|
||||
void* editValuePtr;
|
||||
int16_t currentValue;
|
||||
int16_t minEditValue;
|
||||
int16_t maxEditValue;
|
||||
int16_t minJumpValue;
|
||||
} menu_data_edit_t;
|
||||
|
||||
struct _menu_tune_data_t {
|
||||
menu_data_edit_t reserved; // 13 bytes reserved for number editing functions
|
||||
int8_t status; // 1 byte
|
||||
uint8_t currentValue; // 1 byte
|
||||
TuneItem item; // 3 bytes
|
||||
};
|
||||
|
||||
//static_assert(sizeof(_menu_tune_data_t) == 18);
|
||||
//static_assert(sizeof(menu_data)>= sizeof(_menu_tune_data_t), "_menu_tune_data_t doesn't fit into menu_data");
|
||||
|
||||
void tuneIdlerStallguardThresholdMenu() {
|
||||
// const uint8_t menu_data[32] = "Set Stallguard Threshold";
|
||||
// //static constexpr _menu_tune_data_t * const _md = (_menu_tune_data_t*)&(menu_data[0]);
|
||||
// static constexpr _menu_tune_data_t * const _md = (_menu_tune_data_t*)&(menu_data[0]);
|
||||
|
||||
// // Do not timeout the screen, otherwise there will be FW crash (menu recursion)
|
||||
// //lcd_timeoutToStatus.stop();
|
||||
//if (_md->status == 0) {
|
||||
// _md->status = 1; // Menu entered for the first time
|
||||
|
||||
// // Fetch the TuneItem from PROGMEM
|
||||
// const uint8_t offset = (mmu3.mmuCurrentErrorCode() == ErrorCode::HOMING_IDLER_FAILED) ? 1 : 0;
|
||||
// memcpy_P(&(_md->item), &TuneItems[offset], sizeof(TuneItem));
|
||||
|
||||
// // Fetch the value which is currently in MMU EEPROM
|
||||
// mmu3.readRegister(_md->item.address);
|
||||
// _md->currentValue = mmu3.getLastReadRegisterValue();
|
||||
//}
|
||||
|
||||
// //MENU_BEGIN();
|
||||
// //ON_MENU_LEAVE(
|
||||
// // mmu3.writeRegister(_md->item.address, (uint16_t)_md->currentValue);
|
||||
// // putErrorScreenToSleep = false;
|
||||
// // lcd_return_to_status();
|
||||
// // return;
|
||||
// //);
|
||||
// //MENU_ITEM_BACK(MSG_DONE);
|
||||
// //MENU_ITEM_EDIT_int3_P(
|
||||
// // _i("Sensitivity"), ////MSG_MMU_SENSITIVITY c=18
|
||||
// // &_md->currentValue,
|
||||
// // _md->item.minValue,
|
||||
// // _md->item.maxValue
|
||||
// //);
|
||||
// //MENU_END();
|
||||
|
||||
//START_MENU();
|
||||
//BACK_ITEM(MSG_BACK);
|
||||
//EDIT_ITEM(
|
||||
// int8,
|
||||
// MSG_MMU_SENSITIVITY,
|
||||
// &_md->currentValue,
|
||||
// _md->item.minValue,
|
||||
// _md->item.maxValue,
|
||||
// []{
|
||||
// write_register_and_return_to_status_menu(_md->item.address, _md->currentValue);
|
||||
// }
|
||||
// );
|
||||
//END_MENU();
|
||||
}
|
||||
|
||||
void write_register_and_return_to_status_menu(uint8_t address, uint8_t value) {
|
||||
mmu3.writeRegister(address, (uint16_t)value);
|
||||
putErrorScreenToSleep = false;
|
||||
ui.return_to_status();
|
||||
}
|
||||
|
||||
void tuneIdlerStallguardThreshold() {
|
||||
putErrorScreenToSleep = true;
|
||||
//menu_submenu(tuneIdlerStallguardThresholdMenu);
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
168
Marlin/src/feature/mmu3/mmu2_reporting.h
Normal file
168
Marlin/src/feature/mmu3/mmu2_reporting.h
Normal file
|
@ -0,0 +1,168 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_reporting.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
enum CommandInProgress : uint8_t {
|
||||
NoCommand = 0,
|
||||
CutFilament = 'K',
|
||||
EjectFilament = 'E',
|
||||
Homing = 'H',
|
||||
LoadFilament = 'L',
|
||||
Reset = 'X',
|
||||
ToolChange = 'T',
|
||||
UnloadFilament = 'U',
|
||||
};
|
||||
|
||||
/**
|
||||
* Data class for MMU operation statistics.
|
||||
*
|
||||
* This is used to load/save data from/to EEPROM.
|
||||
* The data is initialized by the settings.load() method.
|
||||
*/
|
||||
class OperationStatistics {
|
||||
public:
|
||||
void increment_load_fails();
|
||||
void increment_mmu_fails();
|
||||
void increment_tool_change_counter();
|
||||
bool reset_per_print_stats(); // Reset only the per print stats.
|
||||
bool reset_fail_stats(); // Reset only fail stats and keep tool change counters
|
||||
bool reset_stats(); // Reset MMU stats and update EEPROM
|
||||
|
||||
static uint16_t fail_total_num; // total failures
|
||||
static uint8_t fail_num; // fails during print
|
||||
static uint16_t load_fail_total_num; // total load failures
|
||||
static uint8_t load_fail_num; // load failures during print
|
||||
static uint16_t tool_change_counter; // number of tool changes during print
|
||||
static uint32_t tool_change_total_counter; // number of total tool changes
|
||||
static int fail_total_num_addr; // total failures EEPROM addr
|
||||
static int fail_num_addr; // fails during print EEPROM addr
|
||||
static int load_fail_total_num_addr; // total load failures EEPROM addr
|
||||
static int load_fail_num_addr; // load failures during print EEPROM addr
|
||||
static int tool_change_counter_addr; // number of tool changes EEPROM addr
|
||||
static int tool_change_total_counter_addr; // number of total tool changes EEPROM addr
|
||||
};
|
||||
|
||||
extern OperationStatistics operation_statistics;
|
||||
|
||||
// Called at the begin of every MMU operation
|
||||
void BeginReport(CommandInProgress cip, ProgressCode ec);
|
||||
|
||||
// Called at the end of every MMU operation
|
||||
void EndReport(CommandInProgress cip, ProgressCode ec);
|
||||
|
||||
// Checks for error screen user input, if the error screen is open
|
||||
void CheckErrorScreenUserInput();
|
||||
|
||||
// Return true if the error screen is sleeping in the background
|
||||
// Error screen sleeps when the firmware is rendering complementary
|
||||
// UI to resolve the error screen, for example tuning Idler Stallguard Threshold
|
||||
bool TuneMenuEntered();
|
||||
|
||||
// @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, ErrorCode ec, uint8_t es);
|
||||
|
||||
// Called when the MMU sends operation progress update
|
||||
void ReportProgressHook(CommandInProgress cip, ProgressCode ec);
|
||||
|
||||
struct TryLoadUnloadReporter {
|
||||
TryLoadUnloadReporter(float delta_mm);
|
||||
~TryLoadUnloadReporter();
|
||||
void Progress(bool sensorState);
|
||||
void DumpToSerial();
|
||||
|
||||
private:
|
||||
// @brief Add one block to the progress bar
|
||||
// @param col pixel position on the LCD status line, should range from 0 to (LCD_WIDTH - 1)
|
||||
// @param sensorState if true, filament is not present, else filament is present. This controls which character to render
|
||||
void Render(uint8_t col, bool sensorState);
|
||||
|
||||
uint8_t dpixel0, dpixel1;
|
||||
uint8_t lcd_cursor_col;
|
||||
// The total length is twice delta_mm. Divide that length by number of pixels
|
||||
// available to get length per pixel.
|
||||
// Note: Below is the reciprocal of (2 * delta_mm) / LCD_WIDTH [mm/pixel]
|
||||
float pixel_per_mm;
|
||||
};
|
||||
|
||||
// Remders the sensor status line. Also used by the "resume temperature" screen.
|
||||
void ReportErrorHookDynamicRender();
|
||||
|
||||
// Renders the static part of the sensor state line. Also used by "resuming temperature screen"
|
||||
void ReportErrorHookSensorLineRender();
|
||||
|
||||
// @return true if the MMU is communicating and available. Can change at runtime.
|
||||
//bool MMUAvailable();
|
||||
|
||||
// Global Enable/Disable use MMU (to be stored in EEPROM)
|
||||
//bool UseMMU();
|
||||
|
||||
// Disable MMU in EEPROM
|
||||
//void DisableMMUInSettings();
|
||||
|
||||
// Increments EEPROM cell - number of failed loads into the nozzle
|
||||
// Note: technically, this is not an MMU error but an error of the printer.
|
||||
void IncrementLoadFails();
|
||||
|
||||
// Increments EEPROM cell - number of MMU errors
|
||||
void IncrementMMUFails();
|
||||
|
||||
// @return true when Cutter is enabled in the menus
|
||||
bool cutter_enabled();
|
||||
|
||||
// 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();
|
||||
|
||||
void tuneIdlerStallguardThreshold();
|
||||
|
||||
void write_register_and_return_to_status_menu(uint8_t address, uint8_t value);
|
||||
|
||||
} // MMU3
|
48
Marlin/src/feature/mmu3/mmu2_state.h
Normal file
48
Marlin/src/feature/mmu3/mmu2_state.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_state.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MMU3 {
|
||||
/**
|
||||
* @brief status of mmu
|
||||
*
|
||||
* States of a printer with the MMU:
|
||||
* - Active
|
||||
* - Connecting
|
||||
* - Stopped
|
||||
*
|
||||
* When the printer's FW starts, the MMU 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.
|
||||
};
|
||||
|
||||
} // MMU3
|
32
Marlin/src/feature/mmu3/mmu2_supported_version.h
Normal file
32
Marlin/src/feature/mmu3/mmu2_supported_version.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_supported_version.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define mmuVersionMajor 3
|
||||
#define mmuVersionMinor 0
|
||||
#define mmuVersionPatch 2
|
159
Marlin/src/feature/mmu3/mmu3-serial-protocol.md
Normal file
159
Marlin/src/feature/mmu3/mmu3-serial-protocol.md
Normal file
|
@ -0,0 +1,159 @@
|
|||
# MMU3 Messages
|
||||
|
||||
Starting with the version 2.0.19 of the MMU firmware, requests and responses have a trailing section that contains the CRC8 of the original message. The general structure is as follows:
|
||||
|
||||
```
|
||||
Requests (what Marlin requests):
|
||||
MMU3:>{RequestMsgCode}{Value}*{CRC8}\n
|
||||
|
||||
Responses (what MMU responds with):
|
||||
MMU3:<{RequestMsgCode}{Value} {ResponseMsgParamCode}{paramValue}*{CRC8}\n
|
||||
```
|
||||
|
||||
An example of that would be:
|
||||
|
||||
```
|
||||
MMU3:>S0*c6\n
|
||||
MMU3:<S0 A3*22\n
|
||||
|
||||
MMU3:>S1*ad\n
|
||||
MMU3:<S1 A0*34\n
|
||||
|
||||
MMU3:>S2*10\n
|
||||
MMU3:<S2 A2*65\n
|
||||
```
|
||||
|
||||
This set of responses combines to indicate firmware version 3.0.2.
|
||||
|
||||
## Startup sequence
|
||||
|
||||
When initialized the MMU waits for requests. Marlin repeatedly sends `S0` commands until it gets an answer:
|
||||
```
|
||||
MMU3:>S0*c6\n
|
||||
MMU3:>S0*c6\n
|
||||
MMU3:>S0*c6\n
|
||||
...
|
||||
```
|
||||
|
||||
Once communication is established the MMU responds with:
|
||||
```
|
||||
MMU3:<S0 A3*22\n
|
||||
```
|
||||
|
||||
Then Marlin continues to get the rest of the MMU firmware version.
|
||||
```
|
||||
MMU3:>S1*ad\n
|
||||
MMU3:<S1 A0*34\n
|
||||
MMU3:>S2*10\n
|
||||
MMU3:<S2 A2*65\n
|
||||
```
|
||||
|
||||
Setting the stepper mode to SpreadCycle (M0) or StealthChop (M1):
|
||||
```
|
||||
MMU3:>M1*{CRC8};
|
||||
MMU3:<---nothing---
|
||||
```
|
||||
|
||||
```
|
||||
MMU3:>P0
|
||||
MMU3:<P0 A{FINDA status}*{CRC8}\n
|
||||
```
|
||||
|
||||
At this point we can be sure the MMU is available and ready. If there was a timeout or other communication problem somewhere, the printer will not be killed, but for safety the MMU feature will be disabled.
|
||||
|
||||
- *Firmware version* is an integer value, and we care about it. As there is no other way of knowing which protocol to use.
|
||||
- *FINDA status* is 1 if the filament is loaded to the extruder, 0 otherwise.
|
||||
|
||||
The *Firmware version* is checked against the required value. If it doesn't match the printer will not be halted, but for safety the MMU feature will be disabled.
|
||||
|
||||
## Toolchange
|
||||
|
||||
```
|
||||
MMU3:>T{Filament index}*{CRC8}\n
|
||||
MMU3:<Q0*ea\n
|
||||
```
|
||||
|
||||
The MMU sends:
|
||||
```
|
||||
MMU3:<T{filament index}*P{ProgressCode}{CRC8}\n
|
||||
```
|
||||
|
||||
Which in normal operation would be as follows, let's say that we requested MMU to load `T0``:
|
||||
```
|
||||
MMU3:>T0*{CRC8}\n
|
||||
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
MMU3:<T0*P5{CRC8}\n # P5 => FeedingToFinda
|
||||
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
MMU3:<T0*P7{CRC8}\n # P7 => FeedingToNozzle
|
||||
```
|
||||
|
||||
As soon as the filament is fed down to the extruder we follow with:
|
||||
|
||||
```
|
||||
MMU3:>C0*{CRC8}\n
|
||||
```
|
||||
|
||||
The MMU will feed a few more millimeters of filament for the extruder gears to grab. When done, the MMU sends:
|
||||
```
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
MMU3:<T0*P9{CRC8}\n # P9 => FinishingMoves
|
||||
```
|
||||
|
||||
After the `T0*P9` response we immediately continue with the next G-code which should be one or more extruder moves to feed the filament into the hotend.
|
||||
|
||||
|
||||
## FINDA status
|
||||
```
|
||||
MMU3:>P0*{CRC8}\n
|
||||
```
|
||||
|
||||
If the filament is loaded to the extruder, FINDA status is 1 and the MMU responds with:
|
||||
```
|
||||
MMU3:<P0 A1*{CRC8}\n
|
||||
```
|
||||
|
||||
…otherwise it replies:
|
||||
```
|
||||
MMU3:<P0 A0*7b\n
|
||||
```
|
||||
|
||||
This could be used as a filament runout sensor if polled regularly.
|
||||
|
||||
## Load filament
|
||||
|
||||
To load a filament to the MMU itself, we run:
|
||||
```
|
||||
MMU3:>L{Filament index}*{CRC8}\n
|
||||
MMU3:<L{Filament index} A1*{CRC8}\n
|
||||
```
|
||||
|
||||
…and immediately after that we query the status:
|
||||
```
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
```
|
||||
|
||||
The MMU will respond with status messages:
|
||||
```
|
||||
MMU3:<L0*P5{CRC8}\n
|
||||
```
|
||||
|
||||
The MMU will load the filament and when done:
|
||||
```
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
MMU3:<L0*P9{CRC8}\n
|
||||
```
|
||||
|
||||
## Unload filament
|
||||
|
||||
- `MMU <= 'U0\n'`
|
||||
|
||||
The MMU will retract current filament from the extruder, and when done:
|
||||
|
||||
- `MMU => 'ok\n'`
|
||||
|
||||
## Eject filament
|
||||
|
||||
- `MMU <= 'E*Filament index*\n'`
|
||||
- `MMU => 'ok\n'`
|
73
Marlin/src/feature/mmu3/mmu_hw/buttons.h
Normal file
73
Marlin/src/feature/mmu3/mmu_hw/buttons.h
Normal file
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* buttons.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Helper macros to parse the operations from Btns()
|
||||
#define BUTTON_OP_RIGHT(X) ((X & 0xF0) >> 4)
|
||||
#define BUTTON_OP_MIDDLE(X) (X & 0x0F)
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Will be mapped onto dialog button responses in the FW
|
||||
// Those responses have their unique+translated texts as well
|
||||
enum class ButtonOperations : uint8_t {
|
||||
NoOperation = 0,
|
||||
Retry = 1,
|
||||
Continue = 2,
|
||||
ResetMMU = 3,
|
||||
Unload = 4,
|
||||
Load = 5,
|
||||
Eject = 6,
|
||||
Tune = 7,
|
||||
StopPrint = 8,
|
||||
DisableMMU = 9,
|
||||
MoreInfo = 10,
|
||||
};
|
||||
|
||||
// Button codes + extended actions performed on the printer's side
|
||||
enum class Buttons : uint_least8_t {
|
||||
Right = 0,
|
||||
Middle,
|
||||
Left,
|
||||
|
||||
// Performed on the printer's side
|
||||
ResetMMU,
|
||||
Load,
|
||||
Eject,
|
||||
StopPrint,
|
||||
DisableMMU,
|
||||
TuneMMU, // Printer changes MMU register value
|
||||
|
||||
NoButton = 0xFF // should be kept last
|
||||
};
|
||||
|
||||
constexpr uint_least8_t buttons_to_uint8t(Buttons b) {
|
||||
return static_cast<uint8_t>(b);
|
||||
}
|
||||
|
||||
} // MMU3
|
55
Marlin/src/feature/mmu3/mmu_hw/check-pce.sh
Executable file
55
Marlin/src/feature/mmu3/mmu_hw/check-pce.sh
Executable file
|
@ -0,0 +1,55 @@
|
|||
#!/bin/bash
|
||||
|
||||
# download Prusa Error Codes for MMU
|
||||
#wget https://raw.githubusercontent.com/3d-gussner/Prusa-Error-Codes/master/04_MMU/error-codes.yaml --output-document=error-codes.yaml
|
||||
wget https://raw.githubusercontent.com/prusa3d/Prusa-Error-Codes/master/04_MMU/error-codes.yaml --output-document=error-codes.yaml
|
||||
|
||||
oifs="$IFS" ## save original IFS
|
||||
IFS=$'\n' ## set IFS to break on newline
|
||||
codes=($(cat error-codes.yaml |grep "code:" |cut -d '"' -f2))
|
||||
titles=($(cat error-codes.yaml |grep 'title:' |cut -d '"' -f2))
|
||||
texts=($(cat error-codes.yaml |grep "text:" |cut -d '"' -f2))
|
||||
actions=($(cat error-codes.yaml |grep "action:" |cut -d ':' -f2))
|
||||
ids=($(cat error-codes.yaml |grep "id:" |cut -d '"' -f2))
|
||||
IFS="$oifs" ## restore original IFS
|
||||
|
||||
filename=errors_list.h
|
||||
|
||||
clear
|
||||
for ((i = 0; i < ${#codes[@]}; i++)) do
|
||||
code=${codes[i]}
|
||||
id=$(cat $filename |grep "${code#04*}" | cut -d "=" -f1 | cut -d "_" -f3- |cut -d " " -f1)
|
||||
title=$(cat $filename |grep "${id}" |grep --max-count=1 "MSG_TITLE" |cut -d '"' -f2)
|
||||
text=$(cat $filename |grep "${id}" |grep --max-count=1 "MSG_DESC" |cut -d '"' -f2)
|
||||
action1=$(cat $filename |grep "),//$id"| cut -d "," -f1)
|
||||
action2=$(cat $filename |grep "),//$id"| cut -d "," -f2)
|
||||
action1=$(echo $action1 | cut -d ":" -f2- |cut -d ":" -f2)
|
||||
action2=$(echo $action2 | cut -d ":" -f2- |cut -d ":" -f2 |cut -d ")" -f1)
|
||||
if [ "$action2" == "NoOperation" ]; then
|
||||
action=" [$action1]"
|
||||
else
|
||||
action=" [$action1,$action2]"
|
||||
fi
|
||||
echo -n "code: $code |"
|
||||
if [ "$id" != "${ids[i]}" ]; then
|
||||
echo -n "$(tput setaf 1) $id $(tput sgr0) # $(tput setaf 2)${ids[i]}$(tput sgr0)|"
|
||||
else
|
||||
echo -n " $id |"
|
||||
fi
|
||||
if [ "$title" != "${titles[i]}" ]; then
|
||||
echo -n "$(tput setaf 1) $title $(tput sgr0) # $(tput setaf 2)${titles[i]}$(tput sgr0)|"
|
||||
else
|
||||
echo -n " $title |"
|
||||
fi
|
||||
if [ "$text" != "${texts[i]}" ]; then
|
||||
echo -n "$(tput setaf 1) $text $(tput sgr0) # $(tput setaf 2)${texts[i]}$(tput sgr0)|"
|
||||
else
|
||||
echo -n " $text |"
|
||||
fi
|
||||
if [ "$action" != "${actions[i]}" ]; then
|
||||
echo -n "$(tput setaf 1) $action $(tput sgr0) # $(tput setaf 2)${actions[i]}$(tput sgr0)|"
|
||||
else
|
||||
echo -n " $action |"
|
||||
fi
|
||||
echo
|
||||
done
|
151
Marlin/src/feature/mmu3/mmu_hw/error_codes.h
Normal file
151
Marlin/src/feature/mmu3/mmu_hw/error_codes.h
Normal file
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* error_codes.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* A complete set of error codes which may be a result of a high-level command/operation.
|
||||
* This header file should be included in the printer's firmware as well as a reference,
|
||||
* therefore the error codes have been extracted to one place.
|
||||
*
|
||||
* Please note the errors are intentionally coded as "negative" values (highest bit set),
|
||||
* becase they are a complement to reporting the state of the high-level state machines -
|
||||
* positive values are considered as normal progress, negative values are errors.
|
||||
*
|
||||
* Please note, that multiple TMC errors can occur at once, thus they are defined as a bitmask of the higher byte.
|
||||
* Also, as there are 3 TMC drivers on the board, each error is added a bit for the corresponding TMC -
|
||||
* TMC_PULLEY_BIT, TMC_SELECTOR_BIT, TMC_IDLER_BIT,
|
||||
* The resulting error is a bitwise OR over 3 TMC drivers and their status, which should cover most of the situations correctly.
|
||||
*/
|
||||
enum class ErrorCode : uint_fast16_t {
|
||||
RUNNING = 0x0000, //!< the operation is still running - keep this value as ZERO as it is used for initialization of error codes as well
|
||||
OK = 0x0001, //!< the operation finished OK
|
||||
|
||||
// TMC bit masks
|
||||
TMC_PULLEY_BIT = 0x0040, //!< +64 TMC Pulley bit
|
||||
TMC_SELECTOR_BIT = 0x0080, //!< +128 TMC Pulley bit
|
||||
TMC_IDLER_BIT = 0x0100, //!< +256 TMC Pulley bit
|
||||
|
||||
// Unload Filament related error codes
|
||||
FINDA_DIDNT_SWITCH_ON = 0x8001, //!< E32769 FINDA didn't switch on while loading filament - either there is something blocking the metal ball or a cable is broken/disconnected
|
||||
FINDA_DIDNT_SWITCH_OFF = 0x8002, //!< E32770 FINDA didn't switch off while unloading filament
|
||||
|
||||
FSENSOR_DIDNT_SWITCH_ON = 0x8003, //!< E32771 Filament sensor didn't switch on while performing LoadFilament
|
||||
FSENSOR_DIDNT_SWITCH_OFF = 0x8004, //!< E32772 Filament sensor didn't switch off while performing UnloadFilament
|
||||
|
||||
FILAMENT_ALREADY_LOADED = 0x8005, //!< E32773 cannot perform operation LoadFilament or move the selector as the filament is already loaded
|
||||
|
||||
INVALID_TOOL = 0x8006, //!< E32774 tool/slot index out of range (typically issuing T5 into an MMU with just 5 slots - valid range 0-4)
|
||||
|
||||
HOMING_FAILED = 0x8007, //!< generic homing failed error - always reported with the corresponding axis bit set (Idler or Selector) as follows:
|
||||
HOMING_SELECTOR_FAILED = HOMING_FAILED | TMC_SELECTOR_BIT, //!< E32903 the Selector was unable to home properly - that means something is blocking its movement
|
||||
HOMING_IDLER_FAILED = HOMING_FAILED | TMC_IDLER_BIT, //!< E33031 the Idler was unable to home properly - that means something is blocking its movement
|
||||
STALLED_PULLEY = HOMING_FAILED | TMC_PULLEY_BIT, //!< E32839 for the Pulley "homing" means just StallGuard detected during Pulley's operation (Pulley doesn't home)
|
||||
|
||||
FINDA_VS_EEPROM_DISREPANCY = 0x8008, //!< E32776 FINDA is pressed but we have no such record in EEPROM - this can only happen at the start of the MMU and can be resolved by issuing an Unload command
|
||||
|
||||
FSENSOR_TOO_EARLY = 0x8009, //!< E32777 FSensor triggered while doing FastFeedToBondtech - that means either:
|
||||
//!< - the PTFE is too short
|
||||
//!< - a piece of filament was left inside - pushed in front of the loaded filament causing the fsensor trigger too early
|
||||
//!< - fsensor is faulty producing bogus triggers
|
||||
|
||||
FINDA_FLICKERS = 0x800A, //!< FINDA flickers - seems to be badly calibrated and happens to be pressed at spots where it used to be not pressed before.
|
||||
//!< The user is obliged to inspect FINDA and tune its switching
|
||||
|
||||
MOVE_FAILED = 0x800B, //!< generic move failed error - always reported with the corresponding axis bit set (Idler or Selector) as follows:
|
||||
MOVE_SELECTOR_FAILED = MOVE_FAILED | TMC_SELECTOR_BIT, //!< E32905 the Selector was unable to move to desired position properly - that means something is blocking its movement, e.g. a piece of filament got out of pulley body
|
||||
MOVE_IDLER_FAILED = MOVE_FAILED | TMC_IDLER_BIT, //!< E33033 the Idler was unable to move - unused at the time of creation, but added for completeness
|
||||
MOVE_PULLEY_FAILED = MOVE_FAILED | TMC_PULLEY_BIT, //!< E32841 the Pulley was unable to move - unused at the time of creation, but added for completeness
|
||||
|
||||
FILAMENT_EJECTED = 0x800C, //!< Filament was ejected, waiting for user input - technically, this is not an error
|
||||
|
||||
MCU_UNDERVOLTAGE_VCC = 0x800D, //!< MCU VCC rail undervoltage.
|
||||
|
||||
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
|
||||
MMU_NOT_RESPONDING = 0x802E, //!< E32814 internal error of the printer - communication with the MMU is not working
|
||||
INTERNAL = 0x802F, //!< E32815 internal runtime error (software)
|
||||
|
||||
// TMC driver init error - TMC dead or bad communication
|
||||
// - E33344 Pulley TMC driver
|
||||
// - E33408 Selector TMC driver
|
||||
// - E33536 Idler TMC driver
|
||||
// - E33728 All 3 TMC driver
|
||||
TMC_IOIN_MISMATCH = 0x8200,
|
||||
|
||||
// TMC driver reset - recoverable, we just need to rehome the axis
|
||||
// Idler: can be rehomed any time
|
||||
// Selector: if there is a filament, remove it and rehome, if there is no filament, just rehome
|
||||
// Pulley: do nothing - for the loading sequence - just restart and move slowly, for the unload sequence just restart
|
||||
// - E33856 Pulley TMC driver
|
||||
// - E33920 Selector TMC driver
|
||||
// - E34048 Idler TMC driver
|
||||
// - E34240 All 3 TMC driver
|
||||
TMC_RESET = 0x8400,
|
||||
|
||||
// not enough current for the TMC, NOT RECOVERABLE
|
||||
// - E34880 Pulley TMC driver
|
||||
// - E34944 Selector TMC driver
|
||||
// - E35072 Idler TMC driver
|
||||
// - E35264 All 3 TMC driver
|
||||
TMC_UNDERVOLTAGE_ON_CHARGE_PUMP = 0x8800,
|
||||
|
||||
// TMC driver serious error - short to ground on coil A or coil B - dangerous to recover
|
||||
// - E36928 Pulley TMC driver
|
||||
// - E36992 Selector TMC driver
|
||||
// - E37120 Idler TMC driver
|
||||
// - E37312 All 3 TMC driver
|
||||
TMC_SHORT_TO_GROUND = 0x9000,
|
||||
|
||||
// TMC driver over temperature warning - can be recovered by restarting the driver.
|
||||
// If this error happens, we should probably go into the error state as soon as the current command is finished.
|
||||
// The driver technically still works at this point.
|
||||
// - E41024 Pulley TMC driver
|
||||
// - E41088 Selector TMC driver
|
||||
// - E41216 Idler TMC driver
|
||||
// - E41408 All 3 TMC driver
|
||||
TMC_OVER_TEMPERATURE_WARN = 0xA000,
|
||||
|
||||
// TMC driver over temperature error - we really shouldn't ever reach this error.
|
||||
// It can still be recovered if the driver cools down below 120C.
|
||||
// The driver needs to be disabled and enabled again for operation to resume after this error is cleared.
|
||||
// - E49216 Pulley TMC driver
|
||||
// - E49280 Selector TMC driver
|
||||
// - E49408 Idler TMC driver
|
||||
// - E49600 All 3 TMC driver
|
||||
TMC_OVER_TEMPERATURE_ERROR = 0xC000,
|
||||
|
||||
// TMC driver - IO pins are unreliable. While in theory it's recoverable, in practice it most likely
|
||||
// means your hardware is borked (we can't command the drivers reliably via STEP/EN/DIR due to electrical
|
||||
// issues or hardware fault. Possible "fixable" cause is undervoltage on the 5v logic line.
|
||||
// Unfixable possible cause: bad or cracked solder joints on the PCB, failed shift register, failed driver.
|
||||
MMU_SOLDERING_NEEDS_ATTENTION = 0xC200,
|
||||
|
||||
};
|
352
Marlin/src/feature/mmu3/mmu_hw/errors_list.h
Normal file
352
Marlin/src/feature/mmu3/mmu_hw/errors_list.h
Normal file
|
@ -0,0 +1,352 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* errors_list.h
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extracted from Prusa-Error-Codes repo
|
||||
* Subject to automation and optimization
|
||||
* BEWARE - This file should only be included by mmu2_error_converter.cpp!
|
||||
*/
|
||||
#include "inttypes.h"
|
||||
#include "../../../core/language.h"
|
||||
#include "../../../lcd/marlinui.h"
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#endif
|
||||
#include "buttons.h"
|
||||
#include "../strlen_cx.h"
|
||||
#include "../ultralcd.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
static constexpr uint8_t ERR_MMU_CODE = 4;
|
||||
|
||||
typedef enum : uint16_t {
|
||||
ERR_UNDEF = 0,
|
||||
|
||||
ERR_MECHANICAL = 100,
|
||||
ERR_MECHANICAL_FINDA_DIDNT_TRIGGER = 101,
|
||||
ERR_MECHANICAL_FINDA_FILAMENT_STUCK = 102,
|
||||
ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER = 103,
|
||||
ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK = 104,
|
||||
|
||||
ERR_MECHANICAL_PULLEY_CANNOT_MOVE = 105,
|
||||
ERR_MECHANICAL_FSENSOR_TOO_EARLY = 106,
|
||||
ERR_MECHANICAL_INSPECT_FINDA = 107,
|
||||
ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED = 108,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_HOME = 115,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_MOVE = 116,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_HOME = 125,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_MOVE = 126,
|
||||
|
||||
ERR_TEMPERATURE = 200,
|
||||
ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT = 201,
|
||||
ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT = 211,
|
||||
ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT = 221,
|
||||
|
||||
ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR = 202,
|
||||
ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR = 212,
|
||||
ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR = 222,
|
||||
|
||||
|
||||
ERR_ELECTRICAL = 300,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR = 301,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR = 311,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR = 321,
|
||||
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET = 302,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET = 312,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET = 322,
|
||||
|
||||
ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR = 303,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR = 313,
|
||||
ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR = 323,
|
||||
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED = 304,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED = 314,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED = 324,
|
||||
|
||||
ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED = 305,
|
||||
ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED = 315,
|
||||
ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED = 325,
|
||||
|
||||
ERR_ELECTRICAL_MMU_MCU_ERROR = 306,
|
||||
|
||||
ERR_CONNECT = 400,
|
||||
ERR_CONNECT_MMU_NOT_RESPONDING = 401,
|
||||
ERR_CONNECT_COMMUNICATION_ERROR = 402,
|
||||
|
||||
|
||||
ERR_SYSTEM = 500,
|
||||
ERR_SYSTEM_FILAMENT_ALREADY_LOADED = 501,
|
||||
ERR_SYSTEM_INVALID_TOOL = 502,
|
||||
ERR_SYSTEM_QUEUE_FULL = 503,
|
||||
ERR_SYSTEM_FW_UPDATE_NEEDED = 504,
|
||||
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;
|
||||
|
||||
// Avr gcc has serious trouble understanding static data structures in PROGMEM
|
||||
// and inadvertedly falls back to copying the whole structure into RAM (which is obviously unwanted).
|
||||
// But since this file ought to be generated in the future from yaml prescription,
|
||||
// it really makes no difference if there are "nice" data structures or plain arrays.
|
||||
static const constexpr err_num_t errorCodes[] PROGMEM = {
|
||||
ERR_MECHANICAL_FINDA_DIDNT_TRIGGER,
|
||||
ERR_MECHANICAL_FINDA_FILAMENT_STUCK,
|
||||
ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER,
|
||||
ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK,
|
||||
ERR_MECHANICAL_PULLEY_CANNOT_MOVE,
|
||||
ERR_MECHANICAL_FSENSOR_TOO_EARLY,
|
||||
ERR_MECHANICAL_INSPECT_FINDA,
|
||||
ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_HOME,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_MOVE,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_HOME,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_MOVE,
|
||||
ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT,
|
||||
ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT,
|
||||
ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT,
|
||||
ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR,
|
||||
ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR,
|
||||
ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED,
|
||||
ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED,
|
||||
ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED,
|
||||
ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED,
|
||||
ERR_ELECTRICAL_MMU_MCU_ERROR,
|
||||
ERR_CONNECT_MMU_NOT_RESPONDING,
|
||||
ERR_CONNECT_COMMUNICATION_ERROR,
|
||||
ERR_SYSTEM_FILAMENT_ALREADY_LOADED,
|
||||
ERR_SYSTEM_INVALID_TOOL,
|
||||
ERR_SYSTEM_QUEUE_FULL,
|
||||
ERR_SYSTEM_FW_UPDATE_NEEDED,
|
||||
ERR_SYSTEM_FW_RUNTIME_ERROR,
|
||||
ERR_SYSTEM_UNLOAD_MANUALLY,
|
||||
ERR_SYSTEM_FILAMENT_EJECTED,
|
||||
ERR_SYSTEM_FILAMENT_CHANGE,
|
||||
ERR_OTHER_UNKNOWN_ERROR
|
||||
};
|
||||
|
||||
FSTR_P const errorTitles[] PROGMEM = {
|
||||
GET_TEXT_F(MSG_TITLE_FINDA_DIDNT_TRIGGER),
|
||||
GET_TEXT_F(MSG_TITLE_FINDA_FILAMENT_STUCK),
|
||||
GET_TEXT_F(MSG_TITLE_FSENSOR_DIDNT_TRIGGER),
|
||||
GET_TEXT_F(MSG_TITLE_FSENSOR_FILAMENT_STUCK),
|
||||
GET_TEXT_F(MSG_TITLE_PULLEY_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_TITLE_FSENSOR_TOO_EARLY),
|
||||
GET_TEXT_F(MSG_TITLE_INSPECT_FINDA),
|
||||
GET_TEXT_F(MSG_TITLE_LOAD_TO_EXTRUDER_FAILED),
|
||||
GET_TEXT_F(MSG_TITLE_SELECTOR_CANNOT_HOME),
|
||||
GET_TEXT_F(MSG_TITLE_SELECTOR_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_TITLE_IDLER_CANNOT_HOME),
|
||||
GET_TEXT_F(MSG_TITLE_IDLER_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
GET_TEXT_F(MSG_TITLE_SELFTEST_FAILED),
|
||||
GET_TEXT_F(MSG_TITLE_SELFTEST_FAILED),
|
||||
GET_TEXT_F(MSG_TITLE_SELFTEST_FAILED),
|
||||
GET_TEXT_F(MSG_TITLE_MMU_MCU_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_MMU_NOT_RESPONDING),
|
||||
GET_TEXT_F(MSG_TITLE_COMMUNICATION_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_FILAMENT_ALREADY_LOADED),
|
||||
GET_TEXT_F(MSG_TITLE_INVALID_TOOL),
|
||||
GET_TEXT_F(MSG_TITLE_QUEUE_FULL),
|
||||
GET_TEXT_F(MSG_TITLE_FW_UPDATE_NEEDED),
|
||||
GET_TEXT_F(MSG_TITLE_FW_RUNTIME_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_UNLOAD_MANUALLY),
|
||||
GET_TEXT_F(MSG_TITLE_FILAMENT_EJECTED),
|
||||
GET_TEXT_F(MSG_TITLE_FILAMENT_CHANGE),
|
||||
GET_TEXT_F(MSG_TITLE_UNKNOWN_ERROR)
|
||||
};
|
||||
|
||||
// @@TODO Looking at the texts, they can be composed of several parts and/or parameterized (could save a lot of space) )
|
||||
// Moreover, some of them have been disabled in favour of saving some more code size.
|
||||
|
||||
FSTR_P const errorDescs[] PROGMEM = {
|
||||
GET_TEXT_F(MSG_DESC_FINDA_DIDNT_TRIGGER),
|
||||
GET_TEXT_F(MSG_DESC_FINDA_FILAMENT_STUCK),
|
||||
GET_TEXT_F(MSG_DESC_FSENSOR_DIDNT_TRIGGER),
|
||||
GET_TEXT_F(MSG_DESC_FSENSOR_FILAMENT_STUCK),
|
||||
GET_TEXT_F(MSG_DESC_PULLEY_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_DESC_FSENSOR_TOO_EARLY),
|
||||
GET_TEXT_F(MSG_DESC_INSPECT_FINDA),
|
||||
GET_TEXT_F(MSG_DESC_LOAD_TO_EXTRUDER_FAILED),
|
||||
GET_TEXT_F(MSG_DESC_SELECTOR_CANNOT_HOME),
|
||||
GET_TEXT_F(MSG_DESC_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_DESC_IDLER_CANNOT_HOME),
|
||||
GET_TEXT_F(MSG_DESC_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_DESC_TMC), // WARNING_TMC_PULLEY_TOO_HOT
|
||||
GET_TEXT_F(MSG_DESC_TMC), // WARNING_TMC_SELECTOR_TOO_HOT
|
||||
GET_TEXT_F(MSG_DESC_TMC), // WARNING_TMC_IDLER_TOO_HOT
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_OVERHEAT_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_OVERHEAT_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_OVERHEAT_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_DRIVER_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_DRIVER_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_DRIVER_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_DRIVER_RESET
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_DRIVER_RESET
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_DRIVER_RESET
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_UNDERVOLTAGE_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_UNDERVOLTAGE_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_UNDERVOLTAGE_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_DRIVER_SHORTED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_DRIVER_SHORTED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_DRIVER_SHORTED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // MMU_PULLEY_SELFTEST_FAILED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // MMU_SELECTOR_SELFTEST_FAILED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // MMU_IDLER_SELFTEST_FAILED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // MSG_DESC_MMU_MCU_ERROR
|
||||
GET_TEXT_F(MSG_DESC_MMU_NOT_RESPONDING),
|
||||
GET_TEXT_F(MSG_DESC_COMMUNICATION_ERROR),
|
||||
GET_TEXT_F(MSG_DESC_FILAMENT_ALREADY_LOADED),
|
||||
GET_TEXT_F(MSG_DESC_INVALID_TOOL),
|
||||
GET_TEXT_F(MSG_DESC_QUEUE_FULL),
|
||||
GET_TEXT_F(MSG_DESC_FW_UPDATE_NEEDED),
|
||||
GET_TEXT_F(MSG_DESC_FW_RUNTIME_ERROR),
|
||||
GET_TEXT_F(MSG_DESC_UNLOAD_MANUALLY),
|
||||
GET_TEXT_F(MSG_DESC_FILAMENT_EJECTED),
|
||||
GET_TEXT_F(MSG_DESC_FILAMENT_CHANGE),
|
||||
GET_TEXT_F(MSG_DESC_UNKNOWN_ERROR)
|
||||
};
|
||||
|
||||
// We have max 3 buttons/operations to select from.
|
||||
// One of them is "More" to show the explanation text normally hidden in the next screens.
|
||||
// It is displayed with W (Double down arrow, special character from CGRAM)
|
||||
// 01234567890123456789
|
||||
// >bttxt >bttxt >W
|
||||
// Therefore at least some of the buttons, which can occur on the screen together, can only be 8-chars long max @@TODO.
|
||||
// Beware - we only have space for 2 buttons on the LCD while the MMU has 3 buttons
|
||||
// -> the left button on the MMU is not used/rendered on the LCD (it is also almost unused on the MMU side)
|
||||
|
||||
// Used to parse the buttons from Btns().
|
||||
FSTR_P const btnOperation[] PROGMEM = {
|
||||
GET_TEXT_F(MSG_BTN_RETRY),
|
||||
GET_TEXT_F(MSG_DONE),
|
||||
GET_TEXT_F(MSG_BTN_RESET_MMU),
|
||||
GET_TEXT_F(MSG_BTN_UNLOAD),
|
||||
GET_TEXT_F(MSG_BTN_LOAD),
|
||||
GET_TEXT_F(MSG_BTN_EJECT),
|
||||
GET_TEXT_F(MSG_TUNE),
|
||||
GET_TEXT_F(MSG_BTN_STOP),
|
||||
GET_TEXT_F(MSG_BTN_DISABLE_MMU)
|
||||
};
|
||||
|
||||
// We have 8 different operations/buttons at this time, so we need at least 4 bits to encode each.
|
||||
// Since one of the buttons is always "More", we can skip that one.
|
||||
// Therefore we need just 1 byte to describe the necessary buttons for each screen.
|
||||
uint8_t constexpr Btns(ButtonOperations bMiddle, ButtonOperations bRight) {
|
||||
return ((uint8_t)bRight) << 4 | ((uint8_t)bMiddle);
|
||||
}
|
||||
|
||||
static const uint8_t errorButtons[] PROGMEM = {
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FINDA_DIDNT_TRIGGER
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FINDA_FILAMENT_STUCK
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FSENSOR_DIDNT_TRIGGER
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FSENSOR_FILAMENT_STUCK
|
||||
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // PULLEY_CANNOT_MOVE
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FSENSOR_TOO_EARLY
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // INSPECT_FINDA
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::NoOperation), // LOAD_TO_EXTRUDER_FAILED
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::Tune), // SELECTOR_CANNOT_HOME
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // SELECTOR_CANNOT_MOVE
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::Tune), // IDLER_CANNOT_HOME
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // IDLER_CANNOT_MOVE
|
||||
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::ResetMMU), // WARNING_TMC_PULLEY_TOO_HOT
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::ResetMMU), // WARNING_TMC_SELECTOR_TOO_HOT
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::ResetMMU), // WARNING_TMC_IDLER_TOO_HOT
|
||||
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_DRIVER_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_DRIVER_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_DRIVER_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_DRIVER_RESET
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_DRIVER_RESET
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_DRIVER_RESET
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // MMU_PULLEY_SELFTEST_FAILED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // MMU_SELECTOR_SELFTEST_FAILED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // MMU_IDLER_SELFTEST_FAILED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // MMU_MCU_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::DisableMMU), // MMU_NOT_RESPONDING
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::DisableMMU), // COMMUNICATION_ERROR
|
||||
|
||||
Btns(ButtonOperations::Unload, ButtonOperations::Continue), // FILAMENT_ALREADY_LOADED
|
||||
Btns(ButtonOperations::StopPrint, ButtonOperations::ResetMMU), // INVALID_TOOL
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // QUEUE_FULL
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::DisableMMU), // FW_UPDATE_NEEDED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // FW_RUNTIME_ERROR
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // UNLOAD_MANUALLY
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::NoOperation), // FILAMENT_EJECTED
|
||||
Btns(ButtonOperations::Eject, ButtonOperations::Load), // FILAMENT_CHANGE
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // UNKOWN_ERROR
|
||||
};
|
||||
|
||||
static_assert(COUNT(errorCodes) == COUNT(errorDescs));
|
||||
static_assert(COUNT(errorCodes) == COUNT(errorTitles));
|
||||
static_assert(COUNT(errorCodes) == COUNT(errorButtons));
|
||||
|
||||
} // MMU3
|
72
Marlin/src/feature/mmu3/mmu_hw/progress_codes.h
Normal file
72
Marlin/src/feature/mmu3/mmu_hw/progress_codes.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* progress_codes.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* A complete set of progress codes which may be reported while running a high-level command/operation.
|
||||
* This header file should be included in the printer's firmware as well as a reference, so the progress
|
||||
* codes are extracted to one place.
|
||||
*/
|
||||
enum class ProgressCode : uint_fast8_t {
|
||||
OK = 0, //!< finished ok
|
||||
|
||||
EngagingIdler, // P1
|
||||
DisengagingIdler, // P2
|
||||
UnloadingToFinda, // P3
|
||||
UnloadingToPulley, // P4
|
||||
FeedingToFinda, // P5
|
||||
FeedingToExtruder, // P6
|
||||
FeedingToNozzle, // P7
|
||||
AvoidingGrind, // P8
|
||||
FinishingMoves, // P9
|
||||
|
||||
ERRDisengagingIdler, // P10
|
||||
ERREngagingIdler, // P11
|
||||
ERRWaitingForUser, // P12
|
||||
ERRInternal, // P13
|
||||
ERRHelpingFilament, // P14
|
||||
ERRTMCFailed, // P15
|
||||
|
||||
UnloadingFilament, // P16
|
||||
LoadingFilament, // P17
|
||||
SelectingFilamentSlot, // P18
|
||||
PreparingBlade, // P19
|
||||
PushingFilament, // P20
|
||||
PerformingCut, // P21
|
||||
ReturningSelector, // P22
|
||||
ParkingSelector, // P23
|
||||
EjectingFilament, // P24
|
||||
RetractingFromFinda, // P25
|
||||
|
||||
Homing, // P26
|
||||
MovingSelector, // P27
|
||||
|
||||
FeedingToFSensor, // P28
|
||||
|
||||
Empty = 0xFF // dummy empty state
|
||||
};
|
70
Marlin/src/feature/mmu3/mmu_hw/registers.h
Normal file
70
Marlin/src/feature/mmu3/mmu_hw/registers.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* registers.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Register map for MMU
|
||||
enum class Register : uint8_t {
|
||||
Project_Major = 0x00,
|
||||
Project_Minor = 0x01,
|
||||
Project_Revision = 0x02,
|
||||
Project_Build_Number = 0x03,
|
||||
MMU_Errors = 0x04,
|
||||
Current_Progress_Code = 0x05,
|
||||
Current_Error_Code = 0x06,
|
||||
Filament_State = 0x07,
|
||||
FINDA_State = 0x08,
|
||||
FSensor_State = 0x09,
|
||||
Motor_Mode = 0x0A,
|
||||
Extra_Load_Distance = 0x0B,
|
||||
FSensor_Unload_Check_Distance = 0xC,
|
||||
Pulley_Unload_Feedrate = 0x0D,
|
||||
Pulley_Acceleration = 0x0E,
|
||||
Selector_Acceleration = 0x0F,
|
||||
Idler_Acceleration = 0x10,
|
||||
Pulley_Load_Feedrate = 0x11,
|
||||
Selector_Nominal_Feedrate = 0x12,
|
||||
Idler_Nominal_Feedrate = 0x13,
|
||||
Pulley_Slow_Feedrate = 0x14,
|
||||
Selector_Homing_Feedrate = 0x15,
|
||||
Idler_Homing_Feedrate = 0x16,
|
||||
Pulley_sg_thrs_R = 0x17,
|
||||
Selector_sg_thrs_R = 0x18,
|
||||
Idler_sg_thrs_R = 0x19,
|
||||
Get_Pulley_Position = 0x1A,
|
||||
Set_Get_Selector_Slot = 0x1B,
|
||||
Set_Get_Idler_Slot = 0x1C,
|
||||
Set_Get_Selector_Cut_iRun = 0x1D,
|
||||
Set_Get_Pulley_iRun = 0x1E,
|
||||
Set_Get_Selector_iRun = 0x1F,
|
||||
Set_Get_Idler_iRun = 0x20,
|
||||
Reserved = 0x21,
|
||||
};
|
||||
|
||||
} // MMU3
|
70
Marlin/src/feature/mmu3/registers.h
Normal file
70
Marlin/src/feature/mmu3/registers.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* registers.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Register map for MMU
|
||||
enum class Register : uint8_t {
|
||||
Project_Major = 0x00,
|
||||
Project_Minor = 0x01,
|
||||
Project_Revision = 0x02,
|
||||
Project_Build_Number = 0x03,
|
||||
MMU_Errors = 0x04,
|
||||
Current_Progress_Code = 0x05,
|
||||
Current_Error_Code = 0x06,
|
||||
Filament_State = 0x07,
|
||||
FINDA_State = 0x08,
|
||||
FSensor_State = 0x09,
|
||||
Motor_Mode = 0x0A,
|
||||
Extra_Load_Distance = 0x0B,
|
||||
FSensor_Unload_Check_Distance = 0xC,
|
||||
Pulley_Unload_Feedrate = 0x0D,
|
||||
Pulley_Acceleration = 0x0E,
|
||||
Selector_Acceleration = 0x0F,
|
||||
Idler_Acceleration = 0x10,
|
||||
Pulley_Load_Feedrate = 0x11,
|
||||
Selector_Nominal_Feedrate = 0x12,
|
||||
Idler_Nominal_Feedrate = 0x13,
|
||||
Pulley_Slow_Feedrate = 0x14,
|
||||
Selector_Homing_Feedrate = 0x15,
|
||||
Idler_Homing_Feedrate = 0x16,
|
||||
Pulley_sg_thrs_R = 0x17,
|
||||
Selector_sg_thrs_R = 0x18,
|
||||
Idler_sg_thrs_R = 0x19,
|
||||
Get_Pulley_Position = 0x1A,
|
||||
Set_Get_Selector_Slot = 0x1B,
|
||||
Set_Get_Idler_Slot = 0x1C,
|
||||
Set_Get_Selector_Cut_iRun = 0x1D,
|
||||
Set_Get_Pulley_iRun = 0x1E,
|
||||
Set_Get_Selector_iRun = 0x1F,
|
||||
Set_Get_Idler_iRun = 0x20,
|
||||
Reserved = 0x21,
|
||||
};
|
||||
|
||||
} // MMU3
|
203
Marlin/src/feature/mmu3/sound.cpp
Normal file
203
Marlin/src/feature/mmu3/sound.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* sound.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
//#include "backlight.h"
|
||||
#include "../../libs/buzzer.h"
|
||||
#include "sound.h"
|
||||
|
||||
// eSOUND_MODE eSoundMode=e_SOUND_MODE_LOUD;
|
||||
// doesn't matter if Sound_Init is called (i.e. the value is in the EEPROM)
|
||||
// !?! eSOUND_MODE eSoundMode; in ultraldc.cpp :: cd_settings_menu() it appears as a local variable like this
|
||||
eSOUND_MODE eSoundMode; // =e_SOUND_MODE_DEFAULT;
|
||||
|
||||
|
||||
static void Sound_SaveMode(void);
|
||||
static void Sound_DoSound_Echo(void);
|
||||
static void Sound_DoSound_Prompt(void);
|
||||
static void Sound_DoSound_Alert(bool bOnce);
|
||||
static void Sound_DoSound_Encoder_Move(void);
|
||||
static void Sound_DoSound_Blind_Alert(void);
|
||||
|
||||
void Sound_Init(void) {
|
||||
// SET_OUTPUT(BEEPER);
|
||||
// eSoundMode = static_cast<eSOUND_MODE>(eeprom_init_default_byte((uint8_t*)EEPROM_SOUND_MODE, e_SOUND_MODE_DEFAULT));
|
||||
}
|
||||
|
||||
void Sound_SaveMode(void) {
|
||||
// eeprom_update_byte((uint8_t*)EEPROM_SOUND_MODE,(uint8_t)eSoundMode);
|
||||
}
|
||||
|
||||
void Sound_CycleState(void) {
|
||||
switch (eSoundMode) {
|
||||
case e_SOUND_MODE_LOUD: eSoundMode = e_SOUND_MODE_ONCE; break;
|
||||
case e_SOUND_MODE_ONCE: eSoundMode = e_SOUND_MODE_SILENT; break;
|
||||
case e_SOUND_MODE_SILENT: eSoundMode = e_SOUND_MODE_BLIND; break;
|
||||
case e_SOUND_MODE_BLIND: eSoundMode = e_SOUND_MODE_LOUD; break;
|
||||
default: eSoundMode = e_SOUND_MODE_LOUD;
|
||||
}
|
||||
Sound_SaveMode();
|
||||
}
|
||||
|
||||
// if critical is true then silent and once mode is ignored
|
||||
void __attribute__((noinline)) Sound_MakeCustom(uint16_t ms, uint16_t tone_, bool critical) {
|
||||
if (critical || eSoundMode != e_SOUND_MODE_SILENT)
|
||||
//if (!tone_) {
|
||||
// WRITE(BEEPER, HIGH);
|
||||
// _delay(ms);
|
||||
// WRITE(BEEPER, LOW);
|
||||
//}
|
||||
//else {
|
||||
// _tone(BEEPER, tone_);
|
||||
// _delay(ms);
|
||||
// _noTone(BEEPER);
|
||||
//}
|
||||
BUZZ(ms, tone_);
|
||||
}
|
||||
|
||||
void Sound_MakeSound(eSOUND_TYPE eSoundType) {
|
||||
switch (eSoundMode) {
|
||||
case e_SOUND_MODE_LOUD:
|
||||
if (eSoundType == e_SOUND_TYPE_ButtonEcho)
|
||||
Sound_DoSound_Echo();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardPrompt)
|
||||
Sound_DoSound_Prompt();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardAlert)
|
||||
Sound_DoSound_Alert(false);
|
||||
break;
|
||||
case e_SOUND_MODE_ONCE:
|
||||
if (eSoundType == e_SOUND_TYPE_ButtonEcho)
|
||||
Sound_DoSound_Echo();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardPrompt)
|
||||
Sound_DoSound_Prompt();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardAlert)
|
||||
Sound_DoSound_Alert(true);
|
||||
break;
|
||||
case e_SOUND_MODE_SILENT:
|
||||
if (eSoundType == e_SOUND_TYPE_StandardAlert)
|
||||
Sound_DoSound_Alert(true);
|
||||
break;
|
||||
case e_SOUND_MODE_BLIND:
|
||||
if (eSoundType == e_SOUND_TYPE_ButtonEcho)
|
||||
Sound_DoSound_Echo();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardPrompt)
|
||||
Sound_DoSound_Prompt();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardAlert)
|
||||
Sound_DoSound_Alert(false);
|
||||
if (eSoundType == e_SOUND_TYPE_EncoderMove)
|
||||
Sound_DoSound_Encoder_Move();
|
||||
if (eSoundType == e_SOUND_TYPE_BlindAlert)
|
||||
Sound_DoSound_Blind_Alert();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Blind_Alert(void) {
|
||||
// backlight_wake(1);
|
||||
uint8_t nI;
|
||||
for (nI = 0; nI < 20; nI++) {
|
||||
BUZZ(94, 404);
|
||||
BUZZ(94, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Encoder_Move(void) {
|
||||
uint8_t nI;
|
||||
for (nI = 0; nI < 5; nI++) {
|
||||
BUZZ(75, 404);
|
||||
BUZZ(75, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Echo(void) {
|
||||
uint8_t nI;
|
||||
for (nI = 0; nI < 10; nI++) {
|
||||
BUZZ(100, 404);
|
||||
BUZZ(100, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Prompt(void) {
|
||||
// backlight_wake(2);
|
||||
BUZZ(500, 404);
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Alert(bool bOnce) {
|
||||
uint8_t nI, nMax;
|
||||
nMax = bOnce ? 1 : 3;
|
||||
for (nI = 0; nI < nMax; nI++) {
|
||||
BUZZ(200, 404);
|
||||
BUZZ(500, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int16_t constexpr CONTINOUS_BEEP_PERIOD = 2000; // in ms
|
||||
// static ShortTimer beep_timer; // Timer to keep track of continous beeping
|
||||
static bool bFirst; // true if the first beep has occurred, e_SOUND_MODE_ONCE
|
||||
|
||||
// @brief Handles sound when waiting for user input
|
||||
// the function must be non-blocking. It is up to the caller
|
||||
// to call this function repeatedly.
|
||||
// Make sure to call sound_wait_for_user_reset() when the user has clicked the knob
|
||||
// Loud - should continuously beep
|
||||
// Silent - should be silent
|
||||
// Once - should beep once
|
||||
// Assist/Blind - as loud with beep and click on knob rotation and press
|
||||
void sound_wait_for_user() {
|
||||
#if BEEPER > 0
|
||||
if (eSoundMode == e_SOUND_MODE_SILENT) return;
|
||||
|
||||
// Handle case where only one beep is needed
|
||||
if (eSoundMode == e_SOUND_MODE_ONCE) {
|
||||
if (bFirst) return;
|
||||
Sound_MakeCustom(80, 0, false);
|
||||
bFirst = true;
|
||||
}
|
||||
|
||||
// Handle case where there should be continous beeps
|
||||
if (beep_timer.expired_cont(CONTINOUS_BEEP_PERIOD)) {
|
||||
beep_timer.start();
|
||||
if (eSoundMode == e_SOUND_MODE_LOUD)
|
||||
Sound_MakeCustom(80, 0, false);
|
||||
else
|
||||
// Assist (lower volume sound)
|
||||
Sound_MakeSound(e_SOUND_TYPE_ButtonEcho);
|
||||
}
|
||||
#endif // BEEPER > 0
|
||||
}
|
||||
|
||||
// @brief Resets the global state of sound_wait_for_user()
|
||||
void sound_wait_for_user_reset() {
|
||||
// beep_timer.stop();
|
||||
bFirst = false;
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
69
Marlin/src/feature/mmu3/sound.h
Normal file
69
Marlin/src/feature/mmu3/sound.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* sound.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define e_SOUND_MODE_NULL 0xFF
|
||||
typedef enum : uint8_t {
|
||||
e_SOUND_MODE_LOUD,
|
||||
e_SOUND_MODE_ONCE,
|
||||
e_SOUND_MODE_SILENT,
|
||||
e_SOUND_MODE_BLIND
|
||||
} eSOUND_MODE;
|
||||
|
||||
#define e_SOUND_MODE_DEFAULT e_SOUND_MODE_LOUD
|
||||
|
||||
typedef enum : uint8_t {
|
||||
e_SOUND_TYPE_ButtonEcho,
|
||||
e_SOUND_TYPE_EncoderEcho,
|
||||
e_SOUND_TYPE_StandardPrompt,
|
||||
e_SOUND_TYPE_StandardConfirm,
|
||||
e_SOUND_TYPE_StandardWarning,
|
||||
e_SOUND_TYPE_StandardAlert,
|
||||
e_SOUND_TYPE_EncoderMove,
|
||||
e_SOUND_TYPE_BlindAlert
|
||||
} eSOUND_TYPE;
|
||||
|
||||
typedef enum : uint8_t {
|
||||
e_SOUND_CLASS_Echo,
|
||||
e_SOUND_CLASS_Prompt,
|
||||
e_SOUND_CLASS_Confirm,
|
||||
e_SOUND_CLASS_Warning,
|
||||
e_SOUND_CLASS_Alert
|
||||
} eSOUND_CLASS;
|
||||
|
||||
extern eSOUND_MODE eSoundMode;
|
||||
|
||||
extern void Sound_Init(void);
|
||||
extern void Sound_CycleState(void);
|
||||
extern void Sound_MakeSound(eSOUND_TYPE eSoundType);
|
||||
extern void Sound_MakeCustom(uint16_t ms, uint16_t tone_, bool critical);
|
||||
void sound_wait_for_user();
|
||||
void sound_wait_for_user_reset();
|
||||
|
||||
//static void Sound_DoSound_Echo(void);
|
||||
//static void Sound_DoSound_Prompt(void);
|
30
Marlin/src/feature/mmu3/strlen_cx.h
Normal file
30
Marlin/src/feature/mmu3/strlen_cx.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* strlen_cx.h
|
||||
*/
|
||||
|
||||
constexpr inline int strlen_constexpr(const char *str) {
|
||||
return *str ? 1 + strlen_constexpr(str + 1) : 0;
|
||||
}
|
217
Marlin/src/feature/mmu3/ultralcd.cpp
Normal file
217
Marlin/src/feature/mmu3/ultralcd.cpp
Normal file
|
@ -0,0 +1,217 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* ultralcd.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2.h"
|
||||
#include "mmu2_marlin_macros.h"
|
||||
#include "mmu_hw/errors_list.h"
|
||||
#include "ultralcd.h"
|
||||
|
||||
#include "../../lcd/menu/menu_item.h"
|
||||
#include "../../gcode/gcode.h"
|
||||
#include "../../lcd/marlinui.h"
|
||||
|
||||
//! @brief Show a two-choice prompt on the last line of the LCD
|
||||
//! @param selected Show first choice as selected if true, the second otherwise
|
||||
//! @param first_choice text caption of first possible choice
|
||||
//! @param second_choice text caption of second possible choice
|
||||
//! @param second_col column on LCD where second choice is rendered.
|
||||
//! @param third_choice text caption of third, optional, choice.
|
||||
void lcd_show_choices_prompt_P(uint8_t selected, const char *first_choice, const char *second_choice, uint8_t second_col, const char *third_choice) {
|
||||
lcd_put_lchar(0, 3, selected == LCD_LEFT_BUTTON_CHOICE ? '>' : ' ');
|
||||
lcd_put_u8str(first_choice);
|
||||
lcd_put_lchar(second_col, 3, selected == LCD_MIDDLE_BUTTON_CHOICE ? '>' : ' ');
|
||||
lcd_put_u8str(second_choice);
|
||||
if (third_choice) {
|
||||
lcd_put_lchar(18, 3, selected == LCD_RIGHT_BUTTON_CHOICE ? '>' : ' ');
|
||||
lcd_put_u8str(third_choice);
|
||||
}
|
||||
}
|
||||
|
||||
void lcd_space(uint8_t n) {
|
||||
while (n--) lcd_put_lchar(' ');
|
||||
}
|
||||
|
||||
// Print extruder status (5 chars total)
|
||||
// Scenario 1: "F?"
|
||||
// There is no filament loaded and no tool change is in progress
|
||||
// Scenario 2: "F[nr.]"
|
||||
// [nr.] ranges from 1 to 5.
|
||||
// Shows which filament is loaded. No tool change is in progress
|
||||
// Scenario 3: "?>[nr.]"
|
||||
// [nr.] ranges from 1 to 5.
|
||||
// There is no filament currently loaded, but [nr.] is currently being loaded via tool change
|
||||
// Scenario 4: "[nr.]>?"
|
||||
// [nr.] ranges from 1 to 5.
|
||||
// This scenario indicates a bug in the firmware if ? is on the right side
|
||||
// Scenario 5: "[nr1.]>[nr2.]"
|
||||
// [nr1.] ranges from 1 to 5.
|
||||
// [nr2.] ranges from 1 to 5.
|
||||
// Filament [nr1.] was loaded, but [nr2.] is currently being loaded via tool change
|
||||
// Scenario 6: "?>?"
|
||||
// This scenario should not be possible and indicates a bug in the firmware
|
||||
uint8_t lcdui_print_extruder(void) {
|
||||
uint8_t chars = 1;
|
||||
lcd_space(1);
|
||||
if (mmu3.get_current_tool() == mmu3.get_tool_change_tool()) {
|
||||
lcd_put_lchar('F');
|
||||
lcd_put_lchar(mmu3.get_current_tool() == (uint8_t)MMU3::FILAMENT_UNKNOWN ? '?' : mmu3.get_current_tool() + '1');
|
||||
chars += 2;
|
||||
}
|
||||
else {
|
||||
lcd_put_lchar(mmu3.get_current_tool() == (uint8_t)MMU3::FILAMENT_UNKNOWN ? '?' : mmu3.get_current_tool() + '1');
|
||||
lcd_put_lchar('>');
|
||||
lcd_put_lchar(mmu3.get_tool_change_tool() == (uint8_t)MMU3::FILAMENT_UNKNOWN ? '?' : mmu3.get_tool_change_tool() + '1');
|
||||
chars += 3;
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
bool pgm_is_whitespace(const char *c_addr) {
|
||||
const char c = pgm_read_byte(c_addr);
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
bool pgm_is_interpunction(const char *c_addr) {
|
||||
const char c = pgm_read_byte(c_addr);
|
||||
return c == '.' || c == ',' || c == ':' || c == ';' || c == '?' || c == '!' || c == '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief show full screen message
|
||||
*
|
||||
* This function is non-blocking
|
||||
* @param msg message to be displayed from PROGMEM
|
||||
* @return rest of the text (to be displayed on next page)
|
||||
*/
|
||||
static FSTR_P const lcd_display_message_fullscreen_nonBlocking(FSTR_P const fmsg) {
|
||||
PGM_P msg = FTOP(fmsg);
|
||||
PGM_P msgend = msg;
|
||||
//bool multi_screen = false;
|
||||
for (uint8_t row = 0; row < LCD_HEIGHT; ++row) {
|
||||
if (pgm_read_byte(msgend) == 0) break;
|
||||
SETCURSOR(0, row);
|
||||
|
||||
// Previous row ended with a complete word, so the first character in the
|
||||
// next row is a whitespace. We can skip the whitespace on a new line.
|
||||
if (pgm_is_whitespace(msg) && ++msg == nullptr) break; // End of the message.
|
||||
|
||||
uint8_t linelen = (strlen_P(msg) > LCD_WIDTH) ? LCD_WIDTH : strlen_P(msg);
|
||||
PGM_P const msgend2 = msg + linelen;
|
||||
msgend = msgend2;
|
||||
if (row == 3 && linelen == LCD_WIDTH) {
|
||||
// Last line of the display, full line should be displayed.
|
||||
// Find out, whether this message will be split into multiple screens.
|
||||
//multi_screen = pgm_read_byte(msgend) != 0;
|
||||
// We do not need this...
|
||||
//if (multi_screen) msgend = (msgend2 -= 2);
|
||||
}
|
||||
if (pgm_read_byte(msgend) != 0 && !pgm_is_whitespace(msgend) && !pgm_is_interpunction(msgend)) {
|
||||
// Splitting a word. Find the start of the current word.
|
||||
while (msgend > msg && !pgm_is_whitespace(msgend - 1)) --msgend;
|
||||
if (msgend == msg) msgend = msgend2; // Found a single long word, which cannot be split. Just cut it.
|
||||
}
|
||||
for (; msg < msgend; ++msg) {
|
||||
const char c = char(pgm_read_byte(msg));
|
||||
if (c == '\n') {
|
||||
// Abort early if '\n' is encountered.
|
||||
// This character is used to force the following words to be printed on the next line.
|
||||
break;
|
||||
}
|
||||
lcd_put_lchar(c);
|
||||
}
|
||||
}
|
||||
// We do not need this part...
|
||||
//if (multi_screen) {
|
||||
// // Display the double down arrow.
|
||||
// lcd_put_lchar(LCD_WIDTH - 2, LCD_HEIGHT - 2, LCD_STR_ARROW_2_DOWN[0]);
|
||||
//}
|
||||
//return multi_screen ? msgend : nullptr;
|
||||
return FPSTR(msgend);
|
||||
}
|
||||
|
||||
FSTR_P const lcd_display_message_fullscreen(FSTR_P const fmsg) {
|
||||
// Disable update of the screen by the usual lcd_update(0) routine.
|
||||
#if HAS_WIRED_LCD
|
||||
//ui.lcdDrawUpdate = LCDViewAction::LCDVIEW_NONE;
|
||||
ui.clear_lcd();
|
||||
return lcd_display_message_fullscreen_nonBlocking(fmsg);
|
||||
#else
|
||||
return fmsg
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief show full screen message and wait
|
||||
*
|
||||
* This function is blocking.
|
||||
* @param msg message to be displayed from PROGMEM
|
||||
*/
|
||||
void lcd_show_fullscreen_message_and_wait(FSTR_P const fmsg) {
|
||||
LcdUpdateDisabler lcdUpdateDisabler;
|
||||
FSTR_P fmsg_next = lcd_display_message_fullscreen(fmsg);
|
||||
const bool multi_screen = fmsg_next != nullptr;
|
||||
ui.use_click();
|
||||
KEEPALIVE_STATE(PAUSED_FOR_USER);
|
||||
// Until confirmed by a button click.
|
||||
for (;;) {
|
||||
if (fmsg_next == nullptr) {
|
||||
// Display the confirm char.
|
||||
//lcd_put_lchar(LCD_WIDTH - 2, LCD_HEIGHT - 2, LCD_STR_CONFIRM[0]);
|
||||
}
|
||||
// Wait for 5 seconds before displaying the next text.
|
||||
for (uint8_t i = 0; i < 100; ++i) {
|
||||
idle(true);
|
||||
safe_delay(50);
|
||||
if (ui.use_click()) {
|
||||
if (fmsg_next == nullptr) {
|
||||
KEEPALIVE_STATE(IN_HANDLER);
|
||||
return ui.go_back();
|
||||
}
|
||||
if (!multi_screen) break;
|
||||
if (fmsg_next == nullptr) fmsg_next = fmsg;
|
||||
fmsg_next = lcd_display_message_fullscreen(fmsg_next);
|
||||
}
|
||||
}
|
||||
//if (multi_screen) {
|
||||
// if (fmsg_next == nullptr) fmsg_next = fmsg;
|
||||
// fmsg_next = lcd_display_message_fullscreen(fmsg_next);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
void lcd_insert_char_into_status(uint8_t position, const char message) {
|
||||
if (position >= LCD_WIDTH) return;
|
||||
//int size = ui.status_message.length();
|
||||
char *str = ui.status_message.buffer();
|
||||
str[position] = message;
|
||||
ui.refresh(LCDVIEW_REDRAW_NOW); // force redraw
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
72
Marlin/src/feature/mmu3/ultralcd.h
Normal file
72
Marlin/src/feature/mmu3/ultralcd.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* ultralcd.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
#include "../../lcd/marlinui.h"
|
||||
|
||||
#define LCD_LEFT_BUTTON_CHOICE 0
|
||||
#define LCD_MIDDLE_BUTTON_CHOICE 1
|
||||
#define LCD_RIGHT_BUTTON_CHOICE 2
|
||||
|
||||
#define LCD_STR_ARROW_2_DOWN "\x88"
|
||||
#define LCD_STR_CONFIRM "\x89"
|
||||
#define LCD_STR_SOLID_BLOCK "\xFF" // from the default character set
|
||||
|
||||
/**
|
||||
* @brief Helper class to temporarily disable LCD updates
|
||||
*
|
||||
* When constructed (on stack), original state state of lcd_update_enabled is stored
|
||||
* and LCD updates are disabled.
|
||||
* When destroyed (gone out of scope), original state of LCD update is restored.
|
||||
* It has zero overhead compared to storing bool saved = lcd_update_enabled
|
||||
* and calling lcd_update_enable(false) and lcd_update_enable(saved).
|
||||
*/
|
||||
class LcdUpdateDisabler {
|
||||
public:
|
||||
LcdUpdateDisabler() : m_updateEnabled(ui.lcdDrawUpdate) {
|
||||
TERN_(HAS_WIRED_LCD, ui.lcdDrawUpdate = LCDViewAction::LCDVIEW_NONE);
|
||||
}
|
||||
~LcdUpdateDisabler() {
|
||||
#if HAS_WIRED_LCD
|
||||
ui.lcdDrawUpdate = m_updateEnabled;
|
||||
ui.clear_lcd();
|
||||
ui.update();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
LCDViewAction m_updateEnabled;
|
||||
};
|
||||
|
||||
bool pgm_is_whitespace(const char *c_addr);
|
||||
bool pgm_is_interpunction(const char *c_addr);
|
||||
FSTR_P const lcd_display_message_fullscreen(FSTR_P const pmsg);
|
||||
void lcd_show_choices_prompt_P(uint8_t selected, const char *first_choice, const char *second_choice, uint8_t second_col, const char *third_choice=nullptr);
|
||||
void lcd_show_fullscreen_message_and_wait(FSTR_P const fmsg);
|
||||
uint8_t lcdui_print_extruder(void);
|
||||
void lcd_space(uint8_t n);
|
||||
void lcd_insert_char_into_status(uint8_t position, const char message);
|
|
@ -31,7 +31,9 @@
|
|||
#include "../../module/motion.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "../../feature/mmu3/mmu2.h"
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#include "../../feature/mmu/mmu2.h"
|
||||
#endif
|
||||
|
||||
|
@ -66,7 +68,12 @@ void GcodeSuite::T(const int8_t tool_index) {
|
|||
// Count this command as movement / activity
|
||||
reset_stepper_timeout();
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
if (parser.string_arg) {
|
||||
mmu3.tool_change(parser.string_arg[0], uint8_t(tool_index)); // Special commands T?/Tx/Tc
|
||||
return;
|
||||
}
|
||||
#elif HAS_PRUSA_MMU2
|
||||
if (parser.string_arg) {
|
||||
mmu2.tool_change(parser.string_arg); // Special commands T?/Tx/Tc
|
||||
return;
|
||||
|
|
|
@ -34,9 +34,14 @@
|
|||
#include "../../../module/tool_change.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "../../../feature/mmu3/mmu2.h"
|
||||
#if ENABLED(MMU_MENUS)
|
||||
#include "../../../lcd/menu/menu_mmu2.h"
|
||||
#endif
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#include "../../../feature/mmu/mmu2.h"
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
#include "../../../lcd/menu/menu_mmu2.h"
|
||||
#endif
|
||||
#endif
|
||||
|
@ -68,6 +73,9 @@
|
|||
* T[toolhead] - Select extruder for filament change
|
||||
* R[temp] - Resume temperature (in current units)
|
||||
*
|
||||
* With MMU_MENUS:
|
||||
* A - Automatic
|
||||
*
|
||||
* Default values are used for omitted arguments.
|
||||
*/
|
||||
void GcodeSuite::M600() {
|
||||
|
@ -101,7 +109,7 @@ void GcodeSuite::M600() {
|
|||
}
|
||||
#endif
|
||||
|
||||
const bool standardM600 = TERN1(MMU2_MENUS, !mmu2.enabled());
|
||||
const bool standardM600 = TERN1(MMU_MENUS, TERN1(HAS_PRUSA_MMU2, !mmu2.enabled()) && TERN1(HAS_PRUSA_MMU3, !mmu3.mmu_hw_enabled));
|
||||
|
||||
// Show initial "wait for start" message
|
||||
if (standardM600)
|
||||
|
@ -157,14 +165,17 @@ void GcodeSuite::M600() {
|
|||
ABS(parser.axisunitsval('L', E_AXIS, fc_settings[active_extruder].load_length)),
|
||||
ADVANCED_PAUSE_PURGE_LENGTH,
|
||||
beep_count,
|
||||
parser.celsiusval('R')
|
||||
parser.celsiusval('R'),
|
||||
true,
|
||||
false
|
||||
DXC_PASS
|
||||
);
|
||||
}
|
||||
else {
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
mmu2_M600();
|
||||
resume_print(0, 0, 0, beep_count, 0 DXC_PASS);
|
||||
#if ENABLED(MMU_MENUS)
|
||||
const bool automatic = parser.seen_test('A');
|
||||
mmu2_M600(automatic);
|
||||
resume_print(0, 0, 0, beep_count, 0, !automatic, false DXC_PASS);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@
|
|||
#include "../../../module/tool_change.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "../../../feature/mmu3/mmu2.h"
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#include "../../../feature/mmu/mmu2.h"
|
||||
#endif
|
||||
|
||||
|
@ -101,7 +103,9 @@ void GcodeSuite::M701() {
|
|||
move_z_by(park_raise);
|
||||
|
||||
// Load filament
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
mmu3.load_to_nozzle(target_extruder);
|
||||
#elif HAS_PRUSA_MMU2
|
||||
mmu2.load_to_nozzle(target_extruder);
|
||||
#else
|
||||
constexpr float purge_length = ADVANCED_PAUSE_PURGE_LENGTH,
|
||||
|
@ -196,7 +200,9 @@ void GcodeSuite::M702() {
|
|||
do_blocking_move_to_z(_MIN(current_position.z + park_point.z, Z_MAX_POS), feedRate_t(NOZZLE_PARK_Z_FEEDRATE));
|
||||
|
||||
// Unload filament
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
mmu3.unload();
|
||||
#elif HAS_PRUSA_MMU2
|
||||
mmu2.unload();
|
||||
#else
|
||||
#if ALL(HAS_MULTI_EXTRUDER, FILAMENT_UNLOAD_ALL_EXTRUDERS)
|
||||
|
|
|
@ -22,10 +22,15 @@
|
|||
|
||||
#include "../../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../gcode.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "../../../feature/mmu3/mmu2.h"
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#include "../../../feature/mmu/mmu2.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* M403: Set filament type for MMU2
|
||||
|
@ -37,13 +42,17 @@
|
|||
* 2 PVA
|
||||
*/
|
||||
void GcodeSuite::M403() {
|
||||
int8_t index = parser.intval('E', -1),
|
||||
const int8_t index = parser.intval('E', -1),
|
||||
type = parser.intval('F', -1);
|
||||
|
||||
if (WITHIN(index, 0, EXTRUDERS - 1) && WITHIN(type, 0, 2))
|
||||
#if HAS_PRUSA_MMU3
|
||||
mmu3.set_filament_type(index, type);
|
||||
#else
|
||||
mmu2.set_filament_type(index, type);
|
||||
#endif
|
||||
else
|
||||
SERIAL_ECHO_MSG("M403 - bad arguments.");
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU2
|
||||
#endif // HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
|
|
204
Marlin/src/gcode/feature/prusa_MMU2/M704-M709.cpp
Normal file
204
Marlin/src/gcode/feature/prusa_MMU2/M704-M709.cpp
Normal file
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "../../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../gcode.h"
|
||||
#include "../../../module/settings.h"
|
||||
#include "../../../feature/mmu3/mmu2.h"
|
||||
#include "../../../feature/mmu3/mmu2_reporting.h"
|
||||
#include "../../../feature/mmu3/SpoolJoin.h"
|
||||
|
||||
// Shared by the G-codes below to save flash memory.
|
||||
static void gcodes_M704_M705_M706(uint16_t gcode) {
|
||||
const int8_t mmuSlotIndex = parser.intval('P', -1);
|
||||
|
||||
if (mmu3.enabled() && WITHIN(mmuSlotIndex, 0, EXTRUDERS - 1)) {
|
||||
switch (gcode) {
|
||||
case 704: mmu3.load_to_feeder(mmuSlotIndex); break;
|
||||
case 705: mmu3.eject_filament(mmuSlotIndex, false); break;
|
||||
case 706:
|
||||
#if ENABLED(MMU_HAS_CUTTER)
|
||||
if (mmu3.cutter_mode > 0) mmu3.cut_filament(mmuSlotIndex);
|
||||
#endif
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ### M704 - Preload to MMU
|
||||
* #### Usage
|
||||
*
|
||||
* M704 [ P ]
|
||||
*
|
||||
* #### Parameters
|
||||
* - `P` - n index of slot (zero based, so 0-4 like T0 and T4)
|
||||
*/
|
||||
void GcodeSuite::M704() {
|
||||
gcodes_M704_M705_M706(704);
|
||||
}
|
||||
|
||||
/**
|
||||
* ### M705 - Eject filament
|
||||
* #### Usage
|
||||
*
|
||||
* M705 [ P ]
|
||||
*
|
||||
* #### Parameters
|
||||
* - `P` - n index of slot (zero based, so 0-4 like T0 and T4)
|
||||
*/
|
||||
void GcodeSuite::M705() {
|
||||
gcodes_M704_M705_M706(705);
|
||||
}
|
||||
|
||||
/*!
|
||||
* ### M706 - Cut filament
|
||||
* #### Usage
|
||||
*
|
||||
* M706 [ P ]
|
||||
*
|
||||
* #### Parameters
|
||||
* - `P` - n index of slot (zero based, so 0-4 like T0 and T4)
|
||||
*/
|
||||
void GcodeSuite::M706() {
|
||||
gcodes_M704_M705_M706(706);
|
||||
}
|
||||
|
||||
/**
|
||||
* ### M707 - Read from MMU register
|
||||
* #### Usage
|
||||
*
|
||||
* M707 [ A ]
|
||||
*
|
||||
* #### Parameters
|
||||
* - `A` - Address of register in hexidecimal.
|
||||
*
|
||||
* #### Example
|
||||
*
|
||||
* M707 A0x1b - Read a 8bit integer from register 0x1b and prints the result onto the serial line.
|
||||
*
|
||||
* Does nothing if the A parameter is not present or if MMU is not enabled.
|
||||
*
|
||||
*/
|
||||
void GcodeSuite::M707() {
|
||||
if (mmu3.enabled() && parser.seenval('A')) {
|
||||
char *address = parser.value_string();
|
||||
mmu3.readRegister(uint8_t(strtol(address, NULL, 16)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ### M708 - Write to MMU register
|
||||
* #### Usage
|
||||
*
|
||||
* M708 [ A | X ]
|
||||
*
|
||||
* #### Parameters
|
||||
* - `A` - Address of register in hexidecimal.
|
||||
* - `X` - Data to write (16-bit integer). Default value 0.
|
||||
*
|
||||
* #### Example
|
||||
* M708 A0x1b X05 - Write to register 0x1b the value 05.
|
||||
*
|
||||
* Does nothing if A parameter is missing or if MMU is not enabled.
|
||||
*/
|
||||
void GcodeSuite::M708() {
|
||||
if (mmu3.enabled() && parser.seenval('A')) {
|
||||
char *address = parser.value_string();
|
||||
const uint8_t addr = uint8_t(strtol(address, NULL, 16));
|
||||
if (addr) {
|
||||
const uint16_t data = parser.ushortval('X', 0);
|
||||
mmu3.writeRegister(addr, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ### M709 - MMU power & reset
|
||||
* The MK3S cannot not power off the MMU, but we can en- and disable the MMU.
|
||||
*
|
||||
* The new state of the MMU is stored in printer's EEPROM.
|
||||
* i.e., If you disable the MMU via M709, it will not be activated after the printer resets.
|
||||
* Usage
|
||||
*
|
||||
* M709 [ S | X ]
|
||||
*
|
||||
* Parameters
|
||||
* - `X` - Reset MMU (0:soft reset | 1:hardware reset | 42: erase MMU eeprom)
|
||||
* - `S` - En-/disable the MMU (0:off | 1:on)
|
||||
*
|
||||
* Examples
|
||||
*
|
||||
* M709 X0 ; issue an X0 command via communication into the MMU (soft reset)
|
||||
* M709 X1 ; toggle the MMU's reset pin (hardware reset)
|
||||
* M709 X42 ; erase MMU EEPROM
|
||||
* M709 S1 ; enable MMU
|
||||
* M709 S0 ; disable MMU
|
||||
* M709 ; Serial message if en- or disabled
|
||||
*/
|
||||
void GcodeSuite::M709() {
|
||||
if (parser.seenval('S')) {
|
||||
if (parser.value_bool())
|
||||
mmu3.start();
|
||||
else
|
||||
mmu3.stop();
|
||||
}
|
||||
|
||||
if (mmu3.enabled() && parser.seenval('X')) {
|
||||
switch (parser.value_byte()) {
|
||||
case 0: mmu3.reset(MMU3::MMU3::Software); break;
|
||||
case 1: mmu3.reset(MMU3::MMU3::ResetPin); break;
|
||||
case 42: mmu3.reset(MMU3::MMU3::EraseEEPROM); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
mmu3.status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Report for M503.
|
||||
* TODO: Report MMU3 G-code settings here, status via a different G-code.
|
||||
*/
|
||||
void GcodeSuite::MMU3_report(const bool forReplay/*=true*/) {
|
||||
using namespace MMU3;
|
||||
report_heading(forReplay, F("MMU3 Operational Stats"));
|
||||
SERIAL_ECHOPGM(" MMU "); serialprintln_onoff(mmu3.mmu_hw_enabled);
|
||||
SERIAL_ECHOPGM(" Stealth Mode "); serialprintln_onoff(mmu3.stealth_mode);
|
||||
#if ENABLED(MMU_HAS_CUTTER)
|
||||
SERIAL_ECHOPGM(" Cutter ");
|
||||
serialprintln_onoff(mmu3.cutter_mode != 0);
|
||||
#endif
|
||||
SERIAL_ECHOPGM(" SpoolJoin "); serialprintln_onoff(spooljoin.enabled);
|
||||
SERIAL_ECHOLNPGM(" Tool Changes ", operation_statistics.tool_change_counter);
|
||||
SERIAL_ECHOLNPGM(" Total Tool Changes ", operation_statistics.tool_change_total_counter);
|
||||
SERIAL_ECHOLNPGM(" Fails ", operation_statistics.fail_num);
|
||||
SERIAL_ECHOLNPGM(" Total Fails ", operation_statistics.fail_total_num);
|
||||
SERIAL_ECHOLNPGM(" Load Fails ", operation_statistics.load_fail_num);
|
||||
SERIAL_ECHOLNPGM(" Total Load Fails ", operation_statistics.load_fail_total_num);
|
||||
SERIAL_ECHOLNPGM(" Power Fails ", mmu3.tmcFailures());
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
|
@ -859,7 +859,7 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
|
|||
case 402: M402(); break; // M402: Stow probe
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
case 403: M403(); break;
|
||||
#endif
|
||||
|
||||
|
@ -988,6 +988,15 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
|
|||
case 702: M702(); break; // M702: Unload Filament
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
case 704: M704(); break; // M704: Preload to MMU
|
||||
case 705: M705(); break; // M705: Eject filament
|
||||
case 706: M706(); break; // M706: Cut filament
|
||||
case 707: M707(); break; // M707: Read from MMU register
|
||||
case 708: M708(); break; // M708: Write to MMU register
|
||||
case 709: M709(); break; // M709: MMU power & reset
|
||||
#endif
|
||||
|
||||
#if ENABLED(CONTROLLER_FAN_EDITABLE)
|
||||
case 710: M710(); break; // M710: Set Controller Fan settings
|
||||
#endif
|
||||
|
|
|
@ -272,6 +272,14 @@
|
|||
* M672 - Set/Reset Duet Smart Effector's sensitivity. (Requires DUET_SMART_EFFECTOR and SMART_EFFECTOR_MOD_PIN)
|
||||
* M701 - Load filament (Requires FILAMENT_LOAD_UNLOAD_GCODES)
|
||||
* M702 - Unload filament (Requires FILAMENT_LOAD_UNLOAD_GCODES)
|
||||
*
|
||||
* M704 - Preload to MMU (Requires PRUSA_MMU3)
|
||||
* M705 - Eject filament (Requires PRUSA_MMU3)
|
||||
* M706 - Cut filament (Requires PRUSA_MMU3)
|
||||
* M707 - Read from MMU register (Requires PRUSA_MMU3)
|
||||
* M708 - Write to MMU register (Requires PRUSA_MMU3)
|
||||
* M709 - MMU power & reset (Requires PRUSA_MMU3)
|
||||
*
|
||||
* M808 - Set or Goto a Repeat Marker (Requires GCODE_REPEAT_MARKERS)
|
||||
* M810-M819 - Define/execute a G-code macro (Requires GCODE_MACROS)
|
||||
* M851 - Set Z probe's XYZ offsets in current units. (Negative values: X=left, Y=front, Z=below)
|
||||
|
@ -1024,7 +1032,7 @@ private:
|
|||
static void M402();
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
static void M403();
|
||||
#endif
|
||||
|
||||
|
@ -1162,6 +1170,16 @@ private:
|
|||
static void M702();
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
static void M704();
|
||||
static void M705();
|
||||
static void M706();
|
||||
static void M707();
|
||||
static void M708();
|
||||
static void M709();
|
||||
static void MMU3_report(const bool forReplay=true);
|
||||
#endif
|
||||
|
||||
#if ENABLED(GCODE_REPEAT_MARKERS)
|
||||
static void M808();
|
||||
#endif
|
||||
|
|
|
@ -177,7 +177,7 @@ void GCodeParser::parse(char *p) {
|
|||
// Skip spaces to get the numeric part
|
||||
while (*p == ' ') p++;
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
if (letter == 'T') {
|
||||
// check for special MMU2 T?/Tx/Tc commands
|
||||
if (*p == '?' || *p == 'x' || *p == 'c') {
|
||||
|
|
|
@ -699,6 +699,8 @@
|
|||
#error "WIFI_SERIAL is now WIFI_SERIAL_PORT."
|
||||
#elif defined(CALIBRATION_MEASUREMENT_RESOLUTION)
|
||||
#error "CALIBRATION_MEASUREMENT_RESOLUTION is no longer needed and should be removed."
|
||||
#elif defined(MMU2_MENUS)
|
||||
#error "MMU2_MENUS is now MMU_MENUS."
|
||||
#endif
|
||||
|
||||
// Changes to Probe Temp Compensation (#17392)
|
||||
|
|
|
@ -95,8 +95,10 @@
|
|||
#define _PRUSA_MMU1 1
|
||||
#define _PRUSA_MMU2 2
|
||||
#define _PRUSA_MMU2S 3
|
||||
#define _PRUSA_MMU3 4
|
||||
#define _EXTENDABLE_EMU_MMU2 12
|
||||
#define _EXTENDABLE_EMU_MMU2S 13
|
||||
#define _EXTENDABLE_EMU_MMU3 14
|
||||
#define _MMU CAT(_,MMU_MODEL)
|
||||
|
||||
#if _MMU == _PRUSA_MMU1
|
||||
|
@ -106,6 +108,8 @@
|
|||
#elif _MMU % 10 == _PRUSA_MMU2S
|
||||
#define HAS_PRUSA_MMU2 1
|
||||
#define HAS_PRUSA_MMU2S 1
|
||||
#elif _MMU % 10 == _PRUSA_MMU3
|
||||
#define HAS_PRUSA_MMU3 1
|
||||
#endif
|
||||
#if _MMU == _EXTENDABLE_EMU_MMU2 || _MMU == _EXTENDABLE_EMU_MMU2S
|
||||
#define HAS_EXTENDABLE_MMU 1
|
||||
|
@ -115,8 +119,10 @@
|
|||
#undef _PRUSA_MMU1
|
||||
#undef _PRUSA_MMU2
|
||||
#undef _PRUSA_MMU2S
|
||||
#undef _PRUSA_MMU3
|
||||
#undef _EXTENDABLE_EMU_MMU2
|
||||
#undef _EXTENDABLE_EMU_MMU2S
|
||||
#undef _EXTENDABLE_EMU_MMU3
|
||||
#endif
|
||||
|
||||
#if ENABLED(E_DUAL_STEPPER_DRIVERS) // E0/E1 steppers act in tandem as E0
|
||||
|
@ -150,7 +156,7 @@
|
|||
#define E_STEPPERS EXTRUDERS
|
||||
#define E_MANUAL EXTRUDERS
|
||||
|
||||
#elif HAS_PRUSA_MMU2 // Průša Multi-Material Unit v2
|
||||
#elif HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3 // Průša Multi-Material Unit v2/v3
|
||||
|
||||
#define E_STEPPERS 1
|
||||
#define E_MANUAL 1
|
||||
|
|
|
@ -865,7 +865,7 @@
|
|||
#undef COOLER_MAXTEMP
|
||||
#endif
|
||||
|
||||
#if HAS_MULTI_EXTRUDER || HAS_MULTI_HOTEND || HAS_PRUSA_MMU2 || (ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1)
|
||||
#if HAS_MULTI_EXTRUDER || HAS_MULTI_HOTEND || HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3 || (ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1)
|
||||
#define HAS_TOOLCHANGE 1
|
||||
#endif
|
||||
|
||||
|
|
|
@ -507,8 +507,8 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
|
|||
#if HAS_FILAMENT_SENSOR
|
||||
#if !PIN_EXISTS(FIL_RUNOUT)
|
||||
#error "FILAMENT_RUNOUT_SENSOR requires FIL_RUNOUT_PIN."
|
||||
#elif HAS_PRUSA_MMU2 && NUM_RUNOUT_SENSORS != 1
|
||||
#error "NUM_RUNOUT_SENSORS must be 1 with MMU2 / MMU2S."
|
||||
#elif (HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3) && NUM_RUNOUT_SENSORS != 1
|
||||
#error "NUM_RUNOUT_SENSORS must be 1 with MMU2 / MMU2S / MMU3."
|
||||
#elif NUM_RUNOUT_SENSORS != 1 && NUM_RUNOUT_SENSORS != E_STEPPERS
|
||||
#error "NUM_RUNOUT_SENSORS must be either 1 or number of E steppers."
|
||||
#elif NUM_RUNOUT_SENSORS >= 8 && !PIN_EXISTS(FIL_RUNOUT8)
|
||||
|
@ -599,7 +599,7 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
|
|||
/**
|
||||
* Multi-Material-Unit 2 / EXTENDABLE_EMU_MMU2 requirements
|
||||
*/
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
#if !HAS_EXTENDABLE_MMU && EXTRUDERS != 5
|
||||
#undef SINGLENOZZLE
|
||||
#error "PRUSA_MMU2(S) requires exactly 5 EXTRUDERS. Please update your Configuration."
|
||||
|
@ -607,14 +607,20 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
|
|||
#error "EXTRUDERS is too large for MMU(S) emulation mode. The maximum value is 15."
|
||||
#elif DISABLED(NOZZLE_PARK_FEATURE)
|
||||
#error "PRUSA_MMU2(S) requires NOZZLE_PARK_FEATURE. Enable it to continue."
|
||||
#elif HAS_PRUSA_MMU2S && DISABLED(FILAMENT_RUNOUT_SENSOR)
|
||||
#error "PRUSA_MMU2S requires FILAMENT_RUNOUT_SENSOR. Enable it to continue."
|
||||
#elif (HAS_PRUSA_MMU2S || HAS_PRUSA_MMU3) && DISABLED(FILAMENT_RUNOUT_SENSOR)
|
||||
#error "PRUSA_MMU2S and HAS_PRUSA_MMU3 requires FILAMENT_RUNOUT_SENSOR. Enable it to continue."
|
||||
#elif ENABLED(MMU_EXTRUDER_SENSOR) && DISABLED(FILAMENT_RUNOUT_SENSOR)
|
||||
#error "MMU_EXTRUDER_SENSOR requires FILAMENT_RUNOUT_SENSOR. Enable it to continue."
|
||||
#elif ENABLED(MMU_EXTRUDER_SENSOR) && !HAS_MARLINUI_MENU
|
||||
#error "MMU_EXTRUDER_SENSOR requires an LCD supporting MarlinUI."
|
||||
#elif ENABLED(MMU2_MENUS) && !HAS_MARLINUI_MENU
|
||||
#error "MMU2_MENUS requires an LCD supporting MarlinUI."
|
||||
#elif ENABLED(MMU_MENUS) && !HAS_MARLINUI_MENU
|
||||
#error "MMU_MENUS requires an LCD supporting MarlinUI."
|
||||
#elif HAS_PRUSA_MMU3 && !HAS_MARLINUI_MENU
|
||||
#error "MMU3 requires an LCD supporting MarlinUI."
|
||||
#elif HAS_PRUSA_MMU3 && DISABLED(MMU_MENUS)
|
||||
#error "MMU3 requires MMU_MENUS."
|
||||
#elif HAS_PRUSA_MMU3 && DISABLED(EEPROM_SETTINGS)
|
||||
#error "MMU3 requires EEPROM_SETTINGS."
|
||||
#elif DISABLED(ADVANCED_PAUSE_FEATURE)
|
||||
static_assert(nullptr == strstr(MMU2_FILAMENT_RUNOUT_SCRIPT, "M600"), "MMU2_FILAMENT_RUNOUT_SCRIPT cannot make use of M600 unless ADVANCED_PAUSE_FEATURE is enabled");
|
||||
#endif
|
||||
|
@ -690,8 +696,8 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
|
|||
#error "MECHANICAL_SWITCHING_NOZZLE and DUAL_X_CARRIAGE are incompatible."
|
||||
#elif ENABLED(SINGLENOZZLE)
|
||||
#error "MECHANICAL_SWITCHING_NOZZLE and SINGLENOZZLE are incompatible."
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#error "MECHANICAL_SWITCHING_NOZZLE and PRUSA_MMU2(S) are incompatible."
|
||||
#elif HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
#error "MECHANICAL_SWITCHING_NOZZLE and PRUSA_MMU2(2S,3) are incompatible."
|
||||
#elif !defined(EVENT_GCODE_TOOLCHANGE_T0)
|
||||
#error "MECHANICAL_SWITCHING_NOZZLE requires EVENT_GCODE_TOOLCHANGE_T0."
|
||||
#elif !defined(EVENT_GCODE_TOOLCHANGE_T1)
|
||||
|
@ -704,8 +710,8 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
|
|||
#error "SWITCHING_NOZZLE and DUAL_X_CARRIAGE are incompatible."
|
||||
#elif ENABLED(SINGLENOZZLE)
|
||||
#error "SWITCHING_NOZZLE and SINGLENOZZLE are incompatible."
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#error "SWITCHING_NOZZLE and PRUSA_MMU2(S) are incompatible."
|
||||
#elif HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
#error "SWITCHING_NOZZLE and PRUSA_MMU2(2S,3) are incompatible."
|
||||
#elif NUM_SERVOS < 1
|
||||
#error "SWITCHING_NOZZLE requires NUM_SERVOS >= 1."
|
||||
#elif !defined(SWITCHING_NOZZLE_SERVO_NR)
|
||||
|
|
|
@ -761,24 +761,52 @@ namespace LanguageNarrow_en {
|
|||
LSTR MSG_MMU2_MENU = _UxGT("MMU");
|
||||
LSTR MSG_KILL_MMU2_FIRMWARE = _UxGT("Update MMU Firmware!");
|
||||
LSTR MSG_MMU2_NOT_RESPONDING = _UxGT("MMU Needs Attention.");
|
||||
LSTR MSG_MMU2_RESUME = _UxGT("MMU Resume");
|
||||
LSTR MSG_MMU2_RESUME = _UxGT("Resume");
|
||||
LSTR MSG_MMU2_RESUMING = _UxGT("MMU Resuming...");
|
||||
LSTR MSG_MMU2_LOAD_FILAMENT = _UxGT("MMU Load");
|
||||
LSTR MSG_MMU2_LOAD_ALL = _UxGT("MMU Load All");
|
||||
LSTR MSG_MMU2_LOAD_TO_NOZZLE = _UxGT("MMU Load to Nozzle");
|
||||
LSTR MSG_MMU2_EJECT_FILAMENT = _UxGT("MMU Eject");
|
||||
LSTR MSG_MMU2_EJECT_FILAMENT_N = _UxGT("MMU Eject ~");
|
||||
LSTR MSG_MMU2_UNLOAD_FILAMENT = _UxGT("MMU Unload");
|
||||
LSTR MSG_MMU2_LOAD_FILAMENT = _UxGT("Load");
|
||||
LSTR MSG_MMU2_LOAD_ALL = _UxGT("Load All");
|
||||
LSTR MSG_MMU2_LOAD_TO_NOZZLE = _UxGT("Load to Nozzle");
|
||||
LSTR MSG_MMU2_CUT_FILAMENT = _UxGT("Cut");
|
||||
LSTR MSG_MMU2_EJECT_FILAMENT = _UxGT("Eject");
|
||||
LSTR MSG_MMU2_EJECT_FILAMENT_N = _UxGT("Eject ~");
|
||||
LSTR MSG_MMU2_UNLOAD_FILAMENT = _UxGT("Unload");
|
||||
LSTR MSG_MMU2_LOADING_FILAMENT = _UxGT("Filament %i Load...");
|
||||
LSTR MSG_MMU2_EJECTING_FILAMENT = _UxGT("Filament Eject...");
|
||||
LSTR MSG_MMU2_UNLOADING_FILAMENT = _UxGT("Filament Unload...");
|
||||
LSTR MSG_MMU2_CUTTING_FILAMENT = _UxGT("Filament %i Cut...");
|
||||
LSTR MSG_MMU2_EJECTING_FILAMENT = _UxGT("Filament %i Eject...");
|
||||
LSTR MSG_MMU2_UNLOADING_FILAMENT = _UxGT("Filament %i Unload...");
|
||||
LSTR MSG_MMU2_ALL = _UxGT("All");
|
||||
LSTR MSG_MMU2_FILAMENT_N = _UxGT("Filament ~");
|
||||
LSTR MSG_MMU2_RESET = _UxGT("Reset MMU");
|
||||
LSTR MSG_MMU2_RESETTING = _UxGT("MMU Resetting...");
|
||||
LSTR MSG_MMU2_EJECT_RECOVER = _UxGT("MMU2 Eject Recover");
|
||||
LSTR MSG_MMU2_RESETTING = _UxGT("Resetting...");
|
||||
LSTR MSG_MMU2_EJECT_RECOVER = _UxGT("Eject Recover");
|
||||
LSTR MSG_MMU2_REMOVE_AND_CLICK = _UxGT("Remove and click...");
|
||||
|
||||
LSTR MSG_MMU_SENSITIVITY = _UxGT("Sensitivity");
|
||||
LSTR MSG_MMU_CUTTER = _UxGT("Cutter");
|
||||
LSTR MSG_MMU_CUTTER_MODE = _UxGT("Cutter Mode");
|
||||
LSTR MSG_MMU_CUTTER_MODE_DISABLE = _UxGT("Disable");
|
||||
LSTR MSG_MMU_CUTTER_MODE_ENABLE = _UxGT("Enable");
|
||||
LSTR MSG_MMU_CUTTER_MODE_ALWAYS = _UxGT("Always");
|
||||
LSTR MSG_MMU_SPOOL_JOIN = _UxGT("Spool Join");
|
||||
LSTR MSG_MMU_STEALTH = _UxGT("Stealth Mode");
|
||||
|
||||
LSTR MSG_MMU_FAIL_STATS = _UxGT("Fail stats");
|
||||
LSTR MSG_MMU_STATISTICS = _UxGT("Statistics");
|
||||
LSTR MSG_MMU_RESET_FAIL_STATS = _UxGT("Reset Fail Stats");
|
||||
LSTR MSG_MMU_RESET_STATS = _UxGT("Reset All Stats");
|
||||
LSTR MSG_MMU_CURRENT_PRINT = _UxGT("Curr. print");
|
||||
LSTR MSG_MMU_CURRENT_PRINT_FAILURES = _UxGT("Curr. print failures");
|
||||
LSTR MSG_MMU_LAST_PRINT = _UxGT("Last print");
|
||||
LSTR MSG_MMU_LAST_PRINT_FAILURES = _UxGT("Last print failures");
|
||||
LSTR MSG_MMU_TOTAL = _UxGT("Total");
|
||||
LSTR MSG_MMU_TOTAL_FAILURES = _UxGT("Total failures");
|
||||
LSTR MSG_MMU_DEV_INCREMENT_FAILS = _UxGT("Increment fails");
|
||||
LSTR MSG_MMU_DEV_INCREMENT_LOAD_FAILS = _UxGT("Increment load fails");
|
||||
LSTR MSG_MMU_FAILS = _UxGT("MMU fails");
|
||||
LSTR MSG_MMU_LOAD_FAILS = _UxGT("MMU load fails");
|
||||
LSTR MSG_MMU_POWER_FAILS = _UxGT("MMU power fails");
|
||||
LSTR MSG_MMU_MATERIAL_CHANGES = _UxGT("Material changes");
|
||||
|
||||
LSTR MSG_MIX = _UxGT("Mix");
|
||||
LSTR MSG_MIX_COMPONENT_N = _UxGT("Component {");
|
||||
LSTR MSG_MIXER = _UxGT("Mixer");
|
||||
|
@ -937,6 +965,251 @@ namespace LanguageNarrow_en {
|
|||
LSTR DGUS_MSG_WRITE_EEPROM_FAILED = _UxGT("EEPROM write failed");
|
||||
LSTR DGUS_MSG_READ_EEPROM_FAILED = _UxGT("EEPROM read failed");
|
||||
LSTR DGUS_MSG_FILAMENT_RUNOUT = _UxGT("Filament runout E%d");
|
||||
|
||||
//
|
||||
// MMU3 Translatable Strings
|
||||
//
|
||||
|
||||
LSTR MSG_TITLE_FINDA_DIDNT_TRIGGER = _UxGT("FINDA DIDNT TRIGGER");
|
||||
LSTR MSG_TITLE_FINDA_FILAMENT_STUCK = _UxGT("FINDA FILAM. STUCK");
|
||||
LSTR MSG_TITLE_FSENSOR_DIDNT_TRIGGER = _UxGT("FSENSOR DIDNT TRIGG.");
|
||||
LSTR MSG_TITLE_FSENSOR_FILAMENT_STUCK = _UxGT("FSENSOR FIL. STUCK");
|
||||
LSTR MSG_TITLE_PULLEY_CANNOT_MOVE = _UxGT("PULLEY CANNOT MOVE");
|
||||
LSTR MSG_TITLE_FSENSOR_TOO_EARLY = _UxGT("FSENSOR TOO EARLY");
|
||||
LSTR MSG_TITLE_INSPECT_FINDA = _UxGT("INSPECT FINDA");
|
||||
LSTR MSG_TITLE_LOAD_TO_EXTRUDER_FAILED = _UxGT("LOAD TO EXTR. FAILED");
|
||||
LSTR MSG_TITLE_SELECTOR_CANNOT_MOVE = _UxGT("SELECTOR CANNOT MOVE");
|
||||
LSTR MSG_TITLE_SELECTOR_CANNOT_HOME = _UxGT("SELECTOR CANNOT HOME");
|
||||
LSTR MSG_TITLE_IDLER_CANNOT_MOVE = _UxGT("IDLER CANNOT MOVE");
|
||||
LSTR MSG_TITLE_IDLER_CANNOT_HOME = _UxGT("IDLER CANNOT HOME");
|
||||
LSTR MSG_TITLE_TMC_WARNING_TMC_TOO_HOT = _UxGT("WARNING TMC TOO HOT");
|
||||
LSTR MSG_TITLE_TMC_OVERHEAT_ERROR = _UxGT("TMC OVERHEAT ERROR");
|
||||
LSTR MSG_TITLE_TMC_DRIVER_ERROR = _UxGT("TMC DRIVER ERROR");
|
||||
LSTR MSG_TITLE_TMC_DRIVER_RESET = _UxGT("TMC DRIVER RESET");
|
||||
LSTR MSG_TITLE_TMC_UNDERVOLTAGE_ERROR = _UxGT("TMC UNDERVOLTAGE ERR");
|
||||
LSTR MSG_TITLE_TMC_DRIVER_SHORTED = _UxGT("TMC DRIVER SHORTED");
|
||||
LSTR MSG_TITLE_SELFTEST_FAILED = _UxGT("MMU SELFTEST FAILED");
|
||||
LSTR MSG_TITLE_MMU_MCU_ERROR = _UxGT("MMU MCU ERROR");
|
||||
LSTR MSG_TITLE_MMU_NOT_RESPONDING = _UxGT("MMU NOT RESPONDING");
|
||||
LSTR MSG_TITLE_COMMUNICATION_ERROR = _UxGT("COMMUNICATION ERROR");
|
||||
LSTR MSG_TITLE_FILAMENT_ALREADY_LOADED = _UxGT("FIL. ALREADY LOADED");
|
||||
LSTR MSG_TITLE_INVALID_TOOL = _UxGT("INVALID TOOL");
|
||||
LSTR MSG_TITLE_QUEUE_FULL = _UxGT("QUEUE FULL");
|
||||
LSTR MSG_TITLE_FW_UPDATE_NEEDED = _UxGT("MMU FW UPDATE NEEDED");
|
||||
LSTR MSG_TITLE_FW_RUNTIME_ERROR = _UxGT("FW RUNTIME ERROR");
|
||||
LSTR MSG_TITLE_UNLOAD_MANUALLY = _UxGT("UNLOAD MANUALLY");
|
||||
LSTR MSG_TITLE_FILAMENT_EJECTED = _UxGT("FILAMENT EJECTED");
|
||||
LSTR MSG_TITLE_FILAMENT_CHANGE = _UxGT("FILAMENT CHANGE");
|
||||
LSTR MSG_TITLE_UNKNOWN_ERROR = _UxGT("UNKNOWN ERROR");
|
||||
|
||||
LSTR MSG_DESC_FINDA_DIDNT_TRIGGER = _UxGT("FINDA didn't trigger while loading the filament. Ensure the filament can move and FINDA works.");
|
||||
LSTR MSG_DESC_FINDA_FILAMENT_STUCK = _UxGT("FINDA didn't switch off while unloading filament. Try unloading manually. Ensure filament can move and FINDA works.");
|
||||
LSTR MSG_DESC_FSENSOR_DIDNT_TRIGGER = _UxGT("Filament sensor didn't trigger while loading the filament. Ensure the sensor is calibrated and the filament reached it.");
|
||||
LSTR MSG_DESC_FSENSOR_FILAMENT_STUCK = _UxGT("Filament sensor didn't switch off while unloading filament. Ensure filament can move and the sensor works.");
|
||||
LSTR MSG_DESC_PULLEY_CANNOT_MOVE = _UxGT("Pulley motor stalled. Ensure the pulley can move and check the wiring.");
|
||||
LSTR MSG_DESC_FSENSOR_TOO_EARLY = _UxGT("Filament sensor triggered too early while loading to extruder. Check there isn't anything stuck in PTFE tube. Check that sensor reads properly.");
|
||||
LSTR MSG_DESC_INSPECT_FINDA = _UxGT("Selector can't move due to FINDA detecting a filament. Make sure no filament is in Selector and FINDA works properly.");
|
||||
LSTR MSG_DESC_LOAD_TO_EXTRUDER_FAILED = _UxGT("Loading to extruder failed. Inspect the filament tip shape. Refine the sensor calibration, if needed.");
|
||||
LSTR MSG_DESC_SELECTOR_CANNOT_HOME = _UxGT("The Selector cannot home properly. Check for anything blocking its movement.");
|
||||
LSTR MSG_DESC_CANNOT_MOVE = _UxGT("Can't move Selector or Idler.");
|
||||
LSTR MSG_DESC_IDLER_CANNOT_HOME = _UxGT("The Idler cannot home properly. Check for anything blocking its movement.");
|
||||
LSTR MSG_DESC_TMC = _UxGT("More details online.");
|
||||
LSTR MSG_DESC_MMU_NOT_RESPONDING = _UxGT("MMU not responding. Check the wiring and connectors.");
|
||||
LSTR MSG_DESC_COMMUNICATION_ERROR = _UxGT("MMU not responding correctly. Check the wiring and connectors.");
|
||||
LSTR MSG_DESC_FILAMENT_ALREADY_LOADED = _UxGT("Cannot perform the action, filament is already loaded. Unload it first.");
|
||||
LSTR MSG_DESC_INVALID_TOOL = _UxGT("Requested filament tool is not available on this hardware. Check the G-code for tool index out of range (T0-T4).");
|
||||
LSTR MSG_DESC_QUEUE_FULL = _UxGT("MMU Firmware internal error, please reset the MMU.");
|
||||
LSTR MSG_DESC_FW_RUNTIME_ERROR = _UxGT("Internal runtime error. Try resetting the MMU or updating the firmware.");
|
||||
LSTR MSG_DESC_UNLOAD_MANUALLY = _UxGT("Filament detected unexpectedly. Ensure no filament is loaded. Check the sensors and wiring.");
|
||||
LSTR MSG_DESC_FILAMENT_EJECTED = _UxGT("Remove the ejected filament from the front of the MMU.");
|
||||
LSTR MSG_DESC_FILAMENT_CHANGE = _UxGT("M600 Filament Change. Load a new filament or eject the old one.");
|
||||
LSTR MSG_DESC_UNKNOWN_ERROR = _UxGT("Unexpected error occurred.");
|
||||
|
||||
LSTR MSG_DESC_FW_UPDATE_NEEDED = _UxGT("MMU FW version is not supported. Update to version " STRINGIFY(mmuVersionMajor) "." STRINGIFY(mmuVersionMinor) "." STRINGIFY(mmuVersionPatch) ".");
|
||||
|
||||
LSTR MSG_BTN_RETRY = _UxGT("Retry");
|
||||
LSTR MSG_BTN_RESET_MMU = _UxGT("ResetMMU");
|
||||
LSTR MSG_BTN_UNLOAD = _UxGT("Unload");
|
||||
LSTR MSG_BTN_LOAD = _UxGT("Load");
|
||||
LSTR MSG_BTN_EJECT = _UxGT("Eject");
|
||||
LSTR MSG_BTN_STOP = _UxGT("Stop");
|
||||
LSTR MSG_BTN_DISABLE_MMU = _UxGT("Disable");
|
||||
LSTR MSG_BTN_MORE = _UxGT("More Info");
|
||||
|
||||
LSTR MSG_ALWAYS = _UxGT("Always");
|
||||
LSTR MSG_BABYSTEP_Z_NOT_SET = _UxGT("Distance between tip of the nozzle and the bed surface has not been set yet. Please follow the manual, chapter First steps, section First layer calibration.");
|
||||
LSTR MSG_BED_DONE = _UxGT("Bed done");
|
||||
LSTR MSG_BED_LEVELING_FAILED_POINT_LOW = _UxGT("Bed leveling failed. Sensor didn't trigger. Debris on nozzle? Waiting for reset.");
|
||||
LSTR MSG_BED_SKEW_OFFSET_DETECTION_FITTING_FAILED = _UxGT("XYZ calibration failed. Please consult the manual.");
|
||||
LSTR MSG_BELT_STATUS = _UxGT("Belt status");
|
||||
LSTR MSG_CANCEL = _UxGT(">Cancel");
|
||||
LSTR MSG_CALIBRATE_Z_AUTO = _UxGT("Calibrating Z");
|
||||
LSTR MSG_CARD_MENU = _UxGT("Print from SD");
|
||||
LSTR MSG_CHECKING_X = _UxGT("Checking X axis");
|
||||
LSTR MSG_CHECKING_Y = _UxGT("Checking Y axis");
|
||||
LSTR MSG_COMMUNITY_MADE = _UxGT("Community made");
|
||||
LSTR MSG_CONFIRM_NOZZLE_CLEAN = _UxGT("Please clean the nozzle for calibration. Click when done.");
|
||||
LSTR MSG_CRASH = _UxGT("Crash");
|
||||
LSTR MSG_CRASH_DETECTED = _UxGT("Crash detected.");
|
||||
LSTR MSG_CRASHDETECT = _UxGT("Crash det.");
|
||||
LSTR MSG_DONE = _UxGT("Done");
|
||||
LSTR MSG_EXTRUDER = _UxGT("Extruder");
|
||||
LSTR MSG_FANS_CHECK = _UxGT("Fans check");
|
||||
LSTR MSG_FIL_RUNOUTS = _UxGT("Fil. runouts");
|
||||
LSTR MSG_HOTEND_FAN_SPEED = _UxGT("Hotend fan:");
|
||||
LSTR MSG_PRINT_FAN_SPEED = _UxGT("Print fan:");
|
||||
LSTR MSG_FILAMENT_CLEAN = _UxGT("Filament extruding & with correct color?");
|
||||
LSTR MSG_FILAMENT_LOADED = _UxGT("Is filament loaded?");
|
||||
LSTR MSG_FIND_BED_OFFSET_AND_SKEW_LINE1 = _UxGT("Searching bed calibration point");
|
||||
LSTR MSG_FINISHING_MOVEMENTS = _UxGT("Finishing movements");
|
||||
LSTR MSG_FOLLOW_CALIBRATION_FLOW = _UxGT("Printer has not been calibrated yet. Please follow the manual, chapter First steps, section Calibration flow.");
|
||||
LSTR MSG_FOLLOW_Z_CALIBRATION_FLOW = _UxGT("There is still a need to make Z calibration. Please follow the manual, chapter First steps, section Calibration flow.");
|
||||
LSTR MSG_FSENSOR_RUNOUT = _UxGT("F. runout");
|
||||
LSTR MSG_FSENSOR_AUTOLOAD = _UxGT("F. autoload");
|
||||
LSTR MSG_FSENSOR_JAM_DETECTION = _UxGT("F. jam detect");
|
||||
LSTR MSG_FSENSOR = _UxGT("Fil. sensor");
|
||||
LSTR MSG_HEATING_COMPLETE = _UxGT("Heating done.");
|
||||
LSTR MSG_HOMEYZ = _UxGT("Calibrate Z");
|
||||
LSTR MSG_SELECT_FILAMENT = _UxGT("Select filament:");
|
||||
LSTR MSG_LAST_PRINT = _UxGT("Last print");
|
||||
LSTR MSG_LAST_PRINT_FAILURES = _UxGT("Last print failures");
|
||||
LSTR MSG_PRELOAD_TO_MMU = _UxGT("Preload to MMU");
|
||||
LSTR MSG_LOAD_FILAMENT = _UxGT("Load filament");
|
||||
LSTR MSG_LOADING_TEST = _UxGT("Loading Test");
|
||||
LSTR MSG_LOADING_FILAMENT = _UxGT("Loading filament");
|
||||
LSTR MSG_TESTING_FILAMENT = _UxGT("Testing filament");
|
||||
LSTR MSG_EJECT_FROM_MMU = _UxGT("Eject from MMU");
|
||||
LSTR MSG_CUT_FILAMENT = _UxGT("Cut filament");
|
||||
LSTR MSG_SHEET = _UxGT("Sheet");
|
||||
LSTR MSG_STEEL_SHEETS = _UxGT("Steel sheets");
|
||||
LSTR MSG_MEASURE_BED_REFERENCE_HEIGHT_LINE1 = _UxGT("Measuring reference height of calibration point");
|
||||
LSTR MSG_CALIBRATION = _UxGT("Calibration");
|
||||
LSTR MSG_PAPER = _UxGT("Place a sheet of paper under the nozzle during the calibration of first 4 points. If the nozzle catches the paper, power off the printer immediately.");
|
||||
LSTR MSG_PLACE_STEEL_SHEET = _UxGT("Please place steel sheet on heatbed.");
|
||||
LSTR MSG_POWER_FAILURES = _UxGT("Power failures");
|
||||
LSTR MSG_PREHEAT_NOZZLE = _UxGT("Preheat the nozzle!");
|
||||
LSTR MSG_PRESS_TO_UNLOAD = _UxGT("Please press the knob to unload filament");
|
||||
LSTR MSG_PULL_OUT_FILAMENT = _UxGT("Please pull out filament immediately");
|
||||
LSTR MSG_RECOVER_PRINT = _UxGT("Blackout occurred. Recover print?");
|
||||
LSTR MSG_REMOVE_STEEL_SHEET = _UxGT("Please remove steel sheet from heatbed.");
|
||||
LSTR MSG_RESET = _UxGT("Reset");
|
||||
LSTR MSG_RESUMING_PRINT = _UxGT("Resuming print");
|
||||
LSTR MSG_SELFTEST_PART_FAN = _UxGT("Front print fan?");
|
||||
LSTR MSG_SELFTEST_HOTEND_FAN = _UxGT("Left hotend fan?");
|
||||
LSTR MSG_SELFTEST_FAILED = _UxGT("Selftest failed");
|
||||
LSTR MSG_SELFTEST_FAN = _UxGT("Fan test");
|
||||
LSTR MSG_SELFTEST_FAN_NO = _UxGT("Not spinning");
|
||||
LSTR MSG_SELFTEST_FAN_YES = _UxGT("Spinning");
|
||||
LSTR MSG_SELFTEST_CHECK_BED = _UxGT("Checking bed");
|
||||
LSTR MSG_SELFTEST_CHECK_FSENSOR = _UxGT("Checking sensors");
|
||||
LSTR MSG_SELFTEST_MOTOR = _UxGT("Motor");
|
||||
LSTR MSG_SELFTEST_FILAMENT_SENSOR = _UxGT("Filament sensor");
|
||||
LSTR MSG_SELFTEST_WIRINGERROR = _UxGT("Wiring error");
|
||||
LSTR MSG_SETTINGS = _UxGT("Settings");
|
||||
LSTR MSG_SET_READY = _UxGT("Set Ready");
|
||||
LSTR MSG_SET_NOT_READY = _UxGT("Set not Ready");
|
||||
LSTR MSG_SELECT_LANGUAGE = _UxGT("Select language");
|
||||
LSTR MSG_SORTING_FILES = _UxGT("Sorting files");
|
||||
LSTR MSG_TOTAL = _UxGT("Total");
|
||||
LSTR MSG_MATERIAL_CHANGES = _UxGT("Material changes");
|
||||
LSTR MSG_TOTAL_FAILURES = _UxGT("Total failures");
|
||||
LSTR MSG_HW_SETUP = _UxGT("HW Setup");
|
||||
LSTR MSG_MODE = _UxGT("Mode");
|
||||
LSTR MSG_HIGH_POWER = _UxGT("High power");
|
||||
LSTR MSG_AUTO_POWER = _UxGT("Auto power");
|
||||
LSTR MSG_SILENT = _UxGT("Silent");
|
||||
LSTR MSG_NORMAL = _UxGT("Normal");
|
||||
LSTR MSG_STEALTH = _UxGT("Stealth");
|
||||
LSTR MSG_STEEL_SHEET_CHECK = _UxGT("Is steel sheet on heatbed?");
|
||||
LSTR MSG_PINDA_CALIBRATION = _UxGT("PINDA cal.");
|
||||
LSTR MSG_PINDA_CALIBRATION_DONE = _UxGT("PINDA calibration is finished and active. It can be disabled in menu Settings->PINDA cal.");
|
||||
LSTR MSG_UNLOAD_FILAMENT = _UxGT("Unload filament");
|
||||
LSTR MSG_UNLOADING_FILAMENT = _UxGT("Unloading filament");
|
||||
LSTR MSG_WIZARD_CALIBRATION_FAILED = _UxGT("Please check our handbook and fix the problem. Then resume the Wizard by rebooting the printer.");
|
||||
LSTR MSG_WIZARD_DONE = _UxGT("All done. Happy printing!");
|
||||
LSTR MSG_WIZARD_HEATING = _UxGT("Preheating nozzle. Please wait.");
|
||||
LSTR MSG_WIZARD_QUIT = _UxGT("You can always resume the Wizard from Calibration -> Wizard.");
|
||||
LSTR MSG_WIZARD_WELCOME = _UxGT("Hi, I am your Original Prusa i3 printer. Would you like me to guide you through the setup process?");
|
||||
LSTR MSG_WIZARD_WELCOME_SHIPPING = _UxGT("Hi, I am your Original Prusa i3 printer. I will guide you through a short setup process, in which the Z-axis will be calibrated. Then, you will be ready to print.");
|
||||
LSTR MSG_V2_CALIBRATION = _UxGT("First layer cal.");
|
||||
LSTR MSG_OFF = _UxGT("Off");
|
||||
LSTR MSG_ON = _UxGT("On");
|
||||
LSTR MSG_NA = _UxGT("N/A");
|
||||
LSTR MSG_NONE = _UxGT("None");
|
||||
LSTR MSG_WARN = _UxGT("Warn");
|
||||
LSTR MSG_STRICT = _UxGT("Strict");
|
||||
LSTR MSG_MODEL = _UxGT("Model");
|
||||
LSTR MSG_GCODE_DIFF_PRINTER_CONTINUE = _UxGT("G-code sliced for a different printer type. Continue?");
|
||||
LSTR MSG_GCODE_DIFF_PRINTER_CANCELLED = _UxGT("G-code sliced for a different printer type. Please re-slice the model again. Print cancelled.");
|
||||
LSTR MSG_GCODE_NEWER_FIRMWARE_CONTINUE = _UxGT("G-code sliced for a newer firmware. Continue?");
|
||||
LSTR MSG_GCODE_NEWER_FIRMWARE_CANCELLED = _UxGT("G-code sliced for a newer firmware. Please update the firmware. Print cancelled.");
|
||||
LSTR MSG_GCODE_DIFF_CONTINUE = _UxGT("G-code sliced for a different level. Continue?");
|
||||
LSTR MSG_GCODE_DIFF_CANCELLED = _UxGT("G-code sliced for a different level. Please re-slice the model again. Print cancelled.");
|
||||
LSTR MSG_NOZZLE_DIFFERS_CONTINUE = _UxGT("Nozzle diameter differs from the G-code. Continue?");
|
||||
LSTR MSG_NOZZLE_DIFFERS_CANCELLED = _UxGT("Nozzle diameter differs from the G-code. Please check the value in settings. Print cancelled.");
|
||||
LSTR MSG_NOZZLE_DIAMETER = _UxGT("Nozzle d.");
|
||||
LSTR MSG_MMU_MODE = _UxGT("MMU Mode");
|
||||
LSTR MSG_SORT = _UxGT("Sort");
|
||||
LSTR MSG_SORT_TIME = _UxGT("Time");
|
||||
LSTR MSG_SORT_ALPHA = _UxGT("Alphabet");
|
||||
LSTR MSG_RPI_PORT = _UxGT("RPi port");
|
||||
LSTR MSG_SOUND_LOUD = _UxGT("Loud");
|
||||
LSTR MSG_SOUND_ONCE = _UxGT("Once");
|
||||
LSTR MSG_SOUND_BLIND = _UxGT("Assist");
|
||||
LSTR MSG_MESH = _UxGT("Mesh");
|
||||
LSTR MSG_MESH_BED_LEVELING = _UxGT("Mesh Bed Leveling");
|
||||
LSTR MSG_Z_PROBE_NR = _UxGT("Z-probe nr.");
|
||||
LSTR MSG_MAGNETS_COMP = _UxGT("Magnets comp.");
|
||||
LSTR MSG_FS_ACTION = _UxGT("FS Action");
|
||||
LSTR MSG_CONTINUE_SHORT = _UxGT("Cont.");
|
||||
LSTR MSG_PAUSE = _UxGT("Pause");
|
||||
LSTR MSG_BL_HIGH = _UxGT("Level Bright");
|
||||
LSTR MSG_BL_LOW = _UxGT("Level Dimmed");
|
||||
LSTR MSG_BRIGHT = _UxGT("Bright");
|
||||
LSTR MSG_DIM = _UxGT("Dim");
|
||||
LSTR MSG_AUTO = _UxGT("Auto");
|
||||
#if FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG
|
||||
// Beware - The space at the beginning is necessary since it is reused in LCD menu items which are to be with a space
|
||||
LSTR MSG_IR_04_OR_NEWER = _UxGT(" 0.4 or newer");
|
||||
LSTR MSG_IR_03_OR_OLDER = _UxGT(" 0.3 or older");
|
||||
LSTR MSG_IR_UNKNOWN = _UxGT("unknown state");
|
||||
#endif
|
||||
LSTR MSG_PAUSED_THERMAL_ERROR = _UxGT("PAUSED THERMAL ERROR");
|
||||
#if ENABLED(THERMAL_MODEL)
|
||||
LSTR MSG_THERMAL_ANOMALY = _UxGT("THERMAL ANOMALY");
|
||||
LSTR MSG_TM_NOT_CAL = _UxGT("Thermal model not calibrated yet.");
|
||||
LSTR MSG_TM_ACK_ERROR = _UxGT("Clear TM error");
|
||||
#endif
|
||||
LSTR MSG_LOAD_ALL = _UxGT("Load All");
|
||||
LSTR MSG_NOZZLE_CNG_MENU = _UxGT("Nozzle change");
|
||||
LSTR MSG_NOZZLE_CNG_READ_HELP = _UxGT("For a Nozzle change please read\nprusa.io/nozzle-mk3s");
|
||||
LSTR MSG_NOZZLE_CNG_CHANGED = _UxGT("Hotend at 280C! Nozzle changed and tightened to specs?");
|
||||
LSTR MSG_REPRINT = _UxGT("Reprint");
|
||||
|
||||
LSTR MSG_PROGRESS_OK = _UxGT("OK");
|
||||
LSTR MSG_PROGRESS_ENGAGE_IDLER = _UxGT("Engaging idler");
|
||||
LSTR MSG_PROGRESS_DISENGAGE_IDLER = _UxGT("Disengaging idler");
|
||||
LSTR MSG_PROGRESS_UNLOAD_FINDA = _UxGT("Unloading to FINDA");
|
||||
LSTR MSG_PROGRESS_UNLOAD_PULLEY = _UxGT("Unloading to pulley");
|
||||
LSTR MSG_PROGRESS_FEED_FINDA = _UxGT("Feeding to FINDA");
|
||||
LSTR MSG_PROGRESS_FEED_EXTRUDER = _UxGT("Feeding to extruder");
|
||||
LSTR MSG_PROGRESS_FEED_NOZZLE = _UxGT("Feeding to nozzle");
|
||||
LSTR MSG_PROGRESS_AVOID_GRIND = _UxGT("Avoiding grind");
|
||||
LSTR MSG_PROGRESS_WAIT_USER = _UxGT("ERR Wait for User");
|
||||
LSTR MSG_PROGRESS_ERR_INTERNAL = _UxGT("ERR Internal");
|
||||
LSTR MSG_PROGRESS_ERR_HELP_FIL = _UxGT("ERR Help filament");
|
||||
LSTR MSG_PROGRESS_ERR_TMC = _UxGT("ERR TMC failed");
|
||||
LSTR MSG_PROGRESS_SELECT_SLOT = _UxGT("Selecting fil. slot");
|
||||
LSTR MSG_PROGRESS_PREPARE_BLADE = _UxGT("Preparing blade");
|
||||
LSTR MSG_PROGRESS_PUSH_FILAMENT = _UxGT("Pushing filament");
|
||||
LSTR MSG_PROGRESS_PERFORM_CUT = _UxGT("Performing cut");
|
||||
LSTR MSG_PROGRESSPSTRETURN_SELECTOR = _UxGT("Returning selector");
|
||||
LSTR MSG_PROGRESS_PARK_SELECTOR = _UxGT("Parking selector");
|
||||
LSTR MSG_PROGRESS_EJECT_FILAMENT = _UxGT("Ejecting filament");
|
||||
LSTR MSG_PROGRESSPSTRETRACT_FINDA = _UxGT("Retract from FINDA");
|
||||
LSTR MSG_PROGRESS_HOMING = _UxGT("Homing");
|
||||
LSTR MSG_PROGRESS_MOVING_SELECTOR = _UxGT("Moving selector");
|
||||
LSTR MSG_PROGRESS_FEED_FSENSOR = _UxGT("Feeding to FSensor");
|
||||
}
|
||||
|
||||
namespace LanguageWide_en {
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
#define MACHINE_CAN_PAUSE 1
|
||||
#endif
|
||||
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
#include "menu_mmu2.h"
|
||||
#endif
|
||||
|
||||
|
@ -355,8 +355,10 @@ void menu_main() {
|
|||
SUBMENU(MSG_MIXER, menu_mixer);
|
||||
#endif
|
||||
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
if (!busy) SUBMENU(MSG_MMU2_MENU, menu_mmu2);
|
||||
#if ENABLED(MMU_MENUS)
|
||||
// MMU3 can show print stats which can be useful during
|
||||
// the print, so MMU menus are required for MMU3.
|
||||
if (TERN1(HAS_PRUSA_MMU2, !busy)) SUBMENU(MSG_MMU2_MENU, menu_mmu2);
|
||||
#endif
|
||||
|
||||
SUBMENU(MSG_CONFIGURATION, menu_configuration);
|
||||
|
|
|
@ -22,10 +22,18 @@
|
|||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
#if ALL(HAS_MARLINUI_MENU, MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "../../feature/mmu3/mmu2.h"
|
||||
#include "../../feature/mmu3/mmu2_reporting.h"
|
||||
#include "../../feature/mmu3/SpoolJoin.h"
|
||||
#else
|
||||
#include "../../feature/mmu/mmu2.h"
|
||||
#endif
|
||||
|
||||
#include "menu_mmu2.h"
|
||||
#include "menu_item.h"
|
||||
|
||||
|
@ -34,21 +42,23 @@
|
|||
//
|
||||
|
||||
inline void action_mmu2_load_to_nozzle(const uint8_t tool) {
|
||||
ui.reset_status();
|
||||
ui.return_to_status();
|
||||
ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(tool + 1));
|
||||
if (mmu2.load_to_nozzle(tool)) ui.reset_status();
|
||||
TERN(HAS_PRUSA_MMU3, mmu3.load_to_nozzle(tool), mmu2.load_to_nozzle(tool));
|
||||
ui.reset_status();
|
||||
}
|
||||
|
||||
void _mmu2_load_to_feeder(const uint8_t index) {
|
||||
void _mmu2_load_to_feeder(const uint8_t tool) {
|
||||
ui.reset_status();
|
||||
ui.return_to_status();
|
||||
ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(index + 1));
|
||||
mmu2.load_to_feeder(index);
|
||||
ui.status_printf(0, GET_TEXT_F(MSG_MMU2_LOADING_FILAMENT), int(tool + 1));
|
||||
TERN(HAS_PRUSA_MMU3, mmu3.load_to_feeder(tool), mmu2.load_to_feeder(tool));
|
||||
ui.reset_status();
|
||||
}
|
||||
|
||||
void action_mmu2_load_all() {
|
||||
EXTRUDER_LOOP() _mmu2_load_to_feeder(e);
|
||||
ui.return_to_status();
|
||||
}
|
||||
|
||||
void menu_mmu2_load_filament() {
|
||||
|
@ -74,15 +84,26 @@ void _mmu2_eject_filament(uint8_t index) {
|
|||
ui.reset_status();
|
||||
ui.return_to_status();
|
||||
ui.status_printf(0, GET_TEXT_F(MSG_MMU2_EJECTING_FILAMENT), int(index + 1));
|
||||
if (mmu2.eject_filament(index, true)) ui.reset_status();
|
||||
if (mmu3.eject_filament(index, true)) ui.reset_status();
|
||||
}
|
||||
|
||||
void _mmu2_cut_filament(uint8_t index) {
|
||||
ui.reset_status();
|
||||
ui.return_to_status();
|
||||
ui.status_printf(0, GET_TEXT_F(MSG_MMU2_CUTTING_FILAMENT), int(index + 1));
|
||||
if (TERN0(HAS_PRUSA_MMU3, mmu3.cut_filament(index, true)))
|
||||
ui.reset_status();
|
||||
}
|
||||
|
||||
void action_mmu2_unload_filament() {
|
||||
ui.reset_status();
|
||||
ui.return_to_status();
|
||||
LCD_MESSAGE(MSG_MMU2_UNLOADING_FILAMENT);
|
||||
idle();
|
||||
if (mmu2.unload()) ui.reset_status();
|
||||
while (!TERN(HAS_PRUSA_MMU3, mmu3.unload(), mmu2.unload())) {
|
||||
safe_delay(50);
|
||||
TERN(HAS_PRUSA_MMU3, MMU3::marlin_idle(true), idle());
|
||||
}
|
||||
ui.reset_status();
|
||||
}
|
||||
|
||||
void menu_mmu2_eject_filament() {
|
||||
|
@ -92,23 +113,214 @@ void menu_mmu2_eject_filament() {
|
|||
END_MENU();
|
||||
}
|
||||
|
||||
// Cutter
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
void menu_mmu3_cutter_set_mode(uint8_t mode) { mmu3.cutter_mode = mode; }
|
||||
void menu_mmu3_cutter_disable() { menu_mmu3_cutter_set_mode(0); }
|
||||
void menu_mmu3_cutter_enable() { menu_mmu3_cutter_set_mode(1); }
|
||||
void menu_mmu3_cutter_always() { menu_mmu3_cutter_set_mode(2); }
|
||||
|
||||
void menu_mmu3_cutter() {
|
||||
START_MENU();
|
||||
BACK_ITEM(MSG_MMU2_MENU);
|
||||
ACTION_ITEM(MSG_MMU_CUTTER_MODE_DISABLE, menu_mmu3_cutter_disable);
|
||||
ACTION_ITEM(MSG_MMU_CUTTER_MODE_ENABLE, menu_mmu3_cutter_enable);
|
||||
ACTION_ITEM(MSG_MMU_CUTTER_MODE_ALWAYS, menu_mmu3_cutter_always);
|
||||
END_MENU();
|
||||
}
|
||||
|
||||
void menu_mmu3_cut_filament() {
|
||||
START_MENU();
|
||||
BACK_ITEM(MSG_MMU2_MENU);
|
||||
EXTRUDER_LOOP() ACTION_ITEM_N(e, MSG_MMU2_FILAMENT_N, []{ _mmu2_cut_filament(MenuItemBase::itemIndex); });
|
||||
END_MENU();
|
||||
}
|
||||
|
||||
// SpoolJoin
|
||||
void spool_join_status() { spooljoin.initStatus(); }
|
||||
|
||||
// Fail Stats Menu
|
||||
void menu_mmu3_fail_stats_last_print() {
|
||||
if (ui.use_click()) return ui.go_back();
|
||||
char buffer1[LCD_WIDTH], buffer2[LCD_WIDTH];
|
||||
|
||||
// had to cast the uint8_t values to uint16_t before formatting them.
|
||||
const uint16_t fail_num = MMU3::operation_statistics.fail_num;
|
||||
const uint16_t load_fail_num = MMU3::operation_statistics.load_fail_num;
|
||||
|
||||
sprintf_P(buffer1, PSTR("%hu"), fail_num);
|
||||
sprintf_P(buffer2, PSTR("%hu"), load_fail_num);
|
||||
|
||||
START_SCREEN();
|
||||
STATIC_ITEM(
|
||||
TERN(printJobOngoing(), MSG_MMU_CURRENT_PRINT_FAILURES, MSG_MMU_LAST_PRINT_FAILURES),
|
||||
SS_INVERT
|
||||
);
|
||||
#ifndef __AVR__
|
||||
// TODO: I couldn't make this work on AVR
|
||||
PSTRING_ITEM(MSG_MMU_FAILS, buffer1, SS_FULL);
|
||||
PSTRING_ITEM(MSG_MMU_LOAD_FAILS, buffer2, SS_FULL);
|
||||
#endif
|
||||
END_SCREEN();
|
||||
}
|
||||
|
||||
void menu_mmu3_fail_stas_total() {
|
||||
if (ui.use_click()) return ui.go_back();
|
||||
char buffer1[LCD_WIDTH], buffer2[LCD_WIDTH], buffer3[LCD_WIDTH];
|
||||
|
||||
sprintf_P(buffer1, PSTR("%hu"), MMU3::operation_statistics.fail_total_num);
|
||||
sprintf_P(buffer2, PSTR("%hu"), MMU3::operation_statistics.load_fail_total_num);
|
||||
sprintf_P(buffer3, PSTR("%hu"), mmu3.tmcFailures());
|
||||
|
||||
START_SCREEN();
|
||||
STATIC_ITEM(MSG_MMU_TOTAL_FAILURES, SS_INVERT);
|
||||
#ifndef __AVR__
|
||||
// TODO: I couldn't make this work on AVR
|
||||
PSTRING_ITEM(MSG_MMU_FAILS, buffer1, SS_FULL);
|
||||
PSTRING_ITEM(MSG_MMU_LOAD_FAILS, buffer2, SS_FULL);
|
||||
PSTRING_ITEM(MSG_MMU_POWER_FAILS, buffer3, SS_FULL);
|
||||
#endif
|
||||
END_SCREEN();
|
||||
}
|
||||
|
||||
#if ENABLED(MARLIN_DEV_MODE)
|
||||
void menu_mmu3_dev_increment_fail_stat() {
|
||||
MMU3::operation_statistics.increment_mmu_fails();
|
||||
}
|
||||
|
||||
void menu_mmu3_dev_increment_load_fail_stat() {
|
||||
MMU3::operation_statistics.increment_load_fails();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void mmu3_reset_fail_stats() {
|
||||
bool result = MMU3::operation_statistics.reset_fail_stats();
|
||||
ui.go_back();
|
||||
MarlinUI::completion_feedback(result);
|
||||
}
|
||||
|
||||
static void mmu3_reset_stats() {
|
||||
bool result = MMU3::operation_statistics.reset_stats();
|
||||
ui.go_back();
|
||||
MarlinUI::completion_feedback(result);
|
||||
}
|
||||
|
||||
void menu_mmu3_toolchange_stat_total() {
|
||||
if (ui.use_click()) return ui.go_back();
|
||||
char buffer1[LCD_WIDTH];
|
||||
sprintf_P(buffer1, PSTR("%u"), MMU3::operation_statistics.tool_change_counter);
|
||||
|
||||
char buffer2[LCD_WIDTH];
|
||||
sprintf_P(buffer2, PSTR("%lu"), MMU3::operation_statistics.tool_change_total_counter);
|
||||
|
||||
START_SCREEN();
|
||||
STATIC_ITEM(MSG_MMU_MATERIAL_CHANGES, SS_INVERT);
|
||||
#ifndef __AVR__
|
||||
// TODO: I couldn't make this work on AVR
|
||||
if (printJobOngoing())
|
||||
PSTRING_ITEM(MSG_MMU_CURRENT_PRINT, buffer1, SS_FULL);
|
||||
else
|
||||
PSTRING_ITEM(MSG_MMU_LAST_PRINT, buffer1, SS_FULL);
|
||||
PSTRING_ITEM(MSG_MMU_TOTAL, buffer2, SS_FULL);
|
||||
#endif
|
||||
END_SCREEN();
|
||||
}
|
||||
|
||||
void menu_mmu3_statistics() {
|
||||
START_MENU();
|
||||
BACK_ITEM(MSG_MMU2_MENU);
|
||||
#if ENABLED(MARLIN_DEV_MODE)
|
||||
ACTION_ITEM(MSG_MMU_DEV_INCREMENT_FAILS, menu_mmu3_dev_increment_fail_stat);
|
||||
ACTION_ITEM(MSG_MMU_DEV_INCREMENT_LOAD_FAILS, menu_mmu3_dev_increment_load_fail_stat);
|
||||
#endif
|
||||
|
||||
SUBMENU(
|
||||
TERN(printJobOngoing(), MSG_MMU_CURRENT_PRINT_FAILURES, MSG_MMU_LAST_PRINT_FAILURES),
|
||||
menu_mmu3_fail_stats_last_print
|
||||
);
|
||||
SUBMENU(MSG_MMU_TOTAL_FAILURES, menu_mmu3_fail_stas_total);
|
||||
SUBMENU(MSG_MMU_MATERIAL_CHANGES, menu_mmu3_toolchange_stat_total);
|
||||
CONFIRM_ITEM(MSG_MMU_RESET_FAIL_STATS,
|
||||
MSG_BUTTON_RESET, MSG_BUTTON_CANCEL,
|
||||
mmu3_reset_fail_stats, nullptr,
|
||||
GET_TEXT_F(MSG_MMU_RESET_FAIL_STATS), (const char *)nullptr, F("?")
|
||||
);
|
||||
CONFIRM_ITEM(MSG_MMU_RESET_STATS,
|
||||
MSG_BUTTON_RESET, MSG_BUTTON_CANCEL,
|
||||
mmu3_reset_stats, nullptr,
|
||||
GET_TEXT_F(MSG_MMU_RESET_STATS), (const char *)nullptr, F("?")
|
||||
);
|
||||
END_MENU();
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
|
||||
//
|
||||
// MMU2 Menu
|
||||
//
|
||||
|
||||
void action_mmu2_reset() {
|
||||
#if HAS_PRUSA_MMU3
|
||||
#if PIN_EXISTS(MMU2_RST)
|
||||
mmu3.reset(MMU3::MMU3::ResetForm::ResetPin);
|
||||
#else
|
||||
mmu3.reset(MMU3::MMU3::ResetForm::Software);
|
||||
#endif
|
||||
#else
|
||||
mmu2.init();
|
||||
#endif
|
||||
ui.reset_status();
|
||||
}
|
||||
|
||||
void menu_mmu2() {
|
||||
const bool busy = printJobOngoing(); // printingIsActive();
|
||||
|
||||
START_MENU();
|
||||
BACK_ITEM(MSG_MAIN_MENU);
|
||||
|
||||
// MMU2/MMU3 Commands
|
||||
if (!busy && TERN1(HAS_PRUSA_MMU3, mmu3.mmu_hw_enabled)) {
|
||||
SUBMENU(MSG_MMU2_LOAD_FILAMENT, menu_mmu2_load_filament);
|
||||
SUBMENU(MSG_MMU2_LOAD_TO_NOZZLE, menu_mmu2_load_to_nozzle);
|
||||
SUBMENU(MSG_MMU2_EJECT_FILAMENT, menu_mmu2_eject_filament);
|
||||
ACTION_ITEM(MSG_MMU2_UNLOAD_FILAMENT, action_mmu2_unload_filament);
|
||||
}
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
// MMU3 Enable/Disable
|
||||
#ifndef __AVR__
|
||||
editable.state = mmu3.mmu_hw_enabled;
|
||||
EDIT_ITEM_F(bool, F("MMU"), &mmu3.mmu_hw_enabled, []{
|
||||
if (editable.state)
|
||||
mmu3.stop();
|
||||
else
|
||||
mmu3.start();
|
||||
});
|
||||
#endif
|
||||
|
||||
// SpoolJoin Enable/Disable
|
||||
EDIT_ITEM(bool, MSG_MMU_SPOOL_JOIN, &spooljoin.enabled, spool_join_status);
|
||||
|
||||
// Cutter Enable/Disable
|
||||
bool cutter_enabled = mmu3.cutter_mode != 0;
|
||||
editable.state = cutter_enabled;
|
||||
EDIT_ITEM(bool, MSG_MMU_CUTTER, &cutter_enabled, []{
|
||||
menu_mmu3_cutter_set_mode((uint8_t)!editable.state);
|
||||
});
|
||||
if (!busy && MMU3::cutter_enabled() && mmu3.mmu_hw_enabled) {
|
||||
SUBMENU(MSG_MMU2_CUT_FILAMENT, menu_mmu3_cut_filament);
|
||||
}
|
||||
|
||||
// Statistics
|
||||
SUBMENU(MSG_MMU_STATISTICS, menu_mmu3_statistics);
|
||||
#endif
|
||||
|
||||
if (TERN1(HAS_PRUSA_MMU3, mmu3.mmu_hw_enabled)) {
|
||||
ACTION_ITEM(MSG_MMU2_RESET, action_mmu2_reset);
|
||||
}
|
||||
|
||||
END_MENU();
|
||||
}
|
||||
|
||||
|
@ -138,19 +350,36 @@ void menu_mmu2_choose_filament() {
|
|||
//
|
||||
|
||||
void menu_mmu2_pause() {
|
||||
feeder_index = mmu2.get_current_tool();
|
||||
feeder_index = mmu3.get_current_tool();
|
||||
START_MENU();
|
||||
#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; });
|
||||
#if HAS_PRUSA_MMU3
|
||||
ACTION_ITEM(MSG_MMU2_UNLOAD_FILAMENT, []{ mmu3.unload(); });
|
||||
ACTION_ITEM(MSG_MMU2_LOAD_FILAMENT, []{ mmu3.load_to_feeder(feeder_index); });
|
||||
ACTION_ITEM(MSG_MMU2_LOAD_TO_NOZZLE, []{ mmu3.load_to_nozzle(feeder_index); });
|
||||
#else
|
||||
ACTION_ITEM(MSG_MMU2_UNLOAD_FILAMENT, []{ mmu2.unload(); });
|
||||
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); });
|
||||
#endif
|
||||
END_MENU();
|
||||
}
|
||||
|
||||
void mmu2_M600() {
|
||||
void mmu2_M600(const bool automatic/*=false*/) {
|
||||
// Disable automatic switching if MMU3 is not enabled or spool join is disabled
|
||||
#if HAS_PRUSA_MMU3
|
||||
if (automatic && spooljoin.enabled) {
|
||||
uint8_t slot;
|
||||
slot = spooljoin.nextSlot();
|
||||
mmu3.load_to_nozzle(slot);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
UNUSED(automatic);
|
||||
#endif
|
||||
ui.defer_status_screen();
|
||||
ui.goto_screen(menu_mmu2_pause);
|
||||
wait_for_mmu_menu = true;
|
||||
|
@ -166,4 +395,4 @@ uint8_t mmu2_choose_filament() {
|
|||
return feeder_index;
|
||||
}
|
||||
|
||||
#endif // HAS_MARLINUI_MENU && MMU2_MENUS
|
||||
#endif // MMU_MENUS
|
||||
|
|
|
@ -24,5 +24,5 @@
|
|||
#include <stdint.h>
|
||||
|
||||
void menu_mmu2();
|
||||
void mmu2_M600();
|
||||
void mmu2_M600(const bool automatic=false);
|
||||
uint8_t mmu2_choose_filament();
|
||||
|
|
|
@ -178,6 +178,12 @@
|
|||
#include "../feature/hotend_idle.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "../feature/mmu3/mmu2.h"
|
||||
#include "../feature/mmu3/SpoolJoin.h"
|
||||
#include "../feature/mmu3/mmu2_reporting.h"
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 1) // No padding between variables
|
||||
|
||||
#if HAS_ETHERNET
|
||||
|
@ -653,6 +659,23 @@ typedef struct SettingsDataStruct {
|
|||
ne_coeff_t stepper_ne; // M592 A B C
|
||||
#endif
|
||||
|
||||
//
|
||||
// MMU3
|
||||
//
|
||||
#if HAS_PRUSA_MMU3
|
||||
bool spool_join_enabled; // EEPROM_SPOOL_JOIN
|
||||
uint16_t fail_total_num; // EEPROM_MMU_FAIL_TOT
|
||||
uint8_t fail_num; // EEPROM_MMU_FAIL
|
||||
uint16_t load_fail_total_num; // EEPROM_MMU_LOAD_FAIL_TOT
|
||||
uint8_t load_fail_num; // EEPROM_MMU_LOAD_FAIL
|
||||
uint16_t tool_change_counter;
|
||||
uint32_t tool_change_total_counter; // EEPROM_MMU_MATERIAL_CHANGES
|
||||
uint8_t cutter_mode; // EEPROM_MMU_CUTTER_ENABLED
|
||||
uint8_t stealth_mode; // EEPROM_MMU_STEALTH
|
||||
bool mmu_hw_enabled; // EEPROM_MMU_ENABLED
|
||||
// uint32_t material_changes
|
||||
#endif
|
||||
|
||||
} SettingsData;
|
||||
|
||||
//static_assert(sizeof(SettingsData) <= MARLIN_EEPROM_SIZE, "EEPROM too small to contain SettingsData!");
|
||||
|
@ -1760,6 +1783,23 @@ void MarlinSettings::postprocess() {
|
|||
EEPROM_WRITE(stepper.ne);
|
||||
#endif
|
||||
|
||||
//
|
||||
// MMU3
|
||||
//
|
||||
#if HAS_PRUSA_MMU3
|
||||
EEPROM_WRITE(spooljoin.enabled); // EEPROM_SPOOL_JOIN
|
||||
// for testing purposes fill with default values
|
||||
EEPROM_WRITE(MMU3::operation_statistics.fail_total_num); //EEPROM_MMU_FAIL_TOT
|
||||
EEPROM_WRITE(MMU3::operation_statistics.fail_num); // EEPROM_MMU_FAIL
|
||||
EEPROM_WRITE(MMU3::operation_statistics.load_fail_total_num); // EEPROM_MMU_LOAD_FAIL_TOT
|
||||
EEPROM_WRITE(MMU3::operation_statistics.load_fail_num); // EEPROM_MMU_LOAD_FAIL
|
||||
EEPROM_WRITE(MMU3::operation_statistics.tool_change_counter);
|
||||
EEPROM_WRITE(MMU3::operation_statistics.tool_change_total_counter); // EEPROM_MMU_MATERIAL_CHANGES
|
||||
EEPROM_WRITE(mmu3.cutter_mode); // EEPROM_MMU_CUTTER_ENABLED
|
||||
EEPROM_WRITE(mmu3.stealth_mode); // EEPROM_MMU_STEALTH
|
||||
EEPROM_WRITE(mmu3.mmu_hw_enabled); // EEPROM_MMU_ENABLED
|
||||
#endif
|
||||
|
||||
//
|
||||
// Report final CRC and Data Size
|
||||
//
|
||||
|
@ -2881,6 +2921,41 @@ void MarlinSettings::postprocess() {
|
|||
EEPROM_READ(stepper.ne);
|
||||
#endif
|
||||
|
||||
//
|
||||
// MMU3
|
||||
//
|
||||
#if HAS_PRUSA_MMU3
|
||||
spooljoin.epprom_addr = eeprom_index;
|
||||
EEPROM_READ(spooljoin.enabled); // EEPROM_SPOOL_JOIN
|
||||
|
||||
MMU3::operation_statistics.fail_total_num_addr = eeprom_index;
|
||||
EEPROM_READ(MMU3::operation_statistics.fail_total_num); //EEPROM_MMU_FAIL_TOT
|
||||
|
||||
MMU3::operation_statistics.fail_num_addr = eeprom_index;
|
||||
EEPROM_READ(MMU3::operation_statistics.fail_num); // EEPROM_MMU_FAIL;
|
||||
|
||||
MMU3::operation_statistics.load_fail_total_num_addr = eeprom_index;
|
||||
EEPROM_READ(MMU3::operation_statistics.load_fail_total_num); // EEPROM_MMU_LOAD_FAIL_TOT
|
||||
|
||||
MMU3::operation_statistics.load_fail_num_addr = eeprom_index;
|
||||
EEPROM_READ(MMU3::operation_statistics.load_fail_num); // EEPROM_MMU_LOAD_FAIL
|
||||
|
||||
MMU3::operation_statistics.tool_change_counter_addr = eeprom_index;
|
||||
EEPROM_READ(MMU3::operation_statistics.tool_change_counter);
|
||||
|
||||
MMU3::operation_statistics.tool_change_total_counter_addr = eeprom_index;
|
||||
EEPROM_READ(MMU3::operation_statistics.tool_change_total_counter); // EEPROM_MMU_MATERIAL_CHANGES
|
||||
|
||||
mmu3.cutter_mode_addr = eeprom_index;
|
||||
EEPROM_READ(mmu3.cutter_mode); // EEPROM_MMU_CUTTER_ENABLED
|
||||
|
||||
mmu3.stealth_mode_addr = eeprom_index;
|
||||
EEPROM_READ(mmu3.stealth_mode); // EEPROM_MMU_STEALTH
|
||||
|
||||
mmu3.mmu_hw_enabled_addr = eeprom_index;
|
||||
EEPROM_READ(mmu3.mmu_hw_enabled); // EEPROM_MMU_ENABLED
|
||||
#endif
|
||||
|
||||
//
|
||||
// Validate Final Size and CRC
|
||||
//
|
||||
|
@ -3743,6 +3818,17 @@ void MarlinSettings::reset() {
|
|||
#endif
|
||||
#endif
|
||||
|
||||
//
|
||||
// MMU Settings
|
||||
//
|
||||
#if HAS_PRUSA_MMU3
|
||||
spooljoin.enabled = false;
|
||||
MMU3::operation_statistics.reset_stats();
|
||||
mmu3.cutter_mode = 0;
|
||||
mmu3.stealth_mode = 0;
|
||||
mmu3.mmu_hw_enabled = true;
|
||||
#endif
|
||||
|
||||
//
|
||||
// Hotend Idle Timeout
|
||||
//
|
||||
|
@ -4062,6 +4148,11 @@ void MarlinSettings::reset() {
|
|||
// Model predictive control
|
||||
//
|
||||
TERN_(MPCTEMP, gcode.M306_report(forReplay));
|
||||
|
||||
//
|
||||
// MMU3
|
||||
//
|
||||
TERN_(HAS_PRUSA_MMU3, gcode.MMU3_report(forReplay));
|
||||
}
|
||||
|
||||
#endif // !DISABLE_M503
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
#define E_STATES EXTRUDERS // All steppers are set together for each mixer. (Currently limited to 1.)
|
||||
#elif HAS_SWITCHING_EXTRUDER
|
||||
#define E_STATES E_STEPPERS // One stepper for every two EXTRUDERS. The last extruder can be non-switching.
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#elif HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
#define E_STATES E_STEPPERS // One E stepper shared with all EXTRUDERS, so setting any only sets one.
|
||||
#else
|
||||
#define E_STATES E_STEPPERS // One stepper for each extruder, so each can be disabled individually.
|
||||
|
|
|
@ -571,7 +571,7 @@ void reset_stepper_drivers(); // Called by settings.load / settings.reset
|
|||
|
||||
#define TOOL_ESTEPPER(T) ((T) >> 1)
|
||||
|
||||
#elif HAS_PRUSA_MMU2 // One multiplexed stepper driver
|
||||
#elif HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3 // One multiplexed stepper driver
|
||||
|
||||
#define E_STEP_WRITE(E,V) E0_STEP_WRITE(V)
|
||||
#define FWD_E_DIR(E) E0_DIR_WRITE(HIGH)
|
||||
|
|
|
@ -76,10 +76,12 @@
|
|||
#include "../feature/fanmux.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU1
|
||||
#include "../feature/mmu/mmu.h"
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "../feature/mmu3/mmu2.h"
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#include "../feature/mmu/mmu2.h"
|
||||
#elif HAS_PRUSA_MMU1
|
||||
#include "../feature/mmu/mmu.h"
|
||||
#endif
|
||||
|
||||
#if HAS_MARLINUI_MENU
|
||||
|
@ -1122,6 +1124,12 @@ void tool_change(const uint8_t new_tool, bool no_move/*=false*/) {
|
|||
mixer.T(new_tool);
|
||||
#endif
|
||||
|
||||
#elif HAS_PRUSA_MMU3
|
||||
|
||||
UNUSED(no_move);
|
||||
|
||||
mmu3.tool_change(new_tool);
|
||||
|
||||
#elif HAS_PRUSA_MMU2
|
||||
|
||||
UNUSED(no_move);
|
||||
|
|
|
@ -86,8 +86,8 @@
|
|||
#if HAS_TEMPERATURE
|
||||
#define HAS_MENU_TEMPERATURE
|
||||
#endif
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#define HAS_MENU_MMU2
|
||||
#if ENABLED(MMU_MENUS)
|
||||
#define HAS_MENU_MMU
|
||||
#endif
|
||||
#if ENABLED(PASSWORD_FEATURE)
|
||||
#define HAS_MENU_PASSWORD
|
||||
|
|
|
@ -59,5 +59,40 @@ opt_set MOTHERBOARD BOARD_BTT_SKR_V1_3 EXTRUDERS 2 \
|
|||
opt_enable PIDTEMPBED PIDTEMPCHAMBER PID_EXTRUSION_SCALING PID_FAN_SCALING
|
||||
exec_test $1 $2 "SKR v1.3 with 2*Extr, bed, chamber all PID." "$3"
|
||||
|
||||
#
|
||||
# SKR 1.4 with MMU2
|
||||
#
|
||||
restore_configs
|
||||
opt_set MOTHERBOARD BOARD_BTT_SKR_V1_4 SERIAL_PORT -1 \
|
||||
BAUDRATE 115200 X_DRIVER_TYPE TMC2209 Y_DRIVER_TYPE TMC2209 \
|
||||
Z_DRIVER_TYPE TMC2209 Z2_DRIVER_TYPE TMC2209 E0_DRIVER_TYPE TMC2209 \
|
||||
EXTRUDERS 5 MMU_MODEL PRUSA_MMU2 HEATER_0_MAXTEMP 305 \
|
||||
BED_MAXTEMP 125 HOTEND_OVERSHOOT 5 INVERT_X_DIR true \
|
||||
INVERT_E0_DIR true X_BED_SIZE 235 Y_BED_SIZE 225 Z_MAX_POS 240 \
|
||||
GRID_MAX_POINTS_X 5 E0_AUTO_FAN_PIN FAN1_PIN \
|
||||
BLTOUCH_HS_MODE true BLTOUCH_HS_EXTRA_CLEARANCE 0 \
|
||||
Z_STEPPER_ALIGN_XY '{{10,110},{200,110}}' \
|
||||
Z_STEPPER_ALIGN_ITERATIONS 10 DEFAULT_STEPPER_TIMEOUT_SEC 0 \
|
||||
SLOWDOWN_DIVISOR 16 SDCARD_CONNECTION ONBOARD BLOCK_BUFFER_SIZE 64 \
|
||||
CHOPPER_TIMING CHOPPER_DEFAULT_24V MMU2_SERIAL_PORT 0
|
||||
opt_enable PIDTEMPBED S_CURVE_ACCELERATION \
|
||||
USE_PROBE_FOR_Z_HOMING BLTOUCH FILAMENT_RUNOUT_SENSOR \
|
||||
AUTO_BED_LEVELING_BILINEAR RESTORE_LEVELING_AFTER_G28 \
|
||||
EXTRAPOLATE_BEYOND_GRID LCD_BED_LEVELING MESH_EDIT_MENU Z_SAFE_HOMING \
|
||||
EEPROM_SETTINGS EEPROM_AUTO_INIT NOZZLE_PARK_FEATURE SDSUPPORT \
|
||||
SPEAKER CR10_STOCKDISPLAY QUICK_HOME BLTOUCH_FORCE_SW_MODE \
|
||||
Z_STEPPER_AUTO_ALIGN INPUT_SHAPING_X INPUT_SHAPING_Y SHAPING_MENU \
|
||||
ADAPTIVE_STEP_SMOOTHING LCD_INFO_MENU STATUS_MESSAGE_SCROLLING \
|
||||
SET_PROGRESS_MANUALLY M73_REPORT SHOW_REMAINING_TIME \
|
||||
PRINT_PROGRESS_SHOW_DECIMALS AUTO_REPORT_SD_STATUS USE_BIG_EDIT_FONT \
|
||||
BABYSTEPPING BABYSTEP_WITHOUT_HOMING BABYSTEP_ALWAYS_AVAILABLE \
|
||||
DOUBLECLICK_FOR_Z_BABYSTEPPING BABYSTEP_DISPLAY_TOTAL LIN_ADVANCE \
|
||||
BEZIER_CURVE_SUPPORT EMERGENCY_PARSER ADVANCED_PAUSE_FEATURE \
|
||||
TMC_DEBUG HOST_ACTION_COMMANDS HOST_PAUSE_M76 HOST_PROMPT_SUPPORT \
|
||||
HOST_STATUS_NOTIFICATIONS MMU2_DEBUG
|
||||
opt_disable Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN FILAMENT_LOAD_UNLOAD_GCODES \
|
||||
PARK_HEAD_ON_PAUSE
|
||||
exec_test $1 $2 "BigTreeTech SKR 1.4 | MMU2" "$3"
|
||||
|
||||
# clean up
|
||||
restore_configs
|
||||
|
|
|
@ -64,5 +64,40 @@ opt_enable EEPROM_SETTINGS EEPROM_CHITCHAT MECHANICAL_GANTRY_CALIBRATION \
|
|||
opt_disable PSU_CONTROL Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN
|
||||
exec_test $1 $2 "Cohesion3D Remix DELTA | ABL Bilinear | EEPROM | Sensorless Homing/Probing | I Axis" "$3"
|
||||
|
||||
#
|
||||
# SKR 1.4 Turbo with MMU3
|
||||
#
|
||||
restore_configs
|
||||
opt_set MOTHERBOARD BOARD_BTT_SKR_V1_4_TURBO SERIAL_PORT -1 \
|
||||
BAUDRATE 115200 X_DRIVER_TYPE TMC2209 Y_DRIVER_TYPE TMC2209 \
|
||||
Z_DRIVER_TYPE TMC2209 Z2_DRIVER_TYPE TMC2209 E0_DRIVER_TYPE TMC2209 \
|
||||
EXTRUDERS 5 MMU_MODEL PRUSA_MMU3 HEATER_0_MAXTEMP 305 \
|
||||
BED_MAXTEMP 125 HOTEND_OVERSHOOT 5 INVERT_X_DIR true \
|
||||
INVERT_E0_DIR true X_BED_SIZE 235 Y_BED_SIZE 225 Z_MAX_POS 240 \
|
||||
GRID_MAX_POINTS_X 5 E0_AUTO_FAN_PIN FAN1_PIN \
|
||||
BLTOUCH_HS_MODE true BLTOUCH_HS_EXTRA_CLEARANCE 0 \
|
||||
Z_STEPPER_ALIGN_XY '{{10,110},{200,110}}' \
|
||||
Z_STEPPER_ALIGN_ITERATIONS 10 DEFAULT_STEPPER_TIMEOUT_SEC 0 \
|
||||
SLOWDOWN_DIVISOR 16 SDCARD_CONNECTION ONBOARD BLOCK_BUFFER_SIZE 64 \
|
||||
CHOPPER_TIMING CHOPPER_DEFAULT_24V MMU2_SERIAL_PORT 0 \
|
||||
Z_MIN_ENDSTOP_HIT_STATE HIGH
|
||||
opt_enable PIDTEMPBED S_CURVE_ACCELERATION \
|
||||
USE_PROBE_FOR_Z_HOMING BLTOUCH FILAMENT_RUNOUT_SENSOR \
|
||||
AUTO_BED_LEVELING_BILINEAR RESTORE_LEVELING_AFTER_G28 \
|
||||
EXTRAPOLATE_BEYOND_GRID LCD_BED_LEVELING MESH_EDIT_MENU Z_SAFE_HOMING \
|
||||
EEPROM_SETTINGS EEPROM_AUTO_INIT NOZZLE_PARK_FEATURE SDSUPPORT \
|
||||
SPEAKER CR10_STOCKDISPLAY QUICK_HOME BLTOUCH_FORCE_SW_MODE \
|
||||
Z_STEPPER_AUTO_ALIGN INPUT_SHAPING_X INPUT_SHAPING_Y SHAPING_MENU \
|
||||
ADAPTIVE_STEP_SMOOTHING LCD_INFO_MENU STATUS_MESSAGE_SCROLLING \
|
||||
SET_PROGRESS_MANUALLY M73_REPORT SHOW_REMAINING_TIME \
|
||||
PRINT_PROGRESS_SHOW_DECIMALS AUTO_REPORT_SD_STATUS USE_BIG_EDIT_FONT \
|
||||
BABYSTEPPING BABYSTEP_WITHOUT_HOMING BABYSTEP_ALWAYS_AVAILABLE \
|
||||
DOUBLECLICK_FOR_Z_BABYSTEPPING BABYSTEP_DISPLAY_TOTAL LIN_ADVANCE \
|
||||
BEZIER_CURVE_SUPPORT EMERGENCY_PARSER ADVANCED_PAUSE_FEATURE \
|
||||
TMC_DEBUG HOST_ACTION_COMMANDS HOST_PAUSE_M76 HOST_PROMPT_SUPPORT HOST_STATUS_NOTIFICATIONS \
|
||||
MMU_SPOOL_JOIN_CONSUMES_ALL_FILAMENT MMU_MENUS MMU2_DEBUG
|
||||
opt_disable Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN FILAMENT_LOAD_UNLOAD_GCODES PARK_HEAD_ON_PAUSE
|
||||
exec_test $1 $2 "BigTreeTech SKR 1.4 Turbo | MMU3" "$3"
|
||||
|
||||
# clean up
|
||||
restore_configs
|
||||
|
|
|
@ -10,8 +10,85 @@ set -e
|
|||
#
|
||||
# Build with the default configurations
|
||||
#
|
||||
restore_configs
|
||||
use_example_configs "Creality/Ender-5 Plus/BigTreeTech SKR 3"
|
||||
exec_test $1 $2 "Creality Ender-5 Plus with BigTreeTech SKR 3" "$3"
|
||||
|
||||
|
||||
#
|
||||
# SKR 3 EZ default
|
||||
#
|
||||
restore_configs
|
||||
opt_set MOTHERBOARD BOARD_BTT_SKR_V3_0_EZ SERIAL_PORT -1
|
||||
exec_test $1 $2 "BigTreeTech SKR 3 EZ | Default Configuration" "$3"
|
||||
|
||||
#
|
||||
# SKR 3 EZ with MMU2
|
||||
#
|
||||
restore_configs
|
||||
opt_set MOTHERBOARD BOARD_BTT_SKR_V3_0_EZ SERIAL_PORT -1 \
|
||||
BAUDRATE 115200 X_DRIVER_TYPE TMC2209 Y_DRIVER_TYPE TMC2209 \
|
||||
Z_DRIVER_TYPE TMC2209 Z2_DRIVER_TYPE TMC2209 E0_DRIVER_TYPE TMC2209 \
|
||||
EXTRUDERS 5 MMU_MODEL PRUSA_MMU2 HEATER_0_MAXTEMP 305 \
|
||||
BED_MAXTEMP 125 HOTEND_OVERSHOOT 5 INVERT_X_DIR true \
|
||||
INVERT_E0_DIR true X_BED_SIZE 235 Y_BED_SIZE 225 Z_MAX_POS 240 \
|
||||
GRID_MAX_POINTS_X 5 E0_AUTO_FAN_PIN FAN1_PIN \
|
||||
BLTOUCH_HS_MODE true BLTOUCH_HS_EXTRA_CLEARANCE 0 \
|
||||
Z_STEPPER_ALIGN_XY '{{10,110},{200,110}}' \
|
||||
Z_STEPPER_ALIGN_ITERATIONS 10 DEFAULT_STEPPER_TIMEOUT_SEC 0 \
|
||||
SLOWDOWN_DIVISOR 16 SDCARD_CONNECTION ONBOARD BLOCK_BUFFER_SIZE 64 \
|
||||
CHOPPER_TIMING CHOPPER_DEFAULT_24V MMU2_SERIAL_PORT 2
|
||||
opt_enable PIDTEMPBED ENDSTOP_INTERRUPTS_FEATURE S_CURVE_ACCELERATION \
|
||||
USE_PROBE_FOR_Z_HOMING BLTOUCH FILAMENT_RUNOUT_SENSOR \
|
||||
AUTO_BED_LEVELING_BILINEAR RESTORE_LEVELING_AFTER_G28 \
|
||||
EXTRAPOLATE_BEYOND_GRID LCD_BED_LEVELING MESH_EDIT_MENU Z_SAFE_HOMING \
|
||||
EEPROM_SETTINGS EEPROM_AUTO_INIT NOZZLE_PARK_FEATURE SDSUPPORT \
|
||||
SPEAKER CR10_STOCKDISPLAY QUICK_HOME BLTOUCH_FORCE_SW_MODE \
|
||||
Z_STEPPER_AUTO_ALIGN INPUT_SHAPING_X INPUT_SHAPING_Y SHAPING_MENU \
|
||||
ADAPTIVE_STEP_SMOOTHING LCD_INFO_MENU STATUS_MESSAGE_SCROLLING \
|
||||
SET_PROGRESS_MANUALLY M73_REPORT SHOW_REMAINING_TIME \
|
||||
PRINT_PROGRESS_SHOW_DECIMALS AUTO_REPORT_SD_STATUS USE_BIG_EDIT_FONT \
|
||||
BABYSTEPPING BABYSTEP_WITHOUT_HOMING BABYSTEP_ALWAYS_AVAILABLE \
|
||||
DOUBLECLICK_FOR_Z_BABYSTEPPING BABYSTEP_DISPLAY_TOTAL LIN_ADVANCE \
|
||||
BEZIER_CURVE_SUPPORT EMERGENCY_PARSER ADVANCED_PAUSE_FEATURE \
|
||||
TMC_DEBUG HOST_ACTION_COMMANDS HOST_PAUSE_M76 HOST_PROMPT_SUPPORT HOST_STATUS_NOTIFICATIONS \
|
||||
MMU2_DEBUG
|
||||
opt_disable Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN FILAMENT_LOAD_UNLOAD_GCODES PARK_HEAD_ON_PAUSE
|
||||
exec_test $1 $2 "BigTreeTech SKR 3 EZ | MMU2" "$3"
|
||||
|
||||
#
|
||||
# SKR 3 EZ with MMU3
|
||||
#
|
||||
restore_configs
|
||||
opt_set MOTHERBOARD BOARD_BTT_SKR_V3_0_EZ SERIAL_PORT -1 \
|
||||
BAUDRATE 115200 X_DRIVER_TYPE TMC2209 Y_DRIVER_TYPE TMC2209 \
|
||||
Z_DRIVER_TYPE TMC2209 Z2_DRIVER_TYPE TMC2209 E0_DRIVER_TYPE TMC2209 \
|
||||
EXTRUDERS 5 MMU_MODEL PRUSA_MMU3 HEATER_0_MAXTEMP 305 \
|
||||
BED_MAXTEMP 125 HOTEND_OVERSHOOT 5 INVERT_X_DIR true \
|
||||
INVERT_E0_DIR true X_BED_SIZE 235 Y_BED_SIZE 225 Z_MAX_POS 240 \
|
||||
GRID_MAX_POINTS_X 5 E0_AUTO_FAN_PIN FAN1_PIN \
|
||||
BLTOUCH_HS_MODE true BLTOUCH_HS_EXTRA_CLEARANCE 0 \
|
||||
Z_STEPPER_ALIGN_XY '{{10,110},{200,110}}' \
|
||||
Z_STEPPER_ALIGN_ITERATIONS 10 DEFAULT_STEPPER_TIMEOUT_SEC 0 \
|
||||
SLOWDOWN_DIVISOR 16 SDCARD_CONNECTION ONBOARD BLOCK_BUFFER_SIZE 64 \
|
||||
CHOPPER_TIMING CHOPPER_DEFAULT_24V MMU2_SERIAL_PORT 2
|
||||
opt_enable PIDTEMPBED ENDSTOP_INTERRUPTS_FEATURE S_CURVE_ACCELERATION \
|
||||
USE_PROBE_FOR_Z_HOMING BLTOUCH FILAMENT_RUNOUT_SENSOR \
|
||||
AUTO_BED_LEVELING_BILINEAR RESTORE_LEVELING_AFTER_G28 \
|
||||
EXTRAPOLATE_BEYOND_GRID LCD_BED_LEVELING MESH_EDIT_MENU Z_SAFE_HOMING \
|
||||
EEPROM_SETTINGS EEPROM_AUTO_INIT NOZZLE_PARK_FEATURE SDSUPPORT \
|
||||
SPEAKER CR10_STOCKDISPLAY QUICK_HOME BLTOUCH_FORCE_SW_MODE \
|
||||
Z_STEPPER_AUTO_ALIGN INPUT_SHAPING_X INPUT_SHAPING_Y SHAPING_MENU \
|
||||
ADAPTIVE_STEP_SMOOTHING LCD_INFO_MENU STATUS_MESSAGE_SCROLLING \
|
||||
SET_PROGRESS_MANUALLY M73_REPORT SHOW_REMAINING_TIME \
|
||||
PRINT_PROGRESS_SHOW_DECIMALS AUTO_REPORT_SD_STATUS USE_BIG_EDIT_FONT \
|
||||
BABYSTEPPING BABYSTEP_WITHOUT_HOMING BABYSTEP_ALWAYS_AVAILABLE \
|
||||
DOUBLECLICK_FOR_Z_BABYSTEPPING BABYSTEP_DISPLAY_TOTAL LIN_ADVANCE \
|
||||
BEZIER_CURVE_SUPPORT EMERGENCY_PARSER ADVANCED_PAUSE_FEATURE \
|
||||
TMC_DEBUG HOST_ACTION_COMMANDS HOST_PAUSE_M76 HOST_PROMPT_SUPPORT HOST_STATUS_NOTIFICATIONS \
|
||||
MMU_MENUS MMU_SPOOL_JOIN_CONSUMES_ALL_FILAMENT MMU2_DEBUG
|
||||
opt_disable Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN FILAMENT_LOAD_UNLOAD_GCODES PARK_HEAD_ON_PAUSE
|
||||
exec_test $1 $2 "BigTreeTech SKR 3 EZ | MMU3" "$3"
|
||||
|
||||
# clean up
|
||||
restore_configs
|
||||
|
|
|
@ -81,6 +81,14 @@ 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"
|
||||
|
||||
#
|
||||
# Rambo with MMU3
|
||||
#
|
||||
restore_configs
|
||||
opt_set MOTHERBOARD BOARD_RAMBO EXTRUDERS 5 MMU_MODEL PRUSA_MMU3
|
||||
opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE EMERGENCY_PARSER MMU_MENUS MMU2_DEBUG EEPROM_SETTINGS
|
||||
exec_test $1 $2 "Rambo with PRUSA_MMU3 " "$3"
|
||||
|
||||
#
|
||||
# Build with the default configurations
|
||||
#
|
||||
|
|
|
@ -181,7 +181,7 @@ HAS_MENU_MULTI_LANGUAGE = build_src_filter=+<src/lcd/menu/menu_la
|
|||
HAS_MENU_LED = build_src_filter=+<src/lcd/menu/menu_led.cpp>
|
||||
HAS_MENU_MEDIA = build_src_filter=+<src/lcd/menu/menu_media.cpp>
|
||||
HAS_MENU_MIXER = build_src_filter=+<src/lcd/menu/menu_mixer.cpp>
|
||||
HAS_MENU_MMU2 = build_src_filter=+<src/lcd/menu/menu_mmu2.cpp>
|
||||
HAS_MENU_MMU = build_src_filter=+<src/lcd/menu/menu_mmu2.cpp>
|
||||
HAS_MENU_ONE_CLICK_PRINT = build_src_filter=+<src/lcd/menu/menu_one_click_print.cpp> +<src/gcode/sd/M1003.cpp>
|
||||
HAS_MENU_PASSWORD = build_src_filter=+<src/lcd/menu/menu_password.cpp>
|
||||
HAS_MENU_POWER_MONITOR = build_src_filter=+<src/lcd/menu/menu_power_monitor.cpp>
|
||||
|
@ -255,6 +255,7 @@ HAS_MEATPACK = build_src_filter=+<src/feature/meatpack
|
|||
MIXING_EXTRUDER = build_src_filter=+<src/feature/mixing.cpp> +<src/gcode/feature/mixing/M163-M165.cpp>
|
||||
HAS_PRUSA_MMU1 = build_src_filter=+<src/feature/mmu/mmu.cpp>
|
||||
HAS_PRUSA_MMU2 = build_src_filter=+<src/feature/mmu/mmu2.cpp> +<src/gcode/feature/prusa_MMU2>
|
||||
HAS_PRUSA_MMU3 = build_src_filter=+<src/feature/mmu3> +<src/gcode/feature/prusa_MMU2>
|
||||
PASSWORD_FEATURE = build_src_filter=+<src/feature/password> +<src/gcode/feature/password>
|
||||
ADVANCED_PAUSE_FEATURE = build_src_filter=+<src/feature/pause.cpp> +<src/gcode/feature/pause/M600.cpp>
|
||||
CONFIGURE_FILAMENT_CHANGE = build_src_filter=+<src/gcode/feature/pause/M603.cpp>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue