Ver Fonte

Merge pull request #3552 from wavexx/temp_model_check

Thermal Model protection
DRracer há 2 anos atrás
pai
commit
0933fdb6fe

+ 0 - 1
.travis.yml

@@ -9,7 +9,6 @@ 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; }
   - rm Firmware/Configuration_prusa.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();
 

+ 2 - 0
Firmware/Dcodes.cpp

@@ -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;
@@ -588,6 +589,7 @@ void dcode_9()
 			}
 		}
 	}
+#endif
 }
 
     /*!

+ 11 - 31
Firmware/Marlin.h

@@ -241,9 +241,10 @@ 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)
@@ -255,27 +256,6 @@ 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
@@ -315,18 +295,12 @@ void homeaxis(uint8_t axis, uint8_t cnt = 1, uint8_t* pstep = 0);
 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;
@@ -364,6 +338,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;
 
@@ -385,6 +363,8 @@ 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
 //! There can be a considerable lag between posting M600 and its real processing which might result
@@ -467,6 +447,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;
 
@@ -504,7 +485,6 @@ void raise_z_above(float target, bool plan=true);
 
 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 

+ 197 - 98
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"
@@ -379,9 +380,10 @@ 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; //!< Bed temperature
 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;
@@ -571,6 +573,10 @@ void servo_init()
   #endif
 }
 
+bool printer_active()
+{
+    return PRINTER_ACTIVE;
+}
 
 bool fans_check_enabled = true;
 
@@ -1272,9 +1278,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 +1371,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.
@@ -1692,10 +1706,6 @@ 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();
 
@@ -1774,7 +1784,7 @@ void serial_read_stream() {
  * 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 +1844,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 +1863,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)
     {
@@ -2206,6 +2219,7 @@ void raise_z_above(float target, bool plan)
 
     // Z needs raising
     current_position[Z_AXIS] = target;
+    clamp_to_software_endstops(current_position);
 
 #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);
@@ -2982,7 +2996,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 +3090,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 +3165,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;
@@ -4138,6 +4152,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
@@ -4179,16 +4194,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
@@ -4642,7 +4647,7 @@ 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
@@ -4701,7 +4706,7 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
 	
     */
     case 2: 
-      if(Stopped == false) {
+      {
         get_arc_coordinates();
         prepare_arc_move(true);
       }
@@ -4709,7 +4714,7 @@ eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,0xFFFF);
  
     // -------------------------------
     case 3: 
-      if(Stopped == false) {
+      {
         get_arc_coordinates();
         prepare_arc_move(false);
       }
@@ -7760,6 +7765,67 @@ 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 ]                                     ; 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
+    */
+    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;
+        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();
+
+        // 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);
+    }
+    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>
@@ -8717,16 +8783,6 @@ Sigma_Exit:
 	}
 	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>
-    @todo Usually doesn't work. Should be fixed or removed. Most of the time, if `Stopped` it set, the print fails and is unrecoverable.
-    */
-    case 999:
-      Stopped = false;
-      lcd_reset_alert_level();
-      gcode_LastN = Stopped_gcode_LastN;
-      FlushSerialRequestResend();
-    break;
 	/*!
 	#### End of M-Commands
     */
@@ -8870,7 +8926,7 @@ Sigma_Exit:
                       active_extruder = tmp_extruder;
                       plan_set_position_curposXYZE();
                       // Move to the old position if 'F' was in the parameters
-                      if (make_move && Stopped == false) {
+                      if (make_move) {
                           prepare_move();
                       }
                   }
@@ -9144,6 +9200,23 @@ Sigma_Exit:
     };
 #endif
 
