Explorar o código

Merge remote-tracking branch 'upstream/MK3' into build-with-cmake

VintagePC %!s(int64=2) %!d(string=hai) anos
pai
achega
c522330433
Modificáronse 100 ficheiros con 9687 adicións e 5229 borrados
  1. 1 0
      .gitignore
  2. 2 1
      .travis.yml
  3. 40 27
      CMakeLists.txt
  4. 0 79
      Firmware/AutoDeplete.cpp
  5. 0 17
      Firmware/AutoDeplete.h
  6. 6 5
      Firmware/Configuration.h
  7. 13 0
      Firmware/ConfigurationStore.cpp
  8. 1 1
      Firmware/ConfigurationStore.h
  9. 56 0
      Firmware/Configuration_adv.h
  10. 6 12
      Firmware/Dcodes.cpp
  11. 2 2
      Firmware/Dcodes.h
  12. 525 0
      Firmware/Filament_sensor.cpp
  13. 214 0
      Firmware/Filament_sensor.h
  14. 24 50
      Firmware/Marlin.h
  15. 2 3
      Firmware/MarlinSerial.h
  16. 897 1117
      Firmware/Marlin_main.cpp
  17. 500 0
      Firmware/Prusa_farm.cpp
  18. 32 0
      Firmware/Prusa_farm.h
  19. 1 1
      Firmware/SdBaseFile.cpp
  20. 82 0
      Firmware/SpoolJoin.cpp
  21. 60 0
      Firmware/SpoolJoin.h
  22. 105 0
      Firmware/Tcodes.cpp
  23. 5 0
      Firmware/Tcodes.h
  24. 6 0
      Firmware/Timer.cpp
  25. 3 2
      Firmware/Timer.h
  26. 0 95
      Firmware/adc.c
  27. 81 0
      Firmware/adc.cpp
  28. 7 27
      Firmware/adc.h
  29. 9 9
      Firmware/cardreader.cpp
  30. 3 3
      Firmware/cardreader.h
  31. 64 92
      Firmware/cmdqueue.cpp
  32. 0 2
      Firmware/cmdqueue.h
  33. 24 6
      Firmware/config.h
  34. 1 1
      Firmware/conv2str.cpp
  35. 2 0
      Firmware/eeprom.cpp
  36. 31 21
      Firmware/eeprom.h
  37. 300 0
      Firmware/fancheck.cpp
  38. 35 0
      Firmware/fancheck.h
  39. 8 16
      Firmware/first_lay_cal.cpp
  40. 0 788
      Firmware/fsensor.cpp
  41. 0 124
      Firmware/fsensor.h
  42. 2 2
      Firmware/language.c
  43. 9 7
      Firmware/language.h
  44. 6 1
      Firmware/lcd.cpp
  45. 3 4
      Firmware/lcd.h
  46. 3 0
      Firmware/macros.h
  47. 1 1
      Firmware/menu.cpp
  48. 14 14
      Firmware/menu.h
  49. 23 27
      Firmware/mesh_bed_calibration.cpp
  50. 25 12
      Firmware/messages.cpp
  51. 18 4
      Firmware/messages.h
  52. 0 1266
      Firmware/mmu.cpp
  53. 0 120
      Firmware/mmu.h
  54. 926 0
      Firmware/mmu2.cpp
  55. 291 0
      Firmware/mmu2.h
  56. 37 0
      Firmware/mmu2/buttons.h
  57. 109 0
      Firmware/mmu2/error_codes.h
  58. 353 0
      Firmware/mmu2/errors_list.h
  59. 45 0
      Firmware/mmu2/progress_codes.h
  60. 22 0
      Firmware/mmu2_crc.cpp
  61. 43 0
      Firmware/mmu2_crc.h
  62. 294 0
      Firmware/mmu2_error_converter.cpp
  63. 48 0
      Firmware/mmu2_error_converter.h
  64. 10 0
      Firmware/mmu2_fsensor.cpp
  65. 17 0
      Firmware/mmu2_fsensor.h
  66. 17 0
      Firmware/mmu2_log.cpp
  67. 39 0
      Firmware/mmu2_log.h
  68. 34 0
      Firmware/mmu2_power.cpp
  69. 11 0
      Firmware/mmu2_power.h
  70. 72 0
      Firmware/mmu2_progress_converter.cpp
  71. 9 0
      Firmware/mmu2_progress_converter.h
  72. 366 0
      Firmware/mmu2_protocol.cpp
  73. 324 0
      Firmware/mmu2_protocol.h
  74. 756 0
      Firmware/mmu2_protocol_logic.cpp
  75. 309 0
      Firmware/mmu2_protocol_logic.h
  76. 295 0
      Firmware/mmu2_reporting.cpp
  77. 50 0
      Firmware/mmu2_reporting.h
  78. 31 0
      Firmware/mmu2_serial.cpp
  79. 20 0
      Firmware/mmu2_serial.h
  80. 20 16
      Firmware/motion_control.cpp
  81. 2 3
      Firmware/motion_control.h
  82. 81 73
      Firmware/pat9125.cpp
  83. 1 8
      Firmware/pat9125.h
  84. 3 0
      Firmware/pins_Einsy_1_0.h
  85. 2 0
      Firmware/pins_Rambo_1_0.h
  86. 2 0
      Firmware/pins_Rambo_1_3.h
  87. 49 49
      Firmware/planner.cpp
  88. 7 4
      Firmware/planner.h
  89. 26 12
      Firmware/sound.h
  90. 20 79
      Firmware/stepper.cpp
  91. 0 5
      Firmware/stepper.h
  92. 5 0
      Firmware/strlen_cx.h
  93. 57 19
      Firmware/swi2c.c
  94. 3 0
      Firmware/swi2c.h
  95. 21 0
      Firmware/system_timer.h
  96. 115 0
      Firmware/temp_model.h
  97. 1447 874
      Firmware/temperature.cpp
  98. 25 56
      Firmware/temperature.h
  99. 21 72
      Firmware/tmc2130.cpp
  100. 0 0
      Firmware/twi.cpp

+ 1 - 0
.gitignore

@@ -15,6 +15,7 @@
 /lang/tmp/
 /lang/Firmware-intl.hex
 /lang/Firmware-intl-en_*.hex
+/lang/*.map
 
 # Temporary files and directories
 *[~#]

+ 2 - 1
.travis.yml

@@ -9,12 +9,13 @@ before_install:
   - sudo iptables -A OUTPUT -o lo -j ACCEPT
   - sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
 script:
-  - bash -x test.sh
   - cp Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h
   - bash -x build.sh || { echo "1_75mm_MK3S-EINSy10a-E3Dv6full variant failed" && false; }
+  - bash -x build.sh EN_ONLY || { echo "1_75mm_MK3S-EINSy10a-E3Dv6full EN_ONLY failed" && false; }
   - rm Firmware/Configuration_prusa.h
   - cp Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h
   - bash -x build.sh || { echo "1_75mm_MK3-EINSy10a-E3Dv6full variant failed" && false; }
+  - bash -x build.sh EN_ONLY || { echo "1_75mm_MK3-EINSy10a-E3Dv6full EN_ONLY failed" && false; }
   - rm Firmware/Configuration_prusa.h
   - cp Firmware/variants/1_75mm_MK25S-RAMBo13a-E3Dv6full.h Firmware/Configuration_prusa.h
   - bash -x build.sh || { echo "1_75mm_MK25S-RAMBo13a-E3Dv6full variant failed" && false; }

+ 40 - 27
CMakeLists.txt

@@ -100,61 +100,74 @@ add_compile_options(-g)
 # Firmware - get file lists.
 #
 SET(FW_SOURCES
-    adc.c
-    bootapp.c
-    timer02.c
-    sm4.c
-    spi.c
-    rbuf.c
-    swi2c.c
-    language.c
-    tone04.c
-    uart2.c
-    xflash.c
-    Marlin_main.cpp
-    AutoDeplete.cpp
-    Configuration.cpp
-    BlinkM.cpp
-    Dcodes.cpp
-    MarlinSerial.cpp
-    ConfigurationStore.cpp
-    Sd2Card.cpp
-    SdBaseFile.cpp
-    SdFatUtil.cpp
-    SdFile.cpp
-    SdVolume.cpp
-    Servo.cpp
-    Timer.cpp
+    adc.cpp
     backlight.cpp
+    BlinkM.cpp
+    bootapp.c
     cardreader.cpp
     cmdqueue.cpp
+    Configuration.cpp
+    ConfigurationStore.cpp
     conv2str.cpp
+    Dcodes.cpp
     eeprom.cpp
+    fancheck.cpp
+    Filament_sensor.cpp
     first_lay_cal.cpp
-    fsensor.cpp
     heatbed_pwm.cpp
     la10compat.cpp
+    language.c
     lcd.cpp
+    Marlin_main.cpp
+    MarlinSerial.cpp
     menu.cpp
     mesh_bed_calibration.cpp
     mesh_bed_leveling.cpp
     messages.cpp
-    mmu.cpp
+    mmu2.cpp
+    mmu2_crc.cpp
+    mmu2_error_converter.cpp
+    mmu2_fsensor.cpp
+    mmu2_log.cpp
+    mmu2_power.cpp
+    mmu2_progress_converter.cpp
+    mmu2_protocol.cpp
+    mmu2_protocol_logic.cpp
+    mmu2_reporting.cpp
+    mmu2_serial.cpp
     motion_control.cpp
     optiboot_xflash.cpp
     pat9125.cpp
     planner.cpp
+    Prusa_farm.cpp
     qr_solve.cpp
+    rbuf.c
+    Sd2Card.cpp
+    SdBaseFile.cpp
+    SdFatUtil.cpp
+    SdFile.cpp
+    SdVolume.cpp
+    Servo.cpp
+    sm4.c
     sound.cpp
     speed_lookuptable.cpp
+    spi.c
+    SpoolJoin.cpp
     stepper.cpp
+    swi2c.c
     swspi.cpp
+    Tcodes.cpp
     temperature.cpp
+    timer02.c
+    Timer.cpp
     tmc2130.cpp
+    tone04.c
     twi.cpp
+    uart2.c
     ultralcd.cpp
     util.cpp
     vector_3.cpp
+    xflash.c
     xflash_dump.cpp
     xyzcal.cpp
 )

+ 0 - 79
Firmware/AutoDeplete.cpp

@@ -1,79 +0,0 @@
-//! @file
-//! @author: Marek Bel
-//! @date Jan 3, 2019
-
-#include "AutoDeplete.h"
-#include "assert.h"
-
-//! @brief bit field marking depleted filaments
-//!
-//! binary 1 marks filament as depleted
-//! Zero initialized value means, that no filament is depleted.
-static uint8_t depleted;
-static const uint8_t filamentCount = 5;
-
-//! @return binary 1 for all filaments
-//! @par fCount number of filaments
-static constexpr uint8_t allDepleted(uint8_t fCount)
-{
-    return fCount == 1 ? 1 : ((1 << (fCount - 1)) | allDepleted(fCount - 1));
-}
-
-//! @brief Is filament available for printing?
-//! @par filament Filament number to be checked
-//! @retval true Filament is available for printing.
-//! @retval false Filament is not available for printing.
-static bool loaded(uint8_t filament)
-{
-    if (depleted & (1 << filament)) return false;
-    return true;
-}
-
-//! @brief Mark filament as not available for printing.
-//! @par filament filament to be marked
-void ad_markDepleted(uint8_t filament)
-{
-    assert(filament < filamentCount);
-    if (filament < filamentCount)
-    {
-        depleted |= 1 << filament;
-    }
-}
-
-//! @brief Mark filament as available for printing.
-//! @par filament filament to be marked
-void ad_markLoaded(uint8_t filament)
-{
-    assert(filament < filamentCount);
-    if (filament < filamentCount)
-    {
-        depleted &= ~(1 << filament);
-    }
-}
-
-//! @brief Get alternative filament, which is not depleted
-//! @par filament filament
-//! @return Filament, if it is depleted, returns next available,
-//! if all filaments are depleted, returns filament function parameter.
-uint8_t ad_getAlternative(uint8_t filament)
-{
-    assert(filament < filamentCount);
-    for (uint8_t i = 0; i<filamentCount; ++i)
-    {
-        uint8_t nextFilament = (filament + i) % filamentCount;
-        if (loaded(nextFilament)) return nextFilament;
-    }
-    return filament;
-}
-
-//! @brief Are all filaments depleted?
-//! @retval true All filaments are depleted.
-//! @retval false All filaments are not depleted.
-bool ad_allDepleted()
-{
-    if (allDepleted(filamentCount) == depleted)
-    {
-        return true;
-    }
-    return false;
-}

+ 0 - 17
Firmware/AutoDeplete.h

@@ -1,17 +0,0 @@
-//! @file
-//! @author: Marek Bel
-//! @brief Filament auto deplete engine for multi-material prints with MMUv2 (Now marketed as SpoolJoin)
-//!
-//! Interface for marking MMUv2 filaments as depleted and getting alternative filament for printing.
-
-#ifndef AUTODEPLETE_H
-#define AUTODEPLETE_H
-
-#include <stdint.h>
-
-void ad_markDepleted(uint8_t filament);
-void ad_markLoaded(uint8_t filament);
-uint8_t ad_getAlternative(uint8_t filament);
-bool ad_allDepleted();
-
-#endif /* AUTODEPLETE_H */

+ 6 - 5
Firmware/Configuration.h

@@ -17,9 +17,9 @@ extern PGM_P sPrinterName;
 
 // Firmware version
 #define FW_MAJOR 3
-#define FW_MINOR 11
-#define FW_REVISION 1
-//#define FW_FLAVOR RC      //uncomment if DEBUG, DEVEL, APLHA, BETA or RC
+#define FW_MINOR 13
+#define FW_REVISION 0
+//#define FW_FLAVOR RC      //uncomment if DEBUG, DEVEL, ALPHA, BETA or RC
 //#define FW_FLAVERSION 1     //uncomment if FW_FLAVOR is defined and versioning is needed.
 #ifndef FW_FLAVOR
     #define FW_VERSION STR(FW_MAJOR) "." STR(FW_MINOR) "." STR(FW_REVISION)
@@ -444,10 +444,12 @@ your extruder heater takes 2 minutes to hit the target on heating.
 // Custom M code points
 #define CUSTOM_M_CODES
 #ifdef CUSTOM_M_CODES
+#ifdef ENABLE_AUTO_BED_LEVELING
   #define CUSTOM_M_CODE_SET_Z_PROBE_OFFSET 851
   #define Z_PROBE_OFFSET_RANGE_MIN -15
   #define Z_PROBE_OFFSET_RANGE_MAX -5
-#endif
+#endif // ENABLE_AUTO_BED_LEVELING
+#endif // CUSTOM_M_CODES
 
 
 // EEPROM
@@ -565,5 +567,4 @@ enum CalibrationStatus
 #include "Configuration_adv.h"
 #include "thermistortables.h"
 
-
 #endif //__CONFIGURATION_H

+ 13 - 0
Firmware/ConfigurationStore.cpp

@@ -75,6 +75,9 @@ void Config_StoreSettings()
   
   if (EEPROM_writeData(reinterpret_cast<uint8_t*>(EEPROM_M500_base),reinterpret_cast<uint8_t*>(&cs),sizeof(cs),0), "cs, invalid version")
   {
+#ifdef TEMP_MODEL
+      temp_model_save_settings();
+#endif
       strcpy(cs.version,EEPROM_VERSION); //!< validate data if write succeed
       EEPROM_writeData(reinterpret_cast<uint8_t*>(EEPROM_M500_base->version), reinterpret_cast<uint8_t*>(cs.version), sizeof(cs.version), "cs.version valid");
   }
@@ -173,6 +176,9 @@ void Config_PrintSettings(uint8_t level)
     printf_P(PSTR(
         "%SArc Settings: P:Max length(mm) S:Min length (mm) N:Corrections R:Min segments F:Segments/sec.\n%S  M214 P%.2f S%.2f N%d R%d F%d\n"),
         echomagic, echomagic, cs.mm_per_arc_segment, cs.min_mm_per_arc_segment, cs.n_arc_correction, cs.min_arc_segments, cs.arc_segments_per_sec);
+#ifdef TEMP_MODEL
+    temp_model_report_settings();
+#endif
 }
 #endif
 
@@ -321,6 +327,10 @@ bool Config_RetrieveSettings()
 
     // Call updatePID (similar to when we have processed M301)
     updatePID();
+#ifdef TEMP_MODEL
+    temp_model_load_settings();
+#endif
+
         SERIAL_ECHO_START;
         SERIAL_ECHOLNPGM("Stored settings retrieved");
     }
@@ -353,6 +363,9 @@ void Config_ResetDefault()
 #ifdef PIDTEMP
     updatePID();
 #endif//PIDTEMP
+#ifdef TEMP_MODEL
+    temp_model_reset_settings();
+#endif
 
   calculate_extruder_multipliers();
 

+ 1 - 1
Firmware/ConfigurationStore.h

@@ -19,7 +19,7 @@ typedef struct
     unsigned long minsegmenttime;
     float max_jerk[4]; //!< Jerk is a maximum immediate velocity change.
     float add_homing[3];
-    float zprobe_zoffset;
+    float zprobe_zoffset; //!< Only used with define ENABLE_AUTO_BED_LEVELING
     float Kp;
     float Ki;
     float Kd;

+ 56 - 0
Firmware/Configuration_adv.h

@@ -346,6 +346,62 @@ const unsigned int dropsegments=5; //everything with less than this number of st
 // 2nd and 3rd byte (LSB first) contains a 16bit length of a command including its preceding comments.
 #define CMDHDRSIZE 3
 
+/**
+ * Advanced Pause for Filament Change
+ *  - Adds the G-code M600 Filament Change to initiate a filament change.
+ *  - This feature is required for the default FILAMENT_RUNOUT_SCRIPT.
+ *
+ * Requirements:
+ *  - For Filament Change parking enable and configure NOZZLE_PARK_FEATURE.
+ *  - For user interaction enable an LCD display, HOST_PROMPT_SUPPORT, or EMERGENCY_PARSER.
+ *
+ * Enable PARK_HEAD_ON_PAUSE to add the G-code M125 Pause and Park.
+ */
+
+#define PAUSE_PARK_RETRACT_FEEDRATE         60  // (mm/s) Initial retract feedrate.
+#define PAUSE_PARK_RETRACT_LENGTH            2  // (mm) Initial retract.
+                                                // This short retract is done immediately, before parking the nozzle.
+#define FILAMENT_CHANGE_UNLOAD_FEEDRATE     10  // (mm/s) Unload filament feedrate. This can be pretty fast.
+#define FILAMENT_CHANGE_UNLOAD_ACCEL        25  // (mm/s^2) Lower acceleration may allow a faster feedrate.
+#define FILAMENT_CHANGE_UNLOAD_LENGTH      100  // (mm) The length of filament for a complete unload.
+                                                //   For Bowden, the full length of the tube and nozzle.
+                                                //   For direct drive, the full length of the nozzle.
+                                                //   Set to 0 for manual unloading.
+#define FILAMENT_CHANGE_SLOW_LOAD_FEEDRATE   6  // (mm/s) Slow move when starting load.
+#define FILAMENT_CHANGE_SLOW_LOAD_LENGTH     0  // (mm) Slow length, to allow time to insert material.
+                                                // 0 to disable start loading and skip to fast load only
+#define FILAMENT_CHANGE_FAST_LOAD_FEEDRATE   6  // (mm/s) Load filament feedrate. This can be pretty fast.
+#define FILAMENT_CHANGE_FAST_LOAD_ACCEL     25  // (mm/s^2) Lower acceleration may allow a faster feedrate.
+#define FILAMENT_CHANGE_FAST_LOAD_LENGTH     0  // (mm) Load length of filament, from extruder gear to nozzle.
+                                                //   For Bowden, the full length of the tube and nozzle.
+                                                //   For direct drive, the full length of the nozzle.
+//#define ADVANCED_PAUSE_CONTINUOUS_PURGE       // Purge continuously up to the purge length until interrupted.
+#define ADVANCED_PAUSE_PURGE_FEEDRATE        3  // (mm/s) Extrude feedrate (after loading). Should be slower than load feedrate.
+#define ADVANCED_PAUSE_PURGE_LENGTH         50  // (mm) Length to extrude after loading.
+                                                //   Set to 0 for manual extrusion.
+                                                //   Filament can be extruded repeatedly from the Filament Change menu
+                                                //   until extrusion is consistent, and to purge old filament.
+#define ADVANCED_PAUSE_RESUME_PRIME          0  // (mm) Extra distance to prime nozzle after returning from park.
+//#define ADVANCED_PAUSE_FANS_PAUSE             // Turn off print-cooling fans while the machine is paused.
+
+                                                // Filament Unload does a Retract, Delay, and Purge first:
+#define FILAMENT_UNLOAD_PURGE_RETRACT       13  // (mm) Unload initial retract length.
+#define FILAMENT_UNLOAD_PURGE_DELAY       5000  // (ms) Delay for the filament to cool after retract.
+#define FILAMENT_UNLOAD_PURGE_LENGTH         8  // (mm) An unretract is done, then this length is purged.
+#define FILAMENT_UNLOAD_PURGE_FEEDRATE      25  // (mm/s) feedrate to purge before unload
+
+#define PAUSE_PARK_NOZZLE_TIMEOUT           45  // (seconds) Time limit before the nozzle is turned off for safety.
+#define FILAMENT_CHANGE_ALERT_BEEPS         10  // Number of alert beeps to play when a response is needed.
+#define PAUSE_PARK_NO_STEPPER_TIMEOUT           // Enable for XYZ steppers to stay powered on during filament change.
+//#define FILAMENT_CHANGE_RESUME_ON_INSERT      // Automatically continue / load filament when runout sensor is triggered again.
+//#define PAUSE_REHEAT_FAST_RESUME              // Reduce number of waits by not prompting again post-timeout before continuing.
+
+//#define PARK_HEAD_ON_PAUSE                    // Park the nozzle during pause and filament change.
+//#define HOME_BEFORE_FILAMENT_CHANGE           // If needed, home before parking for filament change
+
+//#define FILAMENT_LOAD_UNLOAD_GCODES           // Add M701/M702 Load/Unload G-codes, plus Load/Unload in the LCD Prepare menu.
+//#define FILAMENT_UNLOAD_ALL_EXTRUDERS         // Allow M702 to unload all extruders above a minimum target temp (as set by M302)
+
 
 // Firmware based and LCD controlled retract
 // M207 and M208 can be used to define parameters for the retraction.

+ 6 - 12
Firmware/Dcodes.cpp

@@ -44,7 +44,7 @@ void print_hex_word(daddr_t val)
     print_hex_byte(val & 0xFF);
 }
 
-int parse_hex(char* hex, uint8_t* data, int count)
+int parse_hex(const char* hex, uint8_t* data, int count)
 {
 	int parsed = 0;
 	while (*hex)
@@ -573,6 +573,7 @@ void dcode_9()
 		for (uint8_t i = 0; i < ADC_CHAN_CNT; i++)
 			printf_P(PSTR("\tADC%d=%4d\t(%S)\n"), i, dcode_9_ADC_val(i) >> 4, dcode_9_ADC_name(i));
 	}
+#if 0
 	else
 	{
 		uint8_t index = 0xff;
@@ -583,11 +584,12 @@ void dcode_9()
 			if (code_seen('V')) // value to be written as simulated
 			{
 				adc_sim_mask |= (1 << index);
-				adc_values[index] = (((int)code_value()) << 4);
+				adc_values[index] = ((uint16_t)code_value_short() << 4);
 				printf_P(PSTR("ADC%d=%4d\n"), index, adc_values[index] >> 4);
 			}
 		}
 	}
+#endif
 }
 
     /*!
@@ -863,7 +865,7 @@ void dcode_2130()
 }
 #endif //TMC2130
 
-#ifdef PAT9125
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
     /*!
     ### D9125 - PAT9125 filament sensor <a href="https://reprap.org/wiki/G-code#D9:_Read.2FWrite_ADC">D9125: PAT9125 filament sensor</a>
     #### Usage
@@ -876,7 +878,6 @@ void dcode_2130()
     - `R` - Resolution. Not active in code
     - `X` - X values
     - `Y` - Y values
-    - `L` - Activate filament sensor log
     */
 void dcode_9125()
 {
@@ -910,15 +911,8 @@ void dcode_9125()
 		pat9125_y = (int)code_value();
 		LOG("pat9125_y=%d\n", pat9125_y);
 	}
-#ifdef DEBUG_FSENSOR_LOG
-	if (code_seen('L'))
-	{
-		fsensor_log = (int)code_value();
-		LOG("fsensor_log=%d\n", fsensor_log);
-	}
-#endif //DEBUG_FSENSOR_LOG
 }
-#endif //PAT9125
+#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
 
 #endif //DEBUG_DCODES
 

+ 2 - 2
Firmware/Dcodes.h

@@ -53,9 +53,9 @@ extern void dcode_81(); //D81 - Bed analysis. This command will log data to SD c
 	extern void dcode_2130(); //D2130 - TMC2130
 #endif //TMC2130
 
-#ifdef PAT9125
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
 	extern void dcode_9125(); //D9125 - PAT9125
-#endif //PAT9125
+#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
 
 
 #endif //DCODES_H

+ 525 - 0
Firmware/Filament_sensor.cpp

@@ -0,0 +1,525 @@
+#include <avr/pgmspace.h>
+#include <stdio.h>
+#include <util/atomic.h>
+
+#include "Filament_sensor.h"
+#include "Timer.h"
+#include "cardreader.h"
+#include "eeprom.h"
+#include "menu.h"
+#include "planner.h"
+#include "temperature.h"
+#include "ultralcd.h"
+
+#ifdef FILAMENT_SENSOR
+FSensorBlockRunout::FSensorBlockRunout() {
+    fsensor.setRunoutEnabled(false); //suppress filament runouts while loading filament.
+    fsensor.setAutoLoadEnabled(false); //suppress filament autoloads while loading filament.
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+    fsensor.setJamDetectionEnabled(false); //suppress filament jam detection while loading filament.
+#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+//    SERIAL_ECHOLNPGM("FSBlockRunout");
+}
+
+FSensorBlockRunout::~FSensorBlockRunout() {
+    fsensor.settings_init(); // restore filament runout state.
+//    SERIAL_ECHOLNPGM("FSUnBlockRunout");
+}
+
+# if FILAMENT_SENSOR_TYPE == FSENSOR_IR
+IR_sensor fsensor;
+# elif FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG
+IR_sensor_analog fsensor;
+# elif FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125
+PAT9125_sensor fsensor;
+# endif
+
+#else // FILAMENT_SENSOR
+FSensorBlockRunout::FSensorBlockRunout() { }
+FSensorBlockRunout::~FSensorBlockRunout() { }
+#endif // FILAMENT_SENSOR
+
+void Filament_sensor::setEnabled(bool enabled) {
+    eeprom_update_byte((uint8_t *)EEPROM_FSENSOR, enabled);
+    if (enabled) {
+        fsensor.init();
+    } else {
+        fsensor.deinit();
+    }
+}
+
+void Filament_sensor::setAutoLoadEnabled(bool state, bool updateEEPROM) {
+    autoLoadEnabled = state;
+    if (updateEEPROM) {
+        eeprom_update_byte((uint8_t *)EEPROM_FSENS_AUTOLOAD_ENABLED, state);
+    }
+}
+
+void Filament_sensor::setRunoutEnabled(bool state, bool updateEEPROM) {
+    runoutEnabled = state;
+    if (updateEEPROM) {
+        eeprom_update_byte((uint8_t *)EEPROM_FSENS_RUNOUT_ENABLED, state);
+    }
+}
+
+void Filament_sensor::setActionOnError(SensorActionOnError state, bool updateEEPROM) {
+    sensorActionOnError = state;
+    if (updateEEPROM) {
+        eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_ACTION_NA, (uint8_t)state);
+    }
+}
+
+void Filament_sensor::settings_init_common() {
+    bool enabled = eeprom_read_byte((uint8_t *)EEPROM_FSENSOR);
+    if ((state != State::disabled) != enabled) {
+        state = enabled ? State::initializing : State::disabled;
+    }
+
+    autoLoadEnabled = eeprom_read_byte((uint8_t *)EEPROM_FSENS_AUTOLOAD_ENABLED);
+    runoutEnabled = eeprom_read_byte((uint8_t *)EEPROM_FSENS_RUNOUT_ENABLED);
+    sensorActionOnError = (SensorActionOnError)eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_ACTION_NA);
+    if (sensorActionOnError == SensorActionOnError::_Undef) {
+        sensorActionOnError = SensorActionOnError::_Continue;
+    }
+}
+
+bool Filament_sensor::checkFilamentEvents() {
+    if (state != State::ready)
+        return false;
+    if (eventBlankingTimer.running() && !eventBlankingTimer.expired(100)) { // event blanking for 100ms
+        return false;
+    }
+
+    bool newFilamentPresent = fsensor.getFilamentPresent();
+    if (oldFilamentPresent != newFilamentPresent) {
+        oldFilamentPresent = newFilamentPresent;
+        eventBlankingTimer.start();
+        if (newFilamentPresent) { // filament insertion
+//            puts_P(PSTR("filament inserted"));
+            triggerFilamentInserted();
+            postponedLoadEvent = true;
+        } else { // filament removal
+//            puts_P(PSTR("filament removed"));
+            triggerFilamentRemoved();
+        }
+        return true;
+    }
+    return false;
+}
+
+void Filament_sensor::triggerFilamentInserted() {
+    if (autoLoadEnabled
+        && (eFilamentAction == FilamentAction::None)
+        && (! MMU2::mmu2.Enabled() ) // quick and dirty hack to prevent spurious runouts while the MMU is in charge
+        && !(
+            moves_planned() != 0
+            || IS_SD_PRINTING
+            || usb_timer.running()
+            || (lcd_commands_type == LcdCommands::Layer1Cal)
+            || eeprom_read_byte((uint8_t *)EEPROM_WIZARD_ACTIVE)
+            )
+        ) {
+        filAutoLoad();
+    }
+}
+
+void Filament_sensor::triggerFilamentRemoved() {
+//    SERIAL_ECHOLNPGM("triggerFilamentRemoved");
+    if (runoutEnabled
+        && (! MMU2::mmu2.Enabled() ) // quick and dirty hack to prevent spurious runouts just before the toolchange
+        && (eFilamentAction == FilamentAction::None)
+        && !saved_printing
+        && (
+            moves_planned() != 0
+            || IS_SD_PRINTING
+            || usb_timer.running()
+            || (lcd_commands_type == LcdCommands::Layer1Cal)
+            || eeprom_read_byte((uint8_t *)EEPROM_WIZARD_ACTIVE)
+        )
+    ){
+//        SERIAL_ECHOPGM("runoutEnabled="); SERIAL_ECHOLN((int)runoutEnabled);
+//        SERIAL_ECHOPGM("eFilamentAction="); SERIAL_ECHOLN((int)eFilamentAction);
+//        SERIAL_ECHOPGM("saved_printing="); SERIAL_ECHOLN((int)saved_printing);
+        filRunout();
+    }
+}
+
+void Filament_sensor::filAutoLoad() {
+    eFilamentAction = FilamentAction::AutoLoad;
+    if (target_temperature[0] >= EXTRUDE_MINTEMP) {
+        bFilamentPreheatState = true;
+        menu_submenu(mFilamentItemForce);
+    } else {
+        menu_submenu(lcd_generic_preheat_menu);
+        lcd_timeoutToStatus.start();
+    }
+}
+
+void Filament_sensor::filRunout() {
+//    SERIAL_ECHOLNPGM("filRunout");
+    runoutEnabled = false;
+    autoLoadEnabled = false;
+    stop_and_save_print_to_ram(0, 0);
+    restore_print_from_ram_and_continue(0);
+    eeprom_update_byte((uint8_t *)EEPROM_FERROR_COUNT, eeprom_read_byte((uint8_t *)EEPROM_FERROR_COUNT) + 1);
+    eeprom_update_word((uint16_t *)EEPROM_FERROR_COUNT_TOT, eeprom_read_word((uint16_t *)EEPROM_FERROR_COUNT_TOT) + 1);
+    enquecommand_front_P((PSTR("M600")));
+}
+
+void Filament_sensor::triggerError() {
+    state = State::error;
+
+    /// some message, idk
+    ; //
+}
+
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
+void IR_sensor::init() {
+    if (state == State::error) {
+        fsensor.deinit(); // deinit first if there was an error.
+    }
+//    puts_P(PSTR("fsensor::init()"));
+    SET_INPUT(IR_SENSOR_PIN); // input mode
+    WRITE(IR_SENSOR_PIN, 1);  // pullup
+    settings_init();          // also sets the state to State::initializing
+}
+
+void IR_sensor::deinit() {
+//    puts_P(PSTR("fsensor::deinit()"));
+    SET_INPUT(IR_SENSOR_PIN); // input mode
+    WRITE(IR_SENSOR_PIN, 0);  // no pullup
+    state = State::disabled;
+}
+
+bool IR_sensor::update() {
+    switch (state) {
+    case State::initializing:
+        state = State::ready; // the IR sensor gets ready instantly as it's just a gpio read operation.
+        // initialize the current filament state so that we don't create a switching event right after the sensor is ready.
+        oldFilamentPresent = fsensor.getFilamentPresent();
+        [[fallthrough]];
+    case State::ready: {
+        postponedLoadEvent = false;
+        return checkFilamentEvents();
+    } break;
+    case State::disabled:
+    case State::error:
+    default:
+        return false;
+    }
+    return false;
+}
+
+
+
+#ifdef FSENSOR_PROBING
+bool IR_sensor::probeOtherType() { return pat9125_probe(); }
+#endif
+
+void IR_sensor::settings_init() { Filament_sensor::settings_init_common(); }
+
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
+void IR_sensor_analog::init() {
+    IR_sensor::init();
+    IR_sensor::settings_init();
+    sensorRevision = (SensorRevision)eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_PCB);
+}
+
+bool IR_sensor_analog::update() {
+    bool event = IR_sensor::update();
+    if (state == State::ready) {
+        if (getVoltReady()) {
+            clearVoltReady();
+            uint16_t volt = getVoltRaw();
+//            printf_P(PSTR("newVoltRaw:%u\n"), volt / OVERSAMPLENR);
+
+            // detect min-max, some long term sliding window for filtration may be added
+            // avoiding floating point operations, thus computing in raw
+            if (volt > maxVolt) {
+                maxVolt = volt;
+            } else if (volt < minVolt) {
+                minVolt = volt;
+            }
+            //! The trouble is, I can hold the filament in the hole in such a way, that it creates the exact voltage
+            //! to be detected as the new fsensor
+            //! We can either fake it by extending the detection window to a looooong time
+            //! or do some other countermeasures
+
+            //! what we want to detect:
+            //! if minvolt gets below ~0.3V, it means there is an old fsensor
+            //! if maxvolt gets above 4.6V, it means we either have an old fsensor or broken cables/fsensor
+            //! So I'm waiting for a situation, when minVolt gets to range <0, 1.5> and maxVolt gets into range <3.0, 5>
+            //! If and only if minVolt is in range <0.3, 1.5> and maxVolt is in range <3.0, 4.6>, I'm considering a situation with the new fsensor
+            if (minVolt >= IRsensor_Ldiode_TRESHOLD && minVolt <= IRsensor_Lmax_TRESHOLD && maxVolt >= IRsensor_Hmin_TRESHOLD &&
+                maxVolt <= IRsensor_Hopen_TRESHOLD) {
+                IR_ANALOG_Check(SensorRevision::_Old, SensorRevision::_Rev04);
+            }
+            //! If and only if minVolt is in range <0.0, 0.3> and maxVolt is in range  <4.6, 5.0V>, I'm considering a situation with the old fsensor
+            //! Note, we are not relying on one voltage here - getting just +5V can mean an old fsensor or a broken new sensor - that's why
+            //! we need to have both voltages detected correctly to allow switching back to the old fsensor.
+            else if (minVolt < IRsensor_Ldiode_TRESHOLD && maxVolt > IRsensor_Hopen_TRESHOLD && maxVolt <= IRsensor_VMax_TRESHOLD) {
+                IR_ANALOG_Check(SensorRevision::_Rev04, SensorRevision::_Old);
+            }
+
+            if (!checkVoltage(volt)) {
+                triggerError();
+            }
+        }
+    }
+
+    ; //
+
+    return event;
+}
+
+void IR_sensor_analog::voltUpdate(uint16_t raw) { // to be called from the ADC ISR when a cycle is finished
+    voltRaw = raw;
+    voltReady = true;
+}
+
+uint16_t IR_sensor_analog::getVoltRaw() {
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { return voltRaw; }
+}
+
+const char *IR_sensor_analog::getIRVersionText() {
+    switch (sensorRevision) {
+    case SensorRevision::_Old:
+        return _T(MSG_IR_03_OR_OLDER);
+    case SensorRevision::_Rev04:
+        return _T(MSG_IR_04_OR_NEWER);
+    default:
+        return _T(MSG_IR_UNKNOWN);
+    }
+}
+
+void IR_sensor_analog::setSensorRevision(SensorRevision rev, bool updateEEPROM) {
+    sensorRevision = rev;
+    if (updateEEPROM) {
+        eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_PCB, (uint8_t)rev);
+    }
+}
+
+bool IR_sensor_analog::checkVoltage(uint16_t raw) {
+    if (IRsensor_Lmax_TRESHOLD <= raw && raw <= IRsensor_Hmin_TRESHOLD) {
+        /// If the voltage is in forbidden range, the fsensor is ok, but the lever is mounted improperly.
+        /// Or the user is so creative so that he can hold a piece of fillament in the hole in such a genius way,
+        /// that the IR fsensor reading is within 1.5 and 3V ... this would have been highly unusual
+        /// and would have been considered more like a sabotage than normal printer operation
+        if (voltageErrorCnt++ > 4) {
+            puts_P(PSTR("fsensor in forbidden range 1.5-3V - check sensor"));
+            return false;
+        }
+    } else {
+        voltageErrorCnt = 0;
+    }
+    if (sensorRevision == SensorRevision::_Rev04) {
+        /// newer IR sensor cannot normally produce 4.6-5V, this is considered a failure/bad mount
+        if (IRsensor_Hopen_TRESHOLD <= raw && raw <= IRsensor_VMax_TRESHOLD) {
+            puts_P(PSTR("fsensor v0.4 in fault range 4.6-5V - unconnected"));
+            return false;
+        }
+        /// newer IR sensor cannot normally produce 0-0.3V, this is considered a failure
+#if 0 // Disabled as it has to be decided if we gonna use this or not.
+            if(IRsensor_Hopen_TRESHOLD <= raw && raw <= IRsensor_VMax_TRESHOLD) {
+                puts_P(PSTR("fsensor v0.4 in fault range 0.0-0.3V - wrong IR sensor"));
+                return false;
+            }
+#endif
+    }
+    /// If IR sensor is "uknown state" and filament is not loaded > 1.5V return false
+#if 0
+#error "I really think this code can't be enabled anymore because we are constantly checking this voltage."
+        if((sensorRevision == SensorRevision::_Undef) && (raw > IRsensor_Lmax_TRESHOLD)) {
+            puts_P(PSTR("Unknown IR sensor version and no filament loaded detected."));
+            return false;
+        }
+#endif
+    // otherwise the IR fsensor is considered working correctly
+    return true;
+}
+
+bool IR_sensor_analog::getVoltReady() const {
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ return voltReady; }
+}
+
+void IR_sensor_analog::clearVoltReady(){
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ voltReady = false; }
+}
+
+void IR_sensor_analog::IR_ANALOG_Check(SensorRevision isVersion, SensorRevision switchTo) {
+    bool bTemp = (!CHECK_ALL_HEATERS);
+    bTemp = bTemp && (menu_menu == lcd_status_screen);
+    bTemp = bTemp && ((sensorRevision == isVersion) || (sensorRevision == SensorRevision::_Undef));
+    bTemp = bTemp && (state == State::ready);
+    if (bTemp) {
+        nFSCheckCount++;
+        if (nFSCheckCount > FS_CHECK_COUNT) {
+            nFSCheckCount = 0; // not necessary
+            setSensorRevision(switchTo, true);
+            printf_IRSensorAnalogBoardChange();
+            switch (switchTo) {
+            case SensorRevision::_Old:
+                lcd_setstatuspgm(_T(MSG_IR_03_OR_OLDER));
+                break;
+            case SensorRevision::_Rev04:
+                lcd_setstatuspgm(_T(MSG_IR_04_OR_NEWER));
+                break;
+            default:
+                break;
+            }
+        }
+    } else {
+        nFSCheckCount = 0;
+    }
+}
+#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
+#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
+
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+void PAT9125_sensor::init() {
+    if (state == State::error) {
+        deinit(); // deinit first if there was an error.
+    }
+//    puts_P(PSTR("fsensor::init()"));
+
+    settings_init(); // also sets the state to State::initializing
+
+    calcChunkSteps(cs.axis_steps_per_unit[E_AXIS]); // for jam detection
+
+    if (!pat9125_init()) {
+        deinit();
+        triggerError();
+        ; //
+    }
+#ifdef IR_SENSOR_PIN
+    else if (!READ(IR_SENSOR_PIN)) {
+        ; // MK3 fw on MK3S printer
+    }
+#endif // IR_SENSOR_PIN
+}
+
+void PAT9125_sensor::deinit() {
+//    puts_P(PSTR("fsensor::deinit()"));
+    ; //
+    state = State::disabled;
+    filter = 0;
+}
+
+bool PAT9125_sensor::update() {
+    switch (state) {
+    case State::initializing:
+        if (!updatePAT9125()) {
+            break; // still not stable. Stay in the initialization state.
+        }
+        oldFilamentPresent =
+            getFilamentPresent(); // initialize the current filament state so that we don't create a switching event right after the sensor is ready.
+        oldPos = pat9125_y;
+        state = State::ready;
+        break;
+    case State::ready: {
+        updatePAT9125();
+        postponedLoadEvent = false;
+        bool event = checkFilamentEvents();
+
+        ; //
+
+        return event;
+    } break;
+    case State::disabled:
+    case State::error:
+    default:
+        return false;
+    }
+    return false;
+}
+
+#ifdef FSENSOR_PROBING
+bool PAT9125_sensor::probeOtherType() {
+    SET_INPUT(IR_SENSOR_PIN); // input mode
+    WRITE(IR_SENSOR_PIN, 1);  // pullup
+    _delay_us(100); // wait for the pullup to pull the line high (might be needed, not really sure. The internal pullups are quite weak and there might be a
+                    // long wire attached).
+    bool fsensorDetected = !READ(IR_SENSOR_PIN);
+    WRITE(IR_SENSOR_PIN, 0); // no pullup
+    return fsensorDetected;
+}
+#endif
+
+void PAT9125_sensor::setJamDetectionEnabled(bool state, bool updateEEPROM) {
+    jamDetection = state;
+    oldPos = pat9125_y;
+    resetStepCount();
+    jamErrCnt = 0;
+    if (updateEEPROM) {
+        eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_JAM_DETECTION, state);
+    }
+}
+
+void PAT9125_sensor::settings_init() {
+//    puts_P(PSTR("settings_init"));
+    Filament_sensor::settings_init_common();
+    setJamDetectionEnabled(eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_JAM_DETECTION));
+}
+
+int16_t PAT9125_sensor::getStepCount() {
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { return stepCount; }
+}
+
+void PAT9125_sensor::resetStepCount() {
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { stepCount = 0; }
+}
+
+void PAT9125_sensor::filJam() {
+    runoutEnabled = false;
+    autoLoadEnabled = false;
+    jamDetection = false;
+    stop_and_save_print_to_ram(0, 0);
+    restore_print_from_ram_and_continue(0);
+    eeprom_update_byte((uint8_t *)EEPROM_FERROR_COUNT, eeprom_read_byte((uint8_t *)EEPROM_FERROR_COUNT) + 1);
+    eeprom_update_word((uint16_t *)EEPROM_FERROR_COUNT_TOT, eeprom_read_word((uint16_t *)EEPROM_FERROR_COUNT_TOT) + 1);
+    enquecommand_front_P((PSTR("M600")));
+}
+
+bool PAT9125_sensor::updatePAT9125() {
+    if (jamDetection) {
+        int16_t _stepCount = getStepCount();
+        if (abs(_stepCount) >= chunkSteps) { // end of chunk. Check distance
+            resetStepCount();
+            if (!pat9125_update()) { // get up to date data. reinit on error.
+                init();              // try to reinit.
+            }
+            bool fsDir = (pat9125_y - oldPos) > 0;
+            bool stDir = _stepCount > 0;
+            if (fsDir != stDir) {
+                jamErrCnt++;
+            } else if (jamErrCnt) {
+                jamErrCnt--;
+            }
+            oldPos = pat9125_y;
+        }
+        if (jamErrCnt > 10) {
+            jamErrCnt = 0;
+            filJam();
+        }
+    }
+
+    if (!pollingTimer.running() || pollingTimer.expired(pollingPeriod)) {
+        pollingTimer.start();
+        if (!pat9125_update()) {
+            init(); // try to reinit.
+        }
+
+        bool present = (pat9125_s < 17) || (pat9125_s >= 17 && pat9125_b >= 50);
+        if (present != filterFilPresent) {
+            filter++;
+        } else if (filter) {
+            filter--;
+        }
+        if (filter >= filterCnt) {
+            filter = 0;
+            filterFilPresent = present;
+        }
+    }
+    return (filter == 0); // return stability
+}
+#endif // #if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)

+ 214 - 0
Firmware/Filament_sensor.h

@@ -0,0 +1,214 @@
+#pragma once
+#include <inttypes.h>
+
+#include "cmdqueue.h"
+#include "pins.h"
+#include "fastio.h"
+#include "adc.h"
+#include "pat9125.h"
+
+#define FSENSOR_IR 1
+#define FSENSOR_IR_ANALOG 2
+#define FSENSOR_PAT9125 3
+
+/// 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();
+};
+
+/// Base class Filament sensor
+/// 
+/// Ideally, there could have been a nice class hierarchy of filament sensor types with common functionality
+/// extracted into this base class.
+/// But:
+/// - virtual methods take more space
+/// - we don't need to switch among different filament sensors at runtime
+/// Therefore the class hierarchy carefully avoids using virtual methods and doesn't look too fancy.
+#ifdef FILAMENT_SENSOR
+class Filament_sensor {
+public:
+    enum class State : uint8_t {
+        disabled = 0,
+        initializing,
+        ready,
+        error,
+    };
+    
+    enum class SensorActionOnError : uint8_t {
+        _Continue = 0,
+        _Pause = 1,
+        _Undef = EEPROM_EMPTY_VALUE
+    };
+    
+    static void setEnabled(bool enabled);
+    
+    void setAutoLoadEnabled(bool state, bool updateEEPROM = false);
+    bool getAutoLoadEnabled() const { return autoLoadEnabled; }
+    
+    void setRunoutEnabled(bool state, bool updateEEPROM = false);
+    bool getRunoutEnabled() const { return runoutEnabled; }
+    
+    void setActionOnError(SensorActionOnError state, bool updateEEPROM = false);
+    SensorActionOnError getActionOnError() const { return sensorActionOnError; }
+    
+    bool getFilamentLoadEvent() const { return postponedLoadEvent; }
+    
+    bool isError() const { return state == State::error; }
+    bool isReady() const { return state == State::ready; }
+    bool isEnabled() const { return state != State::disabled; }
+    
+protected:
+    void settings_init_common();
+    
+    bool checkFilamentEvents();
+    
+    void triggerFilamentInserted();
+    
+    void triggerFilamentRemoved();
+    
+    static void filAutoLoad();
+    
+    void filRunout();
+    
+    void triggerError();
+    
+    State state;
+    bool autoLoadEnabled;
+    bool runoutEnabled;
+    bool oldFilamentPresent; //for creating filament presence switching events.
+    bool postponedLoadEvent; //this event lasts exactly one update cycle. It is long enough to be able to do polling for load event.
+    ShortTimer eventBlankingTimer;
+    SensorActionOnError sensorActionOnError;
+};
+
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
+class IR_sensor: public Filament_sensor {
+public:
+    void init();
+    void deinit();
+    bool update();
+    bool getFilamentPresent() const { return !READ(IR_SENSOR_PIN); }
+#ifdef FSENSOR_PROBING
+    static bool probeOtherType(); //checks if the wrong fsensor type is detected.
+#endif
+    void settings_init();
+};
+
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
+constexpr static uint16_t Voltage2Raw(float V) {
+    return (V * 1023 * OVERSAMPLENR / VOLT_DIV_REF ) + 0.5F;
+}
+constexpr static float Raw2Voltage(uint16_t raw) {
+    return VOLT_DIV_REF * (raw / (1023.F * OVERSAMPLENR));
+}
+
+class IR_sensor_analog: public IR_sensor {
+public:
+    void init();
+    bool update();
+    void voltUpdate(uint16_t raw);
+    
+    uint16_t __attribute__((noinline)) getVoltRaw();
+    
+    enum class SensorRevision : uint8_t {
+        _Old = 0,
+        _Rev04 = 1,
+        _Undef = EEPROM_EMPTY_VALUE
+    };
+    
+    SensorRevision getSensorRevision() const { return sensorRevision; }
+    
+    const char* __attribute__((noinline)) getIRVersionText();
+    
+    void setSensorRevision(SensorRevision rev, bool updateEEPROM = false);
+    
+    constexpr static uint16_t IRsensor_Ldiode_TRESHOLD = Voltage2Raw(0.3F); // ~0.3V, raw value=982
+    constexpr static uint16_t IRsensor_Lmax_TRESHOLD = Voltage2Raw(1.5F); // ~1.5V (0.3*Vcc), raw value=4910
+    constexpr static uint16_t IRsensor_Hmin_TRESHOLD = Voltage2Raw(3.0F); // ~3.0V (0.6*Vcc), raw value=9821
+    constexpr static uint16_t IRsensor_Hopen_TRESHOLD = Voltage2Raw(4.6F); // ~4.6V (N.C. @ Ru~20-50k, Rd'=56k, Ru'=10k), raw value=15059
+    constexpr static uint16_t IRsensor_VMax_TRESHOLD = Voltage2Raw(5.F); // ~5V, raw value=16368
+    
+private:
+    SensorRevision sensorRevision;
+    
+    bool voltReady; // set by the adc ISR, therefore avoid accessing the variable directly but use getVoltReady()
+    bool getVoltReady()const;
+    void clearVoltReady();
+    
+    uint16_t voltRaw; // set by the adc ISR, therefore avoid accessing the variable directly but use getVoltRaw()
+    bool checkVoltage(uint16_t raw);
+    
+    uint16_t minVolt = Voltage2Raw(6.F);
+    uint16_t maxVolt = 0;
+    uint16_t nFSCheckCount;
+    uint8_t voltageErrorCnt;
+
+    static constexpr uint16_t FS_CHECK_COUNT = 4;
+    /// Switching mechanism of the fsensor type.
+    /// Called from 2 spots which have a very similar behavior
+    /// 1: SensorRevision::_Old -> SensorRevision::_Rev04 and print _i("FS v0.4 or newer")
+    /// 2: SensorRevision::_Rev04 -> sensorRevision=SensorRevision::_Old and print _i("FS v0.3 or older")
+    void IR_ANALOG_Check(SensorRevision isVersion, SensorRevision switchTo);
+};
+#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
+#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
+
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+class PAT9125_sensor: public Filament_sensor {
+public:
+    void init();
+    void deinit();
+    bool update();
+    bool getFilamentPresent() const { return filterFilPresent; }
+#ifdef FSENSOR_PROBING
+    bool probeOtherType(); //checks if the wrong fsensor type is detected.
+#endif
+    
+    void setJamDetectionEnabled(bool state, bool updateEEPROM = false);
+    bool getJamDetectionEnabled() const { return jamDetection; }
+    
+    void stStep(bool rev) { //from stepper isr
+        stepCount += rev ? -1 : 1;
+    }
+    
+    void settings_init();
+private:
+    static constexpr uint16_t pollingPeriod = 10; //[ms]
+    static constexpr uint8_t filterCnt = 5; //how many checks need to be done in order to determine the filament presence precisely.
+    ShortTimer pollingTimer;
+    uint8_t filter;
+    uint8_t filterFilPresent;
+    
+    bool jamDetection;
+    int16_t oldPos;
+    int16_t stepCount;
+    int16_t chunkSteps;
+    uint8_t jamErrCnt;
+    
+    constexpr void calcChunkSteps(float u) {
+        chunkSteps = (int16_t)(1.25 * u); //[mm]
+    }
+    
+    int16_t getStepCount();
+    
+    void resetStepCount();
+    
+    void filJam();
+    
+    bool updatePAT9125();
+};
+#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+
+#if FILAMENT_SENSOR_TYPE == FSENSOR_IR
+extern IR_sensor fsensor;
+#elif FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG
+extern IR_sensor_analog fsensor;
+#elif FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125
+extern PAT9125_sensor fsensor;
+#endif
+
+#endif //FILAMENT_SENSOR

+ 24 - 50
Firmware/Marlin.h

@@ -21,6 +21,7 @@
 #include "Configuration.h"
 #include "pins.h"
 #include "Timer.h"
+#include "mmu2.h"
 extern uint8_t mbl_z_probe_nr;
 
 #ifndef AT90USB
@@ -220,9 +221,6 @@ void manage_inactivity(bool ignore_stepper_queue=false);
 #endif
 
 
-#define FARM_FILAMENT_COLOR_NONE 99;
-
-
 enum AxisEnum {X_AXIS=0, Y_AXIS=1, Z_AXIS=2, E_AXIS=3, X_HEAD=4, Y_HEAD=5};
 #define X_AXIS_MASK  1
 #define Y_AXIS_MASK  2
@@ -236,14 +234,13 @@ void FlushSerialRequestResend();
 void ClearToSend();
 void update_currents();
 
-void get_coordinates();
-void prepare_move();
 void kill(const char *full_screen_message = NULL, unsigned char id = 0);
 void finishAndDisableSteppers();
 
-void UnconditionalStop(); // Stop heaters, motion and clear current print status
-void Stop();              // Emergency stop used by overtemp functions which allows recovery
-bool IsStopped();         // Returns true if the print has been stopped
+void UnconditionalStop();                   // Stop heaters, motion and clear current print status
+void ThermalStop(bool allow_pause = false); // Emergency stop used by overtemp functions which allows
+                                            // recovery (with pause=true)
+bool IsStopped();                           // Returns true if the print has been stopped
 
 //put an ASCII command at the end of the current buffer, read from flash
 #define enquecommand_P(cmd) enquecommand(cmd, true)
@@ -251,31 +248,9 @@ bool IsStopped();         // Returns true if the print has been stopped
 //put an ASCII command at the begin of the current buffer, read from flash
 #define enquecommand_front_P(cmd) enquecommand_front(cmd, true)
 
-void prepare_arc_move(bool isclockwise);
 void clamp_to_software_endstops(float target[3]);
 void refresh_cmd_timeout(void);
 
-// Timer counter, incremented by the 1ms Arduino timer.
-// The standard Arduino timer() function returns this value atomically
-// by disabling / enabling interrupts. This is costly, if the interrupts are known
-// to be disabled.
-#ifdef SYSTEM_TIMER_2
-extern volatile unsigned long timer2_millis;
-#else //SYSTEM_TIMER_2
-extern volatile unsigned long timer0_millis;
-#endif //SYSTEM_TIMER_2
-
-// An unsynchronized equivalent to a standard Arduino _millis() function.
-// To be used inside an interrupt routine.
-
-FORCE_INLINE unsigned long millis_nc() { 
-#ifdef SYSTEM_TIMER_2
-	return timer2_millis;
-#else //SYSTEM_TIMER_2
-	return timer0_millis;
-#endif //SYSTEM_TIMER_2
-}
-
 #ifdef FAST_PWM_FAN
 void setPwmFrequency(uint8_t pin, int val);
 #endif
@@ -306,27 +281,25 @@ extern float max_pos[3];
 extern bool axis_known_position[3];
 extern int fanSpeed;
 extern uint8_t newFanSpeed;
-extern int8_t lcd_change_fil_state;
 extern float default_retraction;
 
+void get_coordinates();
+void prepare_move(uint16_t start_segment_idx = 0);
+void prepare_arc_move(bool isclockwise, uint16_t start_segment_idx = 0);
+uint16_t restore_interrupted_gcode();
+
 #ifdef TMC2130
 void homeaxis(uint8_t axis, uint8_t cnt = 1, uint8_t* pstep = 0);
 #else
 void homeaxis(uint8_t axis, uint8_t cnt = 1);
 #endif //TMC2130
 
-
-#ifdef FAN_SOFT_PWM
-extern unsigned char fanSpeedSoftPwm;
-#endif
-
 #ifdef FWRETRACT
 extern bool retracted[EXTRUDERS];
 extern float retract_length_swap;
 extern float retract_recover_length_swap;
 #endif
 
-
 extern uint8_t host_keepalive_interval;
 
 extern unsigned long starttime;
@@ -336,19 +309,15 @@ extern bool homing_flag;
 extern bool loading_flag;
 extern unsigned long total_filament_used;
 void save_statistics(unsigned long _total_filament_used, unsigned long _total_print_time);
-extern uint8_t status_number;
 extern uint8_t heating_status_counter;
-extern unsigned long PingTime;
-extern bool no_response;
-extern uint8_t important_status;
-extern uint8_t saved_filament_type;
 
 extern bool fan_state[2];
 extern int fan_edge_counter[2];
 extern int fan_speed[2];
 
-// Handling multiple extruders pins
-extern uint8_t active_extruder;
+// Active extruder becomes a #define to make the whole firmware compilable.
+// We may even remove the references to it wherever possible in the future
+#define active_extruder 0
 
 //Long pause
 extern unsigned long pause_time;
@@ -364,6 +333,10 @@ extern uint8_t saved_printing_type;
 #define PRINTING_TYPE_USB 1
 #define PRINTING_TYPE_NONE 2
 
+extern float saved_extruder_temperature; //!< Active extruder temperature
+extern float saved_bed_temperature; //!< Bed temperature
+extern int saved_fan_speed; //!< Print fan speed
+
 //save/restore printing in case that mmu is not responding
 extern bool mmu_print_saved;
 
@@ -383,7 +356,8 @@ extern uint16_t gcode_in_progress;
 extern LongTimer safetyTimer;
 
 #define PRINT_PERCENT_DONE_INIT 0xff
-#define PRINTER_ACTIVE (IS_SD_PRINTING || usb_timer.running() || isPrintPaused || (custom_message_type == CustomMsg::TempCal) || saved_printing || (lcd_commands_type == LcdCommands::Layer1Cal) || mmu_print_saved || homing_flag || mesh_bed_leveling_flag)
+
+extern bool printer_active();
 
 //! Beware - mcode_in_progress is set as soon as the command gets really processed,
 //! which is not the same as posting the M600 command into the command queue
@@ -440,6 +414,7 @@ extern void print_physical_coordinates();
 extern void print_mesh_bed_leveling_table();
 
 extern void stop_and_save_print_to_ram(float z_move, float e_move);
+void restore_extruder_temperature_from_ram();
 extern void restore_print_from_ram_and_continue(float e_move);
 extern void cancel_saved_printing();
 
@@ -467,6 +442,7 @@ extern uint8_t calc_percent_done();
 
 #define KEEPALIVE_STATE(n) do { busy_state = n;} while (0)
 extern void host_keepalive();
+extern void host_autoreport();
 //extern MarlinBusyState busy_state;
 extern int8_t busy_state;
 
@@ -487,12 +463,10 @@ void gcode_M114();
 #if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))))
 void gcode_M123();
 #endif //FANCHECK and TACH_0 and TACH_1
-void gcode_M701();
+void gcode_M701(float fastLoadLength, uint8_t mmuSlotIndex);
 
 #define UVLO !(PINE & (1<<4))
 
-void proc_commands();
-
 
 void M600_load_filament();
 void M600_load_filament_movements();
@@ -500,11 +474,11 @@ void M600_wait_for_user(float HotendTempBckp);
 void M600_check_state(float nozzle_temp);
 void load_filament_final_feed();
 void marlin_wait_for_click();
-void raise_z_above(float target, bool plan=true);
+float raise_z(float delta);
+void raise_z_above(float target);
 
 extern "C" void softReset();
 void stack_error();
-void pullup_error(bool fromTempISR);
 
 extern uint32_t IP_address;
 

+ 2 - 3
Firmware/MarlinSerial.h

@@ -37,11 +37,10 @@
 						
 // These are macros to build serial port register names for the selected SERIAL_PORT (C preprocessor
 // requires two levels of indirection to expand macro values properly)
-#define SERIAL_REGNAME(registerbase,number,suffix) SERIAL_REGNAME_INTERNAL(registerbase,number,suffix)
 #if SERIAL_PORT == 0 && (!defined(UBRR0H) || !defined(UDR0)) // use un-numbered registers if necessary
-#define SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) registerbase##suffix
+#define SERIAL_REGNAME(registerbase,number,suffix) _REGNAME_SHORT(registerbase, suffix)
 #else
-#define SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) registerbase##number##suffix
+#define SERIAL_REGNAME(registerbase,number,suffix) _REGNAME(registerbase, number, suffix)
 #endif
 
 // Registers used by MarlinSerial class (these are expanded 

+ 897 - 1117
Firmware/Marlin_main.cpp

@@ -72,6 +72,7 @@
 #include "planner.h"
 #include "stepper.h"
 #include "temperature.h"
+#include "fancheck.h"
 #include "motion_control.h"
 #include "cardreader.h"
 #include "ConfigurationStore.h"
@@ -80,12 +81,14 @@
 #include "math.h"
 #include "util.h"
 #include "Timer.h"
+#include "Prusa_farm.h"
 
 #include <avr/wdt.h>
 #include <avr/pgmspace.h>
 
+#include "Tcodes.h"
 #include "Dcodes.h"
-#include "AutoDeplete.h"
+#include "SpoolJoin.h"
 
 #ifndef LA_NOCOMPAT
 #include "la10compat.h"
@@ -93,12 +96,7 @@
 
 #include "spi.h"
 
-#ifdef FILAMENT_SENSOR
-#include "fsensor.h"
-#ifdef IR_SENSOR
-#include "pat9125.h" // for pat9125_probe
-#endif
-#endif //FILAMENT_SENSOR
+#include "Filament_sensor.h"
 
 #ifdef TMC2130
 #include "tmc2130.h"
@@ -128,7 +126,7 @@
 #include <SPI.h>
 #endif
 
-#include "mmu.h"
+#include "mmu2.h"
 
 #define VERSION_STRING  "1.0.2"
 
@@ -160,8 +158,6 @@
 CardReader card;
 #endif
 
-unsigned long PingTime = _millis();
-
 uint8_t mbl_z_probe_nr = 3; //numer of Z measurements for each point in mesh bed leveling calibration
 
 //used for PINDA temp calibration and pause print
@@ -193,8 +189,6 @@ int extruder_multiply[EXTRUDERS] = {100
 
 bool homing_flag = false;
 
-int8_t lcd_change_fil_state = 0;
-
 unsigned long pause_time = 0;
 unsigned long start_pause_print = _millis();
 unsigned long t_fan_rising_edge = _millis();
@@ -205,12 +199,6 @@ static LongTimer crashDetTimer;
 
 bool mesh_bed_leveling_flag = false;
 
-#ifdef PRUSA_M28
-bool prusa_sd_card_upload = false;
-#endif
-
-uint8_t status_number = 0;
-
 unsigned long total_filament_used;
 HeatingStatus heating_status;
 uint8_t heating_status_counter;
@@ -254,7 +242,6 @@ float extruder_offset[NUM_EXTRUDER_OFFSETS][EXTRUDERS] = {
 };
 #endif
 
-uint8_t active_extruder = 0;
 int fanSpeed=0;
 uint8_t newFanSpeed = 0;
 
@@ -296,20 +283,14 @@ const char errormagic[] PROGMEM = "Error:";
 const char echomagic[] PROGMEM = "echo:";
 const char G28W0[] PROGMEM = "G28 W0";
 
-bool no_response = false;
-uint8_t important_status;
-uint8_t saved_filament_type;
-
 // Define some coordinates outside the clamp limits (making them invalid past the parsing stage) so
 // that they can be used later for various logical checks
 #define X_COORD_INVALID (X_MIN_POS-1)
-#define Y_COORD_INVALID (Y_MIN_POS-1)
 
-#define SAVED_TARGET_UNSET X_COORD_INVALID
-float saved_target[NUM_AXIS] = {SAVED_TARGET_UNSET, 0, 0, 0};
+#define SAVED_START_POSITION_UNSET X_COORD_INVALID
+float saved_start_position[NUM_AXIS] = {SAVED_START_POSITION_UNSET, 0, 0, 0};
 
-// save/restore printing in case that mmu was not responding 
-bool mmu_print_saved = false;
+uint16_t saved_segment_idx = 0;
 
 // storing estimated time to end of print counted by slicer
 uint8_t print_percent_done_normal = PRINT_PERCENT_DONE_INIT;
@@ -378,10 +359,10 @@ uint8_t saved_printing_type = PRINTING_TYPE_SD;
 static float saved_pos[4] = { X_COORD_INVALID, 0, 0, 0 };
 static uint16_t saved_feedrate2 = 0; //!< Default feedrate (truncated from float)
 static int saved_feedmultiply2 = 0;
-static uint8_t saved_active_extruder = 0;
-static float saved_extruder_temperature = 0.0; //!< Active extruder temperature
+float saved_extruder_temperature = 0.0; //!< Active extruder temperature
+float saved_bed_temperature = 0.0;
 static bool saved_extruder_relative_mode = false;
-static int saved_fanSpeed = 0; //!< Print fan speed
+int saved_fan_speed = 0; //!< Print fan speed
 //! @}
 
 static int saved_feedmultiply_mm = 100;
@@ -443,7 +424,6 @@ AutoReportFeatures autoReportFeatures;
 //=============================Routines======================================
 //===========================================================================
 
-static void get_arc_coordinates();
 static bool setTargetedHotend(int code, uint8_t &extruder);
 static void print_time_remaining_init();
 static void wait_for_heater(long codenum, uint8_t extruder);
@@ -571,6 +551,17 @@ void servo_init()
   #endif
 }
 
+bool __attribute__((noinline)) printer_active() {
+    return IS_SD_PRINTING
+        || usb_timer.running()
+        || isPrintPaused
+        || (custom_message_type == CustomMsg::TempCal)
+        || saved_printing
+        || (lcd_commands_type == LcdCommands::Layer1Cal)
+        || MMU2::mmu2.MMU_PRINT_SAVED()
+        || homing_flag
+        || mesh_bed_leveling_flag;
+}
 
 bool fans_check_enabled = true;
 
@@ -635,7 +626,6 @@ void crashdet_detected(uint8_t mask)
     lcd_setstatus(msg);
 
 	gcode_G28(true, true, false); //home X and Y
-	st_synchronize();
 
 	if (automatic_recovery_after_crash) {
 		enquecommand_P(PSTR("CRASH_RECOVER"));
@@ -653,13 +643,12 @@ void crashdet_detected(uint8_t mask)
         lcd_set_cursor(0, 1);
         lcd_puts_P(_T(MSG_RESUME_PRINT));
         lcd_putc('?');
-        bool yesno = lcd_show_yes_no_and_wait(false);
-		lcd_update_enable(true);
-		if (yesno)
+        uint8_t yesno = lcd_show_yes_no_and_wait(false);
+		if (yesno == LCD_LEFT_BUTTON_CHOICE)
 		{
 			enquecommand_P(PSTR("CRASH_RECOVER"));
 		}
-		else
+		else // LCD_MIDDLE_BUTTON_CHOICE
 		{
 			enquecommand_P(PSTR("CRASH_CANCEL"));
 		}
@@ -694,9 +683,6 @@ void failstats_reset_print()
 	eeprom_update_byte((uint8_t *)EEPROM_POWER_COUNT, 0);
 	eeprom_update_byte((uint8_t *)EEPROM_MMU_FAIL, 0);
 	eeprom_update_byte((uint8_t *)EEPROM_MMU_LOAD_FAIL, 0);
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-    fsensor_softfail = 0;
-#endif
 }
 
 void softReset()
@@ -760,12 +746,15 @@ static void factory_reset(char level)
 		// Force the "Follow calibration flow" message at the next boot up.
 		calibration_status_store(CALIBRATION_STATUS_Z_CALIBRATION);
 		eeprom_write_byte((uint8_t*)EEPROM_WIZARD_ACTIVE, 2); //run wizard
-		farm_mode = false;
-		eeprom_update_byte((uint8_t*)EEPROM_FARM_MODE, farm_mode);
+		farm_disable();
 
 #ifdef FILAMENT_SENSOR
-		fsensor_enable();
-		fsensor_autoload_set(true);
+		fsensor.setEnabled(true);
+		fsensor.setAutoLoadEnabled(true, true);
+		fsensor.setRunoutEnabled(true, true);
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+		fsensor.setJamDetectionEnabled(true, true);
+#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
 #endif //FILAMENT_SENSOR
 		break;
 
@@ -798,7 +787,7 @@ int uart_putchar(char c, FILE *)
 void lcd_splash()
 {
 	lcd_clear(); // clears display and homes screen
-	lcd_puts_P(PSTR("\n Original Prusa i3\n   Prusa Research"));
+	lcd_printf_P(PSTR("\n Original Prusa i3\n   Prusa Research\n%20.20S"), PSTR(FW_VERSION));
 }
 
 
@@ -864,24 +853,14 @@ void show_fw_version_warnings() {
 	lcd_update_enable(true);
 }
 
+#if defined(FILAMENT_SENSOR) && defined(FSENSOR_PROBING)
 //! @brief try to check if firmware is on right type of printer
-static void check_if_fw_is_on_right_printer(){
-#ifdef FILAMENT_SENSOR
-  if((PRINTER_TYPE == PRINTER_MK3) || (PRINTER_TYPE == PRINTER_MK3S)){
-    #ifdef IR_SENSOR
-      if (pat9125_probe()){
-        lcd_show_fullscreen_message_and_wait_P(_i("MK3S firmware detected on MK3 printer"));}////MSG_MK3S_FIRMWARE_ON_MK3 c=20 r=4
-    #endif //IR_SENSOR
-
-    #ifdef PAT9125
-      //will return 1 only if IR can detect filament in bondtech extruder so this may fail even when we have IR sensor
-      const uint8_t ir_detected = !READ(IR_SENSOR_PIN);
-      if (ir_detected){
-        lcd_show_fullscreen_message_and_wait_P(_i("MK3 firmware detected on MK3S printer"));}////MSG_MK3_FIRMWARE_ON_MK3S c=20 r=4
-    #endif //PAT9125
-  }
-#endif //FILAMENT_SENSOR
+static void check_if_fw_is_on_right_printer() {
+    if (fsensor.probeOtherType()) {
+        lcd_show_fullscreen_message_and_wait_P(_i(PRINTER_NAME " firmware detected on " PRINTER_NAME_ALTERNATE " printer"));////c=20 r=4
+    }
 }
+#endif //defined(FILAMENT_SENSOR) && defined(FSENSOR_PROBING)
 
 uint8_t check_printer_version()
 {
@@ -1061,7 +1040,6 @@ void setup()
 {
 	timer2_init(); // enables functional millis
 
-	mmu_init();
 
 	ultralcd_init();
 
@@ -1095,25 +1073,7 @@ void setup()
 	setup_killpin();
 	setup_powerhold();
 
-	farm_mode = eeprom_read_byte((uint8_t*)EEPROM_FARM_MODE); 
-	if (farm_mode == 0xFF) {
-		farm_mode = false; //if farm_mode has not been stored to eeprom yet and farm number is set to zero or EEPROM is fresh, deactivate farm mode
-		eeprom_update_byte((uint8_t*)EEPROM_FARM_MODE, farm_mode);
-	} else if (farm_mode) {
-		no_response = true; //we need confirmation by recieving PRUSA thx
-		important_status = 8;
-		prusa_statistics(8);
-#ifdef HAS_SECOND_SERIAL_PORT
-		selectedSerialPort = 1;
-#endif //HAS_SECOND_SERIAL_PORT
-		MYSERIAL.begin(BAUDRATE);
-#ifdef FILAMENT_SENSOR
-		//disabled filament autoload (PFW360)
-		fsensor_autoload_set(false);
-#endif //FILAMENT_SENSOR
-		// ~ FanCheck -> on
-		eeprom_update_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED, true);
-	}
+    farm_mode_init();
 
 #ifdef TMC2130
     if( FarmOrUserECool() ){
@@ -1152,6 +1112,11 @@ void setup()
 	SERIAL_ECHO_START;
 	puts_P(PSTR(" " FW_VERSION_FULL));
 
+	if (eeprom_read_byte((uint8_t *)EEPROM_MMU_ENABLED)) {
+		MMU2::mmu2.Start();
+	}
+	SpoolJoin::spooljoin.initSpoolJoinStatus();
+
 	//SERIAL_ECHOPAIR("Active sheet before:", static_cast<unsigned long int>(eeprom_read_byte(&(EEPROM_Sheets_base->active_sheet))));
 
 #ifdef DEBUG_SEC_LANG
@@ -1272,9 +1237,15 @@ void setup()
 	else { //printer version was changed so use default settings 
 		Config_ResetDefault();
 	}
-	SdFatUtil::set_stack_guard(); //writes magic number at the end of static variables to protect against overwriting static memory by stack
 
-	tp_init();    // Initialize temperature loop
+    // writes a magic number at the end of static variables to monitor against incorrect overwriting
+    // of static memory by stack (this needs to be done before soft_pwm_init, since the check is
+    // performed inside the soft_pwm_isr)
+    SdFatUtil::set_stack_guard();
+
+    // Initialize pwm/temperature loops
+    soft_pwm_init();
+    temp_mgr_init();
 
 #ifdef EXTRUDER_ALTFAN_DETECT
 	SERIAL_ECHORPGM(_n("Extruder fan type: "));
@@ -1359,7 +1330,9 @@ void setup()
     
 	setup_photpin();
 
+#if 0
 	servo_init();
+#endif
 
 	// Reset the machine correction matrix.
 	// It does not make sense to load the correction matrix until the machine is homed.
@@ -1375,7 +1348,7 @@ void setup()
         xflash_err_msg();
 
 #ifdef FILAMENT_SENSOR
-	fsensor_init();
+    fsensor.init();
 #endif //FILAMENT_SENSOR
 
 
@@ -1389,11 +1362,9 @@ void setup()
     enable_z();
 #endif
 
-    if (farm_mode) {
-        // The farm monitoring SW may accidentally expect 
-        // 2 messages of "printer started" to consider a printer working.
-        prusa_statistics(8);
-    }
+    // The farm monitoring SW may accidentally expect 
+    // 2 messages of "printer started" to consider a printer working.
+    prusa_statistics(8);
 
 	// Enable Toshiba FlashAir SD card / WiFi enahanced card.
 	card.ToshibaFlashAir_enable(eeprom_read_byte((unsigned char*)EEPROM_TOSHIBA_FLASH_AIR_COMPATIBLITY) == 1);
@@ -1509,15 +1480,13 @@ void setup()
 	setup_fan_interrupt();
 #endif //DEBUG_DISABLE_FANCHECK
 
-#ifdef PAT9125
-	fsensor_setup_interrupt();
-#endif //PAT9125
-
 #ifndef DEBUG_DISABLE_STARTMSGS
   KEEPALIVE_STATE(PAUSED_FOR_USER);
 
   if (!farm_mode) {
+#if defined(FILAMENT_SENSOR) && defined(FSENSOR_PROBING)
     check_if_fw_is_on_right_printer();
+#endif //defined(FILAMENT_SENSOR) && defined(FSENSOR_PROBING)
     show_fw_version_warnings();    
   }
 
@@ -1613,7 +1582,7 @@ void setup()
 #ifdef UVLO_SUPPORT
   if (eeprom_read_byte((uint8_t*)EEPROM_UVLO) != 0) { //previous print was terminated by UVLO
 /*
-	  if (lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_RECOVER_PRINT), false))	recover_print();
+	  if (!lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_RECOVER_PRINT), false))	recover_print();
 	  else {
 		  eeprom_update_byte((uint8_t*)EEPROM_UVLO, 0);
 		  lcd_update_enable(true);
@@ -1635,8 +1604,9 @@ void setup()
           #ifdef DEBUG_UVLO_AUTOMATIC_RECOVER 
         puts_P(_N("Normal recovery!")); 
           #endif 
-          if ( lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_RECOVER_PRINT), false) ) recover_print(0); 
-          else { 
+          if ( lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_RECOVER_PRINT), false) == LCD_LEFT_BUTTON_CHOICE) {
+              recover_print(0); 
+          } else { 
               eeprom_update_byte((uint8_t*)EEPROM_UVLO, 0); 
               lcd_update_enable(true); 
               lcd_update(2); 
@@ -1651,7 +1621,6 @@ void setup()
 #endif //UVLO_SUPPORT
 
   fCheckModeInit();
-  fSetMmuMode(mmu_enabled);
   KEEPALIVE_STATE(NOT_BUSY);
 #ifdef WATCHDOG
   wdt_enable(WDTO_4S);
@@ -1692,89 +1661,12 @@ void stack_error() {
     crash_and_burn(dump_crash_reason::stack_error);
 }
 
-void pullup_error(bool fromTempISR) {
-    crash_and_burn(fromTempISR ? dump_crash_reason::bad_pullup_temp_isr : dump_crash_reason::bad_pullup_step_isr);
-}
-
-#ifdef PRUSA_M28
-void trace();
-
-#define CHUNK_SIZE 64 // bytes
-#define SAFETY_MARGIN 1
-char chunk[CHUNK_SIZE+SAFETY_MARGIN];
-
-void serial_read_stream() {
-
-    setAllTargetHotends(0);
-    setTargetBed(0);
-
-    lcd_clear();
-    lcd_puts_P(PSTR(" Upload in progress"));
-
-    // first wait for how many bytes we will receive
-    uint32_t bytesToReceive;
-
-    // receive the four bytes
-    char bytesToReceiveBuffer[4];
-    for (int i=0; i<4; i++) {
-        int data;
-        while ((data = MYSERIAL.read()) == -1) {};
-        bytesToReceiveBuffer[i] = data;
-
-    }
-
-    // make it a uint32
-    memcpy(&bytesToReceive, &bytesToReceiveBuffer, 4);
-
-    // we're ready, notify the sender
-    MYSERIAL.write('+');
-
-    // lock in the routine
-    uint32_t receivedBytes = 0;
-    while (prusa_sd_card_upload) {
-        int i;
-        for (i=0; i<CHUNK_SIZE; i++) {
-            int data;
-
-            // check if we're not done
-            if (receivedBytes == bytesToReceive) {
-                break;
-            }
-
-            // read the next byte
-            while ((data = MYSERIAL.read()) == -1) {};
-            receivedBytes++;
-
-            // save it to the chunk
-            chunk[i] = data;
-        }
-
-        // write the chunk to SD
-        card.write_command_no_newline(&chunk[0]);
-
-        // notify the sender we're ready for more data
-        MYSERIAL.write('+');
-
-        // for safety
-        manage_heater();
-
-        // check if we're done
-        if(receivedBytes == bytesToReceive) {
-            trace(); // beep
-            card.closefile();
-            prusa_sd_card_upload = false;
-            SERIAL_PROTOCOLLNRPGM(MSG_FILE_SAVED);
-        }
-    }
-}
-#endif //PRUSA_M28
-
 
 /**
  * Output autoreport values according to features requested in M155
  */
 #if defined(AUTO_REPORT)
-static void host_autoreport()
+void host_autoreport()
 {
     if (autoReportFeatures.TimerExpired())
     {
@@ -1834,7 +1726,17 @@ void host_keepalive() {
 // Before loop(), the setup() function is called by the main() routine.
 void loop()
 {
-	KEEPALIVE_STATE(NOT_BUSY);
+    // Reset a previously aborted command, we can now start processing motion again
+    planner_aborted = false;
+
+    if(Stopped) {
+        // Currently Stopped (possibly due to an error) and not accepting new serial commands.
+        // Signal to the host that we're currently busy waiting for supervision.
+        KEEPALIVE_STATE(PAUSED_FOR_USER);
+    } else {
+        // Printer is available for processing, reset state
+        KEEPALIVE_STATE(NOT_BUSY);
+    }
 
 	if (isPrintPaused && saved_printing_type == PRINTING_TYPE_USB) { //keep believing that usb is being printed. Prevents accessing dangerous menus while pausing.
 		usb_timer.start();
@@ -1843,13 +1745,6 @@ void loop()
 		;
 	}
     
-#ifdef FANCHECK
-    if (fan_check_error && isPrintPaused && !IS_SD_PRINTING) {
-        KEEPALIVE_STATE(PAUSED_FOR_USER);
-        host_keepalive(); //prevent timeouts since usb processing is disabled until print is resumed. This is for a crude way of pausing a print on all hosts.
-    }
-#endif
-
 #ifdef PRUSA_M28
     if (prusa_sd_card_upload)
     {
@@ -1896,7 +1791,7 @@ void loop()
       // The first character in the block is the block type.      
       char *ptr = cmdbuffer + bufindr;
       if (*ptr == CMDBUFFER_CURRENT_TYPE_SDCARD) {
-        // To support power panic, move the lenght of the command on the SD card to a planner buffer.
+        // To support power panic, move the length of the command on the SD card to a planner buffer.
         union {
           struct {
               char lo;
@@ -1940,7 +1835,7 @@ void loop()
 }
   //check heater every n milliseconds
   manage_heater();
-  isPrintPaused ? manage_inactivity(true) : manage_inactivity(false);
+  manage_inactivity(isPrintPaused);
   checkHitEndstops();
   lcd_update(0);
 #ifdef TMC2130
@@ -1958,7 +1853,7 @@ void loop()
 		}
 	}
 #endif //TMC2130
-	mmu_loop();
+	MMU2::mmu2.mmu_loop();
 }
 
 #define DEFINE_PGM_READ_ANY(type, reader)       \
@@ -2193,19 +2088,15 @@ bool check_commands() {
 	
 }
 
-
-// raise_z_above: slowly raise Z to the requested height
-//
-// contrarily to a simple move, this function will carefully plan a move
-// when the current Z position is unknown. In such cases, stallguard is
-// enabled and will prevent prolonged pushing against the Z tops
-void raise_z_above(float target, bool plan)
+/// @brief Safely move Z-axis by distance delta (mm)
+/// @param delta travel distance in mm
+/// @returns The actual travel distance in mm. Endstop may limit the requested move.
+float raise_z(float delta)
 {
-    if (current_position[Z_AXIS] >= target)
-        return;
+    float travel_z = current_position[Z_AXIS];
 
-    // Z needs raising
-    current_position[Z_AXIS] = target;
+    // Prepare to move Z axis
+    current_position[Z_AXIS] += delta;
 
 #if defined(Z_MIN_PIN) && (Z_MIN_PIN > -1) && !defined(DEBUG_DISABLE_ZMINLIMIT)
     bool z_min_endstop = (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING);
@@ -2216,31 +2107,54 @@ void raise_z_above(float target, bool plan)
     if (axis_known_position[Z_AXIS] || z_min_endstop)
     {
         // current position is known or very low, it's safe to raise Z
-        if(plan) plan_buffer_line_curposXYZE(max_feedrate[Z_AXIS]);
-        return;
-    }
+        clamp_to_software_endstops(current_position);
+        plan_buffer_line_curposXYZE(max_feedrate[Z_AXIS]);
+        st_synchronize();
 
-    // ensure Z is powered in normal mode to overcome initial load
-    enable_z();
-    st_synchronize();
+        // Get the final travel distance
+        travel_z = current_position[Z_AXIS] - travel_z;
+    } else {
+        // ensure Z is powered in normal mode to overcome initial load
+        enable_z();
+        st_synchronize();
 
-    // rely on crashguard to limit damage
-    bool z_endstop_enabled = enable_z_endstop(true);
+        // rely on crashguard to limit damage
+        bool z_endstop_enabled = enable_z_endstop(true);
 #ifdef TMC2130
-    tmc2130_home_enter(Z_AXIS_MASK);
+        tmc2130_home_enter(Z_AXIS_MASK);
 #endif //TMC2130
-    plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 60);
-    st_synchronize();
+        plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 60);
+        st_synchronize();
+
+        // Get the final travel distance
+        travel_z = st_get_position_mm(Z_AXIS) - travel_z;
 #ifdef TMC2130
-    if (endstop_z_hit_on_purpose())
-    {
-        // not necessarily exact, but will avoid further vertical moves
-        current_position[Z_AXIS] = max_pos[Z_AXIS];
-        plan_set_position_curposXYZE();
-    }
-    tmc2130_home_exit();
+        if (endstop_z_hit_on_purpose())
+        {
+            // not necessarily exact, but will avoid further vertical moves
+            current_position[Z_AXIS] = max_pos[Z_AXIS];
+            plan_set_position_curposXYZE();
+        }
+        tmc2130_home_exit();
 #endif //TMC2130
-    enable_z_endstop(z_endstop_enabled);
+        enable_z_endstop(z_endstop_enabled);
+    }
+
+    return travel_z;
+}
+
+// raise_z_above: slowly raise Z to the requested height
+//
+// contrarily to a simple move, this function will carefully plan a move
+// when the current Z position is unknown. In such cases, stallguard is
+// enabled and will prevent prolonged pushing against the Z tops
+void raise_z_above(float target)
+{
+    if (current_position[Z_AXIS] >= target)
+        return;
+
+    // Use absolute value in case the current position is unknown
+    raise_z(fabs(current_position[Z_AXIS] - target));
 }
 
 
@@ -2263,7 +2177,6 @@ bool calibrate_z_auto()
 	//	current_position[axis] = 0;
 	//	plan_set_position_curposXYZE();
 	tmc2130_home_exit();
-	enable_endstops(false);
 	current_position[Z_AXIS] = 0;
 	plan_set_position_curposXYZE();
 	set_destination_to_current();
@@ -2286,7 +2199,7 @@ bool calibrate_z_auto()
 #ifdef TMC2130
 static void check_Z_crash(void)
 {
-	if (READ(Z_TMC2130_DIAG) != 0) { //Z crash
+	if (!READ(Z_TMC2130_DIAG)) { //Z crash
 		FORCE_HIGH_POWER_END;
 		current_position[Z_AXIS] = 0;
 		plan_set_position_curposXYZE();
@@ -2496,96 +2409,7 @@ void retract(bool retracting, bool swapretract = false) {
 } //retract
 #endif //FWRETRACT
 
-#ifdef PRUSA_M28
-void trace() {
-    Sound_MakeCustom(25,440,true);
-}
-#endif
-
-/*
-void ramming() {
-//	  float tmp[4] = DEFAULT_MAX_FEEDRATE;
-	if (current_temperature[0] < 230) {
-		//PLA
-
-		max_feedrate[E_AXIS] = 50;
-		//current_position[E_AXIS] -= 8;
-		//plan_buffer_line_curposXYZE(2100 / 60, active_extruder);
-		//current_position[E_AXIS] += 8;
-		//plan_buffer_line_curposXYZE(2100 / 60, active_extruder);
-		current_position[E_AXIS] += 5.4;
-		plan_buffer_line_curposXYZE(2800 / 60, active_extruder);
-		current_position[E_AXIS] += 3.2;
-		plan_buffer_line_curposXYZE(3000 / 60, active_extruder);
-		current_position[E_AXIS] += 3;
-		plan_buffer_line_curposXYZE(3400 / 60, active_extruder);
-		st_synchronize();
-		max_feedrate[E_AXIS] = 80;
-		current_position[E_AXIS] -= 82;
-		plan_buffer_line_curposXYZE(9500 / 60, active_extruder);
-		max_feedrate[E_AXIS] = 50;//tmp[E_AXIS];
-		current_position[E_AXIS] -= 20;
-		plan_buffer_line_curposXYZE(1200 / 60, active_extruder);
-		current_position[E_AXIS] += 5;
-		plan_buffer_line_curposXYZE(400 / 60, active_extruder);
-		current_position[E_AXIS] += 5;
-		plan_buffer_line_curposXYZE(600 / 60, active_extruder);
-		current_position[E_AXIS] -= 10;
-		st_synchronize();
-		plan_buffer_line_curposXYZE(600 / 60, active_extruder);
-		current_position[E_AXIS] += 10;
-		plan_buffer_line_curposXYZE(600 / 60, active_extruder);
-		current_position[E_AXIS] -= 10;
-		plan_buffer_line_curposXYZE(800 / 60, active_extruder);
-		current_position[E_AXIS] += 10;
-		plan_buffer_line_curposXYZE(800 / 60, active_extruder);
-		current_position[E_AXIS] -= 10;
-		plan_buffer_line_curposXYZE(800 / 60, active_extruder);
-		st_synchronize();
-	}
-	else {
-		//ABS
-		max_feedrate[E_AXIS] = 50;
-		//current_position[E_AXIS] -= 8;
-		//plan_buffer_line_curposXYZE(2100 / 60, active_extruder);
-		//current_position[E_AXIS] += 8;
-		//plan_buffer_line_curposXYZE(2100 / 60, active_extruder);
-		current_position[E_AXIS] += 3.1;
-		plan_buffer_line_curposXYZE(2000 / 60, active_extruder);
-		current_position[E_AXIS] += 3.1;
-		plan_buffer_line_curposXYZE(2500 / 60, active_extruder);
-		current_position[E_AXIS] += 4;
-		plan_buffer_line_curposXYZE(3000 / 60, active_extruder);
-		st_synchronize();
-		//current_position[X_AXIS] += 23; //delay
-		//plan_buffer_line_curposXYZE(600/60, active_extruder); //delay
-		//current_position[X_AXIS] -= 23; //delay
-		//plan_buffer_line_curposXYZE(600/60, active_extruder); //delay
-		_delay(4700);
-		max_feedrate[E_AXIS] = 80;
-		current_position[E_AXIS] -= 92;
-		plan_buffer_line_curposXYZE(9900 / 60, active_extruder);
-		max_feedrate[E_AXIS] = 50;//tmp[E_AXIS];
-		current_position[E_AXIS] -= 5;
-		plan_buffer_line_curposXYZE(800 / 60, active_extruder);
-		current_position[E_AXIS] += 5;
-		plan_buffer_line_curposXYZE(400 / 60, active_extruder);
-		current_position[E_AXIS] -= 5;
-		plan_buffer_line_curposXYZE(600 / 60, active_extruder);
-		st_synchronize();
-		current_position[E_AXIS] += 5;
-		plan_buffer_line_curposXYZE(600 / 60, active_extruder);
-		current_position[E_AXIS] -= 5;
-		plan_buffer_line_curposXYZE(600 / 60, active_extruder);
-		current_position[E_AXIS] += 5;
-		plan_buffer_line_curposXYZE(600 / 60, active_extruder);
-		current_position[E_AXIS] -= 5;
-		plan_buffer_line_curposXYZE(600 / 60, active_extruder);
-		st_synchronize();
 
-	}
-  }
-*/
 
 #ifdef TMC2130
 void force_high_power_mode(bool start_high_power_section) {
@@ -2634,7 +2458,7 @@ void gcode_M105(uint8_t extruder)
     }
 #else
     SERIAL_ERROR_START;
-    SERIAL_ERRORLNRPGM(_i("No thermistors - no temperature"));////MSG_ERR_NO_THERMISTORS
+    SERIAL_ERRORLNRPGM(_n("No thermistors - no temperature"));////MSG_ERR_NO_THERMISTORS
 #endif
 
     SERIAL_PROTOCOLPGM(" @:");
@@ -2729,7 +2553,6 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon
 	//if we are homing all axes, first move z higher to protect heatbed/steel sheet
 	if (home_all_axes) {
         raise_z_above(MESH_HOME_Z_SEARCH);
-		st_synchronize();
 	}
 #ifdef ENABLE_AUTO_BED_LEVELING
       plan_bed_level_matrix.set_to_identity();  //Reset the plane ("erase" all leveling data)
@@ -2834,11 +2657,9 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon
           if(home_z) {
             #if defined (Z_RAISE_BEFORE_HOMING) && (Z_RAISE_BEFORE_HOMING > 0)
               raise_z_above(Z_RAISE_BEFORE_HOMING);
-              st_synchronize();
             #endif // defined (Z_RAISE_BEFORE_HOMING) && (Z_RAISE_BEFORE_HOMING > 0)
             #ifdef MESH_BED_LEVELING  // If Mesh bed leveling, move X&Y to safe position for home
               raise_z_above(MESH_HOME_Z_SEARCH);
-              st_synchronize();
               if (!axis_known_position[X_AXIS]) homeaxis(X_AXIS);
               if (!axis_known_position[Y_AXIS]) homeaxis(Y_AXIS);
               // 1st mesh bed leveling measurement point, corrected.
@@ -2956,7 +2777,7 @@ static void gcode_G28(bool home_x_axis, long home_x_value, bool home_y_axis, lon
     }
 #endif
 
-	  if (farm_mode) { prusa_statistics(20); };
+      prusa_statistics(20);
 
       st_synchronize();
 	  homing_flag = false;
@@ -2982,7 +2803,7 @@ static void gcode_G28(bool home_x_axis, bool home_y_axis, bool home_z_axis)
 static void gcode_G80()
 {
     st_synchronize();
-    if (waiting_inside_plan_buffer_line_print_aborted)
+    if (planner_aborted)
         return;
 
     mesh_bed_leveling_flag = true;
@@ -3076,7 +2897,7 @@ static void gcode_G80()
     plan_buffer_line_curposXYZE(XY_AXIS_FEEDRATE);
     // Wait until the move is finished.
     st_synchronize();
-    if (waiting_inside_plan_buffer_line_print_aborted)
+    if (planner_aborted)
     {
         custom_message_type = custom_message_type_old;
         custom_message_state = custom_message_state_old;
@@ -3151,7 +2972,7 @@ static void gcode_G80()
         //printf_P(PSTR("after clamping: [%f;%f]\n"), current_position[X_AXIS], current_position[Y_AXIS]);
         plan_buffer_line_curposXYZE(XY_AXIS_FEEDRATE);
         st_synchronize();
-        if (waiting_inside_plan_buffer_line_print_aborted)
+        if (planner_aborted)
         {
             custom_message_type = custom_message_type_old;
             custom_message_state = custom_message_state_old;
@@ -3238,9 +3059,7 @@ static void gcode_G80()
 #endif // TMC2130
             // ~ Z-homing (can not be used "G28", because X & Y-homing would have been done before (Z-homing))
             bState=enable_z_endstop(false);
-            current_position[Z_AXIS] -= 1;
-            plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40);
-            st_synchronize();
+            raise_z(-1);
             enable_z_endstop(true);
 #ifdef TMC2130
             tmc2130_home_enter(Z_AXIS_MASK);
@@ -3441,7 +3260,6 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level)
 	int l_feedmultiply = setup_for_endstop_move();
 	lcd_display_message_fullscreen_P(_T(MSG_AUTO_HOME));
   raise_z_above(MESH_HOME_Z_SEARCH);
-  st_synchronize();
 	home_xy();
 
 	enable_endstops(false);
@@ -3480,19 +3298,19 @@ bool gcode_M45(bool onlyZ, int8_t verbosity_level)
 		{
 			KEEPALIVE_STATE(PAUSED_FOR_USER);
 			#ifdef STEEL_SHEET
-			bool result = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_STEEL_SHEET_CHECK), false, false);
-			if(result) lcd_show_fullscreen_message_and_wait_P(_T(MSG_REMOVE_STEEL_SHEET));
+			uint8_t result = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_STEEL_SHEET_CHECK), false);
+			if(result == LCD_LEFT_BUTTON_CHOICE) {
+				lcd_show_fullscreen_message_and_wait_P(_T(MSG_REMOVE_STEEL_SHEET));
+			}
 			#endif //STEEL_SHEET
-		    lcd_show_fullscreen_message_and_wait_P(_T(MSG_PAPER));
+			lcd_show_fullscreen_message_and_wait_P(_T(MSG_PAPER));
 			KEEPALIVE_STATE(IN_HANDLER);
 			lcd_display_message_fullscreen_P(_T(MSG_FIND_BED_OFFSET_AND_SKEW_LINE1));
 			lcd_puts_at_P(0,3,_n("1/4"));
 		}
 			
 		bool endstops_enabled  = enable_endstops(false);
-        current_position[Z_AXIS] -= 1; //move 1mm down with disabled endstop
-        plan_buffer_line_curposXYZE(homing_feedrate[Z_AXIS] / 40);
-        st_synchronize();
+    raise_z(-1);
 
 		// Move the print head close to the bed.
 		current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
@@ -3635,221 +3453,254 @@ void gcode_M123()
 }
 #endif //FANCHECK and TACH_0 or TACH_1
 
-//! extracted code to compute z_shift for M600 in case of filament change operation 
-//! requested from fsensors.
-//! The function ensures, that the printhead lifts to at least 25mm above the heat bed
-//! unlike the previous implementation, which was adding 25mm even when the head was
-//! printing at e.g. 24mm height.
-//! A safety margin of FILAMENTCHANGE_ZADD is added in all cases to avoid touching
-//! the printout.
-//! This function is templated to enable fast change of computation data type.
-//! @return new z_shift value
-template<typename T>
-static T gcode_M600_filament_change_z_shift()
-{
-#ifdef FILAMENTCHANGE_ZADD
-	static_assert(Z_MAX_POS < (255 - FILAMENTCHANGE_ZADD), "Z-range too high, change the T type from uint8_t to uint16_t");
-	// avoid floating point arithmetics when not necessary - results in shorter code
-	T z_shift = T(FILAMENTCHANGE_ZADD); // always move above printout
-	T ztmp = T( current_position[Z_AXIS] );
-	if((ztmp + z_shift) < T(MIN_Z_FOR_SWAP)){
-		z_shift = T(MIN_Z_FOR_SWAP) - ztmp; // make sure to be at least 25mm above the heat bed
-	}
-	return z_shift;
-#else
-	return T(0);
-#endif
-}	
+static void mmu_M600_wait_and_beep() {
+    // Beep and wait for user to remove old filament and prepare new filament for load
+    KEEPALIVE_STATE(PAUSED_FOR_USER);
 
-static void gcode_M600(bool automatic, float x_position, float y_position, float z_shift, float e_shift, float /*e_shift_late*/)
-{
+    int counterBeep = 0;
+    lcd_display_message_fullscreen_P(_i("Remove old filament and press the knob to start loading new filament.")); ////MSG_REMOVE_OLD_FILAMENT c=20 r=5
+    bool bFirst = true;
+
+    while (!lcd_clicked()) {
+        manage_heater();
+        manage_inactivity(true);
+
+#if BEEPER > 0
+        if (counterBeep == 500) {
+            counterBeep = 0;
+        }
+        SET_OUTPUT(BEEPER);
+        if (counterBeep == 0) {
+            if ((eSoundMode == e_SOUND_MODE_BLIND) || (eSoundMode == e_SOUND_MODE_LOUD) || ((eSoundMode == e_SOUND_MODE_ONCE) && bFirst)) {
+                bFirst = false;
+                WRITE(BEEPER, HIGH);
+            }
+        }
+        if (counterBeep == 20) {
+            WRITE(BEEPER, LOW);
+        }
+
+        counterBeep++;
+#endif // BEEPER > 0
+
+        delay_keep_alive(4);
+    }
+    WRITE(BEEPER, LOW);
+}
+
+/**
+ * @brief Handling of unload when using MMU with M600
+ * A fullscreen message showing "Unloading Filament x"
+ * should be shown on the LCD and LCD updates should be
+ * are disabled in the meantime.
+ */ 
+static void mmu_M600_unload_filament() {
+    if (MMU2::mmu2.get_current_tool() == (uint8_t)MMU2::FILAMENT_UNKNOWN) return;
+
+    lcd_update_enable(false);
+    lcd_clear();
+    lcd_puts_at_P(0, 1, _T(MSG_UNLOADING_FILAMENT));
+    lcd_print(' ');
+    lcd_print(MMU2::mmu2.get_current_tool() + 1);
+
+    // unload just current filament for multimaterial printers (used also in M702)
+    MMU2::mmu2.unload();
+    lcd_update_enable(true);
+}
+
+/// @brief load filament for mmu v2
+/// @par nozzle_temp nozzle temperature to load filament
+static void mmu_M600_load_filament(bool automatic, float nozzle_temp) {
+    uint8_t slot;
+    if (automatic) {
+        slot = SpoolJoin::spooljoin.nextSlot();
+    } else {
+        // Only ask for the slot if automatic/SpoolJoin is off
+        slot = choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER));
+    }
+
+    setTargetHotend(nozzle_temp, active_extruder);
+
+    MMU2::mmu2.load_filament_to_nozzle(slot);
+
+    load_filament_final_feed(); // @@TODO verify
+    st_synchronize();
+}
+
+static void gcode_M600(bool automatic, float x_position, float y_position, float z_shift, float e_shift, float /*e_shift_late*/) {
     st_synchronize();
     float lastpos[4];
 
-    if (farm_mode)
-    {
         prusa_statistics(22);
-    }
-
+    
     //First backup current position and settings
     int feedmultiplyBckp = feedmultiply;
     float HotendTempBckp = degTargetHotend(active_extruder);
     int fanSpeedBckp = fanSpeed;
 
-    lastpos[X_AXIS] = current_position[X_AXIS];
-    lastpos[Y_AXIS] = current_position[Y_AXIS];
-    lastpos[Z_AXIS] = current_position[Z_AXIS];
-    lastpos[E_AXIS] = current_position[E_AXIS];
+    memcpy(lastpos, current_position, sizeof(lastpos));
 
-    //Retract E
+    // Retract E
     current_position[E_AXIS] += e_shift;
     plan_buffer_line_curposXYZE(FILAMENTCHANGE_RFEED);
     st_synchronize();
 
-    //Lift Z
-    current_position[Z_AXIS] += z_shift;
-    clamp_to_software_endstops(current_position);
-    plan_buffer_line_curposXYZE(FILAMENTCHANGE_ZFEED);
-    st_synchronize();
+    // Raise the Z axis
+    raise_z(z_shift);
 
-    //Move XY to side
+    // Move XY to side
     current_position[X_AXIS] = x_position;
     current_position[Y_AXIS] = y_position;
     plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED);
     st_synchronize();
 
-    //Beep, manage nozzle heater and wait for user to start unload filament
-    if(!mmu_enabled) M600_wait_for_user(HotendTempBckp);
-
-    lcd_change_fil_state = 0;
+    // Beep, manage nozzle heater and wait for user to start unload filament
+    if (!MMU2::mmu2.Enabled())
+        M600_wait_for_user(HotendTempBckp);
 
     // Unload filament
-    if (mmu_enabled) extr_unload();	//unload just current filament for multimaterial printers (used also in M702)
-    else unload_filament(true); //unload filament for single material (used also in M702)
-    //finish moves
-    st_synchronize();
-
-    if (!mmu_enabled)
+    if (MMU2::mmu2.Enabled())
+        mmu_M600_unload_filament();
+    else
+        unload_filament(FILAMENTCHANGE_FINALRETRACT, true); // unload filament for single material (used also in M702)
+    st_synchronize();          // finish moves
     {
-        KEEPALIVE_STATE(PAUSED_FOR_USER);
-        lcd_change_fil_state = lcd_show_fullscreen_message_yes_no_and_wait_P(
-                _i("Was filament unload successful?"), ////MSG_UNLOAD_SUCCESSFUL c=20 r=2
-                false, true);
-        if (lcd_change_fil_state == 0)
+        FSensorBlockRunout fsBlockRunout;
+        
+        if (!MMU2::mmu2.Enabled())
         {
-			lcd_clear();
-			lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT));
-			current_position[X_AXIS] -= 100;
-			plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED);
-			st_synchronize();
-			lcd_show_fullscreen_message_and_wait_P(_i("Please open idler and remove filament manually."));////MSG_CHECK_IDLER c=20 r=5
-        }
-    }
-
-    if (mmu_enabled)
-    {
-        if (!automatic) {
-            if (saved_printing) mmu_eject_filament(mmu_extruder, false); //if M600 was invoked by filament senzor (FINDA) eject filament so user can easily remove it
-            mmu_M600_wait_and_beep();
-            if (saved_printing) {
-
+            KEEPALIVE_STATE(PAUSED_FOR_USER);
+            uint8_t choice =
+                lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Was filament unload successful?"), false, LCD_LEFT_BUTTON_CHOICE); ////MSG_UNLOAD_SUCCESSFUL c=20 r=2
+            if (choice == LCD_MIDDLE_BUTTON_CHOICE) {
                 lcd_clear();
                 lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT));
-
-                mmu_command(MmuCmd::R0);
-                manage_response(false, false);
+                current_position[X_AXIS] -= 100;
+                plan_buffer_line_curposXYZE(FILAMENTCHANGE_XYFEED);
+                st_synchronize();
+                lcd_show_fullscreen_message_and_wait_P(_i("Please open idler and remove filament manually.")); ////MSG_CHECK_IDLER c=20 r=5
             }
+            M600_load_filament();
+        }
+        else // MMU is enabled
+        {
+            if (!automatic) {
+                if (saved_printing){
+                    // if M600 was invoked by filament senzor (FINDA) eject filament so user can easily remove it
+                    MMU2::mmu2.eject_filament(MMU2::mmu2.get_current_tool(), false);
+                }
+                mmu_M600_wait_and_beep();
+                if (saved_printing) {
+                    lcd_clear();
+                    lcd_puts_at_P(0, 2, _T(MSG_PLEASE_WAIT));
+//@@TODO                mmu_command(MmuCmd::R0);
+//                manage_response(false, false);
+                }
+            }
+            mmu_M600_load_filament(automatic, HotendTempBckp);
+        }
+        if (!automatic)
+            M600_check_state(HotendTempBckp);
+    
+        lcd_update_enable(true);
+    
+        // Not let's go back to print
+        fanSpeed = fanSpeedBckp;
+    
+        // Feed a little of filament to stabilize pressure
+        if (!automatic) {
+            current_position[E_AXIS] += FILAMENTCHANGE_RECFEED;
+            plan_buffer_line_curposXYZE(FILAMENTCHANGE_EXFEED);
         }
-        mmu_M600_load_filament(automatic, HotendTempBckp);
-    }
-    else
-        M600_load_filament();
-
-    if (!automatic) M600_check_state(HotendTempBckp);
 
-		lcd_update_enable(true);
+        // Move XY back
+        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], FILAMENTCHANGE_XYFEED, active_extruder);
+        st_synchronize();
 
-    //Not let's go back to print
-    fanSpeed = fanSpeedBckp;
+        // Move Z back
+        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], current_position[E_AXIS], FILAMENTCHANGE_ZFEED, active_extruder);
+        st_synchronize();
 
-    //Feed a little of filament to stabilize pressure
-    if (!automatic)
-    {
-        current_position[E_AXIS] += FILAMENTCHANGE_RECFEED;
-        plan_buffer_line_curposXYZE(FILAMENTCHANGE_EXFEED);
+        // Set E position to original
+        plan_set_e_position(lastpos[E_AXIS]);
+    
+        memcpy(current_position, lastpos, sizeof(lastpos));
+        set_destination_to_current();
+    
+        // Recover feed rate
+        feedmultiply = feedmultiplyBckp;
+        char cmd[9];
+        sprintf_P(cmd, PSTR("M220 S%i"), feedmultiplyBckp);
+        enquecommand(cmd);
+        
     }
-
-    //Move XY back
-    plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS],
-            FILAMENTCHANGE_XYFEED, active_extruder);
-    st_synchronize();
-    //Move Z back
-    plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], current_position[E_AXIS],
-            FILAMENTCHANGE_ZFEED, active_extruder);
-    st_synchronize();
-
-    //Set E position to original
-    plan_set_e_position(lastpos[E_AXIS]);
-
-    memcpy(current_position, lastpos, sizeof(lastpos));
-    set_destination_to_current();
-
-    //Recover feed rate
-    feedmultiply = feedmultiplyBckp;
-    char cmd[9];
-    sprintf_P(cmd, PSTR("M220 S%i"), feedmultiplyBckp);
-    enquecommand(cmd);
-
-#ifdef IR_SENSOR
-	//this will set fsensor_watch_autoload to correct value and prevent possible M701 gcode enqueuing when M600 is finished
-	fsensor_check_autoload();
-#endif //IR_SENSOR
-
+    
     lcd_setstatuspgm(MSG_WELCOME);
     custom_message_type = CustomMsg::Status;
 }
 
-void gcode_M701()
-{
-	printf_P(PSTR("gcode_M701 begin\n"));
-
-	if (farm_mode)
-	{
-		prusa_statistics(22);
-	}
-
-	if (mmu_enabled) 
-	{
-		extr_adj(tmp_extruder);//loads current extruder
-		mmu_extruder = tmp_extruder;
-	}
-	else
-	{
-		enable_z();
-		custom_message_type = CustomMsg::FilamentLoading;
+void gcode_M701(float fastLoadLength, uint8_t mmuSlotIndex){
+    FSensorBlockRunout fsBlockRunout;
+    
+    prusa_statistics(22);
 
-#ifdef FSENSOR_QUALITY
-		fsensor_oq_meassure_start(40);
-#endif //FSENSOR_QUALITY
+    if (MMU2::mmu2.Enabled() && mmuSlotIndex < MMU_FILAMENT_COUNT) {
+        MMU2::mmu2.load_filament_to_nozzle(mmuSlotIndex);
+    } else {
+        custom_message_type = CustomMsg::FilamentLoading;
+        lcd_setstatuspgm(_T(MSG_LOADING_FILAMENT));
 
-		lcd_setstatuspgm(_T(MSG_LOADING_FILAMENT));
-		current_position[E_AXIS] += 40;
-		plan_buffer_line_curposXYZE(400 / 60); //fast sequence
-		st_synchronize();
+        current_position[E_AXIS] += fastLoadLength;
+        plan_buffer_line_curposXYZE(FILAMENTCHANGE_EFEED_FIRST); //fast sequence
 
-        raise_z_above(MIN_Z_FOR_LOAD, false);
-		current_position[E_AXIS] += 30;
-		plan_buffer_line_curposXYZE(400 / 60); //fast sequence
-		
-		load_filament_final_feed(); //slow sequence
-		st_synchronize();
+        load_filament_final_feed(); // slow sequence
+        st_synchronize();
 
-    Sound_MakeCustom(50,500,false);
+        Sound_MakeCustom(50, 500, false);
 
-		if (!farm_mode && loading_flag) {
-			lcd_load_filament_color_check();
-		}
-		lcd_update_enable(true);
-		lcd_update(2);
-		lcd_setstatuspgm(MSG_WELCOME);
-		disable_z();
-		loading_flag = false;
-		custom_message_type = CustomMsg::Status;
+        if (!farm_mode && loading_flag) {
+            lcd_load_filament_color_check();
+        }
+        lcd_update_enable(true);
+        lcd_update(2);
+        lcd_setstatuspgm(MSG_WELCOME);
+        loading_flag = false;
+        custom_message_type = CustomMsg::Status;
+    }
 
-#ifdef FSENSOR_QUALITY
-        fsensor_oq_meassure_stop();
+    eFilamentAction = FilamentAction::None;
+}
 
-        if (!fsensor_oq_result())
-        {
-            bool disable = lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Fil. sensor response is poor, disable it?"), false, true);
-            lcd_update_enable(true);
-            lcd_update(2);
-            if (disable)
-                fsensor_disable();
+// Common gcode shared by the gcodes. This saves some flash memory
+static void gcodes_M704_M705_M706(uint16_t gcode)
+{
+    uint8_t mmuSlotIndex = 0xffU;
+    if (MMU2::mmu2.Enabled() && code_seen('P'))
+    {
+        mmuSlotIndex = code_value_uint8();
+        if (mmuSlotIndex < MMU_FILAMENT_COUNT) {
+            switch (gcode)
+            {
+            case 704:
+                MMU2::mmu2.load_filament(mmuSlotIndex);
+                break;
+            case 705:
+                MMU2::mmu2.eject_filament(mmuSlotIndex, false);
+                break;
+            case 706:
+#ifdef MMU_HAS_CUTTER
+                if (eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED) != 0){
+                    MMU2::mmu2.cut_filament(mmuSlotIndex);
+                }
+#endif // MMU_HAS_CUTTER
+                break;
+            default:
+                break;
+            }
         }
-#endif //FSENSOR_QUALITY
-	}
+    }
 }
+
 /**
  * @brief Get serial number from 32U2 processor
  *
@@ -4135,6 +3986,7 @@ extern uint8_t st_backlash_y;
 //!@n M302 - Allow cold extrudes, or set the minimum extrude S<temperature>.
 //!@n M303 - PID relay autotune S<temperature> sets the target temperature. (default target temperature = 150C)
 //!@n M304 - Set bed PID parameters P I and D
+//!@n M310 - Temperature model settings
 //!@n M400 - Finish all moves
 //!@n M401 - Lower z-probe if present
 //!@n M402 - Raise z-probe if present
@@ -4176,16 +4028,6 @@ There are reasons why some G Codes aren't in numerical order.
 
 void process_commands()
 {
-#ifdef FANCHECK
-    if(fan_check_error == EFCE_DETECTED) {
-        fan_check_error = EFCE_REPORTED;
-        if (usb_timer.running())
-            lcd_pause_usb_print();
-        else
-            lcd_pause_print();
-    }
-#endif
-
 	if (!buflen) return; //empty command
 
 #ifdef CMDBUFFER_DEBUG
@@ -4248,7 +4090,7 @@ void process_commands()
     */
 
     else if (code_seen_P(PSTR("M0")) || code_seen_P(PSTR("M1 "))) {// M0 and M1 - (Un)conditional stop - Wait for user button press on LCD
-        char *src = strchr_pointer + 2;
+        const char *src = strchr_pointer + 2;
         codenum = 0;
         bool hasP = false, hasS = false;
         if (code_seen('P')) {
@@ -4411,13 +4253,11 @@ void process_commands()
     
     Set of internal PRUSA commands
     #### Usage
-         PRUSA [ Ping | PRN | FAN | fn | thx | uvlo | MMURES | RESET | fv | M28 | SN | Fir | Rev | Lang | Lz | FR ]
+         PRUSA [ PRN | FAN | thx | uvlo | MMURES | RESET | fv | M28 | SN | Fir | Rev | Lang | Lz | FR ]
     
     #### Parameters
-      - `Ping` 
       - `PRN` - Prints revision of the printer
       - `FAN` - Prints fan details
-      - `fn` - Prints farm no.
       - `thx` 
       - `uvlo` 
       - `MMURES` - Reset MMU
@@ -4435,32 +4275,20 @@ void process_commands()
       - `nozzle` - prints nozzle diameter (farm mode only), works like M862.1 P, e.g. `PRUSA nozzle`
     */
 
-
-		if (code_seen_P(PSTR("Ping"))) {  // PRUSA Ping
-			if (farm_mode) {
-				PingTime = _millis();
-			}	  
-		}
-		else if (code_seen_P(PSTR("PRN"))) { // PRUSA PRN
-		  printf_P(_N("%u"), status_number);
-
-        } else if( code_seen_P(PSTR("FANPINTST"))){
+        if (farm_prusa_code_seen()) {}
+        else if(code_seen_P(PSTR("FANPINTST"))) {
             gcode_PRUSA_BadRAMBoFanTest();
-        }else if (code_seen_P(PSTR("FAN"))) { // PRUSA FAN
-			printf_P(_N("E0:%d RPM\nPRN0:%d RPM\n"), 60*fan_speed[0], 60*fan_speed[1]);
-		}
-		else if (code_seen_P(PSTR("thx"))) // PRUSA thx
-		{
-			no_response = false;
-		}	
-		else if (code_seen_P(PSTR("uvlo"))) // PRUSA uvlo
-		{
-               eeprom_update_byte((uint8_t*)EEPROM_UVLO,0); 
-               enquecommand_P(PSTR("M24")); 
-		}	
+        }
+        else if (code_seen_P(PSTR("FAN"))) { // PRUSA FAN
+            printf_P(_N("E0:%d RPM\nPRN0:%d RPM\n"), 60*fan_speed[0], 60*fan_speed[1]);
+        }
+        else if (code_seen_P(PSTR("uvlo"))) { // PRUSA uvlo
+            eeprom_update_byte((uint8_t*)EEPROM_UVLO,0); 
+            enquecommand_P(PSTR("M24")); 
+        }
 		else if (code_seen_P(PSTR("MMURES"))) // PRUSA MMURES
 		{
-			mmu_reset();
+			MMU2::mmu2.Reset(MMU2::MMU2::Software);
 		}
 		else if (code_seen_P(PSTR("RESET"))) { // PRUSA RESET
 #ifdef WATCHDOG
@@ -4472,30 +4300,7 @@ void process_commands()
 #elif defined(BOOTAPP) //this is a safety precaution. This is because the new bootloader turns off the heaters, but the old one doesn't. The watchdog should be used most of the time.
             asm volatile("jmp 0x3E000");
 #endif
-        } else if (code_seen_P(PSTR("fv"))) { // PRUSA fv
-        // get file version
-        #ifdef SDSUPPORT
-        card.openFileReadFilteredGcode(strchr_pointer + 3,true);
-        while (true) {
-            uint16_t readByte = card.getFilteredGcodeChar();
-            MYSERIAL.write(readByte);
-            if (readByte=='\n') {
-                break;
-            }
         }
-        card.closefile();
-
-        #endif // SDSUPPORT
-
-    }
-#ifdef PRUSA_M28
-	else if (code_seen_P(PSTR("M28"))) { // PRUSA M28
-        trace();
-        prusa_sd_card_upload = true;
-        card.openFileWrite(strchr_pointer+4);
-
-	}
-#endif //PRUSA_M28
 #ifdef PRUSA_SN_SUPPORT
 	else if (code_seen_P(PSTR("SN"))) { // PRUSA SN
         char SN[20];
@@ -4508,11 +4313,11 @@ void process_commands()
 #endif //PRUSA_SN_SUPPORT
     else if(code_seen_P(PSTR("Fir"))){ // PRUSA Fir
 
-      SERIAL_PROTOCOLLN(FW_VERSION_FULL);
+      SERIAL_PROTOCOLLNPGM(FW_VERSION_FULL);
 
     } else if(code_seen_P(PSTR("Rev"))){ // PRUSA Rev
 
-      SERIAL_PROTOCOLLN(FILAMENT_SIZE "-" ELECTRONICS "-" NOZZLE_TYPE );
+      SERIAL_PROTOCOLLNPGM(FILAMENT_SIZE "-" ELECTRONICS "-" NOZZLE_TYPE );
 
     } else if(code_seen_P(PSTR("Lang"))) { // PRUSA Lang
 	  lang_reset();
@@ -4639,19 +4444,9 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
     */
     case 0: // G0 -> G1
     case 1: // G1
-      if(Stopped == false) {
-            get_coordinates(); // For X Y Z E F
-
-            // When recovering from a previous print move, restore the originally
-            // calculated target position on the first USB/SD command. This accounts
-            // properly for relative moves
-            if ((saved_target[0] != SAVED_TARGET_UNSET) &&
-                ((CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_SDCARD) ||
-                 (CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR)))
-            {
-                memcpy(destination, saved_target, sizeof(destination));
-                saved_target[0] = SAVED_TARGET_UNSET;
-            }
+        {
+        uint16_t start_segment_idx = restore_interrupted_gcode();
+        get_coordinates(); // For X Y Z E F
 
 		if (total_filament_used > ((current_position[E_AXIS] - destination[E_AXIS]) * 100)) { //protection against total_filament_used overflow
 			total_filament_used = total_filament_used + ((destination[E_AXIS] - current_position[E_AXIS]) * 100);
@@ -4672,7 +4467,7 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
         }
 #endif //FWRETRACT
 
-        prepare_move();
+        prepare_move(start_segment_idx);
         //ClearToSend();
       }
       break;
@@ -4697,21 +4492,24 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
 	  - `F` - The feedrate per minute of the move between the starting point and ending point (if supplied)
 	
     */
-    case 2: 
-      if(Stopped == false) {
-        get_arc_coordinates();
-        prepare_arc_move(true);
-      }
-      break;
- 
-    // -------------------------------
-    case 3: 
-      if(Stopped == false) {
-        get_arc_coordinates();
-        prepare_arc_move(false);
-      }
-      break;
+    case 2:
+    case 3:
+    {
+        uint16_t start_segment_idx = restore_interrupted_gcode();
+#ifdef SF_ARC_FIX
+        bool relative_mode_backup = relative_mode;
+        relative_mode = true;
+#endif
+        get_coordinates(); // For X Y Z E F
+#ifdef SF_ARC_FIX
+        relative_mode=relative_mode_backup;
+#endif
 
+        offset[0] = code_seen('I') ? code_value() : 0.f;
+        offset[1] = code_seen('J') ? code_value() : 0.f;
+        
+        prepare_arc_move((gcode_in_progress == 2), start_segment_idx);
+    } break;
 
     /*!
 	### G4 - Dwell <a href="https://reprap.org/wiki/G-code#G4:_Dwell">G4: Dwell</a>
@@ -4806,11 +4604,11 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
       long home_z_value = 0;
       // Which axes should be homed?
       bool home_x = code_seen(axis_codes[X_AXIS]);
-      home_x_value = code_value_long();
+      if (home_x) home_x_value = code_value_long();
       bool home_y = code_seen(axis_codes[Y_AXIS]);
-      home_y_value = code_value_long();
+      if (home_y) home_y_value = code_value_long();
       bool home_z = code_seen(axis_codes[Z_AXIS]);
-      home_z_value = code_value_long();
+      if (home_z) home_z_value = code_value_long();
       bool without_mbl = code_seen('W');
       // calibrate?
 #ifdef TMC2130
@@ -5110,9 +4908,9 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
             break;
         }
         lcd_show_fullscreen_message_and_wait_P(_i("Stable ambient temperature 21-26C is needed a rigid stand is required."));////MSG_TEMP_CAL_WARNING c=20 r=4
-        bool result = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_STEEL_SHEET_CHECK), false, false);
+        uint8_t result = lcd_show_fullscreen_message_yes_no_and_wait_P(_T(MSG_STEEL_SHEET_CHECK), false);
 
-        if (result)
+        if (result == LCD_LEFT_BUTTON_CHOICE)
         {
             current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
             plan_buffer_line_curposXYZE(3000 / 60);
@@ -5545,30 +5343,23 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
     }
     break;
 
+#ifdef PRUSA_FARM
     /*!
     ### G98 - Activate farm mode <a href="https://reprap.org/wiki/G-code#G98:_Activate_farm_mode">G98: Activate farm mode</a>
-	Enable Prusa-specific Farm functions and g-code.
+    Enable Prusa-specific Farm functions and g-code.
     See Internal Prusa commands.
     */
-	case 98:
-		farm_mode = 1;
-		PingTime = _millis();
-		eeprom_update_byte((unsigned char *)EEPROM_FARM_MODE, farm_mode);
-		SilentModeMenu = SILENT_MODE_OFF;
-		eeprom_update_byte((unsigned char *)EEPROM_SILENT, SilentModeMenu);
-		fCheckModeInit();                       // alternatively invoke printer reset
-		break;
+    case 98:
+        farm_gcode_g98();
+        break;
 
     /*! ### G99 - Deactivate farm mode <a href="https://reprap.org/wiki/G-code#G99:_Deactivate_farm_mode">G99: Deactivate farm mode</a>
- 	Disables Prusa-specific Farm functions and g-code.
-   */
-	case 99:
-		farm_mode = 0;
-		lcd_printer_connected();
-		eeprom_update_byte((unsigned char *)EEPROM_FARM_MODE, farm_mode);
-		lcd_update(2);
-          fCheckModeInit();                       // alternatively invoke printer reset
-		break;
+    Disables Prusa-specific Farm functions and g-code.
+    */
+    case 99:
+        farm_gcode_g99();
+        break;
+#endif //PRUSA_FARM
 	default:
 		printf_P(MSG_UNKNOWN_CODE, 'G', cmdbuffer + bufindr + CMDHDRSIZE);
     }
@@ -5667,23 +5458,32 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
 	### M24 - Start SD print <a href="https://reprap.org/wiki/G-code#M24:_Start.2Fresume_SD_print">M24: Start/resume SD print</a>
     */
     case 24:
-	  if (isPrintPaused)
-          lcd_resume_print();
-      else
+    if (isPrintPaused)
+      lcd_resume_print();
+    else
+    {
+      if (!card.get_sdpos())
       {
-          if (!card.get_sdpos())
-          {
               // A new print has started from scratch, reset stats
               failstats_reset_print();
+              sdpos_atomic = 0;
 #ifndef LA_NOCOMPAT
-              la10c_reset();
+        la10c_reset();
 #endif
-          }
+      }
 
-          card.startFileprint();
-          starttime=_millis();
+      card.startFileprint();
+      starttime=_millis();
+      if (MMU2::mmu2.Enabled())
+      {
+        if (MMU2::mmu2.FindaDetectsFilament() && !fsensor.getFilamentPresent())
+        { // Filament only half way into the PTFE. Unload the filament.
+          MMU2::mmu2.unload();
+          // Tx and Tc gcodes take care of loading the filament to the nozzle.
+        }
       }
-	  break;
+    }
+    break;
 
     /*!
 	### M26 - Set SD index <a href="https://reprap.org/wiki/G-code#M26:_Set_SD_position">M26: Set SD position</a>
@@ -5773,7 +5573,7 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
       }
       starpos = (strchr(strchr_pointer + 4,'*'));
 
-      char* namestartpos = (strchr(strchr_pointer + 4,'!'));   //find ! to indicate filename string start.
+      const char* namestartpos = (strchr(strchr_pointer + 4,'!'));   //find ! to indicate filename string start.
       if(namestartpos==NULL)
       {
         namestartpos=strchr_pointer + 4; //default name position, 4 letters after the M
@@ -5802,6 +5602,7 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
             {
                 // A new print has started from scratch, reset stats
                 failstats_reset_print();
+                sdpos_atomic = 0;
 #ifndef LA_NOCOMPAT
                 la10c_reset();
 #endif
@@ -6434,7 +6235,7 @@ Sigma_Exit:
       }
       LCD_MESSAGERPGM(_T(MSG_HEATING));
 	  heating_status = HeatingStatus::EXTRUDER_HEATING;
-	  if (farm_mode) { prusa_statistics(1); };
+      prusa_statistics(1);
 
 #ifdef AUTOTEMP
         autotemp_enabled=false;
@@ -6465,7 +6266,7 @@ Sigma_Exit:
 
         LCD_MESSAGERPGM(_T(MSG_HEATING_COMPLETE));
 		heating_status = HeatingStatus::EXTRUDER_HEATING_COMPLETE;
-		if (farm_mode) { prusa_statistics(2); };
+        prusa_statistics(2);
         
         //starttime=_millis();
         previous_millis_cmd.start();
@@ -6491,7 +6292,7 @@ Sigma_Exit:
         bool CooldownNoWait = false;
         LCD_MESSAGERPGM(_T(MSG_BED_HEATING));
 		heating_status = HeatingStatus::BED_HEATING;
-		if (farm_mode) { prusa_statistics(1); };
+        prusa_statistics(1);
         if (code_seen('S')) 
 		{
           setTargetBed(code_value());
@@ -6740,9 +6541,9 @@ Sigma_Exit:
               axis_steps_per_sqr_second[i] *= factor;
             }
             cs.axis_steps_per_unit[i] = value;
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-            fsensor_set_axis_steps_per_unit(value);
-#endif
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+            fsensor.init();
+#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
           }
           else {
             cs.axis_steps_per_unit[i] = code_value();
@@ -7755,6 +7556,69 @@ Sigma_Exit:
       PID_autotune(temp, e, c);
     }
     break;
+
+#ifdef TEMP_MODEL
+    /*!
+    ### M310 - Temperature model settings <a href="https://reprap.org/wiki/G-code#M310:_Temperature_model_settings">M310: Temperature model settings</a>
+    #### Usage
+
+        M310                                           ; report values
+        M310 [ A ] [ F ]                               ; autotune
+        M310 [ S ]                                     ; set 0=disable 1=enable
+        M310 [ I ] [ R ]                               ; set resistance at index
+        M310 [ P | C ]                                 ; set power, capacitance
+        M310 [ B | E | W ]                             ; set beeper, warning and error threshold
+        M310 [ T ]                                     ; set ambient temperature correction
+
+    #### Parameters
+    - `I` - resistance index position (0-15)
+    - `R` - resistance value at index (K/W; requires `I`)
+    - `P` - power (W)
+    - `C` - capacitance (J/K)
+    - `S` - set 0=disable 1=enable
+    - `B` - beep and warn when reaching warning threshold 0=disable 1=enable (default: 1)
+    - `E` - error threshold (K/s; default in variant)
+    - `W` - warning threshold (K/s; default in variant)
+    - `T` - ambient temperature correction (K; default in variant)
+    - `A` - autotune C+R values
+    - `F` - force model self-test state (0=off 1=on) during autotune using current values
+    */
+    case 310:
+    {
+        // parse all parameters
+        float P = NAN, C = NAN, R = NAN, E = NAN, W = NAN, T = NAN;
+        int8_t I = -1, S = -1, B = -1, A = -1, F = -1;
+        if(code_seen('C')) C = code_value();
+        if(code_seen('P')) P = code_value();
+        if(code_seen('I')) I = code_value_short();
+        if(code_seen('R')) R = code_value();
+        if(code_seen('S')) S = code_value_short();
+        if(code_seen('B')) B = code_value_short();
+        if(code_seen('E')) E = code_value();
+        if(code_seen('W')) W = code_value();
+        if(code_seen('T')) T = code_value();
+        if(code_seen('A')) A = code_value_short();
+        if(code_seen('F')) F = code_value_short();
+
+        // report values if nothing has been requested
+        if(isnan(C) && isnan(P) && isnan(R) && isnan(E) && isnan(W) && isnan(T) && I < 0 && S < 0 && B < 0 && A < 0) {
+            temp_model_report_settings();
+            break;
+        }
+
+        // update all parameters
+        if(B >= 0) temp_model_set_warn_beep(B);
+        if(!isnan(C) || !isnan(P) || !isnan(T) || !isnan(W) || !isnan(E)) temp_model_set_params(C, P, T, W, E);
+        if(I >= 0 && !isnan(R)) temp_model_set_resistance(I, R);
+
+        // enable the model last, if requested
+        if(S >= 0) temp_model_set_enabled(S);
+
+        // run autotune
+        if(A >= 0) temp_model_autotune(A, F > 0);
+    }
+    break;
+#endif
     
     /*!
 	### M400 - Wait for all moves to finish <a href="https://reprap.org/wiki/G-code#M400:_Wait_for_current_moves_to_finish">M400: Wait for current moves to finish</a>
@@ -7783,14 +7647,13 @@ Sigma_Exit:
 	{
 		// currently three different materials are needed (default, flex and PVA)
 		// add storing this information for different load/unload profiles etc. in the future
-		// firmware does not wait for "ok" from mmu
-		if (mmu_enabled)
+		if (MMU2::mmu2.Enabled())
 		{
 			uint8_t extruder = 255;
 			uint8_t filament = FILAMENT_UNDEFINED;
 			if(code_seen('E')) extruder = code_value_uint8();
 			if(code_seen('F')) filament = code_value_uint8();
-			mmu_set_filament_type(extruder, filament);
+			MMU2::mmu2.set_filament_type(extruder, filament);
 		}
 	}
 	break;
@@ -7866,6 +7729,7 @@ Sigma_Exit:
     break;
     #endif
 
+#ifdef ENABLE_AUTO_BED_LEVELING
 	/*!
 	### M851 - Set Z-Probe Offset <a href="https://reprap.org/wiki/G-code#M851:_Set_Z-Probe_Offset">M851: Set Z-Probe Offset"</a>
     Sets the Z-probe Z offset. This offset is used to determine the actual Z position of the nozzle when using a probe to home Z with G28. This value may also be used by G81 (Prusa) / G29 (Marlin) to apply correction to the Z position.
@@ -7912,6 +7776,7 @@ Sigma_Exit:
       break;
     }
     #endif // CUSTOM_M_CODE_SET_Z_PROBE_OFFSET
+#endif // ENABLE_AUTO_BED_LEVELING
 
 	/*!
 	### M552 - Set IP address <a href="https://reprap.org/wiki/G-code#M552:_Set_IP_address.2C_enable.2Fdisable_network_interface">M552: Set IP address, enable/disable network interface"</a>
@@ -7955,7 +7820,7 @@ Sigma_Exit:
       
     - `X`    - X position, default 211
     - `Y`    - Y position, default 0
-    - `Z`    - relative lift Z, default 2.
+    - `Z`    - relative lift Z, default MIN_Z_FOR_SWAP.
     - `E`    - initial retract, default -2
     - `L`    - later retract distance for removal, default -80
     - `AUTO` - Automatically (only with MMU)
@@ -7966,7 +7831,7 @@ Sigma_Exit:
 
 		float x_position = current_position[X_AXIS];
 		float y_position = current_position[Y_AXIS];
-		float z_shift = 0; // is it necessary to be a float?
+		float z_shift = MIN_Z_FOR_SWAP;
 		float e_shift_init = 0;
 		float e_shift_late = 0;
 		bool automatic = false;
@@ -7995,16 +7860,10 @@ Sigma_Exit:
 		  #endif	
 		}
 
-        //Lift Z
-        if(code_seen('Z'))
-        {
-          z_shift = code_value();
-        }
-        else
-        {
-			z_shift = gcode_M600_filament_change_z_shift<uint8_t>();
-        }
-		//Move XY to side
+        // Z lift. For safety only allow positive values
+        if (code_seen('Z')) z_shift = fabs(code_value());
+
+        //Move XY to side
         if(code_seen('X'))
         {
           x_position = code_value();
@@ -8026,7 +7885,7 @@ Sigma_Exit:
           #endif
         }
 
-		if (mmu_enabled && code_seen_P(PSTR("AUTO")))
+		if (MMU2::mmu2.Enabled() && code_seen_P(PSTR("AUTO")))
 			automatic = true;
 
 		gcode_M600(automatic, x_position, y_position, z_shift, e_shift_init, e_shift_late);
@@ -8265,14 +8124,6 @@ Sigma_Exit:
                          nDiameter=(uint16_t)(code_value()*1000.0+0.5); // [,um]
                          nozzle_diameter_check(nDiameter);
                          }
-/*
-                    else if(code_seen('S')&&farm_mode)
-                         {
-                         nDiameter=(uint16_t)(code_value()*1000.0+0.5); // [,um]
-                         eeprom_update_byte((uint8_t*)EEPROM_NOZZLE_DIAMETER,(uint8_t)ClNozzleDiameter::_Diameter_Undef); // for correct synchronization after farm-mode exiting
-                         eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,nDiameter);
-                         }
-*/
                     else if(code_seen('Q'))
                          SERIAL_PROTOCOLLN((float)eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM)/1000.0);
                     break;
@@ -8281,6 +8132,8 @@ Sigma_Exit:
                          {
                          uint16_t nPrinterModel;
                          nPrinterModel=(uint16_t)code_value_long();
+                         // based on current state of MMU (active/stopped/connecting) perform a runtime update of the printer type
+                         fSetMmuMode(MMU2::mmu2.Enabled());
                          printer_model_check(nPrinterModel);
                          }
                     else if(code_seen('Q'))
@@ -8288,7 +8141,10 @@ Sigma_Exit:
                     break;
                case ClPrintChecking::_Smodel:     // ~ .3
                     if(code_seen('P'))
-                         printer_smodel_check(strchr_pointer);
+                    {
+                        fSetMmuMode(MMU2::mmu2.Enabled());
+                        printer_smodel_check(strchr_pointer);
+                    }
                     else if(code_seen('Q'))
                          SERIAL_PROTOCOLLNRPGM(sPrinterName);
                     break;
@@ -8619,10 +8475,10 @@ Sigma_Exit:
 						cs.axis_steps_per_unit[i] /= fac;
 						position[i] /= fac;
 					}
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-                    if (i == E_AXIS)
-                        fsensor_set_axis_steps_per_unit(cs.axis_steps_per_unit[i]);
-#endif
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+					if (i == E_AXIS)
+						fsensor.init();
+#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
 				}
 			}
 		}
@@ -8673,44 +8529,213 @@ Sigma_Exit:
     break;
 
     /*!
-    ### M701 - Load filament <a href="https://reprap.org/wiki/G-code#M701:_Load_filament">M701: Load filament</a>
+    ### M701 - Load filament to extruder <a href="https://reprap.org/wiki/G-code#M701:_Load_filament">M701: Load filament</a>
+    Load filament into the active extruder.
     #### Usage
 
-        M701 [ E | T ]
+        M701 [ P | T | L | Z ]
 
     #### Parameters
-    - `E` - ID of filament to load, ranges from 0 to 4
-    - `T` - Alias of `E`. Used for compatibility with Marlin
+    - `P` - n index of MMU slot (zero based, so 0-4 like T0 and T4)
+    - `T` - Alias of `P`. Used for compatibility with Marlin
+    - `L` - Extrude distance for insertion (positive value)(manual reload)
+    - `Z` - Move the Z axis by this distance. Default value MIN_Z_FOR_LOAD
     */
-	case 701:
-	{
-		if (mmu_enabled && (code_seen('E') || code_seen('T')))
-			tmp_extruder = code_value_uint8();
-		gcode_M701();
-	}
-	break;
+    case 701:
+    {
+        uint8_t mmuSlotIndex = 0xffU;
+        float fastLoadLength = FILAMENTCHANGE_FIRSTFEED; // Only used without MMU
+        float z_target = MIN_Z_FOR_LOAD;
+        if( MMU2::mmu2.Enabled() )
+        {
+            if( code_seen('P') || code_seen('T') ) {
+                mmuSlotIndex = code_value_uint8();
+            }
+        }
+
+        if (code_seen('L')) fastLoadLength = code_value();
+
+        // Z lift. For safety only allow positive values
+        if (code_seen('Z')) z_target = fabs(code_value());
+
+        // Raise the Z axis
+        float delta = raise_z(z_target);
+
+        // Load filament
+        gcode_M701(fastLoadLength, mmuSlotIndex);
+
+        // Restore Z axis
+        raise_z(-delta);
+    }
+    break;
 
     /*!
     ### M702 - Unload filament <a href="https://reprap.org/wiki/G-code#M702:_Unload_filament">G32: Undock Z Probe sled</a>
     #### Usage
     
-        M702 [ C ]
+        M702 [ U | Z ]
     
     #### Parameters
-    - `C` - Unload just current filament
-    - without any parameters unload all filaments
+    - `U` - Retract distance for removal (manual reload). Default value is 0.
+    - `Z` - Move the Z axis by this distance. Default value MIN_Z_FOR_UNLOAD.
     */
-	case 702:
-	{
-		if (code_seen('C')) {
-			if(mmu_enabled) extr_unload(); //! if "C" unload current filament; if mmu is not present no action is performed
-		}
-		else {
-			if(mmu_enabled) extr_unload(); //! unload current filament
-			else unload_filament();
-		}
-	}
-	break;
+    case 702:
+    {
+        float z_target = MIN_Z_FOR_UNLOAD;
+        float unloadLength = FILAMENTCHANGE_FINALRETRACT;
+        if (code_seen('U')) unloadLength = code_value();
+
+        // For safety only allow positive values
+        if (code_seen('Z')) z_target = fabs(code_value());
+
+        // Raise the Z axis
+        float delta = raise_z(z_target);
+
+        // Unload filament
+        if (MMU2::mmu2.Enabled())  MMU2::mmu2.unload();
+        else unload_filament(unloadLength);
+
+        // Restore Z axis
+        raise_z(-delta);
+    }
+    break;
+
+    /*!
+    ### M704 - Load to MMU <a href="https://reprap.org/wiki/G-code#M704:_Load_to_MMU">M704: Load to MMU</a>
+    #### Usage
+
+        M704 [ P ]
+
+    #### Parameters
+    - `P` - n index of slot (zero based, so 0-4 like T0 and T4)
+    */
+    case 704:
+    {
+        gcodes_M704_M705_M706(704);
+    }
+    break;
+
+    /*!
+    ### M705 - Eject filament <a href="https://reprap.org/wiki/G-code#M705:_Eject_filament">M705: Eject filament</a>
+    #### Usage
+
+        M705 [ P ]
+
+    #### Parameters
+    - `P` - n index of slot (zero based, so 0-4 like T0 and T4)
+    */
+    case 705:
+    {
+        gcodes_M704_M705_M706(705);
+    }
+    break;
+
+
+    /*!
+    ### M706 - Cut filament <a href="https://reprap.org/wiki/G-code#M706:_Cut_filament">M706: Cut filament</a>
+    #### Usage
+
+        M706 [ P ]
+
+    #### Parameters
+    - `P` - n index of slot (zero based, so 0-4 like T0 and T4)
+    */
+    case 706:
+    {
+        gcodes_M704_M705_M706(706);
+    }
+    break;
+
+    /*!
+    ### M707 - Read from MMU register <a href="https://reprap.org/wiki/G-code#M707:_Read_from_MMU_register">M707: Read from MMU register</a>
+    #### 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.
+
+    */
+    case 707: {
+        if ( MMU2::mmu2.Enabled() ) {
+            if( code_seen('A') ) {
+                MMU2::mmu2.ReadRegister(uint8_t(strtol(strchr_pointer+1, NULL, 16)));
+            }
+        }
+    } break;
+
+    /*!
+    ### M708 - Write to MMU register <a href="https://reprap.org/wiki/G-code#M708:_Write_to_MMU_register">M707: Write to MMU register</a>
+    #### 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.
+    */
+    case 708: {
+        if ( MMU2::mmu2.Enabled() ){
+            uint8_t addr = 0;
+            if( code_seen('A') ) {
+                addr = uint8_t(strtol(strchr_pointer+1, NULL, 16));
+            }
+            uint16_t data = 0;
+            if( code_seen('X') ) {
+                data = code_value_short();
+            }
+            if(addr){
+                MMU2::mmu2.WriteRegister(addr, data);
+            }
+        }
+    } break;
+
+    /*!
+    ### M709 - MMU reset <a href="https://reprap.org/wiki/G-code#M709:_MMU_reset">M709: MMU reset</a>
+    The MK3S cannot not power off the MMU, for that reason the functionality is not supported.
+    #### Usage
+
+        M709 [ X ]
+
+    #### Parameters
+    - `X` - Reset MMU (0:soft reset | 1:hardware reset)
+
+    #### Example
+
+    M709 X0 - issue an X0 command via communication into the MMU (soft reset)
+
+    M709 X1 - toggle the MMU's reset pin (hardware reset)
+
+    */
+    case 709:
+    {
+        if (MMU2::mmu2.Enabled() && code_seen('X'))
+        {
+            switch (code_value_uint8())
+            {
+            case 0:
+                MMU2::mmu2.Reset(MMU2::MMU2::Software);
+                break;
+            case 1:
+                MMU2::mmu2.Reset(MMU2::MMU2::ResetPin);
+                break;
+            default:
+                break;
+            }
+        }
+    }
+    break;
 
     /*!
     ### M999 - Restart after being stopped <a href="https://reprap.org/wiki/G-code#M999:_Restart_after_being_stopped_by_error">M999: Restart after being stopped by error</a>
@@ -8719,7 +8744,7 @@ Sigma_Exit:
     case 999:
       Stopped = false;
       lcd_reset_alert_level();
-      gcode_LastN = Stopped_gcode_LastN;
+//@@TODO      gcode_LastN = Stopped_gcode_LastN;
       FlushSerialRequestResend();
     break;
 	/*!
@@ -8743,139 +8768,8 @@ Sigma_Exit:
   @n Tx Same as T?, except nozzle doesn't have to be preheated. Tc must be placed after extruder nozzle is preheated to finish filament load.
   @n Tc Load to nozzle after filament was prepared by Tc and extruder nozzle is already heated.
   */
-  else if(code_seen('T'))
-  {
-      static const char duplicate_Tcode_ignored[] PROGMEM = "Duplicate T-code ignored.";
-      
-      int index;
-      bool load_to_nozzle = false;
-      for (index = 1; *(strchr_pointer + index) == ' ' || *(strchr_pointer + index) == '\t'; index++);
-
-      *(strchr_pointer + index) = tolower(*(strchr_pointer + index));
-
-      if ((*(strchr_pointer + index) < '0' || *(strchr_pointer + index) > '4') && *(strchr_pointer + index) != '?' && *(strchr_pointer + index) != 'x' && *(strchr_pointer + index) != 'c') {
-          SERIAL_ECHOLNPGM("Invalid T code.");
-      }
-	  else if (*(strchr_pointer + index) == 'x'){ //load to bondtech gears; if mmu is not present do nothing
-		if (mmu_enabled)
-		{
-			tmp_extruder = choose_menu_P(_T(MSG_SELECT_FILAMENT), _T(MSG_FILAMENT));
-			if ((tmp_extruder == mmu_extruder) && mmu_fil_loaded) //dont execute the same T-code twice in a row
-			{
-				puts_P(duplicate_Tcode_ignored);
-			}
-			else
-			{
-				st_synchronize();
-				mmu_command(MmuCmd::T0 + tmp_extruder);
-				manage_response(true, true, MMU_TCODE_MOVE);
-			}
-		}
-	  }
-	  else if (*(strchr_pointer + index) == 'c') { //load to from bondtech gears to nozzle (nozzle should be preheated)
-	  	if (mmu_enabled) 
-		{
-			st_synchronize();
-			mmu_continue_loading(usb_timer.running()  || (lcd_commands_type == LcdCommands::Layer1Cal));
-			mmu_extruder = tmp_extruder; //filament change is finished
-			mmu_load_to_nozzle();
-		}
-	  }
-      else {
-          if (*(strchr_pointer + index) == '?')
-          {
-              if(mmu_enabled)
-              {
-                  tmp_extruder = choose_menu_P(_T(MSG_SELECT_FILAMENT), _T(MSG_FILAMENT));
-                  load_to_nozzle = true;
-              } else
-              {
-                  tmp_extruder = choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER));
-              }
-          }
-          else {
-              tmp_extruder = code_value();
-              if (mmu_enabled && lcd_autoDepleteEnabled())
-              {
-                  tmp_extruder = ad_getAlternative(tmp_extruder);
-              }
-          }
-          st_synchronize();
-
-          if (mmu_enabled)
-          {
-              if ((tmp_extruder == mmu_extruder) && mmu_fil_loaded) //dont execute the same T-code twice in a row
-              {
-                  puts_P(duplicate_Tcode_ignored);
-              }
-			  else
-			  {
-#if defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
-			      if (EEPROM_MMU_CUTTER_ENABLED_always == eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED))
-                  {
-                      mmu_command(MmuCmd::K0 + tmp_extruder);
-                      manage_response(true, true, MMU_UNLOAD_MOVE);
-                  }
-#endif //defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
-				  mmu_command(MmuCmd::T0 + tmp_extruder);
-				  manage_response(true, true, MMU_TCODE_MOVE);
-		          mmu_continue_loading(usb_timer.running()  || (lcd_commands_type == LcdCommands::Layer1Cal));
-
-				  mmu_extruder = tmp_extruder; //filament change is finished
-
-				  if (load_to_nozzle)// for single material usage with mmu
-				  {
-					  mmu_load_to_nozzle();
-				  }
-			  }
-          }
-          else
-          {
-              if (tmp_extruder >= EXTRUDERS) {
-                  SERIAL_ECHO_START;
-                  SERIAL_ECHO('T');
-                  SERIAL_PROTOCOLLN((int)tmp_extruder);
-                  SERIAL_ECHOLNRPGM(_n("Invalid extruder"));////MSG_INVALID_EXTRUDER
-              }
-              else {
-#if EXTRUDERS > 1
-                  bool make_move = false;
-#endif
-                  if (code_seen('F')) {
-#if EXTRUDERS > 1
-                      make_move = true;
-#endif
-                      next_feedrate = code_value();
-                      if (next_feedrate > 0.0) {
-                          feedrate = next_feedrate;
-                      }
-                  }
-#if EXTRUDERS > 1
-                  if (tmp_extruder != active_extruder) {
-                      // Save current position to return to after applying extruder offset
-                      set_destination_to_current();
-                      // Offset extruder (only by XY)
-                      int i;
-                      for (i = 0; i < 2; i++) {
-                          current_position[i] = current_position[i] -
-                                  extruder_offset[i][active_extruder] +
-                                  extruder_offset[i][tmp_extruder];
-                      }
-                      // Set the new active extruder and position
-                      active_extruder = tmp_extruder;
-                      plan_set_position_curposXYZE();
-                      // Move to the old position if 'F' was in the parameters
-                      if (make_move && Stopped == false) {
-                          prepare_move();
-                      }
-                  }
-#endif
-                  SERIAL_ECHO_START;
-                  SERIAL_ECHORPGM(_n("Active Extruder: "));////MSG_ACTIVE_EXTRUDER
-                  SERIAL_PROTOCOLLN((int)active_extruder);
-              }
-          }
-      }
+  else if(code_seen('T')){
+        TCodes(strchr_pointer, code_value());
   } // end if(code_seen('T')) (end of T codes)
   /*!
   #### End of T-Codes
@@ -9139,6 +9033,23 @@ Sigma_Exit:
     };
 #endif
 
+#ifdef TEMP_MODEL_DEBUG
+    /*!
+    ## D70 - Enable low-level temperature model logging for offline simulation
+    #### Usage
+
+        D70 [ S ]
+
+    #### Parameters
+    - `S` - Enable 0-1 (default 0)
+    */
+    case 70: {
+        if(code_seen('S'))
+            temp_model_log_enable(code_value_short());
+        break;
+    }
+#endif
+
 #ifdef HEATBED_ANALYSIS
 
     /*!
@@ -9246,7 +9157,7 @@ Sigma_Exit:
 		dcode_2130(); break;
 #endif //TMC2130
 
-#if (defined (FILAMENT_SENSOR) && defined(PAT9125))
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
 
     /*!
     ### D9125 - PAT9125 filament sensor <a href="https://reprap.org/wiki/G-code#D9:_Read.2FWrite_ADC">D9125: PAT9125 filament sensor</a>
@@ -9264,7 +9175,7 @@ Sigma_Exit:
     */
 	case 9125:
 		dcode_9125(); break;
-#endif //FILAMENT_SENSOR
+#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
 
 #endif //DEBUG_DCODES
 
@@ -9348,8 +9259,7 @@ void update_currents() {
 }
 #endif //MOTHERBOARD == BOARD_RAMBO_MINI_1_0 || MOTHERBOARD == BOARD_RAMBO_MINI_1_3
 
-void get_coordinates()
-{
+void get_coordinates() {
   bool seen[4]={false,false,false,false};
   for(int8_t i=0; i < NUM_AXIS; i++) {
     if(code_seen(axis_codes[i]))
@@ -9386,31 +9296,6 @@ void get_coordinates()
   }
 }
 
-void get_arc_coordinates()
-{
-#ifdef SF_ARC_FIX
-   bool relative_mode_backup = relative_mode;
-   relative_mode = true;
-#endif
-   get_coordinates();
-#ifdef SF_ARC_FIX
-   relative_mode=relative_mode_backup;
-#endif
-
-   if(code_seen('I')) {
-     offset[0] = code_value();
-   }
-   else {
-     offset[0] = 0.0;
-   }
-   if(code_seen('J')) {
-     offset[1] = code_value();
-   }
-   else {
-     offset[1] = 0.0;
-   }
-}
-
 void clamp_to_software_endstops(float target[3])
 {
 #ifdef DEBUG_DISABLE_SWLIMITS
@@ -9432,59 +9317,70 @@ void clamp_to_software_endstops(float target[3])
     }
 }
 
+uint16_t restore_interrupted_gcode() {
+    // When recovering from a previous print move, restore the originally
+    // calculated start position on the first USB/SD command. This accounts
+    // properly for relative moves
+    if (
+        (saved_start_position[0] != SAVED_START_POSITION_UNSET) && (
+            (CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_SDCARD) ||
+            (CMDBUFFER_CURRENT_TYPE == CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR)
+        )
+    ) {
+        memcpy(current_position, saved_start_position, sizeof(current_position));
+        saved_start_position[0] = SAVED_START_POSITION_UNSET;
+        return saved_segment_idx;
+    }
+    else
+        return 1; //begin with the first segment
+}
+
 #ifdef MESH_BED_LEVELING
-void mesh_plan_buffer_line(const float &x, const float &y, const float &z, const float &e, const float &feed_rate, const uint8_t extruder) {
+void mesh_plan_buffer_line(const float &x, const float &y, const float &z, const float &e, const float &feed_rate, const uint8_t extruder, uint16_t start_segment_idx = 0) {
         float dx = x - current_position[X_AXIS];
         float dy = y - current_position[Y_AXIS];
-        int n_segments = 0;
+        uint16_t n_segments = 0;
 
         if (mbl.active) {
             float len = fabs(dx) + fabs(dy);
             if (len > 0)
                 // Split to 3cm segments or shorter.
-                n_segments = int(ceil(len / 30.f));
+                n_segments = uint16_t(ceil(len / 30.f));
         }
 
-        if (n_segments > 1) {
-            // In a multi-segment move explicitly set the final target in the plan
-            // as the move will be recalculated in it's entirety
-            float gcode_target[NUM_AXIS];
-            gcode_target[X_AXIS] = x;
-            gcode_target[Y_AXIS] = y;
-            gcode_target[Z_AXIS] = z;
-            gcode_target[E_AXIS] = e;
+        if (n_segments > 1 && start_segment_idx) {
 
             float dz = z - current_position[Z_AXIS];
             float de = e - current_position[E_AXIS];
 
-            for (int i = 1; i < n_segments; ++ i) {
+            for (uint16_t i = start_segment_idx; i < n_segments; ++ i) {
                 float t = float(i) / float(n_segments);
                 plan_buffer_line(current_position[X_AXIS] + t * dx,
                                  current_position[Y_AXIS] + t * dy,
                                  current_position[Z_AXIS] + t * dz,
                                  current_position[E_AXIS] + t * de,
-                                 feed_rate, extruder, gcode_target);
-                if (waiting_inside_plan_buffer_line_print_aborted)
+                                 feed_rate, extruder, current_position, i);
+                if (planner_aborted)
                     return;
             }
         }
         // The rest of the path.
-        plan_buffer_line(x, y, z, e, feed_rate, extruder);
+        plan_buffer_line(x, y, z, e, feed_rate, extruder, current_position);
     }
 #endif  // MESH_BED_LEVELING
     
-void prepare_move()
+void prepare_move(uint16_t start_segment_idx)
 {
   clamp_to_software_endstops(destination);
   previous_millis_cmd.start();
 
   // Do not use feedmultiply for E or Z only moves
-  if( (current_position[X_AXIS] == destination [X_AXIS]) && (current_position[Y_AXIS] == destination [Y_AXIS])) {
+  if((current_position[X_AXIS] == destination[X_AXIS]) && (current_position[Y_AXIS] == destination[Y_AXIS])) {
       plan_buffer_line_destinationXYZE(feedrate/60);
   }
   else {
 #ifdef MESH_BED_LEVELING
-    mesh_plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate*feedmultiply*(1./(60.f*100.f)), active_extruder);
+    mesh_plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate*feedmultiply*(1./(60.f*100.f)), active_extruder, start_segment_idx);
 #else
      plan_buffer_line_destinationXYZE(feedrate*feedmultiply*(1./(60.f*100.f)));
 #endif
@@ -9493,10 +9389,10 @@ void prepare_move()
   set_current_to_destination();
 }
 
-void prepare_arc_move(bool isclockwise) {
+void prepare_arc_move(bool isclockwise, uint16_t start_segment_idx) {
     float r = hypot(offset[X_AXIS], offset[Y_AXIS]); // Compute arc radius for mc_arc
     // Trace the arc
-    mc_arc(current_position, destination, offset, feedrate * feedmultiply / 60 / 100.0, r, isclockwise, active_extruder);
+    mc_arc(current_position, destination, offset, feedrate * feedmultiply / 60 / 100.0, r, isclockwise, active_extruder, start_segment_idx);
     // As far as the parser is concerned, the position is now == target. In reality the
     // motion control system might still be processing the action and the real tool position
     // in any intermediate location.
@@ -9566,7 +9462,7 @@ static void handleSafetyTimer()
 #if (EXTRUDERS > 1)
 #error Implemented only for one extruder.
 #endif //(EXTRUDERS > 1)
-    if ((PRINTER_ACTIVE) || (!degTargetBed() && !degTargetHotend(0)) || (!safetytimer_inactive_time))
+    if (printer_active() || (!degTargetBed() && !degTargetHotend(0)) || (!safetytimer_inactive_time))
     {
         safetyTimer.stop();
     }
@@ -9583,134 +9479,13 @@ static void handleSafetyTimer()
 }
 #endif //SAFETYTIMER
 
-#ifdef IR_SENSOR_ANALOG
-#define FS_CHECK_COUNT 16
-/// Switching mechanism of the fsensor type.
-/// Called from 2 spots which have a very similar behavior
-/// 1: ClFsensorPCB::_Old -> ClFsensorPCB::_Rev04 and print _i("FS v0.4 or newer")
-/// 2: ClFsensorPCB::_Rev04 -> oFsensorPCB=ClFsensorPCB::_Old and print _i("FS v0.3 or older")
-void manage_inactivity_IR_ANALOG_Check(uint16_t &nFSCheckCount, ClFsensorPCB isVersion, ClFsensorPCB switchTo, const char *statusLineTxt_P) {
-    bool bTemp = (!CHECK_ALL_HEATERS);
-    bTemp = bTemp && (menu_menu == lcd_status_screen);
-    bTemp = bTemp && ((oFsensorPCB == isVersion) || (oFsensorPCB == ClFsensorPCB::_Undef));
-    bTemp = bTemp && fsensor_enabled;
-    if (bTemp) {
-        nFSCheckCount++;
-        if (nFSCheckCount > FS_CHECK_COUNT) {
-            nFSCheckCount = 0; // not necessary
-            oFsensorPCB = switchTo;
-            eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_PCB, (uint8_t)oFsensorPCB);
-            printf_IRSensorAnalogBoardChange();
-            lcd_setstatuspgm(statusLineTxt_P);
-        }
-    } else {
-        nFSCheckCount = 0;
-    }
-}
-#endif
-
 void manage_inactivity(bool ignore_stepper_queue/*=false*/) //default argument set in Marlin.h
 {
 #ifdef FILAMENT_SENSOR
-bool bInhibitFlag = false;
-#ifdef IR_SENSOR_ANALOG
-static uint16_t nFSCheckCount=0;
-#endif // IR_SENSOR_ANALOG
-
-	if (mmu_enabled == false)
-	{
-//-//		if (mcode_in_progress != 600) //M600 not in progress
-		if (!PRINTER_ACTIVE) bInhibitFlag=(menu_menu==lcd_menu_show_sensors_state); //Block Filament sensor actions if PRINTER is not active and Support::SensorInfo menu active
-#ifdef IR_SENSOR_ANALOG
-		bInhibitFlag=bInhibitFlag||bMenuFSDetect; // Block Filament sensor actions if Settings::HWsetup::FSdetect menu active
-#endif // IR_SENSOR_ANALOG
-		if ((mcode_in_progress != 600) && (eFilamentAction != FilamentAction::AutoLoad) && (!bInhibitFlag) && (menu_menu != lcd_move_e)) //M600 not in progress, preHeat @ autoLoad menu not active
-		{
-			if (!moves_planned() && !IS_SD_PRINTING && !usb_timer.running() && (lcd_commands_type != LcdCommands::Layer1Cal) && ! eeprom_read_byte((uint8_t*)EEPROM_WIZARD_ACTIVE))
-			{
-#ifdef IR_SENSOR_ANALOG
-				static uint16_t minVolt = Voltage2Raw(6.F), maxVolt = 0;
-				// detect min-max, some long term sliding window for filtration may be added
-				// avoiding floating point operations, thus computing in raw
-				if( current_voltage_raw_IR > maxVolt )maxVolt = current_voltage_raw_IR;
-				if( current_voltage_raw_IR < minVolt )minVolt = current_voltage_raw_IR;
-				
-#if 0 // Start: IR Sensor debug info
-				{ // debug print
-					static uint16_t lastVolt = ~0U;
-					if( current_voltage_raw_IR != lastVolt ){
-						printf_P(PSTR("fs volt=%4.2fV (min=%4.2f max=%4.2f)\n"), Raw2Voltage(current_voltage_raw_IR), Raw2Voltage(minVolt), Raw2Voltage(maxVolt) );
-						lastVolt = current_voltage_raw_IR;
-					}
-				}
-#endif // End: IR Sensor debug info
-				//! The trouble is, I can hold the filament in the hole in such a way, that it creates the exact voltage
-				//! to be detected as the new fsensor
-				//! We can either fake it by extending the detection window to a looooong time
-				//! or do some other countermeasures
-				
-				//! what we want to detect:
-				//! if minvolt gets below ~0.3V, it means there is an old fsensor
-				//! if maxvolt gets above 4.6V, it means we either have an old fsensor or broken cables/fsensor
-				//! So I'm waiting for a situation, when minVolt gets to range <0, 1.5> and maxVolt gets into range <3.0, 5>
-				//! If and only if minVolt is in range <0.3, 1.5> and maxVolt is in range <3.0, 4.6>, I'm considering a situation with the new fsensor
-				if( minVolt >= IRsensor_Ldiode_TRESHOLD && minVolt <= IRsensor_Lmax_TRESHOLD 
-				 && maxVolt >= IRsensor_Hmin_TRESHOLD && maxVolt <= IRsensor_Hopen_TRESHOLD
-				){
-					manage_inactivity_IR_ANALOG_Check(nFSCheckCount, ClFsensorPCB::_Old, ClFsensorPCB::_Rev04, _i("FS v0.4 or newer") ); ////MSG_FS_V_04_OR_NEWER c=18
-				} 
-				//! If and only if minVolt is in range <0.0, 0.3> and maxVolt is in range  <4.6, 5.0V>, I'm considering a situation with the old fsensor
-				//! Note, we are not relying on one voltage here - getting just +5V can mean an old fsensor or a broken new sensor - that's why
-				//! we need to have both voltages detected correctly to allow switching back to the old fsensor.
-				else if( minVolt < IRsensor_Ldiode_TRESHOLD 
-				 && maxVolt > IRsensor_Hopen_TRESHOLD && maxVolt <= IRsensor_VMax_TRESHOLD
-				){
-					manage_inactivity_IR_ANALOG_Check(nFSCheckCount, ClFsensorPCB::_Rev04, oFsensorPCB=ClFsensorPCB::_Old, _i("FS v0.3 or older")); ////MSG_FS_V_03_OR_OLDER c=18
-				}
-#endif // IR_SENSOR_ANALOG
-				if (fsensor_check_autoload())
-				{
-#ifdef PAT9125
-					fsensor_autoload_check_stop();
-#endif //PAT9125
-//-//					if ((int)degHotend0() > extrude_min_temp)
-if(0)
-					{
-						Sound_MakeCustom(50,1000,false);
-						loading_flag = true;
-						enquecommand_front_P((PSTR("M701")));
-					}
-					else
-					{
-/*
-						lcd_update_enable(false);
-						show_preheat_nozzle_warning();
-						lcd_update_enable(true);
-*/
-						eFilamentAction=FilamentAction::AutoLoad;
-						bFilamentFirstRun=false;
-						if(target_temperature[0] >= extrude_min_temp){
-							bFilamentPreheatState=true;
-//							mFilamentItem(target_temperature[0],target_temperature_bed);
-							menu_submenu(mFilamentItemForce);
-						} else {
-							menu_submenu(lcd_generic_preheat_menu);
-							lcd_timeoutToStatus.start();
-						}
-					}
-				}
-			}
-			else
-			{
-#ifdef PAT9125
-				fsensor_autoload_check_stop();
-#endif //PAT9125
-                if (fsensor_enabled && !saved_printing)
-                    fsensor_update();
-			}
-		}
-	}
-#endif //FILAMENT_SENSOR
+    if (fsensor.update()) {
+        lcd_draw_update = 1; //cause lcd update so that fsensor event polling can be done from the lcd draw routine.
+    }
+#endif
 
 #ifdef SAFETYTIMER
 	handleSafetyTimer();
@@ -9795,7 +9570,7 @@ if(0)
     }
   #endif
   check_axes_activity();
-  mmu_loop();
+  MMU2::mmu2.mmu_loop();
 
   // handle longpress
   if(lcd_longpress_trigger)
@@ -9866,6 +9641,7 @@ void UnconditionalStop()
     // Disable all heaters and unroll the temperature wait loop stack
     disable_heater();
     cancel_heatup = true;
+    heating_status = HeatingStatus::NO_HEATING;
 
     // Clear any saved printing state
     cancel_saved_printing();
@@ -9885,39 +9661,60 @@ void UnconditionalStop()
     CRITICAL_SECTION_END;
 }
 
-// Stop: Emergency stop used by overtemp functions which allows recovery
-//
-//   In addition to stopping the print, this prevents subsequent G[0-3] commands to be
-//   processed via USB (using "Stopped") until the print is resumed via M999 or
-//   manually started from scratch with the LCD.
+// Emergency stop used by overtemp functions which allows recovery
+// WARNING: This function is called *continuously* during a thermal failure.
 //
-//   Note that the current instruction is completely discarded, so resuming from Stop()
-//   will introduce either over/under extrusion on the current segment, and will not
-//   survive a power panic. Switching Stop() to use the pause machinery instead (with
-//   the addition of disabling the headers) could allow true recovery in the future.
-void Stop()
+// This either pauses (for thermal model errors) or stops *without recovery* depending on
+// "allow_pause". If pause is allowed, this forces a printer-initiated instantanenous pause (just
+// like an LCD pause) that bypasses the host pausing functionality. In this state the printer is
+// kept in busy state and *must* be recovered from the LCD.
+void ThermalStop(bool allow_pause)
 {
-  // Keep disabling heaters
-  disable_heater();
+    if(Stopped == false) {
+        Stopped = true;
+        if(allow_pause && (IS_SD_PRINTING || usb_timer.running())) {
+            if (!isPrintPaused) {
+                lcd_setalertstatuspgm(_T(MSG_PAUSED_THERMAL_ERROR), LCD_STATUS_CRITICAL);
+
+                // we cannot make a distinction for the host here, the pause must be instantaneous
+                // so we call the lcd_pause_print to save the print state internally. Thermal errors
+                // disable heaters and save the original temperatures to saved_*, which will get
+                // overwritten by stop_and_save_print_to_ram. For this corner-case, re-instate the
+                // original values after the pause handler is called.
+                float bed_temp = saved_bed_temperature;
+                float ext_temp = saved_extruder_temperature;
+                int fan_speed = saved_fan_speed;
+                lcd_pause_print();
+                saved_bed_temperature = bed_temp;
+                saved_extruder_temperature = ext_temp;
+                saved_fan_speed = fan_speed;
+            }
+        } else {
+            // We got a hard thermal error and/or there is no print going on. Just stop.
+            lcd_print_stop();
 
-  // Call the regular stop function if that's the first time during a new print
-  if(Stopped == false) {
-    Stopped = true;
-    lcd_print_stop();
-    Stopped_gcode_LastN = gcode_LastN; // Save last g_code for restart
+            // Also prevent further menu entry
+            menu_set_block(MENU_BLOCK_THERMAL_ERROR);
+        }
 
-    // Eventually report the stopped status (though this is usually overridden by a
-    // higher-priority alert status message)
-    SERIAL_ERROR_START;
-    SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED);
-    LCD_MESSAGERPGM(_T(MSG_STOPPED));
-  }
+        // Report the status on the serial, switch to a busy state
+        SERIAL_ERROR_START;
+        SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED);
 
-  // Return to the status screen to stop any pending menu action which could have been
-  // started by the user while stuck in the Stopped state. This also ensures the NEW
-  // error is immediately shown.
-  if (menu_menu != lcd_status_screen)
-      lcd_return_to_status();
+        // Eventually report the stopped status on the lcd (though this is usually overridden by a
+        // higher-priority alert status message)
+        LCD_MESSAGERPGM(_T(MSG_STOPPED));
+
+        // Make a warning sound! We cannot use Sound_MakeCustom as this would stop further moves.
+        // Turn on the speaker here (if not already), and turn it off when back in the main loop.
+        WRITE(BEEPER, HIGH);
+    }
+
+    // Return to the status screen to stop any pending menu action which could have been
+    // started by the user while stuck in the Stopped state. This also ensures the NEW
+    // error is immediately shown.
+    if (menu_menu != lcd_status_screen)
+        lcd_return_to_status();
 }
 
 bool IsStopped() { return Stopped; };
@@ -10029,23 +9826,9 @@ bool setTargetedHotend(int code, uint8_t &extruder)
       extruder = code_value_uint8();
     if(extruder >= EXTRUDERS) {
       SERIAL_ECHO_START;
-      switch(code){
-        case 104:
-          SERIAL_ECHORPGM(_n("M104 Invalid extruder "));////MSG_M104_INVALID_EXTRUDER
-          break;
-        case 105:
-          SERIAL_ECHORPGM(_n("M105 Invalid extruder "));////MSG_M105_INVALID_EXTRUDER
-          break;
-        case 109:
-          SERIAL_ECHORPGM(_n("M109 Invalid extruder "));////MSG_M109_INVALID_EXTRUDER
-          break;
-        case 218:
-          SERIAL_ECHORPGM(_n("M218 Invalid extruder "));////MSG_M218_INVALID_EXTRUDER
-          break;
-        case 221:
-          SERIAL_ECHORPGM(_n("M221 Invalid extruder "));////MSG_M221_INVALID_EXTRUDER
-          break;
-      }
+      serialprintPGM(PSTR("M"));
+      SERIAL_ECHO(code);
+      SERIAL_ECHOPGM(" Invalid extruder ");
       SERIAL_PROTOCOLLN((int)extruder);
       return true;
     }
@@ -10749,20 +10532,27 @@ void long_pause() //long pause print
 	start_pause_print = _millis();
 
     // Stop heaters
+    heating_status = HeatingStatus::NO_HEATING;
     setAllTargetHotends(0);
 
-	//lift z
-	current_position[Z_AXIS] += Z_PAUSE_LIFT;
-	clamp_to_software_endstops(current_position);
-	plan_buffer_line_curposXYZE(15);
+    // Lift z
+    raise_z(Z_PAUSE_LIFT);
 
-	//Move XY to side
-	current_position[X_AXIS] = X_PAUSE_POS;
-	current_position[Y_AXIS] = Y_PAUSE_POS;
-	plan_buffer_line_curposXYZE(50);
+    // Move XY to side
+    if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]) {
+        current_position[X_AXIS] = X_PAUSE_POS;
+        current_position[Y_AXIS] = Y_PAUSE_POS;
+        plan_buffer_line_curposXYZE(50);
+    }
 
-	// Turn off the print fan
-	fanSpeed = 0;
+    // did we come here from a thermal error?
+    if(get_temp_error()) {
+        // time to stop the error beep
+        WRITE(BEEPER, LOW);
+    } else {
+        // Turn off the print fan
+        fanSpeed = 0;
+    }
 }
 
 void serialecho_temperatures() {
@@ -10829,13 +10619,15 @@ void uvlo_()
     uint16_t feedrate_bckp;
     if (current_block && !pos_invalid)
     {
-        memcpy(saved_target, current_block->gcode_target, sizeof(saved_target));
+        memcpy(saved_start_position, current_block->gcode_start_position, sizeof(saved_start_position));
         feedrate_bckp = current_block->gcode_feedrate;
+        saved_segment_idx = current_block->segment_idx;
     }
     else
     {
-        saved_target[0] = SAVED_TARGET_UNSET;
+        saved_start_position[0] = SAVED_START_POSITION_UNSET;
         feedrate_bckp = feedrate;
+        saved_segment_idx = 0;
     }
 
     // From this point on and up to the print recovery, Z should not move during X/Y travels and
@@ -10866,6 +10658,7 @@ void uvlo_()
 
     // Enable stepper driver interrupt to move Z axis. This should be fine as the planner and
     // command queues are empty, SD card printing is disabled, usb is inhibited.
+    planner_aborted = false;
     sei();
 
     // Retract
@@ -10933,10 +10726,12 @@ void uvlo_()
 	eeprom_update_float((float*)(EEPROM_UVLO_TRAVEL_ACCELL), cs.travel_acceleration);
 
     // Store the saved target
-    eeprom_update_float((float*)(EEPROM_UVLO_SAVED_TARGET+0*4), saved_target[X_AXIS]);
-    eeprom_update_float((float*)(EEPROM_UVLO_SAVED_TARGET+1*4), saved_target[Y_AXIS]);
-    eeprom_update_float((float*)(EEPROM_UVLO_SAVED_TARGET+2*4), saved_target[Z_AXIS]);
-    eeprom_update_float((float*)(EEPROM_UVLO_SAVED_TARGET+3*4), saved_target[E_AXIS]);
+    eeprom_update_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+0*4), saved_start_position[X_AXIS]);
+    eeprom_update_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+1*4), saved_start_position[Y_AXIS]);
+    eeprom_update_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+2*4), saved_start_position[Z_AXIS]);
+    eeprom_update_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+3*4), saved_start_position[E_AXIS]);
+    
+    eeprom_update_word((uint16_t*)EEPROM_UVLO_SAVED_SEGMENT_IDX, saved_segment_idx);
 
 #ifdef LIN_ADVANCE
 	eeprom_update_float((float*)(EEPROM_UVLO_LA_K), extruder_advance_K);
@@ -10998,6 +10793,7 @@ void uvlo_tiny()
 
         // Enable stepper driver interrupt to move Z axis. This should be fine as the planner and
         // command queues are empty, SD card printing is disabled, usb is inhibited.
+        planner_aborted = false;
         sei();
 
         // The axis was moved: adjust Z as done on a regular UVLO.
@@ -11092,7 +10888,7 @@ ISR(INT4_vect) {
 	EIMSK &= ~(1 << 4); //disable INT4 interrupt to make sure that this code will be executed just once 
 	SERIAL_ECHOLNPGM("INT4");
     //fire normal uvlo only in case where EEPROM_UVLO is 0 or if IS_SD_PRINTING is 1. 
-     if(PRINTER_ACTIVE && (!(eeprom_read_byte((uint8_t*)EEPROM_UVLO)))) uvlo_();
+     if(printer_active() && (!(eeprom_read_byte((uint8_t*)EEPROM_UVLO)))) uvlo_();
      if(eeprom_read_byte((uint8_t*)EEPROM_UVLO)) uvlo_tiny();
 }
 
@@ -11119,7 +10915,7 @@ void recover_print(uint8_t automatic) {
   // Set the target bed and nozzle temperatures and wait.
 	sprintf_P(cmd, PSTR("M104 S%d"), target_temperature[active_extruder]);
 	enquecommand(cmd);
-	sprintf_P(cmd, PSTR("M190 S%d"), target_temperature_bed);
+	sprintf_P(cmd, PSTR("M140 S%d"), target_temperature_bed);
 	enquecommand(cmd);
 	sprintf_P(cmd, PSTR("M109 S%d"), target_temperature[active_extruder]);
 	enquecommand(cmd);
@@ -11205,10 +11001,12 @@ bool recover_machine_state_after_power_panic()
   extrudemultiply = (int)eeprom_read_word((uint16_t*)(EEPROM_EXTRUDEMULTIPLY));
 
   // 9) Recover the saved target
-  saved_target[X_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_TARGET+0*4));
-  saved_target[Y_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_TARGET+1*4));
-  saved_target[Z_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_TARGET+2*4));
-  saved_target[E_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_TARGET+3*4));
+  saved_start_position[X_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+0*4));
+  saved_start_position[Y_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+1*4));
+  saved_start_position[Z_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+2*4));
+  saved_start_position[E_AXIS] = eeprom_read_float((float*)(EEPROM_UVLO_SAVED_START_POSITION+3*4));
+  
+  saved_segment_idx = eeprom_read_word((uint16_t*)EEPROM_UVLO_SAVED_SEGMENT_IDX);
 
 #ifdef LIN_ADVANCE
   extruder_advance_K = eeprom_read_float((float*)EEPROM_UVLO_LA_K);
@@ -11222,7 +11020,7 @@ void restore_print_from_eeprom(bool mbl_was_active) {
 	int feedmultiply_rec;
 	uint8_t fan_speed_rec;
 	char cmd[48];
-	char filename[13];
+	char filename[FILENAME_LENGTH];
 	uint8_t depth = 0;
 	char dir_name[9];
 
@@ -11450,13 +11248,16 @@ void stop_and_save_print_to_ram(float z_move, float e_move)
   bool pos_invalid = XY_NO_RESTORE_FLAG;
   if (current_block && !pos_invalid)
   {
-      memcpy(saved_target, current_block->gcode_target, sizeof(saved_target));
+      memcpy(saved_start_position, current_block->gcode_start_position, sizeof(saved_start_position));
       saved_feedrate2 = current_block->gcode_feedrate;
+      saved_segment_idx = current_block->segment_idx;
+      // printf_P(PSTR("stop_and_save_print_to_ram: %f, %f, %f, %f, %u\n"), saved_start_position[0], saved_start_position[1], saved_start_position[2], saved_start_position[3], saved_segment_idx);
   }
   else
   {
-      saved_target[0] = SAVED_TARGET_UNSET;
+      saved_start_position[0] = SAVED_START_POSITION_UNSET;
       saved_feedrate2 = feedrate;
+      saved_segment_idx = 0;
   }
 
 	planner_abort_hard(); //abort printing
@@ -11465,10 +11266,10 @@ void stop_and_save_print_to_ram(float z_move, float e_move)
     if (pos_invalid) saved_pos[X_AXIS] = X_COORD_INVALID;
 
     saved_feedmultiply2 = feedmultiply; //save feedmultiply
-	saved_active_extruder = active_extruder; //save active_extruder
 	saved_extruder_temperature = degTargetHotend(active_extruder);
+	saved_bed_temperature = degTargetBed();
 	saved_extruder_relative_mode = axis_relative_modes & E_AXIS_MASK;
-	saved_fanSpeed = fanSpeed;
+	saved_fan_speed = fanSpeed;
 	cmdqueue_reset(); //empty cmdqueue
 	card.sdprinting = false;
 //	card.closefile();
@@ -11477,7 +11278,7 @@ void stop_and_save_print_to_ram(float z_move, float e_move)
   st_reset_timer();
 	sei();
 	if ((z_move != 0) || (e_move != 0)) { // extruder or z move
-#if 1
+
     // Rather than calling plan_buffer_line directly, push the move into the command queue so that
     // the caller can continue processing. This is used during powerpanic to save the state as we
     // move away from the print.
@@ -11509,16 +11310,19 @@ void stop_and_save_print_to_ram(float z_move, float e_move)
     // If this call is invoked from the main Arduino loop() function, let the caller know that the command
     // in the command queue is not the original command, but a new one, so it should not be removed from the queue.
     repeatcommand_front();
-#else
-		plan_buffer_line(saved_pos[X_AXIS], saved_pos[Y_AXIS], saved_pos[Z_AXIS] + z_move, saved_pos[E_AXIS] + e_move, homing_feedrate[Z_AXIS], active_extruder);
-    st_synchronize(); //wait moving
-    memcpy(current_position, saved_pos, sizeof(saved_pos));
-    set_destination_to_current();
-#endif
-    waiting_inside_plan_buffer_line_print_aborted = true; //unroll the stack
   }
 }
 
+void restore_extruder_temperature_from_ram() {
+    if (degTargetHotend(active_extruder) != saved_extruder_temperature)
+    {
+        setTargetHotendSafe(saved_extruder_temperature, active_extruder);
+        heating_status = HeatingStatus::EXTRUDER_HEATING;
+        wait_for_heater(_millis(), active_extruder);
+        heating_status = HeatingStatus::EXTRUDER_HEATING_COMPLETE;
+    }
+}
+
 //! @brief Restore print from ram
 //!
 //! Restore print saved by stop_and_save_print_to_ram(). Is blocking, restores
@@ -11538,17 +11342,11 @@ void restore_print_from_ram_and_continue(float e_move)
     if (fan_check_error == EFCE_FIXED) fan_check_error = EFCE_OK; //reenable serial stream processing if printing from usb
 #endif
 	
-//	for (int axis = X_AXIS; axis <= E_AXIS; axis++)
-//	    current_position[axis] = st_get_position_mm(axis);
-	active_extruder = saved_active_extruder; //restore active_extruder
-	fanSpeed = saved_fanSpeed;
-	if (degTargetHotend(saved_active_extruder) != saved_extruder_temperature)
-	{
-		setTargetHotendSafe(saved_extruder_temperature, saved_active_extruder);
-		heating_status = HeatingStatus::EXTRUDER_HEATING;
-		wait_for_heater(_millis(), saved_active_extruder);
-		heating_status = HeatingStatus::EXTRUDER_HEATING_COMPLETE;
-	}
+    // restore bed temperature (bed can be disabled during a thermal warning)
+    if (degBed() != saved_bed_temperature)
+        setTargetBed(saved_bed_temperature);
+	fanSpeed = saved_fan_speed;
+	restore_extruder_temperature_from_ram();
 	axis_relative_modes ^= (-saved_extruder_relative_mode ^ axis_relative_modes) & E_AXIS_MASK;
 	float e = saved_pos[E_AXIS] - e_move;
 	plan_set_e_position(e);
@@ -11599,14 +11397,14 @@ void restore_print_from_ram_and_continue(float e_move)
 	lcd_setstatuspgm(MSG_WELCOME);
     saved_printing_type = PRINTING_TYPE_NONE;
 	saved_printing = false;
-    waiting_inside_plan_buffer_line_print_aborted = true; //unroll the stack
+    planner_aborted = true; // unroll the stack
 }
 
 // Cancel the state related to a currently saved print
 void cancel_saved_printing()
 {
     eeprom_update_byte((uint8_t*)EEPROM_UVLO, 0);
-    saved_target[0] = SAVED_TARGET_UNSET;
+    saved_start_position[0] = SAVED_START_POSITION_UNSET;
     saved_printing_type = PRINTING_TYPE_NONE;
     saved_printing = false;
 }
@@ -11678,21 +11476,28 @@ void load_filament_final_feed()
 //! @par nozzle_temp nozzle temperature to load filament
 void M600_check_state(float nozzle_temp)
 {
-    lcd_change_fil_state = 0;
-    while (lcd_change_fil_state != 1)
+    uint8_t lcd_change_filament_state = 0;
+    while (lcd_change_filament_state != 1)
     {
-        lcd_change_fil_state = 0;
         KEEPALIVE_STATE(PAUSED_FOR_USER);
-        lcd_alright();
+        lcd_change_filament_state = lcd_alright();
         KEEPALIVE_STATE(IN_HANDLER);
-        switch(lcd_change_fil_state)
+        switch(lcd_change_filament_state)
         {
         // Filament failed to load so load it again
         case 2:
-            if (mmu_enabled)
-                mmu_M600_load_filament(false, nozzle_temp); //nonautomatic load; change to "wrong filament loaded" option?
-            else
+            if (MMU2::mmu2.Enabled()){
+                // Unload filament
+                mmu_M600_unload_filament();
+
+                // Ask to remove any old filament and load new
+                mmu_M600_wait_and_beep();
+
+                // After user clicks knob, MMU will load the filament
+                mmu_M600_load_filament(false, nozzle_temp);
+            } else {
                 M600_load_filament_movements();
+            }
             break;
 
         // Filament loaded properly but color is not clear
@@ -11810,46 +11615,23 @@ void M600_load_filament() {
 	//load_filament_time = _millis();
 	KEEPALIVE_STATE(PAUSED_FOR_USER);
 
-#ifdef PAT9125
-	fsensor_autoload_check_start();
-#endif //PAT9125
 	while(!lcd_clicked())
 	{
 		manage_heater();
 		manage_inactivity(true);
 #ifdef FILAMENT_SENSOR
-		if (fsensor_check_autoload())
-		{
-      Sound_MakeCustom(50,1000,false);
+		if (fsensor.getFilamentLoadEvent()) {
+			Sound_MakeCustom(50,1000,false);
 			break;
 		}
 #endif //FILAMENT_SENSOR
 	}
-#ifdef PAT9125
-	fsensor_autoload_check_stop();
-#endif //PAT9125
 	KEEPALIVE_STATE(IN_HANDLER);
 
-#ifdef FSENSOR_QUALITY
-	fsensor_oq_meassure_start(70);
-#endif //FSENSOR_QUALITY
-
 	M600_load_filament_movements();
 
-      Sound_MakeCustom(50,1000,false);
+	Sound_MakeCustom(50,1000,false);
 
-#ifdef FSENSOR_QUALITY
-	fsensor_oq_meassure_stop();
-
-	if (!fsensor_oq_result())
-	{
-		bool disable = lcd_show_fullscreen_message_yes_no_and_wait_P(_i("Fil. sensor response is poor, disable it?"), false, true);
-		lcd_update_enable(true);
-		lcd_update(2);
-		if (disable)
-			fsensor_disable();
-	}
-#endif //FSENSOR_QUALITY
 	lcd_update_enable(false);
 }
 
@@ -11871,8 +11653,6 @@ void marlin_wait_for_click()
     KEEPALIVE_STATE(busy_state_backup);
 }
 
-#define FIL_LOAD_LENGTH 60
-
 #ifdef PSU_Delta
 bool bEnableForce_z;
 

+ 500 - 0
Firmware/Prusa_farm.cpp

@@ -0,0 +1,500 @@
+#include "Prusa_farm.h"
+#include "macros.h"
+#include "Marlin.h"
+#include "cmdqueue.h"
+#include "temperature.h"
+#include "cardreader.h"
+#include "conv2str.h"
+#include "util.h"
+#include "ultralcd.h"
+#include "Filament_sensor.h"
+
+#ifdef PRUSA_FARM
+uint8_t farm_mode = 0;
+
+static ShortTimer NcTime;
+static uint8_t farm_timer = 8;
+static uint8_t status_number = 0;
+static bool no_response = false;
+#ifdef PRUSA_M28
+#define CHUNK_SIZE 64 // bytes
+#define SAFETY_MARGIN 1
+bool prusa_sd_card_upload = false;
+char chunk[CHUNK_SIZE+SAFETY_MARGIN];
+#endif
+
+
+static void prusa_statistics_err(char c);
+static void prusa_stat_printerstatus(uint8_t _status);
+static void prusa_stat_farm_number();
+static void prusa_stat_diameter();
+static void prusa_stat_temperatures();
+static void prusa_stat_printinfo();
+static void lcd_send_status();
+#ifdef FARM_CONNECT_MESSAGE
+static void proc_commands();
+static void lcd_connect_printer();
+#endif //FARM_CONNECT_MESSAGE
+#ifdef PRUSA_M28
+static void trace();
+#endif
+
+
+static void prusa_statistics_err(char c) {
+    SERIAL_ECHOPGM("{[ERR:");
+    SERIAL_ECHO(c);
+    SERIAL_ECHO(']');
+    prusa_stat_farm_number();
+}
+
+static void prusa_statistics_case0(uint8_t statnr) {
+    SERIAL_ECHO('{');
+    prusa_stat_printerstatus(statnr);
+    prusa_stat_farm_number();
+    prusa_stat_printinfo();
+}
+
+static void prusa_stat_printerstatus(uint8_t _status) {
+    SERIAL_ECHOPGM("[PRN:");
+    SERIAL_ECHO(_status);
+    SERIAL_ECHO(']');
+}
+
+static void prusa_stat_farm_number() {
+    SERIAL_ECHOPGM("[PFN:0]");
+}
+
+static void prusa_stat_diameter() {
+    SERIAL_ECHOPGM("[DIA:");
+    SERIAL_ECHO(eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM));
+    SERIAL_ECHO(']');
+}
+
+static void prusa_stat_temperatures() {
+    SERIAL_ECHOPGM("[ST0:");
+    SERIAL_ECHO(target_temperature[0]);
+    SERIAL_ECHOPGM("][STB:");
+    SERIAL_ECHO(target_temperature_bed);
+    SERIAL_ECHOPGM("][AT0:");
+    SERIAL_ECHO(current_temperature[0]);
+    SERIAL_ECHOPGM("][ATB:");
+    SERIAL_ECHO(current_temperature_bed);
+    SERIAL_ECHO(']');
+}
+
+static void prusa_stat_printinfo() {
+    SERIAL_ECHOPGM("[TFU:");
+    SERIAL_ECHO(total_filament_used);
+    SERIAL_ECHOPGM("][PCD:");
+    SERIAL_ECHO(itostr3(card.percentDone()));
+    SERIAL_ECHOPGM("][FEM:");
+    SERIAL_ECHO(itostr3(feedmultiply));
+    SERIAL_ECHOPGM("][FNM:");
+    SERIAL_ECHO(card.longFilename[0] ? card.longFilename : card.filename);
+    SERIAL_ECHOPGM("][TIM:");
+    if (starttime != 0) {
+        SERIAL_ECHO(_millis() / 1000 - starttime / 1000);
+    }
+    else {
+        SERIAL_ECHO(0);
+    }
+    SERIAL_ECHOPGM("][FWR:");
+    SERIAL_ECHORPGM(FW_VERSION_STR_P());
+    SERIAL_ECHO(']');
+    prusa_stat_diameter();
+}
+
+static void lcd_send_status() {
+    if (farm_mode && no_response && (NcTime.expired(NC_TIME * 1000))) {
+        //send important status messages periodicaly
+        prusa_statistics(8);
+        NcTime.start();
+#ifdef FARM_CONNECT_MESSAGE
+        lcd_connect_printer();
+#endif //FARM_CONNECT_MESSAGE
+    }
+}
+
+#ifdef FARM_CONNECT_MESSAGE
+static void proc_commands() {
+    if (buflen) {
+        process_commands();
+        if (!cmdbuffer_front_already_processed)
+            cmdqueue_pop_front();
+        cmdbuffer_front_already_processed = false;
+    }
+}
+
+static void lcd_connect_printer() {
+    lcd_update_enable(false);
+    lcd_clear();
+
+    int i = 0;
+    int t = 0;
+    lcd_puts_at_P(0, 0, PSTR("Connect printer to")); 
+    lcd_puts_at_P(0, 1, PSTR("monitoring or hold"));
+    lcd_puts_at_P(0, 2, PSTR("the knob to continue"));
+    while (no_response) {
+        i++;
+        t++;
+        delay_keep_alive(100);
+        proc_commands();
+        if (t == 10) {
+            prusa_statistics(8);
+            t = 0;
+        }
+        if (READ(BTN_ENC)) { //if button is not pressed
+            i = 0; 
+            lcd_puts_at_P(0, 3, PSTR("                    "));
+        }
+        if (i != 0)
+            lcd_putc_at((i * 20) / (NC_BUTTON_LONG_PRESS * 10), 3, LCD_STR_SOLID_BLOCK[0]);
+        if (i == NC_BUTTON_LONG_PRESS * 10)
+            no_response = false;
+    }
+    lcd_update_enable(true);
+    lcd_update(2);
+}
+#endif //FARM_CONNECT_MESSAGE
+
+#ifdef PRUSA_M28
+static void trace() {
+    Sound_MakeCustom(25,440,true);
+}
+
+void serial_read_stream() {
+
+    setAllTargetHotends(0);
+    setTargetBed(0);
+
+    lcd_clear();
+    lcd_puts_P(PSTR(" Upload in progress"));
+
+    // first wait for how many bytes we will receive
+    uint32_t bytesToReceive;
+
+    // receive the four bytes
+    char bytesToReceiveBuffer[4];
+    for (int i=0; i<4; i++) {
+        int data;
+        while ((data = MYSERIAL.read()) == -1) {};
+        bytesToReceiveBuffer[i] = data;
+
+    }
+
+    // make it a uint32
+    memcpy(&bytesToReceive, &bytesToReceiveBuffer, 4);
+
+    // we're ready, notify the sender
+    MYSERIAL.write('+');
+
+    // lock in the routine
+    uint32_t receivedBytes = 0;
+    while (prusa_sd_card_upload) {
+        int i;
+        for (i=0; i<CHUNK_SIZE; i++) {
+            int data;
+
+            // check if we're not done
+            if (receivedBytes == bytesToReceive) {
+                break;
+            }
+
+            // read the next byte
+            while ((data = MYSERIAL.read()) == -1) {};
+            receivedBytes++;
+
+            // save it to the chunk
+            chunk[i] = data;
+        }
+
+        // write the chunk to SD
+        card.write_command_no_newline(&chunk[0]);
+
+        // notify the sender we're ready for more data
+        MYSERIAL.write('+');
+
+        // for safety
+        manage_heater();
+
+        // check if we're done
+        if(receivedBytes == bytesToReceive) {
+            trace(); // beep
+            card.closefile();
+            prusa_sd_card_upload = false;
+            SERIAL_PROTOCOLLNRPGM(MSG_FILE_SAVED);
+        }
+    }
+}
+#endif //PRUSA_M28
+
+
+void prusa_statistics(uint8_t _message) {
+    const uint8_t _fil_nr = 0;
+    if (!farm_mode)
+        return;
+    
+    switch (_message) {
+    case 0: // default message
+        if (busy_state == PAUSED_FOR_USER) {
+            prusa_statistics_case0(15);
+        }
+        else if (isPrintPaused) {
+            prusa_statistics_case0(14);
+        }
+        else if (IS_SD_PRINTING || loading_flag) {
+            prusa_statistics_case0(4);
+        }
+        else {
+            SERIAL_ECHO('{');
+            prusa_stat_printerstatus(1);
+            prusa_stat_farm_number();
+            prusa_stat_diameter();
+            status_number = 1;
+        }
+        break;
+
+    case 1: // 1 heating
+        SERIAL_ECHO('{');
+        prusa_stat_printerstatus(2);
+        prusa_stat_farm_number();
+        status_number = 2;
+        farm_timer = 1;
+        break;
+
+    case 2: // heating done
+        SERIAL_ECHO('{');
+        prusa_stat_printerstatus(3);
+        prusa_stat_farm_number();
+        SERIAL_ECHOLN('}');
+        status_number = 3;
+        farm_timer = 1;
+
+        if (IS_SD_PRINTING || loading_flag) {
+            SERIAL_ECHO('{');
+            prusa_stat_printerstatus(4);
+            prusa_stat_farm_number();
+            status_number = 4;
+        }
+        else {
+            SERIAL_ECHO('{');
+            prusa_stat_printerstatus(3);
+            prusa_stat_farm_number();
+            status_number = 3;
+        }
+        farm_timer = 1;
+        break;
+
+    case 3: // filament change
+        // must do a return here to prevent doing SERIAL_ECHOLN("}") at the very end of this function
+        // saved a considerable amount of FLASH
+        return;
+        break;
+    case 4: // print succesfull
+        SERIAL_ECHOPGM("{[RES:1][FIL:");
+        MYSERIAL.print(int(_fil_nr));
+        SERIAL_ECHO(']');
+        prusa_stat_printerstatus(status_number);
+        prusa_stat_farm_number();
+        farm_timer = 2;
+        break;
+    case 5: // print not succesfull
+        SERIAL_ECHOPGM("{[RES:0][FIL:");
+        MYSERIAL.print(int(_fil_nr));
+        SERIAL_ECHO(']');
+        prusa_stat_printerstatus(status_number);
+        prusa_stat_farm_number();
+        farm_timer = 2;
+        break;
+    case 6: // print done
+        SERIAL_ECHOPGM("{[PRN:8]");
+        prusa_stat_farm_number();
+        status_number = 8;
+        farm_timer = 2;
+        break;
+    case 7:        // print done - stopped
+        SERIAL_ECHOPGM("{[PRN:9]");
+        prusa_stat_farm_number();
+        status_number = 9;
+        farm_timer = 2;
+        break;
+    case 8: // printer started
+        SERIAL_ECHOPGM("{[PRN:0]");
+        prusa_stat_farm_number();
+        status_number = 0;
+        farm_timer = 2;
+        break;
+    case 20: // echo farm no
+        SERIAL_ECHO('{');
+        prusa_stat_printerstatus(status_number);
+        prusa_stat_farm_number();
+        farm_timer = 4;
+        break;
+    case 21: // temperatures
+        SERIAL_ECHO('{');
+        prusa_stat_temperatures();
+        prusa_stat_farm_number();
+        prusa_stat_printerstatus(status_number);
+        break;
+    case 22: // waiting for filament change
+        SERIAL_ECHOPGM("{[PRN:5]");
+        prusa_stat_farm_number();
+        status_number = 5;
+        break;
+
+    case 90: // Error - Thermal Runaway
+        prusa_statistics_err('1');
+        break;
+    case 91: // Error - Thermal Runaway Preheat
+        prusa_statistics_err('2');
+        break;
+    case 92: // Error - Min temp
+        prusa_statistics_err('3');
+        break;
+    case 93: // Error - Max temp
+        prusa_statistics_err('4');
+        break;
+
+    case 99: // heartbeat
+        SERIAL_ECHOPGM("{[PRN:99]");
+        prusa_stat_temperatures();
+        prusa_stat_farm_number();
+        break;
+    }
+    SERIAL_ECHOLN('}');
+}
+
+void prusa_statistics_update_from_status_screen() {
+    if (farm_mode) {
+        farm_timer--;
+        if (farm_timer < 1) {
+            farm_timer = 10;
+            prusa_statistics(0);
+        }
+        switch (farm_timer) {
+        case 8:
+            prusa_statistics(21);
+            if(loading_flag)
+                prusa_statistics(22);
+            break;
+        case 5:
+            if (IS_SD_PRINTING)
+                prusa_statistics(20);
+            break;
+        }
+    }
+}
+
+void prusa_statistics_update_from_lcd_update() {
+    lcd_send_status();
+}
+
+void farm_mode_init() {
+    farm_mode = eeprom_read_byte((uint8_t*)EEPROM_FARM_MODE); 
+    if (farm_mode == 0xFF) {
+        farm_mode = false; //if farm_mode has not been stored to eeprom yet and farm number is set to zero or EEPROM is fresh, deactivate farm mode
+        eeprom_update_byte((uint8_t*)EEPROM_FARM_MODE, farm_mode);
+    }
+    else if (farm_mode) {
+        no_response = true; //we need confirmation by recieving PRUSA thx
+        prusa_statistics(8);
+#ifdef HAS_SECOND_SERIAL_PORT
+        selectedSerialPort = 1;
+#endif //HAS_SECOND_SERIAL_PORT
+        MYSERIAL.begin(BAUDRATE);
+#ifdef FILAMENT_SENSOR
+        //to be converted to Filament_sensor.h...
+        //disabled filament autoload (PFW360)
+        fsensor.setAutoLoadEnabled(false);
+#endif //FILAMENT_SENSOR
+        // ~ FanCheck -> on
+        eeprom_update_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED, true);
+    }
+}
+
+bool farm_prusa_code_seen() {
+    if (!farm_mode)
+        return false;
+    
+    if (code_seen_P(PSTR("PRN"))) { // PRUSA PRN
+        printf_P(_N("%u"), status_number);
+    }
+    else if (code_seen_P(PSTR("thx"))) { // PRUSA thx
+        no_response = false;
+    }
+#ifdef PRUSA_M28
+    else if (code_seen_P(PSTR("M28"))) { // PRUSA M28
+        trace();
+        prusa_sd_card_upload = true;
+        card.openFileWrite(strchr_pointer+4);
+    }
+#endif //PRUSA_M28
+    else if (code_seen_P(PSTR("fv"))) { // PRUSA fv
+        // get file version
+#ifdef SDSUPPORT
+        card.openFileReadFilteredGcode(strchr_pointer + 3, true);
+        while (true) {
+            uint16_t readByte = card.getFilteredGcodeChar();
+            MYSERIAL.write(readByte);
+            if (readByte == '\n') {
+                break;
+            }
+        }
+        card.closefile();
+#endif // SDSUPPORT
+    }
+    else {
+        return false;
+    }
+    
+    return true;
+}
+
+void farm_gcode_g98() {
+    farm_mode = 1;
+    eeprom_update_byte((unsigned char *)EEPROM_FARM_MODE, farm_mode);
+    SilentModeMenu = SILENT_MODE_OFF;
+    eeprom_update_byte((unsigned char *)EEPROM_SILENT, SilentModeMenu);
+    fCheckModeInit(); // alternatively invoke printer reset
+}
+
+void farm_gcode_g99() {
+    farm_disable();
+    lcd_update(2);
+    fCheckModeInit(); // alternatively invoke printer reset
+}
+
+void farm_disable() {
+    farm_mode = false;
+    eeprom_update_byte((uint8_t*)EEPROM_FARM_MODE, farm_mode);
+}
+
+#else //PRUSA_FARM
+
+void prusa_statistics(_UNUSED uint8_t message) {
+}
+
+void prusa_statistics_update_from_status_screen() {
+}
+
+void prusa_statistics_update_from_lcd_update() {
+}
+
+void farm_mode_init() {
+}
+
+bool farm_prusa_code_seen() {
+    return false;
+}
+
+void farm_gcode_g98() {
+}
+
+void farm_gcode_g99() {
+}
+
+void farm_disable() {
+}
+
+#endif //PRUSA_FARM
+

+ 32 - 0
Firmware/Prusa_farm.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include <inttypes.h>
+#include "config.h"
+
+#define FARM_PREHEAT_HOTEND_TEMP 250
+#define FARM_PREHEAT_HPB_TEMP 80
+
+#define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul)
+#define NC_TIME 10 //time in s for periodic important status messages sending which needs reponse from monitoring
+#define NC_BUTTON_LONG_PRESS 15 //time in s
+
+//#define FARM_CONNECT_MESSAGE
+
+#ifdef PRUSA_FARM
+extern uint8_t farm_mode;
+#else
+#define farm_mode 0
+#endif
+
+#ifdef PRUSA_M28
+extern bool prusa_sd_card_upload;
+extern void serial_read_stream();
+#endif
+extern void prusa_statistics(uint8_t _message);
+extern void prusa_statistics_update_from_status_screen();
+extern void prusa_statistics_update_from_lcd_update();
+extern void farm_mode_init();
+extern bool farm_prusa_code_seen();
+extern void farm_gcode_g98();
+extern void farm_gcode_g99();
+extern void farm_disable();

+ 1 - 1
Firmware/SdBaseFile.cpp

@@ -1015,7 +1015,7 @@ void SdBaseFile::printFatTime( uint16_t fatTime) {
  * the value zero, false, is returned for failure.
  */
 bool SdBaseFile::printName() {
-  char name[13];
+  char name[FILENAME_LENGTH];
   if (!getFilename(name)) return false;
   MYSERIAL.print(name);
   return true;

+ 82 - 0
Firmware/SpoolJoin.cpp

@@ -0,0 +1,82 @@
+#include "SpoolJoin.h"
+#include "Marlin.h"
+#include "eeprom.h"
+#include "messages.h"
+#include "language.h"
+
+namespace SpoolJoin {
+
+SpoolJoin spooljoin;
+
+SpoolJoin::SpoolJoin()
+    : status(EEPROM::Unknown)
+    , currentMMUSlot(0)
+{
+}
+
+void SpoolJoin::updateSpoolJoinStatus(EEPROM newStatus)
+{
+    status = newStatus;
+    eeprom_write_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)status);
+}
+
+void SpoolJoin::initSpoolJoinStatus()
+{
+    EEPROM currentStatus = (EEPROM)eeprom_read_byte((uint8_t*)EEPROM_SPOOL_JOIN);
+    if( currentStatus == EEPROM::Empty)
+    {
+        // By default SpoolJoin is disabled
+        updateSpoolJoinStatus(EEPROM::Disabled);
+    } else {
+        updateSpoolJoinStatus(currentStatus);
+    }
+
+    // Useful information to see during bootup
+    SERIAL_ECHOPGM("SpoolJoin is ");
+    if (isSpoolJoinEnabled())
+    {
+        SERIAL_ECHOLNRPGM(_O(MSG_ON));
+    } else {
+        SERIAL_ECHOLNRPGM(_O(MSG_OFF));
+    }
+}
+
+void SpoolJoin::toggleSpoolJoin()
+{
+    if (eeprom_read_byte((uint8_t*)EEPROM_SPOOL_JOIN) == (uint8_t)EEPROM::Disabled)
+    {
+        eeprom_write_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)EEPROM::Enabled);
+    } else {
+        eeprom_write_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)EEPROM::Disabled);
+    }
+}
+
+bool SpoolJoin::isSpoolJoinEnabled()
+{
+    if(eeprom_read_byte((uint8_t*)EEPROM_SPOOL_JOIN) == (uint8_t)EEPROM::Enabled) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void SpoolJoin::setSlot(uint8_t slot)
+{
+    currentMMUSlot = slot;
+}
+
+uint8_t SpoolJoin::nextSlot()
+{
+    SERIAL_ECHOPGM("SpoolJoin: ");
+    SERIAL_ECHO((int)currentMMUSlot);
+
+    if (currentMMUSlot >= 4) currentMMUSlot = 0;
+    else currentMMUSlot++;
+
+    SERIAL_ECHOPGM(" -> ");
+    SERIAL_ECHOLN((int)currentMMUSlot);
+
+    return currentMMUSlot;
+}
+
+}

+ 60 - 0
Firmware/SpoolJoin.h

@@ -0,0 +1,60 @@
+/// @file
+#pragma once
+#include <stdint.h>
+#include "eeprom.h"
+
+// See documentation here: https://help.prusa3d.com/article/spooljoin-mmu2s_134252
+
+namespace SpoolJoin {
+
+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 Called when EEPROM is ready to be read
+    void initSpoolJoinStatus();
+
+    /// @brief Enable SpoolJoin
+    inline void enableSpoolJoin() { updateSpoolJoinStatus(EEPROM::Enabled); };
+
+    /// @brief Disable SpoolJoin
+    inline void disableSpoolJoin() { updateSpoolJoinStatus(EEPROM::Disabled); };
+
+    /// @brief Toggle SpoolJoin
+    static void toggleSpoolJoin();
+
+    /// @brief Check if SpoolJoin is enabled
+    /// @returns true if enabled, false if disabled
+    bool isSpoolJoinEnabled();
+
+    /// @brief Update the saved MMU slot number so SpoolJoin can determine the next slot to use
+    /// @param slot number of the slot to set
+    void setSlot(uint8_t slot);
+
+    /// @brief Fetch the next slot number should count from 0 to 4.
+    /// When filament slot 4 is depleted, the next slot should be 0.
+    /// @returns the next slot, ranges from 0 to 4
+    uint8_t nextSlot();
+
+private:
+    /// @brief Update EEPROM
+    /// @param newStatus Status to write into EEPROM
+    void updateSpoolJoinStatus(EEPROM newStatus);
+
+    /// @brief SpoolJoin status
+    enum EEPROM status;
+
+    /// @brief Currently used slot, ranges from 0 to 4
+    uint8_t currentMMUSlot;
+};
+
+extern SpoolJoin spooljoin;
+
+} // namespace SpoolJoin

+ 105 - 0
Firmware/Tcodes.cpp

@@ -0,0 +1,105 @@
+#include "Tcodes.h"
+#include "SpoolJoin.h"
+#include "Marlin.h"
+#include "language.h"
+#include "messages.h"
+#include "mmu2.h"
+#include "stepper.h"
+#include "ultralcd.h"
+#include <avr/pgmspace.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+
+static const char duplicate_Tcode_ignored[] PROGMEM = "Duplicate T-code ignored.";
+
+inline bool IsInvalidTCode(char *const s, uint8_t i) { 
+    return ((s[i] < '0' || s[i] > '4') && s[i] != '?' && s[i] != 'x' && s[i] != 'c'); 
+}
+
+inline void TCodeInvalid() { 
+    SERIAL_ECHOLNPGM("Invalid T code."); 
+}
+
+struct SChooseFromMenu {
+    uint8_t slot:7;
+    uint8_t loadToNozzle:1;
+    inline constexpr SChooseFromMenu(uint8_t slot, bool loadToNozzle):slot(slot), loadToNozzle(loadToNozzle){}
+    inline constexpr SChooseFromMenu():slot(0), loadToNozzle(false) { }
+};
+
+SChooseFromMenu TCodeChooseFromMenu() {
+    if (MMU2::mmu2.Enabled()) {
+        return SChooseFromMenu( choose_menu_P(_T(MSG_SELECT_FILAMENT), _T(MSG_FILAMENT)), true );
+    } else {
+        return SChooseFromMenu( choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER)), false );
+    }
+}
+
+void TCodes(char *const strchr_pointer, uint8_t codeValue) {
+    uint8_t index = 1;
+    for ( /*nothing*/ ; strchr_pointer[index] == ' ' || strchr_pointer[index] == '\t'; index++)
+        ;
+
+    strchr_pointer[index] = tolower(strchr_pointer[index]);
+
+    if (IsInvalidTCode(strchr_pointer, index)){
+        TCodeInvalid();
+    } else if (strchr_pointer[index] == 'x'){
+        // load to extruder gears; if mmu is not present do nothing
+        if (MMU2::mmu2.Enabled()) {
+            MMU2::mmu2.tool_change(strchr_pointer[index], choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER)));
+        }
+    } else if (strchr_pointer[index] == 'c'){
+        // load from extruder gears to nozzle (nozzle should be preheated)
+        if (MMU2::mmu2.Enabled()) {
+            MMU2::mmu2.tool_change(strchr_pointer[index], MMU2::mmu2.get_current_tool());
+        }
+    } else {
+        SChooseFromMenu selectedSlot;
+        if (strchr_pointer[index] == '?') {
+            selectedSlot = TCodeChooseFromMenu();
+        /*} else if (MMU2::mmu2.Enabled() && SpoolJoin::spooljoin.isSpoolJoinEnabled()) {
+            // TODO: What if the next slot has no filament?
+            selectedSlot.slot = SpoolJoin::spooljoin.nextSlot();*/
+        } else {
+            selectedSlot.slot = codeValue;
+        }
+        st_synchronize();
+
+        if (MMU2::mmu2.Enabled()) {
+            if (selectedSlot.slot == MMU2::mmu2.get_current_tool()){ 
+                // don't execute the same T-code twice in a row
+                puts_P(duplicate_Tcode_ignored);
+            } else {
+#if defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
+                if (EEPROM_MMU_CUTTER_ENABLED_always == eeprom_read_byte((uint8_t *)EEPROM_MMU_CUTTER_ENABLED)) {
+                    MMU2::mmu2.cut_filament(selectedSlot.slot);
+                }
+#endif // defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
+                if (selectedSlot.loadToNozzle){ // for single material usage with mmu
+                    MMU2::mmu2.load_filament_to_nozzle(selectedSlot.slot);
+                } else {
+                    MMU2::mmu2.tool_change(selectedSlot.slot);
+                }
+            }
+        } else {
+            if (selectedSlot.slot >= EXTRUDERS) {
+                SERIAL_ECHO_START;
+                SERIAL_ECHO('T');
+                SERIAL_ECHOLN(selectedSlot.slot + '0');
+                SERIAL_ECHOLNRPGM(_n("Invalid extruder")); ////MSG_INVALID_EXTRUDER
+            } else {
+// @@TODO               if (code_seen('F')) {
+//                    next_feedrate = code_value();
+//                    if (next_feedrate > 0.0) {
+//                        feedrate = next_feedrate;
+//                    }
+//                }
+                SERIAL_ECHO_START;
+                SERIAL_ECHORPGM(_n("Active Extruder: ")); ////MSG_ACTIVE_EXTRUDER
+                SERIAL_ECHOLN(active_extruder + '0'); // this is not changed in our FW at all, can be optimized away
+            }
+        }
+    }
+}

+ 5 - 0
Firmware/Tcodes.h

@@ -0,0 +1,5 @@
+/// @file
+#pragma once
+#include <stdint.h>
+
+void TCodes(char * const strchr_pointer, uint8_t codeValue);

+ 6 - 0
Firmware/Timer.cpp

@@ -77,5 +77,11 @@ T Timer<T>::elapsed() {
   return m_isRunning ? (_millis() - m_started) : 0;
 }
 
+template<typename T>
+bool Timer<T>::expired_cont(T msPeriod)
+{
+    return !m_isRunning || expired(msPeriod);
+}
+
 template class Timer<unsigned long>;
 template class Timer<unsigned short>;

+ 3 - 2
Firmware/Timer.h

@@ -21,8 +21,9 @@ public:
     void start();
     void stop(){m_isRunning = false;}
     bool running()const {return m_isRunning;}
-    bool expired(T msPeriod);
-    T elapsed();
+    bool expired(T msPeriod); // returns true only once after expiration, then stops running
+    T elapsed(); // returns the time in milliseconds since the timer was started or 0 otherwise
+    bool expired_cont(T msPeriod); // return true when continuosly when expired / not running
 protected:
     T started()const {return m_started;}
 private:

+ 0 - 95
Firmware/adc.c

@@ -1,95 +0,0 @@
-//adc.c
-
-#include "adc.h"
-#include <stdio.h>
-#include <avr/io.h>
-#include <avr/pgmspace.h>
-#include "pins.h"
-
-uint8_t adc_state;
-uint8_t adc_count;
-uint16_t adc_values[ADC_CHAN_CNT];
-uint16_t adc_sim_mask;
-
-
-#ifdef ADC_CALLBACK
-	extern void ADC_CALLBACK(void);
-#endif //ADC_CALLBACK
-
-
-void adc_init(void)
-{
-	puts_P(PSTR("adc_init"));
-	adc_sim_mask = 0x00;
-	ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
-	ADMUX |= (1 << REFS0);
-	ADCSRA |= (1 << ADEN);
-//	ADCSRA |= (1 << ADIF) | (1 << ADSC);
-	DIDR0 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) & 0xff);
-	DIDR2 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) >> 8);
-	adc_reset();
-//	adc_sim_mask = 0b0101;
-//	adc_sim_mask = 0b100101;
-//	adc_values[0] = 1023 * 16;
-//	adc_values[2] = 1023 * 16;
-//	adc_values[5] = 1002 * 16;
-}
-
-void adc_reset(void)
-{
-	adc_state = 0;
-	adc_count = 0;
-	uint8_t i; for (i = 0; i < ADC_CHAN_CNT; i++)
-	if ((adc_sim_mask & (1 << i)) == 0)
-		adc_values[i] = 0;
-}
-
-void adc_setmux(uint8_t ch)
-{
-	ch &= 0x0f;
-	if (ch & 0x08) ADCSRB |= (1 << MUX5);
-	else ADCSRB &= ~(1 << MUX5);
-	ADMUX = (ADMUX & ~(0x07)) | (ch & 0x07);
-}
-
-uint8_t adc_chan(uint8_t index)
-{
-	uint8_t chan = 0;
-	uint16_t mask = 1;
-	while (mask)
-	{
-		if ((mask & ADC_CHAN_MSK) && (index-- == 0)) break;
-		mask <<= 1;
-		chan++;
-	}
-	return chan;
-}
-
-void adc_cycle(void)
-{
-	if (adc_state & 0x80)
-	{
-		uint8_t index = adc_state & 0x0f;
-		if ((adc_sim_mask & (1 << index)) == 0)
-			adc_values[index] += ADC;
-		if (++index >= ADC_CHAN_CNT)
-		{
-			index = 0;
-			adc_count++;
-			if (adc_count >= ADC_OVRSAMPL)
-			{
-#ifdef ADC_CALLBACK
-				ADC_CALLBACK();
-#endif //ADC_CALLBACK
-				adc_reset();
-			}
-		}
-		adc_setmux(adc_chan(index));
-		adc_state = index;
-	}
-	else
-	{
-		ADCSRA |= (1 << ADSC); //start conversion
-		adc_state |= 0x80;
-	}
-}

+ 81 - 0
Firmware/adc.cpp

@@ -0,0 +1,81 @@
+#include "adc.h"
+#include <stdio.h>
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+#include <string.h>
+#include "pins.h"
+
+static uint8_t adc_count; //used for oversampling
+static uint8_t adc_channel_idx; //bitmask index
+volatile uint8_t adc_channel; //regular index
+volatile uint16_t adc_values[ADC_CHAN_CNT];
+
+static void adc_reset();
+static void adc_setmux(uint8_t ch);
+
+void adc_init()
+{
+	puts_P(PSTR("adc_init"));
+    DIDR0 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) & 0xff); //disable digital inputs PORTF
+    DIDR2 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) >> 8); //disable digital inputs PORTK
+    ADMUX |= (1 << REFS0); //use AVCC as reference
+
+    //enable ADC, set prescaler/128, enable interrupt
+    ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADIF) | (1 << ADIE);
+}
+
+static void adc_reset()
+{
+    static const uint8_t first_channel_idx = 0;
+    static_assert((1 << first_channel_idx) & ADC_CHAN_MSK);
+
+    ADCSRA &= ~(1 << ADSC); //stop conversion just in case
+    adc_count = 0;
+    adc_channel = 0;
+    adc_channel_idx = first_channel_idx;
+    adc_setmux(adc_channel_idx);
+    memset((void*)adc_values, 0, sizeof(adc_values));
+}
+
+static void adc_setmux(uint8_t ch)
+{
+	ch &= 0x0f;
+	if (ch & 0x08) ADCSRB |= (1 << MUX5);
+	else ADCSRB &= ~(1 << MUX5);
+	ADMUX = (ADMUX & ~(0x07)) | (ch & 0x07);
+}
+
+void adc_start_cycle() {
+	adc_reset();
+	ADCSRA |= (1 << ADSC); //start conversion
+}
+
+#ifdef ADC_CALLBACK
+extern void ADC_CALLBACK();
+#endif //ADC_CALLBACK
+
+ISR(ADC_vect)
+{
+    adc_values[adc_channel] += ADC;
+    if (++adc_count == ADC_OVRSAMPL)
+    {
+        // go to the next channel
+        if (++adc_channel == ADC_CHAN_CNT) {
+#ifdef ADC_CALLBACK
+            ADC_CALLBACK();
+#endif
+            return; // do not start the next measurement since there are no channels remaining
+        }
+
+        // find the next channel
+        while (++adc_channel_idx) {
+            if (ADC_CHAN_MSK & (1 << adc_channel_idx)) {
+                adc_setmux(adc_channel_idx);
+                adc_count = 0;
+                break;
+            }
+        }
+    }
+    ADCSRA |= (1 << ADSC); //start conversion
+}

+ 7 - 27
Firmware/adc.h

@@ -1,15 +1,8 @@
-//adc.h
-#ifndef _ADC_H
-#define _ADC_H
+#pragma once
 
 #include <inttypes.h>
 #include "config.h"
 
-
-#if defined(__cplusplus)
-extern "C" {
-#endif //defined(__cplusplus)
-
 /*
 http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html
 */
@@ -22,24 +15,11 @@ http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html
 # error "ADC_CHAN_MSK oes not match ADC_CHAN_CNT"
 #endif
 
-extern uint8_t adc_state;
-extern uint8_t adc_count;
-extern uint16_t adc_values[ADC_CHAN_CNT];
-extern uint16_t adc_sim_mask;
-
-
-extern void adc_init(void);
-
-extern void adc_reset(void);
-
-extern void adc_setmux(uint8_t ch);
-
-extern uint8_t adc_chan(uint8_t index);
-
-extern void adc_cycle(void);
+#define VOLT_DIV_REF 5 //[V]
 
+extern volatile uint8_t adc_channel;
+extern volatile uint16_t adc_values[ADC_CHAN_CNT];
 
-#if defined(__cplusplus)
-}
-#endif //defined(__cplusplus)
-#endif //_ADC_H
+extern void adc_init();
+extern void adc_start_cycle(); //should be called from an atomic context only
+static inline bool adc_cycle_done() { return adc_channel >= ADC_CHAN_CNT; }

+ 9 - 9
Firmware/cardreader.cpp

@@ -7,6 +7,7 @@
 #include "stepper.h"
 #include "temperature.h"
 #include "language.h"
+#include "Prusa_farm.h"
 
 #ifdef SDSUPPORT
 
@@ -290,7 +291,7 @@ void CardReader::getDirName(char* name, uint8_t level)
 		workDirParents[level].getFilename(name);
 }
 
-uint16_t CardReader::getWorkDirDepth() {
+uint8_t CardReader::getWorkDirDepth() {
 	return workDirDepth;
 }
 
@@ -304,7 +305,7 @@ void CardReader::getAbsFilename(char *t)
     while(*t!=0 && cnt< MAXPATHNAMELENGTH) 
     {t++;cnt++;}  //crawl counter forward.
   }
-  if(cnt<MAXPATHNAMELENGTH-13)
+  if(cnt < MAXPATHNAMELENGTH - FILENAME_LENGTH)
     file.getFilename(t);
   else
     t[0]=0;
@@ -738,7 +739,7 @@ bool CardReader::chdir(const char * relpath, bool doPresort)
     puts(relpath);
 
     if (workDirDepth < MAX_DIR_DEPTH) {
-      for (int d = ++workDirDepth; d--;)
+      for (uint8_t d = ++workDirDepth; d--;)
         workDirParents[d+1] = workDirParents[d];
       workDirParents[0]=*parent;
     }
@@ -760,7 +761,7 @@ void CardReader::updir()
   {
     --workDirDepth;
     workDir = workDirParents[0];
-    for (unsigned int d = 0; d < workDirDepth; d++)
+    for (uint8_t d = 0; d < workDirDepth; d++)
     {
         workDirParents[d] = workDirParents[d+1];
     }
@@ -858,7 +859,7 @@ void CardReader::presort() {
 #endif
 
       uint16_t counter = 0;
-      menu_progressbar_init(fileCnt * fileCnt / 2, _i("Sorting files"));
+      menu_progressbar_init(fileCnt * fileCnt / 2, _T(MSG_SORTING_FILES));
 
       for (uint16_t i = 1; i < fileCnt; ++i){
         // if (!IS_SD_INSERTED) return;
@@ -925,7 +926,7 @@ void CardReader::presort() {
 #endif
 
 			uint16_t counter = 0;
-			menu_progressbar_init(0.5*(fileCnt - 1)*(fileCnt), _i("Sorting files"));
+			menu_progressbar_init(0.5*(fileCnt - 1)*(fileCnt), _T(MSG_SORTING_FILES));
 
 			for (uint16_t i = fileCnt; --i;) {
 				if (!IS_SD_INSERTED) return;
@@ -1008,9 +1009,10 @@ void CardReader::flush_presort() {
 void CardReader::printingHasFinished()
 {
     st_synchronize();
+    file.close();
+
     if(file_subcall_ctr>0) //heading up to a parent file that called current as a procedure.
     {
-      file.close();
       file_subcall_ctr--;
       openFileReadFilteredGcode(filenames[file_subcall_ctr],true);
       setIndex(filespos[file_subcall_ctr]);
@@ -1018,8 +1020,6 @@ void CardReader::printingHasFinished()
     }
     else
     {
-      quickStop();
-      file.close();
       sdprinting = false;
       if(SD_FINISHED_STEPPERRELEASE)
       {

+ 3 - 3
Firmware/cardreader.h

@@ -53,7 +53,7 @@ public:
   void getAbsFilename(char *t);
   void printAbsFilenameFast();
   void getDirName(char* name, uint8_t level);
-  uint16_t getWorkDirDepth();
+  uint8_t getWorkDirDepth();
   
 
   void ls(ls_param params);
@@ -89,7 +89,7 @@ public:
   bool logging;
   bool sdprinting ;  
   bool cardOK ;
-  char filename[13];
+  char filename[FILENAME_LENGTH];
   // There are scenarios when simple modification time is not enough (on MS Windows)
   // Therefore these timestamps hold the most recent one of creation/modification date/times
   uint16_t crmodTime, crmodDate;
@@ -103,7 +103,7 @@ public:
   char dir_names[MAX_DIR_DEPTH][9];
 private:
   SdFile root,*curDir,workDir,workDirParents[MAX_DIR_DEPTH];
-  uint16_t workDirDepth;
+  uint8_t workDirDepth;
 
   // Sort files and folders alphabetically.
 #ifdef SDCARD_SORT_ALPHA

+ 64 - 92
Firmware/cmdqueue.cpp

@@ -1,6 +1,8 @@
+#include <util/atomic.h>
 #include "cmdqueue.h"
 #include "cardreader.h"
 #include "ultralcd.h"
+#include "Prusa_farm.h"
 
 // Reserve BUFSIZE lines of length MAX_CMD_SIZE plus CMDBUFFER_RESERVE_FRONT.
 char cmdbuffer[BUFSIZE * (MAX_CMD_SIZE + 1) + CMDBUFFER_RESERVE_FRONT];
@@ -25,11 +27,7 @@ bool comment_mode = false;
 char *strchr_pointer; // just a pointer to find chars in the command string like X, Y, Z, E, etc
 
 ShortTimer serialTimeoutTimer;
-
-long gcode_N = 0;
 long gcode_LastN = 0;
-long Stopped_gcode_LastN = 0;
-
 uint32_t sdpos_atomic = 0;
 
 
@@ -155,7 +153,7 @@ static bool cmdqueue_could_enqueue_front(size_t len_asked)
 // len_asked does not contain the zero terminator size.
 // This function may update bufindw, therefore for the power panic to work, this function must be called
 // with the interrupts disabled!
-static bool cmdqueue_could_enqueue_back(size_t len_asked, bool atomic_update = false)
+static bool __attribute__((noinline)) cmdqueue_could_enqueue_back(size_t len_asked)
 {
     // MAX_CMD_SIZE has to accommodate the zero terminator.
     if (len_asked >= MAX_CMD_SIZE)
@@ -165,61 +163,29 @@ static bool cmdqueue_could_enqueue_back(size_t len_asked, bool atomic_update = f
         // Full buffer.
         return false;
 
-    if (serial_count > 0) {
-        // If there is some data stored starting at bufindw, len_asked is certainly smaller than
-        // the allocated data buffer. Try to reserve a new buffer and to move the already received
-        // serial data.
-        // How much memory to reserve for the commands pushed to the front?
-        // End of the queue, when pushing to the end.
-        size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE);
-        if (bufindw < bufindr)
-            // Simple case. There is a contiguous space between the write buffer and the read buffer.
-            return endw + CMDBUFFER_RESERVE_FRONT <= bufindr;
-        // Otherwise the free space is split between the start and end.
-        if (// Could one fit to the end, including the reserve?
-            endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) ||
-            // Could one fit to the end, and the reserve to the start?
-            (endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr))
-            return true;
-        // Could one fit both to the start?
-        if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) {
-            // Mark the rest of the buffer as used.
-            memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw);
-            // and point to the start.
-            // Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work.
-            if (atomic_update)
-                cli();
-            bufindw = 0;
-            if (atomic_update)
-                sei();
-            return true;
-        }
-    } else {
-        // How much memory to reserve for the commands pushed to the front?
-        // End of the queue, when pushing to the end.
-        size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE);
-        if (bufindw < bufindr)
-            // Simple case. There is a contiguous space between the write buffer and the read buffer.
-            return endw + CMDBUFFER_RESERVE_FRONT <= bufindr;
-        // Otherwise the free space is split between the start and end.
-        if (// Could one fit to the end, including the reserve?
-            endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) ||
-            // Could one fit to the end, and the reserve to the start?
-            (endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr))
-            return true;
-        // Could one fit both to the start?
-        if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) {
-            // Mark the rest of the buffer as used.
-            memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw);
-            // and point to the start.
-            // Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work.
-            if (atomic_update)
-                cli();
-            bufindw = 0;
-            if (atomic_update)
-                sei();
-            return true;
-        }
+    // If there is some data stored starting at bufindw, len_asked is certainly smaller than
+    // the allocated data buffer. Try to reserve a new buffer and to move the already received
+    // serial data.
+    // How much memory to reserve for the commands pushed to the front?
+    // End of the queue, when pushing to the end.
+    size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE);
+    if (bufindw < bufindr)
+        // Simple case. There is a contiguous space between the write buffer and the read buffer.
+        return endw + CMDBUFFER_RESERVE_FRONT <= bufindr;
+    // Otherwise the free space is split between the start and end.
+    if (// Could one fit to the end, including the reserve?
+        endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) ||
+        // Could one fit to the end, and the reserve to the start?
+        (endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr))
+        return true;
+    // Could one fit both to the start?
+    if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) {
+        // Mark the rest of the buffer as used.
+        memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw);
+        // and point to the start.
+        // Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work.
+        ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { bufindw = 0; }
+        return true;
     }
     return false;
 }
@@ -368,20 +334,10 @@ void repeatcommand_front()
     cmdbuffer_front_already_processed = true;
 } 
 
-void proc_commands() {
-	if (buflen)
-	{
-		process_commands();
-		if (!cmdbuffer_front_already_processed)
-			cmdqueue_pop_front();
-		cmdbuffer_front_already_processed = false;
-	}
-}
-
 void get_command()
 {
     // Test and reserve space for the new command string.
-    if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE - 1, true))
+    if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE - 1))
       return;
 
 	if (MYSERIAL.available() == RX_BUFFER_SIZE - 1) { //compare number of chars buffered in rx buffer with rx buffer size
@@ -413,11 +369,11 @@ void get_command()
       cmdbuffer[bufindw+serial_count+CMDHDRSIZE] = 0; //terminate string
       if(!comment_mode){
 		  
-		  gcode_N = 0;
+		  long gcode_N = -1;
 
 		  // Line numbers must be first in buffer
 
-		  if ((strstr(cmdbuffer+bufindw+CMDHDRSIZE, "PRUSA") == NULL) &&
+		  if ((strstr_P(cmdbuffer+bufindw+CMDHDRSIZE, PSTR("PRUSA")) == NULL) &&
 			  (cmdbuffer[bufindw+CMDHDRSIZE] == 'N')) {
 
 			  // Line number met. When sending a G-code over a serial line, each line may be stamped with its index,
@@ -464,8 +420,6 @@ void get_command()
 
 			  // Don't parse N again with code_seen('N')
 			  cmdbuffer[bufindw + CMDHDRSIZE] = '$';
-			  //if no errors, continue parsing
-			  gcode_LastN = gcode_N;
 		}
         // if we don't receive 'N' but still see '*'
         if ((cmdbuffer[bufindw + CMDHDRSIZE] != 'N') && (cmdbuffer[bufindw + CMDHDRSIZE] != '$') && (strchr(cmdbuffer+bufindw+CMDHDRSIZE, '*') != NULL))
@@ -478,35 +432,49 @@ void get_command()
             serial_count = 0;
             return;
         }
+        // Handle KILL early, even when Stopped
+        if(strcmp(cmdbuffer+bufindw+CMDHDRSIZE, "M112") == 0)
+          kill(MSG_M112_KILL, 2);
+        // Handle the USB timer
         if ((strchr_pointer = strchr(cmdbuffer+bufindw+CMDHDRSIZE, 'G')) != NULL) {
             if (!IS_SD_PRINTING) {
                 usb_timer.start();
             }
-            if (Stopped == true) {
-                if (code_value_uint8() <= 3) {
-                    SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED);
-                    LCD_MESSAGERPGM(_T(MSG_STOPPED));
-                }
-            }
-        } // end of 'G' command
+        }
+        if (Stopped == true) {
+            // Stopped can be set either during error states (thermal error: cannot continue), or
+            // when a printer-initiated action is processed. In such case the printer will send to
+            // the host an action, but cannot know if the action has been processed while new
+            // commands are being sent. In this situation we just drop the command while issuing
+            // periodic "busy" messages in the main loop. Since we're not incrementing the received
+            // line number, a request for resend will happen (if necessary), ensuring we don't skip
+            // commands whenever Stopped is cleared and processing resumes.
+            serial_count = 0;
+            return;
+        }
+
+        // Command is complete: store the current line into buffer, move to the next line.
 
-        //If command was e-stop process now
-        if(strcmp(cmdbuffer+bufindw+CMDHDRSIZE, "M112") == 0)
-          kill(MSG_M112_KILL, 2);
-        
-        // Store the current line into buffer, move to the next line.
 		// Store type of entry
-        cmdbuffer[bufindw] = gcode_N ? CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR : CMDBUFFER_CURRENT_TYPE_USB;
+        cmdbuffer[bufindw] = gcode_N >= 0 ? CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR : CMDBUFFER_CURRENT_TYPE_USB;
+
 #ifdef CMDBUFFER_DEBUG
         SERIAL_ECHO_START;
         SERIAL_ECHOPGM("Storing a command line to buffer: ");
         SERIAL_ECHO(cmdbuffer+bufindw+CMDHDRSIZE);
         SERIAL_ECHOLNPGM("");
 #endif /* CMDBUFFER_DEBUG */
+
+        // Store command itself
         bufindw += strlen(cmdbuffer+bufindw+CMDHDRSIZE) + (1 + CMDHDRSIZE);
         if (bufindw == sizeof(cmdbuffer))
             bufindw = 0;
         ++ buflen;
+
+        // Update the processed gcode line
+        if (gcode_N >= 0)
+            gcode_LastN = gcode_N;
+
 #ifdef CMDBUFFER_DEBUG
         SERIAL_ECHOPGM("Number of commands in the buffer: ");
         SERIAL_ECHO(buflen);
@@ -516,7 +484,7 @@ void get_command()
       serial_count = 0; //clear buffer
       // Don't call cmdqueue_could_enqueue_back if there are no characters waiting
       // in the queue, as this function will reserve the memory.
-      if (MYSERIAL.available() == 0 || ! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1, true))
+      if (MYSERIAL.available() == 0 || ! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1))
           return;
     } // end of "end of line" processing
     else {
@@ -534,7 +502,7 @@ void get_command()
     }
 
   #ifdef SDSUPPORT
-  if(!card.sdprinting || serial_count!=0){
+  if(!card.sdprinting || !card.isFileOpen() || serial_count!=0){
     // If there is a half filled buffer from serial line, wait until return before
     // continuing with the serial line.
      return;
@@ -572,7 +540,7 @@ void get_command()
         // This is either an empty line, or a line with just a comment.
         // Continue to the following line, and continue accumulating the number of bytes
         // read from the sdcard into sd_count, 
-        // so that the lenght of the already read empty lines and comments will be added
+        // so that the length of the already read empty lines and comments will be added
         // to the following non-empty line. 
         return; // prevent cycling indefinitely - let manage_heaters do their job
       }
@@ -614,7 +582,7 @@ void get_command()
       if(card.eof()) break;
 
       // The following line will reserve buffer space if available.
-      if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1, true))
+      if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1))
           return;
     }
     else
@@ -631,6 +599,10 @@ void get_command()
       // cleared by printingHasFinished after peforming all remaining moves.
       if(!cmdqueue_calc_sd_length())
       {
+          // queue is complete, but before we process EOF commands prevent
+          // re-entry by disabling SD processing from any st_synchronize call
+          card.closefile();
+
           SERIAL_PROTOCOLLNRPGM(_n("Done printing file"));////MSG_FILE_PRINTED
           stoptime=_millis();
           char time[30];

+ 0 - 2
Firmware/cmdqueue.h

@@ -52,9 +52,7 @@ extern int serial_count;
 extern bool comment_mode;
 extern char *strchr_pointer;
 
-extern long gcode_N;
 extern long gcode_LastN;
-extern long Stopped_gcode_LastN;
 
 extern bool cmdqueue_pop_front();
 extern void cmdqueue_reset();

+ 24 - 6
Firmware/config.h

@@ -6,7 +6,8 @@
 #include "pins.h"
 
 #if (defined(VOLT_IR_PIN) && defined(IR_SENSOR))
-# define IR_SENSOR_ANALOG
+// TODO: IR_SENSOR_ANALOG currently disabled as being incompatible with the new thermal regulation
+// # define IR_SENSOR_ANALOG
 #endif
 
 //ADC configuration
@@ -20,7 +21,7 @@
 #define ADC_CHAN_CNT      8         //number of used channels)
 #endif //!IR_SENSOR_ANALOG
 #define ADC_OVRSAMPL      16        //oversampling multiplier
-#define ADC_CALLBACK      adc_ready //callback function ()
+#define ADC_CALLBACK      adc_callback //callback function ()
 
 //SWI2C configuration
 //#define SWI2C_SDA         20 //SDA on P3
@@ -42,7 +43,12 @@
 //#define PAT9125_I2C_ADDR  0x73  //ID=NC
 #define PAT9125_XRES      0
 #define PAT9125_YRES      240                   // maximum resolution (5*X cpi)
-#define PAT9124_YRES_MM   (5*PAT9125_YRES/25.4) // counts per mm
+#define PAT9125_YRES_MM   (5*PAT9125_YRES/25.4) // counts per mm
+#define PAT9125_INVERT_X  0 //1 means flipped
+#define PAT9125_INVERT_Y  1 //1 means flipped
+#define PAT9125_SWAP_XY   0 //X is Y and Y is X
+#define PAT9125_12B_RES   1 //8bit or 12bit signed motion data
+#define PAT9125_NEW_INIT  1 //set to 1 to use the magic sequence provided by pixart.
 
 //SM4 configuration
 #define SM4_DEFDELAY      500       //default step delay [us]
@@ -62,7 +68,9 @@
 #define LANG_MODE              1 // sec. language support
 #endif
 
-#define LANG_SIZE_RESERVED     0x3000 // reserved space for secondary language (12288 bytes). Maximum 32768 bytes
+#define LANG_SIZE_RESERVED     0x3500 // reserved space for secondary language (13568 bytes).
+                                      // 0x3D00 Maximum 15616 bytes as it depends on xflash_layout.h
+                                      // 16 Languages max. per group including stock 
 
 #if (LANG_SIZE_RESERVED % 256)
   #error "LANG_SIZE_RESERVED should be a multiple of a page size"
@@ -82,9 +90,12 @@
 //#define COMMUNITY_LANG_GROUP1_DA // Community Danish language
 //#define COMMUNITY_LANG_GROUP1_SL // Community Slovanian language
 //#define COMMUNITY_LANG_GROUP1_LB // Community Luxembourgish language
-//#define COMMUNITY_LANG_GROUP1_LT // Community Lithuanian language
+#endif //COMMUNITY_LANG_GROUP 1
+
+#if (COMMUNITY_LANG_GROUP == 2)
+#define COMMUNITY_LANG_GROUP2_LT // Community Lithuanian language
 //#define COMMUNITY_LANG_GROUP1_QR // Community new language //..use this as a template and replace 'QR'
-#endif
+#endif //COMMUNITY_LANG_GROUP 2
 
 #if (COMMUNITY_LANG_GROUP >=1 )
 #define COMMUNITY_LANGUAGE_SUPPORT
@@ -111,4 +122,11 @@
 #define EMERGENCY_HANDLERS
 #endif
 
+//FARM_MODE
+#if ( LANG_MODE == 0 ) && defined(XFLASH) //Save resources on EINSY and disable FARM_MODE on multi-language version
+#define PRUSA_FARM
+#endif //PRUSA_FARM only in english on EINSYs
+#ifndef XFLASH //enable FARM_MODE on miniRAMBo boards
+#define PRUSA_FARM
+#endif
 #endif //_CONFIG_H

+ 1 - 1
Firmware/conv2str.cpp

@@ -104,7 +104,7 @@ char *ftostr43(const float &x, uint8_t offset)
 //Float to string with 1.23 format
 char *ftostr12ns(const float &x)
 {
-  long xx = x * 100;
+  int xx = x * 100;
 
   xx = abs(xx);
   conv[0] = (xx / 100) % 10 + '0';

+ 2 - 0
Firmware/eeprom.cpp

@@ -105,6 +105,8 @@ if (eeprom_read_byte((uint8_t*)EEPROM_PINDA_TEMP_COMPENSATION) == 0xff) eeprom_u
         eeprom_update_dword((uint32_t *)EEPROM_TOTALTIME, 0);
         eeprom_update_dword((uint32_t *)EEPROM_FILAMENTUSED, 0);
     }
+//Set Cutter OFF if 0xff
+    if (eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED) == 0xff) eeprom_update_byte((uint8_t *)EEPROM_MMU_CUTTER_ENABLED, 0);
 }
 
 //! @brief Get default sheet name for index

+ 31 - 21
Firmware/eeprom.h

@@ -142,9 +142,8 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
 | 0x0F75h 3957		| uint16	| EEPROM_UVLO_MESH_BED_LEVELING			| ???			| ff ffh 65535			| Power Panic Mesh Bed Leveling						| ???			| D3 Ax0f75 C18 
 | 0x0F73h 3955		| uint16	| EEPROM_UVLO_Z_MICROSTEPS				| ???			| ff ffh 65535			| Power Panic Z microsteps							| ???			| D3 Ax0f73 C2 
 | 0x0F72h 3954		| uint8		| EEPROM_UVLO_E_ABS						| ???			| ffh 255				| Power Panic ??? position							| ???			| D3 Ax0f72 C1
-| 0x0F6Eh 3950		| foat		| EEPROM_UVLO_CURRENT_POSITION_E		| ???			| ff ff ff ffh			| Power Panic E position							| ???			| D3 Ax0f6e C4
-| 0x0F6Dh 3949		| ???		| _EEPROM_FREE_NR2_						| ???			| ffh 255				| _Free EEPROM space_								| _free space_	| D3 Ax0f6d C1
-| 0x0F6Ch 3948		| ???		| _EEPROM_FREE_NR3_						| ???			| ffh 255				| _Free EEPROM space_								| _free space_	| D3 Ax0f6c C1
+| 0x0F6Eh 3950		| float		| EEPROM_UVLO_CURRENT_POSITION_E		| ???			| ff ff ff ffh			| Power Panic E position							| ???			| D3 Ax0f6e C4
+| 0x0F6Ch 3948		| uint16_t	| EEPROM_UVLO_SAVED_SEGMENT_IDX			| all			| ff ffh 65535			| Power Panic index of multi-segment move			| ???			| D3 Ax0f6c C2
 | 0x0F6Bh 3947		| ???		| _EEPROM_FREE_NR4_						| ???			| ffh 255				| _Free EEPROM space_								| _free space_	| D3 Ax0f6b C1
 | 0x0F6Ah 3946		| ???		| _EEPROM_FREE_NR5_						| ???			| ffh 255				| _Free EEPROM space_								| _free space_	| D3 Ax0f6a C1
 | 0x0F69h 3945		| uint8		| EEPROM_CRASH_DET						| ffh 255		| ffh 255				| Crash detection: __enabled__						| LCD menu		| D3 Ax0f69 C1
@@ -217,10 +216,10 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
 | ^					| ^			| ^										| 01h 1			| ^						| Sound mode: __once__								| ^				| ^
 | ^					| ^			| ^										| 02h 1			| ^						| Sound mode: __silent__							| ^				| ^
 | ^					| ^			| ^										| 03h 1			| ^						| Sound mode: __assist__							| ^				| ^
-| 0x0ED6 3798		| bool		| EEPROM_AUTO_DEPLETE					| 01h 1			| ffh 255				| MMU2/s autodeplete: __on__						| ???			| D3 Ax0ed6 C1
+| 0x0ED6 3798		| bool		| EEPROM_SPOOL_JOIN					| 01h 1			| ffh 255				| MMU2/s autodeplete: __on__						| ???			| D3 Ax0ed6 C1
 | ^					| ^			| ^										| 00h 0			| ^						| MMU2/s autodeplete: __off__						| ^				| ^
-| 0x0ED5 3797		| bool		| EEPROM_FSENS_OQ_MEASS_ENABLED			| ???			| ffh 255				| PAT1925 ???										| ???			| D3 Ax0ed5 C1
-| ^					| ^			| ^										| ???			| ^						| PAT1925 ???										| ^				| ^
+| 0x0ED5 3797		| bool		| EEPROM_FSENS_RUNOUT_ENABLED			| 01h 1			| ffh 255		__P__	| Filament runout: __enabled__						| LCD menu		| D3 Ax0ed5 C1
+| ^					| ^			| ^										| 00h 0			| ^						| Filament runout: __disabled__						| LCD menu		| ^
 | 0x0ED3 3795		| uint16	| EEPROM_MMU_FAIL_TOT					| ???			| ff ffh 65535	__S/P__	| MMU2/s total failures								| ???			| D3 Ax0ed3 C2
 | 0x0ED2 3794		| uint8		| EEPROM_MMU_FAIL						| ???			| ffh 255		__S/P__	| MMU2/s fails during print							| ???			| D3 Ax0ed2 C1
 | 0x0ED0 3792		| uint16	| EEPROM_MMU_LOAD_FAIL_TOT				| ???			| ff ffh 65535	__S/P__	| MMU2/s total load failures						| ???			| D3 Ax0ed0 C2
@@ -298,11 +297,11 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
 | ^					| ^			| ^										| 01h 1			| ^						| Filament Sensor type IR 0.4 or newer				| ^				| ^
 | 0x0D47 3399		| uint8		| EEPROM_FSENSOR_ACTION_NA				| 00h 0			| ffh 255				| Filament Sensor action: __Continue__				| LCD menu		| D3 Ax0d47 C1
 | ^					| ^			| ^										| 01h 1			| ^						| Filament Sensor action: __Pause__					| ^				| ^
-| 0x0D37 3383		| float		| EEPROM_UVLO_SAVED_TARGET				| ???			| ff ff ff ffh			| Power panic saved target all-axis					| ???			| D3 Ax0d37 C16
-| ^					| ^			| ^										| ???			| ^						| Power panic saved target e-axis					| ^				| D3 Ax0d43 C4
-| ^					| ^			| ^										| ???			| ^						| Power panic saved target z-axis					| ^				| D3 Ax0d3f C4
-| ^					| ^			| ^										| ???			| ^						| Power panic saved target y-axis					| ^				| D3 Ax0d3b C4
-| ^					| ^			| ^										| ???			| ^						| Power panic saved target x-axis					| ^				| D3 Ax0d37 C4
+| 0x0D37 3383		| float		| EEPROM_UVLO_SAVED_START_POSITION		| ???			| ff ff ff ffh			| Power panic saved start position all-axis			| ???			| D3 Ax0d37 C16
+| ^					| ^			| ^										| ???			| ^						| Power panic saved start position e-axis			| ^				| D3 Ax0d43 C4
+| ^					| ^			| ^										| ???			| ^						| Power panic saved start position z-axis			| ^				| D3 Ax0d3f C4
+| ^					| ^			| ^										| ???			| ^						| Power panic saved start position y-axis			| ^				| D3 Ax0d3b C4
+| ^					| ^			| ^										| ???			| ^						| Power panic saved start position x-axis			| ^				| D3 Ax0d37 C4
 | 0x0D35 3381		| uint16	| EEPROM_UVLO_FEEDMULTIPLY				| ???			| ff ffh 65355			| Power panic saved feed multiplier					| ???			| D3 Ax0d35 C2
 | 0x0D34 3380		| uint8		| EEPROM_BACKLIGHT_LEVEL_HIGH			| 00h - ffh 	| 82h 130				| LCD backlight bright:	__128__	Dim value to 255	| LCD menu		| D3 Ax0d34 C1
 | 0x0D33 3379		| uint8		| EEPROM_BACKLIGHT_LEVEL_LOW			| 00h - ffh		| 32h 50				| LCD backlight dim:	__50__ 	0 to Bright value	| LCD menu		| D3 Ax0d33 C1
@@ -334,6 +333,8 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
 | ^					| ^			| ^										| 03h 3			| ^						| bad_isr											| ^				| ^
 | ^					| ^			| ^										| 04h 4			| ^						| bad_pullup_temp_isr								| ^				| ^
 | ^					| ^			| ^										| 05h 5			| ^						| bad_pullup_step_isr								| ^				| ^
+| 0x0D02 3320		| uint8_t	| EEPROM_FSENSOR_JAM_DETECTION			| 01h 1			| ff/01					| fsensor pat9125 jam detection feature				| LCD menu		| D3 Ax0d02 C1
+| 0x0D01 3319		| uint8_t	| EEPROM_MMU_ENABLED        			| 01h 1			| ff/01					| MMU enabled                       				| LCD menu		| D3 Ax0d01 C1
 
 | Address begin		| Bit/Type 	| Name 									| Valid values	| Default/FactoryReset	| Description 										| Gcode/Function| Debug code
 | :--:				| :--: 		| :--: 									| :--:			| :--:					| :--:												| :--:			| :--:
@@ -398,15 +399,14 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
 #define EEPROM_UVLO_FAN_SPEED			(EEPROM_UVLO_FEEDRATE - 1) 
 #define EEPROM_FAN_CHECK_ENABLED		(EEPROM_UVLO_FAN_SPEED - 1)
 #define EEPROM_UVLO_MESH_BED_LEVELING     (EEPROM_FAN_CHECK_ENABLED - 9*2)
-
 #define EEPROM_UVLO_Z_MICROSTEPS     (EEPROM_UVLO_MESH_BED_LEVELING - 2) // uint16_t (could be removed)
 #define EEPROM_UVLO_E_ABS            (EEPROM_UVLO_Z_MICROSTEPS - 1)
 #define EEPROM_UVLO_CURRENT_POSITION_E	(EEPROM_UVLO_E_ABS - 4)                 //float for current position in E
+#define EEPROM_UVLO_SAVED_SEGMENT_IDX   (EEPROM_UVLO_CURRENT_POSITION_E - 2) //uint16_t
 
-#define EEPROM_FREE_NR2         (EEPROM_UVLO_CURRENT_POSITION_E - 1)			// FREE EEPROM SPACE
-#define EEPROM_FREE_NR3         (EEPROM_FREE_NR2 - 1)							// FREE EEPROM SPACE
-#define EEPROM_FREE_NR4         (EEPROM_FREE_NR3 - 1)							// FREE EEPROM SPACE
+#define EEPROM_FREE_NR4         (EEPROM_UVLO_SAVED_SEGMENT_IDX - 1)							// FREE EEPROM SPACE
 #define EEPROM_FREE_NR5         (EEPROM_FREE_NR4 - 1)							// FREE EEPROM SPACE
+
 // Crash detection mode EEPROM setting 
 #define EEPROM_CRASH_DET         (EEPROM_FREE_NR5 - 1)       				    // uint8 (orig EEPROM_UVLO_MESH_BED_LEVELING-12) 
 // Crash detection counter Y (last print)
@@ -494,11 +494,11 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
 
 // Sound Mode
 #define EEPROM_SOUND_MODE (EEPROM_UVLO_TARGET_HOTEND-1) // uint8
-#define EEPROM_AUTO_DEPLETE (EEPROM_SOUND_MODE-1) //bool
+#define EEPROM_SPOOL_JOIN (EEPROM_SOUND_MODE-1) //bool
 
-#define EEPROM_FSENS_OQ_MEASS_ENABLED (EEPROM_AUTO_DEPLETE - 1) //bool
+#define EEPROM_FSENS_RUNOUT_ENABLED (EEPROM_SPOOL_JOIN - 1) //bool
 
-#define EEPROM_MMU_FAIL_TOT (EEPROM_FSENS_OQ_MEASS_ENABLED - 2) //uint16_t
+#define EEPROM_MMU_FAIL_TOT (EEPROM_FSENS_RUNOUT_ENABLED - 2) //uint16_t
 #define EEPROM_MMU_FAIL (EEPROM_MMU_FAIL_TOT - 1) //uint8_t
 
 #define EEPROM_MMU_LOAD_FAIL_TOT (EEPROM_MMU_FAIL - 2) //uint16_t
@@ -526,8 +526,8 @@ static Sheets * const EEPROM_Sheets_base = (Sheets*)(EEPROM_SHEETS_BASE);
 #define EEPROM_FSENSOR_PCB (EEPROM_SHEETS_BASE-1) // uint8
 #define EEPROM_FSENSOR_ACTION_NA (EEPROM_FSENSOR_PCB-1) // uint8
 
-#define EEPROM_UVLO_SAVED_TARGET (EEPROM_FSENSOR_ACTION_NA - 4*4) // 4 x float for saved target for all axes
-#define EEPROM_UVLO_FEEDMULTIPLY (EEPROM_UVLO_SAVED_TARGET - 2) // uint16_t for feedmultiply
+#define EEPROM_UVLO_SAVED_START_POSITION (EEPROM_FSENSOR_ACTION_NA - 4*4) // 4 x float for saved start position for all axes
+#define EEPROM_UVLO_FEEDMULTIPLY (EEPROM_UVLO_SAVED_START_POSITION - 2) // uint16_t for feedmultiply
 
 #define EEPROM_BACKLIGHT_LEVEL_HIGH (EEPROM_UVLO_FEEDMULTIPLY-1) // uint8
 #define EEPROM_BACKLIGHT_LEVEL_LOW (EEPROM_BACKLIGHT_LEVEL_HIGH-1) // uint8
@@ -550,8 +550,18 @@ static Sheets * const EEPROM_Sheets_base = (Sheets*)(EEPROM_SHEETS_BASE);
 #define EEPROM_ECOOL_ENABLE (EEPROM_JOB_ID-1) // uint8_t
 #define EEPROM_FW_CRASH_FLAG (EEPROM_ECOOL_ENABLE-1) // uint8_t
 
+#define EEPROM_TEMP_MODEL_ENABLE (EEPROM_FW_CRASH_FLAG-1) // uint8_t
+#define EEPROM_TEMP_MODEL_P (EEPROM_TEMP_MODEL_ENABLE-4) // float
+#define EEPROM_TEMP_MODEL_C (EEPROM_TEMP_MODEL_P-4) // float
+#define EEPROM_TEMP_MODEL_R (EEPROM_TEMP_MODEL_C-4*16) // float[16]
+#define EEPROM_TEMP_MODEL_Ta_corr (EEPROM_TEMP_MODEL_R-4) // float
+#define EEPROM_TEMP_MODEL_W (EEPROM_TEMP_MODEL_Ta_corr-4) // float
+#define EEPROM_TEMP_MODEL_E (EEPROM_TEMP_MODEL_W-4) // float
+
+#define EEPROM_FSENSOR_JAM_DETECTION (EEPROM_TEMP_MODEL_E-1) // uint8_t
+#define EEPROM_MMU_ENABLED (EEPROM_FSENSOR_JAM_DETECTION-1) // uint8_t
 //This is supposed to point to last item to allow EEPROM overrun check. Please update when adding new items.
-#define EEPROM_LAST_ITEM EEPROM_FW_CRASH_FLAG
+#define EEPROM_LAST_ITEM EEPROM_MMU_ENABLED
 // !!!!!
 // !!!!! this is end of EEPROM section ... all updates MUST BE inserted before this mark !!!!!
 // !!!!!

+ 300 - 0
Firmware/fancheck.cpp

@@ -0,0 +1,300 @@
+// fan control and check
+#include "fancheck.h"
+#include "cardreader.h"
+#include "ultralcd.h"
+#include "sound.h"
+#include "messages.h"
+#include "temperature.h"
+#include "stepper.h"
+
+#define FAN_CHECK_PERIOD 5000 //5s
+#define FAN_CHECK_DURATION 100 //100ms
+
+#ifdef FANCHECK
+volatile uint8_t fan_check_error = EFCE_OK;
+#endif
+
+#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
+  #ifdef EXTRUDER_ALTFAN_DETECT
+  static struct
+  {
+      uint8_t isAltfan : 1;
+      uint8_t altfanOverride : 1;
+  } altfanStatus;
+  #endif //EXTRUDER_ALTFAN_DETECT
+
+  unsigned long extruder_autofan_last_check = _millis();
+  bool fan_measuring = false;
+  static uint8_t fanState = 0;
+#endif
+
+#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
+  #if defined(FAN_PIN) && FAN_PIN > -1
+    #if EXTRUDER_0_AUTO_FAN_PIN == FAN_PIN
+       #error "You cannot set EXTRUDER_0_AUTO_FAN_PIN equal to FAN_PIN"
+    #endif
+  #endif
+
+void setExtruderAutoFanState(uint8_t state)
+{
+    //If bit 1 is set (0x02), then the extruder fan speed won't be adjusted according to temperature. Useful for forcing
+    //the fan to either On or Off during certain tests/errors.
+
+    fanState = state;
+    newFanSpeed = 0;
+    if (fanState & 0x01)
+    {
+#ifdef EXTRUDER_ALTFAN_DETECT
+        if (altfanStatus.isAltfan && !altfanStatus.altfanOverride) newFanSpeed = EXTRUDER_ALTFAN_SPEED_SILENT;
+        else newFanSpeed = EXTRUDER_AUTO_FAN_SPEED;
+#else //EXTRUDER_ALTFAN_DETECT
+        newFanSpeed = EXTRUDER_AUTO_FAN_SPEED;
+#endif //EXTRUDER_ALTFAN_DETECT
+    }
+    timer4_set_fan0(newFanSpeed);
+}
+
+#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))))
+
+void countFanSpeed()
+{
+    //SERIAL_ECHOPGM("edge counter 1:"); MYSERIAL.println(fan_edge_counter[1]);
+    fan_speed[0] = (fan_edge_counter[0] * (float(250) / (_millis() - extruder_autofan_last_check)));
+    fan_speed[1] = (fan_edge_counter[1] * (float(250) / (_millis() - extruder_autofan_last_check)));
+    /*SERIAL_ECHOPGM("time interval: "); MYSERIAL.println(_millis() - extruder_autofan_last_check);
+    SERIAL_ECHOPGM("extruder fan speed:"); MYSERIAL.print(fan_speed[0]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[0]);
+    SERIAL_ECHOPGM("print fan speed:"); MYSERIAL.print(fan_speed[1]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[1]);
+    SERIAL_ECHOLNPGM(" ");*/
+    fan_edge_counter[0] = 0;
+    fan_edge_counter[1] = 0;
+}
+
+//! Prints serialMsg to serial port, displays lcdMsg onto the LCD and beeps.
+//! Extracted from fanSpeedError to save some space.
+//! @param serialMsg pointer into PROGMEM, this text will be printed to the serial port
+//! @param lcdMsg pointer into PROGMEM, this text will be printed onto the LCD
+static void fanSpeedErrorBeep(const char *serialMsg, const char *lcdMsg){
+    SERIAL_ECHOLNRPGM(serialMsg);
+    if (get_message_level() == 0) {
+        Sound_MakeCustom(200,0,true);
+        LCD_ALERTMESSAGERPGM(lcdMsg);
+    }
+}
+
+void fanSpeedError(unsigned char _fan) {
+    if (fan_check_error == EFCE_REPORTED) return;
+    fan_check_error = EFCE_REPORTED;
+
+    if (IS_SD_PRINTING || usb_timer.running()) {
+        // A print is ongoing, pause the print normally
+        if(!isPrintPaused) {
+            if (usb_timer.running())
+                lcd_pause_usb_print();
+            else
+                lcd_pause_print();
+        }
+    }
+    else {
+        // Nothing is going on, but still turn off heaters and report the error
+        setTargetHotend0(0);
+        heating_status = HeatingStatus::NO_HEATING;
+    }
+    switch (_fan) {
+    case 0:	// extracting the same code from case 0 and case 1 into a function saves 72B
+        fanSpeedErrorBeep(PSTR("Extruder fan speed is lower than expected"), MSG_FANCHECK_EXTRUDER);
+        break;
+    case 1:
+        fanSpeedErrorBeep(PSTR("Print fan speed is lower than expected"), MSG_FANCHECK_PRINT);
+        break;
+    }
+}
+
+void checkFanSpeed()
+{
+    uint8_t max_fan_errors[2];
+#ifdef FAN_SOFT_PWM
+    max_fan_errors[1] = 3;  // 15 seconds (Print fan)
+    max_fan_errors[0] = 2;  // 10 seconds (Extruder fan)
+#else //FAN_SOFT_PWM
+    max_fan_errors[1] = 15; // 15 seconds (Print fan)
+    max_fan_errors[0] = 5;  // 5  seconds (Extruder fan)
+#endif //FAN_SOFT_PWM
+
+    if(fans_check_enabled)
+        fans_check_enabled = (eeprom_read_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED) > 0);
+    static uint8_t fan_speed_errors[2] = { 0,0 };
+#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 >-1))
+    if ((fan_speed[0] < 20) && (current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE)){ fan_speed_errors[0]++;}
+    else fan_speed_errors[0] = 0;
+#endif
+#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1))
+    if ((fan_speed[1] < 5) && ((blocks_queued() ? block_buffer[block_buffer_tail].fan_speed : fanSpeed) > MIN_PRINT_FAN_SPEED)) fan_speed_errors[1]++;
+    else fan_speed_errors[1] = 0;
+#endif
+
+    // drop the fan_check_error flag when both fans are ok
+    if( fan_speed_errors[0] == 0 && fan_speed_errors[1] == 0 && fan_check_error == EFCE_REPORTED){
+        // we may even send some info to the LCD from here
+        fan_check_error = EFCE_FIXED;
+    }
+    if ((fan_check_error == EFCE_FIXED) && !printer_active()){
+        fan_check_error = EFCE_OK; //if the issue is fixed while the printer is doing nothing, reenable processing immediately.
+        lcd_reset_alert_level(); //for another fan speed error
+    }
+    if (fans_check_enabled && (fan_check_error == EFCE_OK))
+    {
+        for (uint8_t fan = 0; fan < 2; fan++)
+        {
+            if (fan_speed_errors[fan] > max_fan_errors[fan])
+            {
+                fan_speed_errors[fan] = 0;
+                fanSpeedError(fan);
+            }
+        }
+    }
+}
+#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1)
+
+#ifdef EXTRUDER_ALTFAN_DETECT
+ISR(INT6_vect) {
+    fan_edge_counter[0]++;
+}
+
+bool extruder_altfan_detect()
+{
+    setExtruderAutoFanState(3);
+
+    SET_INPUT(TACH_0);
+
+    uint8_t overrideVal = eeprom_read_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE);
+    if (overrideVal == EEPROM_EMPTY_VALUE)
+    {
+        overrideVal = (calibration_status() == CALIBRATION_STATUS_CALIBRATED) ? 1 : 0;
+        eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, overrideVal);
+    }
+    altfanStatus.altfanOverride = overrideVal;
+
+    CRITICAL_SECTION_START;
+    EICRB &= ~(1 << ISC61);
+    EICRB |= (1 << ISC60);
+    EIMSK |= (1 << INT6);
+    fan_edge_counter[0] = 0;
+    CRITICAL_SECTION_END;
+    extruder_autofan_last_check = _millis();
+
+    _delay(1000);
+
+    EIMSK &= ~(1 << INT6);
+
+    countFanSpeed();
+    altfanStatus.isAltfan = fan_speed[0] > 100;
+    setExtruderAutoFanState(1);
+    return altfanStatus.isAltfan;
+}
+
+void altfanOverride_toggle()
+{
+    altfanStatus.altfanOverride = !altfanStatus.altfanOverride;
+    eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, altfanStatus.altfanOverride);
+}
+
+bool altfanOverride_get()
+{
+    return altfanStatus.altfanOverride;
+}
+
+#endif //EXTRUDER_ALTFAN_DETECT
+
+void checkExtruderAutoFans()
+{
+#if defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1
+    if (!(fanState & 0x02))
+    {
+        fanState &= ~1;
+        fanState |= current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE;
+        fanState |= get_temp_error();
+    }
+    setExtruderAutoFanState(fanState);
+#endif
+}
+
+#endif // any extruder auto fan pins set
+
+#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
+void readFanTach() {
+#ifdef FAN_SOFT_PWM
+    if (READ(TACH_0) != fan_state[0]) {
+        if(fan_measuring) fan_edge_counter[0] ++;
+        fan_state[0] = !fan_state[0];
+    }
+#else //FAN_SOFT_PWM
+    if (READ(TACH_0) != fan_state[0]) {
+        fan_edge_counter[0] ++;
+        fan_state[0] = !fan_state[0];
+    }
+#endif
+    //if (READ(TACH_1) != fan_state[1]) {
+    //	fan_edge_counter[1] ++;
+    //	fan_state[1] = !fan_state[1];
+    //}
+}
+#endif //TACH_0
+
+void checkFans()
+{
+#ifndef DEBUG_DISABLE_FANCHECK
+#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
+
+#ifdef FAN_SOFT_PWM
+#ifdef FANCHECK
+  if ((_millis() - extruder_autofan_last_check > FAN_CHECK_PERIOD) && (!fan_measuring)) {
+	  extruder_autofan_last_check = _millis();
+	  fanSpeedBckp = fanSpeedSoftPwm;
+
+	  if (fanSpeedSoftPwm >= MIN_PRINT_FAN_SPEED) { //if we are in rage where we are doing fan check, set full PWM range for a short time to measure fan RPM by reading tacho signal without modulation by PWM signal
+		//  printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm);
+		  fanSpeedSoftPwm = 255;
+	  }
+	  fan_measuring = true;
+  }
+  if ((_millis() - extruder_autofan_last_check > FAN_CHECK_DURATION) && (fan_measuring)) {
+	  countFanSpeed();
+	  checkFanSpeed();
+	  //printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm);
+	  fanSpeedSoftPwm = fanSpeedBckp;
+	  //printf_P(PSTR("fan PWM: %d; extr fanSpeed measured: %d; print fan speed measured: %d \n"), fanSpeedBckp, fan_speed[0], fan_speed[1]);
+	  extruder_autofan_last_check = _millis();
+	  fan_measuring = false;
+  }
+#endif //FANCHECK
+  checkExtruderAutoFans();
+#else //FAN_SOFT_PWM
+  if(_millis() - extruder_autofan_last_check > 1000)  // only need to check fan state very infrequently
+  {
+#if (defined(FANCHECK) && ((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))
+	countFanSpeed();
+	checkFanSpeed();
+#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1)
+    checkExtruderAutoFans();
+    extruder_autofan_last_check = _millis();
+  }
+#endif //FAN_SOFT_PWM
+
+#endif
+#endif //DEBUG_DISABLE_FANCHECK
+}
+
+void hotendFanSetFullSpeed()
+{
+#ifdef EXTRUDER_ALTFAN_DETECT
+    altfanStatus.altfanOverride = 1; //full speed
+#endif //EXTRUDER_ALTFAN_DETECT
+    setExtruderAutoFanState(3);
+    SET_OUTPUT(FAN_PIN);
+#ifdef FAN_SOFT_PWM
+    fanSpeedSoftPwm = 255;
+#else //FAN_SOFT_PWM
+    analogWrite(FAN_PIN, 255);
+#endif //FAN_SOFT_PWM
+    fanSpeed = 255;
+}

+ 35 - 0
Firmware/fancheck.h

@@ -0,0 +1,35 @@
+// fan control and check
+#pragma once
+
+#include "Configuration.h"
+#include "config.h"
+
+#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
+enum {
+	EFCE_OK = 0,   //!< normal operation, both fans are ok
+	EFCE_FIXED,    //!< previous fan error was fixed
+	EFCE_REPORTED  //!< fan error detected and reported to LCD and serial
+};
+extern volatile uint8_t fan_check_error;
+
+void readFanTach();
+#endif //(defined(TACH_0))
+
+#ifdef EXTRUDER_ALTFAN_DETECT
+extern bool extruder_altfan_detect();
+extern void altfanOverride_toggle();
+extern bool altfanOverride_get();
+#endif //EXTRUDER_ALTFAN_DETECT
+
+#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
+#ifdef FAN_SOFT_PWM
+extern bool fan_measuring;
+#endif //FAN_SOFT_PWM
+
+extern unsigned long extruder_autofan_last_check;
+void setExtruderAutoFanState(uint8_t state);
+void checkExtruderAutoFans();
+#endif
+
+void checkFans();
+void hotendFanSetFullSpeed();

+ 8 - 16
Firmware/first_lay_cal.cpp

@@ -8,33 +8,25 @@
 #include "language.h"
 #include "Marlin.h"
 #include "cmdqueue.h"
-#include "mmu.h"
+#include "mmu2.h"
 #include <avr/pgmspace.h>
 
 //! @brief Wait for preheat
 void lay1cal_wait_preheat()
 {
-    static const char cmd_preheat_0[] PROGMEM = "M107";
-    static const char cmd_preheat_1[] PROGMEM = "M190";
-    static const char cmd_preheat_2[] PROGMEM = "M109";
-    static const char cmd_preheat_4[] PROGMEM = "G28";
-    static const char cmd_preheat_5[] PROGMEM = "G92 E0.0";
-
     const char * const preheat_cmd[] =
     {
-        cmd_preheat_0,
-        cmd_preheat_1,
-        cmd_preheat_2,
-        _T(MSG_M117_V2_CALIBRATION),
-        cmd_preheat_4,
-        cmd_preheat_5,
+        PSTR("M107"),
+        PSTR("M190"),
+        PSTR("M109"),
+        PSTR("G28"),
+        PSTR("G92 E0.0")
     };
 
     for (uint8_t i = 0; i < (sizeof(preheat_cmd)/sizeof(preheat_cmd[0])); ++i)
     {
         enquecommand_P(preheat_cmd[i]);
     }
-
 }
 
 //! @brief Load filament
@@ -42,7 +34,7 @@ void lay1cal_wait_preheat()
 //! @param filament filament to use (applies for MMU only)
 void lay1cal_load_filament(char *cmd_buffer, uint8_t filament)
 {
-    if (mmu_enabled)
+    if (MMU2::mmu2.Enabled())
     {
         enquecommand_P(PSTR("M83"));
         enquecommand_P(PSTR("G1 Y-3.0 F1000.0"));
@@ -81,7 +73,7 @@ void lay1cal_intro_line()
         cmd_intro_mmu_12,
     };
 
-    if (mmu_enabled)
+    if (MMU2::mmu2.Enabled())
     {
         for (uint8_t i = 0; i < (sizeof(intro_mmu_cmd)/sizeof(intro_mmu_cmd[0])); ++i)
         {

+ 0 - 788
Firmware/fsensor.cpp

@@ -1,788 +0,0 @@
-//! @file
-
-#include "Marlin.h"
-
-#include "fsensor.h"
-#include <avr/pgmspace.h>
-#include "pat9125.h"
-#include "stepper.h"
-#include "cmdqueue.h"
-#include "ultralcd.h"
-#include "mmu.h"
-#include "cardreader.h"
-
-#include "adc.h"
-#include "temperature.h"
-#include "config.h"
-
-//! @name Basic parameters
-//! @{
-#define FSENSOR_CHUNK_LEN      1.25 //!< filament sensor chunk length (mm)
-#define FSENSOR_ERR_MAX           4 //!< filament sensor maximum error/chunk count for runout detection
-
-#define FSENSOR_SOFTERR_CMAX      3 //!< number of contiguous soft failures before a triggering a runout
-#define FSENSOR_SOFTERR_DELTA 30000 //!< maximum interval (ms) to consider soft failures contiguous
-//! @}
-
-//! @name Optical quality measurement parameters
-//! @{
-#define FSENSOR_OQ_MAX_ES      2    //!< maximum sum of error blocks during filament recheck
-#define FSENSOR_OQ_MIN_YD      2    //!< minimum yd sum during filament check (counts per inch)
-#define FSENSOR_OQ_MIN_BR      80   //!< minimum brightness value
-#define FSENSOR_OQ_MAX_SH      10   //!< maximum shutter value
-//! @}
-
-const char ERRMSG_PAT9125_NOT_RESP[] PROGMEM = "PAT9125 not responding (%d)!\n";
-
-// PJ7 can not be used (does not have PinChangeInterrupt possibility)
-#define FSENSOR_INT_PIN          75 //!< filament sensor interrupt pin PJ4
-#define FSENSOR_INT_PIN_MASK   0x10 //!< filament sensor interrupt pin mask (bit4)
-#define FSENSOR_INT_PIN_PIN_REG PINJ              // PIN register @ PJ4
-#define FSENSOR_INT_PIN_VECT PCINT1_vect          // PinChange ISR @ PJ4
-#define FSENSOR_INT_PIN_PCMSK_REG PCMSK1          // PinChangeMaskRegister @ PJ4
-#define FSENSOR_INT_PIN_PCMSK_BIT PCINT13         // PinChange Interrupt / PinChange Enable Mask @ PJ4
-#define FSENSOR_INT_PIN_PCICR_BIT PCIE1           // PinChange Interrupt Enable / Flag @ PJ4
-
-//! enabled = initialized and sampled every chunk event
-bool fsensor_enabled = true;
-//! runout watching is done in fsensor_update (called from main loop)
-bool fsensor_watch_runout = true;
-//! not responding - is set if any communication error occurred during initialization or readout
-bool fsensor_not_responding = false;
-
-#ifdef PAT9125
-uint8_t fsensor_int_pin_old = 0;
-//! optical checking "chunk lenght" (already in steps)
-int16_t fsensor_chunk_len = 0;
-//! enable/disable quality meassurement
-bool fsensor_oq_meassure_enabled = false;
-//! number of errors, updated in ISR
-uint8_t fsensor_err_cnt = 0;
-//! variable for accumulating step count (updated callbacks from stepper and ISR)
-int16_t fsensor_st_cnt = 0;
-//! count of total sensor "soft" failures (filament status checks)
-uint8_t fsensor_softfail = 0;
-//! timestamp of last soft failure
-unsigned long fsensor_softfail_last = 0;
-//! count of soft failures within the configured time
-uint8_t fsensor_softfail_ccnt = 0;
-#endif
-
-#ifdef DEBUG_FSENSOR_LOG
-//! log flag: 0=log disabled, 1=log enabled
-uint8_t fsensor_log = 1;
-#endif //DEBUG_FSENSOR_LOG
-
-
-//! @name filament autoload variables
-//! @{
-
-//! autoload feature enabled
-bool fsensor_autoload_enabled = true;
-//! autoload watching enable/disable flag
-bool fsensor_watch_autoload = false;
-
-#ifdef PAT9125
-//
-uint16_t fsensor_autoload_y;
-//
-uint8_t fsensor_autoload_c;
-//
-uint32_t fsensor_autoload_last_millis;
-//
-uint8_t fsensor_autoload_sum;
-//! @}
-#endif
-
-
-//! @name filament optical quality measurement variables
-//! @{
-
-//! Measurement enable/disable flag
-bool fsensor_oq_meassure = false;
-//! skip-chunk counter, for accurate measurement is necessary to skip first chunk...
-uint8_t  fsensor_oq_skipchunk;
-//! number of samples from start of measurement
-uint8_t fsensor_oq_samples;
-//! sum of steps in positive direction movements
-uint16_t fsensor_oq_st_sum;
-//! sum of deltas in positive direction movements
-uint16_t fsensor_oq_yd_sum;
-//! sum of errors during measurement
-uint16_t fsensor_oq_er_sum;
-//! max error counter value during measurement
-uint8_t  fsensor_oq_er_max;
-//! minimum delta value
-int16_t fsensor_oq_yd_min;
-//! maximum delta value
-int16_t fsensor_oq_yd_max;
-//! sum of shutter value
-uint16_t fsensor_oq_sh_sum;
-//! @}
-
-#ifdef IR_SENSOR_ANALOG
-ClFsensorPCB oFsensorPCB;
-ClFsensorActionNA oFsensorActionNA;
-bool bIRsensorStateFlag=false;
-ShortTimer tIRsensorCheckTimer;
-#endif //IR_SENSOR_ANALOG
-
-void fsensor_stop_and_save_print(void)
-{
-    puts_P(PSTR("fsensor_stop_and_save_print"));
-    stop_and_save_print_to_ram(0, 0);
-    fsensor_watch_runout = false;
-}
-
-#ifdef PAT9125
-// Reset all internal counters to zero, including stepper callbacks
-void fsensor_reset_err_cnt()
-{
-    fsensor_err_cnt = 0;
-    pat9125_y = 0;
-    st_reset_fsensor();
-}
-
-void fsensor_set_axis_steps_per_unit(float u)
-{
-    fsensor_chunk_len = (int16_t)(FSENSOR_CHUNK_LEN * u);
-}
-#endif
-
-
-void fsensor_restore_print_and_continue(void)
-{
-    puts_P(PSTR("fsensor_restore_print_and_continue"));
-    fsensor_watch_runout = true;
-#ifdef PAT9125
-    fsensor_reset_err_cnt();
-#endif
-    restore_print_from_ram_and_continue(0);
-}
-
-// fsensor_checkpoint_print cuts the current print job at the current position,
-// allowing new instructions to be inserted in the middle
-void fsensor_checkpoint_print(void)
-{
-    puts_P(PSTR("fsensor_checkpoint_print"));
-    stop_and_save_print_to_ram(0, 0);
-    restore_print_from_ram_and_continue(0);
-}
-
-#ifdef IR_SENSOR_ANALOG
-const char* FsensorIRVersionText()
-{
-	switch(oFsensorPCB)
-	{
-		case ClFsensorPCB::_Old:
-			return _T(MSG_IR_03_OR_OLDER);
-		case ClFsensorPCB::_Rev04:
-			return _T(MSG_IR_04_OR_NEWER);
-		default:
-			return _T(MSG_IR_UNKNOWN);
-	}
-}
-#endif //IR_SENSOR_ANALOG
-
-void fsensor_init(void)
-{
-#ifdef PAT9125
-	uint8_t pat9125 = pat9125_init();
-	printf_P(PSTR("PAT9125_init:%u\n"), pat9125);
-#endif //PAT9125
-	uint8_t fsensor_enabled = eeprom_read_byte((uint8_t*)EEPROM_FSENSOR);
-	fsensor_autoload_enabled=eeprom_read_byte((uint8_t*)EEPROM_FSENS_AUTOLOAD_ENABLED);
-	fsensor_not_responding = false;
-#ifdef PAT9125
-	uint8_t oq_meassure_enabled = eeprom_read_byte((uint8_t*)EEPROM_FSENS_OQ_MEASS_ENABLED);
-	fsensor_oq_meassure_enabled = (oq_meassure_enabled == 1)?true:false;
-	fsensor_set_axis_steps_per_unit(cs.axis_steps_per_unit[E_AXIS]);
-	
-	if (!pat9125){
-		fsensor_enabled = 0; //disable sensor
-		fsensor_not_responding = true;
-	}
-#endif //PAT9125
-#ifdef IR_SENSOR_ANALOG
-	bIRsensorStateFlag=false;
-	oFsensorPCB = (ClFsensorPCB)eeprom_read_byte((uint8_t*)EEPROM_FSENSOR_PCB);
-	oFsensorActionNA = (ClFsensorActionNA)eeprom_read_byte((uint8_t*)EEPROM_FSENSOR_ACTION_NA);
-
-	// If the fsensor is not responding even at the start of the printer,
-	// set this flag accordingly to show N/A in Settings->Filament sensor.
-	// This is even valid for both fsensor board revisions (0.3 or older and 0.4).
-	// Must be done after reading what type of fsensor board we have
-	fsensor_not_responding = ! fsensor_IR_check();
-#endif //IR_SENSOR_ANALOG
-	if (fsensor_enabled){
-		fsensor_enable(false);                  // (in this case) EEPROM update is not necessary
-	} else {
-		fsensor_disable(false);                 // (in this case) EEPROM update is not necessary
-	}
-	printf_P(PSTR("FSensor %S"), (fsensor_enabled?PSTR("ENABLED"):PSTR("DISABLED")));
-#ifdef IR_SENSOR_ANALOG
-	printf_P(PSTR(" (sensor board revision:%S)\n"), FsensorIRVersionText());
-#else //IR_SENSOR_ANALOG
-	MYSERIAL.println();
-#endif //IR_SENSOR_ANALOG
-	if (check_for_ir_sensor()){
-		ir_sensor_detected = true;
-	}
-}
-
-bool fsensor_enable(bool bUpdateEEPROM)
-{
-#ifdef PAT9125
-    (void)bUpdateEEPROM; // silence unused warning in this variant
-
-	if (mmu_enabled == false) { //filament sensor is pat9125, enable only if it is working
-		uint8_t pat9125 = pat9125_init();
-		printf_P(PSTR("PAT9125_init:%u\n"), pat9125);
-		if (pat9125)
-			fsensor_not_responding = false;
-		else
-			fsensor_not_responding = true;
-		fsensor_enabled = pat9125 ? true : false;
-		fsensor_watch_runout = true;
-		fsensor_oq_meassure = false;
-        fsensor_reset_err_cnt();
-		eeprom_update_byte((uint8_t*)EEPROM_FSENSOR, fsensor_enabled ? 0x01 : 0x00);
-		FSensorStateMenu = fsensor_enabled ? 1 : 0;
-	}
-	else //filament sensor is FINDA, always enable 
-	{
-		fsensor_enabled = true;
-		eeprom_update_byte((uint8_t*)EEPROM_FSENSOR, 0x01);
-		FSensorStateMenu = 1;
-	}
-#else // PAT9125
-#ifdef IR_SENSOR_ANALOG
-     if(!fsensor_IR_check())
-          {
-          bUpdateEEPROM=true;
-          fsensor_enabled=false;
-          fsensor_not_responding=true;
-          FSensorStateMenu=0;
-          }
-     else {
-#endif //IR_SENSOR_ANALOG
-     fsensor_enabled=true;
-     fsensor_not_responding=false;
-     FSensorStateMenu=1;
-#ifdef IR_SENSOR_ANALOG
-          }
-#endif //IR_SENSOR_ANALOG
-     if(bUpdateEEPROM)
-          eeprom_update_byte((uint8_t*)EEPROM_FSENSOR, FSensorStateMenu);
-#endif //PAT9125
-	return fsensor_enabled;
-}
-
-void fsensor_disable(bool bUpdateEEPROM)
-{ 
-	fsensor_enabled = false;
-	FSensorStateMenu = 0;
-     if(bUpdateEEPROM)
-          eeprom_update_byte((uint8_t*)EEPROM_FSENSOR, 0x00); 
-}
-
-void fsensor_autoload_set(bool State)
-{
-#ifdef PAT9125
-	if (!State) fsensor_autoload_check_stop();
-#endif //PAT9125
-	fsensor_autoload_enabled = State;
-	eeprom_update_byte((unsigned char *)EEPROM_FSENS_AUTOLOAD_ENABLED, fsensor_autoload_enabled);
-}
-
-void pciSetup(byte pin)
-{
-// !!! "digitalPinTo?????bit()" does not provide the correct results for some MCU pins
-	*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pin
-	PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
-	PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group 
-}
-
-#ifdef PAT9125
-void fsensor_autoload_check_start(void)
-{
-//	puts_P(_N("fsensor_autoload_check_start\n"));
-	if (!fsensor_enabled) return;
-	if (!fsensor_autoload_enabled) return;
-	if (fsensor_watch_autoload) return;
-	if (!pat9125_update()) //update sensor
-	{
-		fsensor_disable();
-		fsensor_not_responding = true;
-		fsensor_watch_autoload = false;
-		printf_P(ERRMSG_PAT9125_NOT_RESP, 3);
-		return;
-	}
-	puts_P(_N("fsensor_autoload_check_start - autoload ENABLED"));
-	fsensor_autoload_y = pat9125_y; //save current y value
-	fsensor_autoload_c = 0; //reset number of changes counter
-	fsensor_autoload_sum = 0;
-	fsensor_autoload_last_millis = _millis();
-	fsensor_watch_runout = false;
-	fsensor_watch_autoload = true;
-}
-
-
-void fsensor_autoload_check_stop(void)
-{
-//	puts_P(_N("fsensor_autoload_check_stop\n"));
-	if (!fsensor_enabled) return;
-//	puts_P(_N("fsensor_autoload_check_stop 1\n"));
-	if (!fsensor_autoload_enabled) return;
-//	puts_P(_N("fsensor_autoload_check_stop 2\n"));
-	if (!fsensor_watch_autoload) return;
-	puts_P(_N("fsensor_autoload_check_stop - autoload DISABLED"));
-	fsensor_autoload_sum = 0;
-	fsensor_watch_autoload = false;
-	fsensor_watch_runout = true;
-    fsensor_reset_err_cnt();
-}
-#endif //PAT9125
-
-bool fsensor_check_autoload(void)
-{
-	if (!fsensor_enabled) return false;
-	if (!fsensor_autoload_enabled) return false;
-	if (ir_sensor_detected) {
-		if (READ(IR_SENSOR_PIN)) {
-			fsensor_watch_autoload = true;
-		}
-		else if (fsensor_watch_autoload == true) {
-			fsensor_watch_autoload = false;
-			return true;
-		}
-	}
-#ifdef PAT9125
-	if (!fsensor_watch_autoload)
-	{
-		fsensor_autoload_check_start();
-		return false;
-	}
-#if 0
-	uint8_t fsensor_autoload_c_old = fsensor_autoload_c;
-#endif
-	if ((_millis() - fsensor_autoload_last_millis) < 25) return false;
-	fsensor_autoload_last_millis = _millis();
-	if (!pat9125_update_y()) //update sensor
-	{
-		fsensor_disable();
-		fsensor_not_responding = true;
-		printf_P(ERRMSG_PAT9125_NOT_RESP, 2);
-		return false;
-	}
-	int16_t dy = pat9125_y - fsensor_autoload_y;
-	if (dy) //? dy value is nonzero
-	{
-		if (dy > 0) //? delta-y value is positive (inserting)
-		{
-			fsensor_autoload_sum += dy;
-			fsensor_autoload_c += 3; //increment change counter by 3
-		}
-		else if (fsensor_autoload_c > 1)
-			fsensor_autoload_c -= 2; //decrement change counter by 2 
-		fsensor_autoload_y = pat9125_y; //save current value
-	}
-	else if (fsensor_autoload_c > 0)
-		fsensor_autoload_c--;
-	if (fsensor_autoload_c == 0) fsensor_autoload_sum = 0;
-#if 0
-  	puts_P(_N("fsensor_check_autoload\n"));
-  	if (fsensor_autoload_c != fsensor_autoload_c_old)
-  		printf_P(PSTR("fsensor_check_autoload dy=%d c=%d sum=%d\n"), dy, fsensor_autoload_c, fsensor_autoload_sum);
-#endif
-//	if ((fsensor_autoload_c >= 15) && (fsensor_autoload_sum > 30))
-	if ((fsensor_autoload_c >= 12) && (fsensor_autoload_sum > 20))
-	{
-//		puts_P(_N("fsensor_check_autoload = true !!!\n"));
-		return true;
-	}
-#endif //PAT9125
-	return false;
-}
-
-#ifdef PAT9125
-void fsensor_oq_meassure_set(bool State)
-{
-	fsensor_oq_meassure_enabled = State;
-	eeprom_update_byte((unsigned char *)EEPROM_FSENS_OQ_MEASS_ENABLED, fsensor_oq_meassure_enabled);
-}
-
-void fsensor_oq_meassure_start(uint8_t skip)
-{
-	if (!fsensor_enabled) return;
-	if (!fsensor_oq_meassure_enabled) return;
-	puts_P(PSTR("fsensor_oq_meassure_start"));
-	fsensor_oq_skipchunk = skip;
-	fsensor_oq_samples = 0;
-	fsensor_oq_st_sum = 0;
-	fsensor_oq_yd_sum = 0;
-	fsensor_oq_er_sum = 0;
-	fsensor_oq_er_max = 0;
-	fsensor_oq_yd_min = INT16_MAX;
-	fsensor_oq_yd_max = 0;
-	fsensor_oq_sh_sum = 0;
-	pat9125_update();
-	pat9125_y = 0;
-	fsensor_oq_meassure = true;
-}
-
-void fsensor_oq_meassure_stop(void)
-{
-	if (!fsensor_enabled) return;
-	if (!fsensor_oq_meassure_enabled) return;
-	printf_P(PSTR("fsensor_oq_meassure_stop, %u samples\n"), fsensor_oq_samples);
-	printf_P(_N(" st_sum=%u yd_sum=%u er_sum=%u er_max=%u\n"), fsensor_oq_st_sum, fsensor_oq_yd_sum, fsensor_oq_er_sum, fsensor_oq_er_max);
-	printf_P(_N(" yd_min=%u yd_max=%u yd_avg=%u sh_avg=%u\n"), fsensor_oq_yd_min, fsensor_oq_yd_max, (uint16_t)((uint32_t)fsensor_oq_yd_sum * fsensor_chunk_len / fsensor_oq_st_sum), (uint16_t)(fsensor_oq_sh_sum / fsensor_oq_samples));
-	fsensor_oq_meassure = false;
-}
-
-#ifdef FSENSOR_QUALITY
-const char _OK[] PROGMEM = "OK";
-const char _NG[] PROGMEM = "NG!";
-
-bool fsensor_oq_result(void)
-{
-	if (!fsensor_enabled) return true;
-	if (!fsensor_oq_meassure_enabled) return true;
-	puts_P(_N("fsensor_oq_result"));
-	bool res_er_sum = (fsensor_oq_er_sum <= FSENSOR_OQ_MAX_ES);
-	printf_P(_N(" er_sum = %u %S\n"), fsensor_oq_er_sum, (res_er_sum?_OK:_NG));
-	bool res_er_max = (fsensor_oq_er_max <= FSENSOR_OQ_MAX_EM);
-	printf_P(_N(" er_max = %u %S\n"), fsensor_oq_er_max, (res_er_max?_OK:_NG));
-	uint8_t yd_avg = ((uint32_t)fsensor_oq_yd_sum * fsensor_chunk_len / fsensor_oq_st_sum);
-	bool res_yd_avg = (yd_avg >= FSENSOR_OQ_MIN_YD) && (yd_avg <= FSENSOR_OQ_MAX_YD);
-	printf_P(_N(" yd_avg = %u %S\n"), yd_avg, (res_yd_avg?_OK:_NG));
-	bool res_yd_max = (fsensor_oq_yd_max <= (yd_avg * FSENSOR_OQ_MAX_PD));
-	printf_P(_N(" yd_max = %u %S\n"), fsensor_oq_yd_max, (res_yd_max?_OK:_NG));
-	bool res_yd_min = (fsensor_oq_yd_min >= (yd_avg / FSENSOR_OQ_MAX_ND));
-	printf_P(_N(" yd_min = %u %S\n"), fsensor_oq_yd_min, (res_yd_min?_OK:_NG));
-
-	uint16_t yd_dev = (fsensor_oq_yd_max - yd_avg) + (yd_avg - fsensor_oq_yd_min);
-	printf_P(_N(" yd_dev = %u\n"), yd_dev);
-
-	uint16_t yd_qua = 10 * yd_avg / (yd_dev + 1);
-	printf_P(_N(" yd_qua = %u %S\n"), yd_qua, ((yd_qua >= 8)?_OK:_NG));
-
-	uint8_t sh_avg = (fsensor_oq_sh_sum / fsensor_oq_samples);
-	bool res_sh_avg = (sh_avg <= FSENSOR_OQ_MAX_SH);
-	if (yd_qua >= 8) res_sh_avg = true;
-
-	printf_P(_N(" sh_avg = %u %S\n"), sh_avg, (res_sh_avg?_OK:_NG));
-	bool res = res_er_sum && res_er_max && res_yd_avg && res_yd_max && res_yd_min && res_sh_avg;
-	printf_P(_N("fsensor_oq_result %S\n"), (res?_OK:_NG));
-	return res;
-}
-#endif //FSENSOR_QUALITY
-
-FORCE_INLINE static void fsensor_isr(int st_cnt)
-{
-	uint8_t old_err_cnt = fsensor_err_cnt;
-	uint8_t pat9125_res = fsensor_oq_meassure?pat9125_update():pat9125_update_y();
-	if (!pat9125_res)
-	{
-		fsensor_disable();
-		fsensor_not_responding = true;
-		printf_P(ERRMSG_PAT9125_NOT_RESP, 1);
-	}
-
-	if (st_cnt != 0)
-	{
-        // movement was planned, check for sensor movement
-        int8_t st_dir = st_cnt >= 0;
-        int8_t pat9125_dir = pat9125_y >= 0;
-
-        if (pat9125_y == 0)
-        {
-            if (st_dir)
-            {
-                // no movement detected: we might be within a blind sensor range,
-                // update the frame and shutter parameters we didn't earlier
-                if (!fsensor_oq_meassure)
-                    pat9125_update_bs();
-
-                // increment the error count only if underexposed: filament likely missing
-                if ((pat9125_b < FSENSOR_OQ_MIN_BR) && (pat9125_s > FSENSOR_OQ_MAX_SH))
-                {
-                    // check for a dark frame (<30% avg brightness) with long exposure
-                    ++fsensor_err_cnt;
-                }
-                else
-                {
-                    // good frame, filament likely present
-                    if(fsensor_err_cnt) --fsensor_err_cnt;
-                }
-            }
-        }
-        else if (pat9125_dir != st_dir)
-        {
-            // detected direction opposite of motor movement
-            if (st_dir) ++fsensor_err_cnt;
-        }
-        else if (pat9125_dir == st_dir)
-        {
-            // direction agreeing with planned movement
-            if (fsensor_err_cnt) --fsensor_err_cnt;
-        }
-
-        if (st_dir && fsensor_oq_meassure)
-        {
-            // extruding with quality assessment
-            if (fsensor_oq_skipchunk)
-            {
-                fsensor_oq_skipchunk--;
-                fsensor_err_cnt = 0;
-            }
-            else
-            {
-                if (st_cnt == fsensor_chunk_len)
-                {
-                    if (pat9125_y > 0) if (fsensor_oq_yd_min > pat9125_y) fsensor_oq_yd_min = (fsensor_oq_yd_min + pat9125_y) / 2;
-                    if (pat9125_y >= 0) if (fsensor_oq_yd_max < pat9125_y) fsensor_oq_yd_max = (fsensor_oq_yd_max + pat9125_y) / 2;
-                }
-                fsensor_oq_samples++;
-                fsensor_oq_st_sum += st_cnt;
-                if (pat9125_y > 0) fsensor_oq_yd_sum += pat9125_y;
-                if (fsensor_err_cnt > old_err_cnt)
-                    fsensor_oq_er_sum += (fsensor_err_cnt - old_err_cnt);
-                if (fsensor_oq_er_max < fsensor_err_cnt)
-                    fsensor_oq_er_max = fsensor_err_cnt;
-                fsensor_oq_sh_sum += pat9125_s;
-            }
-        }
-	}
-
-#ifdef DEBUG_FSENSOR_LOG
-	if (fsensor_log)
-	{
-		printf_P(_N("FSENSOR cnt=%d dy=%d err=%u %S\n"), st_cnt, pat9125_y, fsensor_err_cnt, (fsensor_err_cnt > old_err_cnt)?_N("NG!"):_N("OK"));
-		if (fsensor_oq_meassure) printf_P(_N("FSENSOR st_sum=%u yd_sum=%u er_sum=%u er_max=%u yd_max=%u\n"), fsensor_oq_st_sum, fsensor_oq_yd_sum, fsensor_oq_er_sum, fsensor_oq_er_max, fsensor_oq_yd_max);
-	}
-#endif //DEBUG_FSENSOR_LOG
-
-	pat9125_y = 0;
-}
-
-ISR(FSENSOR_INT_PIN_VECT)
-{
-    if (mmu_enabled || ir_sensor_detected) return;
-    if (!((fsensor_int_pin_old ^ FSENSOR_INT_PIN_PIN_REG) & FSENSOR_INT_PIN_MASK)) return;
-    fsensor_int_pin_old = FSENSOR_INT_PIN_PIN_REG;
-
-    // prevent isr re-entry
-    static bool _lock = false;
-    if (!_lock)
-    {
-        // fetch fsensor_st_cnt atomically
-        int st_cnt = fsensor_st_cnt;
-        fsensor_st_cnt = 0;
-
-        _lock = true;
-        sei();
-        fsensor_isr(st_cnt);
-        cli();
-        _lock = false;
-    }
-}
-
-void fsensor_setup_interrupt(void)
-{
-	WRITE(FSENSOR_INT_PIN, 0);
-	SET_OUTPUT(FSENSOR_INT_PIN);
-	fsensor_int_pin_old = 0;
-
-	//pciSetup(FSENSOR_INT_PIN);
-// !!! "pciSetup()" does not provide the correct results for some MCU pins
-// so interrupt registers settings:
-     FSENSOR_INT_PIN_PCMSK_REG |= bit(FSENSOR_INT_PIN_PCMSK_BIT); // enable corresponding PinChangeInterrupt (individual pin)
-     PCIFR |= bit(FSENSOR_INT_PIN_PCICR_BIT);     // clear previous occasional interrupt (set of pins)
-     PCICR |= bit(FSENSOR_INT_PIN_PCICR_BIT);     // enable corresponding PinChangeInterrupt (set of pins)
-}
-
-void fsensor_st_block_chunk(int cnt)
-{
-	if (!fsensor_enabled) return;
-	fsensor_st_cnt += cnt;
-
-	// !!! bit toggling (PINxn <- 1) (for PinChangeInterrupt) does not work for some MCU pins
-	WRITE(FSENSOR_INT_PIN, !READ(FSENSOR_INT_PIN));
-}
-#endif //PAT9125
-
-
-//! Common code for enqueing M600 and supplemental codes into the command queue.
-//! Used both for the IR sensor and the PAT9125
-void fsensor_enque_M600(){
-	puts_P(PSTR("fsensor_update - M600"));
-	eeprom_update_byte((uint8_t*)EEPROM_FERROR_COUNT, eeprom_read_byte((uint8_t*)EEPROM_FERROR_COUNT) + 1);
-	eeprom_update_word((uint16_t*)EEPROM_FERROR_COUNT_TOT, eeprom_read_word((uint16_t*)EEPROM_FERROR_COUNT_TOT) + 1);
-	enquecommand_front_P((PSTR("M600")));
-}
-
-//! @brief filament sensor update (perform M600 on filament runout)
-//!
-//! Works only if filament sensor is enabled.
-//! When the filament sensor error count is larger then FSENSOR_ERR_MAX, pauses print, tries to move filament back and forth.
-//! If there is still no plausible signal from filament sensor plans M600 (Filament change).
-void fsensor_update(void)
-{
-#ifdef PAT9125
-    if (fsensor_watch_runout && (fsensor_err_cnt > FSENSOR_ERR_MAX))
-        {
-            fsensor_stop_and_save_print();
-            KEEPALIVE_STATE(IN_HANDLER);
-
-            bool autoload_enabled_tmp = fsensor_autoload_enabled;
-            fsensor_autoload_enabled = false;
-            bool oq_meassure_enabled_tmp = fsensor_oq_meassure_enabled;
-            fsensor_oq_meassure_enabled = true;
-
-            // move the nozzle away while checking the filament
-            current_position[Z_AXIS] += 0.8;
-            if(current_position[Z_AXIS] > Z_MAX_POS) current_position[Z_AXIS] = Z_MAX_POS;
-            plan_buffer_line_curposXYZE(max_feedrate[Z_AXIS]);
-            st_synchronize();
-
-            // check the filament in isolation
-            fsensor_reset_err_cnt();
-            fsensor_oq_meassure_start(0);
-            float e_tmp = current_position[E_AXIS];
-            current_position[E_AXIS] -= 3;
-            plan_buffer_line_curposXYZE(250/60);
-            current_position[E_AXIS] = e_tmp;
-            plan_buffer_line_curposXYZE(200/60);
-            st_synchronize();
-            fsensor_oq_meassure_stop();
-
-            bool err = false;
-            err |= (fsensor_err_cnt > 0);                   // final error count is non-zero
-            err |= (fsensor_oq_er_sum > FSENSOR_OQ_MAX_ES); // total error count is above limit
-            err |= (fsensor_oq_yd_sum < FSENSOR_OQ_MIN_YD); // total measured distance is below limit
-
-            fsensor_restore_print_and_continue();
-            fsensor_autoload_enabled = autoload_enabled_tmp;
-            fsensor_oq_meassure_enabled = oq_meassure_enabled_tmp;
-            unsigned long now = _millis();
-            if (!err && (now - fsensor_softfail_last) > FSENSOR_SOFTERR_DELTA)
-                fsensor_softfail_ccnt = 0;
-            if (!err && fsensor_softfail_ccnt <= FSENSOR_SOFTERR_CMAX)
-            {
-                puts_P(PSTR("fsensor_err_cnt = 0"));
-                ++fsensor_softfail;
-                ++fsensor_softfail_ccnt;
-                fsensor_softfail_last = now;
-            }
-            else
-            {
-                fsensor_softfail_ccnt = 0;
-                fsensor_softfail_last = 0;
-                fsensor_enque_M600();
-            }
-        }
-#else //PAT9125
-        if (CHECK_FSENSOR && ir_sensor_detected)
-        {
-            if (READ(IR_SENSOR_PIN))
-            {                                  // IR_SENSOR_PIN ~ H
-#ifdef IR_SENSOR_ANALOG
-                if(!bIRsensorStateFlag)
-                {
-                    bIRsensorStateFlag=true;
-                    tIRsensorCheckTimer.start();
-                }
-                else
-                {
-                    if(tIRsensorCheckTimer.expired(IR_SENSOR_STEADY))
-                    {
-                        uint8_t nMUX1,nMUX2;
-                        uint16_t nADC;
-                        bIRsensorStateFlag=false;
-                        // sequence for direct data reading from AD converter
-                        DISABLE_TEMPERATURE_INTERRUPT();
-                        nMUX1=ADMUX;        // ADMUX saving
-                        nMUX2=ADCSRB;
-                        adc_setmux(VOLT_IR_PIN);
-                        ADCSRA|=(1<<ADSC);  // first conversion after ADMUX change discarded (preventively)
-                        while(ADCSRA&(1<<ADSC))
-                            ;
-                        ADCSRA|=(1<<ADSC);  // second conversion used
-                        while(ADCSRA&(1<<ADSC))
-                            ;
-                        nADC=ADC;
-                        ADMUX=nMUX1;        // ADMUX restoring
-                        ADCSRB=nMUX2;
-                        ENABLE_TEMPERATURE_INTERRUPT();
-                        // end of sequence for ...
-                        // Detection of correct function of fsensor v04 - it must NOT read >4.6V
-                        // If it does, it means a disconnected cables or faulty board
-                        if( (oFsensorPCB == ClFsensorPCB::_Rev04) && ( (nADC*OVERSAMPLENR) > IRsensor_Hopen_TRESHOLD ) )
-                        {
-                            fsensor_disable();
-                            fsensor_not_responding = true;
-                            printf_P(PSTR("IR sensor not responding (%d)!\n"),1);
-                            if((ClFsensorActionNA)eeprom_read_byte((uint8_t*)EEPROM_FSENSOR_ACTION_NA)==ClFsensorActionNA::_Pause)
-
-                            // if we are printing and FS action is set to "Pause", force pause the print
-                            if(oFsensorActionNA==ClFsensorActionNA::_Pause)
-                                lcd_pause_print();
-                        }
-                        else
-                        {
-#endif //IR_SENSOR_ANALOG
-                            fsensor_checkpoint_print();
-                            fsensor_enque_M600();
-#ifdef IR_SENSOR_ANALOG
-                        }
-                    }
-                }
-                   }
-                   else
-                   {                                  // IR_SENSOR_PIN ~ L
-                        bIRsensorStateFlag=false;
-#endif //IR_SENSOR_ANALOG
-            }
-        }
-#endif //PAT9125
-}
-
-#ifdef IR_SENSOR_ANALOG
-/// This is called only upon start of the printer or when switching the fsensor ON in the menu
-/// We cannot do temporal window checks here (aka the voltage has been in some range for a period of time)
-bool fsensor_IR_check(){
-    if( IRsensor_Lmax_TRESHOLD <= current_voltage_raw_IR && current_voltage_raw_IR <= IRsensor_Hmin_TRESHOLD ){
-        /// If the voltage is in forbidden range, the fsensor is ok, but the lever is mounted improperly.
-        /// Or the user is so creative so that he can hold a piece of fillament in the hole in such a genius way,
-        /// that the IR fsensor reading is within 1.5 and 3V ... this would have been highly unusual
-        /// and would have been considered more like a sabotage than normal printer operation
-        puts_P(PSTR("fsensor in forbidden range 1.5-3V - check sensor"));
-        return false; 
-    }
-    if( oFsensorPCB == ClFsensorPCB::_Rev04 ){
-        /// newer IR sensor cannot normally produce 4.6-5V, this is considered a failure/bad mount
-        if( IRsensor_Hopen_TRESHOLD <= current_voltage_raw_IR && current_voltage_raw_IR <= IRsensor_VMax_TRESHOLD ){
-            puts_P(PSTR("fsensor v0.4 in fault range 4.6-5V - unconnected"));
-            return false;
-        }
-        /// newer IR sensor cannot normally produce 0-0.3V, this is considered a failure 
-#if 0	//Disabled as it has to be decided if we gonna use this or not.
-        if( IRsensor_Hopen_TRESHOLD <= current_voltage_raw_IR && current_voltage_raw_IR <= IRsensor_VMax_TRESHOLD ){
-            puts_P(PSTR("fsensor v0.4 in fault range 0.0-0.3V - wrong IR sensor"));
-            return false;
-        }
-#endif
-    }
-    /// If IR sensor is "uknown state" and filament is not loaded > 1.5V return false
-#if 0
-    if( (oFsensorPCB == ClFsensorPCB::_Undef) && ( current_voltage_raw_IR > IRsensor_Lmax_TRESHOLD ) ){
-        puts_P(PSTR("Unknown IR sensor version and no filament loaded detected."));
-        return false;
-    }
-#endif
-    // otherwise the IR fsensor is considered working correctly
-    return true;
-}
-#endif //IR_SENSOR_ANALOG

+ 0 - 124
Firmware/fsensor.h

@@ -1,124 +0,0 @@
-//! @file
-#ifndef FSENSOR_H
-#define FSENSOR_H
-
-#include <inttypes.h>
-#include "config.h"
-
-
-// enable/disable flag
-extern bool fsensor_enabled;
-// not responding flag
-extern bool fsensor_not_responding;
-#ifdef PAT9125
-// optical checking "chunk lenght" (already in steps)
-extern int16_t fsensor_chunk_len;
-// count of soft failures
-extern uint8_t fsensor_softfail;
-#endif
-
-//! @name save restore printing
-//! @{
-extern void fsensor_stop_and_save_print(void);
-//! restore print - restore position and heatup to original temperature
-extern void fsensor_restore_print_and_continue(void);
-//! split the current gcode stream to insert new instructions
-extern void fsensor_checkpoint_print(void);
-//! @}
-
-//! initialize
-extern void fsensor_init(void);
-
-#ifdef PAT9125
-//! update axis resolution
-extern void fsensor_set_axis_steps_per_unit(float u);
-#endif
-
-//! @name enable/disable
-//! @{
-extern bool fsensor_enable(bool bUpdateEEPROM=true);
-extern void fsensor_disable(bool bUpdateEEPROM=true);
-//! @}
-
-//autoload feature enabled
-extern bool fsensor_autoload_enabled;
-extern void fsensor_autoload_set(bool State);
-
-extern void fsensor_update(void);
-#ifdef PAT9125
-//! setup pin-change interrupt
-extern void fsensor_setup_interrupt(void);
-
-//! @name autoload support
-//! @{
-
-extern void fsensor_autoload_check_start(void);
-extern void fsensor_autoload_check_stop(void);
-#endif //PAT9125
-extern bool fsensor_check_autoload(void);
-//! @}
-
-#ifdef PAT9125
-//! @name optical quality measurement support
-//! @{
-extern bool fsensor_oq_meassure_enabled;
-extern void fsensor_oq_meassure_set(bool State);
-extern void fsensor_oq_meassure_start(uint8_t skip);
-extern void fsensor_oq_meassure_stop(void);
-extern bool fsensor_oq_result(void);
-//! @}
-
-//! @name callbacks from stepper
-//! @{
-extern void fsensor_st_block_chunk(int cnt);
-
-// debugging
-extern uint8_t fsensor_log;
-
-// There's really nothing to do in block_begin: the stepper ISR likely has
-// called us already at the end of the last block, making this integration
-// redundant. LA1.5 might not always do that during a coasting move, so attempt
-// to drain fsensor_st_cnt anyway at the beginning of the new block.
-#define fsensor_st_block_begin(rev) fsensor_st_block_chunk(0)
-//! @}
-#endif //PAT9125
-
-#define VOLT_DIV_REF 5
-
-#ifdef IR_SENSOR_ANALOG
-#define IR_SENSOR_STEADY 10                       // [ms]
-
-enum class ClFsensorPCB:uint_least8_t
-{
-    _Old=0,
-    _Rev04=1,
-    _Undef=EEPROM_EMPTY_VALUE
-};
-
-enum class ClFsensorActionNA:uint_least8_t
-{
-    _Continue=0,
-    _Pause=1,
-    _Undef=EEPROM_EMPTY_VALUE
-};
-
-extern ClFsensorPCB oFsensorPCB;
-extern ClFsensorActionNA oFsensorActionNA;
-extern const char* FsensorIRVersionText();
-
-extern bool fsensor_IR_check();
-constexpr uint16_t Voltage2Raw(float V){
-	return ( V * 1023 * OVERSAMPLENR / VOLT_DIV_REF ) + 0.5F;
-}
-constexpr float Raw2Voltage(uint16_t raw){
-	return VOLT_DIV_REF*(raw / (1023.F * OVERSAMPLENR) );
-}
-constexpr uint16_t IRsensor_Ldiode_TRESHOLD = Voltage2Raw(0.3F); // ~0.3V, raw value=982
-constexpr uint16_t IRsensor_Lmax_TRESHOLD = Voltage2Raw(1.5F); // ~1.5V (0.3*Vcc), raw value=4910
-constexpr uint16_t IRsensor_Hmin_TRESHOLD = Voltage2Raw(3.0F); // ~3.0V (0.6*Vcc), raw value=9821
-constexpr uint16_t IRsensor_Hopen_TRESHOLD = Voltage2Raw(4.6F); // ~4.6V (N.C. @ Ru~20-50k, Rd'=56k, Ru'=10k), raw value=15059
-constexpr uint16_t IRsensor_VMax_TRESHOLD = Voltage2Raw(5.F); // ~5V, raw value=16368
-
-#endif //IR_SENSOR_ANALOG
-
-#endif //FSENSOR_H

+ 2 - 2
Firmware/language.c

@@ -240,9 +240,9 @@ const char* lang_get_name_by_code(uint16_t code)
 #ifdef COMMUNITY_LANG_GROUP1_HR
 	case LANG_CODE_HR: return _n("Hrvatski"); //community Croatian contribution
 #endif // COMMUNITY_LANG_GROUP1_HR
-#ifdef COMMUNITY_LANG_GROUP1_LT
+#ifdef COMMUNITY_LANG_GROUP2_LT
 	case LANG_CODE_LT: return _n("Lietuviu"); //community Lithuanian contribution
-#endif // COMMUNITY_LANG_GROUP1_LT
+#endif // COMMUNITY_LANG_GROUP2_LT
 #ifdef COMMUNITY_LANG_GROUP1_RO
 	case LANG_CODE_RO: return _n("Romana"); //community Romanian contribution
 #endif // COMMUNITY_LANG_GROUP1_RO

+ 9 - 7
Firmware/language.h

@@ -26,10 +26,10 @@
 #define PROGMEM_I1 __attribute__((section(".progmem1")))
 #define PROGMEM_N1 __attribute__((section(".progmem2")))
 #define _I(s) (__extension__({static const char __c[] PROGMEM_I1 = s; &__c[0];}))
-#define ISTR(s) s
-#define _i(s) _I(s)
-#define _T(s) s
-#define _O(s) s
+#define ISTR(s) (s) // declare a translatable string
+#define _i(s) _I(s) // declare a translatable string and return the translated form
+#define _T(s) (s)   // return translated string from reference
+#define _O(s) (s)   // return original (untranslated) string from reference
 #else //(LANG_MODE == 0)
 // section .loc_sec (originaly .progmem0) will be used for localized translated strings
 #define PROGMEM_I2 __attribute__((section(".loc_sec")))
@@ -43,8 +43,10 @@
 #define _T(s) lang_get_translation(s)
 #define _O(s) (s + 2)
 #endif //(LANG_MODE == 0)
+
 #define _N(s) (__extension__({static const char __c[] PROGMEM_N1 = s; &__c[0];}))
-#define _n(s) _N(s)
+#define _n(s) _N(s) // declare and return untranslated string
+#define _R(s) (s)   // return reference to translatable string (for warning suppression)
 
 /** @brief lang_table_header_t structure - (size= 16byte) */
 typedef struct
@@ -121,9 +123,9 @@ typedef struct
 #ifdef COMMUNITY_LANG_GROUP1_HR
 #define LANG_CODE_HR 0x6872 //!<'hr'
 #endif // COMMUNITY_LANG_GROUP1_HR
-#ifdef COMMUNITY_LANG_GROUP1_LT
+#ifdef COMMUNITY_LANG_GROUP2_LT
 #define LANG_CODE_LT 0x6C74 //!<'lt'
-#endif // COMMUNITY_LANG_GROUP1_LT
+#endif // COMMUNITY_LANG_GROUP2_LT
 #ifdef COMMUNITY_LANG_GROUP1_SK
 #define LANG_CODE_SK 0x736b //!<'sk'
 #endif // COMMUNITY_LANG_GROUP1_SK

+ 6 - 1
Firmware/lcd.cpp

@@ -528,6 +528,12 @@ void lcd_print(const char* s)
 	while (*s) lcd_write(*(s++));
 }
 
+void lcd_print_pad(const char* s, uint8_t len)
+{
+    while (len-- && *s) lcd_write(*(s++));
+    lcd_space(len);
+}
+
 void lcd_print(char c, int base)
 {
 	lcd_print((long) c, base);
@@ -638,7 +644,6 @@ uint8_t lcd_button_pressed = 0;
 uint8_t lcd_update_enabled = 1;
 
 uint32_t lcd_next_update_millis = 0;
-uint8_t lcd_status_update_delay = 0;
 
 
 

+ 3 - 4
Firmware/lcd.h

@@ -53,6 +53,7 @@ extern void lcd_printNumber(unsigned long n, uint8_t base);
 extern void lcd_printFloat(double number, uint8_t digits);
 
 extern void lcd_print(const char*);
+extern void lcd_print_pad(const char*, uint8_t len);
 extern void lcd_print(char, int = 0);
 extern void lcd_print(unsigned char, int = 0);
 extern void lcd_print(int, int = 10);
@@ -107,8 +108,6 @@ extern LongTimer lcd_timeoutToStatus;
 
 extern uint32_t lcd_next_update_millis;
 
-extern uint8_t lcd_status_update_delay;
-
 extern lcd_longpress_func_t lcd_longpress_func;
 extern bool lcd_longpress_trigger;
 
@@ -191,14 +190,14 @@ private:
 //Custom characters defined in the first 8 characters of the LCD
 #define LCD_STR_BEDTEMP      "\x00"
 #define LCD_STR_DEGREE       "\x01"
-#define LCD_STR_ARROW_2_DOWN "\x01"
 #define LCD_STR_THERMOMETER  "\x02"
-#define LCD_STR_CONFIRM      "\x02"
 #define LCD_STR_UPLEVEL      "\x03"
 #define LCD_STR_REFRESH      "\x04"
 #define LCD_STR_FOLDER       "\x05"
 #define LCD_STR_FEEDRATE     "\x06"
+#define LCD_STR_ARROW_2_DOWN "\x06"
 #define LCD_STR_CLOCK        "\x07"
+#define LCD_STR_CONFIRM      "\x07"
 #define LCD_STR_ARROW_RIGHT  "\x7E" //from the default character set
 #define LCD_STR_SOLID_BLOCK  "\xFF"  //from the default character set
 

+ 3 - 0
Firmware/macros.h

@@ -11,6 +11,9 @@
   #define CRITICAL_SECTION_END    SREG = _sreg;
 #endif //CRITICAL_SECTION_START
 
+#define _REGNAME(registerbase,number,suffix) registerbase##number##suffix
+#define _REGNAME_SHORT(registerbase,suffix) registerbase##suffix
+
 // Macros to make a string from a macro
 #define STRINGIFY_(M) #M
 #define STRINGIFY(M) STRINGIFY_(M)

+ 1 - 1
Firmware/menu.cpp

@@ -24,7 +24,7 @@ uint8_t menu_data[MENU_DATA_SIZE];
 #endif
 
 uint8_t menu_depth = 0;
-uint8_t menu_block_entering_on_serious_errors = SERIOUS_ERR_NONE;
+uint8_t menu_block_mask = MENU_BLOCK_NONE;
 uint8_t menu_line = 0;
 uint8_t menu_item = 0;
 uint8_t menu_row = 0;

+ 14 - 14
Firmware/menu.h

@@ -29,26 +29,26 @@ extern uint8_t menu_data[MENU_DATA_SIZE];
 
 extern uint8_t menu_depth;
 
-//! definition of serious errors possibly blocking the main menu
+//! definition of reasons blocking the main menu
 //! Use them as bit mask, so that the code may set various errors at the same time
 enum ESeriousErrors {
-	SERIOUS_ERR_NONE            = 0,
-	SERIOUS_ERR_MINTEMP_HEATER  = 0x01,
-	SERIOUS_ERR_MINTEMP_BED     = 0x02
+	MENU_BLOCK_NONE                = 0,
+	MENU_BLOCK_THERMAL_ERROR       = 0x01,
+#ifdef TEMP_MODEL
+	MENU_BLOCK_TEMP_MODEL_AUTOTUNE = 0x02,
+#endif
 }; // and possibly others in the future.
 
-//! this is a flag for disabling entering the main menu. If this is set
-//! to anything != 0, the only the main status screen will be shown on the
-//! LCD and the user will be prevented from entering the menu.
-//! Now used only to block doing anything with the printer when there is
-//! the infamous MINTEMP error (SERIOUS_ERR_MINTEMP).
-extern uint8_t menu_block_entering_on_serious_errors;
+//! this is a flag for disabling entering the main menu and longpress. If this is set to anything !=
+//! 0, the only the main status screen will be shown on the LCD and the user will be prevented from
+//! entering the menu.
+extern uint8_t menu_block_mask;
 
-//! a pair of macros for manipulating the serious errors
+//! a pair of macros for manipulating menu entry
 //! a c++ class would have been better
-#define menu_set_serious_error(x) menu_block_entering_on_serious_errors |= x;
-#define menu_unset_serious_error(x) menu_block_entering_on_serious_errors &= ~x;
-#define menu_is_serious_error(x) (menu_block_entering_on_serious_errors & x) != 0
+#define menu_set_block(x) menu_block_mask |= x;
+#define menu_unset_block(x) menu_block_mask &= ~x;
+#define menu_is_blocked(x) (menu_block_mask & x) != 0
 
 extern uint8_t menu_line;
 extern uint8_t menu_item;

+ 23 - 27
Firmware/mesh_bed_calibration.cpp

@@ -418,9 +418,9 @@ BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
     for (uint8_t i = 0; i < npts; ++i) {
         float x = vec_x[0] * measured_pts[i * 2] + vec_y[0] * measured_pts[i * 2 + 1] + cntr[0];
         float y = vec_x[1] * measured_pts[i * 2] + vec_y[1] * measured_pts[i * 2 + 1] + cntr[1];
-        float errX = sqr(pgm_read_float(true_pts + i * 2) - x);
-        float errY = sqr(pgm_read_float(true_pts + i * 2 + 1) - y);
-        float err = sqrt(errX + errY);
+        float errX = pgm_read_float(true_pts + i * 2) - x;
+        float errY = pgm_read_float(true_pts + i * 2 + 1) - y;
+        float err = hypot(errX, errY);
 		#ifdef SUPPORT_VERBOSITY
 		if (verbosity_level >= 10) {
 			SERIAL_ECHOPGM("point #");
@@ -434,15 +434,15 @@ BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
 				if(verbosity_level >= 20) SERIAL_ECHOPGM("Point on first row");
 				#endif // SUPPORT_VERBOSITY
 				float w = point_weight_y(i, measured_pts[2 * i + 1]);
-				if (sqrt(errX) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X ||
-					(w != 0.f && sqrt(errY) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y)) {
+				if (errX > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X ||
+					(w != 0.f && errY > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y)) {
 					result = BED_SKEW_OFFSET_DETECTION_FITTING_FAILED;
 					#ifdef SUPPORT_VERBOSITY
 					if (verbosity_level >= 20) {
 						SERIAL_ECHOPGM(", weigth Y: ");
 						MYSERIAL.print(w);
-						if (sqrt(errX) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X) SERIAL_ECHOPGM(", error X > max. error X");
-						if (w != 0.f && sqrt(errY) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y) SERIAL_ECHOPGM(", error Y > max. error Y");
+						if (errX > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X) SERIAL_ECHOPGM(", error X > max. error X");
+						if (w != 0.f && errY > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y) SERIAL_ECHOPGM(", error Y > max. error Y");
 					}
 					#endif // SUPPORT_VERBOSITY
 				}
@@ -477,9 +477,9 @@ BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
 			SERIAL_ECHOPGM("error: ");
             MYSERIAL.print(err);
 			SERIAL_ECHOPGM(", error X: ");
-			MYSERIAL.print(sqrt(errX));
+			MYSERIAL.print(errX);
 			SERIAL_ECHOPGM(", error Y: ");
-			MYSERIAL.print(sqrt(errY));
+			MYSERIAL.print(errY);
 			SERIAL_ECHOLNPGM("");
 			SERIAL_ECHOLNPGM("");
         }
@@ -645,7 +645,7 @@ BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
             SERIAL_ECHOPGM(", ");
             MYSERIAL.print(pgm_read_float(true_pts + i * 2 + 1), 5);
             SERIAL_ECHOPGM("), error: ");
-            MYSERIAL.print(sqrt(sqr(measured_pts[i * 2] - x) + sqr(measured_pts[i * 2 + 1] - y)));
+            MYSERIAL.print( hypot(measured_pts[i * 2] - x, measured_pts[i * 2 + 1] - y) );
             SERIAL_ECHOLNPGM("");
         }
 		if (verbosity_level >= 20) {
@@ -810,7 +810,7 @@ void world2machine_read_valid(float vec_x[2], float vec_y[2], float cntr[2])
     else
     {
         // Length of the vec_x shall be close to unity.
-        float l = sqrt(vec_x[0] * vec_x[0] + vec_x[1] * vec_x[1]);
+        float l = hypot(vec_x[0], vec_x[1]);
         if (l < 0.9 || l > 1.1)
         {
 #if 0
@@ -821,7 +821,7 @@ void world2machine_read_valid(float vec_x[2], float vec_y[2], float cntr[2])
             reset = true;
         }
         // Length of the vec_y shall be close to unity.
-        l = sqrt(vec_y[0] * vec_y[0] + vec_y[1] * vec_y[1]);
+        l = hypot(vec_y[0], vec_y[1]);
         if (l < 0.9 || l > 1.1)
         {
 #if 0
@@ -832,7 +832,7 @@ void world2machine_read_valid(float vec_x[2], float vec_y[2], float cntr[2])
             reset = true;
         }
         // Correction of the zero point shall be reasonably small.
-        l = sqrt(cntr[0] * cntr[0] + cntr[1] * cntr[1]);
+        l = hypot(cntr[0], cntr[1]);
         if (l > 15.f)
         {
 #if 0
@@ -974,7 +974,7 @@ bool find_bed_induction_sensor_point_z(float minimum_z, uint8_t n_iter, int
 		goto error;
 	}
 #ifdef TMC2130
-	if (READ(Z_TMC2130_DIAG) != 0)
+	if (!READ(Z_TMC2130_DIAG))
 	{
 		//printf_P(PSTR("crash detected 1, current_pos[Z]: %f \n"), current_position[Z_AXIS]);
 		goto error; //crash Z detected
@@ -995,8 +995,7 @@ bool find_bed_induction_sensor_point_z(float minimum_z, uint8_t n_iter, int
 		//printf_P(PSTR("Zs: %f, Z: %f, delta Z: %f"), z_bckp, current_position[Z_AXIS], (z_bckp - current_position[Z_AXIS]));
 		if (fabs(current_position[Z_AXIS] - z_bckp) < 0.025) {
 			//printf_P(PSTR("PINDA triggered immediately, move Z higher and repeat measurement\n")); 
-			current_position[Z_AXIS] += 0.5;
-			go_to_current(homing_feedrate[Z_AXIS]/60);
+			raise_z(0.5);
 			current_position[Z_AXIS] = minimum_z;
             go_to_current(homing_feedrate[Z_AXIS]/(4*60));
             // we have to let the planner know where we are right now as it is not where we said to go.
@@ -1011,7 +1010,7 @@ bool find_bed_induction_sensor_point_z(float minimum_z, uint8_t n_iter, int
 			goto error;
 		}
 #ifdef TMC2130
-		if (READ(Z_TMC2130_DIAG) != 0) {
+		if (!READ(Z_TMC2130_DIAG)) {
 			//printf_P(PSTR("crash detected 2, current_pos[Z]: %f \n"), current_position[Z_AXIS]);
 			goto error; //crash Z detected
 		}
@@ -1579,7 +1578,7 @@ inline bool improve_bed_induction_sensor_point()
         // Trim the vector from center_old_[x,y] to destination[x,y] by the bed dimensions.
         float vx = destination[X_AXIS] - center_old_x;
         float vy = destination[Y_AXIS] - center_old_y;
-        float l  = sqrt(vx*vx+vy*vy);
+        float l  = hypot(vx, vy);
         float t;
         if (destination[X_AXIS] < X_MIN_POS) {
             // Exiting the bed at xmin.
@@ -2441,16 +2440,16 @@ BedSkewOffsetDetectionResultType find_bed_offset_and_skew(int8_t verbosity_level
 			#ifdef SUPPORT_VERBOSITY
 			if (verbosity_level >= 10) {
 				// Length of the vec_x
-				float l = sqrt(vec_x[0] * vec_x[0] + vec_x[1] * vec_x[1]);
+				float l = hypot(vec_x[0], vec_x[1]);
 				SERIAL_ECHOLNPGM("X vector length:");
 				MYSERIAL.println(l);
 
 				// Length of the vec_y
-				l = sqrt(vec_y[0] * vec_y[0] + vec_y[1] * vec_y[1]);
+				l = hypot(vec_y[0], vec_y[1]);
 				SERIAL_ECHOLNPGM("Y vector length:");
 				MYSERIAL.println(l);
 				// Zero point correction
-				l = sqrt(cntr[0] * cntr[0] + cntr[1] * cntr[1]);
+				l = hypot(cntr[0], cntr[1]);
 				SERIAL_ECHOLNPGM("Zero point correction:");
 				MYSERIAL.println(l);
 
@@ -2528,7 +2527,7 @@ BedSkewOffsetDetectionResultType improve_bed_offset_and_skew(int8_t method, int8
     bool endstop_z_enabled = enable_z_endstop(false);
 
 #ifdef MESH_BED_CALIBRATION_SHOW_LCD
-    lcd_display_message_fullscreen_P(_i("Improving bed calibration point"));////MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1 c=60
+    lcd_display_message_fullscreen_P(_i("Improving bed calibration point"));////MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1 c=20 r=4
 #endif /* MESH_BED_CALIBRATION_SHOW_LCD */
 
     // Collect a matrix of 9x9 points.
@@ -2792,10 +2791,7 @@ canceled:
 bool sample_z() {
 	bool sampled = true;
 	//make space
-	current_position[Z_AXIS] += 150;
-	go_to_current(homing_feedrate[Z_AXIS] / 60);
-	//plan_buffer_line_curposXYZE(feedrate, active_extruder););
-
+	raise_z(150);
 	lcd_show_fullscreen_message_and_wait_P(_T(MSG_PLACE_STEEL_SHEET));
 
 	// Sample Z heights for the mesh bed leveling.
@@ -2857,7 +2853,7 @@ bool sample_mesh_and_store_reference()
         homeaxis(Z_AXIS);
 
 #ifdef TMC2130
-		if (!axis_known_position[Z_AXIS] && (READ(Z_TMC2130_DIAG) != 0)) //Z crash
+		if (!axis_known_position[Z_AXIS] && (!READ(Z_TMC2130_DIAG))) //Z crash
 		{
 			kill(_T(MSG_BED_LEVELING_FAILED_POINT_LOW));
 			return false;

+ 25 - 12
Firmware/messages.cpp

@@ -5,6 +5,7 @@
 #include "Configuration_prusa.h"
 
 //internationalized messages
+const char MSG_ALWAYS[] PROGMEM_I1 = ISTR("Always"); ////MSG_ALWAYS c=6
 const char MSG_AUTO_HOME[] PROGMEM_I1 = ISTR("Auto home"); ////MSG_AUTO_HOME c=18
 const char MSG_BABYSTEP_Z[] PROGMEM_I1 = ISTR("Live adjust Z"); ////MSG_BABYSTEP_Z c=18
 const char MSG_BABYSTEP_Z_NOT_SET[] PROGMEM_I1 = ISTR("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."); ////MSG_BABYSTEP_Z_NOT_SET c=20 r=12
@@ -14,7 +15,7 @@ const char MSG_BED_HEATING[] PROGMEM_I1 = ISTR("Bed Heating"); ////MSG_BED_HEATI
 const char MSG_BED_LEVELING_FAILED_POINT_LOW[] PROGMEM_I1 = ISTR("Bed leveling failed. Sensor didn't trigger. Debris on nozzle? Waiting for reset."); ////MSG_BED_LEVELING_FAILED_POINT_LOW c=20 r=6
 const char MSG_BED_SKEW_OFFSET_DETECTION_FITTING_FAILED[] PROGMEM_I1 = ISTR("XYZ calibration failed. Please consult the manual."); ////MSG_BED_SKEW_OFFSET_DETECTION_FITTING_FAILED c=20 r=8
 const char MSG_BELT_STATUS[] PROGMEM_I1 = ISTR("Belt status");////MSG_BELT_STATUS c=18
-const char MSG_CANCEL[] PROGMEM_I1 = ISTR(">Cancel");////MSG_CANCEL c=9
+const char MSG_CANCEL[] PROGMEM_I1 = ISTR(">Cancel");////MSG_CANCEL c=10
 const char MSG_CALIBRATE_Z_AUTO[] PROGMEM_I1 = ISTR("Calibrating Z"); ////MSG_CALIBRATE_Z_AUTO c=20 r=2
 const char MSG_CARD_MENU[] PROGMEM_I1 = ISTR("Print from SD"); ////MSG_CARD_MENU c=18
 const char MSG_CHECKING_X[] PROGMEM_I1 = ISTR("Checking X axis"); ////MSG_CHECKING_X c=20
@@ -40,7 +41,9 @@ const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1[] PROGMEM_I1 = ISTR("Searching bed
 const char MSG_FINISHING_MOVEMENTS[] PROGMEM_I1 = ISTR("Finishing movements"); ////MSG_FINISHING_MOVEMENTS c=20
 const char MSG_FOLLOW_CALIBRATION_FLOW[] PROGMEM_I1 = ISTR("Printer has not been calibrated yet. Please follow the manual, chapter First steps, section Calibration flow."); ////MSG_FOLLOW_CALIBRATION_FLOW c=20 r=8
 const char MSG_FOLLOW_Z_CALIBRATION_FLOW[] PROGMEM_I1 = ISTR("There is still a need to make Z calibration. Please follow the manual, chapter First steps, section Calibration flow."); ////MSG_FOLLOW_Z_CALIBRATION_FLOW c=20 r=9
+const char MSG_FSENSOR_RUNOUT[] PROGMEM_I1 = ISTR("F. runout"); ////MSG_FSENSOR_RUNOUT c=13
 const char MSG_FSENSOR_AUTOLOAD[] PROGMEM_I1 = ISTR("F. autoload"); ////MSG_FSENSOR_AUTOLOAD c=13
+const char MSG_FSENSOR_JAM_DETECTION[] PROGMEM_I1 = ISTR("F. jam detect"); ////MSG_FSENSOR_JAM_DETECTION c=13
 const char MSG_FSENSOR[] PROGMEM_I1 = ISTR("Fil. sensor"); ////MSG_FSENSOR c=12
 const char MSG_HEATING[] PROGMEM_I1 = ISTR("Heating"); ////MSG_HEATING c=20
 const char MSG_HEATING_COMPLETE[] PROGMEM_I1 = ISTR("Heating done."); ////MSG_HEATING_COMPLETE c=20
@@ -51,10 +54,11 @@ const char MSG_SELECT_FILAMENT[] PROGMEM_I1 = ISTR("Select filament:"); ////MSG_
 const char MSG_LAST_PRINT[] PROGMEM_I1 = ISTR("Last print"); ////MSG_LAST_PRINT c=18
 const char MSG_LAST_PRINT_FAILURES[] PROGMEM_I1 = ISTR("Last print failures"); ////MSG_LAST_PRINT_FAILURES c=20
 const char MSG_LOAD_FILAMENT[] PROGMEM_I1 = ISTR("Load filament"); ////MSG_LOAD_FILAMENT c=17
+const char MSG_LOAD_TO_EXTRUDER[] PROGMEM_I1 = ISTR("Load to extruder"); ////MSG_LOAD_TO_EXTRUDER c=18
 const char MSG_LOADING_FILAMENT[] PROGMEM_I1 = ISTR("Loading filament"); ////MSG_LOADING_FILAMENT c=20
+const char MSG_TESTING_FILAMENT[] PROGMEM_I1 = ISTR("Testing filament"); ////MSG_TESTING_FILAMENT c=20
 const char MSG_EJECT_FILAMENT[] PROGMEM_I1 = ISTR("Eject filament"); ////MSG_EJECT_FILAMENT c=17
 const char MSG_CUT_FILAMENT[] PROGMEM_I1 = ISTR("Cut filament"); ////MSG_CUT_FILAMENT c=17
-const char MSG_M117_V2_CALIBRATION[] PROGMEM_I1 = ISTR("M117 First layer cal."); ////MSG_M117_V2_CALIBRATION c=25
 const char MSG_MAIN[] PROGMEM_I1 = ISTR("Main"); ////MSG_MAIN c=18
 const char MSG_BACK[] PROGMEM_I1 = ISTR("Back"); ////MSG_BACK c=18
 const char MSG_SHEET[] PROGMEM_I1 = ISTR("Sheet"); ////MSG_SHEET c=10
@@ -92,6 +96,8 @@ const char MSG_SELFTEST_MOTOR[] PROGMEM_I1 = ISTR("Motor"); ////MSG_SELFTEST_MOT
 const char MSG_SELFTEST_FILAMENT_SENSOR[] PROGMEM_I1 = ISTR("Filament sensor"); ////MSG_SELFTEST_FILAMENT_SENSOR c=17
 const char MSG_SELFTEST_WIRINGERROR[] PROGMEM_I1 = ISTR("Wiring error"); ////MSG_SELFTEST_WIRINGERROR c=18
 const char MSG_SETTINGS[] PROGMEM_I1 = ISTR("Settings"); ////MSG_SETTINGS c=18
+const char MSG_SELECT_LANGUAGE[] PROGMEM_I1 = ISTR("Select language"); ////MSG_SELECT_LANGUAGE c=18
+const char MSG_SORTING_FILES[] PROGMEM_I1 = ISTR("Sorting files"); ////MSG_SORTING_FILES c=20
 const char MSG_TOTAL[] PROGMEM_I1 = ISTR("Total"); ////MSG_TOTAL c=6
 const char MSG_TOTAL_FAILURES[] PROGMEM_I1 = ISTR("Total failures"); ////MSG_TOTAL_FAILURES c=20
 const char MSG_HW_SETUP[] PROGMEM_I1 = ISTR("HW Setup"); ////MSG_HW_SETUP c=18
@@ -106,7 +112,7 @@ const char MSG_STOP_PRINT[] PROGMEM_I1 = ISTR("Stop print"); ////MSG_STOP_PRINT
 const char MSG_STOPPED[] PROGMEM_I1 = ISTR("STOPPED."); ////MSG_STOPPED c=20
 const char MSG_PINDA_CALIBRATION[] PROGMEM_I1 = ISTR("PINDA cal."); ////MSG_PINDA_CALIBRATION c=13
 const char MSG_PINDA_CALIBRATION_DONE[] PROGMEM_I1 = ISTR("PINDA calibration is finished and active. It can be disabled in menu Settings->PINDA cal."); ////MSG_PINDA_CALIBRATION_DONE c=20 r=8
-const char MSG_UNLOAD_FILAMENT[] PROGMEM_I1 = ISTR("Unload filament"); ////MSG_UNLOAD_FILAMENT c=18
+const char MSG_UNLOAD_FILAMENT[] PROGMEM_I1 = ISTR("Unload filament"); ////MSG_UNLOAD_FILAMENT c=16
 const char MSG_UNLOADING_FILAMENT[] PROGMEM_I1 = ISTR("Unloading filament"); ////MSG_UNLOADING_FILAMENT c=20
 const char MSG_INFO_SCREEN[] PROGMEM_I1 = ISTR("Info screen"); ////MSG_INFO_SCREEN c=18
 const char MSG_WIZARD_CALIBRATION_FAILED[] PROGMEM_I1 = ISTR("Please check our handbook and fix the problem. Then resume the Wizard by rebooting the printer."); ////MSG_WIZARD_CALIBRATION_FAILED c=20 r=8
@@ -114,7 +120,7 @@ const char MSG_WIZARD_DONE[] PROGMEM_I1 = ISTR("All is done. Happy printing!");
 const char MSG_WIZARD_HEATING[] PROGMEM_I1 = ISTR("Preheating nozzle. Please wait."); ////MSG_WIZARD_HEATING c=20 r=3
 const char MSG_WIZARD_QUIT[] PROGMEM_I1 = ISTR("You can always resume the Wizard from Calibration -> Wizard."); ////MSG_WIZARD_QUIT c=20 r=8
 const char MSG_WIZARD_WELCOME[] PROGMEM_I1 = ISTR("Hi, I am your Original Prusa i3 printer. Would you like me to guide you through the setup process?"); ////MSG_WIZARD_WELCOME c=20 r=7
-const char MSG_WIZARD_WELCOME_SHIPPING[] PROGMEM_I1 = ISTR("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."); ////MSG_WIZARD_WELCOME_SHIPPING c=20 r=16
+const char MSG_WIZARD_WELCOME_SHIPPING[] PROGMEM_I1 = ISTR("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."); ////MSG_WIZARD_WELCOME_SHIPPING c=20 r=12
 const char MSG_YES[] PROGMEM_I1 = ISTR("Yes"); ////MSG_YES c=4
 const char MSG_V2_CALIBRATION[] PROGMEM_I1 = ISTR("First layer cal."); ////MSG_V2_CALIBRATION c=18
 const char MSG_OFF[] PROGMEM_I1 = ISTR("Off"); ////MSG_OFF c=3
@@ -153,18 +159,23 @@ const char MSG_TIMEOUT[] PROGMEM_I1 = ISTR("Timeout"); ////MSG_TIMEOUT c=12
 const char MSG_BRIGHT[] PROGMEM_I1 = ISTR("Bright"); ////MSG_BRIGHT c=6
 const char MSG_DIM[] PROGMEM_I1 = ISTR("Dim"); ////MSG_DIM c=6
 const char MSG_AUTO[] PROGMEM_I1 = ISTR("Auto"); ////MSG_AUTO c=6
-#ifdef IR_SENSOR_ANALOG
+#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
 const char MSG_IR_04_OR_NEWER[] PROGMEM_I1 = ISTR(" 0.4 or newer");////MSG_IR_04_OR_NEWER c=18
 const char MSG_IR_03_OR_OLDER[] PROGMEM_I1 = ISTR(" 0.3 or older");////MSG_IR_03_OR_OLDER c=18
 const char MSG_IR_UNKNOWN[] PROGMEM_I1 = ISTR("unknown state");////MSG_IR_UNKNOWN c=18
 #endif
+extern const char MSG_PAUSED_THERMAL_ERROR[] PROGMEM_I1 = ISTR("PAUSED THERMAL ERROR");////MSG_PAUSED_THERMAL_ERROR c=20
+#ifdef TEMP_MODEL
+extern const char MSG_THERMAL_ANOMALY[] PROGMEM_I1 = ISTR("THERMAL ANOMALY");////MSG_THERMAL_ANOMALY c=20
+#endif
+extern const char MSG_LOAD_ALL[] PROGMEM_I1 = ISTR("Load All"); ////MSG_LOAD_ALL c=18
 
 //not internationalized messages
-const char MSG_AUTO_DEPLETE[] PROGMEM_N1 = ISTR("SpoolJoin"); ////MSG_AUTO_DEPLETE c=13
-const char MSG_FIRMWARE[] PROGMEM_N1 = ISTR("Firmware"); ////MSG_FIRMWARE c=8
-const char MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY[] PROGMEM_N1 = ISTR("FlashAir"); ////MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY c=8
-const char MSG_PINDA[] PROGMEM_N1 = ISTR("PINDA");////MSG_PINDA c=5
+const char MSG_SPOOL_JOIN[] PROGMEM_N1 = "SpoolJoin"; ////MSG_SPOOL_JOIN c=13
+const char MSG_FIRMWARE[] PROGMEM_N1 = "Firmware"; ////MSG_FIRMWARE c=8
+const char MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY[] PROGMEM_N1 = "FlashAir"; ////MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY c=8
+const char MSG_PINDA[] PROGMEM_N1 = "PINDA"; ////MSG_PINDA c=5
 const char MSG_WELCOME[] PROGMEM_N1 = WELCOME_MSG;
 const char MSG_SD_WORKDIR_FAIL[] PROGMEM_N1 = "workDir open failed"; ////
 const char MSG_BROWNOUT_RESET[] PROGMEM_N1 = " Brown out Reset"; ////
@@ -177,7 +188,9 @@ const char MSG_WATCHDOG_RESET[] PROGMEM_N1 = " Watchdog Reset"; ////
 const char MSG_Z_MAX[] PROGMEM_N1 = "z_max: "; ////
 const char MSG_Z_MIN[] PROGMEM_N1 = "z_min: "; ////
 const char MSG_ZPROBE_OUT[] PROGMEM_N1 = "Z probe out. bed"; ////
+#ifdef ENABLE_AUTO_BED_LEVELING
 const char MSG_ZPROBE_ZOFFSET[] PROGMEM_N1 = "Z Offset"; ////
+#endif
 const char MSG_TMC_OVERTEMP[] PROGMEM_N1 = "TMC DRIVER OVERTEMP"; ////
 const char MSG_Enqueing[] PROGMEM_N1 = "enqueing \""; ////
 const char MSG_ENDSTOPS_HIT[] PROGMEM_N1 = "endstops hit: "; ////
@@ -186,11 +199,11 @@ const char MSG_OK[] PROGMEM_N1 = "ok"; ////
 const char MSG_SD_OPEN_FILE_FAIL[] PROGMEM_N1 = "open failed, File: "; ////
 const char MSG_ENDSTOP_OPEN[] PROGMEM_N1 = "open"; ////
 const char MSG_POWERUP[] PROGMEM_N1 = "PowerUp"; ////
-const char MSG_ERR_STOPPED[] PROGMEM_N1 = "Printer stopped due to errors. Fix the error and use M999 to restart. (Temperature is reset. Set it after restarting)"; ////
+const char MSG_ERR_STOPPED[] PROGMEM_N1 = "Printer stopped due to errors. Supervision required."; ////
 const char MSG_ENDSTOP_HIT[] PROGMEM_N1 = "TRIGGERED"; ////
-const char MSG_OCTOPRINT_PAUSE[] PROGMEM_N1 = "// action:pause"; ////
+const char MSG_OCTOPRINT_ASK_PAUSE[] PROGMEM_N1 = "// action:pause"; ////
 const char MSG_OCTOPRINT_PAUSED[] PROGMEM_N1 = "// action:paused"; ////
-const char MSG_OCTOPRINT_RESUME[] PROGMEM_N1 = "// action:resume"; ////
+const char MSG_OCTOPRINT_ASK_RESUME[] PROGMEM_N1 = "// action:resume"; ////
 const char MSG_OCTOPRINT_RESUMED[] PROGMEM_N1 = "// action:resumed"; ////
 const char MSG_OCTOPRINT_CANCEL[] PROGMEM_N1 = "// action:cancel"; ////
 const char MSG_FANCHECK_EXTRUDER[] PROGMEM_N1 = "Err: EXTR. FAN ERROR"; ////c=20

+ 18 - 4
Firmware/messages.h

@@ -11,6 +11,7 @@ extern "C" {
 
 // LCD Menu Messages
 //internationalized messages
+extern const char MSG_ALWAYS[];
 extern const char MSG_AUTO_HOME[];
 extern const char MSG_BABYSTEP_Z[];
 extern const char MSG_BABYSTEP_Z_NOT_SET[];
@@ -46,7 +47,9 @@ extern const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1[];
 extern const char MSG_FINISHING_MOVEMENTS[];
 extern const char MSG_FOLLOW_CALIBRATION_FLOW[];
 extern const char MSG_FOLLOW_Z_CALIBRATION_FLOW[];
+extern const char MSG_FSENSOR_RUNOUT[];
 extern const char MSG_FSENSOR_AUTOLOAD[];
+extern const char MSG_FSENSOR_JAM_DETECTION[];
 extern const char MSG_FSENSOR[];
 extern const char MSG_HEATING[];
 extern const char MSG_HEATING_COMPLETE[];
@@ -57,7 +60,9 @@ extern const char MSG_SELECT_FILAMENT[];
 extern const char MSG_LAST_PRINT[];
 extern const char MSG_LAST_PRINT_FAILURES[];
 extern const char MSG_LOAD_FILAMENT[];
+extern const char MSG_LOAD_TO_EXTRUDER[];
 extern const char MSG_LOADING_FILAMENT[];
+extern const char MSG_TESTING_FILAMENT[];
 extern const char MSG_M117_V2_CALIBRATION[];
 extern const char MSG_MAIN[];
 extern const char MSG_BACK[];
@@ -98,6 +103,8 @@ extern const char MSG_SELFTEST_MOTOR[];
 extern const char MSG_SELFTEST_FILAMENT_SENSOR[];
 extern const char MSG_SELFTEST_WIRINGERROR[];
 extern const char MSG_SETTINGS[];
+extern const char MSG_SELECT_LANGUAGE[];
+extern const char MSG_SORTING_FILES[];
 extern const char MSG_TOTAL[];
 extern const char MSG_TOTAL_FAILURES[];
 extern const char MSG_HW_SETUP[];
@@ -127,7 +134,7 @@ extern const char MSG_WELCOME[];
 extern const char MSG_OFF[];
 extern const char MSG_ON[];
 extern const char MSG_NA[];
-extern const char MSG_AUTO_DEPLETE[];
+extern const char MSG_SPOOL_JOIN[];
 extern const char MSG_CUTTER[];
 extern const char MSG_NONE[];
 extern const char MSG_WARN[];
@@ -163,11 +170,16 @@ extern const char MSG_TIMEOUT[];
 extern const char MSG_BRIGHT[];
 extern const char MSG_DIM[];
 extern const char MSG_AUTO[];
-#ifdef IR_SENSOR_ANALOG
+#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
 extern const char MSG_IR_04_OR_NEWER[];
 extern const char MSG_IR_03_OR_OLDER[];
 extern const char MSG_IR_UNKNOWN[];
 #endif
+extern const char MSG_PAUSED_THERMAL_ERROR[];
+#ifdef TEMP_MODEL
+extern const char MSG_THERMAL_ANOMALY[];
+#endif
+extern const char MSG_LOAD_ALL[];
 
 //not internationalized messages
 extern const char MSG_BROWNOUT_RESET[];
@@ -180,7 +192,9 @@ extern const char MSG_WATCHDOG_RESET[];
 extern const char MSG_Z_MAX[];
 extern const char MSG_Z_MIN[];
 extern const char MSG_ZPROBE_OUT[];
+#ifdef ENABLE_AUTO_BED_LEVELING
 extern const char MSG_ZPROBE_ZOFFSET[];
+#endif
 extern const char MSG_TMC_OVERTEMP[];
 extern const char MSG_Enqueing[];
 extern const char MSG_ENDSTOPS_HIT[];
@@ -193,9 +207,9 @@ extern const char MSG_ERR_STOPPED[];
 extern const char MSG_ENDSTOP_HIT[];
 extern const char MSG_EJECT_FILAMENT[];
 extern const char MSG_CUT_FILAMENT[];
-extern const char MSG_OCTOPRINT_PAUSE[];
+extern const char MSG_OCTOPRINT_ASK_PAUSE[];
 extern const char MSG_OCTOPRINT_PAUSED[];
-extern const char MSG_OCTOPRINT_RESUME[];
+extern const char MSG_OCTOPRINT_ASK_RESUME[];
 extern const char MSG_OCTOPRINT_RESUMED[];
 extern const char MSG_OCTOPRINT_CANCEL[];
 extern const char MSG_FANCHECK_EXTRUDER[];

+ 0 - 1266
Firmware/mmu.cpp

@@ -1,1266 +0,0 @@
-//! @file
-
-#include "mmu.h"
-#include "planner.h"
-#include "language.h"
-#include "lcd.h"
-#include "uart2.h"
-#include "temperature.h"
-#include "Configuration_prusa.h"
-#include "fsensor.h"
-#include "cardreader.h"
-#include "cmdqueue.h"
-#include "ultralcd.h"
-#include "menu.h"
-#include "sound.h"
-#include "printers.h"
-#include <avr/pgmspace.h>
-#include "AutoDeplete.h"
-#include "fastio.h"
-#include "pins.h"
-//-//
-#include "util.h"
-
-#ifdef TMC2130
-#include "tmc2130.h"
-#endif //TMC2130
-
-#define MMU_TODELAY 100
-#define MMU_TIMEOUT 10
-#define MMU_CMD_TIMEOUT 45000ul //45s timeout for mmu commands (except P0)
-#define MMU_P0_TIMEOUT 3000ul //timeout for P0 command: 3seconds
-#define MMU_MAX_RESEND_ATTEMPTS 2
-
-
-namespace
-{
-    enum class S : uint_least8_t
-    {
-        WaitStealthMode,
-        GetFindaInit,
-        GetBuildNr,
-        GetVersion,
-        Init,
-        Disabled,
-        Idle,
-        GetFinda,
-        WaitCmd, //!< wait for command response
-        Pause,
-        GetDrvError, //!< get power failures count
-		SwitchMode //switch mmu between stealth and normal mode 
-    };
-}
-
-bool mmu_enabled = false;
-bool mmu_ready = false;
-bool mmu_fil_loaded = false; //if true: blocks execution of duplicit T-codes
-
-static S mmu_state = S::Disabled;
-
-MmuCmd mmu_cmd = MmuCmd::None;
-
-//idler ir sensor
-static uint8_t mmu_idl_sens = 0;
-bool ir_sensor_detected = false; 
-static bool mmu_loading_flag = false; //when set to true, we assume that mmu2 unload was finished and loading phase is now performed; printer can send 'A' to mmu2 to abort loading process
-
-uint8_t mmu_extruder = MMU_FILAMENT_UNKNOWN;
-
-//! This variable probably has no meaning and is planed to be removed
-uint8_t tmp_extruder = MMU_FILAMENT_UNKNOWN;
-
-int8_t mmu_finda = -1;
-
-int16_t mmu_version = -1;
-
-int16_t mmu_buildnr = -1;
-
-LongTimer mmu_last_request;
-LongTimer mmu_last_response;
-LongTimer mmu_last_finda_response;
-
-MmuCmd mmu_last_cmd = MmuCmd::None;
-uint16_t mmu_power_failures = 0;
-
-
-#ifdef MMU_DEBUG
-static const auto DEBUG_PUTCHAR = putchar;
-static const auto DEBUG_PUTS_P = puts_P;
-static const auto DEBUG_PRINTF_P = printf_P;
-#else //MMU_DEBUG
-#define DEBUG_PUTCHAR(c)
-#define DEBUG_PUTS_P(str)
-#define DEBUG_PRINTF_P( __fmt, ... )
-#endif //MMU_DEBUG
-
-#if defined(MMU_FINDA_DEBUG) && defined(MMU_DEBUG)
-static const auto FDEBUG_PUTS_P = puts_P;
-static const auto FDEBUG_PRINTF_P = printf_P;
-#else
-#define FDEBUG_PUTS_P(str)
-#define FDEBUG_PRINTF_P( __fmt, ... )
-#endif //defined(MMU_FINDA_DEBUG) && defined(MMU_DEBUG)
-
-
-//clear rx buffer
-void mmu_clr_rx_buf(void)
-{
-	while (fgetc(uart2io) >= 0);
-}
-
-//send command - puts
-int mmu_puts_P(const char* str)
-{
-	mmu_clr_rx_buf();                          //clear rx buffer
-    int r = fputs_P(str, uart2io);             //send command
-	mmu_last_request.start();
-	return r;
-}
-
-//send command - printf
-int mmu_printf_P(const char* format, ...)
-{
-	va_list args;
-	va_start(args, format);
-	mmu_clr_rx_buf();                          //clear rx buffer
-	int r = vfprintf_P(uart2io, format, args); //send command
-	va_end(args);
-	mmu_last_request.start();
-	return r;
-}
-
-//check 'ok' response
-int8_t mmu_rx_ok(void)
-{
-	int8_t res = uart2_rx_str_P(PSTR("ok\n"));
-	if (res == 1) mmu_last_response.start();
-	return res;
-}
-
-//check 'start' response
-int8_t mmu_rx_start(void)
-{
-	int8_t res = uart2_rx_str_P(PSTR("start\n"));
-	if (res == 1) mmu_last_response.start();
-	return res;
-}
-
-//initialize mmu2 unit - first part - should be done at begining of startup process
-void mmu_init(void)
-{
-#ifdef MMU_HWRESET
-	WRITE(MMU_RST_PIN, 1);
-	SET_OUTPUT(MMU_RST_PIN);                   //setup reset pin
-#endif //MMU_HWRESET
-	uart2_init();                              //init uart2
-	_delay_ms(10);                             //wait 10ms for sure
-	mmu_reset();                               //reset mmu (HW or SW), do not wait for response
-	mmu_state = S::Init;
-	SET_INPUT(IR_SENSOR_PIN); //input mode
-	WRITE(IR_SENSOR_PIN, 1); //pullup
-}
-
-//if IR_SENSOR defined, always returns true
-//otherwise check for ir sensor and returns true if idler IR sensor was detected, otherwise returns false
-bool check_for_ir_sensor() 
-{
-#ifdef IR_SENSOR
-	return true;
-#else //IR_SENSOR
-
-	bool detected = false;
-	//if IR_SENSOR_PIN input is low and pat9125sensor is not present we detected idler sensor
-	if ((READ(IR_SENSOR_PIN) == 0) 
-#ifdef PAT9125
-		&& fsensor_not_responding
-#endif //PAT9125
-	) 
-	{		
-		detected = true;
-		//printf_P(PSTR("Idler IR sensor detected\n"));
-	}
-	else
-	{
-		//printf_P(PSTR("Idler IR sensor not detected\n"));
-	}
-	return detected;
-#endif //IR_SENSOR
-}
-
-static bool activate_stealth_mode()
-{
-#ifdef MMU_FORCE_STEALTH_MODE
-	return true;
-#else
-	return (eeprom_read_byte((uint8_t*)EEPROM_MMU_STEALTH) == 1);
-#endif
-}
-
-//mmu main loop - state machine processing
-void mmu_loop(void)
-{
-	static uint8_t mmu_attempt_nr = 0;
-//	printf_P(PSTR("MMU loop, state=%d\n"), mmu_state);
-	switch (mmu_state)
-	{
-	case S::Disabled:
-		return;
-	case S::Init:
-		if (mmu_rx_start() > 0)
-		{
-		    DEBUG_PUTS_P(PSTR("MMU => 'start'"));
-		    DEBUG_PUTS_P(PSTR("MMU <= 'S1'"));
-		    mmu_puts_P(PSTR("S1\n")); //send 'read version' request
-			mmu_state = S::GetVersion;
-		}
-		else if (_millis() > 30000) //30sec after reset disable mmu
-		{
-			puts_P(PSTR("MMU not responding - DISABLED"));
-			mmu_state = S::Disabled;
-		}
-		return;
-	case S::GetVersion:
-		if (mmu_rx_ok() > 0)
-		{
-			fscanf_P(uart2io, PSTR("%u"), &mmu_version); //scan version from buffer
-			DEBUG_PRINTF_P(PSTR("MMU => '%dok'\n"), mmu_version);
-			DEBUG_PUTS_P(PSTR("MMU <= 'S2'"));
-			mmu_puts_P(PSTR("S2\n")); //send 'read buildnr' request
-			mmu_state = S::GetBuildNr;
-		}
-		return;
-	case S::GetBuildNr:
-		if (mmu_rx_ok() > 0)
-		{
-			fscanf_P(uart2io, PSTR("%u"), &mmu_buildnr); //scan buildnr from buffer
-			DEBUG_PRINTF_P(PSTR("MMU => '%dok'\n"), mmu_buildnr);
-			bool version_valid = mmu_check_version();
-			if (!version_valid) mmu_show_warning();
-			else puts_P(PSTR("MMU version valid"));
-			
-			if (!activate_stealth_mode())
-			{
-				FDEBUG_PUTS_P(PSTR("MMU <= 'P0'"));
-				mmu_puts_P(PSTR("P0\n")); //send 'read finda' request
-				mmu_state = S::GetFindaInit;
-			}
-			else
-			{
-				DEBUG_PUTS_P(PSTR("MMU <= 'M1'"));
-				mmu_puts_P(PSTR("M1\n")); //set mmu mode to stealth
-				mmu_state = S::WaitStealthMode;
-			}
-
-		}
-		return;
-	case S::WaitStealthMode:
-		if (mmu_rx_ok() > 0)
-		{
-			FDEBUG_PUTS_P(PSTR("MMU <= 'P0'"));
-		    mmu_puts_P(PSTR("P0\n")); //send 'read finda' request
-			mmu_state = S::GetFindaInit;
-		}
-		return;
-	case S::GetFindaInit:
-		if (mmu_rx_ok() > 0)
-		{
-			fscanf_P(uart2io, PSTR("%hhu"), &mmu_finda); //scan finda from buffer. MUST BE %hhu!!!
-			mmu_last_finda_response.start();
-			FDEBUG_PRINTF_P(PSTR("MMU => '%dok'\n"), mmu_finda);
-			puts_P(PSTR("MMU - ENABLED"));
-			mmu_enabled = true;
-            //-//
-            // ... PrinterType/Name
-            fSetMmuMode(true);
-			mmu_state = S::Idle;
-		}
-		return;
-	case S::Idle:
-		if (mmu_cmd != MmuCmd::None) //command request ?
-		{
-			if ((mmu_cmd >= MmuCmd::T0) && (mmu_cmd <= MmuCmd::T4))
-			{
-				const uint8_t filament = mmu_cmd - MmuCmd::T0;
-				DEBUG_PRINTF_P(PSTR("MMU <= 'T%d'\n"), filament);
-				mmu_printf_P(PSTR("T%d\n"), filament);
-				mmu_state = S::WaitCmd; // wait for response
-				mmu_fil_loaded = true;
-				mmu_idl_sens = 1;
-			}
-			else if ((mmu_cmd >= MmuCmd::L0) && (mmu_cmd <= MmuCmd::L4))
-			{
-			    const uint8_t filament = mmu_cmd - MmuCmd::L0;
-			    DEBUG_PRINTF_P(PSTR("MMU <= 'L%d'\n"), filament);
-			    mmu_printf_P(PSTR("L%d\n"), filament);
-			    mmu_state = S::WaitCmd; // wait for response
-			}
-			else if (mmu_cmd == MmuCmd::C0)
-			{
-			    DEBUG_PRINTF_P(PSTR("MMU <= 'C0'\n"));
-				mmu_puts_P(PSTR("C0\n")); //send 'continue loading'
-				mmu_state = S::WaitCmd;
-				mmu_idl_sens = 1;
-			}
-			else if (mmu_cmd == MmuCmd::U0)
-			{
-			    DEBUG_PRINTF_P(PSTR("MMU <= 'U0'\n"));
-				mmu_puts_P(PSTR("U0\n")); //send 'unload current filament'
-				mmu_fil_loaded = false;
-				mmu_state = S::WaitCmd;
-			}
-			else if ((mmu_cmd >= MmuCmd::E0) && (mmu_cmd <= MmuCmd::E4))
-			{
-			    const uint8_t filament = mmu_cmd - MmuCmd::E0;
-				DEBUG_PRINTF_P(PSTR("MMU <= 'E%d'\n"), filament);
-				mmu_printf_P(PSTR("E%d\n"), filament); //send eject filament
-				mmu_fil_loaded = false;
-				mmu_state = S::WaitCmd;
-			}
-			else if ((mmu_cmd >= MmuCmd::K0) && (mmu_cmd <= MmuCmd::K4))
-            {
-                const uint8_t filament = mmu_cmd - MmuCmd::K0;
-                DEBUG_PRINTF_P(PSTR("MMU <= 'K%d'\n"), filament);
-                mmu_printf_P(PSTR("K%d\n"), filament); //send eject filament
-                mmu_fil_loaded = false;
-                mmu_state = S::WaitCmd;
-            }
-			else if (mmu_cmd == MmuCmd::R0)
-			{
-			    DEBUG_PRINTF_P(PSTR("MMU <= 'R0'\n"));
-				mmu_puts_P(PSTR("R0\n")); //send recover after eject
-				mmu_state = S::WaitCmd;
-			}
-			else if (mmu_cmd == MmuCmd::S3)
-			{
-			    DEBUG_PRINTF_P(PSTR("MMU <= 'S3'\n"));
-				mmu_puts_P(PSTR("S3\n")); //send power failures request
-				mmu_state = S::GetDrvError;
-			}
-			else if (mmu_cmd == MmuCmd::W0)
-			{
-			    DEBUG_PRINTF_P(PSTR("MMU <= 'W0'\n"));
-			    mmu_puts_P(PSTR("W0\n"));
-			    mmu_state = S::Pause;
-			}
-			mmu_last_cmd = mmu_cmd;
-			mmu_cmd = MmuCmd::None;
-		}
-		else if ((eeprom_read_byte((uint8_t*)EEPROM_MMU_STEALTH) != SilentModeMenu_MMU) && mmu_ready) {
-				DEBUG_PRINTF_P(PSTR("MMU <= 'M%d'\n"), SilentModeMenu_MMU);
-				mmu_printf_P(PSTR("M%d\n"), SilentModeMenu_MMU);
-				mmu_state = S::SwitchMode;
-		}
-		else if (mmu_last_response.expired(300)) //request every 300ms
-		{
-#ifndef IR_SENSOR
-			if(check_for_ir_sensor()) ir_sensor_detected = true;
-#endif //IR_SENSOR not defined
-			FDEBUG_PUTS_P(PSTR("MMU <= 'P0'"));
-		    mmu_puts_P(PSTR("P0\n")); //send 'read finda' request
-			mmu_state = S::GetFinda;
-		}
-		return;
-	case S::GetFinda: //response to command P0
-        if (mmu_idl_sens)
-        {
-            if (READ(IR_SENSOR_PIN) == 0 && mmu_loading_flag)
-            {
-#ifdef MMU_DEBUG
-                printf_P(PSTR("MMU <= 'A'\n"));
-#endif //MMU_DEBUG  
-                mmu_puts_P(PSTR("A\n")); //send 'abort' request
-                mmu_idl_sens = 0;
-                //printf_P(PSTR("MMU IDLER_SENSOR = 0 - ABORT\n"));
-            }
-            //else
-                //printf_P(PSTR("MMU IDLER_SENSOR = 1 - WAIT\n"));
-        }
-		if (mmu_rx_ok() > 0)
-		{
-			fscanf_P(uart2io, PSTR("%hhu"), &mmu_finda); //scan finda from buffer. MUST BE %hhu!!!
-			mmu_last_finda_response.start();
-			FDEBUG_PRINTF_P(PSTR("MMU => '%dok'\n"), mmu_finda);
-			//printf_P(PSTR("Eact: %d\n"), int(e_active()));
-			if (!mmu_finda && CHECK_FSENSOR && fsensor_enabled) {
-				fsensor_checkpoint_print();
-				if (mmu_extruder != MMU_FILAMENT_UNKNOWN) // Can't deplete unknown extruder.
-                    ad_markDepleted(mmu_extruder);
-				if (lcd_autoDepleteEnabled() && !ad_allDepleted() && mmu_extruder != MMU_FILAMENT_UNKNOWN) // Can't auto if F=?
-				{
-				    enquecommand_front_P(PSTR("M600 AUTO")); //save print and run M600 command
-				}
-				else
-				{
-				    enquecommand_front_P(PSTR("M600")); //save print and run M600 command
-				}
-			}
-			mmu_state = S::Idle;
-			if (mmu_cmd == MmuCmd::None)
-				mmu_ready = true;
-		}
-		else if (mmu_last_request.expired(MMU_P0_TIMEOUT))
-		{ //resend request after timeout (30s)
-			mmu_state = S::Idle;
-		}
-		return;
-	case S::WaitCmd: //response to mmu commands
-        if (mmu_idl_sens)
-        {
-            if (READ(IR_SENSOR_PIN) == 0 && mmu_loading_flag)
-            {
-                DEBUG_PRINTF_P(PSTR("MMU <= 'A'\n"));
-                mmu_puts_P(PSTR("A\n")); //send 'abort' request
-                mmu_idl_sens = 0;
-                //printf_P(PSTR("MMU IDLER_SENSOR = 0 - ABORT\n"));
-            }
-            //else
-                //printf_P(PSTR("MMU IDLER_SENSOR = 1 - WAIT\n"));
-        }
-		if (mmu_rx_ok() > 0)
-		{
-		    DEBUG_PRINTF_P(PSTR("MMU => 'ok'\n"));
-			mmu_attempt_nr = 0;
-			mmu_last_cmd = MmuCmd::None;
-			mmu_ready = true;
-			mmu_state = S::Idle;
-		}
-		else if (mmu_last_request.expired(MMU_CMD_TIMEOUT))
-		{ //resend request after timeout (5 min)
-			if (mmu_last_cmd != MmuCmd::None)
-			{
-				if (mmu_attempt_nr++ < MMU_MAX_RESEND_ATTEMPTS &&
-				    mmu_last_cmd >= MmuCmd::T0 && mmu_last_cmd <= MmuCmd::T4)
-				{
-				    DEBUG_PRINTF_P(PSTR("MMU retry attempt nr. %d\n"), mmu_attempt_nr - 1);
-					mmu_cmd = mmu_last_cmd;
-				}
-				else {
-					mmu_cmd = MmuCmd::None;
-					mmu_last_cmd = MmuCmd::None; //check
-					mmu_attempt_nr = 0;
-				}
-			}
-			mmu_state = S::Idle;
-		}
-		return;
-	case S::Pause:
-        if (mmu_rx_ok() > 0)
-        {
-            DEBUG_PRINTF_P(PSTR("MMU => 'ok', resume print\n"));
-            mmu_attempt_nr = 0;
-            mmu_last_cmd = MmuCmd::None;
-            mmu_ready = true;
-            mmu_state = S::Idle;
-            lcd_resume_print();
-        }
-        if (mmu_cmd != MmuCmd::None)
-        {
-            mmu_state = S::Idle;
-        }
-	    return;
-	case S::GetDrvError:
-		if (mmu_rx_ok() > 0)
-		{
-			fscanf_P(uart2io, PSTR("%d"), &mmu_power_failures); //scan power failures
-			DEBUG_PRINTF_P(PSTR("MMU => 'ok'\n"));
-			mmu_last_cmd = MmuCmd::None;
-			mmu_ready = true;
-			mmu_state = S::Idle;
-		}
-		else if (mmu_last_request.expired(MMU_CMD_TIMEOUT))
-		{ //timeout 45 s
-			mmu_state = S::Idle;
-		}
-		return;
-	case S::SwitchMode:
-		if (mmu_rx_ok() > 0)
-		{
-			DEBUG_PRINTF_P(PSTR("MMU => 'ok'\n"));
-			eeprom_update_byte((uint8_t*)EEPROM_MMU_STEALTH, SilentModeMenu_MMU);
-			mmu_state = S::Idle;
-		}
-		else if (mmu_last_request.expired(MMU_CMD_TIMEOUT))
-		{ //timeout 45 s
-			mmu_state = S::Idle;
-		}
-		return;		
-	}
-}
-
-void mmu_reset(void)
-{
-#ifdef MMU_HWRESET                             //HW - pulse reset pin
-	WRITE(MMU_RST_PIN, 0);
-	_delay_us(100);
-	WRITE(MMU_RST_PIN, 1);
-#else                                          //SW - send X0 command
-    mmu_puts_P(PSTR("X0\n"));
-#endif
-}
-
-int8_t mmu_set_filament_type(uint8_t extruder, uint8_t filament)
-{
-	printf_P(PSTR("MMU <= 'F%d %d'\n"), extruder, filament);
-	mmu_printf_P(PSTR("F%d %d\n"), extruder, filament);
-	unsigned char timeout = MMU_TIMEOUT;       //10x100ms
-	while ((mmu_rx_ok() <= 0) && (--timeout))
-		delay_keep_alive(MMU_TODELAY);
-	return timeout?1:0;
-}
-
-//! @brief Enqueue MMUv2 command
-//!
-//! Call manage_response() after enqueuing to process command.
-//! If T command is enqueued, it disables current for extruder motor if TMC2130 driver present.
-//! If T or L command is enqueued, it marks filament loaded in AutoDeplete module.
-void mmu_command(MmuCmd cmd)
-{
-	if ((cmd >= MmuCmd::T0) && (cmd <= MmuCmd::T4))
-	{
-		//disable extruder motor
-#ifdef TMC2130
-		tmc2130_set_pwr(E_AXIS, 0);
-#endif //TMC2130
-		//printf_P(PSTR("E-axis disabled\n"));
-		ad_markLoaded(cmd - MmuCmd::T0);
-	}
-    if ((cmd >= MmuCmd::L0) && (cmd <= MmuCmd::L4))
-    {
-        ad_markLoaded(cmd - MmuCmd::L0);
-    }
-
-	mmu_cmd = cmd;
-	mmu_ready = false;
-}
-
-//! @brief Rotate extruder idler to catch filament
-//! @par synchronize
-//!  * true blocking call
-//!  * false non-blocking call
-void mmu_load_step(bool synchronize)
-{
-		current_position[E_AXIS] = current_position[E_AXIS] + MMU_LOAD_FEEDRATE * 0.1;
-		plan_buffer_line_curposXYZE(MMU_LOAD_FEEDRATE);
-		if (synchronize) st_synchronize();
-}
-
-//! @brief Is nozzle hot enough to move extruder wheels and do we have idler sensor?
-//!
-//! Do load steps only if temperature is higher then min. temp for safe extrusion and
-//! idler sensor present.
-//! Otherwise "cold extrusion prevented" would be send to serial line periodically
-//! and watchdog reset will be triggered by lack of keep_alive processing.
-//!
-//! @retval true temperature is high enough to move extruder
-//! @retval false temperature is not high enough to move extruder, turned
-//!         off E-stepper to prevent over-heating and allow filament pull-out if necessary
-bool can_extrude()
-{
-    if ((degHotend(active_extruder) < EXTRUDE_MINTEMP) || !ir_sensor_detected)
-    {
-        disable_e0();
-        delay_keep_alive(100);
-        return false;
-    }
-    return true;
-}
-
-static void get_response_print_info(uint8_t move) {
-	printf_P(PSTR("mmu_get_response - begin move: "), move);
-	switch (move) {
-		case MMU_LOAD_MOVE: puts_P(PSTR("load")); break;
-		case MMU_UNLOAD_MOVE: puts_P(PSTR("unload")); break;
-		case MMU_TCODE_MOVE: puts_P(PSTR("T-code")); break;
-		case MMU_NO_MOVE: puts_P(PSTR("no move")); break;
-		default: puts_P(PSTR("error: unknown move")); break;
-	}
-}
-
-bool mmu_get_response(uint8_t move)
-{
-
-	get_response_print_info(move);
-	KEEPALIVE_STATE(IN_PROCESS);
-	while (mmu_cmd != MmuCmd::None)
-	{
-		delay_keep_alive(100);
-	}
-
-	while (!mmu_ready)
-	{
-		if ((mmu_state != S::WaitCmd) && (mmu_last_cmd == MmuCmd::None))
-			break;
-
-		switch (move) {
-			case MMU_LOAD_MOVE:
-			    mmu_loading_flag = true;
-				if (can_extrude()) mmu_load_step();
-				//don't rely on "ok" signal from mmu unit; if filament detected by idler sensor during loading stop loading movements to prevent infinite loading
-				if (READ(IR_SENSOR_PIN) == 0) move = MMU_NO_MOVE;
-				break;
-			case MMU_UNLOAD_MOVE:
-				if (READ(IR_SENSOR_PIN) == 0) //filament is still detected by idler sensor, printer helps with unlading 
-				{
-				    if (can_extrude())
-				    {
-                        puts_P(PSTR("Unload 1"));
-                        current_position[E_AXIS] = current_position[E_AXIS] - MMU_LOAD_FEEDRATE * MMU_LOAD_TIME_MS*0.001;
-                        plan_buffer_line_curposXYZE(MMU_LOAD_FEEDRATE);
-                        st_synchronize();
-				    }
-				}
-				else //filament was unloaded from idler, no additional movements needed 
-				{ 
-					puts_P(PSTR("Unloading finished 1"));
-					disable_e0(); //turn off E-stepper to prevent overheating and alow filament pull-out if necessary
-					move = MMU_NO_MOVE;
-				}
-				break;
-			case MMU_TCODE_MOVE: //first do unload and then continue with infinite loading movements
-				if (READ(IR_SENSOR_PIN) == 0) //filament detected by idler sensor, we must unload first 
-				{
-                    if (can_extrude())
-                    {
-                        puts_P(PSTR("Unload 2"));
-                        current_position[E_AXIS] = current_position[E_AXIS] - MMU_LOAD_FEEDRATE * MMU_LOAD_TIME_MS*0.001;
-                        plan_buffer_line_curposXYZE(MMU_LOAD_FEEDRATE);
-                        st_synchronize();
-                    }
-				}
-				else //delay to allow mmu unit to pull out filament from bondtech gears and then start with infinite loading 
-				{ 
-					puts_P(PSTR("Unloading finished 2"));
-					disable_e0(); //turn off E-stepper to prevent overheating and alow filament pull-out if necessary
-					delay_keep_alive(MMU_LOAD_TIME_MS);
-					move = MMU_LOAD_MOVE;
-					get_response_print_info(move);
-				}
-				break;
-			case MMU_NO_MOVE:
-			default: 
-				delay_keep_alive(100);
-				break;
-		}
-	}
-	printf_P(PSTR("mmu_get_response() returning: %d\n"), mmu_ready);
-	bool ret = mmu_ready;
-	mmu_ready = false;
-//	printf_P(PSTR("mmu_get_response - end %d\n"), ret?1:0);
-	return ret;
-}
-
-//! @brief Wait for active extruder to reach temperature set
-//!
-//! This function is blocking and showing lcd_wait_for_heater() screen
-//! which is constantly updated with nozzle temperature.
-void mmu_wait_for_heater_blocking()
-{
-    while ((degTargetHotend(active_extruder) - degHotend(active_extruder)) > 5)
-    {
-        delay_keep_alive(1000);
-        lcd_wait_for_heater();
-    }
-}
-
-void manage_response(bool move_axes, bool turn_off_nozzle, uint8_t move)
-{
-	bool response = false;
-	mmu_print_saved = false;
-	bool lcd_update_was_enabled = false;
-	float hotend_temp_bckp = degTargetHotend(active_extruder);
-	float z_position_bckp = current_position[Z_AXIS];
-	float x_position_bckp = current_position[X_AXIS];
-	float y_position_bckp = current_position[Y_AXIS];	
-	uint8_t screen = 0; //used for showing multiscreen messages
-	mmu_loading_flag = false;
-	while(!response)
-	{
-		  response = mmu_get_response(move); //wait for "ok" from mmu
-		  if (!response) { //no "ok" was received in reserved time frame, user will fix the issue on mmu unit
-			  if (!mmu_print_saved) { //first occurence, we are saving current position, park print head in certain position and disable nozzle heater
-				  
-				  uint8_t mmu_fail = eeprom_read_byte((uint8_t*)EEPROM_MMU_FAIL);
-				  uint16_t mmu_fail_tot = eeprom_read_word((uint16_t*)EEPROM_MMU_FAIL_TOT);
-				  if(mmu_fail < 255) eeprom_update_byte((uint8_t*)EEPROM_MMU_FAIL, mmu_fail + 1);
-				  if(mmu_fail_tot < 65535) eeprom_update_word((uint16_t*)EEPROM_MMU_FAIL_TOT, mmu_fail_tot + 1);
-
-				  if (lcd_update_enabled) {
-					  lcd_update_was_enabled = true;
-					  lcd_update_enable(false);
-				  }
-				  st_synchronize();
-				  mmu_print_saved = true;
-				  puts_P(PSTR("MMU not responding"));
-				  KEEPALIVE_STATE(PAUSED_FOR_USER);
-				  hotend_temp_bckp = degTargetHotend(active_extruder);
-				  if (move_axes) {
-					  z_position_bckp = current_position[Z_AXIS];
-					  x_position_bckp = current_position[X_AXIS];
-					  y_position_bckp = current_position[Y_AXIS];
-				  
-					  //lift z
-					  current_position[Z_AXIS] += Z_PAUSE_LIFT;
-					  if (current_position[Z_AXIS] > Z_MAX_POS) current_position[Z_AXIS] = Z_MAX_POS;
-					  plan_buffer_line_curposXYZE(15);
-					  st_synchronize();
-					  					  
-					  //Move XY to side
-					  current_position[X_AXIS] = X_PAUSE_POS;
-					  current_position[Y_AXIS] = Y_PAUSE_POS;
-					  plan_buffer_line_curposXYZE(50);
-					  st_synchronize();
-				  }
-				  if (turn_off_nozzle) {
-					  //set nozzle target temperature to 0
-					  setAllTargetHotends(0);
-				  }
-				  disable_e0(); //turn off E-stepper to prevent overheating and alow filament pull-out if necessary
-			  }
-
-			  //first three lines are used for printing multiscreen message; last line contains measured and target nozzle temperature
-			  if (screen == 0) { //screen 0
-				  lcd_display_message_fullscreen_P(_i("MMU needs user attention."));////MSG_MMU_USER_ATTENTION c=20 r=3
-				  screen++;
-			  }
-			  else {  //screen 1
-				  if((degTargetHotend(active_extruder) == 0) && turn_off_nozzle) lcd_display_message_fullscreen_P(_i("Press the knob to resume nozzle temperature."));////MSG_RESUME_NOZZLE_TEMP c=20 r=4
-				  else lcd_display_message_fullscreen_P(_i("Fix the issue and then press button on MMU unit."));////MSG_MMU_FIX_ISSUE c=20 r=4
-				  screen=0;
-			  }
-
-			  //5 seconds delay
-			  for (uint8_t i = 0; i < 5; i++) {
-				  if (lcd_clicked()) {
-					  setTargetHotend(hotend_temp_bckp, active_extruder);
-					 /// mmu_cmd = mmu_last_cmd;
-					  break;
-				  }		  
-
-				  //Print the hotend temperature (9 chars total) and fill rest of the line with space
-				  lcd_set_cursor(0, 4); //line 4
-				  int chars = lcd_printf_P(_N("%c%3d/%d%c"), LCD_STR_THERMOMETER[0],(int)(degHotend(active_extruder) + 0.5), (int)(degTargetHotend(active_extruder) + 0.5), LCD_STR_DEGREE[0]);
-				  lcd_space(9 - chars);
-				  delay_keep_alive(1000);
-			  }
-		  }
-		  else if (mmu_print_saved) {
-			  puts_P(PSTR("MMU starts responding"));
-			  KEEPALIVE_STATE(IN_HANDLER);
-			  mmu_loading_flag = false;
-			  if (turn_off_nozzle) 
-			  {
-				lcd_clear();
-				setTargetHotend(hotend_temp_bckp, active_extruder);
-				if (((degTargetHotend(active_extruder) - degHotend(active_extruder)) > 5)) {
-					lcd_display_message_fullscreen_P(_i("MMU OK. Resuming temperature...")); ////MSG_MMU_OK_RESUMING_TEMPERATURE c=20 r=4
-					delay_keep_alive(3000);
-				}
-				mmu_wait_for_heater_blocking();
-			  }			  
-			  if (move_axes) {
-				  lcd_clear();
-				  lcd_display_message_fullscreen_P(_i("MMU OK. Resuming position...")); ////MSG_MMU_OK_RESUMING_POSITION c=20 r=4
-				  current_position[X_AXIS] = x_position_bckp;
-				  current_position[Y_AXIS] = y_position_bckp;
-				  plan_buffer_line_curposXYZE(50);
-				  st_synchronize();
-				  current_position[Z_AXIS] = z_position_bckp;
-				  plan_buffer_line_curposXYZE(15);
-				  st_synchronize();
-			  }
-			  else {
-				  lcd_clear();
-				  lcd_display_message_fullscreen_P(_i("MMU OK. Resuming...")); ////MSG_MMU_OK_RESUMING c=20 r=4
-				  delay_keep_alive(1000); //delay just for showing MMU OK message for a while in case that there are no xyz movements
-			  }
-		  }
-	}
-	if (lcd_update_was_enabled) lcd_update_enable(true);
-#ifdef TMC2130
-			//enable extruder motor (disabled in mmu_command, start of T-code processing)
-			tmc2130_set_pwr(E_AXIS, 1);
-			//printf_P(PSTR("E-axis enabled\n"));
-#endif //TMC2130
-}
-
-//! @brief load filament to nozzle of multimaterial printer
-//!
-//! This function is used only only after T? (user select filament) and M600 (change filament).
-//! It is not used after T0 .. T4 command (select filament), in such case, gcode is responsible for loading
-//! filament to nozzle.
-//!
-void mmu_load_to_nozzle()
-{
-	st_synchronize();
-	
-	const bool saved_e_relative_mode = axis_relative_modes & E_AXIS_MASK;
-	if (!saved_e_relative_mode) axis_relative_modes |= E_AXIS_MASK;
-	if (ir_sensor_detected)
-	{
-		current_position[E_AXIS] += 3.0f;
-	}
-	else
-	{
-		current_position[E_AXIS] += 7.2f;
-	}
-    float feedrate = 562;
-	plan_buffer_line_curposXYZE(feedrate / 60);
-    st_synchronize();
-	current_position[E_AXIS] += 14.4f;
-	feedrate = 871;
-	plan_buffer_line_curposXYZE(feedrate / 60);
-    st_synchronize();
-	current_position[E_AXIS] += 36.0f;
-	feedrate = 1393;
-	plan_buffer_line_curposXYZE(feedrate / 60);
-    st_synchronize();
-	current_position[E_AXIS] += 14.4f;
-	feedrate = 871;
-	plan_buffer_line_curposXYZE(feedrate / 60);
-    st_synchronize();
-	if (!saved_e_relative_mode) axis_relative_modes &= ~E_AXIS_MASK;
-}
-
-void mmu_M600_wait_and_beep() {
-		//Beep and wait for user to remove old filament and prepare new filament for load
-
-		KEEPALIVE_STATE(PAUSED_FOR_USER);
-
-		int counterBeep = 0;
-		lcd_display_message_fullscreen_P(_i("Remove old filament and press the knob to start loading new filament.")); ////MSG_REMOVE_OLD_FILAMENT c=20 r=5
-		bool bFirst=true;
-
-		while (!lcd_clicked()){
-			manage_heater();
-			manage_inactivity(true);
-
-			#if BEEPER > 0
-			if (counterBeep == 500) {
-				counterBeep = 0;
-			}
-			SET_OUTPUT(BEEPER);
-			if (counterBeep == 0) {
-				if((eSoundMode==e_SOUND_MODE_BLIND)|| (eSoundMode==e_SOUND_MODE_LOUD)||((eSoundMode==e_SOUND_MODE_ONCE)&&bFirst))
-				{
-					bFirst=false;
-					WRITE(BEEPER, HIGH);
-				}
-			}
-			if (counterBeep == 20) {
-				WRITE(BEEPER, LOW);
-			}
-				
-			counterBeep++;
-			#endif //BEEPER > 0
-
-			delay_keep_alive(4);
-		}
-		WRITE(BEEPER, LOW);
-}
-
-//! @brief load filament for mmu v2
-//! @par nozzle_temp nozzle temperature to load filament
-void mmu_M600_load_filament(bool automatic, float nozzle_temp)
-{ 
-    tmp_extruder = mmu_extruder;
-    if (automatic) {
-        tmp_extruder = ad_getAlternative(tmp_extruder);
-    }
-    lcd_update_enable(false);
-    lcd_clear();
-    lcd_puts_at_P(0, 1, _T(MSG_LOADING_FILAMENT));
-    lcd_print(' ');
-    lcd_print(tmp_extruder + 1);
-
-    //printf_P(PSTR("T code: %d \n"), tmp_extruder);
-    //mmu_printf_P(PSTR("T%d\n"), tmp_extruder);
-    setTargetHotend(nozzle_temp,active_extruder);
-    mmu_wait_for_heater_blocking();
-
-    mmu_command(MmuCmd::T0 + tmp_extruder);
-
-    manage_response(false, true, MMU_LOAD_MOVE);
-    mmu_continue_loading(usb_timer.running() || (lcd_commands_type == LcdCommands::Layer1Cal));
-    mmu_extruder = tmp_extruder; //filament change is finished
-
-    mmu_load_to_nozzle();
-    load_filament_final_feed();
-    st_synchronize();
-}
-
-void extr_adj(uint8_t extruder) //loading filament into the MMU unit
-{
-    MmuCmd cmd = MmuCmd::L0 + extruder;
-    if (extruder > (MmuCmd::L4 - MmuCmd::L0))
-    {
-        printf_P(PSTR("Filament out of range %d \n"),extruder);
-        return;
-    }
-    mmu_command(cmd);
-	
-	//show which filament is currently loaded
-	
-	lcd_update_enable(false);
-	lcd_clear();
-	lcd_puts_at_P(0, 1, _T(MSG_LOADING_FILAMENT));
-	//if(strlen(_T(MSG_LOADING_FILAMENT))>18) lcd.setCursor(0, 1);
-	//else lcd.print(" ");
-	lcd_print(' ');
-	lcd_print(extruder + 1);
-
-	// get response
-	manage_response(false, false);
-
-	lcd_update_enable(true);
-	
-	
-	//lcd_return_to_status();
-}
-
-struct E_step
-{
-    float extrude;   //!< extrude distance in mm
-    float feed_rate; //!< feed rate in mm/s
-};
-static const E_step ramming_sequence[] PROGMEM =
-{
-    {1.0,   1000.0/60},
-    {1.0,   1500.0/60},
-    {2.0,   2000.0/60},
-    {1.5,   3000.0/60},
-    {2.5,   4000.0/60},
-    {-15.0, 5000.0/60},
-    {-14.0, 1200.0/60},
-    {-6.0,  600.0/60},
-    {10.0,  700.0/60},
-    {-10.0, 400.0/60},
-    {-50.0, 2000.0/60},
-};
-
-//! @brief Unload sequence to optimize shape of the tip of the unloaded filament
-void mmu_filament_ramming()
-{
-    for(uint8_t i = 0; i < (sizeof(ramming_sequence)/sizeof(E_step));++i)
-    {
-        current_position[E_AXIS] += pgm_read_float(&(ramming_sequence[i].extrude));
-        plan_buffer_line_curposXYZE(pgm_read_float(&(ramming_sequence[i].feed_rate)));
-        st_synchronize();
-    }
-}
-
-
-//! @brief show which filament is currently unloaded
-void extr_unload_view()
-{
-    lcd_clear();
-    lcd_puts_at_P(0, 1, _T(MSG_UNLOADING_FILAMENT));
-    lcd_print(' ');
-    if (mmu_extruder == MMU_FILAMENT_UNKNOWN) lcd_print(' ');
-    else lcd_print(mmu_extruder + 1);
-}
-
-void extr_unload()
-{ //unload just current filament for multimaterial printers
-	if (degHotend0() > EXTRUDE_MINTEMP)
-	{
-		st_synchronize();
-
-        menu_submenu(extr_unload_view);
-
-		mmu_filament_ramming();
-
-		mmu_command(MmuCmd::U0);
-		// get response
-		manage_response(false, true, MMU_UNLOAD_MOVE);
-
-        menu_back();
-	}
-	else
-	{
-		show_preheat_nozzle_warning();
-	}
-}
-
-void load_all()
-{
-	enquecommand_P(PSTR("M701 E0"));
-	enquecommand_P(PSTR("M701 E1"));
-	enquecommand_P(PSTR("M701 E2"));
-	enquecommand_P(PSTR("M701 E3"));
-	enquecommand_P(PSTR("M701 E4"));
-}
-
-bool mmu_check_version()
-{
-	return (mmu_buildnr >= MMU_REQUIRED_FW_BUILDNR);
-}
-
-void mmu_show_warning()
-{
-	printf_P(PSTR("MMU2 firmware version invalid. Required version: build number %d or higher."), MMU_REQUIRED_FW_BUILDNR);
-	kill(_i("Please update firmware in your MMU2. Waiting for reset.")); ////MSG_UPDATE_MMU2_FW c=20 r=4
-}
-
-void lcd_mmu_load_to_nozzle(uint8_t filament_nr)
-{
-    menu_back();
-    bFilamentAction = false;                            // NOT in "mmu_load_to_nozzle_menu()"
-    if (degHotend0() > EXTRUDE_MINTEMP)
-    {
-        tmp_extruder = filament_nr;
-        lcd_update_enable(false);
-        lcd_clear();
-        lcd_puts_at_P(0, 1, _T(MSG_LOADING_FILAMENT));
-        lcd_print(' ');
-        lcd_print(tmp_extruder + 1);
-        mmu_command(MmuCmd::T0 + tmp_extruder);
-        manage_response(true, true, MMU_TCODE_MOVE);
-        mmu_continue_loading(false);
-        mmu_extruder = tmp_extruder; //filament change is finished
-        raise_z_above(MIN_Z_FOR_LOAD, false);
-        mmu_load_to_nozzle();
-        load_filament_final_feed();
-        st_synchronize();
-        custom_message_type = CustomMsg::FilamentLoading;
-        lcd_setstatuspgm(_T(MSG_LOADING_FILAMENT));
-        lcd_return_to_status();
-        lcd_update_enable(true);
-        lcd_load_filament_color_check();
-        lcd_setstatuspgm(MSG_WELCOME);
-        custom_message_type = CustomMsg::Status;
-    }
-    else
-    {
-        show_preheat_nozzle_warning();
-    }
-}
-
-#ifdef MMU_HAS_CUTTER
-void mmu_cut_filament(uint8_t filament_nr)
-{
-    menu_back();
-    bFilamentAction=false;                            // NOT in "mmu_load_to_nozzle_menu()"
-    if (degHotend0() > EXTRUDE_MINTEMP)
-    {
-        LcdUpdateDisabler disableLcdUpdate;
-        lcd_clear();
-        lcd_puts_at_P(0, 1, _i("Cutting filament")); ////MSG_MMU_CUTTING_FIL c=18
-        lcd_print(' ');
-        lcd_print(filament_nr + 1);
-        mmu_filament_ramming();
-        mmu_command(MmuCmd::K0 + filament_nr);
-        manage_response(false, false, MMU_UNLOAD_MOVE);
-    }
-    else
-    {
-        show_preheat_nozzle_warning();
-    }
-}
-#endif //MMU_HAS_CUTTER
-
-void mmu_eject_filament(uint8_t filament, bool recover)
-{
-//-//
-bFilamentAction=false;                            // NOT in "mmu_fil_eject_menu()"
-	if (filament < 5) 
-	{
-
-		if (degHotend0() > EXTRUDE_MINTEMP)
-		{
-			st_synchronize();
-
-			{
-			    LcdUpdateDisabler disableLcdUpdate;
-                lcd_clear();
-                lcd_puts_at_P(0, 1, _i("Ejecting filament")); ////MSG_EJECTING_FILAMENT c=20
-                mmu_filament_ramming();
-                mmu_command(MmuCmd::E0 + filament);
-                manage_response(false, false, MMU_UNLOAD_MOVE);
-                if (recover)
-                {
-                    lcd_show_fullscreen_message_and_wait_P(_i("Please remove filament and then press the knob.")); ////MSG_EJECT_REMOVE c=20 r=4
-                    mmu_command(MmuCmd::R0);
-                    manage_response(false, false);
-                }
-
-            }
-		}
-		else
-		{
-			show_preheat_nozzle_warning();
-		}
-	}
-	else
-	{
-		puts_P(PSTR("Filament nr out of range!"));
-	}
-}
-
-//! @brief Fits filament tip into heatbreak?
-//!
-//! If PTFE tube is jammed, this causes filament to be unloaded and no longer
-//! being detected by the pulley IR sensor.
-//! @retval true Fits
-//! @retval false Doesn't fit
-static bool can_load()
-{
-    current_position[E_AXIS] += 60;
-    plan_buffer_line_curposXYZE(MMU_LOAD_FEEDRATE);
-    current_position[E_AXIS] -= 52;
-    plan_buffer_line_curposXYZE(MMU_LOAD_FEEDRATE);
-    st_synchronize();
-
-    uint_least8_t filament_detected_count = 0;
-    const float e_increment = 0.2;
-    const uint_least8_t steps = 6.0 / e_increment;
-    DEBUG_PUTS_P(PSTR("MMU can_load:"));
-    for(uint_least8_t i = 0; i < steps; ++i)
-    {
-        current_position[E_AXIS] -= e_increment;
-        plan_buffer_line_curposXYZE(MMU_LOAD_FEEDRATE);
-        st_synchronize();
-        if(0 == READ(IR_SENSOR_PIN))
-        {
-            ++filament_detected_count;
-            DEBUG_PUTCHAR('O');
-        }
-        else
-        {
-            DEBUG_PUTCHAR('o');
-        }
-    }
-    if (filament_detected_count > steps - 4)
-    {
-        DEBUG_PUTS_P(PSTR(" succeeded."));
-        return true;
-    }
-    else
-    {
-        DEBUG_PUTS_P(PSTR(" failed."));
-        return false;
-    }
-}
-
-//! @brief load more
-//!
-//! Try to feed more filament from MMU if it is not detected by filament sensor.
-//! @retval true Success, filament detected by IR sensor
-//! @retval false Failed, filament not detected by IR sensor after maximum number of attempts
-static bool load_more()
-{
-    for (uint8_t i = 0; i < MMU_IDLER_SENSOR_ATTEMPTS_NR; i++)
-    {
-        if (READ(IR_SENSOR_PIN) == 0) return true;
-        DEBUG_PRINTF_P(PSTR("Additional load attempt nr. %d\n"), i);
-        mmu_command(MmuCmd::C0);
-        manage_response(true, true, MMU_LOAD_MOVE);
-    }
-    return false;
-}
-
-static void increment_load_fail()
-{
-    uint8_t mmu_load_fail = eeprom_read_byte((uint8_t*)EEPROM_MMU_LOAD_FAIL);
-    uint16_t mmu_load_fail_tot = eeprom_read_word((uint16_t*)EEPROM_MMU_LOAD_FAIL_TOT);
-    if(mmu_load_fail < 255) eeprom_update_byte((uint8_t*)EEPROM_MMU_LOAD_FAIL, mmu_load_fail + 1);
-    if(mmu_load_fail_tot < 65535) eeprom_update_word((uint16_t*)EEPROM_MMU_LOAD_FAIL_TOT, mmu_load_fail_tot + 1);
-}
-
-//! @brief continue loading filament
-//! @par blocking
-//!  * true blocking - do not return until successful load
-//!  * false non-blocking - pause print and return on load failure
-//!
-//! @startuml
-//! [*] --> [*] : !ir_sensor_detected /\n send MmuCmd::C0
-//! [*] --> LoadMore
-//! LoadMore --> [*] : filament \ndetected
-//! LoadMore --> Retry : !filament detected /\n increment load fail
-//! Retry --> [*] : filament \ndetected
-//! Retry --> Unload : !filament \ndetected
-//! Unload --> [*] : non-blocking
-//! Unload --> Retry : button \nclicked
-//!
-//! Retry : Cut filament if enabled
-//! Retry : repeat last T-code
-//! Unload : unload filament
-//! Unload : pause print
-//! Unload : show error message
-//!
-//! @enduml
-void mmu_continue_loading(bool blocking)
-{
-	if (!ir_sensor_detected)
-	{
-	    mmu_command(MmuCmd::C0);
-	    return;
-	}
-
-    bool success = load_more();
-    if (success) success = can_load();
-
-    enum class Ls : uint_least8_t
-    {
-        Enter,
-        Retry,
-        Unload,
-    };
-    Ls state = Ls::Enter;
-
-    const uint_least8_t max_retry = 3;
-    uint_least8_t retry = 0;
-
-    while (!success)
-    {
-        switch (state)
-        {
-        case Ls::Enter:
-            increment_load_fail();
-            // FALLTHRU
-        case Ls::Retry:
-            ++retry; // overflow not handled, as it is not dangerous.
-            if (retry >= max_retry)
-            {
-                state = Ls::Unload;
-#ifdef MMU_HAS_CUTTER
-                if (1 == eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED))
-                {
-                    mmu_command(MmuCmd::K0 + tmp_extruder);
-                    manage_response(true, true, MMU_UNLOAD_MOVE);
-                }
-#endif //MMU_HAS_CUTTER
-            }
-            mmu_command(MmuCmd::T0 + tmp_extruder);
-            manage_response(true, true, MMU_TCODE_MOVE);
-            success = load_more();
-            if (success) success = can_load();
-
-            break;
-        case Ls::Unload:
-            stop_and_save_print_to_ram(0, 0);
-            long_pause();
-
-            mmu_command(MmuCmd::U0);
-            manage_response(false, true, MMU_UNLOAD_MOVE);
-
-            setAllTargetHotends(0);
-            lcd_setstatuspgm(_i("MMU load failed"));////MSG_MMU_LOAD_FAILED c=20
-
-            if (blocking)
-            {
-                marlin_wait_for_click();
-                st_synchronize();
-                restore_print_from_ram_and_continue(0);
-                state = Ls::Retry;
-            }
-            else
-            {
-                mmu_fil_loaded = false; //so we can retry same T-code again
-                isPrintPaused = true;
-                mmu_command(MmuCmd::W0);
-                return;
-            }
-            break;
-        }
-    }
-}

+ 0 - 120
Firmware/mmu.h

@@ -1,120 +0,0 @@
-//! @file
-
-#ifndef MMU_H
-#define MMU_H
-
-#include <inttypes.h>
-#include "Timer.h"
-
-
-extern bool mmu_enabled;
-extern bool mmu_fil_loaded;
-
-extern uint8_t mmu_extruder;
-
-extern uint8_t tmp_extruder;
-
-extern int8_t mmu_finda;
-extern LongTimer mmu_last_finda_response;
-extern bool ir_sensor_detected;
-
-extern int16_t mmu_version;
-extern int16_t mmu_buildnr;
-
-extern uint16_t mmu_power_failures;
-
-#define MMU_FILAMENT_UNKNOWN 255
-
-#define MMU_NO_MOVE 0
-#define MMU_UNLOAD_MOVE 1
-#define MMU_LOAD_MOVE 2
-#define MMU_TCODE_MOVE 3
-
-#define MMU_LOAD_FEEDRATE 19.02f //mm/s
-#define MMU_LOAD_TIME_MS 2000 //should be fine tuned to load time for shortest allowed PTFE tubing and maximum loading speed
-
-enum class MmuCmd : uint_least8_t
-{
-    None,
-    T0,
-    T1,
-    T2,
-    T3,
-    T4,
-    L0,
-    L1,
-    L2,
-    L3,
-    L4,
-    C0,
-    U0,
-    E0,
-    E1,
-    E2,
-    E3,
-    E4,
-    K0,
-    K1,
-    K2,
-    K3,
-    K4,
-    R0,
-    S3,
-    W0, //!< Wait and signal load error
-};
-
-inline MmuCmd operator+ (MmuCmd cmd, uint8_t filament)
-{
-    return static_cast<MmuCmd>(static_cast<uint8_t>(cmd) + filament );
-}
-
-inline uint8_t operator- (MmuCmd cmda, MmuCmd cmdb)
-{
-    return (static_cast<uint8_t>(cmda) - static_cast<uint8_t>(cmdb));
-}
-
-extern int mmu_puts_P(const char* str);
-
-extern int mmu_printf_P(const char* format, ...);
-
-extern int8_t mmu_rx_ok(void);
-
-extern bool check_for_ir_sensor();
-
-extern void mmu_init(void);
-
-extern void mmu_loop(void);
-
-
-extern void mmu_reset(void);
-
-extern int8_t mmu_set_filament_type(uint8_t extruder, uint8_t filament);
-
-extern void mmu_command(MmuCmd cmd);
-
-extern bool mmu_get_response(uint8_t move = 0);
-
-extern void manage_response(bool move_axes, bool turn_off_nozzle, uint8_t move = MMU_NO_MOVE);
-
-extern void mmu_load_to_nozzle();
-
-extern void mmu_M600_load_filament(bool automatic, float nozzle_temp);
-extern void mmu_M600_wait_and_beep();
-
-extern void extr_adj(uint8_t extruder);
-extern void extr_unload();
-extern void load_all();
-
-extern bool mmu_check_version();
-extern void mmu_show_warning();
-extern void lcd_mmu_load_to_nozzle(uint8_t filament_nr);
-extern void mmu_eject_filament(uint8_t filament, bool recover);
-#ifdef MMU_HAS_CUTTER
-extern void mmu_cut_filament(uint8_t filament_nr);
-#endif //MMU_HAS_CUTTER
-extern void mmu_continue_loading(bool blocking);
-extern void mmu_filament_ramming();
-extern void mmu_wait_for_heater_blocking();
-extern void mmu_load_step(bool synchronize = true);
-
-#endif //MMU_H

+ 926 - 0
Firmware/mmu2.cpp

@@ -0,0 +1,926 @@
+#include "mmu2.h"
+#include "mmu2_error_converter.h"
+#include "mmu2_fsensor.h"
+#include "mmu2_log.h"
+#include "mmu2_power.h"
+#include "mmu2_progress_converter.h"
+#include "mmu2_reporting.h"
+
+#include "Marlin.h"
+#include "language.h"
+#include "messages.h"
+#include "sound.h"
+#include "stepper.h"
+#include "strlen_cx.h"
+#include "temperature.h"
+#include "ultralcd.h"
+#include "cardreader.h" // for IS_SD_PRINTING
+#include "SpoolJoin.h"
+
+// As of FW 3.12 we only support building the FW with only one extruder, all the multi-extruder infrastructure will be removed.
+// Saves at least 800B of code size
+static_assert(EXTRUDERS==1);
+
+// Settings for filament load / unload from the LCD menu.
+// This is for Prusa MK3-style extruders. Customize for your hardware.
+#define MMU2_FILAMENTCHANGE_EJECT_FEED 80.0
+
+#define NOZZLE_PARK_XY_FEEDRATE 50
+#define NOZZLE_PARK_Z_FEEDRATE 15
+
+// 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
+static constexpr float MMU2_LOAD_TO_NOZZLE_LENGTH = 87.0F + 5.0F;
+
+// 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
+static constexpr float MMU2_TOOL_CHANGE_LOAD_LENGTH = 30.0F;
+
+static constexpr float MMU2_LOAD_TO_NOZZLE_FEED_RATE = 20.0F; // mm/s
+static constexpr float MMU2_UNLOAD_TO_FINDA_FEED_RATE = 120.0F; // mm/s
+
+// The first the MMU does is initialise its axis. Meanwhile the E-motor will unload 20mm of filament in approx. 1 second.
+static constexpr float MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH = 20.0f; // mm
+static constexpr float MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE = 20.0f; // mm/s
+
+static constexpr uint8_t MMU2_NO_TOOL = 99;
+static constexpr uint32_t MMU_BAUD = 115200;
+
+struct E_Step {
+    float extrude;       ///< extrude distance in mm
+    float feedRate; ///< feed rate in mm/s
+};
+
+static constexpr E_Step ramming_sequence[] PROGMEM = {
+    { 0.2816F,  1339.0F / 60.F},
+    { 0.3051F,  1451.0F / 60.F},
+    { 0.3453F,  1642.0F / 60.F},
+    { 0.3990F,  1897.0F / 60.F},
+    { 0.4761F,  2264.0F / 60.F},
+    { 0.5767F,  2742.0F / 60.F},
+    { 0.5691F,  3220.0F / 60.F},
+    { 0.1081F,  3220.0F / 60.F},
+    { 0.7644F,  3635.0F / 60.F},
+    { 0.8248F,  3921.0F / 60.F},
+    { 0.8483F,  4033.0F / 60.F},
+    { -15.0F,   6000.0F / 60.F},
+    { -24.5F,   1200.0F / 60.F},
+    { -7.0F,    600.0F / 60.F},
+    { -3.5F,    360.0F / 60.F},
+    { 20.0F,    454.0F / 60.F},
+    { -20.0F,   303.0F / 60.F},
+    { -35.0F,   2000.0F / 60.F},
+};
+
+static constexpr E_Step load_to_nozzle_sequence[] PROGMEM = { 
+    { 10.0F,  810.0F / 60.F}, // feed rate = 13.5mm/s - Load fast until filament reach end of nozzle
+    { 25.0F,  198.0F / 60.F}, // feed rate = 3.3mm/s  - Load slower once filament is out of the nozzle
+};
+
+namespace MMU2 {
+
+void execute_extruder_sequence(const E_Step *sequence, int steps);
+
+template<typename F>
+void waitForHotendTargetTemp(uint16_t delay, F f){
+    while (((degTargetHotend(active_extruder) - degHotend(active_extruder)) > 5)) {
+        f();
+        delay_keep_alive(delay);
+    }
+}
+
+void WaitForHotendTargetTempBeep(){
+    waitForHotendTargetTemp(3000, []{ Sound_MakeSound(e_SOUND_TYPE_StandardPrompt); } );
+}
+
+MMU2 mmu2;
+
+MMU2::MMU2()
+    : is_mmu_error_monitor_active(false)
+    , logic(&mmu2Serial)
+    , extruder(MMU2_NO_TOOL)
+    , tool_change_extruder(MMU2_NO_TOOL)
+    , resume_position()
+    , resume_hotend_temp(0)
+    , logicStepLastStatus(StepStatus::Finished)
+    , state(xState::Stopped)
+    , mmu_print_saved(SavedState::None)
+    , loadFilamentStarted(false)
+    , unloadFilamentStarted(false)
+    , loadingToNozzle(false)
+    , inAutoRetry(false)
+    , retryAttempts(MAX_RETRIES)
+{
+}
+
+void MMU2::Start() {
+#ifdef MMU_HWRESET
+    WRITE(MMU_RST_PIN, 1);
+    SET_OUTPUT(MMU_RST_PIN); // setup reset pin
+#endif //MMU_HWRESET
+
+    mmu2Serial.begin(MMU_BAUD);
+
+    PowerOn(); // I repurposed this to serve as our EEPROM disable toggle.
+    Reset(ResetForm::ResetPin);
+
+    mmu2Serial.flush(); // make sure the UART buffer is clear before starting communication
+
+    extruder = MMU2_NO_TOOL;
+    state = xState::Connecting;
+
+    // start the communication
+    logic.Start();
+
+    ResetRetryAttempts();
+}
+
+void MMU2::Stop() {
+    StopKeepPowered();
+    PowerOff(); // This also disables the MMU in the EEPROM.
+}
+
+void MMU2::StopKeepPowered(){
+    state = xState::Stopped;
+    logic.Stop();
+    mmu2Serial.close();
+}
+
+void MMU2::Reset(ResetForm level){
+    switch (level) {
+    case Software: ResetX0(); break;
+    case ResetPin: TriggerResetPin(); break;
+    case CutThePower: PowerCycle(); break;
+    default: break;
+    }
+}
+
+void MMU2::ResetX0() {
+    logic.ResetMMU(); // Send soft reset
+}
+
+void MMU2::TriggerResetPin(){
+    reset();
+}
+
+void MMU2::PowerCycle(){
+    // cut the power to the MMU and after a while restore it
+    // Sadly, MK3/S/+ cannot do this 
+    // NOTE: the below will toggle the EEPROM var. Should we
+    // assert this function is never called in the MK3 FW? Do we even care?
+    PowerOff();
+    delay_keep_alive(1000);
+    PowerOn();
+}
+
+void MMU2::PowerOff(){
+    power_off();
+}
+
+void MMU2::PowerOn(){
+    power_on();
+}
+
+bool MMU2::ReadRegister(uint8_t address){
+    if( ! WaitForMMUReady())
+        return false;
+    logic.ReadRegister(address); // we may signal the accepted/rejected status of the response as return value of this function
+    manage_response(false, false);
+    return true;
+}
+
+bool MMU2::WriteRegister(uint8_t address, uint16_t data){
+    if( ! WaitForMMUReady())
+        return false;
+    logic.WriteRegister(address, data); // we may signal the accepted/rejected status of the response as return value of this function
+    manage_response(false, false);
+    return true;
+}
+
+void MMU2::mmu_loop() {
+    // We only leave this method if the current command was successfully completed - that's the Marlin's way of blocking operation
+    // Atomic compare_exchange would have been the most appropriate solution here, but this gets called only in Marlin's task,
+    // so thread safety should be kept
+    static bool avoidRecursion = false;
+    if (avoidRecursion)
+        return;
+    avoidRecursion = true;
+
+    logicStepLastStatus = LogicStep(); // it looks like the mmu_loop doesn't need to be a blocking call
+
+    if (is_mmu_error_monitor_active){
+        // Call this every iteration to keep the knob rotation responsive
+        // This includes when mmu_loop is called within manage_response
+        ReportErrorHook((uint16_t)lastErrorCode, mmu2.MMUCurrentErrorCode() == ErrorCode::OK ? ErrorSourcePrinter : ErrorSourceMMU);
+    }
+
+    avoidRecursion = false;
+}
+
+void MMU2::CheckFINDARunout()
+{
+    // Check for FINDA filament runout
+    if (!FindaDetectsFilament() && CHECK_FSENSOR) {
+        SERIAL_ECHOLNPGM("FINDA filament runout!");
+        stop_and_save_print_to_ram(0, 0);
+        restore_print_from_ram_and_continue(0);
+        if (SpoolJoin::spooljoin.isSpoolJoinEnabled() && get_current_tool() != (uint8_t)FILAMENT_UNKNOWN) // Can't auto if F=?
+        {
+            enquecommand_front_P(PSTR("M600 AUTO")); //save print and run M600 command
+        }
+        else
+        {
+            enquecommand_front_P(PSTR("M600")); //save print and run M600 command
+        }
+    }
+}
+
+struct ReportingRAII {
+    CommandInProgress cip;
+    inline ReportingRAII(CommandInProgress cip):cip(cip){
+        BeginReport(cip, (uint16_t)ProgressCode::EngagingIdler);
+    }
+    inline ~ReportingRAII(){
+        EndReport(cip, (uint16_t)ProgressCode::OK);
+    }
+};
+
+bool MMU2::WaitForMMUReady(){
+    switch(State()){
+    case xState::Stopped:
+        return false;
+    case xState::Connecting:
+        // shall we wait until the MMU reconnects?
+        // fire-up a fsm_dlg and show "MMU not responding"?
+    default:
+        return true;
+    }
+}
+
+bool MMU2::RetryIfPossible(uint16_t ec){
+    if( retryAttempts ){
+        SERIAL_ECHOPGM("retryAttempts=");SERIAL_ECHOLN((uint16_t)retryAttempts);
+        SetButtonResponse(ButtonOperations::Retry);
+        // check, that Retry is actually allowed on that operation
+        if( ButtonAvailable(ec) != NoButton ){
+            inAutoRetry = true;
+            SERIAL_ECHOLNPGM("RetryButtonPressed");
+            // We don't decrement until the button is acknowledged by the MMU.
+            //--retryAttempts; // "used" one retry attempt
+            return true;
+        }
+    }
+    inAutoRetry = false;
+    return false;
+}
+
+void MMU2::ResetRetryAttempts(){
+    SERIAL_ECHOLNPGM("ResetRetryAttempts");
+    retryAttempts = MAX_RETRIES;
+}
+
+void MMU2::DecrementRetryAttempts(){
+    if (inAutoRetry && retryAttempts)
+    {
+        SERIAL_ECHOLNPGM("DecrementRetryAttempts");
+        retryAttempts--;
+    }
+}
+
+bool MMU2::tool_change(uint8_t index) {
+    if( ! WaitForMMUReady())
+        return false;
+
+    if (index != extruder) {
+        if (!IS_SD_PRINTING && !usb_timer.running())
+        {
+            // If Tcodes are used manually through the serial
+            // we need to unload manually as well
+            unload();
+        }
+
+        ReportingRAII rep(CommandInProgress::ToolChange);
+        FSensorBlockRunout blockRunout;
+
+        st_synchronize();
+
+        tool_change_extruder = index;
+        logic.ToolChange(index); // let the MMU pull the filament out and push a new one in
+        manage_response(true, true);
+        
+        // reset current position to whatever the planner thinks it is
+        plan_set_e_position(current_position[E_AXIS]);
+
+        extruder = index; //filament change is finished
+        SpoolJoin::spooljoin.setSlot(index);
+
+        // @@TODO really report onto the serial? May be for the Octoprint? Not important now
+        //        SERIAL_ECHO_START();
+        //        SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, int(extruder));
+    }
+    return true;
+}
+
+/// Handle special T?/Tx/Tc commands
+///
+///- T? Gcode to extrude shouldn't have to follow, load to extruder wheels is done automatically
+///- Tx Same as T?, except nozzle doesn't have to be preheated. Tc must be placed after extruder nozzle is preheated to finish filament load.
+///- Tc Load to nozzle after filament was prepared by Tx and extruder nozzle is already heated.
+bool MMU2::tool_change(char code, uint8_t slot) {
+    if( ! WaitForMMUReady())
+        return false;
+
+    FSensorBlockRunout blockRunout;
+
+    switch (code) {
+    case '?': {
+        waitForHotendTargetTemp(100, []{});
+        load_filament_to_nozzle(slot);
+    } break;
+
+    case 'x': {
+        set_extrude_min_temp(0); // Allow cold extrusion since Tx only loads to the gears not nozzle
+        st_synchronize();
+        tool_change_extruder = slot;
+        logic.ToolChange(slot);
+        manage_response(false, false);
+        extruder = slot;
+        SpoolJoin::spooljoin.setSlot(slot);
+        set_extrude_min_temp(EXTRUDE_MINTEMP);
+    } break;
+
+    case 'c': {
+        waitForHotendTargetTemp(100, []{});
+        execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof (load_to_nozzle_sequence[0]));
+    } break;
+    }
+
+    return true;
+}
+
+void MMU2::get_statistics() {
+    logic.Statistics();
+}
+
+uint8_t MMU2::get_current_tool() const {
+    return extruder == MMU2_NO_TOOL ? (uint8_t)FILAMENT_UNKNOWN : extruder;
+}
+
+uint8_t MMU2::get_tool_change_tool() const {
+    return tool_change_extruder == MMU2_NO_TOOL ? (uint8_t)FILAMENT_UNKNOWN : tool_change_extruder;
+}
+
+bool MMU2::set_filament_type(uint8_t index, uint8_t type) {
+    if( ! WaitForMMUReady())
+        return false;
+    
+    // @@TODO - this is not supported in the new MMU yet
+    // cmd_arg = filamentType;
+    // command(MMU_CMD_F0 + index);
+
+    manage_response(false, false); // true, true); -- Comment: how is it possible for a filament type set to fail?
+    
+    return true;
+}
+
+bool MMU2::unload() {
+    if( ! WaitForMMUReady())
+        return false;
+
+    WaitForHotendTargetTempBeep();
+
+    {
+        FSensorBlockRunout blockRunout;
+        ReportingRAII rep(CommandInProgress::UnloadFilament);
+        filament_ramming();
+
+        logic.UnloadFilament();
+        manage_response(false, true);
+        Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
+
+        // no active tool
+        extruder = MMU2_NO_TOOL;
+        tool_change_extruder = MMU2_NO_TOOL;
+    }
+    return true;
+}
+
+bool MMU2::cut_filament(uint8_t index){
+    if( ! WaitForMMUReady())
+        return false;
+
+    ReportingRAII rep(CommandInProgress::CutFilament);
+    logic.CutFilament(index);
+    manage_response(false, true);
+    
+    return true;
+}
+
+void FullScreenMsg(const char *pgmS, uint8_t slot){
+    lcd_update_enable(false);
+    lcd_clear();
+    lcd_puts_at_P(0, 1, pgmS);
+    lcd_print(' ');
+    lcd_print(slot + 1);
+}
+
+bool MMU2::load_to_extruder(uint8_t index){
+    FullScreenMsg(_T(MSG_TESTING_FILAMENT), index);
+    tool_change(index);
+    st_synchronize();
+    unload();
+    lcd_update_enable(true);
+    return true;
+}
+
+bool MMU2::load_filament(uint8_t index) {
+    if( ! WaitForMMUReady())
+        return false;
+
+    FullScreenMsg(_T(MSG_LOADING_FILAMENT), index);
+
+    ReportingRAII rep(CommandInProgress::LoadFilament);
+    logic.LoadFilament(index);
+    manage_response(false, false);
+    Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
+
+    lcd_update_enable(true);
+
+    return true;
+}
+
+struct LoadingToNozzleRAII {
+    MMU2 &mmu2;
+    explicit inline LoadingToNozzleRAII(MMU2 &mmu2):mmu2(mmu2){
+        mmu2.loadingToNozzle = true;
+    }
+    inline ~LoadingToNozzleRAII(){
+        mmu2.loadingToNozzle = false;
+    }
+};
+
+bool MMU2::load_filament_to_nozzle(uint8_t index) {
+    if( ! WaitForMMUReady())
+        return false;
+
+    LoadingToNozzleRAII ln(*this);
+
+    WaitForHotendTargetTempBeep();
+
+    FullScreenMsg(_T(MSG_LOADING_FILAMENT), index);
+    {
+        // used for MMU-menu operation "Load to Nozzle"
+        ReportingRAII rep(CommandInProgress::ToolChange);
+        FSensorBlockRunout blockRunout;
+
+        if( extruder != MMU2_NO_TOOL ){ // we already have some filament loaded - free it + shape its tip properly
+            filament_ramming();
+        }
+
+        tool_change_extruder = index;
+        logic.ToolChange(index);
+        manage_response(true, true);
+
+        // The MMU's idler is disengaged at this point
+        // That means the MK3/S now has fully control
+
+        // reset current position to whatever the planner thinks it is
+        st_synchronize();
+        plan_set_e_position(current_position[E_AXIS]);
+
+        // Finish loading to the nozzle with finely tuned steps.
+        execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof (load_to_nozzle_sequence[0]));
+
+        extruder = index;
+        SpoolJoin::spooljoin.setSlot(index);
+
+        Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
+    }
+    lcd_update_enable(true);
+    return true;
+}
+
+bool MMU2::eject_filament(uint8_t index, bool recover) {
+    if( ! WaitForMMUReady())
+        return false;
+
+    ReportingRAII rep(CommandInProgress::EjectFilament);
+    current_position[E_AXIS] -= MMU2_FILAMENTCHANGE_EJECT_FEED;
+    plan_buffer_line_curposXYZE(2500.F / 60.F);
+    st_synchronize();
+    logic.EjectFilament(index);
+    manage_response(false, false);
+
+    if (recover) {
+        //        LCD_MESSAGEPGM(MSG_MMU2_EJECT_RECOVER);
+        Sound_MakeSound(e_SOUND_TYPE_StandardPrompt);
+//@@TODO        wait_for_user = true;
+        
+        //#if ENABLED(HOST_PROMPT_SUPPORT)
+        //        host_prompt_do(PROMPT_USER_CONTINUE, PSTR("MMU2 Eject Recover"), PSTR("Continue"));
+        //#endif
+        //#if ENABLED(EXTENSIBLE_UI)
+        //        ExtUI::onUserConfirmRequired_P(PSTR("MMU2 Eject Recover"));
+        //#endif
+        
+//@@TODO        while (wait_for_user) idle(true);
+        
+        Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
+        // logic.Command(); //@@TODO command(MMU_CMD_R0);
+        manage_response(false, false);
+    }
+
+    // no active tool
+    extruder = MMU2_NO_TOOL;
+    tool_change_extruder = MMU2_NO_TOOL;
+    Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
+//    disable_E0();
+
+    return true;
+}
+
+void MMU2::Button(uint8_t index){
+    LogEchoEvent_P(PSTR("Button"));
+    logic.Button(index);
+}
+
+void MMU2::Home(uint8_t mode){
+    logic.Home(mode);
+}
+
+void MMU2::SaveAndPark(bool move_axes, bool turn_off_nozzle) {
+    if (mmu_print_saved == SavedState::None) { // First occurrence. Save current position, park print head, disable nozzle heater.
+        LogEchoEvent_P(PSTR("Saving and parking"));
+        st_synchronize();
+      
+        resume_hotend_temp = degTargetHotend(active_extruder);
+
+        if (move_axes){
+            mmu_print_saved |= SavedState::ParkExtruder;
+            // save current pos
+            for(uint8_t i = 0; i < 3; ++i){
+                resume_position.xyz[i] = current_position[i];
+            }
+
+            // lift Z
+            raise_z(MMU_ERR_Z_PAUSE_LIFT);
+
+            // move XY aside
+            current_position[X_AXIS] = MMU_ERR_X_PAUSE_POS;
+            current_position[Y_AXIS] = MMU_ERR_Y_PAUSE_POS;
+            plan_buffer_line_curposXYZE(NOZZLE_PARK_XY_FEEDRATE);
+            st_synchronize();
+        }
+
+        if (turn_off_nozzle){
+            mmu_print_saved |= SavedState::CooldownPending;
+            LogEchoEvent_P(PSTR("Heater cooldown pending"));
+            // This just sets the flag that we should timeout and shut off the nozzle in 30 minutes...
+            //setAllTargetHotends(0);
+        }
+    }
+    // keep the motors powered forever (until some other strategy is chosen)
+    // @@TODO do we need that in 8bit?
+    // gcode.reset_stepper_timeout();
+}
+
+void MMU2::ResumeHotendTemp() {
+    if ((mmu_print_saved & SavedState::CooldownPending))
+    {
+        // Clear the "pending" flag if we haven't cooled yet.
+        mmu_print_saved &= ~(SavedState::CooldownPending);
+        LogEchoEvent_P(PSTR("Cooldown flag cleared"));
+    }
+    if ((mmu_print_saved & SavedState::Cooldown) && resume_hotend_temp) {
+        LogEchoEvent_P(PSTR("Resuming Temp"));
+        MMU2_ECHO_MSGRPGM(PSTR("Restoring hotend temperature "));
+        SERIAL_ECHOLN(resume_hotend_temp);
+        mmu_print_saved &= ~(SavedState::Cooldown);
+        setTargetHotend(resume_hotend_temp, active_extruder);
+        lcd_display_message_fullscreen_P(_i("MMU Retry: Restoring temperature...")); ////MSG_MMU_RESTORE_TEMP c=20 r=4
+        //@todo better report the event and let the GUI do its work somewhere else
+        ReportErrorHookSensorLineRender();
+        waitForHotendTargetTemp(1000, []{
+            ReportErrorHookDynamicRender();
+            manage_inactivity(true);
+        });
+        lcd_update_enable(true); // temporary hack to stop this locking the printer...
+        LogEchoEvent_P(PSTR("Hotend temperature reached"));
+        lcd_clear();
+    }
+}
+
+void MMU2::ResumeUnpark(){
+    if (mmu_print_saved & SavedState::ParkExtruder) {
+        LogEchoEvent_P(PSTR("Resuming XYZ"));
+
+        current_position[X_AXIS] = resume_position.xyz[X_AXIS];
+        current_position[Y_AXIS] = resume_position.xyz[Y_AXIS];
+        plan_buffer_line_curposXYZE(NOZZLE_PARK_XY_FEEDRATE);
+        st_synchronize();
+        
+        current_position[Z_AXIS] = resume_position.xyz[Z_AXIS];
+        plan_buffer_line_curposXYZE(NOZZLE_PARK_Z_FEEDRATE);
+        st_synchronize();
+        mmu_print_saved &= ~(SavedState::ParkExtruder);
+    }
+}
+
+void MMU2::CheckUserInput(){
+    auto btn = ButtonPressed((uint16_t)lastErrorCode);
+
+    // Was a button pressed on the MMU itself instead of the LCD?
+    if (btn == Buttons::NoButton && lastButton != Buttons::NoButton){
+        btn = lastButton;
+        lastButton = Buttons::NoButton; // Clear it. 
+    }
+
+    switch (btn) {
+    case Left:
+    case Middle:
+    case Right:
+        SERIAL_ECHOPGM("CheckUserInput-btnLMR ");
+        SERIAL_ECHOLN(btn);
+        ResumeHotendTemp(); // Recover the hotend temp before we attempt to do anything else...
+        Button(btn);
+        break;
+    case RestartMMU:
+        Reset(ResetPin); // we cannot do power cycle on the MK3
+        // ... but mmu2_power.cpp knows this and triggers a soft-reset instead.
+        break;
+    case DisableMMU:
+        Stop(); // Poweroff handles updating the EEPROM shutoff.
+        break;
+    case StopPrint:
+        // @@TODO not sure if we shall handle this high level operation at this spot
+        break;
+    default:
+        break;
+    }
+}
+
+/// Originally, this was used to wait for response and deal with timeout if necessary.
+/// The new protocol implementation enables much nicer and intense reporting, so this method will boil down
+/// just to verify the result of an issued command (which was basically the original idea)
+///
+/// It is closely related to mmu_loop() (which corresponds to our ProtocolLogic::Step()), which does NOT perform any blocking wait for a command to finish.
+/// But - in case of an error, the command is not yet finished, but we must react accordingly - move the printhead elsewhere, stop heating, eat a cat or so.
+/// That's what's being done here...
+void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
+    mmu_print_saved = SavedState::None;
+
+    KEEPALIVE_STATE(IN_PROCESS);
+
+    LongTimer nozzleTimeout;
+
+    for (;;) {
+        // in our new implementation, we know the exact state of the MMU at any moment, we do not have to wait for a timeout
+        // So in this case we shall decide if the operation is:
+        // - still running -> wait normally in idle()
+        // - failed -> then do the safety moves on the printer like before
+        // - finished ok -> proceed with reading other commands
+        manage_heater();
+        manage_inactivity(true); // calls LogicStep() and remembers its return status
+        lcd_update(0);
+
+        if (mmu_print_saved & SavedState::CooldownPending){
+            if (!nozzleTimeout.running()){
+                nozzleTimeout.start();
+                LogEchoEvent_P(PSTR("Cooling Timeout started"));
+            } else if (nozzleTimeout.expired(DEFAULT_SAFETYTIMER_TIME_MINS*60*1000ul)){ // mins->msec. TODO: do we use the global or have our own independent timeout
+                mmu_print_saved &= ~(SavedState::CooldownPending);
+                mmu_print_saved |= SavedState::Cooldown;
+                setAllTargetHotends(0);
+                LogEchoEvent_P(PSTR("Heater cooldown"));
+            }
+        } else if (nozzleTimeout.running()) {
+            nozzleTimeout.stop();
+            LogEchoEvent_P(PSTR("Cooling timer stopped"));
+        }
+
+        switch (logicStepLastStatus) {
+        case Finished: 
+            // command/operation completed, let Marlin continue its work
+            // the E may have some more moves to finish - wait for them
+            ResumeUnpark(); // We can now travel back to the tower or wherever we were when we saved.
+            ResetRetryAttempts(); // Reset the retry counter.
+            st_synchronize(); 
+            return;
+        case VersionMismatch: // this basically means the MMU will be disabled until reconnected
+            CheckUserInput();
+            return;
+        case CommandError:
+            // Don't proceed to the park/save if we are doing an autoretry.
+            if (inAutoRetry){
+                continue;
+            }
+            [[fallthrough]];
+        case CommunicationTimeout:
+        case ProtocolError:
+            SaveAndPark(move_axes, turn_off_nozzle); // and wait for the user to resolve the problem
+            CheckUserInput();
+            break;
+        case CommunicationRecovered: // @@TODO communication recovered and may be an error recovered as well
+            // may be the logic layer can detect the change of state a respond with one "Recovered" to be handled here
+            ResumeHotendTemp();
+            ResumeUnpark();
+            break;
+        case Processing: // wait for the MMU to respond
+        default:
+            break;
+        }
+    }
+}
+
+StepStatus MMU2::LogicStep() {
+    CheckUserInput(); // Process any buttons before proceeding with another MMU Query
+    StepStatus ss = logic.Step();
+    switch (ss) {
+    case Finished:
+        // At this point it is safe to trigger a runout and not interrupt the MMU protocol
+        CheckFINDARunout();
+        break;
+    case Processing:
+        OnMMUProgressMsg(logic.Progress());
+        break;
+    case CommandError:
+        ReportError(logic.Error(), ErrorSourceMMU);
+        break;
+    case CommunicationTimeout:
+        state = xState::Connecting;
+        ReportError(ErrorCode::MMU_NOT_RESPONDING, ErrorSourcePrinter);
+        break;
+    case ProtocolError:
+        state = xState::Connecting;
+        ReportError(ErrorCode::PROTOCOL_ERROR, ErrorSourcePrinter);
+        break;
+    case VersionMismatch:
+        StopKeepPowered();
+        ReportError(ErrorCode::VERSION_MISMATCH, ErrorSourcePrinter);
+        break;
+    case ButtonPushed:
+        lastButton = logic.Button();
+        LogEchoEvent_P(PSTR("MMU Button pushed"));
+        CheckUserInput(); // Process the button immediately
+        break;
+    default:
+        break;
+    }
+    
+    if( logic.Running() ){
+        state = xState::Active;
+    }
+    return ss;
+}
+
+void MMU2::filament_ramming() {
+    execute_extruder_sequence((const E_Step *)ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step));
+}
+
+void MMU2::execute_extruder_sequence(const E_Step *sequence, uint8_t steps) {
+    st_synchronize();
+    const E_Step *step = sequence;
+    for (uint8_t i = 0; i < steps; i++) {
+        current_position[E_AXIS] += pgm_read_float(&(step->extrude));
+        plan_buffer_line_curposXYZE(pgm_read_float(&(step->feedRate)));
+        st_synchronize();
+        step++;
+    }
+}
+
+void MMU2::ReportError(ErrorCode ec, uint8_t res) {
+    // Due to a potential lossy error reporting layers linked to this hook
+    // we'd better report everything to make sure especially the error states
+    // do not get lost. 
+    // - The good news here is the fact, that the MMU reports the errors repeatedly until resolved.
+    // - The bad news is, that MMU not responding may repeatedly occur on printers not having the MMU at all.
+    // 
+    // Not sure how to properly handle this situation, options:
+    // - skip reporting "MMU not responding" (at least for now)
+    // - report only changes of states (we can miss an error message)
+    // - may be some combination of MMUAvailable + UseMMU flags and decide based on their state
+    // Right now the filtering of MMU_NOT_RESPONDING is done in ReportErrorHook() as it is not a problem if mmu2.cpp
+
+    // Depending on the Progress code, we may want to do some action when an error occurs
+    switch (logic.Progress()){
+    case ProgressCode::UnloadingToFinda:
+        unloadFilamentStarted = false;
+        break;
+    case ProgressCode::FeedingToFSensor:
+        // FSENSOR error during load. Make sure E-motor stops moving.
+        loadFilamentStarted = false;
+        break;
+    default:
+        break;
+    }
+
+    ReportErrorHook((uint16_t)ec, res);
+
+    if( ec != lastErrorCode ){ // deduplicate: only report changes in error codes into the log
+        lastErrorCode = ec;
+        LogErrorEvent_P( _O(PrusaErrorTitle(PrusaErrorCodeIndex((uint16_t)ec))) );
+    }
+
+    static_assert(mmu2Magic[0] == 'M' 
+        && mmu2Magic[1] == 'M' 
+        && mmu2Magic[2] == 'U' 
+        && mmu2Magic[3] == '2' 
+        && mmu2Magic[4] == ':' 
+        && strlen_constexpr(mmu2Magic) == 5, 
+        "MMU2 logging prefix mismatch, must be updated at various spots"
+    );
+}
+
+void MMU2::ReportProgress(ProgressCode pc) {
+    ReportProgressHook((CommandInProgress)logic.CommandInProgress(), (uint16_t)pc);
+    LogEchoEvent_P( _O(ProgressCodeToText((uint16_t)pc)) );
+}
+
+void MMU2::OnMMUProgressMsg(ProgressCode pc){
+    if (pc != lastProgressCode) {
+        OnMMUProgressMsgChanged(pc);
+    } else {
+        OnMMUProgressMsgSame(pc);
+    }
+}
+
+void MMU2::OnMMUProgressMsgChanged(ProgressCode pc){
+    ReportProgress(pc);
+    lastProgressCode = pc;
+    switch (pc) {
+    case ProgressCode::UnloadingToFinda:
+        if ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::UnloadFilament
+        || ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::ToolChange))
+        {
+            // If MK3S sent U0 command, ramming sequence takes care of releasing the filament.
+            // If Toolchange is done while printing, PrusaSlicer takes care of releasing the filament
+            // If printing is not in progress, ToolChange will issue a U0 command.
+            break;
+        } else {
+            // We're likely recovering from an MMU error
+            st_synchronize();
+            unloadFilamentStarted = true;
+            current_position[E_AXIS] -= MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH;
+            plan_buffer_line_curposXYZE(MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE);
+        }
+        break;
+    case ProgressCode::FeedingToFSensor:
+        // prepare for the movement of the E-motor
+        st_synchronize();
+        loadFilamentStarted = true;
+        break;
+    default:
+        // do nothing yet
+        break;
+    }
+}
+
+void MMU2::OnMMUProgressMsgSame(ProgressCode pc){
+    switch (pc) {
+    case ProgressCode::UnloadingToFinda:
+        if (unloadFilamentStarted && !blocks_queued()) { // Only plan a move if there is no move ongoing
+            if (fsensor.getFilamentPresent()) {
+                current_position[E_AXIS] -= MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH;
+                plan_buffer_line_curposXYZE(MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE);
+            } else {
+                unloadFilamentStarted = false;
+            }
+        }
+        break;
+    case ProgressCode::FeedingToFSensor:
+        if (loadFilamentStarted) {
+            switch (WhereIsFilament()) {
+            case FilamentState::AT_FSENSOR:
+                // fsensor triggered, finish FeedingToExtruder state
+                loadFilamentStarted = false;
+                // After the MMU knows the FSENSOR is triggered it will:
+                // 1. Push the filament by additional 30mm (see fsensorToNozzle)
+                // 2. Disengage the idler and push another 5mm.
+                current_position[E_AXIS] += 30.0f + 2.0f;
+                plan_buffer_line_curposXYZE(MMU2_LOAD_TO_NOZZLE_FEED_RATE);
+                break;
+            case FilamentState::NOT_PRESENT:
+                // fsensor not triggered, continue moving extruder
+                if (!blocks_queued()) { // Only plan a move if there is no move ongoing
+                    current_position[E_AXIS] += 2.0f;
+                    plan_buffer_line_curposXYZE(MMU2_LOAD_TO_NOZZLE_FEED_RATE);
+                }
+                break;
+            default:
+                // Abort here?
+                break;
+            }
+        }
+        break;
+    default:
+        // do nothing yet
+        break;
+    }
+}
+
+} // namespace MMU2

+ 291 - 0
Firmware/mmu2.h

@@ -0,0 +1,291 @@
+/// @file
+#pragma once
+#include "mmu2_protocol_logic.h"
+
+struct E_Step;
+
+namespace MMU2 {
+
+static constexpr uint8_t MAX_RETRIES = 3U;
+
+/// @@TODO hmmm, 12 bytes... may be we can reduce that
+struct xyz_pos_t {
+    float xyz[3];
+    xyz_pos_t()=default;
+};
+
+// general MMU setup for MK3
+enum : uint8_t {
+    FILAMENT_UNKNOWN = 0xffU
+};
+
+struct Version {
+    uint8_t major, minor, build;
+};
+
+/// Top-level interface between Logic and Marlin.
+/// Intentionally named MMU2 to be (almost) a drop-in replacement for the previous implementation.
+/// Most of the public methods share the original naming convention as well.
+class MMU2 {
+public:
+    MMU2();
+    
+    /// 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();
+    
+    /// States of a printer with the MMU:
+    /// - Active
+    /// - Connecting
+    /// - Stopped
+    /// 
+    /// When the printer's FW starts, the MMU2 mode is either Stopped or NotResponding (based on user's preference).
+    /// When the MMU successfully establishes communication, the state changes to Active.
+    enum class xState : uint_fast8_t {
+        Active, ///< MMU has been detected, connected, communicates and is ready to be worked with.
+        Connecting, ///< MMU is connected but it doesn't communicate (yet). The user wants the MMU, but it is not ready to be worked with.
+        Stopped ///< The user doesn't want the printer to work with the MMU. The MMU itself is not powered and does not work at all.
+    };
+    
+    inline xState State() const { return state; }
+    
+    // @@TODO temporary wrappers to make old gcc survive the code
+    inline bool Enabled()const { return State() == xState::Active; }
+
+    /// Different levels of resetting the MMU
+    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)
+    };
+
+    /// 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 ReportErrorSource: uint8_t {
+        ErrorSourcePrinter = 0,
+        ErrorSourceMMU = 1,
+    };
+
+    /// 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
+    /// @returns 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
+    /// @returns true upon success
+    bool WriteRegister(uint8_t address, uint16_t data);
+
+
+    /// The main loop of MMU processing.
+    /// Doesn't loop (block) inside, performs just one step of logic state machines.
+    /// Also, internally it prevents recursive entries.
+    void mmu_loop();
+
+    /// The main MMU command - select a different slot
+    /// @param index of the slot to be selected
+    /// @returns false if the operation cannot be performed (Stopped)
+    bool tool_change(uint8_t index);
+    
+    /// 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.
+    /// @returns 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)
+    /// @returns false if the operation cannot be performed (Stopped)
+    bool load_filament(uint8_t index);
+    
+    /// Load (push) filament from the MMU into the printer's nozzle
+    /// @returns false if the operation cannot be performed (Stopped or cold extruder)
+    bool load_filament_to_nozzle(uint8_t index);
+
+    /// 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 index, bool recover);
+
+    /// Issue a Cut command into the MMU
+    /// Requires unloaded filament from the printer (obviously)
+    /// @returns false if the operation cannot be performed (Stopped)
+    bool cut_filament(uint8_t index);
+
+    /// 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.
+    bool load_to_extruder(uint8_t index);
+
+    /// @returns the active filament slot index (0-4) or 0xff in case of no active tool
+    uint8_t get_current_tool() const;
+
+    /// @returns 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 index, 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);
+
+    /// @returns current state of FINDA (true=filament present, false=filament not present)
+    inline bool FindaDetectsFilament()const { return logic.FindaPressed(); }
+
+    inline uint16_t TotalFailStatistics()const { return logic.FailStatistics(); }
+
+    /// @returns Current error code
+    inline ErrorCode MMUCurrentErrorCode() const { return logic.Error(); }
+
+    /// @returns 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}; 
+        }
+    }
+
+    // Helper variable to monitor knob in MMU error screen in blocking functions e.g. manage_response
+    bool is_mmu_error_monitor_active;
+
+    /// 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
+    bool RetryIfPossible(uint16_t ec);
+
+    /// Decrement the retry attempts, if in a retry. 
+    // Called by the MMU protocol when a sent button is acknowledged.
+    void DecrementRetryAttempts();
+
+private:
+    /// Reset the retryAttempts back to the default value
+    void ResetRetryAttempts();
+    /// Perform software self-reset of the MMU (sends an X0 command)
+    void ResetX0();
+    
+    /// 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
+    void manage_response(const bool move_axes, const bool turn_off_nozzle);
+    
+    /// 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
+    StepStatus LogicStep();
+    
+    void filament_ramming();
+    void execute_extruder_sequence(const E_Step *sequence, uint8_t steps);
+
+    /// 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, uint8_t 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
+    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);
+    
+    /// Save print and park the print head
+    void SaveAndPark(bool move_axes, bool turn_off_nozzle);
+
+    /// 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.
+    /// @returns false if the MMU is not ready to perform the command (for whatever reason)
+    bool WaitForMMUReady();
+
+    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;
+    Buttons lastButton = Buttons::NoButton;
+
+    StepStatus logicStepLastStatus;
+    
+    enum xState state;
+
+    uint8_t mmu_print_saved;
+    bool loadFilamentStarted;
+    bool unloadFilamentStarted;
+    
+    friend struct LoadingToNozzleRAII;
+    /// true in case we are doing the LoadToNozzle operation - that means the filament shall be loaded all the way down to the nozzle
+    /// unlike the mid-print ToolChange commands, which only load the first ~30mm and then the G-code takes over.
+    bool loadingToNozzle;
+    
+    uint8_t retryAttempts;
+
+    bool inAutoRetry;
+};
+
+/// 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 MMU2 mmu2;
+
+} // namespace MMU2

+ 37 - 0
Firmware/mmu2/buttons.h

@@ -0,0 +1,37 @@
+#pragma once
+#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 MMU2 {
+
+/// 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,
+    RestartMMU  = 3,
+    Unload      = 4,
+    StopPrint   = 5,
+    DisableMMU  = 6,
+};
+
+/// Button codes + extended actions performed on the printer's side
+enum Buttons : uint8_t {
+    Right = 0,
+    Middle,
+    Left,
+    
+    // performed on the printer's side
+    RestartMMU,
+    StopPrint,
+    DisableMMU,
+    
+    NoButton = 0xff // shall be kept last
+};
+
+
+} // namespace MMU2

+ 109 - 0
Firmware/mmu2/error_codes.h

@@ -0,0 +1,109 @@
+/// @file error_codes.h
+#pragma once
+#include <stdint.h>
+
+/// A complete set of error codes which may be a result of a high-level command/operation.
+/// This header file shall 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 FastFeedToExtruder - 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
+
+    MOVE_FAILED = 0x800a, ///< 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
+
+    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
+    /// - E33404 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
+};

+ 353 - 0
Firmware/mmu2/errors_list.h

@@ -0,0 +1,353 @@
+// Extracted from Prusa-Error-Codes repo
+// Subject to automation and optimization
+// BEWARE - this file shall be included only into mmu2_error_converter.cpp, not anywhere else!
+#pragma once
+#include "inttypes.h"
+#include "../language.h"
+#include <avr/pgmspace.h>
+#include "buttons.h"
+
+namespace MMU2 {
+
+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_DIDNT_GO_OFF = 102,
+    ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER = 103,
+    ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF = 104,
+
+    ERR_MECHANICAL_PULLEY_CANNOT_MOVE = 105,
+    ERR_MECHANICAL_FSENSOR_TOO_EARLY = 106,
+    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_PULLEY_WARNING_TMC_TOO_HOT = 201,
+    ERR_TEMPERATURE_SELECTOR_WARNING_TMC_TOO_HOT = 211,
+    ERR_TEMPERATURE_IDLER_WARNING_TMC_TOO_HOT = 221,
+
+    ERR_TEMPERATURE_PULLEY_TMC_OVERHEAT_ERROR = 202,
+    ERR_TEMPERATURE_SELECTOR_TMC_OVERHEAT_ERROR = 212,
+    ERR_TEMPERATURE_IDLER_TMC_OVERHEAT_ERROR = 222,
+
+
+    ERR_ELECTRICAL = 300,
+    ERR_ELECTRICAL_PULLEY_TMC_DRIVER_ERROR = 301,
+    ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_ERROR = 311,
+    ERR_ELECTRICAL_IDLER_TMC_DRIVER_ERROR = 321,
+
+    ERR_ELECTRICAL_PULLEY_TMC_DRIVER_RESET = 302,
+    ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_RESET = 312,
+    ERR_ELECTRICAL_IDLER_TMC_DRIVER_RESET = 322,
+
+    ERR_ELECTRICAL_PULLEY_TMC_UNDERVOLTAGE_ERROR = 303,
+    ERR_ELECTRICAL_SELECTOR_TMC_UNDERVOLTAGE_ERROR = 313,
+    ERR_ELECTRICAL_IDLER_TMC_UNDERVOLTAGE_ERROR = 323,
+
+    ERR_ELECTRICAL_PULLEY_TMC_DRIVER_SHORTED = 304,
+    ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_SHORTED = 314,
+    ERR_ELECTRICAL_IDLER_TMC_DRIVER_SHORTED = 324,
+
+
+    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_OTHER = 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 uint16_t errorCodes[] PROGMEM = {
+    ERR_MECHANICAL_FINDA_DIDNT_TRIGGER,
+    ERR_MECHANICAL_FINDA_DIDNT_GO_OFF,
+    ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER,
+    ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF,
+    ERR_MECHANICAL_PULLEY_CANNOT_MOVE,
+    ERR_MECHANICAL_FSENSOR_TOO_EARLY,
+    ERR_MECHANICAL_SELECTOR_CANNOT_HOME,
+    ERR_MECHANICAL_SELECTOR_CANNOT_MOVE,
+    ERR_MECHANICAL_IDLER_CANNOT_HOME,
+    ERR_MECHANICAL_IDLER_CANNOT_MOVE,
+    ERR_TEMPERATURE_PULLEY_WARNING_TMC_TOO_HOT,
+    ERR_TEMPERATURE_SELECTOR_WARNING_TMC_TOO_HOT,
+    ERR_TEMPERATURE_IDLER_WARNING_TMC_TOO_HOT,
+    ERR_TEMPERATURE_PULLEY_TMC_OVERHEAT_ERROR,
+    ERR_TEMPERATURE_SELECTOR_TMC_OVERHEAT_ERROR,
+    ERR_TEMPERATURE_IDLER_TMC_OVERHEAT_ERROR,
+    ERR_ELECTRICAL_PULLEY_TMC_DRIVER_ERROR,
+    ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_ERROR,
+    ERR_ELECTRICAL_IDLER_TMC_DRIVER_ERROR,
+    ERR_ELECTRICAL_PULLEY_TMC_DRIVER_RESET,
+    ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_RESET,
+    ERR_ELECTRICAL_IDLER_TMC_DRIVER_RESET,
+    ERR_ELECTRICAL_PULLEY_TMC_UNDERVOLTAGE_ERROR,
+    ERR_ELECTRICAL_SELECTOR_TMC_UNDERVOLTAGE_ERROR,
+    ERR_ELECTRICAL_IDLER_TMC_UNDERVOLTAGE_ERROR,
+    ERR_ELECTRICAL_PULLEY_TMC_DRIVER_SHORTED,
+    ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_SHORTED,
+    ERR_ELECTRICAL_IDLER_TMC_DRIVER_SHORTED,
+    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
+};
+
+// @@TODO some of the strings are duplicates, can be merged into one     01234567890123456789
+static const char MSG_TITLE_FINDA_DIDNT_TRIGGER[] PROGMEM_I1     = ISTR("FINDA DIDNT TRIGGER"); ////MSG_TITLE_FINDA_DIDNT_TRIGGER c=20
+static const char MSG_TITLE_FINDA_DIDNT_GO_OFF[] PROGMEM_I1      = ISTR("FINDA: FILAM. STUCK"); ////MSG_TITLE_FINDA_DIDNT_GO_OFF c=20
+static const char MSG_TITLE_FSENSOR_DIDNT_TRIGGER[] PROGMEM_I1   = ISTR("FSENSOR DIDNT TRIGG."); ////MSG_TITLE_FSENSOR_DIDNT_TRIGGER c=20
+static const char MSG_TITLE_FSENSOR_DIDNT_GO_OFF[] PROGMEM_I1    = ISTR("FSENSOR: FIL. STUCK"); ////MSG_TITLE_FSENSOR_DIDNT_GO_OFF c=20
+static const char MSG_TITLE_PULLEY_CANNOT_MOVE[] PROGMEM_I1      = ISTR("PULLEY CANNOT MOVE"); ////MSG_TITLE_PULLEY_CANNOT_MOVE c=20
+static const char MSG_TITLE_FSENSOR_TOO_EARLY[] PROGMEM_I1       = ISTR("FSENSOR TOO EARLY"); ////MSG_TITLE_FSENSOR_TOO_EARLY c=20
+static const char MSG_TITLE_SELECTOR_CANNOT_MOVE[] PROGMEM_I1    = ISTR("SELECTOR CANNOT MOVE"); ////MSG_TITLE_SELECTOR_CANNOT_MOVE c=20
+static const char MSG_TITLE_SELECTOR_CANNOT_HOME[] PROGMEM_I1    = ISTR("SELECTOR CANNOT HOME"); ////MSG_TITLE_SELECTOR_CANNOT_HOME c=20
+static const char MSG_TITLE_IDLER_CANNOT_MOVE[] PROGMEM_I1       = ISTR("IDLER CANNOT MOVE"); ////MSG_TITLE_IDLER_CANNOT_MOVE c=20
+static const char MSG_TITLE_IDLER_CANNOT_HOME[] PROGMEM_I1       = ISTR("IDLER CANNOT HOME"); ////MSG_TITLE_IDLER_CANNOT_HOME c=20
+static const char MSG_TITLE_TMC_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("WARNING TMC TOO HOT"); ////MSG_TITLE_TMC_WARNING_TMC_TOO_HOT c=20
+//static const char MSG_TITLE_TMC_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("WARNING TMC TOO HOT"); ////MSG_TITLE_TMC_WARNING_TMC_TOO_HOT c=20
+//static const char MSG_TITLE_TMC_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("WARNING TMC TOO HOT");
+static const char MSG_TITLE_TMC_OVERHEAT_ERROR[] PROGMEM_I1      = ISTR("TMC OVERHEAT ERROR"); ////MSG_TITLE_TMC_OVERHEAT_ERROR c=20
+//static const char MSG_TITLE_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC OVERHEAT ERROR");
+//static const char MSG_TITLE_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC OVERHEAT ERROR");
+static const char MSG_TITLE_TMC_DRIVER_ERROR[] PROGMEM_I1        = ISTR("TMC DRIVER ERROR"); ////MSG_TITLE_TMC_DRIVER_ERROR c=20
+//static const char MSG_TITLE_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC DRIVER ERROR");
+//static const char MSG_TITLE_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC DRIVER ERROR");
+static const char MSG_TITLE_TMC_DRIVER_RESET[] PROGMEM_I1        = ISTR("TMC DRIVER RESET"); ////MSG_TITLE_TMC_DRIVER_RESET c=20
+//static const char MSG_TITLE_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC DRIVER RESET");
+//static const char MSG_TITLE_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC DRIVER RESET");
+static const char MSG_TITLE_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1  = ISTR("TMC UNDERVOLTAGE ERR"); ////MSG_TITLE_TMC_UNDERVOLTAGE_ERROR c=20
+//static const char MSG_TITLE_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("TMC UNDERVOLTAGE ERR");
+//static const char MSG_TITLE_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("TMC UNDERVOLTAGE ERR");
+static const char MSG_TITLE_TMC_DRIVER_SHORTED[] PROGMEM_I1      = ISTR("TMC DRIVER SHORTED"); ////MSG_TITLE_TMC_DRIVER_SHORTED c=20
+//static const char MSG_TITLE_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("TMC DRIVER SHORTED");
+//static const char MSG_TITLE_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("TMC DRIVER SHORTED");
+static const char MSG_TITLE_MMU_NOT_RESPONDING[] PROGMEM_I1      = ISTR("MMU NOT RESPONDING"); ////MSG_TITLE_MMU_NOT_RESPONDING c=20
+static const char MSG_TITLE_COMMUNICATION_ERROR[] PROGMEM_I1     = ISTR("COMMUNICATION ERROR"); ////MSG_TITLE_COMMUNICATION_ERROR c=20
+static const char MSG_TITLE_FIL_ALREADY_LOADED[] PROGMEM_I1      = ISTR("FILAMENT ALREADY LOA"); ////MSG_TITLE_FIL_ALREADY_LOADED c=20
+static const char MSG_TITLE_INVALID_TOOL[] PROGMEM_I1            = ISTR("INVALID TOOL"); ////MSG_TITLE_INVALID_TOOL c=20
+static const char MSG_TITLE_QUEUE_FULL[] PROGMEM_I1              = ISTR("QUEUE FULL"); ////MSG_TITLE_QUEUE_FULL c=20
+static const char MSG_TITLE_FW_UPDATE_NEEDED[] PROGMEM_I1        = ISTR("MMU FW UPDATE NEEDED"); ////MSG_TITLE_FW_UPDATE_NEEDED c=20
+static const char MSG_TITLE_FW_RUNTIME_ERROR[] PROGMEM_I1        = ISTR("FW RUNTIME ERROR"); ////MSG_TITLE_FW_RUNTIME_ERROR c=20
+static const char MSG_TITLE_UNLOAD_MANUALLY[] PROGMEM_I1         = ISTR("UNLOAD MANUALLY"); ////MSG_TITLE_UNLOAD_MANUALLY c=20
+
+static const char * const errorTitles [] PROGMEM = {
+    _R(MSG_TITLE_FINDA_DIDNT_TRIGGER),
+    _R(MSG_TITLE_FINDA_DIDNT_GO_OFF),
+    _R(MSG_TITLE_FSENSOR_DIDNT_TRIGGER),
+    _R(MSG_TITLE_FSENSOR_DIDNT_GO_OFF),
+    _R(MSG_TITLE_PULLEY_CANNOT_MOVE),
+    _R(MSG_TITLE_FSENSOR_TOO_EARLY),
+    _R(MSG_TITLE_SELECTOR_CANNOT_HOME),
+    _R(MSG_TITLE_SELECTOR_CANNOT_MOVE),
+    _R(MSG_TITLE_IDLER_CANNOT_HOME),
+    _R(MSG_TITLE_IDLER_CANNOT_MOVE),
+    _R(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
+    _R(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
+    _R(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
+    _R(MSG_TITLE_TMC_OVERHEAT_ERROR),
+    _R(MSG_TITLE_TMC_OVERHEAT_ERROR),
+    _R(MSG_TITLE_TMC_OVERHEAT_ERROR),
+    _R(MSG_TITLE_TMC_DRIVER_ERROR),
+    _R(MSG_TITLE_TMC_DRIVER_ERROR),
+    _R(MSG_TITLE_TMC_DRIVER_ERROR),
+    _R(MSG_TITLE_TMC_DRIVER_RESET),
+    _R(MSG_TITLE_TMC_DRIVER_RESET),
+    _R(MSG_TITLE_TMC_DRIVER_RESET),
+    _R(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
+    _R(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
+    _R(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
+    _R(MSG_TITLE_TMC_DRIVER_SHORTED),
+    _R(MSG_TITLE_TMC_DRIVER_SHORTED),
+    _R(MSG_TITLE_TMC_DRIVER_SHORTED),
+    _R(MSG_TITLE_MMU_NOT_RESPONDING),
+    _R(MSG_TITLE_COMMUNICATION_ERROR),
+    _R(MSG_TITLE_FIL_ALREADY_LOADED),
+    _R(MSG_TITLE_INVALID_TOOL),
+    _R(MSG_TITLE_QUEUE_FULL),
+    _R(MSG_TITLE_FW_UPDATE_NEEDED),
+    _R(MSG_TITLE_FW_RUNTIME_ERROR),
+    _R(MSG_TITLE_UNLOAD_MANUALLY)
+};
+
+// @@TODO looking at the texts, they can be composed of several parts and/or parametrized (could save a lot of space ;) )
+// Moreover, some of them have been disabled in favour of saving some more code size.
+static const char MSG_DESC_FINDA_DIDNT_TRIGGER[] PROGMEM_I1 = ISTR("FINDA didn't trigger while loading the filament. Ensure the filament can move and FINDA works."); ////MSG_DESC_FINDA_DIDNT_TRIGGER c=20 r=8
+static const char MSG_DESC_FINDA_DIDNT_GO_OFF[] PROGMEM_I1 = ISTR("FINDA didn't switch off while unloading filament. Try unloading manually. Ensure filament can move and FINDA works."); ////MSG_DESC_FINDA_DIDNT_GO_OFF c=20 r=8
+static const char MSG_DESC_FSENSOR_DIDNT_TRIGGER[] PROGMEM_I1 = ISTR("Filament sensor didn't trigger while loading the filament. Ensure the filament reached the fsensor and the sensor works."); ////MSG_DESC_FSENSOR_DIDNT_TRIGGER c=20 r=8
+static const char MSG_DESC_FSENSOR_DIDNT_GO_OFF[] PROGMEM_I1 = ISTR("Filament sensor didn't switch off while unloading filament. Ensure filament can move and the sensor works."); ////MSG_DESC_FSENSOR_DIDNT_GO_OFF c=20 r=8
+static const char MSG_DESC_PULLEY_STALLED[] PROGMEM_I1 = ISTR("Pulley motor stalled. Ensure the pulley can move and check the wiring."); ////MSG_DESC_PULLEY_STALLED c=20 r=8
+static const char MSG_DESC_FSENSOR_TOO_EARLY[] PROGMEM_I1 = ISTR("Filament sensor triggered too early while loading to extruder. Check there isn't anything stuck in PTFE tube. Check that sensor reads properly."); ////MSG_DESC_FSENSOR_TOO_EARLY c=20 r=8
+static const char MSG_DESC_SELECTOR_CANNOT_HOME[] PROGMEM_I1 = ISTR("The Selector cannot home properly. Check for anything blocking its movement."); ////MSG_DESC_SELECTOR_CANNOT_HOME c=20 r=8
+static const char MSG_DESC_CANNOT_MOVE[] PROGMEM_I1 = ISTR("Can't move Selector or Idler."); /////MSG_DESC_CANNOT_MOVE c=20 r=4
+//static const char MSG_DESC_SELECTOR_CANNOT_MOVE[] PROGMEM_I1 = ISTR("The Selector cannot move. Check for anything blocking its movement. Check the wiring is correct.");
+static const char MSG_DESC_IDLER_CANNOT_HOME[] PROGMEM_I1 = ISTR("The Idler cannot home properly. Check for anything blocking its movement."); ////MSG_DESC_IDLER_CANNOT_HOME c=20 r=8
+//static const char MSG_DESC_IDLER_CANNOT_MOVE[] PROGMEM_I1 = ISTR("The Idler cannot move properly. Check for anything blocking its movement. Check the wiring is correct.");
+static const char MSG_DESC_TMC[] PROGMEM_I1 = ISTR("More details online."); ////MSG_DESC_TMC c=20 r=8
+//static const char MSG_DESC_PULLEY_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("TMC driver for the Pulley motor is almost overheating. Make sure there is sufficient airflow near the MMU board.");
+//static const char MSG_DESC_SELECTOR_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("TMC driver for the Selector motor is almost overheating. Make sure there is sufficient airflow near the MMU board.");
+//static const char MSG_DESC_IDLER_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("TMC driver for the Idler motor is almost overheating. Make sure there is sufficient airflow near the MMU board.");
+//static const char MSG_DESC_PULLEY_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Pulley motor is overheated. Cool down the MMU board and reset MMU.");
+//static const char MSG_DESC_SELECTOR_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Selector motor is overheated. Cool down the MMU board and reset MMU.");
+//static const char MSG_DESC_IDLER_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Idler motor is overheated. Cool down the MMU board and reset MMU.");
+//static const char MSG_DESC_PULLEY_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Pulley motor is not responding. Try resetting the MMU. If the issue persists contact support.");
+//static const char MSG_DESC_SELECTOR_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Selector motor is not responding. Try resetting the MMU. If the issue persists contact support.");
+//static const char MSG_DESC_IDLER_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Idler motor is not responding. Try resetting the MMU. If the issue persists contact support.");
+//static const char MSG_DESC_PULLEY_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC driver for the Pulley motor was restarted. There is probably an issue with the electronics. Check the wiring and connectors.");
+//static const char MSG_DESC_SELECTOR_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC driver for the Selector motor was restarted. There is probably an issue with the electronics. Check the wiring and connectors.");
+//static const char MSG_DESC_IDLER_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC driver for the Idler motor was restarted. There is probably an issue with the electronics. Check the wiring and connectors.");
+//static const char MSG_DESC_PULLEY_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("Not enough current for the Pulley TMC driver. There is probably an issue with the electronics. Check the wiring and connectors.");
+//static const char MSG_DESC_SELECTOR_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("Not enough current for the Selector TMC driver. There is probably an issue with the electronics. Check the wiring and connectors.");
+//static const char MSG_DESC_IDLER_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("Not enough current for the Idler TMC driver. There is probably an issue with the electronics. Check the wiring and connectors.");
+//static const char MSG_DESC_PULLEY_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("Short circuit on the Pulley TMC driver. Check the wiring and connectors. If the issue persists contact support.");
+//static const char MSG_DESC_SELECTOR_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("Short circuit on the Selector TMC driver. Check the wiring and connectors. If the issue persists contact support.");
+//static const char MSG_DESC_IDLER_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("Short circuit on the Idler TMC driver. Check the wiring and connectors. If the issue persists contact support.");
+static const char MSG_DESC_MMU_NOT_RESPONDING[] PROGMEM_I1 = ISTR("MMU unit not responding. Check the wiring and connectors. If the issue persists, contact support."); ////MSG_DESC_MMU_NOT_RESPONDING c=20 r=8
+static const char MSG_DESC_COMMUNICATION_ERROR[] PROGMEM_I1 = ISTR("MMU unit not responding correctly. Check the wiring and connectors. If the issue persists, contact support."); ////MSG_DESC_COMMUNICATION_ERROR c=20 r=9
+static const char MSG_DESC_FILAMENT_ALREADY_LOADED[] PROGMEM_I1 = ISTR("Cannot perform the action, filament is already loaded. Unload it first."); ////MSG_DESC_FILAMENT_ALREADY_LOADED c=20 r=8
+static const char MSG_DESC_INVALID_TOOL[] PROGMEM_I1 = ISTR("Requested filament tool is not available on this hardware. Check the G-code for tool index out of range (T0-T4)."); ////MSG_DESC_INVALID_TOOL c=20 r=8
+static const char MSG_DESC_QUEUE_FULL[] PROGMEM_I1 = ISTR("MMU Firmware internal error, please reset the MMU."); ////MSG_DESC_QUEUE_FULL c=20 r=8
+static const char MSG_DESC_FW_UPDATE_NEEDED[] PROGMEM_I1 = ISTR("The MMU unit reports its FW version incompatible with the printer's firmware. Make sure the MMU firmware is up to date."); ////MSG_DESC_FW_UPDATE_NEEDED c=20 r=9
+static const char MSG_DESC_FW_RUNTIME_ERROR[] PROGMEM_I1 = ISTR("Internal runtime error. Try resetting the MMU unit or updating the firmware. If the issue persists, contact support."); ////MSG_DESC_FW_RUNTIME_ERROR c=20 r=11
+static const char MSG_DESC_UNLOAD_MANUALLY[] PROGMEM_I1 = ISTR("Unexpected FINDA reading. Ensure no filament is under FINDA and the selector is free. Check FINDA connection."); ////MSG_DESC_UNLOAD_MANUALLY c=20 r=8
+
+static const char * const errorDescs[] PROGMEM = {
+    _R(MSG_DESC_FINDA_DIDNT_TRIGGER),
+    _R(MSG_DESC_FINDA_DIDNT_GO_OFF),
+    _R(MSG_DESC_FSENSOR_DIDNT_TRIGGER),
+    _R(MSG_DESC_FSENSOR_DIDNT_GO_OFF),
+    _R(MSG_DESC_PULLEY_STALLED),
+    _R(MSG_DESC_FSENSOR_TOO_EARLY),
+    _R(MSG_DESC_SELECTOR_CANNOT_HOME),
+    _R(MSG_DESC_CANNOT_MOVE),
+    _R(MSG_DESC_IDLER_CANNOT_HOME),
+    _R(MSG_DESC_CANNOT_MOVE),
+    _R(MSG_DESC_TMC), // descPULLEY_WARNING_TMC_TOO_HOT
+    _R(MSG_DESC_TMC), // descSELECTOR_WARNING_TMC_TOO_HOT
+    _R(MSG_DESC_TMC), // descIDLER_WARNING_TMC_TOO_HOT
+    _R(MSG_DESC_TMC), // descPULLEY_TMC_OVERHEAT_ERROR
+    _R(MSG_DESC_TMC), // descSELECTOR_TMC_OVERHEAT_ERROR
+    _R(MSG_DESC_TMC), // descIDLER_TMC_OVERHEAT_ERROR
+    _R(MSG_DESC_TMC), // descPULLEY_TMC_DRIVER_ERROR
+    _R(MSG_DESC_TMC), // descSELECTOR_TMC_DRIVER_ERROR
+    _R(MSG_DESC_TMC), // descIDLER_TMC_DRIVER_ERROR
+    _R(MSG_DESC_TMC), // descPULLEY_TMC_DRIVER_RESET
+    _R(MSG_DESC_TMC), // descSELECTOR_TMC_DRIVER_RESET
+    _R(MSG_DESC_TMC), // descIDLER_TMC_DRIVER_RESET
+    _R(MSG_DESC_TMC), // descPULLEY_TMC_UNDERVOLTAGE_ERROR
+    _R(MSG_DESC_TMC), // descSELECTOR_TMC_UNDERVOLTAGE_ERROR
+    _R(MSG_DESC_TMC), // descIDLER_TMC_UNDERVOLTAGE_ERROR
+    _R(MSG_DESC_TMC), // descPULLEY_TMC_DRIVER_SHORTED
+    _R(MSG_DESC_TMC), // descSELECTOR_TMC_DRIVER_SHORTED
+    _R(MSG_DESC_TMC), // descIDLER_TMC_DRIVER_SHORTED
+    _R(MSG_DESC_MMU_NOT_RESPONDING),
+    _R(MSG_DESC_COMMUNICATION_ERROR),
+    _R(MSG_DESC_FILAMENT_ALREADY_LOADED),
+    _R(MSG_DESC_INVALID_TOOL),
+    _R(MSG_DESC_QUEUE_FULL),
+    _R(MSG_DESC_FW_UPDATE_NEEDED),
+    _R(MSG_DESC_FW_RUNTIME_ERROR),
+    _R(MSG_DESC_UNLOAD_MANUALLY)
+};
+
+// 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.
+// 01234567890123456789
+// >bttxt >bttxt>MoreW
+// Therefore at least some of the buttons, which can occur on the screen together, need to be 5-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)
+static const char MSG_BTN_RETRY[] PROGMEM_I1 = ISTR("Retry"); ////MSG_BTN_RETRY c=5
+static const char MSG_BTN_CONTINUE[] PROGMEM_I1 = ISTR("Done"); ////MSG_BTN_CONTINUE c=5
+static const char MSG_BTN_RESTART_MMU[] PROGMEM_I1 = ISTR("Reset MMU"); ////MSG_BTN_RESTART_MMU c=9
+static const char MSG_BTN_UNLOAD[] PROGMEM_I1 = ISTR("Unload"); ////MSG_BTN_UNLOAD c=6
+static const char MSG_BTN_STOP[] PROGMEM_I1 = ISTR("Stop"); ////MSG_BTN_STOP c=5
+static const char MSG_BTN_DISABLE_MMU[] PROGMEM_I1 = ISTR("Disable"); ////MSG_BTN_DISABLE_MMU c=9
+static const char MSG_BTN_MORE[] PROGMEM_I1 = ISTR("More\x06"); ////MSG_BTN_MORE c=5
+
+// Used to parse the buttons from Btns().
+static const char * const btnOperation[] PROGMEM = {
+    _R(MSG_BTN_RETRY),
+    _R(MSG_BTN_CONTINUE),
+    _R(MSG_BTN_RESTART_MMU),
+    _R(MSG_BTN_UNLOAD),
+    _R(MSG_BTN_STOP),
+    _R(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::Continue),//FINDA_DIDNT_TRIGGER
+    Btns(ButtonOperations::Retry, ButtonOperations::Continue),//FINDA_DIDNT_GO_OFF
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//FSENSOR_DIDNT_TRIGGER
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//FSENSOR_DIDNT_GO_OFF
+
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//PULLEY_STALLED
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//FSENSOR_TOO_EARLY
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//SELECTOR_CANNOT_HOME
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//SELECTOR_CANNOT_MOVE
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//IDLER_CANNOT_HOME
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//IDLER_CANNOT_MOVE
+
+    Btns(ButtonOperations::Continue, ButtonOperations::RestartMMU),//PULLEY_WARNING_TMC_TOO_HOT
+    Btns(ButtonOperations::Continue, ButtonOperations::RestartMMU),//SELECTOR_WARNING_TMC_TOO_HOT
+    Btns(ButtonOperations::Continue, ButtonOperations::RestartMMU),//IDLER_WARNING_TMC_TOO_HOT
+
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_OVERHEAT_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_OVERHEAT_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_OVERHEAT_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_DRIVER_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_DRIVER_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_DRIVER_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_DRIVER_RESET
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_DRIVER_RESET
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_DRIVER_RESET
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_UNDERVOLTAGE_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_UNDERVOLTAGE_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_UNDERVOLTAGE_ERROR
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_DRIVER_SHORTED
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_DRIVER_SHORTED
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_DRIVER_SHORTED
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//MMU_NOT_RESPONDING
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//COMMUNICATION_ERROR
+
+    Btns(ButtonOperations::Unload, ButtonOperations::Continue),//FILAMENT_ALREADY_LOADED
+    Btns(ButtonOperations::StopPrint, ButtonOperations::RestartMMU),//INVALID_TOOL
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//QUEUE_FULL
+    Btns(ButtonOperations::DisableMMU, ButtonOperations::NoOperation),//FW_UPDATE_NEEDED
+    Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//FW_RUNTIME_ERROR
+    Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//UNLOAD_MANUALLY
+};
+
+static_assert( sizeof(errorCodes) / sizeof(errorCodes[0]) == sizeof(errorDescs) / sizeof (errorDescs[0]));
+static_assert( sizeof(errorCodes) / sizeof(errorCodes[0]) == sizeof(errorTitles) / sizeof (errorTitles[0]));
+static_assert( sizeof(errorCodes) / sizeof(errorCodes[0]) == sizeof(errorButtons) / sizeof (errorButtons[0]));
+
+} // namespace MMU2

+ 45 - 0
Firmware/mmu2/progress_codes.h

@@ -0,0 +1,45 @@
+/// @file progress_codes.h
+#pragma once
+#include <stdint.h>
+
+/// A complete set of progress codes which may be reported while running a high-level command/operation
+/// This header file shall be included in the printer's firmware as well as a reference,
+/// therefore the progress codes have been 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
+};

+ 22 - 0
Firmware/mmu2_crc.cpp

@@ -0,0 +1,22 @@
+/// @file
+#include "mmu2_crc.h"
+
+#ifdef __AVR__
+#include <util/crc16.h>
+#endif
+
+namespace modules {
+namespace crc {
+
+#ifdef __AVR__
+uint8_t CRC8::CCITT_update(uint8_t crc, uint8_t b) {
+    return _crc8_ccitt_update(crc, b);
+}
+#else
+uint8_t CRC8::CCITT_update(uint8_t crc, uint8_t b) {
+    return CCITT_updateCX(crc, b);
+}
+#endif
+
+} // namespace crc
+} // namespace modules

+ 43 - 0
Firmware/mmu2_crc.h

@@ -0,0 +1,43 @@
+/// @file
+#pragma once
+#include <stdint.h>
+
+namespace modules {
+
+/// 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

+ 294 - 0
Firmware/mmu2_error_converter.cpp

@@ -0,0 +1,294 @@
+#include "mmu2_error_converter.h"
+#include "mmu2/error_codes.h"
+#include "mmu2/errors_list.h"
+#include "language.h"
+#include <stdio.h>
+
+namespace MMU2 {
+
+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 ConvertMMUErrorCode
+// 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) {
+    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_DIDNT_GO_OFF) == 1);
+static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER) == 2);
+static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF) == 3);
+
+uint8_t PrusaErrorCodeIndex(uint16_t ec) {
+    switch (ec) {
+    case (uint16_t)ErrorCode::FINDA_DIDNT_SWITCH_ON:
+        return FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_TRIGGER);
+    case (uint16_t)ErrorCode::FINDA_DIDNT_SWITCH_OFF:
+        return FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_GO_OFF);
+    case (uint16_t)ErrorCode::FSENSOR_DIDNT_SWITCH_ON:
+        return FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER);
+    case (uint16_t)ErrorCode::FSENSOR_DIDNT_SWITCH_OFF:
+        return FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF);
+    case (uint16_t)ErrorCode::FSENSOR_TOO_EARLY:
+        return FindErrorIndex(ERR_MECHANICAL_FSENSOR_TOO_EARLY);
+        
+    case (uint16_t)ErrorCode::STALLED_PULLEY:
+    case (uint16_t)ErrorCode::MOVE_PULLEY_FAILED:
+        return FindErrorIndex(ERR_MECHANICAL_PULLEY_CANNOT_MOVE);
+        
+    case (uint16_t)ErrorCode::HOMING_SELECTOR_FAILED:
+        return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_HOME);
+    case (uint16_t)ErrorCode::MOVE_SELECTOR_FAILED:
+        return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_MOVE);
+        
+    case (uint16_t)ErrorCode::HOMING_IDLER_FAILED:
+        return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_HOME);
+    case (uint16_t)ErrorCode::MOVE_IDLER_FAILED:
+        return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_MOVE);
+        
+    case (uint16_t)ErrorCode::MMU_NOT_RESPONDING:
+        return FindErrorIndex(ERR_CONNECT_MMU_NOT_RESPONDING);
+    case (uint16_t)ErrorCode::PROTOCOL_ERROR:
+        return FindErrorIndex(ERR_CONNECT_COMMUNICATION_ERROR);
+    case (uint16_t)ErrorCode::FILAMENT_ALREADY_LOADED:
+        return FindErrorIndex(ERR_SYSTEM_FILAMENT_ALREADY_LOADED);
+    case (uint16_t)ErrorCode::INVALID_TOOL:
+        return FindErrorIndex(ERR_SYSTEM_INVALID_TOOL);
+    case (uint16_t)ErrorCode::QUEUE_FULL:
+        return FindErrorIndex(ERR_SYSTEM_QUEUE_FULL);
+    case (uint16_t)ErrorCode::VERSION_MISMATCH:
+        return FindErrorIndex(ERR_SYSTEM_FW_UPDATE_NEEDED);
+    case (uint16_t)ErrorCode::INTERNAL:
+        return FindErrorIndex(ERR_SYSTEM_FW_RUNTIME_ERROR);
+    case (uint16_t)ErrorCode::FINDA_VS_EEPROM_DISREPANCY:
+        return FindErrorIndex(ERR_SYSTEM_UNLOAD_MANUALLY);
+    }
+
+    // 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 (ec & (uint16_t)ErrorCode::TMC_PULLEY_BIT) {
+        if (ec & (uint16_t)ErrorCode::TMC_IOIN_MISMATCH)
+            return FindErrorIndex(ERR_ELECTRICAL_PULLEY_TMC_DRIVER_ERROR);
+        if (ec & (uint16_t)ErrorCode::TMC_RESET)
+            return FindErrorIndex(ERR_ELECTRICAL_PULLEY_TMC_DRIVER_RESET);
+        if (ec & (uint16_t)ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)
+            return FindErrorIndex(ERR_ELECTRICAL_PULLEY_TMC_UNDERVOLTAGE_ERROR);
+        if (ec & (uint16_t)ErrorCode::TMC_SHORT_TO_GROUND)
+            return FindErrorIndex(ERR_ELECTRICAL_PULLEY_TMC_DRIVER_SHORTED);
+        if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_WARN)
+            return FindErrorIndex(ERR_TEMPERATURE_PULLEY_WARNING_TMC_TOO_HOT);
+        if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_ERROR)
+            return FindErrorIndex(ERR_TEMPERATURE_PULLEY_TMC_OVERHEAT_ERROR);
+    } else if (ec & (uint16_t)ErrorCode::TMC_SELECTOR_BIT) {
+        if (ec & (uint16_t)ErrorCode::TMC_IOIN_MISMATCH)
+            return FindErrorIndex(ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_ERROR);
+        if (ec & (uint16_t)ErrorCode::TMC_RESET)
+            return FindErrorIndex(ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_RESET);
+        if (ec & (uint16_t)ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)
+            return FindErrorIndex(ERR_ELECTRICAL_SELECTOR_TMC_UNDERVOLTAGE_ERROR);
+        if (ec & (uint16_t)ErrorCode::TMC_SHORT_TO_GROUND)
+            return FindErrorIndex(ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_SHORTED);
+        if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_WARN)
+            return FindErrorIndex(ERR_TEMPERATURE_SELECTOR_WARNING_TMC_TOO_HOT);
+        if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_ERROR)
+            return FindErrorIndex(ERR_TEMPERATURE_SELECTOR_TMC_OVERHEAT_ERROR);
+    } else if (ec & (uint16_t)ErrorCode::TMC_IDLER_BIT) {
+        if (ec & (uint16_t)ErrorCode::TMC_IOIN_MISMATCH)
+            return FindErrorIndex(ERR_ELECTRICAL_IDLER_TMC_DRIVER_ERROR);
+        if (ec & (uint16_t)ErrorCode::TMC_RESET)
+            return FindErrorIndex(ERR_ELECTRICAL_IDLER_TMC_DRIVER_RESET);
+        if (ec & (uint16_t)ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)
+            return FindErrorIndex(ERR_ELECTRICAL_IDLER_TMC_UNDERVOLTAGE_ERROR);
+        if (ec & (uint16_t)ErrorCode::TMC_SHORT_TO_GROUND)
+            return FindErrorIndex(ERR_ELECTRICAL_IDLER_TMC_DRIVER_SHORTED);
+        if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_WARN)
+            return FindErrorIndex(ERR_TEMPERATURE_IDLER_WARNING_TMC_TOO_HOT);
+        if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_ERROR)
+            return FindErrorIndex(ERR_TEMPERATURE_IDLER_TMC_OVERHEAT_ERROR);
+    }
+
+    // if nothing got caught, return a generic runtime error
+    return FindErrorIndex(ERR_SYSTEM_FW_RUNTIME_ERROR);
+}
+
+uint16_t PrusaErrorCode(uint8_t i){
+    return pgm_read_word(errorCodes + i);
+}
+
+const char * const PrusaErrorTitle(uint8_t i){
+    return (const char * const)pgm_read_ptr(errorTitles + i);
+}
+
+const char * const PrusaErrorDesc(uint8_t i){
+    return (const char * const)pgm_read_ptr(errorDescs + i);
+}
+
+uint8_t PrusaErrorButtons(uint8_t i){
+    return pgm_read_byte(errorButtons + i);
+}
+
+const char * const PrusaErrorButtonTitle(uint8_t bi){
+    // -1 represents the hidden NoOperation button which is not drawn in any way
+    return (const char * const)pgm_read_ptr(btnOperation + bi - 1);
+}
+
+const char * const PrusaErrorButtonMore(){
+    return _R(MSG_BTN_MORE);
+}
+
+struct ResetOnExit {
+    ResetOnExit() = default;
+    ~ResetOnExit(){
+        buttonSelectedOperation = ButtonOperations::NoOperation;
+    }
+};
+
+Buttons ButtonPressed(uint16_t ec) {
+    if (buttonSelectedOperation == ButtonOperations::NoOperation) {
+        return NoButton; // no button
+    }
+    
+    ResetOnExit ros; // clear buttonSelectedOperation on exit from this call
+    return ButtonAvailable(ec);
+}
+
+Buttons ButtonAvailable(uint16_t 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 restart MMU)
+    // Based on Prusa-Error-Codes errors_list.h
+    // So far hardcoded, but shall be generated in the future
+    switch ( PrusaErrorCode(ei) ) {
+    case ERR_MECHANICAL_FINDA_DIDNT_TRIGGER:
+    case ERR_MECHANICAL_FINDA_DIDNT_GO_OFF:
+        switch (buttonSelectedOperation) {
+        case ButtonOperations::Retry: // "Repeat action"
+            return Middle;
+        case ButtonOperations::Continue: // "Continue"
+            return Right;
+        default:
+            break;
+        }
+        break;
+    case ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER:
+    case ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF:
+    case ERR_MECHANICAL_FSENSOR_TOO_EARLY:
+    case ERR_MECHANICAL_SELECTOR_CANNOT_HOME:
+    case ERR_MECHANICAL_SELECTOR_CANNOT_MOVE:
+    case ERR_MECHANICAL_IDLER_CANNOT_HOME:
+    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 Middle;
+        default:
+            break;
+        }
+        break;
+        
+    case ERR_TEMPERATURE_PULLEY_WARNING_TMC_TOO_HOT:
+    case ERR_TEMPERATURE_SELECTOR_WARNING_TMC_TOO_HOT:
+    case ERR_TEMPERATURE_IDLER_WARNING_TMC_TOO_HOT:
+        switch (buttonSelectedOperation) {
+        case ButtonOperations::Continue: // "Continue"
+            return Left;
+        case ButtonOperations::RestartMMU: // "Restart MMU"
+            return RestartMMU;
+        default:
+            break;
+        }
+        break;
+        
+    case ERR_TEMPERATURE_PULLEY_TMC_OVERHEAT_ERROR:
+    case ERR_TEMPERATURE_SELECTOR_TMC_OVERHEAT_ERROR:
+    case ERR_TEMPERATURE_IDLER_TMC_OVERHEAT_ERROR:
+        
+    case ERR_ELECTRICAL_PULLEY_TMC_DRIVER_ERROR:
+    case ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_ERROR:
+    case ERR_ELECTRICAL_IDLER_TMC_DRIVER_ERROR:
+        
+    case ERR_ELECTRICAL_PULLEY_TMC_DRIVER_RESET:
+    case ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_RESET:
+    case ERR_ELECTRICAL_IDLER_TMC_DRIVER_RESET:
+        
+    case ERR_ELECTRICAL_PULLEY_TMC_UNDERVOLTAGE_ERROR:
+    case ERR_ELECTRICAL_SELECTOR_TMC_UNDERVOLTAGE_ERROR:
+    case ERR_ELECTRICAL_IDLER_TMC_UNDERVOLTAGE_ERROR:
+        
+    case ERR_ELECTRICAL_PULLEY_TMC_DRIVER_SHORTED:
+    case ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_SHORTED:
+    case ERR_ELECTRICAL_IDLER_TMC_DRIVER_SHORTED:
+        
+    case ERR_CONNECT_MMU_NOT_RESPONDING:
+    case ERR_CONNECT_COMMUNICATION_ERROR:
+        
+    case ERR_SYSTEM_QUEUE_FULL:
+    case ERR_SYSTEM_FW_RUNTIME_ERROR:
+        switch (buttonSelectedOperation) {
+        case ButtonOperations::RestartMMU: // "Restart MMU"
+            return RestartMMU;
+        default:
+            break;
+        }
+        break;
+    case ERR_SYSTEM_FW_UPDATE_NEEDED:
+        switch (buttonSelectedOperation) {
+        case ButtonOperations::DisableMMU: // "Disable"
+            return DisableMMU;
+        default:
+            break;
+        }
+        break;
+    case ERR_SYSTEM_FILAMENT_ALREADY_LOADED:
+        switch (buttonSelectedOperation) {
+        case ButtonOperations::Unload: // "Unload"
+            return Left;
+        case ButtonOperations::Continue: // "Proceed/Continue"
+            return Right;
+        default:
+            break;
+        }
+        break;
+        
+    case ERR_SYSTEM_INVALID_TOOL:
+        switch (buttonSelectedOperation) {
+        case ButtonOperations::StopPrint: // "Stop print"
+            return StopPrint;
+        case ButtonOperations::RestartMMU: // "Restart MMU"
+            return RestartMMU;
+        default:
+            break;
+        }
+        break;
+    default:
+        break;
+    }
+    
+    return NoButton;
+}
+
+void SetButtonResponse(ButtonOperations rsp){
+    buttonSelectedOperation = rsp;
+}
+
+} // namespace MMU2

+ 48 - 0
Firmware/mmu2_error_converter.h

@@ -0,0 +1,48 @@
+#pragma once
+#include <stdint.h>
+#include <stddef.h>
+#include "mmu2/buttons.h"
+
+namespace MMU2 {
+
+/// Translates MMU2::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(uint16_t ec);
+
+/// @returns pointer to a PROGMEM string representing the Title of the Prusa-Error-Codes error
+/// @param i index of the error - obtained by calling ErrorCodeIndex
+const char * const PrusaErrorTitle(uint8_t i);
+
+/// @returns 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
+const char * const PrusaErrorDesc(uint8_t i);
+
+/// @returns the actual numerical value of the Prusa-Error-Codes error
+/// @param i index of the error - obtained by calling ErrorCodeIndex
+uint16_t PrusaErrorCode(uint8_t i);
+
+/// @returns Btns pair of buttons for a particular Prusa-Error-Codes error
+/// @param i index of the error - obtained by calling ErrorCodeIndex
+uint8_t PrusaErrorButtons(uint8_t i);
+
+/// @returns 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
+const char * const PrusaErrorButtonTitle(uint8_t bi);
+
+/// @returns pointer to a PROGMEM string representing the "More" button
+const char * const PrusaErrorButtonMore();
+
+/// Sets the selected button for later pick-up by the MMU state machine.
+/// Used to save the GUI selection/decoupling
+void SetButtonResponse(ButtonOperations rsp);
+
+/// @returns button index/code based on currently processed error/screen
+/// Clears the "pressed" button upon exit
+Buttons ButtonPressed(uint16_t ec);
+
+/// @returns 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(uint16_t ec);
+
+} // namespace MMU2

+ 10 - 0
Firmware/mmu2_fsensor.cpp

@@ -0,0 +1,10 @@
+#include "mmu2_fsensor.h"
+#include "Filament_sensor.h"
+
+namespace MMU2 {
+
+FilamentState WhereIsFilament(){
+    return fsensor.getFilamentPresent() ? FilamentState::AT_FSENSOR : FilamentState::NOT_PRESENT;
+}
+
+} // namespace MMU2

+ 17 - 0
Firmware/mmu2_fsensor.h

@@ -0,0 +1,17 @@
+#pragma once
+#include <stdint.h>
+#include "Filament_sensor.h"
+
+namespace MMU2 {
+
+/// 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
+};
+
+FilamentState WhereIsFilament();
+
+} // namespace MMU2

+ 17 - 0
Firmware/mmu2_log.cpp

@@ -0,0 +1,17 @@
+#include "mmu2_log.h"
+
+namespace MMU2 {
+
+void LogErrorEvent_P(const char *msg){
+    SERIAL_ERROR_START;
+    SERIAL_MMU2();
+    SERIAL_ECHOLNRPGM(msg);
+}
+
+void LogEchoEvent_P(const char *msg){
+    SERIAL_ECHO_START;
+    SERIAL_MMU2();
+    SERIAL_ECHOLNRPGM(msg);
+}
+
+} // namespace MMU2

+ 39 - 0
Firmware/mmu2_log.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#ifndef UNITTEST
+#include "Marlin.h"
+
+// Beware - before changing this prefix, think twice
+// you'd need to change appmain.cpp app_marlin_serial_output_write_hook
+// and MMU2::ReportError + MMU2::ReportProgress
+static constexpr char mmu2Magic[] PROGMEM = "MMU2:";
+
+namespace MMU2 {
+
+/// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
+/// @param msg pointer to a string in PROGMEM
+void LogErrorEvent_P(const char *msg);
+
+/// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
+/// @param msg pointer to a string in PROGMEM
+void LogEchoEvent_P(const char *msg);
+
+} // namespace
+
+#define SERIAL_MMU2() { serialprintPGM(mmu2Magic); }
+
+#define MMU2_ECHO_MSG(S) do{ SERIAL_ECHO_START; SERIAL_MMU2(); SERIAL_ECHO(S); }while(0)
+#define MMU2_ERROR_MSG(S) do{ SERIAL_ERROR_START; SERIAL_MMU2(); SERIAL_ECHO(S); }while(0)
+#define MMU2_ECHO_MSGRPGM(S) do{ SERIAL_ECHO_START; SERIAL_MMU2(); SERIAL_ECHORPGM(S); }while(0)
+#define MMU2_ERROR_MSGRPGM(S) do{ SERIAL_ERROR_START; SERIAL_MMU2(); SERIAL_ECHORPGM(S); }while(0)
+
+#else // #ifndef UNITTEST
+
+#define MMU2_ECHO_MSG(S) /* */
+#define MMU2_ERROR_MSG(S) /* */
+#define SERIAL_ECHO(S) /* */
+#define SERIAL_ECHOLN(S) /* */
+#define MMU2_ECHO_MSGRPGM(S) /* */
+#define MMU2_ERROR_MSGRPGM(S) /* */
+
+#endif // #ifndef UNITTEST

+ 34 - 0
Firmware/mmu2_power.cpp

@@ -0,0 +1,34 @@
+#include "mmu2_power.h"
+#include "Configuration_prusa.h"
+#include "pins.h"
+#include "fastio.h"
+#include <util/delay.h>
+#include "mmu2.h"
+#include "eeprom.h"
+
+namespace MMU2 {
+
+// sadly, on MK3 we cannot do actual power cycle on HW...
+// so we just block the MMU via EEPROM var instead.
+void power_on()
+{
+    eeprom_update_byte((uint8_t *)EEPROM_MMU_ENABLED, true);
+}
+
+void power_off()
+{
+    eeprom_update_byte((uint8_t *)EEPROM_MMU_ENABLED, false);
+}
+
+void reset() {
+#ifdef MMU_HWRESET // HW - pulse reset pin
+    WRITE(MMU_RST_PIN, 0);
+    _delay_us(100);
+    WRITE(MMU_RST_PIN, 1);
+#else
+    mmu2.Reset(MMU2::Software); // @@TODO needs to be redesigned, this power implementation shall not know anything about the MMU itself
+#endif
+    // otherwise HW reset is not available
+}
+
+} // namespace MMU2

+ 11 - 0
Firmware/mmu2_power.h

@@ -0,0 +1,11 @@
+#pragma once
+
+namespace MMU2 {
+
+void power_on();
+
+void power_off();
+
+void reset();
+
+} // namespace MMU2

+ 72 - 0
Firmware/mmu2_progress_converter.cpp

@@ -0,0 +1,72 @@
+#include "mmu2_progress_converter.h"
+#include "language.h"
+#include "mmu2/progress_codes.h"
+#include <avr/pgmspace.h>
+
+namespace MMU2 {
+                                                                   //01234567890123456789
+static const char MSG_PROGRESS_OK[] PROGMEM_I1               = ISTR("OK"); ////MSG_PROGRESS_OK c=4
+static const char MSG_PROGRESS_ENGAGE_IDLER[] PROGMEM_I1     = ISTR("Engaging idler"); ////MSG_PROGRESS_ENGAGE_IDLER c=20
+static const char MSG_PROGRESS_DISENGAGE_IDLER[] PROGMEM_I1  = ISTR("Disengaging idler"); ////MSG_PROGRESS_DISENGAGE_IDLER c=20
+static const char MSG_PROGRESS_UNLOAD_FINDA[] PROGMEM_I1     = ISTR("Unloading to FINDA"); ////MSG_PROGRESS_UNLOAD_FINDA c=20
+static const char MSG_PROGRESS_UNLOAD_PULLEY[] PROGMEM_I1    = ISTR("Unloading to pulley"); ////MSG_PROGRESS_UNLOAD_PULLEY c=20
+static const char MSG_PROGRESS_FEED_FINDA[] PROGMEM_I1       = ISTR("Feeding to FINDA"); ////MSG_PROGRESS_FEED_FINDA c=20
+static const char MSG_PROGRESS_FEED_EXTRUDER[] PROGMEM_I1    = ISTR("Feeding to extruder"); ////MSG_PROGRESS_FEED_EXTRUDER c=20
+static const char MSG_PROGRESS_FEED_NOZZLE[] PROGMEM_I1      = ISTR("Feeding to nozzle"); ////MSG_PROGRESS_FEED_NOZZLE c=20
+static const char MSG_PROGRESS_AVOID_GRIND[] PROGMEM_I1      = ISTR("Avoiding grind"); ////MSG_PROGRESS_AVOID_GRIND c=20
+static const char MSG_PROGRESS_WAIT_USER[] PROGMEM_I1        = ISTR("ERR Wait for User"); ////MSG_PROGRESS_WAIT_USER c=20
+static const char MSG_PROGRESS_ERR_INTERNAL[] PROGMEM_I1     = ISTR("ERR Internal"); ////MSG_PROGRESS_ERR_INTERNAL c=20
+static const char MSG_PROGRESS_ERR_HELP_FIL[] PROGMEM_I1     = ISTR("ERR Help filament"); ////MSG_PROGRESS_ERR_HELP_FIL c=20
+static const char MSG_PROGRESS_ERR_TMC[] PROGMEM_I1          = ISTR("ERR TMC failed"); ////MSG_PROGRESS_ERR_TMC c=20
+static const char MSG_PROGRESS_SELECT_SLOT[] PROGMEM_I1      = ISTR("Selecting fil. slot"); ////MSG_PROGRESS_SELECT_SLOT c=20
+static const char MSG_PROGRESS_PREPARE_BLADE[] PROGMEM_I1    = ISTR("Preparing blade"); ////MSG_PROGRESS_PREPARE_BLADE c=20
+static const char MSG_PROGRESS_PUSH_FILAMENT[] PROGMEM_I1    = ISTR("Pushing filament"); ////MSG_PROGRESS_PUSH_FILAMENT c=20
+static const char MSG_PROGRESS_PERFORM_CUT[] PROGMEM_I1      = ISTR("Performing cut"); ////MSG_PROGRESS_PERFORM_CUT c=20
+static const char MSG_PROGRESS_RETURN_SELECTOR[] PROGMEM_I1  = ISTR("Returning selector"); ////MSG_PROGRESS_RETURN_SELECTOR c=20
+static const char MSG_PROGRESS_PARK_SELECTOR[] PROGMEM_I1    = ISTR("Parking selector"); ////MSG_PROGRESS_PARK_SELECTOR c=20
+static const char MSG_PROGRESS_EJECT_FILAMENT[] PROGMEM_I1   = ISTR("Ejecting filament"); ////MSG_PROGRESS_EJECT_FILAMENT c=20 //@@todo duplicate
+static const char MSG_PROGRESS_RETRACT_FINDA[] PROGMEM_I1    = ISTR("Retract from FINDA"); ////MSG_PROGRESS_RETRACT_FINDA c=20
+static const char MSG_PROGRESS_HOMING[] PROGMEM_I1           = ISTR("Homing"); ////MSG_PROGRESS_HOMING c=20
+static const char MSG_PROGRESS_MOVING_SELECTOR[] PROGMEM_I1  = ISTR("Moving selector"); ////MSG_PROGRESS_MOVING_SELECTOR c=20
+static const char MSG_PROGRESS_FEED_FSENSOR[] PROGMEM_I1     = ISTR("Feeding to FSensor"); ////MSG_PROGRESS_FEED_FSENSOR c=20
+
+static const char * const progressTexts[] PROGMEM = {
+    _R(MSG_PROGRESS_OK),
+    _R(MSG_PROGRESS_ENGAGE_IDLER),
+    _R(MSG_PROGRESS_DISENGAGE_IDLER),
+    _R(MSG_PROGRESS_UNLOAD_FINDA),
+    _R(MSG_PROGRESS_UNLOAD_PULLEY),
+    _R(MSG_PROGRESS_FEED_FINDA),
+    _R(MSG_PROGRESS_FEED_EXTRUDER),
+    _R(MSG_PROGRESS_FEED_NOZZLE),
+    _R(MSG_PROGRESS_AVOID_GRIND),
+    _R(MSG_FINISHING_MOVEMENTS), //reuse from messages.cpp
+    _R(MSG_PROGRESS_DISENGAGE_IDLER), // err disengaging idler is the same text
+    _R(MSG_PROGRESS_ENGAGE_IDLER), // engage dtto.
+    _R(MSG_PROGRESS_WAIT_USER),
+    _R(MSG_PROGRESS_ERR_INTERNAL),
+    _R(MSG_PROGRESS_ERR_HELP_FIL),
+    _R(MSG_PROGRESS_ERR_TMC),
+    _R(MSG_UNLOADING_FILAMENT), //reuse from messages.cpp
+    _R(MSG_LOADING_FILAMENT), //reuse from messages.cpp
+    _R(MSG_PROGRESS_SELECT_SLOT),
+    _R(MSG_PROGRESS_PREPARE_BLADE),
+    _R(MSG_PROGRESS_PUSH_FILAMENT),
+    _R(MSG_PROGRESS_PERFORM_CUT),
+    _R(MSG_PROGRESS_RETURN_SELECTOR),
+    _R(MSG_PROGRESS_PARK_SELECTOR),
+    _R(MSG_PROGRESS_EJECT_FILAMENT),
+    _R(MSG_PROGRESS_RETRACT_FINDA),
+    _R(MSG_PROGRESS_HOMING),
+    _R(MSG_PROGRESS_MOVING_SELECTOR),
+    _R(MSG_PROGRESS_FEED_FSENSOR)
+};
+
+const char * const ProgressCodeToText(uint16_t pc){
+    // @@TODO ?? a better fallback option?
+    return ( pc <= (sizeof(progressTexts) / sizeof(progressTexts[0])) )
+       ? static_cast<const char * const>(pgm_read_ptr(&progressTexts[pc]))
+       : static_cast<const char * const>(pgm_read_ptr(&progressTexts[0]));
+}
+
+} // namespace MMU2

+ 9 - 0
Firmware/mmu2_progress_converter.h

@@ -0,0 +1,9 @@
+#pragma once
+#include <stdint.h>
+#include <stddef.h>
+
+namespace MMU2 {
+
+const char * const ProgressCodeToText(uint16_t pc);
+
+}

+ 366 - 0
Firmware/mmu2_protocol.cpp

@@ -0,0 +1,366 @@
+/// @file
+#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;
+            }
+        }
+    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.CRC(), 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.CRC(), 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.CRC(), 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.CRC(), 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.CRC(), 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

+ 324 - 0
Firmware/mmu2_protocol.h

@@ -0,0 +1,324 @@
+/// @file protocol.h
+#pragma once
+#include <stdint.h>
+#include "mmu2_crc.h"
+
+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 CRC() 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 CRC() 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:
+    inline 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
+    /// @returns state of the message being decoded
+    DecodeStatus DecodeRequest(uint8_t c);
+
+    /// Decodes response message in rxbuff
+    /// @returns 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
+    /// @returns 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
+    /// @returns number of bytes written into txbuff
+    static uint8_t EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff);
+
+    /// @returns 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; }
+
+    /// @returns 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
+    /// @returns 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
+    /// @returns number of bytes written into txbuff
+    static uint8_t EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, 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
+    /// @returns 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
+    /// @returns number of bytes written into txbuff
+    static uint8_t EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff);
+
+    /// @returns the most recently lexed request message
+    inline const RequestMsg GetRequestMsg() const { return requestMsg; }
+
+    /// @returns 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':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+            return c - '0';
+        case 'a':
+        case 'b':
+        case 'c':
+        case 'd':
+        case 'e':
+        case 'f':
+            return c - 'a' + 10;
+        default:
+            return 0;
+        }
+    }
+
+    static constexpr uint8_t Nibble2Char(uint8_t n) {
+        switch (n) {
+        case 0:
+        case 1:
+        case 2:
+        case 3:
+        case 4:
+        case 5:
+        case 6:
+        case 7:
+        case 8:
+        case 9:
+            return n + '0';
+        case 0xa:
+        case 0xb:
+        case 0xc:
+        case 0xd:
+        case 0xe:
+        case 0xf:
+            return n - 10 + 'a';
+        default:
+            return 0;
+        }
+    }
+
+    /// @returns number of characters written
+    static uint8_t UInt8ToHex(uint8_t value, uint8_t *dst);
+
+    /// @returns 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
+
+namespace mp = modules::protocol;

+ 756 - 0
Firmware/mmu2_protocol_logic.cpp

@@ -0,0 +1,756 @@
+#include "mmu2_protocol_logic.h"
+#include "mmu2_log.h"
+#include "mmu2_fsensor.h"
+#include "system_timer.h"
+#include <string.h>
+
+namespace MMU2 {
+
+static const uint8_t supportedMmuFWVersion[3] PROGMEM = { 2, 1, 3 };
+
+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::SendFINDAQuery() {
+    SendMsg(RequestMsg(RequestMsgCodes::Finda, 0));
+    scopeState = ScopeState::FINDAReqSent;
+}
+
+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 };
+    
+    /// @returns 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::MatchingPart;
+        } else if(ok == 2 && c == '\n'){
+            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 = uart->read()) >= 0) {
+        ++bytesConsumed;
+        RecordReceivedByte(c);
+        switch (protocol.DecodeResponse(c)) {
+        case DecodeStatus::MessageCompleted:
+            rsp = protocol.GetResponseMsg();
+            LogResponse();
+            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 ){
+                // hack bad FW version - BEWARE - we silently assume that the first query is an "S0"
+                // The old MMU FW responds with "ok\n" and we fake the response to a bad FW version at this spot
+                rsp = ResponseMsg(RequestMsg(RequestMsgCodes::Version, 0), ResponseMsgParamCodes::Accepted, 0);
+                return MessageReady;
+            } else if( old == OldMMUFWDetector::State::MatchingPart ){
+                break;
+            }
+            }
+            [[fallthrough]]; // otherwise
+        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)) {
+        return CommunicationTimeout;
+    }
+    return Processing;
+}
+
+void ProtocolLogic::SendMsg(RequestMsg rq) {
+    uint8_t txbuff[Protocol::MaxRequestSize()];
+    uint8_t len = Protocol::EncodeRequest(rq, txbuff);
+    uart->write(txbuff, len);
+    LogRequestMsg(txbuff, len);
+    RecordUARTActivity();
+}
+
+void ProtocolLogic::SendWriteMsg(RequestMsg rq){
+    uint8_t txbuff[Protocol::MaxRequestSize()];
+    uint8_t len = Protocol::EncodeWriteRequest(rq.value, rq.value2, txbuff);
+    uart->write(txbuff, len);
+    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;
+            } else {
+                SendVersion(stage);
+            }
+        } else {
+            dataTO.Reset(); // 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
+        if (auto expmsg = ExpectingMessage(); expmsg != MessageReady) // this whole statement takes 12B
+            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 - minor
+        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.
+            // For now we just send the state of the filament sensor, but we may request
+            // data point states from the MMU as well. TBD in the future, especially with another protocol
+            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.
+        break;
+    default:
+        return VersionMismatch;
+    }
+    return Finished;
+}
+
+StepStatus ProtocolLogic::DelayedRestartWait() {
+    if (Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on
+        while (uart->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
+        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:
+        progressCode = ProgressCode::OK;
+        scopeState = ScopeState::Ready;
+        return Finished;
+    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:
+        SendFINDAQuery();
+        return Processing;
+    case ScopeState::FINDAReqSent:
+        findaPressed = rsp.paramValue;
+        SendReadRegister(4, ScopeState::StatisticsSent);
+        return Processing;
+    case ScopeState::StatisticsSent:
+        scopeState = ScopeState::Wait;
+        return Processing;
+    case ScopeState::ButtonSent:
+        if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
+            // Button was accepted, decrement the retry.
+            mmu2.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);
+                SendFINDAQuery();
+                return ButtonPushed;
+            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
+                // For now let's behave just like "finished"
+            case ResponseMsgParamCodes::Finished:
+                errorCode = ErrorCode::OK;
+                break;
+            default:
+                errorCode = static_cast<ErrorCode>(rsp.paramValue);
+                SendFINDAQuery(); // continue Idle state without restarting the communication
+                return CommandError;
+            }
+            break;
+        default:
+            return ProtocolError;
+        }
+        SendFINDAQuery();
+        return Processing;
+    case ScopeState::FINDAReqSent:
+        findaPressed = rsp.paramValue;
+        SendReadRegister(4, ScopeState::StatisticsSent);
+        return Processing;
+    case ScopeState::StatisticsSent:
+        failStatistics = rsp.paramValue;
+        scopeState = ScopeState::Ready;
+        return Finished;
+    case ScopeState::ButtonSent:
+        if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
+            // Button was accepted, decrement the retry.
+            mmu2.DecrementRetryAttempts();
+        }
+        SendFINDAQuery();
+        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(MMU2Serial *uart)
+    : 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)
+    , uart(uart)
+    , errorCode(ErrorCode::OK)
+    , progressCode(ProgressCode::OK)
+    , buttonCode(NoButton)
+    , lastFSensor((uint8_t)WhereIsFilament())
+    , findaPressed(false)
+    , failStatistics(0)
+    , mmuFwVersion { 0, 0, 0 }
+{}
+
+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() {
+    PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, 0));
+}
+
+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 0:
+    case 1:
+    case 2:
+    case 3:
+    case 4:
+    case 5:
+    case 6:
+    case 7:
+    case 8:
+    case 9:
+        return c + '0';
+    case 10:
+    case 11:
+    case 12:
+    case 13:
+    case 14:
+    case 15:
+        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];
+        if (b < 32)
+            b = '.';
+        if (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() + 2;
+    char tmp[rqs] = ">";
+    static char lastMsg[rqs] = "";
+    for (uint8_t i = 0; i < size; ++i) {
+        uint8_t b = txbuff[i];
+        if (b < 32)
+            b = '.';
+        if (b > 127)
+            b = '.';
+        tmp[i + 1] = b;
+    }
+    tmp[size + 1] = '\n';
+    tmp[size + 2] = 0;
+    if (!strncmp_P(tmp, PSTR(">S0*99.\n"), 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_MSG(tmp);
+    }
+    memcpy(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_MSG(lrb);
+    SERIAL_ECHOLN();
+}
+
+StepStatus ProtocolLogic::SuppressShortDropOuts(const char *msg_P, StepStatus ss) {
+    if (dataTO.Record(ss)) {
+        LogError(msg_P);
+        return dataTO.InitialCause();
+    } else {
+        return Processing; // suppress short drop outs of communication
+    }
+}
+
+StepStatus ProtocolLogic::HandleCommunicationTimeout() {
+    uart->flush(); // clear the output buffer
+    protocol.ResetResponseDecoder();
+    Start();
+    return SuppressShortDropOuts(PSTR("Communication timeout"), CommunicationTimeout);
+}
+
+StepStatus ProtocolLogic::HandleProtocolError() {
+    uart->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
+        bool previousCommandFinished = currentScope == Scope::Command; // @@TODO this is a nasty hack :(
+        if (!ActivatePlannedRequest()) {                               // if nothing is planned, switch to Idle
+            SwitchToIdle();
+        } else {
+            // if the previous cycle was Idle and now we have planned a new command -> avoid returning Finished
+            if (!previousCommandFinished && currentScope == Scope::Command) {
+                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 shall 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"));
+        Stop(); // cannot continue
+        break;
+    case ProtocolError:
+        currentStatus = HandleProtocolError();
+        break;
+    case CommunicationTimeout:
+        currentStatus = HandleCommunicationTimeout();
+        break;
+    default:
+        break;
+    }
+    return currentStatus;
+}
+
+uint8_t ProtocolLogic::CommandInProgress() const {
+    if (currentScope != Scope::Command)
+        return 0;
+    return (uint8_t)ReqMsg().code;
+}
+
+bool DropOutFilter::Record(StepStatus ss) {
+    if (occurrences == maxOccurrences) {
+        cause = ss;
+    }
+    --occurrences;
+    return occurrences == 0;
+}
+
+} // namespace MMU2

+ 309 - 0
Firmware/mmu2_protocol_logic.h

@@ -0,0 +1,309 @@
+#pragma once
+#include <stdint.h>
+// #include <array> //@@TODO Don't we have STL for AVR somewhere?
+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; }
+    constexpr uint8_t size()const { return N; }
+    inline T &operator[](uint8_t i){
+        return data[i];
+    }
+};
+
+#include "mmu2/error_codes.h"
+#include "mmu2/progress_codes.h"
+#include "mmu2/buttons.h"
+#include "mmu2_protocol.h"
+
+#include "mmu2_serial.h"
+
+/// New MMU2 protocol logic
+namespace MMU2 {
+
+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,
+    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
+    CommunicationRecovered,
+    ButtonPushed, ///< The MMU reported the user pushed one of its three buttons.
+};
+
+static constexpr uint32_t linkLayerTimeout = 2000;                 ///< default link layer communication timeout
+static constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; ///< data layer communication timeout
+static 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 {
+    StepStatus cause;
+    uint8_t occurrences;
+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;
+
+    /// @returns true if the error should be reported to higher levels (max. number of consecutive occurrences reached)
+    bool Record(StepStatus ss);
+
+    /// @returns 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; }
+};
+
+/// Logic layer of the MMU vs. printer communication protocol
+class ProtocolLogic {
+public:
+    ProtocolLogic(MMU2Serial *uart);
+
+    /// 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();
+    void Button(uint8_t index);
+    void Home(uint8_t mode);
+    void ReadRegister(uint8_t address);
+    void WriteRegister(uint8_t address, uint16_t data);
+
+    /// Step the state machine
+    StepStatus Step();
+
+    /// @returns the current/latest error code as reported by the MMU
+    ErrorCode Error() const { return errorCode; }
+
+    /// @returns the current/latest process code as reported by the MMU
+    ProgressCode Progress() const { return progressCode; }
+
+    /// @returns 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 findaPressed;
+    }
+
+    inline uint16_t FailStatistics() const {
+        return failStatistics;
+    }
+
+    inline uint8_t MmuFwVersionMajor() const {
+        return mmuFwVersion[0];
+    }
+
+    inline uint8_t MmuFwVersionMinor() const {
+        return mmuFwVersion[1];
+    }
+
+    inline uint8_t MmuFwVersionRevision() const {
+        return mmuFwVersion[2];
+    }
+#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();
+
+    enum class State : uint_fast8_t {
+        Stopped,      ///< stopped for whatever reason
+        InitSequence, ///< initial sequence running
+        Running       ///< normal operation - Idle + Command processing
+    };
+
+    // individual sub-state machines - may be they can be combined into a union since only one is active at once
+    // or we can blend them into ProtocolLogic at the cost of a less nice code (but hopefully shorter)
+//    Stopped stopped;
+//    StartSeq startSeq;
+//    DelayedRestart delayedRestart;
+//    Idle idle;
+//    Command command;
+//    ProtocolLogicPartBase *currentState; ///< command currently being processed
+    
+    enum class Scope : uint_fast8_t {
+        Stopped,
+        StartSeq,
+        DelayedRestart,
+        Idle,
+        Command
+    };
+    Scope currentScope;
+
+    // basic scope members
+    /// @returns 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,
+        FINDAReqSent,
+        StatisticsSent,
+        ButtonSent,
+        ReadRegisterSent,
+        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
+
+    /// @returns 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);
+
+    /// @returns 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 shall 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 SendFINDAQuery();
+    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(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
+    
+    array<uint8_t, 16> lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes
+    uint8_t lrb;
+
+    MMU2Serial *uart; ///< UART interface
+
+    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
+
+    bool findaPressed;
+    uint16_t failStatistics;
+
+    uint8_t mmuFwVersion[3];
+    uint16_t mmuFwVersionBuild;
+
+    friend class ProtocolLogicPartBase;
+    friend class Stopped;
+    friend class Command;
+    friend class Idle;
+    friend class StartSeq;
+    friend class DelayedRestart;
+
+    friend class MMU2;
+};
+
+} // namespace MMU2

+ 295 - 0
Firmware/mmu2_reporting.cpp

@@ -0,0 +1,295 @@
+#include "mmu2.h"
+#include "mmu2_reporting.h"
+#include "mmu2_error_converter.h"
+#include "mmu2/error_codes.h"
+#include "mmu2/buttons.h"
+#include "ultralcd.h"
+#include "Filament_sensor.h"
+#include "language.h"
+#include "temperature.h"
+#include "sound.h"
+
+namespace MMU2 {
+
+const char * const ProgressCodeToText(uint16_t pc); // we may join progress convertor and reporter together
+
+void BeginReport(CommandInProgress cip, uint16_t ec) {
+    custom_message_type = CustomMsg::MMUProgress;
+    lcd_setstatuspgm( _T(ProgressCodeToText(ec)) );
+}
+
+void EndReport(CommandInProgress cip, uint16_t ec) {
+    // clear the status msg line - let the printed filename get visible again
+    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){
+    // beware - this optimization abuses the fact, that FindaDetectsFilament returns 0 or 1 and '0' is followed by '1' in the ASCII table
+    lcd_putc_at(3, 2, mmu2.FindaDetectsFilament() + '0');
+    lcd_putc_at(8, 2, fsensor.getFilamentPresent() + '0');
+
+    // print active/changing filament slot
+    lcd_set_cursor(10, 2);
+    lcdui_print_extruder();
+
+    // Print active extruder temperature
+    lcd_set_cursor(16, 2);
+    lcd_printf_P(PSTR("%3d"), (int)(degHotend(0) + 0.5));
+}
+
+/**
+ * @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) {
+    //! 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
+    //! |prusa3d.com/ERR04504|     <- URL 20 characters
+    //! |FI:1 FS:1  5>3 t201°|     <- status line, t is thermometer symbol
+    //! |>Retry >Done >MoreW |     <- buttons
+    bool two_choices = false;
+
+    // Read and determine what operations should be shown on the menu
+    const uint8_t button_operation   = PrusaErrorButtons(ei);
+    const uint8_t button_op_right = BUTTON_OP_RIGHT(button_operation);
+    const uint8_t 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;
+    }
+
+    lcd_set_custom_characters_nextpage();
+    lcd_update_enable(false);
+    lcd_clear();
+
+    // Print title and header
+    lcd_printf_P(PSTR("%.20S\nprusa3d.com/ERR04%hu"), _T(PrusaErrorTitle(ei)), PrusaErrorCode(ei) );
+
+    ReportErrorHookSensorLineRender();
+    
+    // Render the choices
+    lcd_show_choices_prompt_P(two_choices ? LCD_LEFT_BUTTON_CHOICE : LCD_MIDDLE_BUTTON_CHOICE, _T(PrusaErrorButtonTitle(button_op_middle)), _T(two_choices ? PrusaErrorButtonMore() : PrusaErrorButtonTitle(button_op_right)), two_choices ? 10 : 7, two_choices ? nullptr : _T(PrusaErrorButtonMore()));
+}
+
+extern void ReportErrorHookSensorLineRender()
+{
+    // Render static characters in third line
+    lcd_set_cursor(0, 2);
+    lcd_printf_P(PSTR("FI:  FS:    >  %c   %c"), LCD_STR_THERMOMETER[0], LCD_STR_DEGREE[0]);
+}
+
+
+/**
+ * @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;
+    bool two_choices = false;
+    static int8_t enc_dif = lcd_encoder_diff;
+
+    if (lcd_encoder_diff == 0)
+    {
+         // lcd_update_enable(true) was called outside ReportErrorHookMonitor
+         // It will set lcd_encoder_diff to 0, sync enc_dif
+        enc_dif = 0;
+    }
+
+    // Read and determine what operations should be shown on the menu
+    const uint8_t button_operation   = PrusaErrorButtons(ei);
+    const uint8_t button_op_right = BUTTON_OP_RIGHT(button_operation);
+    const uint8_t 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;
+    }
+
+    static int8_t current_selection = two_choices ? LCD_LEFT_BUTTON_CHOICE : LCD_MIDDLE_BUTTON_CHOICE;
+    static int8_t choice_selected = -1;
+
+    // Check if knob was rotated
+    if (abs(enc_dif - lcd_encoder_diff) >= ENCODER_PULSES_PER_STEP) {
+        if (two_choices == false) { // third_choice is not nullptr, safe to dereference
+            if (enc_dif > lcd_encoder_diff && current_selection != LCD_LEFT_BUTTON_CHOICE) {
+                // Rotating knob counter clockwise
+                current_selection--;
+            } else if (enc_dif < lcd_encoder_diff && current_selection != LCD_RIGHT_BUTTON_CHOICE) {
+                // Rotating knob clockwise
+                current_selection++;
+            }
+        } else {
+            if (enc_dif > lcd_encoder_diff && current_selection != LCD_LEFT_BUTTON_CHOICE) {
+                // Rotating knob counter clockwise
+                current_selection = LCD_LEFT_BUTTON_CHOICE;
+            } else if (enc_dif < lcd_encoder_diff && current_selection != LCD_MIDDLE_BUTTON_CHOICE) {
+                // Rotating knob clockwise
+                current_selection = LCD_MIDDLE_BUTTON_CHOICE;
+            }
+        }
+
+        // Update '>' render only
+        //! @brief Button menu
+        //!
+        //! @code{.unparsed}
+        //! |01234567890123456789|
+        //! |                    |
+        //! |                    |
+        //! |                    |
+        //! |>(left)             |
+        //! ----------------------
+        //! Three choices 
+        //! |>(left)>(mid)>(righ)|
+        //! ----------------------
+        //! Two choices
+        //! ----------------------
+        //! |>(left)   >(mid)    |
+        //! ----------------------
+        //! @endcode
+        //
+        lcd_set_cursor(0, 3);
+        lcd_print(current_selection == LCD_LEFT_BUTTON_CHOICE ? '>': ' ');
+        if (two_choices == false)
+        {
+            lcd_set_cursor(7, 3);
+            lcd_print(current_selection == LCD_MIDDLE_BUTTON_CHOICE ? '>': ' ');
+            lcd_set_cursor(13, 3);
+            lcd_print(current_selection == LCD_RIGHT_BUTTON_CHOICE ? '>': ' ');
+        } else {
+            lcd_set_cursor(10, 3);
+            lcd_print(current_selection == LCD_MIDDLE_BUTTON_CHOICE ? '>': ' ');
+        }
+        // Consume rotation event and make feedback sound
+        enc_dif = lcd_encoder_diff;
+        Sound_MakeSound(e_SOUND_TYPE_EncoderMove);
+    }
+
+    // Check if knob was clicked and consume the event
+    if (lcd_clicked()) {
+        Sound_MakeSound(e_SOUND_TYPE_ButtonEcho);
+        choice_selected = current_selection;
+    } else {
+        // continue monitoring
+        return ret;
+    }
+
+    if ((two_choices && choice_selected == LCD_MIDDLE_BUTTON_CHOICE)      // Two choices and middle button selected
+        || (!two_choices && choice_selected == LCD_RIGHT_BUTTON_CHOICE)) // Three choices and right most button selected
+    {
+        // 'More' show error description
+        lcd_show_fullscreen_message_and_wait_P(_T(PrusaErrorDesc(ei)));
+        ret = 1;
+    } else if(choice_selected == LCD_MIDDLE_BUTTON_CHOICE) {
+        SetButtonResponse((ButtonOperations)button_op_right);
+        ret = 2;
+    } else {
+        SetButtonResponse((ButtonOperations)button_op_middle);
+        ret = 2;
+    }
+
+    // Reset static variables to their default value
+    current_selection = two_choices ? LCD_LEFT_BUTTON_CHOICE : LCD_MIDDLE_BUTTON_CHOICE;
+    choice_selected = -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;
+
+void ReportErrorHook(uint16_t ec, uint8_t res) {
+    if (mmu2.MMUCurrentErrorCode() == ErrorCode::OK && res == MMU2::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;
+    } else {
+        // attempt an automatic Retry button
+        if( ReportErrorHookState == ReportErrorHookStates::MONITOR_SELECTION ){
+            if( mmu2.RetryIfPossible(ec) ){
+                ReportErrorHookState = ReportErrorHookStates::DISMISS_ERROR_SCREEN;
+            }
+        }
+    }
+
+    const uint8_t ei = PrusaErrorCodeIndex(ec);
+
+    switch ((uint8_t)ReportErrorHookState)
+    {
+    case (uint8_t)ReportErrorHookStates::RENDER_ERROR_SCREEN:
+        ReportErrorHookStaticRender(ei);
+        ReportErrorHookState = ReportErrorHookStates::MONITOR_SELECTION;
+        // Fall through
+    case (uint8_t)ReportErrorHookStates::MONITOR_SELECTION:
+        mmu2.is_mmu_error_monitor_active = true;
+        ReportErrorHookDynamicRender(); // Render dynamic characters
+        switch (ReportErrorHookMonitor(ei))
+        {
+            case 0:
+                // No choice selected, return to loop()
+                break;
+            case 1:
+                // More button selected, change state
+                ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
+                break;
+            case 2:
+                // Exit error screen and enable lcd updates
+                lcd_set_custom_characters();
+                lcd_update_enable(true);
+                lcd_return_to_status();
+                // Reset the state in case a new error is reported
+                mmu2.is_mmu_error_monitor_active = false;
+                ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
+                break;
+            default:
+                break;
+        }
+        return; // Always return to loop() to let MMU trigger a call to ReportErrorHook again
+        break;
+    case (uint8_t)ReportErrorHookStates::DISMISS_ERROR_SCREEN:
+        lcd_set_custom_characters();
+        lcd_update_enable(true);
+        lcd_return_to_status();
+        // Reset the state in case a new error is reported
+        mmu2.is_mmu_error_monitor_active = false;
+        ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
+        break;
+    default:
+        break;
+    }
+}
+
+void ReportProgressHook(CommandInProgress cip, uint16_t ec) {
+    if (cip != CommandInProgress::NoCommand)
+    {
+        custom_message_type = CustomMsg::MMUProgress;
+        lcd_setstatuspgm( _T(ProgressCodeToText(ec)) );
+    } else {
+        // If there is no command in progress we can display other
+        // useful information such as the name of the SD file 
+        // being printed
+        custom_message_type = CustomMsg::Status;
+    }
+}
+
+} // namespace MMU2

+ 50 - 0
Firmware/mmu2_reporting.h

@@ -0,0 +1,50 @@
+/// @file mmu2_reporting.h
+
+#pragma once
+#include <stdint.h>
+
+namespace MMU2 {
+
+enum CommandInProgress : uint8_t {
+    NoCommand = 0,
+    CutFilament = 'C',
+    EjectFilament = 'E',
+    Homing = 'H',
+    LoadFilament = 'L',
+    Reset = 'X',
+    ToolChange = 'T',
+    UnloadFilament = 'U',
+};
+
+/// Called at the begin of every MMU operation
+void BeginReport(CommandInProgress cip, uint16_t ec);
+
+/// Called at the end of every MMU operation
+void EndReport(CommandInProgress cip, uint16_t ec);
+
+/**
+ * @brief Called when the MMU or MK3S sends operation error (even repeatedly).
+ * Render MMU error screen on the LCD. This must be non-blocking
+ * and allow the MMU and printer to communicate with each other.
+ * @param[in] ec error code
+ * @param[in] res reporter error source, is either Printer (0) or MMU (1)
+ */
+void ReportErrorHook(uint16_t ec, uint8_t res);
+
+/// Called when the MMU sends operation progress update
+void ReportProgressHook(CommandInProgress cip, uint16_t ec);
+
+/// 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();
+
+/// @returns 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();
+
+} // namespace

+ 31 - 0
Firmware/mmu2_serial.cpp

@@ -0,0 +1,31 @@
+#include "mmu2_serial.h"
+#include "uart2.h"
+
+namespace MMU2 {
+
+void MMU2Serial::begin(uint32_t baud){
+    uart2_init(baud); // @@TODO we may skip the baud rate setting in case of 8bit FW ... could save some bytes...
+}
+
+void MMU2Serial::close() {
+    // @@TODO - probably turn off the UART
+}
+
+int MMU2Serial::read() {
+    return fgetc(uart2io);
+}
+
+void MMU2Serial::flush() {
+    // @@TODO - clear the output buffer
+}
+
+size_t MMU2Serial::write(const uint8_t *buffer, size_t size) {
+    while(size--){
+        fputc(*buffer, uart2io);
+        ++buffer;
+    }
+}
+
+MMU2Serial mmu2Serial;
+
+} // namespace MMU2

+ 20 - 0
Firmware/mmu2_serial.h

@@ -0,0 +1,20 @@
+#pragma once
+#include <stdint.h>
+#include <stddef.h>
+
+namespace MMU2 {
+
+/// A minimal serial interface for the MMU
+class MMU2Serial {
+public:
+    MMU2Serial() = default;
+    void begin(uint32_t baud);
+    void close();
+    int read();
+    void flush();
+    size_t write(const uint8_t *buffer, size_t size);
+};
+
+extern MMU2Serial mmu2Serial;
+
+} // namespace MMU2

+ 20 - 16
Firmware/motion_control.cpp

@@ -26,13 +26,16 @@
 
 // The arc is approximated by generating a huge number of tiny, linear segments. The length of each 
 // segment is configured in settings.mm_per_arc_segment.  
-void mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, bool isclockwise, uint8_t extruder)
+void mc_arc(const float* position, float* target, const float* offset, float feed_rate, float radius, bool isclockwise, uint8_t extruder, uint16_t start_segment_idx)
 {
+    float start_position[4];
+    memcpy(start_position, position, sizeof(start_position));
+    
     float r_axis_x = -offset[X_AXIS];  // Radius vector from center to current location
     float r_axis_y = -offset[Y_AXIS];
-    float center_axis_x = position[X_AXIS] - r_axis_x;
-    float center_axis_y = position[Y_AXIS] - r_axis_y;
-    float travel_z = target[Z_AXIS] - position[Z_AXIS];
+    float center_axis_x = start_position[X_AXIS] - r_axis_x;
+    float center_axis_y = start_position[Y_AXIS] - r_axis_y;
+    float travel_z = target[Z_AXIS] - start_position[Z_AXIS];
     float rt_x = target[X_AXIS] - center_axis_x;
     float rt_y = target[Y_AXIS] - center_axis_y;
     // 20200419 - Add a variable that will be used to hold the arc segment length
@@ -40,7 +43,7 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
     // 20210109 - Add a variable to hold the n_arc_correction value
     unsigned char n_arc_correction = cs.n_arc_correction;
 
-    // CCW angle between position and target from circle center. Only one atan2() trig computation required.
+    // CCW angle between start_position and target from circle center. Only one atan2() trig computation required.
     float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y);
     if (angular_travel_total < 0) { angular_travel_total += 2 * M_PI; }
 
@@ -76,7 +79,7 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
 
     //20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
     //to compensate when start pos = target pos && angle is zero -> angle = 2Pi
-    if (position[X_AXIS] == target[X_AXIS] && position[Y_AXIS] == target[Y_AXIS] && angular_travel_total == 0)
+    if (start_position[X_AXIS] == target[X_AXIS] && start_position[Y_AXIS] == target[Y_AXIS] && angular_travel_total == 0)
     {
         angular_travel_total += 2 * M_PI;
     }
@@ -113,13 +116,13 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
     */
 
     // If there is only one segment, no need to do a bunch of work since this is a straight line!
-    if (segments > 1)
+    if (segments > 1 && start_segment_idx)
     {
         // Calculate theta per segments, and linear (z) travel per segment, e travel per segment
         // as well as the small angle approximation for sin and cos.
         const float theta_per_segment = angular_travel_total / segments,
             linear_per_segment = travel_z / (segments),
-            segment_extruder_travel = (target[E_AXIS] - position[E_AXIS]) / (segments),
+            segment_extruder_travel = (target[E_AXIS] - start_position[E_AXIS]) / (segments),
             sq_theta_per_segment = theta_per_segment * theta_per_segment,
             sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
             cos_T = 1 - 0.5f * sq_theta_per_segment;
@@ -142,21 +145,22 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
             }
 
             // Update Position
-            position[X_AXIS] = center_axis_x + r_axis_x;
-            position[Y_AXIS] = center_axis_y + r_axis_y;
-            position[Z_AXIS] += linear_per_segment;
-            position[E_AXIS] += segment_extruder_travel;
+            start_position[X_AXIS] = center_axis_x + r_axis_x;
+            start_position[Y_AXIS] = center_axis_y + r_axis_y;
+            start_position[Z_AXIS] += linear_per_segment;
+            start_position[E_AXIS] += segment_extruder_travel;
             // Clamp to the calculated position.
-            clamp_to_software_endstops(position);
+            clamp_to_software_endstops(start_position);
             // Insert the segment into the buffer
-            plan_buffer_line(position[X_AXIS], position[Y_AXIS], position[Z_AXIS], position[E_AXIS], feed_rate, extruder, position);
+            if (i >= start_segment_idx)
+                plan_buffer_line(start_position[X_AXIS], start_position[Y_AXIS], start_position[Z_AXIS], start_position[E_AXIS], feed_rate, extruder, position, i);
             // Handle the situation where the planner is aborted hard.
-            if (waiting_inside_plan_buffer_line_print_aborted)
+            if (planner_aborted)
                 return;
         }
     }
     // Clamp to the target position.
     clamp_to_software_endstops(target);
     // Ensure last segment arrives at target location.
-    plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, target);
+    plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, position, 0);
 }

+ 2 - 3
Firmware/motion_control.h

@@ -26,7 +26,6 @@
 // offset == offset from current xyz, axis_XXX defines circle plane in tool space, axis_linear is
 // the direction of helical travel, radius == circle radius, isclockwise boolean. Used
 // for vector transformation direction.
-void mc_arc(float *position, float *target, float *offset, float feed_rate, float radius,
-  bool isclockwise, uint8_t extruder);
-  
+void mc_arc(const float *position, float *target, const float *offset, float feed_rate, float radius, bool isclockwise, uint8_t extruder, uint16_t start_segment_idx);
+
 #endif

+ 81 - 73
Firmware/pat9125.cpp

@@ -4,7 +4,9 @@
 #include <avr/pgmspace.h>
 #include "config.h"
 #include <stdio.h>
+#include "Configuration_prusa.h"
 
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
 
 //PAT9125 registers
 #define PAT9125_PID1			0x00
@@ -46,27 +48,26 @@ uint8_t pat9125_s = 0;
 
 
 // Init sequence, address & value.
-const PROGMEM uint8_t pat9125_init_seq1[] = {
+const PROGMEM uint8_t pat9125_init_bank0[] = {
 	// Disable write protect.
 	PAT9125_WP, 0x5a,
 	// Set the X resolution to zero to let the sensor know that it could safely ignore movement in the X axis.
-    PAT9125_RES_X, PAT9125_XRES,
-    // Set the Y resolution to a maximum (or nearly a maximum).
-    PAT9125_RES_Y, PAT9125_YRES,
-    // Set 12-bit X/Y data format.
-    PAT9125_ORIENTATION, 0x04,
-//	PAT9125_ORIENTATION, 0x04 | (xinv?0x08:0) | (yinv?0x10:0), //!? direction switching does not work
-    // Now continues the magic sequence from the PAT912EL Application Note: Firmware Guides for Tracking Optimization.
-    0x5e, 0x08,
-    0x20, 0x64,
-    0x2b, 0x6d,
-    0x32, 0x2f,
-    // stopper
-    0x0ff
+	PAT9125_RES_X, PAT9125_XRES,
+	// Set the Y resolution to a maximum (or nearly a maximum).
+	PAT9125_RES_Y, PAT9125_YRES,
+	// Set data format and sensor orientation.
+	PAT9125_ORIENTATION, ((PAT9125_12B_RES?0x04:0) | (PAT9125_INVERT_X?0x08:0) | (PAT9125_INVERT_Y?0x10:0) | (PAT9125_SWAP_XY?0x20:0)),
+	
+	// Now continues the magic sequence from the PAT912EL Application Note: Firmware Guides for Tracking Optimization.
+	0x5e, 0x08,
+	0x20, 0x64,
+	0x2b, 0x6d,
+	0x32, 0x2f,
+	0xff //end of sequence
 };
 
 // Init sequence, address & value.
-const PROGMEM uint8_t pat9125_init_seq2[] = {
+const PROGMEM uint8_t pat9125_init_bank1[] = {
 	// Magic sequence to enforce full frame rate of the sensor.
 	0x06, 0x028,
 	0x33, 0x0d0,
@@ -93,14 +94,14 @@ const PROGMEM uint8_t pat9125_init_seq2[] = {
 	0x6e, 0x022,
 	0x71, 0x007,
 	0x72, 0x008,
-	// stopper
-    0x0ff
+	0xff //end of sequence
 };
 
 
-uint8_t pat9125_rd_reg(uint8_t addr);
-void pat9125_wr_reg(uint8_t addr, uint8_t data);
-uint8_t pat9125_wr_reg_verify(uint8_t addr, uint8_t data);
+static uint8_t pat9125_rd_reg(uint8_t addr);
+static void pat9125_wr_reg(uint8_t addr, uint8_t data);
+static uint8_t pat9125_wr_reg_verify(uint8_t addr, uint8_t data);
+static uint8_t pat9125_wr_seq(const uint8_t* seq);
 
 extern FILE _uartout;
 #define uartout (&_uartout)
@@ -113,26 +114,23 @@ uint8_t pat9125_probe()
   #error not implemented
 #elif defined(PAT9125_SWI2C)
     swi2c_init();
-    return swi2c_readByte_A8(PAT9125_I2C_ADDR,0x00,NULL);
+    return swi2c_check(PAT9125_I2C_ADDR) == 0;
 #elif defined(PAT9125_I2C)
     twi_init();
-  #ifdef IR_SENSOR
-    // NOTE: this is called from the MK3S variant, so it should be kept minimal
-    uint8_t data;
-    return (twi_r8(PAT9125_I2C_ADDR,PAT9125_PID1,&data) == 0);
-  #else
-    return (pat9125_rd_reg(PAT9125_PID1) != 0);
-  #endif
+    return twi_check(PAT9125_I2C_ADDR) == 0;
 #endif
 }
 
 uint8_t pat9125_init(void)
 {
-    if (!pat9125_probe())
-        return 0;
+	if (!pat9125_probe())
+		return 0;
 
-    // Verify that the sensor responds with its correct product ID.
-    pat9125_PID1 = pat9125_rd_reg(PAT9125_PID1);
+// Switch to bank0, not allowed to perform pat9125_wr_reg_verify on this register.
+	pat9125_wr_reg(PAT9125_BANK_SELECTION, 0);
+
+	// Verify that the sensor responds with its correct product ID.
+	pat9125_PID1 = pat9125_rd_reg(PAT9125_PID1);
 	pat9125_PID2 = pat9125_rd_reg(PAT9125_PID2);
 	if ((pat9125_PID1 != 0x31) || (pat9125_PID2 != 0x91))
 	{
@@ -142,54 +140,49 @@ uint8_t pat9125_init(void)
 			return 0;
 	}
 
-#ifdef PAT9125_NEW_INIT
-	// Switch to bank0, not allowed to perform OTS_RegWriteRead.
-	pat9125_wr_reg(PAT9125_BANK_SELECTION, 0);
+#if PAT9125_NEW_INIT
 	// Software reset (i.e. set bit7 to 1). It will reset to 0 automatically.
-	// After the reset, OTS_RegWriteRead is not allowed.
+	// pat9125_wr_reg_verify is not allowed because the register contents will change as soon as they are written. No point in verifying those.
 	pat9125_wr_reg(PAT9125_CONFIG, 0x97);
 	// Wait until the sensor reboots.
-	// Delay 1ms.
-	_delay_us(1000);
-	{
-		const uint8_t *ptr = pat9125_init_seq1;
-		for (;;) {
-			const uint8_t addr = pgm_read_byte_near(ptr ++);
-			if (addr == 0x0ff)
-				break;
-			if (! pat9125_wr_reg_verify(addr, pgm_read_byte_near(ptr ++)))
-				// Verification of the register write failed.
-				return 0;
-		}
-	}
-	// Delay 10ms.
-	_delay_ms(10);
-	// Switch to bank1, not allowed to perform OTS_RegWrite.
+	_delay_ms(1);
+	
+	//Write init sequence in bank0. MUST ALREADY BE IN bank0.
+	if (!pat9125_wr_seq(pat9125_init_bank0))
+		return 0;
+	
+	_delay_ms(10); // not sure why this is here. But I'll allow it.
+	
+	// Switch to bank1, not allowed to perform pat9125_wr_reg_verify on this register.
 	pat9125_wr_reg(PAT9125_BANK_SELECTION, 0x01);
-	{
-		const uint8_t *ptr = pat9125_init_seq2;
-		for (;;) {
-			const uint8_t addr = pgm_read_byte_near(ptr ++);
-			if (addr == 0x0ff)
-				break;
-			if (! pat9125_wr_reg_verify(addr, pgm_read_byte_near(ptr ++)))
-				// Verification of the register write failed.
-				return 0;
-		}
-	}
-	// Switch to bank0, not allowed to perform OTS_RegWriteRead.
+	//Write init sequence in bank1. MUST ALREADY BE IN bank1.
+	if (!pat9125_wr_seq(pat9125_init_bank1))
+		return 0;
+	
+	// Switch to bank0, not allowed to perform pat9125_wr_reg_verify on this register.
 	pat9125_wr_reg(PAT9125_BANK_SELECTION, 0x00);
+	
 	// Enable write protect.
-	pat9125_wr_reg(PAT9125_WP, 0x00);
+	pat9125_wr_reg(PAT9125_WP, 0x00); //prevents writing to registers over 0x09
 
 	pat9125_PID1 = pat9125_rd_reg(PAT9125_PID1);
 	pat9125_PID2 = pat9125_rd_reg(PAT9125_PID2);
-#endif //PAT9125_NEW_INIT
 
+#else //PAT9125_NEW_INIT
+	// Disable write protect.
+	pat9125_wr_reg(PAT9125_WP, 0x5a); //allows writing to all registers
+	
 	pat9125_wr_reg(PAT9125_RES_X, PAT9125_XRES);
 	pat9125_wr_reg(PAT9125_RES_Y, PAT9125_YRES);
-	fprintf_P(uartout, PSTR("PAT9125_RES_X=%u\n"), pat9125_rd_reg(PAT9125_RES_X));
-	fprintf_P(uartout, PSTR("PAT9125_RES_Y=%u\n"), pat9125_rd_reg(PAT9125_RES_Y));
+	printf_P(PSTR("PAT9125_RES_X=%u\n"), pat9125_rd_reg(PAT9125_RES_X));
+	printf_P(PSTR("PAT9125_RES_Y=%u\n"), pat9125_rd_reg(PAT9125_RES_Y));
+	
+	pat9125_wr_reg(PAT9125_ORIENTATION, ((PAT9125_12B_RES?0x04:0) | (PAT9125_INVERT_X?0x08:0) | (PAT9125_INVERT_Y?0x10:0) | (PAT9125_SWAP_XY?0x20:0)));
+	
+	// Enable write protect.
+	pat9125_wr_reg(PAT9125_WP, 0x00); //prevents writing to registers over 0x09
+#endif //PAT9125_NEW_INIT
+
 	return 1;
 }
 
@@ -212,7 +205,7 @@ uint8_t pat9125_update(void)
 			if (iDX & 0x800) iDX -= 4096;
 			if (iDY & 0x800) iDY -= 4096;
 			pat9125_x += iDX;
-			pat9125_y -= iDY; //negative number, because direction switching does not work
+			pat9125_y += iDY;
 		}
 		return 1;
 	}
@@ -232,7 +225,7 @@ uint8_t pat9125_update_y(void)
 			if (pat9125_PID1 == 0xff) return 0;
 			int16_t iDY = ucYL | ((ucXYH << 8) & 0xf00);
 			if (iDY & 0x800) iDY -= 4096;
-			pat9125_y -= iDY; //negative number, because direction switching does not work
+			pat9125_y += iDY;
 		}
 		return 1;
 	}
@@ -251,7 +244,7 @@ uint8_t pat9125_update_bs(void)
 	return 0;
 }
 
-uint8_t pat9125_rd_reg(uint8_t addr)
+static uint8_t pat9125_rd_reg(uint8_t addr)
 {
 	uint8_t data = 0;
 #if defined(PAT9125_SWSPI)
@@ -274,7 +267,7 @@ uint8_t pat9125_rd_reg(uint8_t addr)
     return 0;
 }
 
-void pat9125_wr_reg(uint8_t addr, uint8_t data)
+static void pat9125_wr_reg(uint8_t addr, uint8_t data)
 {
 #if defined(PAT9125_SWSPI)
 	swspi_start();
@@ -296,8 +289,23 @@ void pat9125_wr_reg(uint8_t addr, uint8_t data)
     return;
 }
 
-uint8_t pat9125_wr_reg_verify(uint8_t addr, uint8_t data)
+static uint8_t pat9125_wr_reg_verify(uint8_t addr, uint8_t data)
 {
 	pat9125_wr_reg(addr, data);
 	return pat9125_rd_reg(addr) == data;
 }
+
+static uint8_t pat9125_wr_seq(const uint8_t* seq)
+{
+	for (;;) {
+		const uint8_t addr = pgm_read_byte(seq++);
+		if (addr == 0xff)
+			break;
+		if (!pat9125_wr_reg_verify(addr, pgm_read_byte(seq++)))
+			// Verification of the register write failed.
+			return 0;
+	}
+	return 1;
+}
+
+#endif

+ 1 - 8
Firmware/pat9125.h

@@ -1,12 +1,7 @@
-//pat9125.h
-#ifndef PAT9125_H
-#define PAT9125_H
+#pragma once
 
 #include <inttypes.h>
 
-extern uint8_t pat9125_PID1;
-extern uint8_t pat9125_PID2;
-
 extern int16_t pat9125_x;
 extern int16_t pat9125_y;
 extern uint8_t pat9125_b;
@@ -17,5 +12,3 @@ extern uint8_t pat9125_init(void);
 extern uint8_t pat9125_update(void);    // update all sensor data
 extern uint8_t pat9125_update_y(void);  // update _y only
 extern uint8_t pat9125_update_bs(void); // update _b/_s only
-
-#endif //PAT9125_H

+ 3 - 0
Firmware/pins_Einsy_1_0.h

@@ -78,6 +78,9 @@
 #define VOLT_IR_PIN          8 //A8
 
 
+#define TEMP_TIM 5
+
+
 #define E0_TMC2130_CS       66
 #define E0_TMC2130_DIAG     65
 #define E0_STEP_PIN         34

+ 2 - 0
Firmware/pins_Rambo_1_0.h

@@ -57,6 +57,8 @@
 #define TEMP_PINDA_PIN          1 //A1
 
 
+#define TEMP_TIM 3
+
 
 #define E0_STEP_PIN            34
 #define E0_DIR_PIN             43

+ 2 - 0
Firmware/pins_Rambo_1_3.h

@@ -60,6 +60,8 @@
 #define TEMP_PINDA_PIN          1 //A1
 
 
+#define TEMP_TIM 3
+
 
 #define E0_STEP_PIN            34
 #define E0_DIR_PIN             43

+ 49 - 49
Firmware/planner.cpp

@@ -55,6 +55,7 @@
 #include "planner.h"
 #include "stepper.h"
 #include "temperature.h"
+#include "fancheck.h"
 #include "ultralcd.h"
 #include "language.h"
 #include "ConfigurationStore.h"
@@ -68,6 +69,9 @@
 #include "tmc2130.h"
 #endif //TMC2130
 
+#include <util/atomic.h>
+
+
 //===========================================================================
 //=============================public variables ============================
 //===========================================================================
@@ -95,8 +99,6 @@ static float previous_speed[NUM_AXIS]; // Speed of previous path line segment
 static float previous_nominal_speed; // Nominal speed of previous path line segment
 static float previous_safe_speed; // Exit speed limited by a jerk to full halt of a previous last segment.
 
-uint8_t maxlimit_status;
-
 #ifdef AUTOTEMP
 float autotemp_max=250;
 float autotemp_min=210;
@@ -592,17 +594,7 @@ void check_axes_activity()
 #endif
 }
 
-bool waiting_inside_plan_buffer_line_print_aborted = false;
-/*
-void planner_abort_soft()
-{
-    // Empty the queue.
-    while (blocks_queued()) plan_discard_current_block();
-    // Relay to planner wait routine, that the current line shall be canceled.
-    waiting_inside_plan_buffer_line_print_aborted = true;
-    //current_position[i]
-}
-*/
+bool planner_aborted = false;
 
 #ifdef PLANNER_DIAGNOSTICS
 static inline void planner_update_queue_min_counter()
@@ -615,12 +607,8 @@ static inline void planner_update_queue_min_counter()
 
 extern volatile uint32_t step_events_completed; // The number of step events executed in the current block
 
-void planner_abort_hard()
+void planner_reset_position()
 {
-    // Abort the stepper routine and flush the planner queue.
-    DISABLE_STEPPER_DRIVER_INTERRUPT();
-
-    // Now the front-end (the Marlin_main.cpp with its current_position) is out of sync.
     // First update the planner's current position in the physical motor steps.
     position[X_AXIS] = st_get_position(X_AXIS);
     position[Y_AXIS] = st_get_position(Y_AXIS);
@@ -632,6 +620,7 @@ void planner_abort_hard()
     current_position[Y_AXIS] = st_get_position_mm(Y_AXIS);
     current_position[Z_AXIS] = st_get_position_mm(Z_AXIS);
     current_position[E_AXIS] = st_get_position_mm(E_AXIS);
+
     // Apply the mesh bed leveling correction to the Z axis.
 #ifdef MESH_BED_LEVELING
     if (mbl.active) {
@@ -664,24 +653,36 @@ void planner_abort_hard()
 #endif
     }
 #endif
-    // Clear the planner queue, reset and re-enable the stepper timer.
-    quickStop();
 
     // Apply inverse world correction matrix.
     machine2world(current_position[X_AXIS], current_position[Y_AXIS]);
-    memcpy(destination, current_position, sizeof(destination));
+    set_destination_to_current();
 #ifdef LIN_ADVANCE
     memcpy(position_float, current_position, sizeof(position_float));
 #endif
+}
+
+void planner_abort_hard()
+{
+    // Abort the stepper routine and flush the planner queue.
+    DISABLE_STEPPER_DRIVER_INTERRUPT();
+
+    // Now the front-end (the Marlin_main.cpp with its current_position) is out of sync.
+    planner_reset_position();
+
+    // Relay to planner wait routine that the current line shall be canceled.
+    planner_aborted = true;
+
+    // Clear the planner queue, reset and re-enable the stepper timer.
+    quickStop();
+
     // Resets planner junction speeds. Assumes start from rest.
     previous_nominal_speed = 0.0;
     memset(previous_speed, 0, sizeof(previous_speed));
 
+    // Reset position sync requests
     plan_reset_next_e_queue = false;
     plan_reset_next_e_sched = false;
-
-    // Relay to planner wait routine, that the current line shall be canceled.
-    waiting_inside_plan_buffer_line_print_aborted = true;
 }
 
 void plan_buffer_line_curposXYZE(float feed_rate) {
@@ -700,14 +701,17 @@ float junction_deviation = 0.1;
 // Add a new linear movement to the buffer. steps_x, _y and _z is the absolute position in 
 // mm. Microseconds specify how many microseconds the move should take to perform. To aid acceleration
 // calculation the caller must also provide the physical length of the line in millimeters.
-void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_target)
+void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_start_position, uint16_t segment_idx)
 {
-    // Calculate the buffer head after we push this byte
+  // CRITICAL_SECTION_START; //prevent stack overflow in ISR
+  // printf_P(PSTR("plan_buffer_line(%f, %f, %f, %f, %f, %u, [%f,%f,%f,%f], %u)\n"), x, y, z, e, feed_rate, extruder, gcode_start_position[0], gcode_start_position[1], gcode_start_position[2], gcode_start_position[3], segment_idx);
+  // CRITICAL_SECTION_END;
+
+  // Calculate the buffer head after we push this byte
   uint8_t next_buffer_head = next_block_index(block_buffer_head);
 
-  // If the buffer is full: good! That means we are well ahead of the robot. 
+  // If the buffer is full: good! That means we are well ahead of the robot.
   // Rest here until there is room in the buffer.
-  waiting_inside_plan_buffer_line_print_aborted = false;
   if (block_buffer_tail == next_buffer_head) {
       do {
           manage_heater(); 
@@ -715,18 +719,14 @@ void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate
           manage_inactivity(false); 
           lcd_update(0);
       } while (block_buffer_tail == next_buffer_head);
-      if (waiting_inside_plan_buffer_line_print_aborted) {
-          // Inside the lcd_update(0) routine the print has been aborted.
-          // Cancel the print, do not plan the current line this routine is waiting on.
-#ifdef PLANNER_DIAGNOSTICS
-          planner_update_queue_min_counter();
-#endif /* PLANNER_DIAGNOSTICS */
-          return;
-      }
   }
 #ifdef PLANNER_DIAGNOSTICS
   planner_update_queue_min_counter();
 #endif /* PLANNER_DIAGNOSTICS */
+  if(planner_aborted) {
+      // avoid planning the block early if aborted
+      return;
+  }
 
   // Prepare to set up new block
   block_t *block = &block_buffer[block_buffer_head];
@@ -737,16 +737,14 @@ void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate
   // Set sdlen for calculating sd position
   block->sdlen = 0;
 
-  // Save original destination of the move
-  if (gcode_target)
-      memcpy(block->gcode_target, gcode_target, sizeof(block_t::gcode_target));
+  // Save original start position of the move
+  if (gcode_start_position)
+      memcpy(block->gcode_start_position, gcode_start_position, sizeof(block_t::gcode_start_position));
   else
-  {
-      block->gcode_target[X_AXIS] = x;
-      block->gcode_target[Y_AXIS] = y;
-      block->gcode_target[Z_AXIS] = z;
-      block->gcode_target[E_AXIS] = e;
-  }
+      memcpy(block->gcode_start_position, current_position, sizeof(block_t::gcode_start_position));
+  
+  // Save the index of this segment (when a single G0/1/2/3 command plans multiple segments)
+  block->segment_idx = segment_idx;
 
   // Save the global feedrate at scheduling time
   block->gcode_feedrate = feedrate;
@@ -913,8 +911,6 @@ block->steps_y.wide = labs((target[X_AXIS]-position[X_AXIS]) - (target[Y_AXIS]-p
     block->direction_bits |= (1<<E_AXIS); 
   }
 
-  block->active_extruder = extruder;
-
   //enable active axes
   #ifdef COREXY
   if((block->steps_x.wide != 0) || (block->steps_y.wide != 0))
@@ -1331,8 +1327,12 @@ Having the real displacement of the head, we can calculate the total movement le
   if (block->step_event_count.wide <= 32767)
     block->flag |= BLOCK_FLAG_DDA_LOWRES;
 
-  // Move the buffer head. From now the block may be picked up by the stepper interrupt controller.
-  block_buffer_head = next_buffer_head;
+  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+      // Move the buffer head ensuring the current block hasn't been cancelled from an isr context
+      // (this is possible both during crash detection *and* uvlo, thus needing a global cli)
+      if(planner_aborted) return;
+      block_buffer_head = next_buffer_head;
+  }
 
   // Update position
   memcpy(position, target, sizeof(target)); // position[] = target[]

+ 7 - 4
Firmware/planner.h

@@ -75,7 +75,6 @@ typedef struct {
   dda_usteps_t step_event_count;            // The number of step events required to complete this block
   uint32_t acceleration_rate;               // The acceleration rate used for acceleration calculation
   unsigned char direction_bits;             // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h)
-  unsigned char active_extruder;            // Selects the active extruder
   // accelerate_until and decelerate_after are set by calculate_trapezoid_for_block() and they need to be synchronized with the stepper interrupt controller.
   uint32_t accelerate_until;                // The index of the step event on which to stop acceleration
   uint32_t decelerate_after;                // The index of the step event on which to start decelerating
@@ -122,7 +121,8 @@ typedef struct {
 #endif
 
   // Save/recovery state data
-  float gcode_target[NUM_AXIS];     // Target (abs mm) of the original Gcode instruction
+  float gcode_start_position[NUM_AXIS]; // Start (abs mm) of the original Gcode instruction
+  uint16_t segment_idx;             // The index of the for loop that generates segments
   uint16_t gcode_feedrate;          // Default and/or move feedrate
   uint16_t sdlen;                   // Length of the Gcode instruction
 } block_t;
@@ -159,7 +159,7 @@ void plan_buffer_line_destinationXYZE(float feed_rate);
 
 void plan_set_position_curposXYZE();
 
-void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_target = NULL);
+void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_start_position = NULL, uint16_t segment_idx = 0);
 //void plan_buffer_line(const float &x, const float &y, const float &z, const float &e, float feed_rate, const uint8_t &extruder);
 #endif // ENABLE_AUTO_BED_LEVELING
 
@@ -255,11 +255,14 @@ FORCE_INLINE bool planner_queue_full() {
     return block_buffer_tail == next_block_index;
 }
 
+// Reset machine position from stepper counters
+extern void planner_reset_position();
+
 // Abort the stepper routine, clean up the block queue,
 // wait for the steppers to stop,
 // update planner's current position and the current_position of the front end.
 extern void planner_abort_hard();
-extern bool waiting_inside_plan_buffer_line_print_aborted;
+extern bool planner_aborted;
 
 #ifdef PREVENT_DANGEROUS_EXTRUDE
 extern int extrude_min_temp;

+ 26 - 12
Firmware/sound.h

@@ -1,18 +1,34 @@
+#pragma once
 #include <stdint.h>
-#ifndef SOUND_H
-#define SOUND_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_MODE_LOUD,
+    e_SOUND_MODE_ONCE,
+    e_SOUND_MODE_SILENT,
+    e_SOUND_MODE_BLIND
+} eSOUND_MODE;
 
-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;
+#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;
 
@@ -25,5 +41,3 @@ extern void Sound_MakeCustom(uint16_t ms,uint16_t tone_ ,bool critical);
 
 //static void Sound_DoSound_Echo(void);
 //static void Sound_DoSound_Prompt(void);
-
-#endif // SOUND_H

+ 20 - 79
Firmware/stepper.cpp

@@ -36,14 +36,13 @@
 #include "tmc2130.h"
 #endif //TMC2130
 
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-#include "fsensor.h"
-int fsensor_counter; //counter for e-steps
-#endif //FILAMENT_SENSOR
+#include "Filament_sensor.h"
 
-#include "mmu.h"
+#include "mmu2.h"
 #include "ConfigurationStore.h"
 
+#include "Prusa_farm.h"
+
 #ifdef DEBUG_STACK_MONITOR
 uint16_t SP_min = 0x21FF;
 #endif //DEBUG_STACK_MONITOR
@@ -287,15 +286,6 @@ ISR(TIMER1_COMPA_vect) {
 	if (sp < SP_min) SP_min = sp;
 #endif //DEBUG_STACK_MONITOR
 
-#ifdef DEBUG_PULLUP_CRASH
-    // check for faulty pull-ups enabled on thermistor inputs
-    if ((PORTF & (uint8_t)(ADC_DIDR_MSK & 0xff)) || (PORTK & (uint8_t)((ADC_DIDR_MSK >> 8) & 0xff)))
-        pullup_error(false);
-#else
-    PORTF &= ~(uint8_t)(ADC_DIDR_MSK & 0xff);
-    PORTK &= ~(uint8_t)((ADC_DIDR_MSK >> 8) & 0xff);
-#endif // DEBUG_PULLUP_CRASH
-
 #ifdef LIN_ADVANCE
     advance_isr_scheduler();
 #else
@@ -464,9 +454,6 @@ FORCE_INLINE void stepper_next_block()
 #endif /* LIN_ADVANCE */
       count_direction[E_AXIS] = 1;
     }
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-    fsensor_st_block_begin(count_direction[E_AXIS] < 0);
-#endif //FILAMENT_SENSOR
   }
   else {
       _NEXT_ISR(2000); // 1kHz.
@@ -502,7 +489,7 @@ FORCE_INLINE void stepper_check_endstops()
       #if ( (defined(X_MIN_PIN) && (X_MIN_PIN > -1)) || defined(TMC2130_SG_HOMING) ) && !defined(DEBUG_DISABLE_XMINLIMIT)
       #ifdef TMC2130_SG_HOMING
         // Stall guard homing turned on
-        SET_BIT_TO(_endstop, X_AXIS, (READ(X_TMC2130_DIAG) != 0));
+        SET_BIT_TO(_endstop, X_AXIS, (!READ(X_TMC2130_DIAG)));
       #else
         // Normal homing
         SET_BIT_TO(_endstop, X_AXIS, (READ(X_MIN_PIN) != X_MIN_ENDSTOP_INVERTING));
@@ -519,7 +506,7 @@ FORCE_INLINE void stepper_check_endstops()
       #if ( (defined(X_MAX_PIN) && (X_MAX_PIN > -1)) || defined(TMC2130_SG_HOMING) ) && !defined(DEBUG_DISABLE_XMAXLIMIT)          
         #ifdef TMC2130_SG_HOMING
         // Stall guard homing turned on
-          SET_BIT_TO(_endstop, X_AXIS + 4, (READ(X_TMC2130_DIAG) != 0));
+          SET_BIT_TO(_endstop, X_AXIS + 4, (!READ(X_TMC2130_DIAG)));
         #else
         // Normal homing
           SET_BIT_TO(_endstop, X_AXIS + 4, (READ(X_MAX_PIN) != X_MAX_ENDSTOP_INVERTING));
@@ -543,7 +530,7 @@ FORCE_INLINE void stepper_check_endstops()
       #if ( (defined(Y_MIN_PIN) && (Y_MIN_PIN > -1)) || defined(TMC2130_SG_HOMING) ) && !defined(DEBUG_DISABLE_YMINLIMIT)          
       #ifdef TMC2130_SG_HOMING
       // Stall guard homing turned on
-        SET_BIT_TO(_endstop, Y_AXIS, (READ(Y_TMC2130_DIAG) != 0));
+        SET_BIT_TO(_endstop, Y_AXIS, (!READ(Y_TMC2130_DIAG)));
       #else
       // Normal homing
         SET_BIT_TO(_endstop, Y_AXIS, (READ(Y_MIN_PIN) != Y_MIN_ENDSTOP_INVERTING));
@@ -560,7 +547,7 @@ FORCE_INLINE void stepper_check_endstops()
       #if ( (defined(Y_MAX_PIN) && (Y_MAX_PIN > -1)) || defined(TMC2130_SG_HOMING) ) && !defined(DEBUG_DISABLE_YMAXLIMIT)                
         #ifdef TMC2130_SG_HOMING
         // Stall guard homing turned on
-          SET_BIT_TO(_endstop, Y_AXIS + 4, (READ(Y_TMC2130_DIAG) != 0));
+          SET_BIT_TO(_endstop, Y_AXIS + 4, (!READ(Y_TMC2130_DIAG)));
         #else
         // Normal homing
           SET_BIT_TO(_endstop, Y_AXIS + 4, (READ(Y_MAX_PIN) != Y_MAX_ENDSTOP_INVERTING));
@@ -586,7 +573,7 @@ FORCE_INLINE void stepper_check_endstops()
             SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING));
           else
 #endif //TMC2130_STEALTH_Z
-            SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) || (READ(Z_TMC2130_DIAG) != 0));
+            SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) || (!READ(Z_TMC2130_DIAG)));
         #else
           SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING));
         #endif //TMC2130_SG_HOMING
@@ -608,7 +595,7 @@ FORCE_INLINE void stepper_check_endstops()
           SET_BIT_TO(_endstop, Z_AXIS + 4, 0);
         else
 #endif //TMC2130_STEALTH_Z
-          SET_BIT_TO(_endstop, Z_AXIS + 4, (READ(Z_TMC2130_DIAG) != 0));
+          SET_BIT_TO(_endstop, Z_AXIS + 4, (!READ(Z_TMC2130_DIAG)));
         #else
         SET_BIT_TO(_endstop, Z_AXIS + 4, (READ(Z_MAX_PIN) != Z_MAX_ENDSTOP_INVERTING));
         #endif //TMC2130_SG_HOMING
@@ -641,7 +628,7 @@ FORCE_INLINE void stepper_check_endstops()
         SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING));
       else
 #endif //TMC2130_STEALTH_Z
-        SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) || (READ(Z_TMC2130_DIAG) != 0));
+        SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) || (!READ(Z_TMC2130_DIAG)));
       #else
       SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING));
       #endif //TMC2130_SG_HOMING
@@ -711,9 +698,9 @@ FORCE_INLINE void stepper_tick_lowres()
 #ifdef LIN_ADVANCE
       e_steps += count_direction[E_AXIS];
 #else
-	#ifdef FILAMENT_SENSOR
-	  fsensor_counter += count_direction[E_AXIS];
-	#endif //FILAMENT_SENSOR
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+      fsensor.stStep(count_direction[E_AXIS] < 0);
+#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
       STEP_NC_LO(E_AXIS);
 #endif
     }
@@ -773,9 +760,9 @@ FORCE_INLINE void stepper_tick_highres()
 #ifdef LIN_ADVANCE
       e_steps += count_direction[E_AXIS];
 #else
-    #ifdef FILAMENT_SENSOR
-      fsensor_counter += count_direction[E_AXIS];
-    #endif //FILAMENT_SENSOR
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+      fsensor.stStep(count_direction[E_AXIS] < 0);
+#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
       STEP_NC_LO(E_AXIS);
 #endif
     }
@@ -970,21 +957,9 @@ FORCE_INLINE void isr() {
 
     // If current block is finished, reset pointer
     if (step_events_completed.wide >= current_block->step_event_count.wide) {
-#if !defined(LIN_ADVANCE) && defined(FILAMENT_SENSOR)
-		fsensor_st_block_chunk(fsensor_counter);
-		fsensor_counter = 0;
-#endif //FILAMENT_SENSOR
-
       current_block = NULL;
       plan_discard_current_block();
     }
-#if !defined(LIN_ADVANCE) && defined(FILAMENT_SENSOR)
-	else if ((abs(fsensor_counter) >= fsensor_chunk_len))
-  	{
-      fsensor_st_block_chunk(fsensor_counter);
-  	  fsensor_counter = 0;
-  	}
-#endif //FILAMENT_SENSOR
   }
 
 #ifdef TMC2130
@@ -1080,19 +1055,11 @@ FORCE_INLINE void advance_isr_scheduler() {
             STEP_NC_HI(E_AXIS);
             e_steps += (rev? 1: -1);
             STEP_NC_LO(E_AXIS);
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-            fsensor_counter += (rev? -1: 1);
-#endif
+#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
+            fsensor.stStep(rev);
+#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
         }
         while(--max_ticks);
-
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-        if (abs(fsensor_counter) >= fsensor_chunk_len)
-        {
-            fsensor_st_block_chunk(fsensor_counter);
-            fsensor_counter = 0;
-        }
-#endif
     }
 
     // Schedule the next closest tick, ignoring advance if scheduled too
@@ -1186,22 +1153,6 @@ void st_init()
   #endif
 
   //endstops and pullups
-
-  #ifdef TMC2130_SG_HOMING
-    SET_INPUT(X_TMC2130_DIAG);
-    WRITE(X_TMC2130_DIAG,HIGH);
-    
-    SET_INPUT(Y_TMC2130_DIAG);
-    WRITE(Y_TMC2130_DIAG,HIGH);
-    
-    SET_INPUT(Z_TMC2130_DIAG);
-    WRITE(Z_TMC2130_DIAG,HIGH);
-
-	SET_INPUT(E0_TMC2130_DIAG);
-    WRITE(E0_TMC2130_DIAG,HIGH);
-    
-  #endif
-    
   #if defined(X_MIN_PIN) && X_MIN_PIN > -1
     SET_INPUT(X_MIN_PIN);
     #ifdef ENDSTOPPULLUP_XMIN
@@ -1691,13 +1642,3 @@ void microstep_readings()
       #endif
 }
 #endif //TMC2130
-
-
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-void st_reset_fsensor()
-{
-    CRITICAL_SECTION_START;
-    fsensor_counter = 0;
-    CRITICAL_SECTION_END;
-}
-#endif //FILAMENT_SENSOR

+ 0 - 5
Firmware/stepper.h

@@ -87,9 +87,4 @@ void microstep_readings();
   void babystep(const uint8_t axis,const bool direction); // perform a short step with a single stepper motor, outside of any convention
 #endif
 
-#if defined(FILAMENT_SENSOR) && defined(PAT9125)
-// reset the internal filament sensor state
-void st_reset_fsensor();
-#endif
-
 #endif

+ 5 - 0
Firmware/strlen_cx.h

@@ -0,0 +1,5 @@
+#pragma once
+
+constexpr inline int strlen_constexpr(const char* str){
+    return *str ? 1 + strlen_constexpr(str + 1) : 0;
+}

+ 57 - 19
Firmware/swi2c.c

@@ -15,23 +15,49 @@
 #define SWI2C_ASHF   0x01 //address shift (<< 1)
 #define SWI2C_DMSK   0x7f //device address mask
 
+static void __delay(void);
+static void swi2c_start(void);
+static void swi2c_stop(void);
+// static void swi2c_ack(void);
+static void swi2c_nack(void);
+static uint8_t swi2c_wait_ack();
+static uint8_t swi2c_read(void);
+static void swi2c_write(uint8_t data);
 
-void __delay(void)
-{
-	_delay_us(1.5);
-}
 
 void swi2c_init(void)
 {
-	WRITE(SWI2C_SDA, 1);
-	WRITE(SWI2C_SCL, 1);
-	SET_OUTPUT(SWI2C_SDA);
-	SET_OUTPUT(SWI2C_SCL);
-	uint8_t i; for (i = 0; i < 100; i++)
+	SET_INPUT(SWI2C_SDA);
+	WRITE(SWI2C_SDA, 1); //SDA must be input with pullups while we are not sure if the slave is outputing or not
+
+	WRITE(SWI2C_SCL, 0);
+	SET_OUTPUT(SWI2C_SCL); //SCL can be an output at all times. The bus is not in a multi-master configuration.
+
+	for (uint8_t i = 0; i < 100; i++) //wait. Not sure what for, but wait anyway.
 		__delay();
+
+	for (uint8_t i = 0; i < 10; i++) { //send nack 10 times. This makes sure that the slave gets a nack regardless of it's state when we init the bus.
+		swi2c_nack();
+	}
+	swi2c_stop(); //"release" the bus by sending a stop condition.
+
+	SET_OUTPUT(SWI2C_SDA); //finally make the SDA line an output since the bus is idle for sure.
+}
+
+void swi2c_disable(void)
+{
+	SET_INPUT(SWI2C_SDA);
+	WRITE(SWI2C_SDA, 0);
+	SET_INPUT(SWI2C_SCL);
+	WRITE(SWI2C_SCL, 0);
 }
 
-void swi2c_start(void)
+static void __delay(void)
+{
+	_delay_us(1.5);
+}
+
+static void swi2c_start(void)
 {
 	WRITE(SWI2C_SDA, 0);
 	__delay();
@@ -39,7 +65,7 @@ void swi2c_start(void)
 	__delay();
 }
 
-void swi2c_stop(void)
+static void swi2c_stop(void)
 {
 	WRITE(SWI2C_SCL, 1);
 	__delay();
@@ -47,7 +73,8 @@ void swi2c_stop(void)
 	__delay();
 }
 
-void swi2c_ack(void)
+/*
+static void swi2c_ack(void)
 {
 	WRITE(SWI2C_SDA, 0);
 	__delay();
@@ -56,8 +83,19 @@ void swi2c_ack(void)
 	WRITE(SWI2C_SCL, 0);
 	__delay();
 }
+*/
 
-uint8_t swi2c_wait_ack()
+static void swi2c_nack(void)
+{
+	WRITE(SWI2C_SDA, 1);
+	__delay();
+	WRITE(SWI2C_SCL, 1);
+	__delay();
+	WRITE(SWI2C_SCL, 0);
+	__delay();
+}
+
+static uint8_t swi2c_wait_ack()
 {
 	SET_INPUT(SWI2C_SDA);
 	__delay();
@@ -77,13 +115,13 @@ uint8_t swi2c_wait_ack()
 	return ack;
 }
 
-uint8_t swi2c_read(void)
+static uint8_t swi2c_read(void)
 {
 	WRITE(SWI2C_SDA, 1);
 	__delay();
 	SET_INPUT(SWI2C_SDA);
 	uint8_t data = 0;
-	int8_t bit; for (bit = 7; bit >= 0; bit--)
+	for (uint8_t bit = 8; bit-- > 0;)
 	{
 		WRITE(SWI2C_SCL, 1);
 		__delay();
@@ -95,9 +133,9 @@ uint8_t swi2c_read(void)
 	return data;
 }
 
-void swi2c_write(uint8_t data)
+static void swi2c_write(uint8_t data)
 {
-	int8_t bit; for (bit = 7; bit >= 0; bit--)
+	for (uint8_t bit = 8; bit-- > 0;)
 	{
 		WRITE(SWI2C_SDA, data & _BV(bit));
 		__delay();
@@ -112,9 +150,9 @@ uint8_t swi2c_check(uint8_t dev_addr)
 {
 	swi2c_start();
 	swi2c_write((dev_addr & SWI2C_DMSK) << SWI2C_ASHF);
-	if (!swi2c_wait_ack()) { swi2c_stop(); return 0; }
+	if (!swi2c_wait_ack()) { swi2c_stop(); return 1; }
 	swi2c_stop();
-	return 1;
+	return 0;
 }
 
 #ifdef SWI2C_A8 //8bit address

+ 3 - 0
Firmware/swi2c.h

@@ -13,6 +13,9 @@ extern "C" {
 //initialize
 extern void swi2c_init(void);
 
+//deinit pins
+extern void swi2c_disable(void);
+
 //check device address acknowledge
 extern uint8_t swi2c_check(uint8_t dev_addr);
 

+ 21 - 0
Firmware/system_timer.h

@@ -4,6 +4,7 @@
 #define FIRMWARE_SYSTEM_TIMER_H_
 
 #include "Arduino.h"
+#include "macros.h"
 #define SYSTEM_TIMER_2
 
 #ifdef SYSTEM_TIMER_2
@@ -26,4 +27,24 @@
 #define timer02_set_pwm0(pwm0)
 #endif //SYSTEM_TIMER_2
 
+// Timer counter, incremented by the 1ms Arduino timer.
+// The standard Arduino timer() function returns this value atomically
+// by disabling / enabling interrupts. This is costly, if the interrupts are known
+// to be disabled.
+#ifdef SYSTEM_TIMER_2
+extern volatile unsigned long timer2_millis;
+#else //SYSTEM_TIMER_2
+extern volatile unsigned long timer0_millis;
+#endif //SYSTEM_TIMER_2
+
+// An unsynchronized equivalent to a standard Arduino _millis() function.
+// To be used inside an interrupt routine.
+FORCE_INLINE unsigned long millis_nc() {
+#ifdef SYSTEM_TIMER_2
+	return timer2_millis;
+#else //SYSTEM_TIMER_2
+	return timer0_millis;
+#endif //SYSTEM_TIMER_2
+}
+
 #endif /* FIRMWARE_SYSTEM_TIMER_H_ */

+ 115 - 0
Firmware/temp_model.h

@@ -0,0 +1,115 @@
+// model-based temperature safety checker declarations
+#ifndef TEMP_MGR_INTV
+#error "this file is not a public interface, it should be used *only* within temperature.cpp!"
+#endif
+
+#include "planner.h"
+
+constexpr uint8_t TEMP_MODEL_CAL_S = 60;     // Maximum recording length during calibration (s)
+constexpr uint8_t TEMP_MODEL_CAL_R_STEP = 4; // Fan interpolation steps during calibration
+constexpr float TEMP_MODEL_fS = 0.065;       // simulation filter (1st-order IIR factor)
+constexpr float TEMP_MODEL_fE = 0.05;        // error filter (1st-order IIR factor)
+
+// transport delay buffer size (samples)
+constexpr uint8_t TEMP_MODEL_LAG_SIZE = (TEMP_MODEL_LAG / TEMP_MGR_INTV + 0.5);
+
+// resistance values for all fan levels
+constexpr uint8_t TEMP_MODEL_R_SIZE = (1 << FAN_SOFT_PWM_BITS);
+
+namespace temp_model {
+
+struct model_data
+{
+    // temporary buffers
+    float dT_lag_buf[TEMP_MODEL_LAG_SIZE]; // transport delay buffer
+    uint8_t dT_lag_idx = 0;                // transport delay buffer index
+    float dT_err_prev = 0;                 // previous temperature delta error
+    float T_prev = 0;                      // last temperature extruder
+
+    // configurable parameters
+    float P;                               // heater power (W)
+    float C;                               // heatblock capacitance (J/K)
+    float R[TEMP_MODEL_R_SIZE];            // heatblock resistance for all fan levels (K/W)
+    float Ta_corr;                         // ambient temperature correction (K)
+
+    // thresholds
+    float warn;                            // warning threshold (K/s)
+    float err;                             // error threshold (K/s)
+
+    // status flags
+    union
+    {
+        bool flags;
+        struct
+        {
+            bool uninitialized: 1;         // model is not initialized
+            bool error: 1;                 // error threshold set
+            bool warning: 1;               // warning threshold set
+        } flag_bits;
+    };
+
+    // pre-computed values (initialized via reset)
+    float C_i;                             // heatblock capacitance (precomputed dT/C)
+    float warn_s;                          // warning threshold (per sample)
+    float err_s;                           // error threshold (per sample)
+
+    // simulation functions
+    void reset(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp);
+    void step(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp);
+};
+
+static bool enabled;          // model check enabled
+static bool warn_beep = true; // beep on warning threshold
+static model_data data;       // default heater data
+
+static bool calibrated(); // return calibration/model validity status
+static void check();      // check and trigger errors or warnings based on current state
+
+// warning state (updated from from isr context)
+volatile static struct
+{
+    float dT_err;    // temperature delta error (per sample)
+    bool warning: 1; // warning condition
+    bool assert: 1;  // warning is still asserted
+} warning_state;
+
+static void handle_warning(); // handle warnings from user context
+
+#ifdef TEMP_MODEL_DEBUG
+static struct
+{
+    volatile struct
+    {
+        uint32_t stamp;
+        int8_t delta_ms;
+        uint8_t counter;
+        uint8_t cur_pwm;
+        float cur_temp;
+        float cur_amb;
+    } entry;
+
+    uint8_t serial;
+    bool enabled;
+} log_buf;
+
+static void log_usr(); // user log handler
+static void log_isr(); // isr log handler
+#endif
+
+} // namespace temp_model
+
+namespace temp_model_cal {
+
+// recording scratch buffer
+struct rec_entry
+{
+    float temp;  // heater temperature
+    uint8_t pwm; // heater PWM
+};
+
+constexpr uint16_t REC_BUFFER_SIZE = TEMP_MODEL_CAL_S / TEMP_MGR_INTV;
+static rec_entry* const rec_buffer = (rec_entry*)block_buffer; // oh-hey, free memory!
+static_assert(sizeof(rec_entry[REC_BUFFER_SIZE]) <= sizeof(block_buffer),
+    "recording length too long to fit within available buffer");
+
+} // namespace temp_model_cal

+ 1447 - 874
Firmware/temperature.cpp

@@ -28,26 +28,76 @@
 
  */
 
-
-#include "Marlin.h"
-#include "cmdqueue.h"
+#include "temperature.h"
+#include "stepper.h"
 #include "ultralcd.h"
 #include "menu.h"
-#include "conv2str.h"
 #include "sound.h"
-#include "temperature.h"
-#include "cardreader.h"
+#include "fancheck.h"
+#include "messages.h"
+#include "language.h"
 
 #include "SdFatUtil.h"
 
 #include <avr/wdt.h>
+#include <util/atomic.h>
 #include "adc.h"
 #include "ConfigurationStore.h"
-#include "messages.h"
 #include "Timer.h"
 #include "Configuration_prusa.h"
+#include "Prusa_farm.h"
+
+#if (ADC_OVRSAMPL != OVERSAMPLENR)
+#error "ADC_OVRSAMPL oversampling must match OVERSAMPLENR"
+#endif
+
+#ifdef SYSTEM_TIMER_2
+#define ENABLE_SOFT_PWM_INTERRUPT()  TIMSK2 |= (1<<OCIE2B)
+#define DISABLE_SOFT_PWM_INTERRUPT() TIMSK2 &= ~(1<<OCIE2B)
+#else //SYSTEM_TIMER_2
+#define ENABLE_SOFT_PWM_INTERRUPT()  TIMSK0 |= (1<<OCIE0B)
+#define DISABLE_SOFT_PWM_INTERRUPT() TIMSK0 &= ~(1<<OCIE0B)
+#endif //SYSTEM_TIMER_2
 
-#include "config.h"
+// temperature manager timer configuration
+#define TEMP_MGR_INTV   0.27 // seconds, ~3.7Hz
+#define TEMP_TIM_PRESCALE 256
+#define TEMP_TIM_OCRA_OVF (uint16_t)(TEMP_MGR_INTV / ((long double)TEMP_TIM_PRESCALE / F_CPU))
+#define TEMP_TIM_REGNAME(registerbase,number,suffix) _REGNAME(registerbase,number,suffix)
+#undef B0 //Necessary hack because of "binary.h" included in "Arduino.h" included in "system_timer.h" included in this file...
+#define TCCRxA TEMP_TIM_REGNAME(TCCR, TEMP_TIM, A)
+#define TCCRxB TEMP_TIM_REGNAME(TCCR, TEMP_TIM, B)
+#define TCCRxC TEMP_TIM_REGNAME(TCCR, TEMP_TIM, C)
+#define TCNTx TEMP_TIM_REGNAME(TCNT, TEMP_TIM,)
+#define OCRxA TEMP_TIM_REGNAME(OCR, TEMP_TIM, A)
+#define TIMSKx TEMP_TIM_REGNAME(TIMSK, TEMP_TIM,)
+#define TIFRx TEMP_TIM_REGNAME(TIFR, TEMP_TIM,)
+#define TIMERx_COMPA_vect TEMP_TIM_REGNAME(TIMER, TEMP_TIM, _COMPA_vect)
+#define CSx0 TEMP_TIM_REGNAME(CS, TEMP_TIM, 0)
+#define CSx1 TEMP_TIM_REGNAME(CS, TEMP_TIM, 1)
+#define CSx2 TEMP_TIM_REGNAME(CS, TEMP_TIM, 2)
+#define WGMx0 TEMP_TIM_REGNAME(WGM, TEMP_TIM, 0)
+#define WGMx1 TEMP_TIM_REGNAME(WGM, TEMP_TIM, 1)
+#define WGMx2 TEMP_TIM_REGNAME(WGM, TEMP_TIM, 2)
+#define WGMx3 TEMP_TIM_REGNAME(WGM, TEMP_TIM, 3)
+#define COMxA0 TEMP_TIM_REGNAME(COM, TEMP_TIM, A0)
+#define COMxB0 TEMP_TIM_REGNAME(COM, TEMP_TIM, B0)
+#define COMxC0 TEMP_TIM_REGNAME(COM, TEMP_TIM, C0)
+#define OCIExA TEMP_TIM_REGNAME(OCIE, TEMP_TIM, A)
+#define OCFxA TEMP_TIM_REGNAME(OCF, TEMP_TIM, A)
+
+#define TEMP_MGR_INT_FLAG_STATE()    (TIFRx & (1<<OCFxA))
+#define TEMP_MGR_INT_FLAG_CLEAR()    TIFRx |= (1<<OCFxA)
+#define TEMP_MGR_INTERRUPT_STATE()   (TIMSKx & (1<<OCIExA))
+#define ENABLE_TEMP_MGR_INTERRUPT()  TIMSKx |=  (1<<OCIExA)
+#define DISABLE_TEMP_MGR_INTERRUPT() TIMSKx &= ~(1<<OCIExA)
+
+#ifdef TEMP_MODEL
+// temperature model interface
+#include "temp_model.h"
+#endif
+
+#include "Filament_sensor.h"
 
 //===========================================================================
 //=============================public variables============================
@@ -58,13 +108,12 @@ int current_temperature_raw[EXTRUDERS] = { 0 };
 float current_temperature[EXTRUDERS] = { 0.0 };
 
 #ifdef PINDA_THERMISTOR
-uint16_t current_temperature_raw_pinda =  0 ; //value with more averaging applied
-uint16_t current_temperature_raw_pinda_fast = 0; //value read from adc
+uint16_t current_temperature_raw_pinda = 0;
 float current_temperature_pinda = 0.0;
 #endif //PINDA_THERMISTOR
 
 #ifdef AMBIENT_THERMISTOR
-int current_temperature_raw_ambient =  0 ;
+int current_temperature_raw_ambient = 0;
 float current_temperature_ambient = 0.0;
 #endif //AMBIENT_THERMISTOR
 
@@ -76,10 +125,6 @@ int current_voltage_raw_pwr = 0;
 int current_voltage_raw_bed = 0;
 #endif
 
-#ifdef IR_SENSOR_ANALOG
-uint16_t current_voltage_raw_IR = 0;
-#endif //IR_SENSOR_ANALOG
-
 int current_temperature_bed_raw = 0;
 float current_temperature_bed = 0.0;
   
@@ -87,17 +132,19 @@ float current_temperature_bed = 0.0;
 #ifdef PIDTEMP
   float _Kp, _Ki, _Kd;
   int pid_cycle, pid_number_of_cycles;
-  bool pid_tuning_finished = false;
-#endif //PIDTEMP
-  
-#ifdef FAN_SOFT_PWM
-  unsigned char fanSpeedSoftPwm;
-#endif
+  static bool pid_tuning_finished = true;
 
-#ifdef FANCHECK
-  volatile uint8_t fan_check_error = EFCE_OK;
-#endif
+  bool pidTuningRunning() {
+      return !pid_tuning_finished;
+  }
 
+  void preparePidTuning() {
+      // ensure heaters are disabled before we switch off PID management!
+      disable_heater();
+      pid_tuning_finished = false;
+  }
+#endif //PIDTEMP
+  
 unsigned char soft_pwm_bed;
 
 #ifdef BABYSTEPPING
@@ -116,12 +163,9 @@ static volatile bool temp_meas_ready = false;
   static float pTerm[EXTRUDERS];
   static float iTerm[EXTRUDERS];
   static float dTerm[EXTRUDERS];
-  //int output;
   static float pid_error[EXTRUDERS];
   static float iState_sum_min[EXTRUDERS];
   static float iState_sum_max[EXTRUDERS];
-  // static float pid_input[EXTRUDERS];
-  // static float pid_output[EXTRUDERS];
   static bool pid_reset[EXTRUDERS];
 #endif //PIDTEMP
 #ifdef PIDTEMPBED
@@ -131,7 +175,6 @@ static volatile bool temp_meas_ready = false;
   static float pTerm_bed;
   static float iTerm_bed;
   static float dTerm_bed;
-  //int output;
   static float pid_error_bed;
   static float temp_iState_min_bed;
   static float temp_iState_max_bed;
@@ -141,26 +184,11 @@ static volatile bool temp_meas_ready = false;
   static unsigned char soft_pwm[EXTRUDERS];
 
 #ifdef FAN_SOFT_PWM
+  unsigned char fanSpeedSoftPwm;
   static unsigned char soft_pwm_fan;
 #endif
-
 uint8_t fanSpeedBckp = 255;
 
-#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
-  unsigned long extruder_autofan_last_check = _millis();
-  
-  bool fan_measuring = false;
-  uint8_t fanState = 0;
-#ifdef EXTRUDER_ALTFAN_DETECT
-  struct
-  {
-    uint8_t isAltfan : 1;
-    uint8_t altfanOverride : 1;
-  } altfanStatus;
-#endif //EXTRUDER_ALTFAN_DETECT
-#endif
-
-
 #if EXTRUDERS > 3
   # error Unsupported number of extruders
 #elif EXTRUDERS > 2
@@ -171,8 +199,6 @@ uint8_t fanSpeedBckp = 255;
   # define ARRAY_BY_EXTRUDERS(v1, v2, v3) { v1 }
 #endif
 
-static ShortTimer oTimer4minTempHeater,oTimer4minTempBed;
-
 // Init min and max temp with extreme values to prevent false errors during startup
 static int minttemp_raw[EXTRUDERS] = ARRAY_BY_EXTRUDERS( HEATER_0_RAW_LO_TEMP , HEATER_1_RAW_LO_TEMP , HEATER_2_RAW_LO_TEMP );
 static int maxttemp_raw[EXTRUDERS] = ARRAY_BY_EXTRUDERS( HEATER_0_RAW_HI_TEMP , HEATER_1_RAW_HI_TEMP , HEATER_2_RAW_HI_TEMP );
@@ -199,7 +225,7 @@ static float analog2tempBed(int raw);
 #ifdef AMBIENT_MAXTEMP
 static float analog2tempAmbient(int raw);
 #endif
-static void updateTemperaturesFromRawValues();
+static void updateTemperatures();
 
 enum TempRunawayStates : uint8_t
 {
@@ -226,56 +252,6 @@ static void temp_runaway_check(uint8_t _heater_id, float _target_temperature, fl
 static void temp_runaway_stop(bool isPreheat, bool isBed);
 #endif
 
-#ifdef EXTRUDER_ALTFAN_DETECT
-ISR(INT6_vect) {
-	fan_edge_counter[0]++;
-}
-
-bool extruder_altfan_detect()
-{
-	setExtruderAutoFanState(3);
-
-	SET_INPUT(TACH_0);
-
-	uint8_t overrideVal = eeprom_read_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE);
-	if (overrideVal == EEPROM_EMPTY_VALUE)
-	{
-		overrideVal = (calibration_status() == CALIBRATION_STATUS_CALIBRATED) ? 1 : 0;
-		eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, overrideVal);
-	}
-	altfanStatus.altfanOverride = overrideVal;
-
-	CRITICAL_SECTION_START;
-	EICRB &= ~(1 << ISC61);
-	EICRB |= (1 << ISC60);
-	EIMSK |= (1 << INT6);
-	fan_edge_counter[0] = 0;
-	CRITICAL_SECTION_END;
-	extruder_autofan_last_check = _millis();
-
-	_delay(1000);
-
-	EIMSK &= ~(1 << INT6);
-
-	countFanSpeed();
-	altfanStatus.isAltfan = fan_speed[0] > 100;
-	setExtruderAutoFanState(1);
-	return altfanStatus.isAltfan;
-}
-
-void altfanOverride_toggle()
-{
-    altfanStatus.altfanOverride = !altfanStatus.altfanOverride;
-    eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, altfanStatus.altfanOverride);
-}
-
-bool altfanOverride_get()
-{
-    return altfanStatus.altfanOverride;
-}
-
-#endif //EXTRUDER_ALTFAN_DETECT
-
 // return "false", if all extruder-heaters are 'off' (ie. "true", if any heater is 'on')
 bool checkAllHotends(void)
 {
@@ -288,8 +264,9 @@ bool checkAllHotends(void)
 //          codegen bug causing a stack overwrite issue in process_commands()
 void __attribute__((noinline)) PID_autotune(float temp, int extruder, int ncycles)
 {
+  preparePidTuning();
+
   pid_number_of_cycles = ncycles;
-  pid_tuning_finished = false;
   float input = 0.0;
   pid_cycle=0;
   bool heating = true;
@@ -316,15 +293,13 @@ void __attribute__((noinline)) PID_autotune(float temp, int extruder, int ncycle
        ||(extruder < 0)
   #endif
        ){
-          SERIAL_ECHOLN("PID Autotune failed. Bad extruder number.");
+          SERIAL_ECHOLNPGM("PID Autotune failed. Bad extruder number.");
 		  pid_tuning_finished = true;
 		  pid_cycle = 0;
           return;
         }
 	
-  SERIAL_ECHOLN("PID Autotune start");
-  
-  disable_heater(); // switch off all heaters.
+  SERIAL_ECHOLNPGM("PID Autotune start");
 
   if (extruder<0)
   {
@@ -340,15 +315,12 @@ void __attribute__((noinline)) PID_autotune(float temp, int extruder, int ncycle
      target_temperature[extruder] = (int)temp; // to display the requested target extruder temperature properly on the main screen
   }
 
-
-
-
- for(;;) {
+  for(;;) {
 #ifdef WATCHDOG
     wdt_reset();
 #endif //WATCHDOG
     if(temp_meas_ready == true) { // temp sample ready
-      updateTemperaturesFromRawValues();
+      updateTemperatures();
 
       input = (extruder<0)?current_temperature_bed:current_temperature[extruder];
 
@@ -495,6 +467,7 @@ void __attribute__((noinline)) PID_autotune(float temp, int extruder, int ncycle
 
 void updatePID()
 {
+  // TODO: iState_sum_max and PID values should be synchronized for temp_mgr_isr
 #ifdef PIDTEMP
   for(uint_least8_t e = 0; e < EXTRUDERS; e++) {
      iState_sum_max[e] = PID_INTEGRAL_DRIVE_MAX / cs.Ki;  
@@ -511,399 +484,109 @@ int getHeaterPower(int heater) {
   return soft_pwm[heater];
 }
 
-#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
-
-  #if defined(FAN_PIN) && FAN_PIN > -1
-    #if EXTRUDER_0_AUTO_FAN_PIN == FAN_PIN 
-       #error "You cannot set EXTRUDER_0_AUTO_FAN_PIN equal to FAN_PIN"
-    #endif
-  #endif
+// reset PID state after changing target_temperature
+void resetPID(uint8_t extruder _UNUSED) {}
 
-void setExtruderAutoFanState(uint8_t state)
+enum class TempErrorSource : uint8_t
 {
-	//If bit 1 is set (0x02), then the extruder fan speed won't be adjusted according to temperature. Useful for forcing
-	//the fan to either On or Off during certain tests/errors.
-
-	fanState = state;
-	newFanSpeed = 0;
-	if (fanState & 0x01)
-	{
-#ifdef EXTRUDER_ALTFAN_DETECT
-		if (altfanStatus.isAltfan && !altfanStatus.altfanOverride) newFanSpeed = EXTRUDER_ALTFAN_SPEED_SILENT;
-		else newFanSpeed = EXTRUDER_AUTO_FAN_SPEED;
-#else //EXTRUDER_ALTFAN_DETECT
-		newFanSpeed = EXTRUDER_AUTO_FAN_SPEED;
-#endif //EXTRUDER_ALTFAN_DETECT
-	}
-	timer4_set_fan0(newFanSpeed);
-}
-
-#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))))
+    hotend,
+    bed,
+#ifdef AMBIENT_THERMISTOR
+    ambient,
+#endif
+};
 
-void countFanSpeed()
+// thermal error type (in order of decreasing priority!)
+enum class TempErrorType : uint8_t
 {
-	//SERIAL_ECHOPGM("edge counter 1:"); MYSERIAL.println(fan_edge_counter[1]);
-	fan_speed[0] = (fan_edge_counter[0] * (float(250) / (_millis() - extruder_autofan_last_check)));
-	fan_speed[1] = (fan_edge_counter[1] * (float(250) / (_millis() - extruder_autofan_last_check)));
-	/*SERIAL_ECHOPGM("time interval: "); MYSERIAL.println(_millis() - extruder_autofan_last_check);
-	SERIAL_ECHOPGM("extruder fan speed:"); MYSERIAL.print(fan_speed[0]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[0]);
-	SERIAL_ECHOPGM("print fan speed:"); MYSERIAL.print(fan_speed[1]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[1]);
-	SERIAL_ECHOLNPGM(" ");*/
-	fan_edge_counter[0] = 0;
-	fan_edge_counter[1] = 0;
-}
+    max,
+    min,
+    preheat,
+    runaway,
+#ifdef TEMP_MODEL
+    model,
+#endif
+};
 
-void checkFanSpeed()
+// error state (updated via set_temp_error from isr context)
+volatile static union
 {
-	uint8_t max_fan_errors[2];
-#ifdef FAN_SOFT_PWM
-	max_fan_errors[1] = 3;  // 15 seconds (Print fan)
-	max_fan_errors[0] = 2;  // 10 seconds (Extruder fan)
-#else //FAN_SOFT_PWM
-	max_fan_errors[1] = 15; // 15 seconds (Print fan)
-	max_fan_errors[0] = 5;  // 5  seconds (Extruder fan)
-#endif //FAN_SOFT_PWM
-
-	if(fans_check_enabled)
-		fans_check_enabled = (eeprom_read_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED) > 0);
-	static uint8_t fan_speed_errors[2] = { 0,0 };
-#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 >-1))
-	if ((fan_speed[0] < 20) && (current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE)){ fan_speed_errors[0]++;}
-	else fan_speed_errors[0] = 0;
-#endif
-#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1))
-	if ((fan_speed[1] < 5) && ((blocks_queued() ? block_buffer[block_buffer_tail].fan_speed : fanSpeed) > MIN_PRINT_FAN_SPEED)) fan_speed_errors[1]++;
-	else fan_speed_errors[1] = 0;
-#endif
-
-	// drop the fan_check_error flag when both fans are ok
-	if( fan_speed_errors[0] == 0 && fan_speed_errors[1] == 0 && fan_check_error == EFCE_REPORTED){
-		// we may even send some info to the LCD from here
-		fan_check_error = EFCE_FIXED;
-	}
-	if ((fan_check_error == EFCE_FIXED) && !PRINTER_ACTIVE){
-		fan_check_error = EFCE_OK; //if the issue is fixed while the printer is doing nothing, reenable processing immediately.
-		lcd_reset_alert_level(); //for another fan speed error
-	}
-	if (fans_check_enabled && (fan_check_error == EFCE_OK))
-	{
-		for (uint8_t fan = 0; fan < 2; fan++)
-		{
-			if (fan_speed_errors[fan] > max_fan_errors[fan])
-			{
-				fan_speed_errors[fan] = 0;
-				fanSpeedError(fan);
-			}
-		}
-	}
-}
-
-//! Prints serialMsg to serial port, displays lcdMsg onto the LCD and beeps.
-//! Extracted from fanSpeedError to save some space.
-//! @param serialMsg pointer into PROGMEM, this text will be printed to the serial port
-//! @param lcdMsg pointer into PROGMEM, this text will be printed onto the LCD
-static void fanSpeedErrorBeep(const char *serialMsg, const char *lcdMsg){
-	SERIAL_ECHOLNRPGM(serialMsg);
-	if (get_message_level() == 0) {
-		Sound_MakeCustom(200,0,true);
-		LCD_ALERTMESSAGERPGM(lcdMsg);
-	}
-}
+    uint8_t v;
+    struct
+    {
+        uint8_t error: 1;  // error condition
+        uint8_t assert: 1; // error is still asserted
+        uint8_t source: 2; // source
+        uint8_t index: 1;  // source index
+        uint8_t type: 3;   // error type
+    };
+} temp_error_state;
+
+// set the error type from within the temp_mgr isr to be handled in manager_heater
+// - immediately disable all heaters and turn on all fans at full speed
+// - prevent the user to set temperatures until all errors are cleared
+void set_temp_error(TempErrorSource source, uint8_t index, TempErrorType type)
+{
+    // save the original target temperatures for recovery before disabling heaters
+    if(!temp_error_state.error && !saved_printing) {
+        saved_bed_temperature = target_temperature_bed;
+        saved_extruder_temperature = target_temperature[index];
+        saved_fan_speed = fanSpeed;
+    }
 
-void fanSpeedError(unsigned char _fan) {
-	if (get_message_level() != 0 && isPrintPaused) return;
-	//to ensure that target temp. is not set to zero in case that we are resuming print
-	if (card.sdprinting || usb_timer.running()) {
-		if (heating_status != HeatingStatus::NO_HEATING) {
-			lcd_print_stop();
-		}
-		else {
-			fan_check_error = EFCE_DETECTED; //plans error for next processed command
-		}
-	}
-	else {
-		// SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSED); //Why pause octoprint? usb_timer.running() would be true in that case, so there is no need for this.
-		setTargetHotend0(0);
-        heating_status = HeatingStatus::NO_HEATING;
-        fan_check_error = EFCE_REPORTED;
-	}
-	switch (_fan) {
-	case 0:	// extracting the same code from case 0 and case 1 into a function saves 72B
-		fanSpeedErrorBeep(PSTR("Extruder fan speed is lower than expected"), MSG_FANCHECK_EXTRUDER);
-		break;
-	case 1:
-		fanSpeedErrorBeep(PSTR("Print fan speed is lower than expected"), MSG_FANCHECK_PRINT);
-		break;
-	}
-}
-#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1)
+    // keep disabling heaters and keep fans on as long as the condition is asserted
+    disable_heater();
+    hotendFanSetFullSpeed();
 
+    // set the initial error source to the highest priority error
+    if(!temp_error_state.error || (uint8_t)type < temp_error_state.type) {
+        temp_error_state.source = (uint8_t)source;
+        temp_error_state.index = index;
+        temp_error_state.type = (uint8_t)type;
+    }
 
-void checkExtruderAutoFans()
-{
-#if defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1
-	if (!(fanState & 0x02))
-	{
-		fanState &= ~1;
-		fanState |= current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE;
-	}
-	setExtruderAutoFanState(fanState);
-#endif 
+    // always set the error state
+    temp_error_state.error = true;
+    temp_error_state.assert = true;
 }
 
-#endif // any extruder auto fan pins set
-
-// ready for eventually parameters adjusting
-void resetPID(uint8_t)                            // only for compiler-warning elimination (if function do nothing)
-//void resetPID(uint8_t extruder)
+bool get_temp_error()
 {
+    return temp_error_state.v;
 }
 
+void handle_temp_error();
+
 void manage_heater()
 {
 #ifdef WATCHDOG
     wdt_reset();
 #endif //WATCHDOG
 
-  float pid_input;
-  float pid_output;
+    // limit execution to the same rate as temp_mgr (low-level fault handling is already handled -
+    // any remaining error handling is just user-facing and can wait one extra cycle)
+    if(!temp_meas_ready)
+        return;
 
-  if(temp_meas_ready != true)   //better readability
-    return; 
-// more precisely - this condition partially stabilizes time interval for regulation values evaluation (@ ~ 230ms)
+    // syncronize temperatures with isr
+    updateTemperatures();
 
-  // ADC values need to be converted before checking: converted values are later used in MINTEMP
-  updateTemperaturesFromRawValues();
-
-  check_max_temp();
-  check_min_temp();
-
-#ifdef TEMP_RUNAWAY_BED_HYSTERESIS
-  temp_runaway_check(0, target_temperature_bed, current_temperature_bed, (int)soft_pwm_bed, true);
-#endif
-
-  for(uint8_t e = 0; e < EXTRUDERS; e++) 
-  {
-
-#ifdef TEMP_RUNAWAY_EXTRUDER_HYSTERESIS
-	  temp_runaway_check(e+1, target_temperature[e], current_temperature[e], (int)soft_pwm[e], false);
+#ifdef TEMP_MODEL
+    // handle model warnings first, so not to override the error handler
+    if(temp_model::warning_state.warning)
+        temp_model::handle_warning();
 #endif
 
-  #ifdef PIDTEMP
-    pid_input = current_temperature[e];
-
-    #ifndef PID_OPENLOOP
-        if(target_temperature[e] == 0) {
-          pid_output = 0;
-          pid_reset[e] = true;
-        } else {
-          pid_error[e] = target_temperature[e] - pid_input;
-          if(pid_reset[e]) {
-            iState_sum[e] = 0.0;
-            dTerm[e] = 0.0;                       // 'dState_last[e]' initial setting is not necessary (see end of if-statement)
-            pid_reset[e] = false;
-          }
-#ifndef PonM
-          pTerm[e] = cs.Kp * pid_error[e];
-          iState_sum[e] += pid_error[e];
-          iState_sum[e] = constrain(iState_sum[e], iState_sum_min[e], iState_sum_max[e]);
-          iTerm[e] = cs.Ki * iState_sum[e];
-          // PID_K1 defined in Configuration.h in the PID settings
-          #define K2 (1.0-PID_K1)
-          dTerm[e] = (cs.Kd * (pid_input - dState_last[e]))*K2 + (PID_K1 * dTerm[e]); // e.g. digital filtration of derivative term changes
-          pid_output = pTerm[e] + iTerm[e] - dTerm[e]; // subtraction due to "Derivative on Measurement" method (i.e. derivative of input instead derivative of error is used)
-          if (pid_output > PID_MAX) {
-            if (pid_error[e] > 0 ) iState_sum[e] -= pid_error[e]; // conditional un-integration
-            pid_output=PID_MAX;
-          } else if (pid_output < 0) {
-            if (pid_error[e] < 0 ) iState_sum[e] -= pid_error[e]; // conditional un-integration
-            pid_output=0;
-          }
-#else // PonM ("Proportional on Measurement" method)
-          iState_sum[e] += cs.Ki * pid_error[e];
-          iState_sum[e] -= cs.Kp * (pid_input - dState_last[e]);
-          iState_sum[e] = constrain(iState_sum[e], 0, PID_INTEGRAL_DRIVE_MAX);
-          dTerm[e] = cs.Kd * (pid_input - dState_last[e]);
-          pid_output = iState_sum[e] - dTerm[e];  // subtraction due to "Derivative on Measurement" method (i.e. derivative of input instead derivative of error is used)
-          pid_output = constrain(pid_output, 0, PID_MAX);
-#endif // PonM
-        }
-        dState_last[e] = pid_input;
-    #else 
-          pid_output = constrain(target_temperature[e], 0, PID_MAX);
-    #endif //PID_OPENLOOP
-    #ifdef PID_DEBUG
-    SERIAL_ECHO_START;
-    SERIAL_ECHO(" PID_DEBUG ");
-    SERIAL_ECHO(e);
-    SERIAL_ECHO(": Input ");
-    SERIAL_ECHO(pid_input);
-    SERIAL_ECHO(" Output ");
-    SERIAL_ECHO(pid_output);
-    SERIAL_ECHO(" pTerm ");
-    SERIAL_ECHO(pTerm[e]);
-    SERIAL_ECHO(" iTerm ");
-    SERIAL_ECHO(iTerm[e]);
-    SERIAL_ECHO(" dTerm ");
-    SERIAL_ECHOLN(-dTerm[e]);
-    #endif //PID_DEBUG
-  #else /* PID off */
-    pid_output = 0;
-    if(current_temperature[e] < target_temperature[e]) {
-      pid_output = PID_MAX;
-    }
-  #endif
-
-    // Check if temperature is within the correct range
-    if((current_temperature[e] < maxttemp[e]) && (target_temperature[e] != 0))
-    {
-      soft_pwm[e] = (int)pid_output >> 1;
-    }
-    else
-    {
-      soft_pwm[e] = 0;
-    }
-  } // End extruder for loop
-
-#define FAN_CHECK_PERIOD 5000 //5s
-#define FAN_CHECK_DURATION 100 //100ms
-
-#ifndef DEBUG_DISABLE_FANCHECK
-  #if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
+    // handle temperature errors
+    if(temp_error_state.v)
+        handle_temp_error();
 
-#ifdef FAN_SOFT_PWM
-#ifdef FANCHECK
-  if ((_millis() - extruder_autofan_last_check > FAN_CHECK_PERIOD) && (!fan_measuring)) {
-	  extruder_autofan_last_check = _millis();
-	  fanSpeedBckp = fanSpeedSoftPwm;
-	  
-	  if (fanSpeedSoftPwm >= MIN_PRINT_FAN_SPEED) { //if we are in rage where we are doing fan check, set full PWM range for a short time to measure fan RPM by reading tacho signal without modulation by PWM signal
-		//  printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm);
-		  fanSpeedSoftPwm = 255;
-	  }
-	  fan_measuring = true;
-  }
-  if ((_millis() - extruder_autofan_last_check > FAN_CHECK_DURATION) && (fan_measuring)) {
-	  countFanSpeed();
-	  checkFanSpeed();
-	  //printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm);
-	  fanSpeedSoftPwm = fanSpeedBckp;
-	  //printf_P(PSTR("fan PWM: %d; extr fanSpeed measured: %d; print fan speed measured: %d \n"), fanSpeedBckp, fan_speed[0], fan_speed[1]);
-	  extruder_autofan_last_check = _millis();
-	  fan_measuring = false;
-  }
-#endif //FANCHECK
-  checkExtruderAutoFans();
-#else //FAN_SOFT_PWM
-  if(_millis() - extruder_autofan_last_check > 1000)  // only need to check fan state very infrequently
-  {
-#if (defined(FANCHECK) && ((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))
-	countFanSpeed();
-	checkFanSpeed();
-#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1)
-    checkExtruderAutoFans();
-    extruder_autofan_last_check = _millis();
-  }  
-#endif //FAN_SOFT_PWM
-
-  #endif  
-#endif //DEBUG_DISABLE_FANCHECK
-  
-  #ifndef PIDTEMPBED
-  if(_millis() - previous_millis_bed_heater < BED_CHECK_INTERVAL)
-    return;
-  previous_millis_bed_heater = _millis();
-  #endif
+    // periodically check fans
+    checkFans();
 
-  #if TEMP_SENSOR_BED != 0
-
-  #ifdef PIDTEMPBED
-    pid_input = current_temperature_bed;
-
-    #ifndef PID_OPENLOOP
-		  pid_error_bed = target_temperature_bed - pid_input;
-		  pTerm_bed = cs.bedKp * pid_error_bed;
-		  temp_iState_bed += pid_error_bed;
-		  temp_iState_bed = constrain(temp_iState_bed, temp_iState_min_bed, temp_iState_max_bed);
-		  iTerm_bed = cs.bedKi * temp_iState_bed;
-
-		  //PID_K1 defined in Configuration.h in the PID settings
-		  #define K2 (1.0-PID_K1)
-		  dTerm_bed= (cs.bedKd * (pid_input - temp_dState_bed))*K2 + (PID_K1 * dTerm_bed);
-		  temp_dState_bed = pid_input;
-
-		  pid_output = pTerm_bed + iTerm_bed - dTerm_bed;
-          	  if (pid_output > MAX_BED_POWER) {
-            	    if (pid_error_bed > 0 )  temp_iState_bed -= pid_error_bed; // conditional un-integration
-                    pid_output=MAX_BED_POWER;
-          	  } else if (pid_output < 0){
-            	    if (pid_error_bed < 0 )  temp_iState_bed -= pid_error_bed; // conditional un-integration
-                    pid_output=0;
-                  }
-
-    #else 
-      pid_output = constrain(target_temperature_bed, 0, MAX_BED_POWER);
-    #endif //PID_OPENLOOP
-
-	  if(current_temperature_bed < BED_MAXTEMP)
-	  {
-	    soft_pwm_bed = (int)pid_output >> 1;
-		timer02_set_pwm0(soft_pwm_bed << 1);
-	  }
-	  else {
-	    soft_pwm_bed = 0;
-		timer02_set_pwm0(soft_pwm_bed << 1);
-	  }
-
-    #elif !defined(BED_LIMIT_SWITCHING)
-      // Check if temperature is within the correct range
-      if(current_temperature_bed < BED_MAXTEMP)
-      {
-        if(current_temperature_bed >= target_temperature_bed)
-        {
-          soft_pwm_bed = 0;
-		  timer02_set_pwm0(soft_pwm_bed << 1);
-        }
-        else 
-        {
-          soft_pwm_bed = MAX_BED_POWER>>1;
-		  timer02_set_pwm0(soft_pwm_bed << 1);
-        }
-      }
-      else
-      {
-        soft_pwm_bed = 0;
-		timer02_set_pwm0(soft_pwm_bed << 1);
-        WRITE(HEATER_BED_PIN,LOW);
-      }
-    #else //#ifdef BED_LIMIT_SWITCHING
-      // Check if temperature is within the correct band
-      if(current_temperature_bed < BED_MAXTEMP)
-      {
-        if(current_temperature_bed > target_temperature_bed + BED_HYSTERESIS)
-        {
-          soft_pwm_bed = 0;
-		  timer02_set_pwm0(soft_pwm_bed << 1);
-        }
-        else if(current_temperature_bed <= target_temperature_bed - BED_HYSTERESIS)
-        {
-          soft_pwm_bed = MAX_BED_POWER>>1;
-          timer02_set_pwm0(soft_pwm_bed << 1);
-        }
-      }
-      else
-      {
-        soft_pwm_bed = 0;
-		timer02_set_pwm0(soft_pwm_bed << 1);
-        WRITE(HEATER_BED_PIN,LOW);
-      }
-    #endif
-      if(target_temperature_bed==0)
-	  {
-        soft_pwm_bed = 0;
-		timer02_set_pwm0(soft_pwm_bed << 1);
-	  }
-  #endif
+#ifdef TEMP_MODEL_DEBUG
+    temp_model::log_usr();
+#endif
 }
 
 #define PGM_RD_W(x)   (short)pgm_read_word(&x)
@@ -1029,36 +712,7 @@ static float analog2tempAmbient(int raw)
 }
 #endif //AMBIENT_THERMISTOR
 
-/* Called to get the raw values into the the actual temperatures. The raw values are created in interrupt context,
-    and this function is called from normal context as it is too slow to run in interrupts and will block the stepper routine otherwise */
-static void updateTemperaturesFromRawValues()
-{
-    for(uint8_t e=0;e<EXTRUDERS;e++)
-    {
-        current_temperature[e] = analog2temp(current_temperature_raw[e], e);
-    }
-
-#ifdef PINDA_THERMISTOR
-	current_temperature_raw_pinda = (uint16_t)((uint32_t)current_temperature_raw_pinda * 3 + current_temperature_raw_pinda_fast) >> 2;
-	current_temperature_pinda = analog2tempBed(current_temperature_raw_pinda);
-#endif
-
-#ifdef AMBIENT_THERMISTOR
-	current_temperature_ambient = analog2tempAmbient(current_temperature_raw_ambient); //thermistor for ambient is NTCG104LH104JT1 (2000)
-#endif
-   
-#ifdef DEBUG_HEATER_BED_SIM
-	current_temperature_bed = target_temperature_bed;
-#else //DEBUG_HEATER_BED_SIM
-	current_temperature_bed = analog2tempBed(current_temperature_bed_raw);
-#endif //DEBUG_HEATER_BED_SIM
-
-    CRITICAL_SECTION_START;
-    temp_meas_ready = false;
-    CRITICAL_SECTION_END;
-}
-
-void tp_init()
+void soft_pwm_init()
 {
 #if MB(RUMBA) && ((TEMP_SENSOR_0==-1)||(TEMP_SENSOR_1==-1)||(TEMP_SENSOR_2==-1)||(TEMP_SENSOR_BED==-1))
   //disable RUMBA JTAG in case the thermocouple extension is plugged on top of JTAG connector
@@ -1122,20 +776,6 @@ void tp_init()
 	digitalWrite(MAX6675_SS,1);
   #endif
 
-  adc_init();
-
-  timer0_init(); //enables the heatbed timer.
-
-  // timer2 already enabled earlier in the code
-  // now enable the COMPB temperature interrupt
-  OCR2B = 128;
-  TIMSK2 |= (1<<OCIE2B);
-  
-  timer4_init(); //for tone and Extruder fan PWM
-  
-  // Wait for temperature measurement to settle
-  _delay(250);
-
 #ifdef HEATER_0_MINTEMP
   minttemp[0] = HEATER_0_MINTEMP;
   while(analog2temp(minttemp_raw[0], 0) < HEATER_0_MINTEMP) {
@@ -1236,19 +876,27 @@ void tp_init()
 #endif
   }
 #endif //AMBIENT_MAXTEMP
+
+  timer0_init(); //enables the heatbed timer.
+
+  // timer2 already enabled earlier in the code
+  // now enable the COMPB temperature interrupt
+  OCR2B = 128;
+  ENABLE_SOFT_PWM_INTERRUPT();
+
+  timer4_init(); //for tone and Extruder fan PWM
 }
 
 #if (defined (TEMP_RUNAWAY_BED_HYSTERESIS) && TEMP_RUNAWAY_BED_TIMEOUT > 0) || (defined (TEMP_RUNAWAY_EXTRUDER_HYSTERESIS) && TEMP_RUNAWAY_EXTRUDER_TIMEOUT > 0)
-void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _current_temperature, float _output, bool _isbed)
+static void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _current_temperature, float _output, bool _isbed)
 {
-     float __delta;
+	float __delta;
 	float __hysteresis = 0;
 	uint16_t __timeout = 0;
 	bool temp_runaway_check_active = false;
 	static float __preheat_start[2] = { 0,0}; //currently just bed and one extruder
 	static uint8_t __preheat_counter[2] = { 0,0};
 	static uint8_t __preheat_errors[2] = { 0,0};
-		
 
 	if (_millis() - temp_runaway_timer[_heater_id] > 2000)
 	{
@@ -1325,11 +973,8 @@ void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _cu
 				}
 
 				if (__preheat_errors[_heater_id] > ((_isbed) ? 3 : 5)) 
-				{
-					if (farm_mode) { prusa_statistics(0); }
-					temp_runaway_stop(true, _isbed);
-					if (farm_mode) { prusa_statistics(91); }
-				}
+                    set_temp_error((_isbed?TempErrorSource::bed:TempErrorSource::hotend), _heater_id, TempErrorType::preheat);
+
 				__preheat_start[_heater_id] = _current_temperature;
 				__preheat_counter[_heater_id] = 0;
 			}
@@ -1366,11 +1011,7 @@ void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _cu
 				{
 					temp_runaway_error_counter[_heater_id]++;
 					if (temp_runaway_error_counter[_heater_id] * 2 > __timeout)
-					{
-						if (farm_mode) { prusa_statistics(0); }
-						temp_runaway_stop(false, _isbed);
-						if (farm_mode) { prusa_statistics(90); }
-					}
+                        set_temp_error((_isbed?TempErrorSource::bed:TempErrorSource::hotend), _heater_id, TempErrorType::runaway);
 				}
 			}
 		}
@@ -1378,106 +1019,42 @@ void temp_runaway_check(uint8_t _heater_id, float _target_temperature, float _cu
 	}
 }
 
-void temp_runaway_stop(bool isPreheat, bool isBed)
+static void temp_runaway_stop(bool isPreheat, bool isBed)
 {
-    disable_heater();
-    Sound_MakeCustom(200,0,true);
-
-    if (isPreheat)
-	{
-		lcd_setalertstatuspgm(isBed? PSTR("BED PREHEAT ERROR") : PSTR("PREHEAT ERROR"), LCD_STATUS_CRITICAL);
-		SERIAL_ERROR_START;
-		isBed ? SERIAL_ERRORLNPGM(" THERMAL RUNAWAY (PREHEAT HEATBED)") : SERIAL_ERRORLNPGM(" THERMAL RUNAWAY (PREHEAT HOTEND)");
-
-#ifdef EXTRUDER_ALTFAN_DETECT
-		altfanStatus.altfanOverride = 1; //full speed
-#endif //EXTRUDER_ALTFAN_DETECT
-		setExtruderAutoFanState(3);
-		SET_OUTPUT(FAN_PIN);
-#ifdef FAN_SOFT_PWM
-		fanSpeedSoftPwm = 255;
-#else //FAN_SOFT_PWM
-		analogWrite(FAN_PIN, 255);
-#endif //FAN_SOFT_PWM
-		fanSpeed = 255;
-	}
-	else
-	{
-		lcd_setalertstatuspgm(isBed? PSTR("BED THERMAL RUNAWAY") : PSTR("THERMAL RUNAWAY"), LCD_STATUS_CRITICAL);
-		SERIAL_ERROR_START;
-		isBed ? SERIAL_ERRORLNPGM(" HEATBED THERMAL RUNAWAY") : SERIAL_ERRORLNPGM(" HOTEND THERMAL RUNAWAY");
-	}
-
-    Stop();
+    if(IsStopped() == false) {
+        if (isPreheat) {
+            lcd_setalertstatuspgm(isBed? PSTR("BED PREHEAT ERROR") : PSTR("PREHEAT ERROR"), LCD_STATUS_CRITICAL);
+            SERIAL_ERROR_START;
+            if (isBed) {
+                SERIAL_ERRORLNPGM(" THERMAL RUNAWAY (PREHEAT HEATBED)");
+            } else {
+                SERIAL_ERRORLNPGM(" THERMAL RUNAWAY (PREHEAT HOTEND)");
+            }
+        } else {
+            lcd_setalertstatuspgm(isBed? PSTR("BED THERMAL RUNAWAY") : PSTR("THERMAL RUNAWAY"), LCD_STATUS_CRITICAL);
+            SERIAL_ERROR_START;
+            if (isBed) {
+                SERIAL_ERRORLNPGM(" HEATBED THERMAL RUNAWAY");
+            } else {
+                SERIAL_ERRORLNPGM(" HOTEND THERMAL RUNAWAY");
+            }
+        }
+        prusa_statistics(0);
+        prusa_statistics(isPreheat? 91 : 90);
+    }
+    ThermalStop();
 }
 #endif
 
-
-void disable_heater()
-{
-  setAllTargetHotends(0);
-  setTargetBed(0);
-  #if defined(TEMP_0_PIN) && TEMP_0_PIN > -1
-  target_temperature[0]=0;
-  soft_pwm[0]=0;
-   #if defined(HEATER_0_PIN) && HEATER_0_PIN > -1  
-     WRITE(HEATER_0_PIN,LOW);
-   #endif
-  #endif
-     
-  #if defined(TEMP_1_PIN) && TEMP_1_PIN > -1 && EXTRUDERS > 1
-    target_temperature[1]=0;
-    soft_pwm[1]=0;
-    #if defined(HEATER_1_PIN) && HEATER_1_PIN > -1 
-      WRITE(HEATER_1_PIN,LOW);
-    #endif
-  #endif
-      
-  #if defined(TEMP_2_PIN) && TEMP_2_PIN > -1 && EXTRUDERS > 2
-    target_temperature[2]=0;
-    soft_pwm[2]=0;
-    #if defined(HEATER_2_PIN) && HEATER_2_PIN > -1  
-      WRITE(HEATER_2_PIN,LOW);
-    #endif
-  #endif 
-
-  #if defined(TEMP_BED_PIN) && TEMP_BED_PIN > -1
-    target_temperature_bed=0;
-    soft_pwm_bed=0;
-	timer02_set_pwm0(soft_pwm_bed << 1);
-	bedPWMDisabled = 0;
-    #if defined(HEATER_BED_PIN) && HEATER_BED_PIN > -1
-      //WRITE(HEATER_BED_PIN,LOW);
-    #endif
-  #endif 
-}
-//! codes of alert messages for the LCD - it is shorter to compare an uin8_t
-//! than raw const char * of the messages themselves.
-//! Could be used for MAXTEMP situations too - after reaching MAXTEMP and turning off the heater automagically
-//! the heater/bed may cool down and a similar alert message like "MAXTERM fixed..." may be displayed.
-enum { LCDALERT_NONE = 0, LCDALERT_HEATERMINTEMP, LCDALERT_BEDMINTEMP, LCDALERT_MINTEMPFIXED, LCDALERT_PLEASERESTART };
-
-//! remember the last alert message sent to the LCD
-//! to prevent flicker and improve speed
-uint8_t last_alert_sent_to_lcd = LCDALERT_NONE;
-
-
-//! update the current temperature error message
+//! signal a temperature error on both the lcd and serial
 //! @param type short error abbreviation (PROGMEM)
-void temp_update_messagepgm(const char* PROGMEM type)
+//! @param e optional extruder index for hotend errors
+static void temp_error_messagepgm(const char* PROGMEM type, uint8_t e = EXTRUDERS)
 {
     char msg[LCD_WIDTH];
     strcpy_P(msg, PSTR("Err: "));
     strcat_P(msg, type);
     lcd_setalertstatus(msg, LCD_STATUS_CRITICAL);
-}
-
-//! signal a temperature error on both the lcd and serial
-//! @param type short error abbreviation (PROGMEM)
-//! @param e optional extruder index for hotend errors
-void temp_error_messagepgm(const char* PROGMEM type, uint8_t e = EXTRUDERS)
-{
-    temp_update_messagepgm(type);
 
     SERIAL_ERROR_START;
 
@@ -1492,106 +1069,54 @@ void temp_error_messagepgm(const char* PROGMEM type, uint8_t e = EXTRUDERS)
 }
 
 
-void max_temp_error(uint8_t e) {
-  disable_heater();
-  if(IsStopped() == false) {
-    temp_error_messagepgm(PSTR("MAXTEMP"), e);
-  }
-  #ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE
-  Stop();
-  #endif
-
-    SET_OUTPUT(FAN_PIN);
-    SET_OUTPUT(BEEPER);
-    WRITE(FAN_PIN, 1);
-    WRITE(BEEPER, 1);
-#ifdef EXTRUDER_ALTFAN_DETECT
-    altfanStatus.altfanOverride = 1; //full speed
-#endif //EXTRUDER_ALTFAN_DETECT
-    setExtruderAutoFanState(3);
-    // fanSpeed will consumed by the check_axes_activity() routine.
-    fanSpeed=255;
-	if (farm_mode) { prusa_statistics(93); }
-}
-
-void min_temp_error(uint8_t e) {
-#ifdef DEBUG_DISABLE_MINTEMP
-	return;
-#endif
-  disable_heater();
-//if (current_temperature_ambient < MINTEMP_MINAMBIENT) return;
-	static const char err[] PROGMEM = "MINTEMP";
-  if(IsStopped() == false) {
-    temp_error_messagepgm(err, e);
-    last_alert_sent_to_lcd = LCDALERT_HEATERMINTEMP;
-  } else if( last_alert_sent_to_lcd != LCDALERT_HEATERMINTEMP ){ // only update, if the lcd message is to be changed (i.e. not the same as last time)
-	// we are already stopped due to some error, only update the status message without flickering
-    temp_update_messagepgm(err);
-	last_alert_sent_to_lcd = LCDALERT_HEATERMINTEMP;
-  }
-  #ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE
-//	if( last_alert_sent_to_lcd != LCDALERT_HEATERMINTEMP ){
-//		last_alert_sent_to_lcd = LCDALERT_HEATERMINTEMP;
-//		lcd_print_stop();
-//	}
-  Stop();
-  #endif
-  if (farm_mode) { prusa_statistics(92); }
+static void max_temp_error(uint8_t e) {
+    if(IsStopped() == false) {
+        temp_error_messagepgm(PSTR("MAXTEMP"), e);
+        prusa_statistics(93);
+    }
+#ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE
+    ThermalStop();
+#endif
+}
 
+static void min_temp_error(uint8_t e) {
+    static const char err[] PROGMEM = "MINTEMP";
+    if(IsStopped() == false) {
+        temp_error_messagepgm(err, e);
+        prusa_statistics(92);
+    }
+    ThermalStop();
 }
 
-void bed_max_temp_error(void) {
-  disable_heater();
-  if(IsStopped() == false) {
-    temp_error_messagepgm(PSTR("MAXTEMP BED"));
-  }
-  #ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE
-  Stop();
-  #endif
+static void bed_max_temp_error(void) {
+    if(IsStopped() == false) {
+        temp_error_messagepgm(PSTR("MAXTEMP BED"));
+    }
+    ThermalStop();
 }
 
-void bed_min_temp_error(void) {
-#ifdef DEBUG_DISABLE_MINTEMP
-	return;
-#endif
-    disable_heater();
+static void bed_min_temp_error(void) {
     static const char err[] PROGMEM = "MINTEMP BED";
     if(IsStopped() == false) {
         temp_error_messagepgm(err);
-		last_alert_sent_to_lcd = LCDALERT_BEDMINTEMP;
-	} else if( last_alert_sent_to_lcd != LCDALERT_BEDMINTEMP ){ // only update, if the lcd message is to be changed (i.e. not the same as last time)
-		// we are already stopped due to some error, only update the status message without flickering
-        temp_update_messagepgm(err);
-		last_alert_sent_to_lcd = LCDALERT_BEDMINTEMP;
-    }
-#ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE
-    Stop();
-#endif
+	}
+    ThermalStop();
 }
 
 
 #ifdef AMBIENT_THERMISTOR
-void ambient_max_temp_error(void) {
-    disable_heater();
+static void ambient_max_temp_error(void) {
     if(IsStopped() == false) {
         temp_error_messagepgm(PSTR("MAXTEMP AMB"));
     }
-#ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE
-    Stop();
-#endif
+    ThermalStop();
 }
 
-void ambient_min_temp_error(void) {
-#ifdef DEBUG_DISABLE_MINTEMP
-	return;
-#endif
-    disable_heater();
+static void ambient_min_temp_error(void) {
     if(IsStopped() == false) {
         temp_error_messagepgm(PSTR("MINTEMP AMB"));
     }
-#ifndef BOGUS_TEMPERATURE_FAILSAFE_OVERRIDE
-    Stop();
-#endif
+    ThermalStop();
 }
 #endif
 
@@ -1652,49 +1177,33 @@ int read_max6675()
 }
 #endif
 
+#ifdef BABYSTEPPING
+FORCE_INLINE static void applyBabysteps() {
+  for(uint8_t axis=0;axis<3;axis++)
+  {
+    int curTodo=babystepsTodo[axis]; //get rid of volatile for performance
 
-extern "C" {
-
-
-void adc_ready(void) //callback from adc when sampling finished
+    if(curTodo>0)
 {
-	current_temperature_raw[0] = adc_values[ADC_PIN_IDX(TEMP_0_PIN)]; //heater
-#ifdef PINDA_THERMISTOR
-	current_temperature_raw_pinda_fast = adc_values[ADC_PIN_IDX(TEMP_PINDA_PIN)];
-#endif //PINDA_THERMISTOR
-	current_temperature_bed_raw = adc_values[ADC_PIN_IDX(TEMP_BED_PIN)];
-#ifdef VOLT_PWR_PIN
-	current_voltage_raw_pwr = adc_values[ADC_PIN_IDX(VOLT_PWR_PIN)];
-#endif
-#ifdef AMBIENT_THERMISTOR
-	current_temperature_raw_ambient = adc_values[ADC_PIN_IDX(TEMP_AMBIENT_PIN)]; // 5->6
-#endif //AMBIENT_THERMISTOR
-#ifdef VOLT_BED_PIN
-	current_voltage_raw_bed = adc_values[ADC_PIN_IDX(VOLT_BED_PIN)]; // 6->9
-#endif
-#ifdef IR_SENSOR_ANALOG
-     current_voltage_raw_IR = adc_values[ADC_PIN_IDX(VOLT_IR_PIN)];
-#endif //IR_SENSOR_ANALOG
-	temp_meas_ready = true;
+      ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+        babystep(axis,/*fwd*/true);
+        babystepsTodo[axis]--; //less to do next time
+      }
+    }
+    else
+    if(curTodo<0)
+    {
+      ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+        babystep(axis,/*fwd*/false);
+        babystepsTodo[axis]++; //less to do next time
+      }
+    }
+  }
 }
+#endif //BABYSTEPPING
 
-} // extern "C"
-
-FORCE_INLINE static void temperature_isr()
+FORCE_INLINE static void soft_pwm_core()
 {
-#ifdef DEBUG_PULLUP_CRASH
-    // check for faulty pull-ups enabled on thermistor inputs
-    if ((PORTF & (uint8_t)(ADC_DIDR_MSK & 0xff)) || (PORTK & (uint8_t)((ADC_DIDR_MSK >> 8) & 0xff)))
-        pullup_error(true);
-#else
-    PORTF &= ~(uint8_t)(ADC_DIDR_MSK & 0xff);
-    PORTK &= ~(uint8_t)((ADC_DIDR_MSK >> 8) & 0xff);
-#endif // DEBUG_PULLUP_CRASH
-
-
-	if (!temp_meas_ready) adc_cycle();
-	lcd_buttons_update();
-
   static uint8_t pwm_count = (1 << SOFT_PWM_SCALE);
   static uint8_t soft_pwm_0;
 #ifdef SLOW_PWM_HEATERS
@@ -2029,36 +1538,22 @@ FORCE_INLINE static void temperature_isr()
   } //if ((pwm_count % 64) == 0) {
   
 #endif //ifndef SLOW_PWM_HEATERS
+}
+
+FORCE_INLINE static void soft_pwm_isr()
+{
+  lcd_buttons_update();
+  soft_pwm_core();
 
-  
 #ifdef BABYSTEPPING
-  for(uint8_t axis=0;axis<3;axis++)
-  {
-    int curTodo=babystepsTodo[axis]; //get rid of volatile for performance
-   
-    if(curTodo>0)
-    {
-      CRITICAL_SECTION_START;
-      babystep(axis,/*fwd*/true);
-      babystepsTodo[axis]--; //less to do next time
-      CRITICAL_SECTION_END;
-    }
-    else
-    if(curTodo<0)
-    {
-      CRITICAL_SECTION_START;
-      babystep(axis,/*fwd*/false);
-      babystepsTodo[axis]++; //less to do next time
-      CRITICAL_SECTION_END;
-    }
-  }
+  applyBabysteps();
 #endif //BABYSTEPPING
 
   // Check if a stack overflow happened
   if (!SdFatUtil::test_stack_integrity()) stack_error();
 
 #if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
-  check_fans();
+  readFanTach();
 #endif //(defined(TACH_0))
 }
 
@@ -2069,48 +1564,45 @@ ISR(TIMER2_COMPB_vect)
 ISR(TIMER0_COMPB_vect)
 #endif //SYSTEM_TIMER_2
 {
-    static bool _lock = false;
-    if (!_lock)
-    {
-        _lock = true;
-        sei();
-        temperature_isr();
-        cli();
-        _lock = false;
+    DISABLE_SOFT_PWM_INTERRUPT();
+    NONATOMIC_BLOCK(NONATOMIC_FORCEOFF) {
+        soft_pwm_isr();
     }
+    ENABLE_SOFT_PWM_INTERRUPT();
 }
 
-void check_max_temp()
+void check_max_temp_raw()
 {
-//heater
+    //heater
 #if HEATER_0_RAW_LO_TEMP > HEATER_0_RAW_HI_TEMP
     if (current_temperature_raw[0] <= maxttemp_raw[0]) {
 #else
     if (current_temperature_raw[0] >= maxttemp_raw[0]) {
 #endif
-        max_temp_error(0);
+        set_temp_error(TempErrorSource::hotend, 0, TempErrorType::max);
     }
-//bed
+    //bed
 #if defined(BED_MAXTEMP) && (TEMP_SENSOR_BED != 0)
 #if HEATER_BED_RAW_LO_TEMP > HEATER_BED_RAW_HI_TEMP
     if (current_temperature_bed_raw <= bed_maxttemp_raw) {
 #else
     if (current_temperature_bed_raw >= bed_maxttemp_raw) {
 #endif
-       bed_max_temp_error();
+        set_temp_error(TempErrorSource::bed, 0, TempErrorType::max);
     }
 #endif
-//ambient
+    //ambient
 #if defined(AMBIENT_MAXTEMP) && (TEMP_SENSOR_AMBIENT != 0)
 #if AMBIENT_RAW_LO_TEMP > AMBIENT_RAW_HI_TEMP
     if (current_temperature_raw_ambient <= ambient_maxttemp_raw) {
 #else
     if (current_temperature_raw_ambient >= ambient_maxttemp_raw) {
 #endif
-       ambient_max_temp_error();
+        set_temp_error(TempErrorSource::ambient, 0, TempErrorType::max);
     }
 #endif
 }
+
 //! number of repeating the same state with consecutive step() calls
 //! used to slow down text switching
 struct alert_automaton_mintemp {
@@ -2122,9 +1614,10 @@ private:
 	States state = States::Init;
 	uint8_t repeat = ALERT_AUTOMATON_SPEED_DIV;
 
-	void substep(States next_state){
+	void substep(const char* next_msg, States next_state){
 		if( repeat == 0 ){
 			state = next_state; // advance to the next state
+			lcd_setalertstatuspgm(next_msg, LCD_STATUS_CRITICAL);
 			repeat = ALERT_AUTOMATON_SPEED_DIV; // and prepare repeating for it too
 		} else {
 			--repeat;
@@ -2139,25 +1632,18 @@ public:
 		switch(state){
 		case States::Init: // initial state - check hysteresis
 			if( current_temp > mintemp ){
+				lcd_setalertstatuspgm(m2, LCD_STATUS_CRITICAL);
 				state = States::TempAboveMintemp;
 			}
 			// otherwise keep the Err MINTEMP alert message on the display,
 			// i.e. do not transfer to state 1
 			break;
 		case States::TempAboveMintemp: // the temperature has risen above the hysteresis check
-			lcd_setalertstatuspgm(m2);
-			substep(States::ShowMintemp);
-			last_alert_sent_to_lcd = LCDALERT_MINTEMPFIXED;
+		case States::ShowMintemp: // displaying "MINTEMP fixed"
+			substep(m1, States::ShowPleaseRestart);
 			break;
 		case States::ShowPleaseRestart: // displaying "Please restart"
-			lcd_updatestatuspgm(m1);
-			substep(States::ShowMintemp);
-			last_alert_sent_to_lcd = LCDALERT_PLEASERESTART;
-			break;
-		case States::ShowMintemp: // displaying "MINTEMP fixed"
-			lcd_updatestatuspgm(m2);
-			substep(States::ShowPleaseRestart);
-			last_alert_sent_to_lcd = LCDALERT_MINTEMPFIXED;
+			substep(m2, States::ShowMintemp);
 			break;
 		}
 	}
@@ -2168,23 +1654,12 @@ static alert_automaton_mintemp alert_automaton_hotend(m2hotend), alert_automaton
 
 void check_min_temp_heater0()
 {
-//heater
 #if HEATER_0_RAW_LO_TEMP > HEATER_0_RAW_HI_TEMP
 	if (current_temperature_raw[0] >= minttemp_raw[0]) {
 #else
 	if (current_temperature_raw[0] <= minttemp_raw[0]) {
 #endif
-		menu_set_serious_error(SERIOUS_ERR_MINTEMP_HEATER);
-		min_temp_error(0);
-	} else if( menu_is_serious_error(SERIOUS_ERR_MINTEMP_HEATER) ) {
-		// no recovery, just force the user to restart the printer
-		// which is a safer variant than just continuing printing
-		// The automaton also checks for hysteresis - the temperature must have reached a few degrees above the MINTEMP, before
-		// we shall signalize, that MINTEMP has been fixed
-		// Code notice: normally the alert_automaton instance would have been placed here 
-		// as static alert_automaton_mintemp alert_automaton_hotend, but
-		// due to stupid compiler that takes 16 more bytes.
-		alert_automaton_hotend.step(current_temperature[0], minttemp[0] + TEMP_HYSTERESIS);
+        set_temp_error(TempErrorSource::hotend, 0, TempErrorType::min);
 	}
 }
 
@@ -2195,12 +1670,7 @@ void check_min_temp_bed()
 #else
 	if (current_temperature_bed_raw <= bed_minttemp_raw) {
 #endif
-		menu_set_serious_error(SERIOUS_ERR_MINTEMP_BED);
-		bed_min_temp_error();
-	} else if( menu_is_serious_error(SERIOUS_ERR_MINTEMP_BED) ){
-		// no recovery, just force the user to restart the printer
-		// which is a safer variant than just continuing printing
-		alert_automaton_bed.step(current_temperature_bed, BED_MINTEMP + TEMP_HYSTERESIS);
+        set_temp_error(TempErrorSource::bed, 0, TempErrorType::min);
 	}
 }
 
@@ -2212,89 +1682,104 @@ void check_min_temp_ambient()
 #else
 	if (current_temperature_raw_ambient <= ambient_minttemp_raw) {
 #endif
-		ambient_min_temp_error();
+        set_temp_error(TempErrorSource::ambient, 0, TempErrorType::min);
 	}
 }
 #endif
 
-void check_min_temp()
+void handle_temp_error()
 {
-static bool bCheckingOnHeater=false;              // state variable, which allows to short no-checking delay (is set, when temperature is (first time) over heaterMintemp)
-static bool bCheckingOnBed=false;                 // state variable, which allows to short no-checking delay (is set, when temperature is (first time) over bedMintemp)
+    // relay to the original handler
+    switch((TempErrorType)temp_error_state.type) {
+    case TempErrorType::min:
+        switch((TempErrorSource)temp_error_state.source) {
+        case TempErrorSource::hotend:
+            if(temp_error_state.assert) {
+                min_temp_error(temp_error_state.index);
+            } else {
+                // no recovery, just force the user to restart the printer
+                // which is a safer variant than just continuing printing
+                // The automaton also checks for hysteresis - the temperature must have reached a few degrees above the MINTEMP, before
+                // we shall signalize, that MINTEMP has been fixed
+                // Code notice: normally the alert_automaton instance would have been placed here
+                // as static alert_automaton_mintemp alert_automaton_hotend, but
+                alert_automaton_hotend.step(current_temperature[0], minttemp[0] + TEMP_HYSTERESIS);
+            }
+            break;
+        case TempErrorSource::bed:
+            if(temp_error_state.assert) {
+                bed_min_temp_error();
+            } else {
+                // no recovery, just force the user to restart the printer
+                // which is a safer variant than just continuing printing
+                alert_automaton_bed.step(current_temperature_bed, BED_MINTEMP + TEMP_HYSTERESIS);
+            }
+            break;
 #ifdef AMBIENT_THERMISTOR
-#ifdef AMBIENT_MINTEMP
-check_min_temp_ambient();
+        case TempErrorSource::ambient:
+            ambient_min_temp_error();
+            break;
 #endif
-#if AMBIENT_RAW_LO_TEMP > AMBIENT_RAW_HI_TEMP
-if(current_temperature_raw_ambient>(OVERSAMPLENR*MINTEMP_MINAMBIENT_RAW)) // thermistor is NTC type
-#else
-if(current_temperature_raw_ambient=<(OVERSAMPLENR*MINTEMP_MINAMBIENT_RAW))
+        }
+        break;
+    case TempErrorType::max:
+        switch((TempErrorSource)temp_error_state.source) {
+        case TempErrorSource::hotend:
+            max_temp_error(temp_error_state.index);
+            break;
+        case TempErrorSource::bed:
+            bed_max_temp_error();
+            break;
+#ifdef AMBIENT_THERMISTOR
+        case TempErrorSource::ambient:
+            ambient_max_temp_error();
+            break;
 #endif
-     {                                            // ambient temperature is low
-#endif //AMBIENT_THERMISTOR
-// *** 'common' part of code for MK2.5 & MK3
-// * nozzle checking
-if(target_temperature[active_extruder]>minttemp[active_extruder])
-     {                                            // ~ nozzle heating is on
-     bCheckingOnHeater=bCheckingOnHeater||(current_temperature[active_extruder]>(minttemp[active_extruder]+TEMP_HYSTERESIS)); // for eventually delay cutting
-     if(oTimer4minTempHeater.expired(HEATER_MINTEMP_DELAY)||(!oTimer4minTempHeater.running())||bCheckingOnHeater)
-          {
-          bCheckingOnHeater=true;                 // not necessary
-		check_min_temp_heater0();               // delay is elapsed or temperature is/was over minTemp => periodical checking is active
-          }
-     }
-else {                                            // ~ nozzle heating is off
-     oTimer4minTempHeater.start();
-     bCheckingOnHeater=false;
-     }
-// * bed checking
-if(target_temperature_bed>BED_MINTEMP)
-     {                                            // ~ bed heating is on
-     bCheckingOnBed=bCheckingOnBed||(current_temperature_bed>(BED_MINTEMP+TEMP_HYSTERESIS)); // for eventually delay cutting
-     if(oTimer4minTempBed.expired(BED_MINTEMP_DELAY)||(!oTimer4minTempBed.running())||bCheckingOnBed)
-          {
-          bCheckingOnBed=true;                    // not necessary
-		check_min_temp_bed();                   // delay is elapsed or temperature is/was over minTemp => periodical checking is active
-          }
-     }
-else {                                            // ~ bed heating is off
-     oTimer4minTempBed.start();
-     bCheckingOnBed=false;
-     }
-// *** end of 'common' part
+        }
+        break;
+    case TempErrorType::preheat:
+    case TempErrorType::runaway:
+        switch((TempErrorSource)temp_error_state.source) {
+        case TempErrorSource::hotend:
+        case TempErrorSource::bed:
+            temp_runaway_stop(
+                ((TempErrorType)temp_error_state.type == TempErrorType::preheat),
+                ((TempErrorSource)temp_error_state.source == TempErrorSource::bed));
+            break;
 #ifdef AMBIENT_THERMISTOR
-     }
-else {                                            // ambient temperature is standard
-     check_min_temp_heater0();
-     check_min_temp_bed();
-     }
-#endif //AMBIENT_THERMISTOR
-}
- 
-#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
-void check_fans() {
-#ifdef FAN_SOFT_PWM
-	if (READ(TACH_0) != fan_state[0]) {
-		if(fan_measuring) fan_edge_counter[0] ++;
-		fan_state[0] = !fan_state[0];
-	}
-#else //FAN_SOFT_PWM
-	if (READ(TACH_0) != fan_state[0]) {
-		fan_edge_counter[0] ++;
-		fan_state[0] = !fan_state[0];
-	}
+        case TempErrorSource::ambient:
+            // not needed
+            break;
 #endif
-	//if (READ(TACH_1) != fan_state[1]) {
-	//	fan_edge_counter[1] ++;
-	//	fan_state[1] = !fan_state[1];
-	//}
+        }
+        break;
+#ifdef TEMP_MODEL
+    case TempErrorType::model:
+        if(temp_error_state.assert) {
+            if(IsStopped() == false) {
+                SERIAL_ECHOLNPGM("TM: error triggered!");
+            }
+            ThermalStop(true);
+            WRITE(BEEPER, HIGH);
+        } else {
+            temp_error_state.v = 0;
+            WRITE(BEEPER, LOW);
+            menu_unset_block(MENU_BLOCK_THERMAL_ERROR);
+
+            // hotend error was transitory and disappeared, re-enable bed
+            if (!target_temperature_bed)
+                target_temperature_bed = saved_bed_temperature;
+
+            SERIAL_ECHOLNPGM("TM: error cleared");
+        }
+        break;
+#endif
+    }
 }
-#endif //TACH_0
 
 #ifdef PIDTEMP
 // Apply the scale factors to the PID values
 
-
 float scalePID_i(float i)
 {
 	return i*PID_dT;
@@ -2345,3 +1830,1091 @@ bool has_temperature_compensation()
 #endif //PINDA_THERMISTOR
 
 
+// RAII helper class to run a code block with temp_mgr_isr disabled
+class TempMgrGuard
+{
+    bool temp_mgr_state;
+
+public:
+    TempMgrGuard() {
+        ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+            temp_mgr_state = TEMP_MGR_INTERRUPT_STATE();
+            DISABLE_TEMP_MGR_INTERRUPT();
+        }
+    }
+
+    ~TempMgrGuard() throw() {
+        ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+            if(temp_mgr_state) ENABLE_TEMP_MGR_INTERRUPT();
+        }
+    }
+};
+
+void temp_mgr_init()
+{
+    // initialize the ADC and start a conversion
+    adc_init();
+    adc_start_cycle();
+
+    // initialize temperature timer
+    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+
+        // CTC
+        TCCRxB &= ~(1<<WGMx3);
+        TCCRxB |=  (1<<WGMx2);
+        TCCRxA &= ~(1<<WGMx1);
+        TCCRxA &= ~(1<<WGMx0);
+
+        // output mode = 00 (disconnected)
+        TCCRxA &= ~(3<<COMxA0);
+        TCCRxA &= ~(3<<COMxB0);
+
+        // x/256 prescaler
+        TCCRxB |=  (1<<CSx2);
+        TCCRxB &= ~(1<<CSx1);
+        TCCRxB &= ~(1<<CSx0);
+
+        // reset counter
+        TCNTx = 0;
+        OCRxA = TEMP_TIM_OCRA_OVF;
+
+        // clear pending interrupts, enable COMPA
+        TEMP_MGR_INT_FLAG_CLEAR();
+        ENABLE_TEMP_MGR_INTERRUPT();
+
+    }
+}
+
+static void pid_heater(uint8_t e, const float current, const int target)
+{
+    float pid_input;
+    float pid_output;
+
+#ifdef PIDTEMP
+    pid_input = current;
+
+#ifndef PID_OPENLOOP
+    if(target == 0) {
+        pid_output = 0;
+        pid_reset[e] = true;
+    } else {
+        pid_error[e] = target - pid_input;
+        if(pid_reset[e]) {
+            iState_sum[e] = 0.0;
+            dTerm[e] = 0.0;                       // 'dState_last[e]' initial setting is not necessary (see end of if-statement)
+            pid_reset[e] = false;
+        }
+#ifndef PonM
+        pTerm[e] = cs.Kp * pid_error[e];
+        iState_sum[e] += pid_error[e];
+        iState_sum[e] = constrain(iState_sum[e], iState_sum_min[e], iState_sum_max[e]);
+        iTerm[e] = cs.Ki * iState_sum[e];
+        // PID_K1 defined in Configuration.h in the PID settings
+#define K2 (1.0-PID_K1)
+        dTerm[e] = (cs.Kd * (pid_input - dState_last[e]))*K2 + (PID_K1 * dTerm[e]); // e.g. digital filtration of derivative term changes
+        pid_output = pTerm[e] + iTerm[e] - dTerm[e]; // subtraction due to "Derivative on Measurement" method (i.e. derivative of input instead derivative of error is used)
+        if (pid_output > PID_MAX) {
+            if (pid_error[e] > 0 ) iState_sum[e] -= pid_error[e]; // conditional un-integration
+            pid_output=PID_MAX;
+        } else if (pid_output < 0) {
+            if (pid_error[e] < 0 ) iState_sum[e] -= pid_error[e]; // conditional un-integration
+            pid_output=0;
+        }
+#else // PonM ("Proportional on Measurement" method)
+        iState_sum[e] += cs.Ki * pid_error[e];
+        iState_sum[e] -= cs.Kp * (pid_input - dState_last[e]);
+        iState_sum[e] = constrain(iState_sum[e], 0, PID_INTEGRAL_DRIVE_MAX);
+        dTerm[e] = cs.Kd * (pid_input - dState_last[e]);
+        pid_output = iState_sum[e] - dTerm[e];  // subtraction due to "Derivative on Measurement" method (i.e. derivative of input instead derivative of error is used)
+        pid_output = constrain(pid_output, 0, PID_MAX);
+#endif // PonM
+    }
+    dState_last[e] = pid_input;
+#else //PID_OPENLOOP
+    pid_output = constrain(target[e], 0, PID_MAX);
+#endif //PID_OPENLOOP
+
+#ifdef PID_DEBUG
+    SERIAL_ECHO_START;
+    SERIAL_ECHO(" PID_DEBUG ");
+    SERIAL_ECHO(e);
+    SERIAL_ECHO(": Input ");
+    SERIAL_ECHO(pid_input);
+    SERIAL_ECHO(" Output ");
+    SERIAL_ECHO(pid_output);
+    SERIAL_ECHO(" pTerm ");
+    SERIAL_ECHO(pTerm[e]);
+    SERIAL_ECHO(" iTerm ");
+    SERIAL_ECHO(iTerm[e]);
+    SERIAL_ECHO(" dTerm ");
+    SERIAL_ECHOLN(-dTerm[e]);
+#endif //PID_DEBUG
+
+#else /* PID off */
+    pid_output = 0;
+    if(current[e] < target[e]) {
+        pid_output = PID_MAX;
+    }
+#endif
+
+    // Check if temperature is within the correct range
+    if((current < maxttemp[e]) && (target != 0))
+        soft_pwm[e] = (int)pid_output >> 1;
+    else
+        soft_pwm[e] = 0;
+}
+
+static void pid_bed(const float current, const int target)
+{
+    float pid_input;
+    float pid_output;
+
+#ifndef PIDTEMPBED
+    if(_millis() - previous_millis_bed_heater < BED_CHECK_INTERVAL)
+        return;
+    previous_millis_bed_heater = _millis();
+#endif
+
+#if TEMP_SENSOR_BED != 0
+
+#ifdef PIDTEMPBED
+    pid_input = current;
+
+#ifndef PID_OPENLOOP
+    pid_error_bed = target - pid_input;
+    pTerm_bed = cs.bedKp * pid_error_bed;
+    temp_iState_bed += pid_error_bed;
+    temp_iState_bed = constrain(temp_iState_bed, temp_iState_min_bed, temp_iState_max_bed);
+    iTerm_bed = cs.bedKi * temp_iState_bed;
+
+    //PID_K1 defined in Configuration.h in the PID settings
+#define K2 (1.0-PID_K1)
+    dTerm_bed= (cs.bedKd * (pid_input - temp_dState_bed))*K2 + (PID_K1 * dTerm_bed);
+    temp_dState_bed = pid_input;
+
+    pid_output = pTerm_bed + iTerm_bed - dTerm_bed;
+    if (pid_output > MAX_BED_POWER) {
+        if (pid_error_bed > 0 )  temp_iState_bed -= pid_error_bed; // conditional un-integration
+        pid_output=MAX_BED_POWER;
+    } else if (pid_output < 0){
+        if (pid_error_bed < 0 )  temp_iState_bed -= pid_error_bed; // conditional un-integration
+        pid_output=0;
+    }
+
+#else
+    pid_output = constrain(target, 0, MAX_BED_POWER);
+#endif //PID_OPENLOOP
+
+    if(current < BED_MAXTEMP)
+    {
+        soft_pwm_bed = (int)pid_output >> 1;
+        timer02_set_pwm0(soft_pwm_bed << 1);
+    }
+    else
+    {
+        soft_pwm_bed = 0;
+        timer02_set_pwm0(soft_pwm_bed << 1);
+    }
+
+#elif !defined(BED_LIMIT_SWITCHING)
+    // Check if temperature is within the correct range
+    if(current < BED_MAXTEMP)
+    {
+        if(current >= target)
+        {
+            soft_pwm_bed = 0;
+            timer02_set_pwm0(soft_pwm_bed << 1);
+        }
+        else
+        {
+            soft_pwm_bed = MAX_BED_POWER>>1;
+            timer02_set_pwm0(soft_pwm_bed << 1);
+        }
+    }
+    else
+    {
+        soft_pwm_bed = 0;
+        timer02_set_pwm0(soft_pwm_bed << 1);
+        WRITE(HEATER_BED_PIN,LOW);
+    }
+#else //#ifdef BED_LIMIT_SWITCHING
+    // Check if temperature is within the correct band
+    if(current < BED_MAXTEMP)
+    {
+        if(current > target + BED_HYSTERESIS)
+        {
+            soft_pwm_bed = 0;
+            timer02_set_pwm0(soft_pwm_bed << 1);
+        }
+        else if(current <= target - BED_HYSTERESIS)
+        {
+            soft_pwm_bed = MAX_BED_POWER>>1;
+            timer02_set_pwm0(soft_pwm_bed << 1);
+        }
+    }
+    else
+    {
+        soft_pwm_bed = 0;
+        timer02_set_pwm0(soft_pwm_bed << 1);
+        WRITE(HEATER_BED_PIN,LOW);
+    }
+#endif //BED_LIMIT_SWITCHING
+
+    if(target==0)
+    {
+        soft_pwm_bed = 0;
+        timer02_set_pwm0(soft_pwm_bed << 1);
+    }
+#endif //TEMP_SENSOR_BED
+}
+
+// ISR-safe temperatures
+static volatile bool adc_values_ready = false;
+float current_temperature_isr[EXTRUDERS];
+int target_temperature_isr[EXTRUDERS];
+float current_temperature_bed_isr;
+int target_temperature_bed_isr;
+#ifdef PINDA_THERMISTOR
+float current_temperature_pinda_isr;
+#endif
+#ifdef AMBIENT_THERMISTOR
+float current_temperature_ambient_isr;
+#endif
+
+// ISR callback from adc when sampling finished
+void adc_callback()
+{
+    current_temperature_raw[0] = adc_values[ADC_PIN_IDX(TEMP_0_PIN)]; //heater
+    current_temperature_bed_raw = adc_values[ADC_PIN_IDX(TEMP_BED_PIN)];
+#ifdef PINDA_THERMISTOR
+    current_temperature_raw_pinda = adc_values[ADC_PIN_IDX(TEMP_PINDA_PIN)];
+#endif //PINDA_THERMISTOR
+#ifdef AMBIENT_THERMISTOR
+    current_temperature_raw_ambient = adc_values[ADC_PIN_IDX(TEMP_AMBIENT_PIN)]; // 5->6
+#endif //AMBIENT_THERMISTOR
+#ifdef VOLT_PWR_PIN
+    current_voltage_raw_pwr = adc_values[ADC_PIN_IDX(VOLT_PWR_PIN)];
+#endif
+#ifdef VOLT_BED_PIN
+    current_voltage_raw_bed = adc_values[ADC_PIN_IDX(VOLT_BED_PIN)]; // 6->9
+#endif
+#ifdef IR_SENSOR_ANALOG
+    current_voltage_raw_IR = adc_values[ADC_PIN_IDX(VOLT_IR_PIN)];
+#endif //IR_SENSOR_ANALOG
+    adc_values_ready = true;
+}
+
+static void setCurrentTemperaturesFromIsr()
+{
+    for(uint8_t e=0;e<EXTRUDERS;e++)
+        current_temperature[e] = current_temperature_isr[e];
+    current_temperature_bed = current_temperature_bed_isr;
+#ifdef PINDA_THERMISTOR
+    current_temperature_pinda = current_temperature_pinda_isr;
+#endif
+#ifdef AMBIENT_THERMISTOR
+    current_temperature_ambient = current_temperature_ambient_isr;
+#endif
+}
+
+static void setIsrTargetTemperatures()
+{
+    for(uint8_t e=0;e<EXTRUDERS;e++)
+        target_temperature_isr[e] = target_temperature[e];
+    target_temperature_bed_isr = target_temperature_bed;
+}
+
+/* Synchronize temperatures:
+   - fetch updated values from temp_mgr_isr to current values
+   - update target temperatures for temp_mgr_isr regulation *if* no temperature error is set
+   This function is blocking: check temp_meas_ready before calling! */
+static void updateTemperatures()
+{
+    TempMgrGuard temp_mgr_guard;
+    setCurrentTemperaturesFromIsr();
+    if(!temp_error_state.v) {
+        // refuse to update target temperatures in any error condition!
+        setIsrTargetTemperatures();
+    }
+    temp_meas_ready = false;
+}
+
+/* Convert raw values into actual temperatures for temp_mgr. The raw values are created in the ADC
+   interrupt context, while this function runs from temp_mgr_isr which *is* preemptible as
+   analog2temp is relatively slow */
+static void setIsrTemperaturesFromRawValues()
+{
+    for(uint8_t e=0;e<EXTRUDERS;e++)
+        current_temperature_isr[e] = analog2temp(current_temperature_raw[e], e);
+    current_temperature_bed_isr = analog2tempBed(current_temperature_bed_raw);
+#ifdef PINDA_THERMISTOR
+    current_temperature_pinda_isr = analog2tempBed(current_temperature_raw_pinda);
+#endif
+#ifdef AMBIENT_THERMISTOR
+    current_temperature_ambient_isr = analog2tempAmbient(current_temperature_raw_ambient); //thermistor for ambient is NTCG104LH104JT1 (2000)
+#endif
+    temp_meas_ready = true;
+}
+
+static void temp_mgr_pid()
+{
+    for(uint8_t e = 0; e < EXTRUDERS; e++)
+        pid_heater(e, current_temperature_isr[e], target_temperature_isr[e]);
+    pid_bed(current_temperature_bed_isr, target_temperature_bed_isr);
+}
+
+static void check_temp_runaway()
+{
+#ifdef TEMP_RUNAWAY_EXTRUDER_HYSTERESIS
+    for(uint8_t e = 0; e < EXTRUDERS; e++)
+        temp_runaway_check(e+1, target_temperature_isr[e], current_temperature_isr[e], soft_pwm[e], false);
+#endif
+#ifdef TEMP_RUNAWAY_BED_HYSTERESIS
+    temp_runaway_check(0, target_temperature_bed_isr, current_temperature_bed_isr, soft_pwm_bed, true);
+#endif
+}
+
+static void check_temp_raw();
+
+static void temp_mgr_isr()
+{
+    // update *_isr temperatures from raw values for PID regulation
+    setIsrTemperaturesFromRawValues();
+
+    // clear the error assertion flag before checking again
+    temp_error_state.assert = false;
+    check_temp_raw(); // check min/max temp using raw values
+    check_temp_runaway(); // classic temperature hysteresis check
+#ifdef TEMP_MODEL
+    temp_model::check(); // model-based heater check
+#ifdef TEMP_MODEL_DEBUG
+    temp_model::log_isr();
+#endif
+#endif
+
+    // PID regulation
+    if (pid_tuning_finished)
+        temp_mgr_pid();
+}
+
+ISR(TIMERx_COMPA_vect)
+{
+    // immediately schedule a new conversion
+    if(adc_values_ready != true) return;
+    adc_values_ready = false;
+    adc_start_cycle();
+
+    // run temperature management with interrupts enabled to reduce latency
+    DISABLE_TEMP_MGR_INTERRUPT();
+    NONATOMIC_BLOCK(NONATOMIC_FORCEOFF) {
+        temp_mgr_isr();
+    }
+    ENABLE_TEMP_MGR_INTERRUPT();
+}
+
+void disable_heater()
+{
+  setAllTargetHotends(0);
+  setTargetBed(0);
+
+  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+      // propagate all values down the chain
+      setIsrTargetTemperatures();
+      temp_mgr_pid();
+
+      // we can't call soft_pwm_core directly to toggle the pins as it would require removing the inline
+      // attribute, so disable each pin individually
+#if defined(HEATER_0_PIN) && HEATER_0_PIN > -1 && EXTRUDERS > 0
+      WRITE(HEATER_0_PIN,LOW);
+#endif
+#if defined(HEATER_1_PIN) && HEATER_1_PIN > -1 && EXTRUDERS > 1
+      WRITE(HEATER_1_PIN,LOW);
+#endif
+#if defined(HEATER_2_PIN) && HEATER_2_PIN > -1 && EXTRUDERS > 2
+      WRITE(HEATER_2_PIN,LOW);
+#endif
+#if defined(HEATER_BED_PIN) && HEATER_BED_PIN > -1
+      // TODO: this doesn't take immediate effect!
+      timer02_set_pwm0(0);
+      bedPWMDisabled = 0;
+#endif
+  }
+}
+
+static void check_min_temp_raw()
+{
+    static bool bCheckingOnHeater = false; // state variable, which allows to short no-checking delay (is set, when temperature is (first time) over heaterMintemp)
+    static bool bCheckingOnBed    = false; // state variable, which allows to short no-checking delay (is set, when temperature is (first time) over bedMintemp)
+    static ShortTimer oTimer4minTempHeater;
+    static ShortTimer oTimer4minTempBed;
+
+#ifdef AMBIENT_THERMISTOR
+#ifdef AMBIENT_MINTEMP
+    // we need to check ambient temperature
+    check_min_temp_ambient();
+#endif
+#if AMBIENT_RAW_LO_TEMP > AMBIENT_RAW_HI_TEMP
+    if(current_temperature_raw_ambient>(OVERSAMPLENR*MINTEMP_MINAMBIENT_RAW)) // thermistor is NTC type
+#else
+    if(current_temperature_raw_ambient=<(OVERSAMPLENR*MINTEMP_MINAMBIENT_RAW))
+#endif
+    {
+        // ambient temperature is low
+#endif //AMBIENT_THERMISTOR
+        // *** 'common' part of code for MK2.5 & MK3
+        // * nozzle checking
+        if(target_temperature_isr[active_extruder]>minttemp[active_extruder]) {
+            // ~ nozzle heating is on
+            bCheckingOnHeater=bCheckingOnHeater||(current_temperature_isr[active_extruder]>(minttemp[active_extruder]+TEMP_HYSTERESIS)); // for eventually delay cutting
+            if(oTimer4minTempHeater.expired(HEATER_MINTEMP_DELAY)||(!oTimer4minTempHeater.running())||bCheckingOnHeater) {
+                bCheckingOnHeater=true;   // not necessary
+                check_min_temp_heater0(); // delay is elapsed or temperature is/was over minTemp => periodical checking is active
+            }
+        }
+        else {
+            // ~ nozzle heating is off
+            oTimer4minTempHeater.start();
+            bCheckingOnHeater=false;
+        }
+        // * bed checking
+        if(target_temperature_bed_isr>BED_MINTEMP) {
+            // ~ bed heating is on
+            bCheckingOnBed=bCheckingOnBed||(current_temperature_bed_isr>(BED_MINTEMP+TEMP_HYSTERESIS)); // for eventually delay cutting
+            if(oTimer4minTempBed.expired(BED_MINTEMP_DELAY)||(!oTimer4minTempBed.running())||bCheckingOnBed) {
+                bCheckingOnBed=true;  // not necessary
+                check_min_temp_bed(); // delay is elapsed or temperature is/was over minTemp => periodical checking is active
+            }
+        }
+        else {
+            // ~ bed heating is off
+            oTimer4minTempBed.start();
+            bCheckingOnBed=false;
+        }
+        // *** end of 'common' part
+#ifdef AMBIENT_THERMISTOR
+    }
+    else {
+        // ambient temperature is standard
+        check_min_temp_heater0();
+        check_min_temp_bed();
+    }
+#endif //AMBIENT_THERMISTOR
+}
+
+static void check_temp_raw()
+{
+    // order is relevant: check_min_temp_raw requires max to be reliable due to
+    // ambient temperature being used for low handling temperatures
+    check_max_temp_raw();
+    check_min_temp_raw();
+}
+
+#ifdef TEMP_MODEL
+namespace temp_model {
+
+void model_data::reset(uint8_t heater_pwm _UNUSED, uint8_t fan_pwm _UNUSED,
+    float heater_temp _UNUSED, float ambient_temp _UNUSED)
+{
+    // pre-compute invariant values
+    C_i = (TEMP_MGR_INTV / C);
+    warn_s = warn * TEMP_MGR_INTV;
+    err_s = err * TEMP_MGR_INTV;
+
+    // initial values
+    for(uint8_t i = 0; i != TEMP_MODEL_LAG_SIZE; ++i)
+        dT_lag_buf[i] = NAN;
+    dT_lag_idx = 0;
+    dT_err_prev = 0;
+    T_prev = NAN;
+
+    // clear the initialization flag
+    flag_bits.uninitialized = false;
+}
+
+static constexpr float iir_mul(const float a, const float b, const float f, const float nanv)
+{
+    const float a_ = !isnan(a) ? a : nanv;
+    return (a_ * (1.f - f)) + (b * f);
+}
+
+void model_data::step(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp)
+{
+    constexpr float soft_pwm_inv = 1. / ((1 << 7) - 1);
+
+    // input values
+    const float heater_scale = soft_pwm_inv * heater_pwm;
+    const float cur_heater_temp = heater_temp;
+    const float cur_ambient_temp = ambient_temp + Ta_corr;
+    const float cur_R = R[fan_pwm]; // resistance at current fan power (K/W)
+
+    float dP = P * heater_scale; // current power [W]
+    float dPl = (cur_heater_temp - cur_ambient_temp) / cur_R; // [W] leakage power
+    float dT = (dP - dPl) * C_i; // expected temperature difference (K)
+
+    // filter and lag dT
+    uint8_t dT_next_idx = (dT_lag_idx == (TEMP_MODEL_LAG_SIZE - 1) ? 0: dT_lag_idx + 1);
+    float dT_lag = dT_lag_buf[dT_next_idx];
+    float dT_lag_prev = dT_lag_buf[dT_lag_idx];
+    float dT_f = iir_mul(dT_lag_prev, dT, TEMP_MODEL_fS, dT);
+    dT_lag_buf[dT_next_idx] = dT_f;
+    dT_lag_idx = dT_next_idx;
+
+    // calculate and filter dT_err
+    float dT_err = (cur_heater_temp - T_prev) - dT_lag;
+    float dT_err_f = iir_mul(dT_err_prev, dT_err, TEMP_MODEL_fE, 0.);
+    T_prev = cur_heater_temp;
+    dT_err_prev = dT_err_f;
+
+    // check and trigger errors
+    flag_bits.error = (fabsf(dT_err_f) > err_s);
+    flag_bits.warning = (fabsf(dT_err_f) > warn_s);
+}
+
+// verify calibration status and trigger a model reset if valid
+void setup()
+{
+    if(!calibrated()) enabled = false;
+    data.flag_bits.uninitialized = true;
+}
+
+bool calibrated()
+{
+    if(!(data.P >= 0)) return false;
+    if(!(data.C >= 0)) return false;
+    if(!(data.Ta_corr != NAN)) return false;
+    for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i) {
+        if(!(temp_model::data.R[i] >= 0))
+            return false;
+    }
+    if(!(data.warn != NAN)) return false;
+    if(!(data.err != NAN)) return false;
+    return true;
+}
+
+void check()
+{
+    if(!enabled) return;
+
+    uint8_t heater_pwm = soft_pwm[0];
+    uint8_t fan_pwm = soft_pwm_fan;
+    float heater_temp = current_temperature_isr[0];
+    float ambient_temp = current_temperature_ambient_isr;
+
+    // check if a reset is required to seed the model: this needs to be done with valid
+    // ADC values, so we can't do that directly in init()
+    if(data.flag_bits.uninitialized)
+        data.reset(heater_pwm, fan_pwm, heater_temp, ambient_temp);
+
+    // step the model
+    data.step(heater_pwm, fan_pwm, heater_temp, ambient_temp);
+
+    // handle errors
+    if(data.flag_bits.error)
+        set_temp_error(TempErrorSource::hotend, 0, TempErrorType::model);
+
+    // handle warning conditions as lower-priority but with greater feedback
+    warning_state.assert = data.flag_bits.warning;
+    if(warning_state.assert) {
+        warning_state.warning = true;
+        warning_state.dT_err = temp_model::data.dT_err_prev;
+    }
+}
+
+void handle_warning()
+{
+    // update values
+    float warn = data.warn;
+    float dT_err;
+    {
+        TempMgrGuard temp_mgr_guard;
+        dT_err = warning_state.dT_err;
+    }
+    dT_err /= TEMP_MGR_INTV; // per-sample => K/s
+
+    printf_P(PSTR("TM: error |%f|>%f\n"), (double)dT_err, (double)warn);
+
+    static bool first = true;
+    if(warning_state.assert) {
+        if (first) {
+            if(warn_beep) {
+                lcd_setalertstatuspgm(_T(MSG_THERMAL_ANOMALY), LCD_STATUS_INFO);
+                WRITE(BEEPER, HIGH);
+            }
+        } else {
+            if(warn_beep) TOGGLE(BEEPER);
+        }
+    } else {
+        // warning cleared, reset state
+        warning_state.warning = false;
+        if(warn_beep) WRITE(BEEPER, LOW);
+        first = true;
+    }
+}
+
+#ifdef TEMP_MODEL_DEBUG
+void log_usr()
+{
+    if(!log_buf.enabled) return;
+
+    uint8_t counter = log_buf.entry.counter;
+    if (counter == log_buf.serial) return;
+
+    int8_t delta_ms;
+    uint8_t cur_pwm;
+
+    // avoid strict-aliasing warnings
+    union { float cur_temp; uint32_t cur_temp_b; };
+    union { float cur_amb; uint32_t cur_amb_b; };
+
+    {
+        TempMgrGuard temp_mgr_guard;
+        delta_ms = log_buf.entry.delta_ms;
+        counter = log_buf.entry.counter;
+        cur_pwm = log_buf.entry.cur_pwm;
+        cur_temp = log_buf.entry.cur_temp;
+        cur_amb = log_buf.entry.cur_amb;
+    }
+
+    uint8_t d = counter - log_buf.serial;
+    log_buf.serial = counter;
+
+    printf_P(PSTR("TML %d %d %x %lx %lx\n"), (unsigned)d - 1, (int)delta_ms + 1,
+        (int)cur_pwm, (unsigned long)cur_temp_b, (unsigned long)cur_amb_b);
+}
+
+void log_isr()
+{
+    if(!log_buf.enabled) return;
+
+    uint32_t stamp = _millis();
+    uint8_t delta_ms = stamp - log_buf.entry.stamp - (uint32_t)(TEMP_MGR_INTV * 1000);
+    log_buf.entry.stamp = stamp;
+
+    ++log_buf.entry.counter;
+    log_buf.entry.delta_ms = delta_ms;
+    log_buf.entry.cur_pwm = soft_pwm[0];
+    log_buf.entry.cur_temp = current_temperature_isr[0];
+    log_buf.entry.cur_amb = current_temperature_ambient_isr;
+}
+#endif
+
+} // namespace temp_model
+
+static void temp_model_reset_enabled(bool enabled)
+{
+    TempMgrGuard temp_mgr_guard;
+    temp_model::enabled = enabled;
+    temp_model::data.flag_bits.uninitialized = true;
+}
+
+void temp_model_set_enabled(bool enabled)
+{
+    // set the enabled flag
+    {
+        TempMgrGuard temp_mgr_guard;
+        temp_model::enabled = enabled;
+        temp_model::setup();
+    }
+
+    // verify that the model has been enabled
+    if(enabled && !temp_model::enabled)
+        SERIAL_ECHOLNPGM("TM: invalid parameters, cannot enable");
+}
+
+void temp_model_set_warn_beep(bool enabled)
+{
+    temp_model::warn_beep = enabled;
+}
+
+void temp_model_set_params(float C, float P, float Ta_corr, float warn, float err)
+{
+    TempMgrGuard temp_mgr_guard;
+
+    if(!isnan(C) && C > 0) temp_model::data.C = C;
+    if(!isnan(P) && P > 0) temp_model::data.P = P;
+    if(!isnan(Ta_corr)) temp_model::data.Ta_corr = Ta_corr;
+    if(!isnan(err) && err > 0) temp_model::data.err = err;
+    if(!isnan(warn) && warn > 0) temp_model::data.warn = warn;
+
+    // ensure warn <= err
+    if (temp_model::data.warn > temp_model::data.err)
+        temp_model::data.warn = temp_model::data.err;
+
+    temp_model::setup();
+}
+
+void temp_model_set_resistance(uint8_t index, float R)
+{
+    if(index >= TEMP_MODEL_R_SIZE || R <= 0)
+        return;
+
+    TempMgrGuard temp_mgr_guard;
+    temp_model::data.R[index] = R;
+    temp_model::setup();
+}
+
+void temp_model_report_settings()
+{
+    SERIAL_ECHO_START;
+    SERIAL_ECHOLNPGM("Temperature Model settings:");
+    for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i)
+        printf_P(PSTR("%S  M310 I%u R%.2f\n"), echomagic, (unsigned)i, (double)temp_model::data.R[i]);
+    printf_P(PSTR("%S  M310 P%.2f C%.2f S%u B%u E%.2f W%.2f T%.2f\n"),
+        echomagic, (double)temp_model::data.P, (double)temp_model::data.C,
+        (unsigned)temp_model::enabled, (unsigned)temp_model::warn_beep,
+        (double)temp_model::data.err, (double)temp_model::data.warn,
+        (double)temp_model::data.Ta_corr);
+}
+
+void temp_model_reset_settings()
+{
+    TempMgrGuard temp_mgr_guard;
+
+    temp_model::data.P = TEMP_MODEL_P;
+    temp_model::data.C = TEMP_MODEL_C;
+    temp_model::data.R[0] = TEMP_MODEL_R;
+    for(uint8_t i = 1; i != TEMP_MODEL_R_SIZE; ++i)
+        temp_model::data.R[i] = NAN;
+    temp_model::data.Ta_corr = TEMP_MODEL_Ta_corr;
+    temp_model::data.warn = TEMP_MODEL_W;
+    temp_model::data.err = TEMP_MODEL_E;
+    temp_model::warn_beep = true;
+    temp_model::enabled = false;
+}
+
+void temp_model_load_settings()
+{
+    static_assert(TEMP_MODEL_R_SIZE == 16); // ensure we don't desync with the eeprom table
+    TempMgrGuard temp_mgr_guard;
+
+    temp_model::enabled = eeprom_read_byte((uint8_t*)EEPROM_TEMP_MODEL_ENABLE);
+    temp_model::data.P = eeprom_read_float((float*)EEPROM_TEMP_MODEL_P);
+    temp_model::data.C = eeprom_read_float((float*)EEPROM_TEMP_MODEL_C);
+    for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i)
+        temp_model::data.R[i] = eeprom_read_float((float*)EEPROM_TEMP_MODEL_R + i);
+    temp_model::data.Ta_corr = eeprom_read_float((float*)EEPROM_TEMP_MODEL_Ta_corr);
+    temp_model::data.warn = eeprom_read_float((float*)EEPROM_TEMP_MODEL_W);
+    temp_model::data.err = eeprom_read_float((float*)EEPROM_TEMP_MODEL_E);
+
+    if(!temp_model::calibrated()) {
+        SERIAL_ECHOLNPGM("TM: stored calibration invalid, resetting");
+        temp_model_reset_settings();
+    }
+    temp_model::setup();
+}
+
+void temp_model_save_settings()
+{
+    eeprom_update_byte((uint8_t*)EEPROM_TEMP_MODEL_ENABLE, temp_model::enabled);
+    eeprom_update_float((float*)EEPROM_TEMP_MODEL_P, temp_model::data.P);
+    eeprom_update_float((float*)EEPROM_TEMP_MODEL_C, temp_model::data.C);
+    for(uint8_t i = 0; i != TEMP_MODEL_R_SIZE; ++i)
+        eeprom_update_float((float*)EEPROM_TEMP_MODEL_R + i, temp_model::data.R[i]);
+    eeprom_update_float((float*)EEPROM_TEMP_MODEL_Ta_corr, temp_model::data.Ta_corr);
+    eeprom_update_float((float*)EEPROM_TEMP_MODEL_W, temp_model::data.warn);
+    eeprom_update_float((float*)EEPROM_TEMP_MODEL_E, temp_model::data.err);
+}
+
+namespace temp_model_cal {
+
+// set current fan speed for both front/backend
+static __attribute__((noinline)) void set_fan_speed(uint8_t fan_speed)
+{
+    fanSpeed = fan_speed;
+#ifdef FAN_SOFT_PWM
+    fanSpeedSoftPwm = fan_speed;
+#endif
+}
+
+static void waiting_handler()
+{
+    manage_heater();
+    host_keepalive();
+    host_autoreport();
+    checkFans();
+    lcd_update(0);
+}
+
+static void wait(unsigned ms)
+{
+    unsigned long mark = _millis() + ms;
+    while(_millis() < mark) {
+        if(temp_error_state.v) break;
+        waiting_handler();
+    }
+}
+
+static void __attribute__((noinline)) wait_temp()
+{
+    while(current_temperature[0] < (target_temperature[0] - TEMP_HYSTERESIS)) {
+        if(temp_error_state.v) break;
+        waiting_handler();
+    }
+}
+
+static void cooldown(float temp)
+{
+    uint8_t old_speed = fanSpeed;
+    set_fan_speed(255);
+    while(current_temperature[0] >= temp) {
+        if(temp_error_state.v) break;
+        float ambient = current_temperature_ambient + temp_model::data.Ta_corr;
+        if(current_temperature[0] < (ambient + TEMP_HYSTERESIS)) {
+            // do not get stuck waiting very close to ambient temperature
+            break;
+        }
+        waiting_handler();
+    }
+    set_fan_speed(old_speed);
+}
+
+static uint16_t record(uint16_t samples = REC_BUFFER_SIZE) {
+    TempMgrGuard temp_mgr_guard;
+
+    uint16_t pos = 0;
+    while(pos < samples) {
+        if(!TEMP_MGR_INT_FLAG_STATE()) {
+            // temperatures not ready yet, just manage heaters while waiting to reduce jitter
+            manage_heater();
+            continue;
+        }
+        TEMP_MGR_INT_FLAG_CLEAR();
+
+        // manually repeat what the regular isr would do
+        if(adc_values_ready != true) continue;
+        adc_values_ready = false;
+        adc_start_cycle();
+        temp_mgr_isr();
+
+        // stop recording for an hard error condition
+        if(temp_error_state.v)
+            return 0;
+
+        // record a new entry
+        rec_entry& entry = rec_buffer[pos];
+        entry.temp = current_temperature_isr[0];
+        entry.pwm = soft_pwm[0];
+        ++pos;
+
+        // it's now safer to give regular serial/lcd updates a shot
+        waiting_handler();
+    }
+
+    return pos;
+}
+
+static float cost_fn(uint16_t samples, float* const var, float v, uint8_t fan_pwm, float ambient)
+{
+    *var = v;
+    temp_model::data.reset(rec_buffer[0].pwm, fan_pwm, rec_buffer[0].temp, ambient);
+    float err = 0;
+    uint16_t cnt = 0;
+    for(uint16_t i = 1; i < samples; ++i) {
+        temp_model::data.step(rec_buffer[i].pwm, fan_pwm, rec_buffer[i].temp, ambient);
+        float err_v = temp_model::data.dT_err_prev;
+        if(!isnan(err_v)) {
+            err += err_v * err_v;
+            ++cnt;
+        }
+    }
+    return cnt ? (err / cnt) : NAN;
+}
+
+constexpr float GOLDEN_RATIO = 0.6180339887498949;
+
+static void update_section(float points[2], const float bounds[2])
+{
+    float d = GOLDEN_RATIO * (bounds[1] - bounds[0]);
+    points[0] = bounds[0] + d;
+    points[1] = bounds[1] - d;
+}
+
+static float estimate(uint16_t samples,
+    float* const var, float min, float max,
+    float thr, uint16_t max_itr,
+    uint8_t fan_pwm, float ambient)
+{
+    // during estimation we alter the model values without an extra copy to conserve memory
+    // so we cannot keep the main checker active until a value has been found
+    bool was_enabled = temp_model::enabled;
+    temp_model_reset_enabled(false);
+
+    float orig = *var;
+    float e = NAN;
+    float points[2];
+    float bounds[2] = {min, max};
+    update_section(points, bounds);
+
+    for(uint8_t it = 0; it != max_itr; ++it) {
+        float c1 = cost_fn(samples, var, points[0], fan_pwm, ambient);
+        float c2 = cost_fn(samples, var, points[1], fan_pwm, ambient);
+        bool dir = (c2 < c1);
+        bounds[dir] = points[!dir];
+        update_section(points, bounds);
+        float x = points[!dir];
+        e = (1-GOLDEN_RATIO) * fabsf((bounds[0]-bounds[1]) / x);
+
+        printf_P(PSTR("TM iter:%u v:%.2f e:%.3f\n"), it, x, e);
+        if(e < thr) {
+            if(x == min || x == max) {
+                // real value likely outside of the search boundaries
+                break;
+            }
+
+            *var = x;
+            temp_model_reset_enabled(was_enabled);
+            return e;
+        }
+    }
+
+    SERIAL_ECHOLNPGM("TM estimation did not converge");
+    *var = orig;
+    temp_model_reset_enabled(was_enabled);
+    return NAN;
+}
+
+static bool autotune(int16_t cal_temp)
+{
+    uint16_t samples;
+    float e;
+
+    // bootstrap C/R values without fan
+    set_fan_speed(0);
+
+    for(uint8_t i = 0; i != 2; ++i) {
+        const char* PROGMEM verb = (i == 0? PSTR("initial"): PSTR("refining"));
+
+        target_temperature[0] = 0;
+        if(current_temperature[0] >= TEMP_MODEL_CAL_Tl) {
+            printf_P(PSTR("TM: cooling down to %dC\n"), TEMP_MODEL_CAL_Tl);
+            cooldown(TEMP_MODEL_CAL_Tl);
+            wait(10000);
+        }
+
+        printf_P(PSTR("TM: %S C estimation\n"), verb);
+        target_temperature[0] = cal_temp;
+        samples = record();
+        if(temp_error_state.v || !samples)
+            return true;
+
+        // we need a high R value for the initial C guess
+        if(isnan(temp_model::data.R[0]))
+            temp_model::data.R[0] = TEMP_MODEL_Rh;
+
+        e = estimate(samples, &temp_model::data.C,
+            TEMP_MODEL_Cl, TEMP_MODEL_Ch, TEMP_MODEL_C_thr, TEMP_MODEL_C_itr,
+            0, current_temperature_ambient);
+        if(isnan(e))
+            return true;
+
+        wait_temp();
+        if(i) break; // we don't need to refine R
+        wait(30000); // settle PID regulation
+
+        printf_P(PSTR("TM: %S R estimation @ %dC\n"), verb, cal_temp);
+        samples = record();
+        if(temp_error_state.v || !samples)
+            return true;
+
+        e = estimate(samples, &temp_model::data.R[0],
+            TEMP_MODEL_Rl, TEMP_MODEL_Rh, TEMP_MODEL_R_thr, TEMP_MODEL_R_itr,
+            0, current_temperature_ambient);
+        if(isnan(e))
+            return true;
+    }
+
+    // Estimate fan losses at regular intervals, starting from full speed to avoid low-speed
+    // kickstart issues, although this requires us to wait more for the PID stabilization.
+    // Normally exhibits logarithmic behavior with the stock fan+shroud, so the shorter interval
+    // at lower speeds is helpful to increase the resolution of the interpolation.
+    set_fan_speed(255);
+    wait(30000);
+
+    for(int8_t i = TEMP_MODEL_R_SIZE - 1; i > 0; i -= TEMP_MODEL_CAL_R_STEP) {
+        uint8_t speed = 256 / TEMP_MODEL_R_SIZE * (i + 1) - 1;
+        set_fan_speed(speed);
+        wait(10000);
+
+        printf_P(PSTR("TM: R[%u] estimation\n"), (unsigned)i);
+        samples = record();
+        if(temp_error_state.v || !samples)
+            return true;
+
+        // a fixed fan pwm (the norminal value) is used here, as soft_pwm_fan will be modified
+        // during fan measurements and we'd like to include that skew during normal operation.
+        e = estimate(samples, &temp_model::data.R[i],
+            TEMP_MODEL_Rl, temp_model::data.R[0], TEMP_MODEL_R_thr, TEMP_MODEL_R_itr,
+            i, current_temperature_ambient);
+        if(isnan(e))
+            return true;
+    }
+
+    // interpolate remaining steps to speed-up calibration
+    // TODO: verify that the sampled values are monotically increasing?
+    int8_t next = TEMP_MODEL_R_SIZE - 1;
+    for(uint8_t i = TEMP_MODEL_R_SIZE - 2; i != 0; --i) {
+        if(!((TEMP_MODEL_R_SIZE - i - 1) % TEMP_MODEL_CAL_R_STEP)) {
+            next = i;
+            continue;
+        }
+        int8_t prev = next - TEMP_MODEL_CAL_R_STEP;
+        if(prev < 0) prev = 0;
+        float f = (float)(i - prev) / TEMP_MODEL_CAL_R_STEP;
+        float d = (temp_model::data.R[next] - temp_model::data.R[prev]);
+        temp_model::data.R[i] = temp_model::data.R[prev] + d * f;
+    }
+
+    return false;
+}
+
+} // namespace temp_model_cal
+
+void temp_model_autotune(int16_t temp, bool selftest)
+{
+    if(moves_planned() || printer_active()) {
+        SERIAL_ECHOLNPGM("TM: printer needs to be idle for calibration");
+        return;
+    }
+
+    // lockout the printer during calibration
+    KEEPALIVE_STATE(IN_PROCESS);
+    menu_set_block(MENU_BLOCK_TEMP_MODEL_AUTOTUNE);
+    lcd_setstatuspgm(_i("Temp. model autotune")); ////MSG_TEMP_MODEL_AUTOTUNE c=20
+    lcd_return_to_status();
+
+    // set the model checking state during self-calibration
+    bool was_enabled = temp_model::enabled;
+    temp_model_reset_enabled(selftest);
+
+    SERIAL_ECHOLNPGM("TM: autotune start");
+    bool err = temp_model_cal::autotune(temp > 0 ? temp : TEMP_MODEL_CAL_Th);
+
+    // always reset temperature
+    disable_heater();
+
+    if(err) {
+        SERIAL_ECHOLNPGM("TM: autotune failed");
+        lcd_setstatuspgm(_i("TM autotune failed")); ////MSG_TM_AUTOTUNE_FAILED c=20
+        if(temp_error_state.v)
+            temp_model_cal::set_fan_speed(255);
+    } else {
+        lcd_setstatuspgm(MSG_WELCOME);
+        temp_model_cal::set_fan_speed(0);
+        temp_model_set_enabled(was_enabled);
+        temp_model_report_settings();
+    }
+
+    menu_unset_block(MENU_BLOCK_TEMP_MODEL_AUTOTUNE);
+}
+
+#ifdef TEMP_MODEL_DEBUG
+void temp_model_log_enable(bool enable)
+{
+    if(enable) {
+        TempMgrGuard temp_mgr_guard;
+        temp_model::log_buf.entry.stamp = _millis();
+    }
+    temp_model::log_buf.enabled = enable;
+}
+#endif
+#endif

+ 25 - 56
Firmware/temperature.h

@@ -22,29 +22,13 @@
 #define temperature_h 
 
 #include "Marlin.h"
-#include "planner.h"
-
-#include "stepper.h"
-
 #include "config.h"
 
-
-#ifdef SYSTEM_TIMER_2
-
-#define ENABLE_TEMPERATURE_INTERRUPT()  TIMSK2 |= (1<<OCIE2B)
-#define DISABLE_TEMPERATURE_INTERRUPT() TIMSK2 &= ~(1<<OCIE2B)
-
-#else //SYSTEM_TIMER_2
-
-#define ENABLE_TEMPERATURE_INTERRUPT()  TIMSK0 |= (1<<OCIE0B)
-#define DISABLE_TEMPERATURE_INTERRUPT() TIMSK0 &= ~(1<<OCIE0B)
-
-#endif //SYSTEM_TIMER_2
-
-
 // public functions
-void tp_init();  //initialize the heating
+void soft_pwm_init(); //initialize the soft pwm isr
+void temp_mgr_init(); //initialize the temperature handler
 void manage_heater(); //it is critical that this is called periodically.
+bool get_temp_error(); //return true if any thermal error is set
 
 extern bool checkAllHotends(void);
 
@@ -82,20 +66,18 @@ extern int current_voltage_raw_bed;
 extern uint16_t current_voltage_raw_IR;
 #endif //IR_SENSOR_ANALOG
 
-#if defined(CONTROLLERFAN_PIN) && CONTROLLERFAN_PIN > -1
-  extern unsigned char soft_pwm_bed;
-#endif
-
 extern bool bedPWMDisabled;
 
 #ifdef PIDTEMP
   extern int pid_cycle, pid_number_of_cycles;
   extern float _Kp,_Ki,_Kd;
-  extern bool pid_tuning_finished;
   float scalePID_i(float i);
   float scalePID_d(float d);
   float unscalePID_i(float i);
   float unscalePID_d(float d);
+
+  bool pidTuningRunning(); // returns true if PID tuning is still running
+  void preparePidTuning(); // non-blocking call to set "pidTuningRunning" to true immediately
 #endif
 
 
@@ -156,8 +138,7 @@ FORCE_INLINE void setTargetHotend(const float &celsius, uint8_t extruder) {
 static inline void setTargetHotendSafe(const float &celsius, uint8_t extruder)
 {
     if (extruder<EXTRUDERS) {
-      target_temperature[extruder] = celsius;
-      resetPID(extruder);
+        setTargetHotend(celsius, extruder);
     }
 }
 
@@ -218,7 +199,7 @@ FORCE_INLINE bool isCoolingBed() {
 #define CHECK_ALL_HEATERS (checkAllHotends()||(target_temperature_bed!=0))
 
 int getHeaterPower(int heater);
-void disable_heater(); // Disable all heaters
+void disable_heater(); // Disable all heaters *instantaneously*
 void updatePID();
 
 
@@ -235,39 +216,27 @@ FORCE_INLINE void autotempShutdown(){
 
 void PID_autotune(float temp, int extruder, int ncycles);
 
-void setExtruderAutoFanState(uint8_t state);
-void checkExtruderAutoFans();
-
-
-#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
-
-enum { 
-	EFCE_OK = 0,   //!< normal operation, both fans are ok
-	EFCE_FIXED,    //!< previous fan error was fixed
-	EFCE_DETECTED, //!< fan error detected, but not reported yet
-	EFCE_REPORTED  //!< fan error detected and reported to LCD and serial
-};
-extern volatile uint8_t fan_check_error;
-
-void countFanSpeed();
-void checkFanSpeed();
-void fanSpeedError(unsigned char _fan);
-
-void check_fans();
+#ifdef TEMP_MODEL
+void temp_model_set_enabled(bool enabled);
+void temp_model_set_warn_beep(bool enabled);
+void temp_model_set_params(float C = NAN, float P = NAN, float Ta_corr = NAN, float warn = NAN, float err = NAN);
+void temp_model_set_resistance(uint8_t index, float R);
 
-#endif //(defined(TACH_0))
+void temp_model_report_settings();
+void temp_model_reset_settings();
+void temp_model_load_settings();
+void temp_model_save_settings();
 
-void check_min_temp();
-void check_max_temp();
+void temp_model_autotune(int16_t temp = 0, bool selftest = false);
 
-#ifdef EXTRUDER_ALTFAN_DETECT
-  extern bool extruder_altfan_detect();
-  extern void altfanOverride_toggle();
-  extern bool altfanOverride_get();
-#endif //EXTRUDER_ALTFAN_DETECT
+#ifdef TEMP_MODEL_DEBUG
+void temp_model_log_enable(bool enable);
+#endif
+#endif
 
-extern unsigned long extruder_autofan_last_check;
+#ifdef FAN_SOFT_PWM
+extern unsigned char fanSpeedSoftPwm;
+#endif
 extern uint8_t fanSpeedBckp;
-extern bool fan_measuring;
 
 #endif

+ 21 - 72
Firmware/tmc2130.cpp

@@ -11,7 +11,7 @@
 #include "Timer.h"
 
 #define TMC2130_GCONF_NORMAL 0x00000000 // spreadCycle
-#define TMC2130_GCONF_SGSENS 0x00003180 // spreadCycle with stallguard (stall activates DIAG0 and DIAG1 [pushpull])
+#define TMC2130_GCONF_SGSENS 0x00000180 // spreadCycle with stallguard (stall activates DIAG0 and DIAG1 [open collector])
 #define TMC2130_GCONF_SILENT 0x00000004 // stealthChop
 
 
@@ -66,11 +66,6 @@ tmc2130_chopper_config_t tmc2130_chopper_config[4] = {
 bool tmc2130_sg_stop_on_crash = true;
 uint8_t tmc2130_sg_diag_mask = 0x00;
 uint8_t tmc2130_sg_crash = 0;
-uint16_t tmc2130_sg_err[4] = {0, 0, 0, 0};
-uint16_t tmc2130_sg_cnt[4] = {0, 0, 0, 0};
-#ifdef DEBUG_CRASHDET_COUNTERS
-bool tmc2130_sg_change = false;
-#endif
 
 //used for triggering a periodic check (1s) of the overtemperature pre-warning flag at ~120C (+-20C)
 ShortTimer tmc2130_overtemp_timer;
@@ -157,15 +152,21 @@ void tmc2130_init(TMCInitParams params)
 	SET_OUTPUT(Y_TMC2130_CS);
 	SET_OUTPUT(Z_TMC2130_CS);
 	SET_OUTPUT(E0_TMC2130_CS);
+	
 	SET_INPUT(X_TMC2130_DIAG);
 	SET_INPUT(Y_TMC2130_DIAG);
 	SET_INPUT(Z_TMC2130_DIAG);
 	SET_INPUT(E0_TMC2130_DIAG);
+	WRITE(X_TMC2130_DIAG,HIGH);
+	WRITE(Y_TMC2130_DIAG,HIGH);
+	WRITE(Z_TMC2130_DIAG,HIGH);
+	WRITE(E0_TMC2130_DIAG,HIGH);
+	
 	for (uint_least8_t axis = 0; axis < 2; axis++) // X Y axes
 	{
 		tmc2130_setup_chopper(axis, tmc2130_mres[axis], tmc2130_current_h[axis], tmc2130_current_r[axis]);
 		tmc2130_wr(axis, TMC2130_REG_TPOWERDOWN, 0x00000000);
-		tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16));
+		tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
 		tmc2130_wr(axis, TMC2130_REG_TCOOLTHRS, (tmc2130_mode == TMC2130_MODE_SILENT)?0:__tcoolthrs(axis));
 		tmc2130_wr(axis, TMC2130_REG_GCONF, (tmc2130_mode == TMC2130_MODE_SILENT)?TMC2130_GCONF_SILENT:TMC2130_GCONF_SGSENS);
 		tmc2130_wr_PWMCONF(axis, tmc2130_pwm_ampl[axis], tmc2130_pwm_grad[axis], tmc2130_pwm_freq[axis], tmc2130_pwm_auto[axis], 0, 0);
@@ -179,7 +180,7 @@ void tmc2130_init(TMCInitParams params)
 #ifndef TMC2130_STEALTH_Z
 		tmc2130_wr(axis, TMC2130_REG_GCONF, TMC2130_GCONF_SGSENS);
 #else //TMC2130_STEALTH_Z
-		tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16));
+		tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
 		tmc2130_wr(axis, TMC2130_REG_TCOOLTHRS, (tmc2130_mode == TMC2130_MODE_SILENT)?0:__tcoolthrs(axis));
 		tmc2130_wr(axis, TMC2130_REG_GCONF, (tmc2130_mode == TMC2130_MODE_SILENT)?TMC2130_GCONF_SILENT:TMC2130_GCONF_SGSENS);
 		tmc2130_wr_PWMCONF(axis, tmc2130_pwm_ampl[axis], tmc2130_pwm_grad[axis], tmc2130_pwm_freq[axis], tmc2130_pwm_auto[axis], 0, 0);
@@ -210,15 +211,6 @@ void tmc2130_init(TMCInitParams params)
 #endif //TMC2130_STEALTH_E
 	}
 
-	tmc2130_sg_err[0] = 0;
-	tmc2130_sg_err[1] = 0;
-	tmc2130_sg_err[2] = 0;
-	tmc2130_sg_err[3] = 0;
-	tmc2130_sg_cnt[0] = 0;
-	tmc2130_sg_cnt[1] = 0;
-	tmc2130_sg_cnt[2] = 0;
-	tmc2130_sg_cnt[3] = 0;
-
 #ifdef TMC2130_LINEARITY_CORRECTION
 #ifdef TMC2130_LINEARITY_CORRECTION_XYZ
 	tmc2130_set_wave(X_AXIS, 247, tmc2130_wave_fac[X_AXIS]);
@@ -238,48 +230,22 @@ void tmc2130_init(TMCInitParams params)
 uint8_t tmc2130_sample_diag()
 {
 	uint8_t mask = 0;
-	if (READ(X_TMC2130_DIAG)) mask |= X_AXIS_MASK;
-	if (READ(Y_TMC2130_DIAG)) mask |= Y_AXIS_MASK;
-//	if (READ(Z_TMC2130_DIAG)) mask |= Z_AXIS_MASK;
-//	if (READ(E0_TMC2130_DIAG)) mask |= E_AXIS_MASK;
+	if (!READ(X_TMC2130_DIAG)) mask |= X_AXIS_MASK;
+	if (!READ(Y_TMC2130_DIAG)) mask |= Y_AXIS_MASK;
+//	if (!READ(Z_TMC2130_DIAG)) mask |= Z_AXIS_MASK;
+//	if (!READ(E0_TMC2130_DIAG)) mask |= E_AXIS_MASK;
 	return mask;
 }
 
 void tmc2130_st_isr()
 {
-	if (tmc2130_mode == TMC2130_MODE_SILENT || tmc2130_sg_stop_on_crash == false) return;
-	uint8_t crash = 0;
-	uint8_t diag_mask = tmc2130_sample_diag();
-//	for (uint8_t axis = X_AXIS; axis <= E_AXIS; axis++)
-	for (uint8_t axis = X_AXIS; axis <= Z_AXIS; axis++)
-	{
-		uint8_t mask = (X_AXIS_MASK << axis);
-		if (diag_mask & mask) tmc2130_sg_err[axis]++;
-		else
-			if (tmc2130_sg_err[axis] > 0) tmc2130_sg_err[axis]--;
-		if (tmc2130_sg_cnt[axis] < tmc2130_sg_err[axis])
-		{
-			tmc2130_sg_cnt[axis] = tmc2130_sg_err[axis];
-#ifdef DEBUG_CRASHDET_COUNTERS
-			tmc2130_sg_change = true;
-#endif
-			uint8_t sg_thr = 64;
-//			if (axis == Y_AXIS) sg_thr = 64;
-			if (tmc2130_sg_err[axis] >= sg_thr)
-			{
-				tmc2130_sg_err[axis] = 0;
-				crash |= mask;
-			}
-		}
-	}
-	if (tmc2130_sg_homing_axes_mask == 0)
-	{
-		if (tmc2130_sg_stop_on_crash && crash)
-		{
-			tmc2130_sg_crash = crash;
-			tmc2130_sg_stop_on_crash = false;
-			crashdet_stop_and_save_print();
-		}
+	if (tmc2130_mode == TMC2130_MODE_SILENT || tmc2130_sg_stop_on_crash == false || tmc2130_sg_homing_axes_mask != 0)
+		return;
+	uint8_t mask = tmc2130_sample_diag();
+	if (tmc2130_sg_stop_on_crash && mask) {
+		tmc2130_sg_crash = mask;
+		tmc2130_sg_stop_on_crash = false;
+		crashdet_stop_and_save_print();
 	}
 }
 
@@ -312,7 +278,6 @@ void tmc2130_home_enter(uint8_t axes_mask)
 			//Configuration to spreadCycle
 			tmc2130_wr(axis, TMC2130_REG_GCONF, TMC2130_GCONF_NORMAL);
 			tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr_home[axis]) << 16));
-//			tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
 			tmc2130_wr(axis, TMC2130_REG_TCOOLTHRS, __tcoolthrs(axis));
 			tmc2130_setup_chopper(axis, tmc2130_mres[axis], tmc2130_current_h[axis], tmc2130_current_r_home[axis]);
 			if (mask & (X_AXIS_MASK | Y_AXIS_MASK | Z_AXIS_MASK))
@@ -349,8 +314,7 @@ void tmc2130_home_exit()
 				{
 //					tmc2130_wr(axis, TMC2130_REG_GCONF, TMC2130_GCONF_NORMAL);
 					tmc2130_setup_chopper(axis, tmc2130_mres[axis], tmc2130_current_h[axis], tmc2130_current_r[axis]);
-//					tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
-					tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16));
+					tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
 					tmc2130_wr(axis, TMC2130_REG_TCOOLTHRS, __tcoolthrs(axis));
 					tmc2130_wr(axis, TMC2130_REG_GCONF, TMC2130_GCONF_SGSENS);
 				}
@@ -413,22 +377,7 @@ void tmc2130_check_overtemp()
 
 		}
 		tmc2130_overtemp_timer.start();
-#ifdef DEBUG_CRASHDET_COUNTERS
-		tmc2130_sg_change = true;
-#endif
-	}
-#ifdef DEBUG_CRASHDET_COUNTERS
-	if (tmc2130_sg_change)
-	{
-		for (int i = 0; i < 4; i++)
-		{
-			tmc2130_sg_change = false;
-			lcd_set_cursor(0 + i*4, 3);
-			lcd_print(itostr3(tmc2130_sg_cnt[i]));
-			lcd_print(' ');
-		}
 	}
-#endif //DEBUG_CRASHDET_COUNTERS
 }
 
 void tmc2130_setup_chopper(uint8_t axis, uint8_t mres, uint8_t current_h, uint8_t current_r)

+ 0 - 0
Firmware/twi.cpp


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio