Prusa MMU3 (#26635)

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
This commit is contained in:
Erkan Ozgur Yilmaz 2024-08-24 00:33:13 +01:00 committed by GitHub
parent a664587219
commit a9c529f004
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 8975 additions and 122 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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();

View 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

View 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;

File diff suppressed because it is too large Load diff

View 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;

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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);
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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'`

View 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

View 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

View 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,
};

View 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

View 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
};

View 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

View 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

View 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

View 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);

View 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;
}

View 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

View 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);

View file

@ -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;

View file

@ -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
}
}

View file

@ -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)

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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') {

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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);

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
#

View file

@ -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>