+#ifdef TEMP_MODEL_DEBUG
+    /*!
+    ## D70 - Enable low-level temperature model logging for offline simulation
+    #### Usage
+
+        D70 [ I ]
+
+    #### Parameters
+    - `I` - Enable 0-1 (default 0)
+    */
+    case 70: {
+        if(code_seen('I'))
+            temp_model_log_enable(code_value_short());
+        break;
+    }
+#endif
+
 #ifdef HEATBED_ANALYSIS
 
     /*!
@@ -9469,7 +9542,7 @@ void mesh_plan_buffer_line(const float &x, const float &y, const float &z, const
                                  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)
+                if (planner_aborted)
                     return;
             }
         }
@@ -9871,6 +9944,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();
@@ -9890,39 +9964,58 @@ 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) {
+                // 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);
+
+        // 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();
+    // 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; };
@@ -10754,20 +10847,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_above(current_position[Z_AXIS] + Z_PAUSE_LIFT, true);
 
-	//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() {
@@ -10871,6 +10971,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
@@ -11003,6 +11104,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.
@@ -11124,7 +11226,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);
@@ -11472,8 +11574,9 @@ void stop_and_save_print_to_ram(float z_move, float e_move)
     saved_feedmultiply2 = feedmultiply; //save feedmultiply
 	saved_active_extruder = active_extruder; //save active_extruder
 	saved_extruder_temperature = degTargetHotend(active_extruder);
+	saved_bed_temperature = degBed();
 	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();
@@ -11482,7 +11585,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.
@@ -11514,13 +11617,6 @@ 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
   }
 }
 
@@ -11543,10 +11639,13 @@ 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;
+    // restore bed temperature (bed can be disabled during a thermal warning)
+    if (degBed() != saved_bed_temperature)
+        setTargetBed(saved_bed_temperature);
+
+	// restore active_extruder
+	active_extruder = saved_active_extruder;
+	fanSpeed = saved_fan_speed;
 	if (degTargetHotend(saved_active_extruder) != saved_extruder_temperature)
 	{
 		setTargetHotendSafe(saved_extruder_temperature, saved_active_extruder);
@@ -11604,7 +11703,7 @@ 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

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

+ 6 - 28
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,9 @@ 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);
-
+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; }

+ 25 - 15
Firmware/cmdqueue.cpp

@@ -28,7 +28,6 @@ ShortTimer serialTimeoutTimer;
 
 long gcode_N = 0;
 long gcode_LastN = 0;
-long Stopped_gcode_LastN = 0;
 
 uint32_t sdpos_atomic = 0;
 
@@ -464,8 +463,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 +475,48 @@ 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;
+
 #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
+        gcode_LastN = gcode_N;
+
 #ifdef CMDBUFFER_DEBUG
         SERIAL_ECHOPGM("Number of commands in the buffer: ");
         SERIAL_ECHO(buflen);

+ 0 - 1
Firmware/cmdqueue.h

@@ -54,7 +54,6 @@ 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();

+ 3 - 2
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

+ 9 - 1
Firmware/eeprom.h

@@ -550,8 +550,16 @@ 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
+
 //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_TEMP_MODEL_E
 // !!!!!
 // !!!!! 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();

+ 2 - 56
Firmware/fsensor.cpp

@@ -688,62 +688,8 @@ void fsensor_update(void)
         {
             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
+                fsensor_checkpoint_print();
+                fsensor_enque_M600();
             }
         }
 #endif //PAT9125

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

+ 7 - 3
Firmware/messages.cpp

@@ -159,6 +159,10 @@ const char MSG_IR_04_OR_NEWER[] PROGMEM_I1 = ISTR(" 0.4 or newer");////MSG_IR_04
 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
+#ifdef TEMP_MODEL
+extern const char MSG_THERMAL_ANOMALY[] PROGMEM_I1 = ISTR("THERMAL ANOMALY");////c=20
+extern const char MSG_PAUSED_THERMAL_ERROR[] PROGMEM_I1 = ISTR("PAUSED THERMAL ERROR");////c=20
+#endif
 
 //not internationalized messages
 const char MSG_AUTO_DEPLETE[] PROGMEM_N1 = ISTR("SpoolJoin"); ////MSG_AUTO_DEPLETE c=13
@@ -186,11 +190,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

+ 6 - 2
Firmware/messages.h

@@ -168,6 +168,10 @@ extern const char MSG_IR_04_OR_NEWER[];
 extern const char MSG_IR_03_OR_OLDER[];
 extern const char MSG_IR_UNKNOWN[];
 #endif
+#ifdef TEMP_MODEL
+extern const char MSG_THERMAL_ANOMALY[];
+extern const char MSG_PAUSED_THERMAL_ERROR[];
+#endif
 
 //not internationalized messages
 extern const char MSG_BROWNOUT_RESET[];
@@ -193,9 +197,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[];

+ 1 - 0
Firmware/mmu.cpp

@@ -10,6 +10,7 @@
 #include "fsensor.h"
 #include "cardreader.h"
 #include "cmdqueue.h"
+#include "stepper.h"
 #include "ultralcd.h"
 #include "menu.h"
 #include "sound.h"

+ 1 - 1
Firmware/motion_control.cpp

@@ -151,7 +151,7 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
             // 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);
             // Handle the situation where the planner is aborted hard.
-            if (waiting_inside_plan_buffer_line_print_aborted)
+            if (planner_aborted)
                 return;
         }
     }

+ 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

+ 36 - 34
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 ============================
 //===========================================================================
@@ -592,17 +596,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 +609,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 +622,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,8 +655,6 @@ 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]);
@@ -673,15 +662,29 @@ void planner_abort_hard()
 #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) {
@@ -702,12 +705,11 @@ float junction_deviation = 0.1;
 // 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)
 {
-    // Calculate the buffer head after we push this byte
+  // 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 +717,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];
@@ -1331,8 +1329,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[]

+ 4 - 1
Firmware/planner.h

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

+ 0 - 9
Firmware/stepper.cpp

@@ -287,15 +287,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

+ 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 lenght 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

+ 1413 - 871
Firmware/temperature.cpp

@@ -28,26 +28,73 @@
 
  */
 
-
-#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 "config.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
+
+// 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
 
 //===========================================================================
 //=============================public variables============================
@@ -58,13 +105,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
 
@@ -87,17 +133,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 +164,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 +176,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 +185,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 +200,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 +226,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 +253,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 +265,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 +294,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 +316,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 +468,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 +485,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;
-
-  if(temp_meas_ready != true)   //better readability
-    return; 
-// more precisely - this condition partially stabilizes time interval for regulation values evaluation (@ ~ 230ms)
-
-  // ADC values need to be converted before checking: converted values are later used in MINTEMP
-  updateTemperaturesFromRawValues();
+    // 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;
 
-  check_max_temp();
-  check_min_temp();
+    // syncronize temperatures with isr
+    updateTemperatures();
 
-#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)
-
-#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
+    // handle temperature errors
+    if(temp_error_state.v)
+        handle_temp_error();
 
-  #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 +713,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 +777,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 +877,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 +974,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 +1012,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 +1020,44 @@ 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");
+            }
+        }
+        if (farm_mode) {
+            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 +1072,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);
+        if (farm_mode) 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);
+        if (farm_mode) 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 +1180,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
-{
-	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;
+    if(curTodo>0)
+    {
+      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 +1541,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 +1567,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 +1617,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 +1635,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 +1657,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 +1673,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 +1685,105 @@ 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
+        }
+        break;
+#ifdef TEMP_MODEL
+    case TempErrorType::model:
+        if(temp_error_state.assert) {
+            if(IsStopped() == false) {
+                lcd_setalertstatuspgm(MSG_PAUSED_THERMAL_ERROR, LCD_STATUS_CRITICAL);
+                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
-	//if (READ(TACH_1) != fan_state[1]) {
-	//	fan_edge_counter[1] ++;
-	//	fan_state[1] = !fan_state[1];
-	//}
+    }
 }
-#endif //TACH_0
 
 #ifdef PIDTEMP
 // Apply the scale factors to the PID values
 
-
 float scalePID_i(float i)
 {
 	return i*PID_dT;
@@ -2345,3 +1834,1056 @@ 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, uint8_t fan_pwm, float heater_temp, float ambient_temp)
+{
+    // pre-compute invariant values
+    C_i = (TEMP_MGR_INTV / C);
+    warn_s = warn * TEMP_MGR_INTV;
+    err_s = err * TEMP_MGR_INTV;
+
+    // initial values
+    memset(dT_lag_buf, 0, sizeof(dT_lag_buf));
+    dT_lag_idx = 0;
+    dT_err_prev = 0;
+    T_prev = heater_temp;
+
+    // perform one step to initialize the first delta
+    step(heater_pwm, fan_pwm, heater_temp, ambient_temp);
+
+    // clear the initialization flag
+    flag_bits.uninitialized = false;
+}
+
+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 = (dT_lag_prev * (1.f - TEMP_MODEL_fS)) + (dT * TEMP_MODEL_fS);
+    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 = (dT_err_prev * (1.f - TEMP_MODEL_fE)) + (dT_err * TEMP_MODEL_fE);
+    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(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 - (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
+
+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 = NAN;
+    for(uint8_t i = 0; 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 {
+
+void waiting_handler()
+{
+    manage_heater();
+    host_keepalive();
+    host_autoreport();
+    checkFans();
+    lcd_update(0);
+}
+
+void wait(unsigned ms)
+{
+    unsigned long mark = _millis() + ms;
+    while(_millis() < mark) {
+        if(temp_error_state.v) break;
+        waiting_handler();
+    }
+}
+
+void wait_temp()
+{
+    while(current_temperature[0] < (target_temperature[0] - TEMP_HYSTERESIS)) {
+        if(temp_error_state.v) break;
+        waiting_handler();
+    }
+}
+
+void cooldown(float temp)
+{
+    float old_speed = fanSpeedSoftPwm;
+    fanSpeedSoftPwm = 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();
+    }
+    fanSpeedSoftPwm = old_speed;
+}
+
+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;
+}
+
+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;
+    for(uint16_t i = 1; i < samples; ++i) {
+        temp_model::data.step(rec_buffer[i].pwm, fan_pwm, rec_buffer[i].temp, ambient);
+        err += fabsf(temp_model::data.dT_err_prev);
+    }
+    return (err / (samples - 1));
+}
+
+constexpr float GOLDEN_RATIO = 0.6180339887498949;
+
+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;
+}
+
+float estimate(uint16_t samples,
+    float* const var, float min, float max,
+    float thr, uint16_t max_itr,
+    uint8_t fan_pwm, float ambient)
+{
+    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;
+            return e;
+        }
+    }
+
+    SERIAL_ECHOLNPGM("TM estimation did not converge");
+    *var = orig;
+    return NAN;
+}
+
+bool autotune(int16_t cal_temp)
+{
+    uint16_t samples;
+    float e;
+
+    // bootstrap C/R values without fan
+    fanSpeedSoftPwm = 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);
+        }
+
+        // we need a valid R value for the initial C guess
+        if(isnan(temp_model::data.R[0]))
+            temp_model::data.R[0] = TEMP_MODEL_Rh;
+
+        printf_P(PSTR("TM: %S C estimation\n"), verb);
+        target_temperature[0] = cal_temp;
+        samples = record();
+        if(temp_error_state.v || !samples)
+            return true;
+
+        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.
+    fanSpeedSoftPwm = 255;
+    wait(30000);
+
+    for(int8_t i = TEMP_MODEL_R_SIZE - 1; i > 0; i -= TEMP_MODEL_CAL_R_STEP) {
+        fanSpeedSoftPwm = 256 / TEMP_MODEL_R_SIZE * (i + 1) - 1;
+        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)
+{
+    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"));
+    lcd_return_to_status();
+
+    // disable the model checking during self-calibration
+    bool was_enabled = temp_model::enabled;
+    temp_model_set_enabled(false);
+
+    SERIAL_ECHOLNPGM("TM: autotune start");
+    bool err = temp_model_cal::autotune(temp > 0 ? temp : TEMP_MODEL_CAL_Th);
+
+    // always reset temperature
+    target_temperature[0] = 0;
+
+    if(err) {
+        SERIAL_ECHOLNPGM("TM: autotune failed");
+        lcd_setstatuspgm(_i("TM autotune failed"));
+        if(temp_error_state.v)
+            fanSpeedSoftPwm = 255;
+    } else {
+        lcd_setstatuspgm(MSG_WELCOME);
+        fanSpeedSoftPwm = 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);
 
-#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

+ 160 - 94
Firmware/ultralcd.cpp

@@ -10,7 +10,7 @@
 #include "Marlin.h"
 #include "language.h"
 #include "cardreader.h"
-#include "temperature.h"
+#include "fancheck.h"
 #include "stepper.h"
 #include "ConfigurationStore.h"
 #include "printers.h"
@@ -94,14 +94,15 @@ static bool lcd_autoDeplete;
 
 static float manual_feedrate[] = MANUAL_FEEDRATE;
 
-/* !Configuration settings */
+/* LCD message status */
+static LongTimer lcd_status_message_timeout;
+static uint8_t lcd_status_message_level;
+static char lcd_status_message[LCD_WIDTH + 1] = WELCOME_MSG;
 
-uint8_t lcd_status_message_level;
-char lcd_status_message[LCD_WIDTH + 1] = WELCOME_MSG;
+/* !Configuration settings */
 
 static uint8_t lay1cal_filament = 0;
 
-
 static const char separator[] PROGMEM = "--------------------";
 
 /** forward declarations **/
@@ -597,7 +598,12 @@ void lcdui_print_status_line(void)
             break;
         }
     }
-    else if ((IS_SD_PRINTING) && (custom_message_type == CustomMsg::Status)) { // If printing from SD, show what we are printing
+    else if ((IS_SD_PRINTING) &&
+        (custom_message_type == CustomMsg::Status) &&
+        (lcd_status_message_level <= LCD_STATUS_INFO) &&
+        lcd_status_message_timeout.expired_cont(LCD_STATUS_INFO_TIMEOUT))
+    {
+        // If printing from SD, show what we are printing
 		const char* longFilenameOLD = (card.longFilename[0] ? card.longFilename : card.filename);
         if(strlen(longFilenameOLD) > LCD_WIDTH) {
             uint8_t gh = scrollstuff;
@@ -860,7 +866,7 @@ void lcd_status_screen()                          // NOT static due to using ins
 	}
 
 	if (current_click
-		&& ( menu_block_entering_on_serious_errors == SERIOUS_ERR_NONE ) // or a serious error blocks entering the menu
+		&& ( menu_block_mask == MENU_BLOCK_NONE ) // or a serious error blocks entering the menu
 	)
 	{
 		menu_depth = 0; //redundant, as already done in lcd_return_to_status(), just to be sure
@@ -869,14 +875,34 @@ void lcd_status_screen()                          // NOT static due to using ins
 	}
 }
 
+void print_stop();
+
 void lcd_commands()
 {
+    if (planner_aborted) {
+        // we are still within an aborted command. do not process any LCD command until we return
+        return;
+    }
+
+    if (lcd_commands_type == LcdCommands::StopPrint)
+    {
+        if (!blocks_queued() && !homing_flag)
+        {
+            custom_message_type = CustomMsg::Status;
+            lcd_setstatuspgm(_T(MSG_PRINT_ABORTED));
+            lcd_commands_type = LcdCommands::Idle;
+            lcd_commands_step = 0;
+            print_stop();
+        }
+    }
+
 	if (lcd_commands_type == LcdCommands::LongPause)
 	{
 		if (!blocks_queued() && !homing_flag)
 		{
 			if (custom_message_type != CustomMsg::M117)
 			{
+				custom_message_type = CustomMsg::Status;
 				lcd_setstatuspgm(_i("Print paused"));////MSG_PRINT_PAUSED c=20
 			}
 			lcd_commands_type = LcdCommands::Idle;
@@ -1021,14 +1047,14 @@ void lcd_commands()
 			lcd_commands_step = 3;
 		}
 		if (lcd_commands_step == 3 && !blocks_queued()) { //PID calibration
+			preparePidTuning(); // ensure we don't move to the next step early
 			sprintf_P(cmd1, PSTR("M303 E0 S%3u"), pid_temp);
 			// setting the correct target temperature (for visualization) is done in PID_autotune
 			enquecommand(cmd1);
 			lcd_setstatuspgm(_i("PID cal."));////MSG_PID_RUNNING c=20
 			lcd_commands_step = 2;
 		}
-		if (lcd_commands_step == 2 && pid_tuning_finished) { //saving to eeprom
-			pid_tuning_finished = false;
+		if (lcd_commands_step == 2 && !pidTuningRunning()) { //saving to eeprom
 			custom_message_state = 0;
 			lcd_setstatuspgm(_i("PID cal. finished"));////MSG_PID_FINISHED c=20
 			setAllTargetHotends(0);  // reset all hotends temperature including the number displayed on the main screen
@@ -1067,18 +1093,19 @@ void lcd_return_to_status()
 void lcd_pause_print()
 {
     stop_and_save_print_to_ram(0.0, -default_retraction);
-    lcd_return_to_status();
+
+    SERIAL_ECHOLNRPGM(MSG_OCTOPRINT_PAUSED);
     isPrintPaused = true;
-    if (LcdCommands::Idle == lcd_commands_type) {
-        lcd_commands_type = LcdCommands::LongPause;
-    }
-    SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSED);
+
+    // return to status is required to continue processing in the main loop!
+    lcd_commands_type = LcdCommands::LongPause;
+    lcd_return_to_status();
 }
 
 //! @brief Send host action "pause"
 void lcd_pause_usb_print()
 {
-    SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSE);
+    SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_ASK_PAUSE);
 }
 
 static void lcd_move_menu_axis();
@@ -5665,22 +5692,37 @@ static bool fan_error_selftest()
     return 0;
 }
 
+bool resume_print_checks() {
+    // reset the lcd status so that a newer error will be shown
+    lcd_return_to_status();
+    lcd_reset_alert_level();
+
+    // ensure thermal issues (temp or fan) are resolved before we allow to resume
+    if (get_temp_error()
+#ifdef FANCHECK
+        || fan_error_selftest()
+#endif
+        ) {
+        return false; // abort if error persists
+    }
+
+    return true;
+}
+
 //! @brief Resume paused print, send host action "resumed"
 //! @todo It is not good to call restore_print_from_ram_and_continue() from function called by lcd_update(),
 //! as restore_print_from_ram_and_continue() calls lcd_update() internally.
 void lcd_resume_print()
 {
-    lcd_return_to_status();
-    lcd_reset_alert_level(); //for fan speed error
-    if (fan_error_selftest()) {
-        if (usb_timer.running()) SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_PAUSED);
-        return; //abort if error persists
-    }
+    // reset lcd and ensure we can resume first
+    if (!resume_print_checks()) return;
+
     cmdqueue_serial_disabled = false;
     lcd_setstatuspgm(_T(MSG_FINISHING_MOVEMENTS));
     st_synchronize();
     custom_message_type = CustomMsg::Resuming;
     isPrintPaused = false;
+    Stopped = false; // resume processing USB commands again
     restore_print_from_ram_and_continue(default_retraction);
     pause_time += (_millis() - start_pause_print); //accumulate time when print is paused for correct statistics calculation
     refresh_cmd_timeout();
@@ -5691,7 +5733,11 @@ void lcd_resume_print()
 //! @brief Resume paused USB/host print, send host action "resume"
 void lcd_resume_usb_print()
 {
-    SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_RESUME); //resume octoprint
+    // reset lcd and ensure we can resume first
+    if (!resume_print_checks()) return;
+
+    // resume the usb host
+    SERIAL_PROTOCOLLNRPGM(MSG_OCTOPRINT_ASK_RESUME);
 }
 
 static void change_sheet()
@@ -5872,14 +5918,16 @@ static void lcd_main_menu()
     }
     if(isPrintPaused)
     {
+        // only allow resuming if hardware errors (temperature or fan) are cleared
+        if(!get_temp_error()
 #ifdef FANCHECK
-        if((fan_check_error == EFCE_FIXED) || (fan_check_error == EFCE_OK))
+            && ((fan_check_error == EFCE_FIXED) || (fan_check_error == EFCE_OK))
 #endif //FANCHECK
-        {
-            if (usb_timer.running()) {
-                MENU_ITEM_SUBMENU_P(_T(MSG_RESUME_PRINT), lcd_resume_usb_print);
-            } else {
+           ) {
+            if (saved_printing) {
                 MENU_ITEM_SUBMENU_P(_T(MSG_RESUME_PRINT), lcd_resume_print);
+            } else {
+                MENU_ITEM_SUBMENU_P(_T(MSG_RESUME_PRINT), lcd_resume_usb_print);
             }
         }
     }
@@ -6286,56 +6334,61 @@ static void lcd_sd_updir()
   menu_data_reset(); //Forces reloading of cached variables.
 }
 
-void lcd_print_stop()
+// continue stopping the print from the main loop after lcd_print_stop() is called
+void print_stop()
 {
-    if (!card.sdprinting) {
-        SERIAL_ECHOLNRPGM(MSG_OCTOPRINT_CANCEL); // for Octoprint
-    }
-    UnconditionalStop();
-
-    // TODO: all the following should be moved in the main marlin loop!
-#ifdef MESH_BED_LEVELING
-    mbl.active = false; //also prevents undoing the mbl compensation a second time in the second planner_abort_hard()
-#endif
-
-	lcd_setstatuspgm(_T(MSG_PRINT_ABORTED));
-	stoptime = _millis();
-	unsigned long t = (stoptime - starttime - pause_time) / 1000; //time in s
-	pause_time = 0;
-	save_statistics(total_filament_used, t);
+    // save printing time
+    stoptime = _millis();
+    unsigned long t = (stoptime - starttime - pause_time) / 1000; //time in s
+    save_statistics(total_filament_used, t);
 
-    // reset current command
-    lcd_commands_step = 0;
-    lcd_commands_type = LcdCommands::Idle;
-
-    lcd_cooldown(); //turns off heaters and fan; goes to status screen.
-
-    if (axis_known_position[Z_AXIS]) {
-        current_position[Z_AXIS] += Z_CANCEL_LIFT;
-        clamp_to_software_endstops(current_position);
-        plan_buffer_line_curposXYZE(manual_feedrate[Z_AXIS] / 60);
-    }
+    // lift Z
+    raise_z_above(current_position[Z_AXIS] + 10, true);
 
-    if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]) //if axis are homed, move to parked position.
-    {
+    // if axis are homed, move to parking position.
+    if (axis_known_position[X_AXIS] && axis_known_position[Y_AXIS]) {
         current_position[X_AXIS] = X_CANCEL_POS;
         current_position[Y_AXIS] = Y_CANCEL_POS;
         plan_buffer_line_curposXYZE(manual_feedrate[0] / 60);
     }
     st_synchronize();
 
-    if (mmu_enabled) extr_unload(); //M702 C
+    // 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;
+    }
 
+    if (mmu_enabled) extr_unload(); //M702 C
     finishAndDisableSteppers(); //M84
+    axis_relative_modes = E_AXIS_MASK; //XYZ absolute, E relative
+}
 
-    lcd_setstatuspgm(MSG_WELCOME);
-    custom_message_type = CustomMsg::Status;
+void lcd_print_stop()
+{
+    // UnconditionalStop() will internally cause planner_abort_hard(), meaning we _cannot_ plan
+    // any more move in this call! Any further move must happen inside print_stop(), which is called
+    // by the main loop one iteration later.
+    UnconditionalStop();
 
-    planner_abort_hard(); //needs to be done since plan_buffer_line resets waiting_inside_plan_buffer_line_print_aborted to false. Also copies current to destination.
-    
-    axis_relative_modes = E_AXIS_MASK; //XYZ absolute, E relative
-    
-    isPrintPaused = false; //clear isPrintPaused flag to allow starting next print after pause->stop scenario.
+    if (!card.sdprinting) {
+        SERIAL_ECHOLNRPGM(MSG_OCTOPRINT_CANCEL); // for Octoprint
+    }
+
+#ifdef MESH_BED_LEVELING
+    mbl.active = false;
+#endif
+
+    // clear any pending paused state immediately
+    pause_time = 0;
+    isPrintPaused = false;
+
+    // return to status is required to continue processing in the main loop!
+    lcd_commands_type = LcdCommands::StopPrint;
+    lcd_return_to_status();
 }
 
 void lcd_sdcard_stop()
@@ -7952,57 +8005,70 @@ void lcd_finishstatus() {
 
 }
 
-void lcd_setstatus(const char* message)
+static bool lcd_message_check(uint8_t priority)
 {
-  if (lcd_status_message_level > 0)
-    return;
-  lcd_updatestatus(message);
+    // regular priority check
+    if (priority >= lcd_status_message_level)
+        return true;
+
+    // check if we can override an info message yet
+    if (lcd_status_message_level == LCD_STATUS_INFO) {
+        return lcd_status_message_timeout.expired_cont(LCD_STATUS_INFO_TIMEOUT);
+    }
+
+    return false;
 }
 
-void lcd_updatestatuspgm(const char *message){
-	strncpy_P(lcd_status_message, message, LCD_WIDTH);
+static void lcd_updatestatus(const char *message, bool progmem = false)
+{
+    if (progmem)
+        strncpy_P(lcd_status_message, message, LCD_WIDTH);
+    else
+        strncpy(lcd_status_message, message, LCD_WIDTH);
+
 	lcd_status_message[LCD_WIDTH] = 0;
 	lcd_finishstatus();
 	// hack lcd_draw_update to 1, i.e. without clear
 	lcd_draw_update = 1;
 }
 
-void lcd_setstatuspgm(const char* message)
+void lcd_setstatus(const char* message)
 {
-  if (lcd_status_message_level > 0)
-    return;
-  lcd_updatestatuspgm(message);
+    if (lcd_message_check(LCD_STATUS_NONE))
+        lcd_updatestatus(message);
 }
 
-void lcd_updatestatus(const char *message){
-	strncpy(lcd_status_message, message, LCD_WIDTH);
-	lcd_status_message[LCD_WIDTH] = 0;
-	lcd_finishstatus();
-	// hack lcd_draw_update to 1, i.e. without clear
-	lcd_draw_update = 1;
+void lcd_setstatuspgm(const char* message)
+{
+    if (lcd_message_check(LCD_STATUS_NONE))
+        lcd_updatestatus(message, true);
 }
 
-void lcd_setalertstatuspgm(const char* message, uint8_t severity)
+void lcd_setalertstatus_(const char* message, uint8_t severity, bool progmem)
 {
-  if (severity > lcd_status_message_level) {
-      lcd_updatestatuspgm(message);
-      lcd_status_message_level = severity;
-      lcd_return_to_status();
-  }
+    if (lcd_message_check(severity)) {
+        lcd_updatestatus(message, progmem);
+        lcd_status_message_timeout.start();
+        lcd_status_message_level = severity;
+        custom_message_type = CustomMsg::Status;
+        custom_message_state = 0;
+        lcd_return_to_status();
+    }
 }
 
 void lcd_setalertstatus(const char* message, uint8_t severity)
 {
-  if (severity > lcd_status_message_level) {
-      lcd_updatestatus(message);
-      lcd_status_message_level = severity;
-      lcd_return_to_status();
-  }
+    lcd_setalertstatus_(message, severity, false);
+}
+
+void lcd_setalertstatuspgm(const char* message, uint8_t severity)
+{
+    lcd_setalertstatus_(message, severity, true);
 }
 
 void lcd_reset_alert_level()
 {
-  lcd_status_message_level = 0;
+    lcd_status_message_level = 0;
 }
 
 uint8_t get_message_level()
@@ -8013,9 +8079,9 @@ uint8_t get_message_level()
 void menu_lcd_longpress_func(void)
 {
 	backlight_wake();
-    if (homing_flag || mesh_bed_leveling_flag || menu_menu == lcd_babystep_z || menu_menu == lcd_move_z)
+    if (homing_flag || mesh_bed_leveling_flag || menu_menu == lcd_babystep_z || menu_menu == lcd_move_z || menu_block_mask != MENU_BLOCK_NONE)
     {
-        // disable longpress during re-entry, while homing or calibration
+        // disable longpress during re-entry, while homing, calibration or if a serious error
         lcd_quick_feedback();
         return;
     }

+ 12 - 10
Firmware/ultralcd.h

@@ -9,14 +9,19 @@ extern void menu_lcd_lcdupdate_func(void);
 
 // Call with a false parameter to suppress the LCD update from various places like the planner or the temp control.
 void ultralcd_init();
-void lcd_setstatus(const char* message);
-void lcd_setstatuspgm(const char* message);
 
 //! LCD status severities
-#define LCD_STATUS_CRITICAL 2 //< Heater failure
-#define LCD_STATUS_ALERT    1 //< Other hardware issue
+#define LCD_STATUS_CRITICAL 3 //< Heater failure
+#define LCD_STATUS_ALERT    2 //< Other hardware issue
+#define LCD_STATUS_INFO     1 //< Message times out after a while
 #define LCD_STATUS_NONE     0 //< No alert message set
 
+#define LCD_STATUS_INFO_TIMEOUT 20000
+
+// Set the current status message (equivalent to LCD_STATUS_NONE)
+void lcd_setstatus(const char* message);
+void lcd_setstatuspgm(const char* message);
+
 //! return to the main status screen and display the alert message
 //! Beware - it has sideeffects:
 //! - always returns the display to the main status screen
@@ -25,13 +30,10 @@ void lcd_setstatuspgm(const char* message);
 void lcd_setalertstatus(const char* message, uint8_t severity = LCD_STATUS_ALERT);
 void lcd_setalertstatuspgm(const char* message, uint8_t severity = LCD_STATUS_ALERT);
 
-//! only update the alert message on the main status screen
-//! has no sideeffects, may be called multiple times
-void lcd_updatestatus(const char *message);
-void lcd_updatestatuspgm(const char *message);
-
-void lcd_reset_alert_level();
+//! Get/reset the current alert level
 uint8_t get_message_level();
+void lcd_reset_alert_level();
+
 void lcd_adjust_z();
 void lcd_pick_babystep();
 void lcd_alright();

+ 28 - 0
Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h

@@ -413,6 +413,34 @@
 #define TEMP_RUNAWAY_EXTRUDER_HYSTERESIS 15
 #define TEMP_RUNAWAY_EXTRUDER_TIMEOUT 45
 
+// model-based temperature check
+#define TEMP_MODEL 1          // enable model-based temperature checks
+#define TEMP_MODEL_DEBUG 1    // extended runtime logging
+
+#define TEMP_MODEL_P 38.      // heater power (W)
+
+#define TEMP_MODEL_C 11.      // initial guess for heatblock capacitance (J/K)
+#define TEMP_MODEL_Cl 5       // C estimation lower limit
+#define TEMP_MODEL_Ch 20      // C estimation upper limit
+#define TEMP_MODEL_C_thr 0.01 // C estimation iteration threshold
+#define TEMP_MODEL_C_itr 30   // C estimation iteration limit
+
+#define TEMP_MODEL_R 25       // initial guess for heatblock resistance (K/W)
+#define TEMP_MODEL_Rl 5       // R estimation lower limit
+#define TEMP_MODEL_Rh 50      // R estimation upper limit
+#define TEMP_MODEL_R_thr 0.01 // R estimation iteration threshold
+#define TEMP_MODEL_R_itr 30   // R estimation iteration limit
+
+#define TEMP_MODEL_Ta_corr -7 // Default ambient temperature correction
+#define TEMP_MODEL_LAG 2.1    // Temperature transport delay (s)
+
+#define TEMP_MODEL_W 1.2      // Default warning threshold (K/s)
+#define TEMP_MODEL_E 1.74     // Default error threshold (K/s)
+
+#define TEMP_MODEL_CAL_Th 230 // Default calibration working temperature (C)
+#define TEMP_MODEL_CAL_Tl 50  // Default calibration cooling temperature (C)
+
+
 /*------------------------------------
  MOTOR CURRENT SETTINGS
  *------------------------------------*/

+ 28 - 0
Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h

@@ -417,6 +417,34 @@
 #define TEMP_RUNAWAY_EXTRUDER_HYSTERESIS 15
 #define TEMP_RUNAWAY_EXTRUDER_TIMEOUT 45
 
+// model-based temperature check
+#define TEMP_MODEL 1          // enable model-based temperature checks
+#define TEMP_MODEL_DEBUG 1    // extended runtime logging
+
+#define TEMP_MODEL_P 38.      // heater power (W)
+
+#define TEMP_MODEL_C 11.      // initial guess for heatblock capacitance (J/K)
+#define TEMP_MODEL_Cl 5       // C estimation lower limit
+#define TEMP_MODEL_Ch 20      // C estimation upper limit
+#define TEMP_MODEL_C_thr 0.01 // C estimation iteration threshold
+#define TEMP_MODEL_C_itr 30   // C estimation iteration limit
+
+#define TEMP_MODEL_R 25       // initial guess for heatblock resistance (K/W)
+#define TEMP_MODEL_Rl 5       // R estimation lower limit
+#define TEMP_MODEL_Rh 50      // R estimation upper limit
+#define TEMP_MODEL_R_thr 0.01 // R estimation iteration threshold
+#define TEMP_MODEL_R_itr 30   // R estimation iteration limit
+
+#define TEMP_MODEL_Ta_corr -7 // Default ambient temperature correction
+#define TEMP_MODEL_LAG 2.1    // Temperature transport delay (s)
+
+#define TEMP_MODEL_W 1.2      // Default warning threshold (K/s)
+#define TEMP_MODEL_E 1.74     // Default error threshold (K/s)
+
+#define TEMP_MODEL_CAL_Th 230 // Default calibration working temperature (C)
+#define TEMP_MODEL_CAL_Tl 50  // Default calibration cooling temperature (C)
+
+
 /*------------------------------------
  MOTOR CURRENT SETTINGS
  *------------------------------------*/

+ 15 - 19
Firmware/xyzcal.cpp

@@ -138,15 +138,18 @@ pos_mm_t pos_2_mm(float pos){
 void xyzcal_meassure_enter(void)
 {
 	DBG(_n("xyzcal_meassure_enter\n"));
+
+	// disable heaters and stop motion before we initialize sm4
 	disable_heater();
-	DISABLE_TEMPERATURE_INTERRUPT();
-#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1))
-	DISABLE_FANCHECK_INTERRUPT();
-#endif //(defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1))
+	st_synchronize();
+
+	// disable incompatible interrupts
 	DISABLE_STEPPER_DRIVER_INTERRUPT();
 #ifdef WATCHDOG
 	wdt_disable();
 #endif //WATCHDOG
+
+	// setup internal callbacks
 	sm4_stop_cb = 0;
 	sm4_update_pos_cb = xyzcal_update_pos;
 	sm4_calc_delay_cb = xyzcal_calc_delay;
@@ -155,21 +158,18 @@ void xyzcal_meassure_enter(void)
 void xyzcal_meassure_leave(void)
 {
 	DBG(_n("xyzcal_meassure_leave\n"));
-    planner_abort_hard();
-	ENABLE_TEMPERATURE_INTERRUPT();
-#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1))
-	ENABLE_FANCHECK_INTERRUPT();
-#endif //(defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1))
-	ENABLE_STEPPER_DRIVER_INTERRUPT();
+
+	// resync planner position from counters (changed by xyzcal_update_pos)
+	planner_reset_position();
+
+	// re-enable interrupts
 #ifdef WATCHDOG
 	wdt_enable(WDTO_4S);
 #ifdef EMERGENCY_HANDLERS
 	WDTCSR |= (1 << WDIE);
 #endif //EMERGENCY_HANDLERS
 #endif //WATCHDOG
-	sm4_stop_cb = 0;
-	sm4_update_pos_cb = 0;
-	sm4_calc_delay_cb = 0;
+	ENABLE_STEPPER_DRIVER_INTERRUPT();
 }
 
 
@@ -999,13 +999,9 @@ BedSkewOffsetDetectionResultType xyzcal_scan_and_process(){
 	return ret;
 }
 
-BedSkewOffsetDetectionResultType xyzcal_find_bed_induction_sensor_point_xy(void){
-	BedSkewOffsetDetectionResultType ret = BED_SKEW_OFFSET_DETECTION_POINT_NOT_FOUND;
-
-    //@size=258
+BedSkewOffsetDetectionResultType xyzcal_find_bed_induction_sensor_point_xy(void) {
     // DBG(_n("xyzcal_find_bed_induction_sensor_point_xy x=%ld y=%ld z=%ld\n"), count_position[X_AXIS], count_position[Y_AXIS], count_position[Z_AXIS]);
-	st_synchronize();
-
+	BedSkewOffsetDetectionResultType ret = BED_SKEW_OFFSET_DETECTION_POINT_NOT_FOUND;
 	xyzcal_meassure_enter();
 	if (xyzcal_searchZ())
 		ret = xyzcal_scan_and_process();