From 084f6b5b448cb9f67297dca40541127ea260f552 Mon Sep 17 00:00:00 2001
From: Scott Lahteine <sourcetree@thinkyhead.com>
Date: Thu, 28 Apr 2016 18:18:13 -0700
Subject: [PATCH] Temperature singleton class

---
 Marlin/Marlin.h                               |   4 -
 Marlin/Marlin_main.cpp                        | 126 +++--
 Marlin/cardreader.cpp                         |   2 +-
 Marlin/configuration_store.cpp                |  58 ++-
 Marlin/dogm_lcd_implementation.h              |   6 +-
 Marlin/endstops.cpp                           |   2 +-
 Marlin/planner.cpp                            |  19 +-
 Marlin/temperature.cpp                        | 382 ++++------------
 Marlin/temperature.h                          | 431 ++++++++++++------
 Marlin/ultralcd.cpp                           |  70 +--
 .../ultralcd_implementation_hitachi_HD44780.h |  20 +-
 11 files changed, 536 insertions(+), 584 deletions(-)

diff --git a/Marlin/Marlin.h b/Marlin/Marlin.h
index f6f488df84..4714601fa1 100644
--- a/Marlin/Marlin.h
+++ b/Marlin/Marlin.h
@@ -340,10 +340,6 @@ extern int16_t code_value_short();
   extern uint8_t host_keepalive_interval;
 #endif
 
-#if ENABLED(PREVENT_DANGEROUS_EXTRUDE)
-  extern float extrude_min_temp;
-#endif
-
 #if FAN_COUNT > 0
   extern int fanSpeeds[FAN_COUNT];
 #endif
diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp
index fc05f174ac..0d2d9f5172 100644
--- a/Marlin/Marlin_main.cpp
+++ b/Marlin/Marlin_main.cpp
@@ -544,10 +544,6 @@ static void report_current_position();
   }
 #endif
 
-#if ENABLED(PREVENT_DANGEROUS_EXTRUDE)
-  float extrude_min_temp = EXTRUDE_MINTEMP;
-#endif
-
 #if ENABLED(SDSUPPORT)
   #include "SdFatUtil.h"
   int freeMemory() { return SdFatUtil::FreeRam(); }
@@ -816,7 +812,7 @@ void setup() {
 
   lcd_init();
 
-  tp_init();    // Initialize temperature loop
+  thermalManager.init();    // Initialize temperature loop
 
   #if ENABLED(DELTA) || ENABLED(SCARA)
     // Vital to init kinematic equivalent for X0 Y0 Z0
@@ -3839,7 +3835,7 @@ inline void gcode_M31() {
   SERIAL_ECHO_START;
   SERIAL_ECHOLN(time);
   lcd_setstatus(time);
-  autotempShutdown();
+  thermalManager.autotempShutdown();
 }
 
 #if ENABLED(SDSUPPORT)
@@ -4274,10 +4270,10 @@ inline void gcode_M104() {
 
   if (code_seen('S')) {
     float temp = code_value();
-    setTargetHotend(temp, target_extruder);
+    thermalManager.setTargetHotend(temp, target_extruder);
     #if ENABLED(DUAL_X_CARRIAGE)
       if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0)
-        setTargetHotend1(temp == 0.0 ? 0.0 : temp + duplicate_extruder_temp_offset);
+        thermalManager.setTargetHotend(temp == 0.0 ? 0.0 : temp + duplicate_extruder_temp_offset, 1);
     #endif
 
     /**
@@ -4296,7 +4292,7 @@ inline void gcode_M104() {
      */
     else print_job_timer.start();
 
-    if (temp > degHotend(target_extruder)) LCD_MESSAGEPGM(MSG_HEATING);
+    if (temp > thermalManager.degHotend(target_extruder)) LCD_MESSAGEPGM(MSG_HEATING);
   }
 }
 
@@ -4305,41 +4301,41 @@ inline void gcode_M104() {
   void print_heaterstates() {
     #if HAS_TEMP_HOTEND
       SERIAL_PROTOCOLPGM(" T:");
-      SERIAL_PROTOCOL_F(degHotend(target_extruder), 1);
+      SERIAL_PROTOCOL_F(thermalManager.degHotend(target_extruder), 1);
       SERIAL_PROTOCOLPGM(" /");
-      SERIAL_PROTOCOL_F(degTargetHotend(target_extruder), 1);
+      SERIAL_PROTOCOL_F(thermalManager.degTargetHotend(target_extruder), 1);
     #endif
     #if HAS_TEMP_BED
       SERIAL_PROTOCOLPGM(" B:");
-      SERIAL_PROTOCOL_F(degBed(), 1);
+      SERIAL_PROTOCOL_F(thermalManager.degBed(), 1);
       SERIAL_PROTOCOLPGM(" /");
-      SERIAL_PROTOCOL_F(degTargetBed(), 1);
+      SERIAL_PROTOCOL_F(thermalManager.degTargetBed(), 1);
     #endif
     #if EXTRUDERS > 1
       for (int8_t e = 0; e < EXTRUDERS; ++e) {
         SERIAL_PROTOCOLPGM(" T");
         SERIAL_PROTOCOL(e);
         SERIAL_PROTOCOLCHAR(':');
-        SERIAL_PROTOCOL_F(degHotend(e), 1);
+        SERIAL_PROTOCOL_F(thermalManager.degHotend(e), 1);
         SERIAL_PROTOCOLPGM(" /");
-        SERIAL_PROTOCOL_F(degTargetHotend(e), 1);
+        SERIAL_PROTOCOL_F(thermalManager.degTargetHotend(e), 1);
       }
     #endif
     #if HAS_TEMP_BED
       SERIAL_PROTOCOLPGM(" B@:");
       #ifdef BED_WATTS
-        SERIAL_PROTOCOL(((BED_WATTS) * getHeaterPower(-1)) / 127);
+        SERIAL_PROTOCOL(((BED_WATTS) * thermalManager.getHeaterPower(-1)) / 127);
         SERIAL_PROTOCOLCHAR('W');
       #else
-        SERIAL_PROTOCOL(getHeaterPower(-1));
+        SERIAL_PROTOCOL(thermalManager.getHeaterPower(-1));
       #endif
     #endif
     SERIAL_PROTOCOLPGM(" @:");
     #ifdef EXTRUDER_WATTS
-      SERIAL_PROTOCOL(((EXTRUDER_WATTS) * getHeaterPower(target_extruder)) / 127);
+      SERIAL_PROTOCOL(((EXTRUDER_WATTS) * thermalManager.getHeaterPower(target_extruder)) / 127);
       SERIAL_PROTOCOLCHAR('W');
     #else
-      SERIAL_PROTOCOL(getHeaterPower(target_extruder));
+      SERIAL_PROTOCOL(thermalManager.getHeaterPower(target_extruder));
     #endif
     #if EXTRUDERS > 1
       for (int8_t e = 0; e < EXTRUDERS; ++e) {
@@ -4347,27 +4343,27 @@ inline void gcode_M104() {
         SERIAL_PROTOCOL(e);
         SERIAL_PROTOCOLCHAR(':');
         #ifdef EXTRUDER_WATTS
-          SERIAL_PROTOCOL(((EXTRUDER_WATTS) * getHeaterPower(e)) / 127);
+          SERIAL_PROTOCOL(((EXTRUDER_WATTS) * thermalManager.getHeaterPower(e)) / 127);
           SERIAL_PROTOCOLCHAR('W');
         #else
-          SERIAL_PROTOCOL(getHeaterPower(e));
+          SERIAL_PROTOCOL(thermalManager.getHeaterPower(e));
         #endif
       }
     #endif
     #if ENABLED(SHOW_TEMP_ADC_VALUES)
       #if HAS_TEMP_BED
         SERIAL_PROTOCOLPGM("    ADC B:");
-        SERIAL_PROTOCOL_F(degBed(), 1);
+        SERIAL_PROTOCOL_F(thermalManager.degBed(), 1);
         SERIAL_PROTOCOLPGM("C->");
-        SERIAL_PROTOCOL_F(rawBedTemp() / OVERSAMPLENR, 0);
+        SERIAL_PROTOCOL_F(thermalManager.rawBedTemp() / OVERSAMPLENR, 0);
       #endif
       for (int8_t cur_extruder = 0; cur_extruder < EXTRUDERS; ++cur_extruder) {
         SERIAL_PROTOCOLPGM("  T");
         SERIAL_PROTOCOL(cur_extruder);
         SERIAL_PROTOCOLCHAR(':');
-        SERIAL_PROTOCOL_F(degHotend(cur_extruder), 1);
+        SERIAL_PROTOCOL_F(thermalManager.degHotend(cur_extruder), 1);
         SERIAL_PROTOCOLPGM("C->");
-        SERIAL_PROTOCOL_F(rawHotendTemp(cur_extruder) / OVERSAMPLENR, 0);
+        SERIAL_PROTOCOL_F(thermalManager.rawHotendTemp(cur_extruder) / OVERSAMPLENR, 0);
       }
     #endif
   }
@@ -4427,10 +4423,10 @@ inline void gcode_M109() {
   bool no_wait_for_cooling = code_seen('S');
   if (no_wait_for_cooling || code_seen('R')) {
     float temp = code_value();
-    setTargetHotend(temp, target_extruder);
+    thermalManager.setTargetHotend(temp, target_extruder);
     #if ENABLED(DUAL_X_CARRIAGE)
       if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0)
-        setTargetHotend1(temp == 0.0 ? 0.0 : temp + duplicate_extruder_temp_offset);
+        thermalManager.setTargetHotend(temp == 0.0 ? 0.0 : temp + duplicate_extruder_temp_offset, 1);
     #endif
 
     /**
@@ -4449,7 +4445,7 @@ inline void gcode_M109() {
      */
     else print_job_timer.start();
 
-    if (temp > degHotend(target_extruder)) LCD_MESSAGEPGM(MSG_HEATING);
+    if (temp > thermalManager.degHotend(target_extruder)) LCD_MESSAGEPGM(MSG_HEATING);
   }
 
   #if ENABLED(AUTOTEMP)
@@ -4462,7 +4458,7 @@ inline void gcode_M109() {
     #define TEMP_CONDITIONS (!residency_start_ms || PENDING(now, residency_start_ms + (TEMP_RESIDENCY_TIME) * 1000UL))
   #else
     // Loop until the temperature is very close target
-    #define TEMP_CONDITIONS (wants_to_cool ? isCoolingHotend(target_extruder) : isHeatingHotend(target_extruder))
+    #define TEMP_CONDITIONS (wants_to_cool ? thermalManager.isCoolingHotend(target_extruder) : thermalManager.isHeatingHotend(target_extruder))
   #endif //TEMP_RESIDENCY_TIME > 0
 
   float theTarget = -1;
@@ -4473,7 +4469,6 @@ inline void gcode_M109() {
   KEEPALIVE_STATE(NOT_BUSY);
 
   do {
-
     now = millis();
     if (ELAPSED(now, next_temp_ms)) { //Print temp & remaining time every 1s while waiting
       next_temp_ms = now + 1000UL;
@@ -4495,9 +4490,9 @@ inline void gcode_M109() {
     }
 
     // Target temperature might be changed during the loop
-    if (theTarget != degTargetHotend(target_extruder)) {
-      theTarget = degTargetHotend(target_extruder);
-      wants_to_cool = isCoolingHotend(target_extruder);
+    if (theTarget != thermalManager.degTargetHotend(target_extruder)) {
+      wants_to_cool = thermalManager.isCoolingHotend(target_extruder);
+      theTarget = thermalManager.degTargetHotend(target_extruder);
 
       // Exit if S<lower>, continue if S<higher>, R<lower>, or R<higher>
       if (no_wait_for_cooling && wants_to_cool) break;
@@ -4512,7 +4507,7 @@ inline void gcode_M109() {
 
     #if TEMP_RESIDENCY_TIME > 0
 
-      float temp_diff = fabs(theTarget - degHotend(target_extruder));
+      float temp_diff = fabs(theTarget - thermalManager.degHotend(target_extruder));
 
       if (!residency_start_ms) {
         // Start the TEMP_RESIDENCY_TIME timer when we reach target temp for the first time.
@@ -4542,7 +4537,7 @@ inline void gcode_M109() {
 
     LCD_MESSAGEPGM(MSG_BED_HEATING);
     bool no_wait_for_cooling = code_seen('S');
-    if (no_wait_for_cooling || code_seen('R')) setTargetBed(code_value());
+    if (no_wait_for_cooling || code_seen('R')) thermalManager.setTargetBed(code_value());
 
     #if TEMP_BED_RESIDENCY_TIME > 0
       millis_t residency_start_ms = 0;
@@ -4550,7 +4545,7 @@ inline void gcode_M109() {
       #define TEMP_BED_CONDITIONS (!residency_start_ms || PENDING(now, residency_start_ms + (TEMP_BED_RESIDENCY_TIME) * 1000UL))
     #else
       // Loop until the temperature is very close target
-      #define TEMP_BED_CONDITIONS (wants_to_cool ? isCoolingBed() : isHeatingBed())
+      #define TEMP_BED_CONDITIONS (wants_to_cool ? thermalManager.isCoolingBed() : thermalManager.isHeatingBed())
     #endif //TEMP_BED_RESIDENCY_TIME > 0
 
     float theTarget = -1;
@@ -4580,16 +4575,16 @@ inline void gcode_M109() {
       }
 
       // Target temperature might be changed during the loop
-      if (theTarget != degTargetBed()) {
-        theTarget = degTargetBed();
-        wants_to_cool = isCoolingBed();
+      if (theTarget != thermalManager.degTargetBed()) {
+        wants_to_cool = thermalManager.isCoolingBed();
+        theTarget = thermalManager.degTargetBed();
 
         // Exit if S<lower>, continue if S<higher>, R<lower>, or R<higher>
         if (no_wait_for_cooling && wants_to_cool) break;
 
         // Prevent a wait-forever situation if R is misused i.e. M190 R0
-        // Simply don't wait for cooling below 30C
-        if (wants_to_cool && theTarget < (EXTRUDE_MINTEMP)/2) break;
+        // Simply don't wait to cool a bed under 30C
+        if (wants_to_cool && theTarget < 30) break;
       }
 
       idle();
@@ -4597,7 +4592,7 @@ inline void gcode_M109() {
 
       #if TEMP_BED_RESIDENCY_TIME > 0
 
-        float temp_diff = fabs(degBed() - degTargetBed());
+        float temp_diff = fabs(theTarget - thermalManager.degBed());
 
         if (!residency_start_ms) {
           // Start the TEMP_BED_RESIDENCY_TIME timer when we reach target temp for the first time.
@@ -4720,7 +4715,7 @@ inline void gcode_M112() { kill(PSTR(MSG_KILLED)); }
  */
 inline void gcode_M140() {
   if (DEBUGGING(DRYRUN)) return;
-  if (code_seen('S')) setTargetBed(code_value());
+  if (code_seen('S')) thermalManager.setTargetBed(code_value());
 }
 
 #if ENABLED(ULTIPANEL)
@@ -4811,7 +4806,7 @@ inline void gcode_M140() {
  *      This code should ALWAYS be available for EMERGENCY SHUTDOWN!
  */
 inline void gcode_M81() {
-  disable_all_heaters();
+  thermalManager.disable_all_heaters();
   stepper.finish_and_disable();
   #if FAN_COUNT > 0
     #if FAN_COUNT > 1
@@ -5469,7 +5464,7 @@ inline void gcode_M226() {
         NOMORE(lpq_len, LPQ_MAX_LEN);
       #endif
 
-      updatePID();
+      thermalManager.updatePID();
       SERIAL_ECHO_START;
       #if ENABLED(PID_PARAMS_PER_EXTRUDER)
         SERIAL_ECHO(" e:"); // specify extruder in serial output
@@ -5499,18 +5494,19 @@ inline void gcode_M226() {
 #if ENABLED(PIDTEMPBED)
 
   inline void gcode_M304() {
-    if (code_seen('P')) bedKp = code_value();
-    if (code_seen('I')) bedKi = scalePID_i(code_value());
-    if (code_seen('D')) bedKd = scalePID_d(code_value());
+    if (code_seen('P')) thermalManager.bedKp = code_value();
+    if (code_seen('I')) thermalManager.bedKi = scalePID_i(code_value());
+    if (code_seen('D')) thermalManager.bedKd = scalePID_d(code_value());
+
+    thermalManager.updatePID();
 
-    updatePID();
     SERIAL_ECHO_START;
     SERIAL_ECHO(" p:");
-    SERIAL_ECHO(bedKp);
+    SERIAL_ECHO(thermalManager.bedKp);
     SERIAL_ECHO(" i:");
-    SERIAL_ECHO(unscalePID_i(bedKi));
+    SERIAL_ECHO(unscalePID_i(thermalManager.bedKi));
     SERIAL_ECHO(" d:");
-    SERIAL_ECHOLN(unscalePID_d(bedKd));
+    SERIAL_ECHOLN(unscalePID_d(thermalManager.bedKd));
   }
 
 #endif // PIDTEMPBED
@@ -5567,13 +5563,11 @@ inline void gcode_M226() {
 
 #if ENABLED(PREVENT_DANGEROUS_EXTRUDE)
 
-  void set_extrude_min_temp(float temp) { extrude_min_temp = temp; }
-
   /**
    * M302: Allow cold extrudes, or set the minimum extrude S<temperature>.
    */
   inline void gcode_M302() {
-    set_extrude_min_temp(code_seen('S') ? code_value() : 0);
+    thermalManager.extrude_min_temp = code_seen('S') ? code_value() : 0;
   }
 
 #endif // PREVENT_DANGEROUS_EXTRUDE
@@ -5599,7 +5593,7 @@ inline void gcode_M303() {
 
     KEEPALIVE_STATE(NOT_BUSY); // don't send "busy: processing" messages during autotune output
 
-    PID_autotune(temp, e, c, u);
+    thermalManager.PID_autotune(temp, e, c, u);
 
     KEEPALIVE_STATE(IN_HANDLER);
   #else
@@ -5781,7 +5775,7 @@ inline void gcode_M400() { stepper.synchronize(); }
     NOMORE(meas_delay_cm, MAX_MEASUREMENT_DELAY);
 
     if (filwidth_delay_index2 == -1) { // Initialize the ring buffer if not done since startup
-      int temp_ratio = widthFil_to_size_ratio();
+      int temp_ratio = thermalManager.widthFil_to_size_ratio();
 
       for (uint8_t i = 0; i < COUNT(measurement_delay); ++i)
         measurement_delay[i] = temp_ratio - 100;  // Subtract 100 to scale within a signed byte
@@ -5984,7 +5978,7 @@ inline void gcode_M503() {
    */
   inline void gcode_M600() {
 
-    if (degHotend(active_extruder) < extrude_min_temp) {
+    if (thermalManager.tooColdToExtrude(active_extruder)) {
       SERIAL_ERROR_START;
       SERIAL_ERRORLNPGM(MSG_TOO_COLD_FOR_M600);
       return;
@@ -7268,7 +7262,7 @@ void mesh_buffer_line(float x, float y, float z, const float e, float feed_rate,
     if (DEBUGGING(DRYRUN)) return;
     float de = dest_e - curr_e;
     if (de) {
-      if (degHotend(active_extruder) < extrude_min_temp) {
+      if (thermalManager.tooColdToExtrude(active_extruder)) {
         curr_e = dest_e; // Behave as if the move really took place, but ignore E part
         SERIAL_ECHO_START;
         SERIAL_ECHOLNPGM(MSG_ERR_COLD_EXTRUDE_STOP);
@@ -7565,7 +7559,7 @@ void plan_arc(
     millis_t ms = millis();
     if (ELAPSED(ms, nextMotorCheck)) {
       nextMotorCheck = ms + 2500UL; // Not a time critical function, so only check every 2.5s
-      if (X_ENABLE_READ == X_ENABLE_ON || Y_ENABLE_READ == Y_ENABLE_ON || Z_ENABLE_READ == Z_ENABLE_ON || soft_pwm_bed > 0
+      if (X_ENABLE_READ == X_ENABLE_ON || Y_ENABLE_READ == Y_ENABLE_ON || Z_ENABLE_READ == Z_ENABLE_ON || thermalManager.soft_pwm_bed > 0
           || E0_ENABLE_READ == E_ENABLE_ON // If any of the drivers are enabled...
           #if EXTRUDERS > 1
             || E1_ENABLE_READ == E_ENABLE_ON
@@ -7683,9 +7677,9 @@ void plan_arc(
     if (ELAPSED(millis(), next_status_led_update_ms)) {
       next_status_led_update_ms += 500; // Update every 0.5s
       for (int8_t cur_extruder = 0; cur_extruder < EXTRUDERS; ++cur_extruder)
-        max_temp = max(max(max_temp, degHotend(cur_extruder)), degTargetHotend(cur_extruder));
+        max_temp = max(max(max_temp, thermalManager.degHotend(cur_extruder)), thermalManager.degTargetHotend(cur_extruder));
       #if HAS_TEMP_BED
-        max_temp = max(max(max_temp, degTargetBed()), degBed());
+        max_temp = max(max(max_temp, thermalManager.degTargetBed()), thermalManager.degBed());
       #endif
       bool new_led = (max_temp > 55.0) ? true : (max_temp < 54.0) ? false : red_led;
       if (new_led != red_led) {
@@ -7726,7 +7720,7 @@ void idle(
     bool no_stepper_sleep/*=false*/
   #endif
 ) {
-  manage_heater();
+  thermalManager.manage_heater();
   manage_inactivity(
     #if ENABLED(FILAMENTCHANGEENABLE)
       no_stepper_sleep
@@ -7831,7 +7825,7 @@ void manage_inactivity(bool ignore_stepper_queue/*=false*/) {
 
   #if ENABLED(EXTRUDER_RUNOUT_PREVENT)
     if (ELAPSED(ms, previous_cmd_ms + (EXTRUDER_RUNOUT_SECONDS) * 1000UL))
-      if (degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP) {
+      if (thermalManager.degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP) {
         bool oldstatus;
         switch (active_extruder) {
           case 0:
@@ -7914,7 +7908,7 @@ void kill(const char* lcd_msg) {
   #endif
 
   cli(); // Stop interrupts
-  disable_all_heaters();
+  thermalManager.disable_all_heaters();
   disable_all_steppers();
 
   #if HAS_POWER_SWITCH
@@ -8010,7 +8004,7 @@ void kill(const char* lcd_msg) {
 #endif // FAST_PWM_FAN
 
 void stop() {
-  disable_all_heaters();
+  thermalManager.disable_all_heaters();
   if (IsRunning()) {
     Running = false;
     Stopped_gcode_LastN = gcode_LastN; // Save last g_code for restart
diff --git a/Marlin/cardreader.cpp b/Marlin/cardreader.cpp
index 1e9aab3b66..2fead53823 100644
--- a/Marlin/cardreader.cpp
+++ b/Marlin/cardreader.cpp
@@ -609,7 +609,7 @@ void CardReader::printingHasFinished() {
     sdprinting = false;
     if (SD_FINISHED_STEPPERRELEASE)
       enqueue_and_echo_commands_P(PSTR(SD_FINISHED_RELEASECOMMAND));
-    autotempShutdown();
+    thermalManager.autotempShutdown();
   }
 }
 
diff --git a/Marlin/configuration_store.cpp b/Marlin/configuration_store.cpp
index 8946156b11..0dbcd995b2 100644
--- a/Marlin/configuration_store.cpp
+++ b/Marlin/configuration_store.cpp
@@ -95,7 +95,7 @@
  *  363  M301 L        lpq_len (int)
  *
  * PIDTEMPBED:
- *  365  M304 PID  bedKp, bedKi, bedKd (float x3)
+ *  365  M304 PID  thermalManager.bedKp, thermalManager.bedKi, thermalManager.bedKd (float x3)
  *
  * DOGLCD:
  *  377  M250 C    lcd_contrast (int)
@@ -261,9 +261,9 @@ void Config_StoreSettings()  {
     #endif // !PIDTEMP
       {
         dummy = DUMMY_PID_VALUE; // When read, will not change the existing value
-        EEPROM_WRITE_VAR(i, dummy);
+        EEPROM_WRITE_VAR(i, dummy); // Kp
         dummy = 0.0f;
-        for (uint8_t q = 3; q--;) EEPROM_WRITE_VAR(i, dummy);
+        for (uint8_t q = 3; q--;) EEPROM_WRITE_VAR(i, dummy); // Ki, Kd, Kc
       }
 
   } // Extruders Loop
@@ -274,13 +274,14 @@ void Config_StoreSettings()  {
   EEPROM_WRITE_VAR(i, lpq_len);
 
   #if DISABLED(PIDTEMPBED)
-    float bedKp = DUMMY_PID_VALUE, bedKi = DUMMY_PID_VALUE, bedKd = DUMMY_PID_VALUE;
+    dummy = DUMMY_PID_VALUE;
+    for (uint8_t q = 3; q--;) EEPROM_WRITE_VAR(i, dummy);
+  #else
+    EEPROM_WRITE_VAR(i, thermalManager.bedKp);
+    EEPROM_WRITE_VAR(i, thermalManager.bedKi);
+    EEPROM_WRITE_VAR(i, thermalManager.bedKd);
   #endif
 
-  EEPROM_WRITE_VAR(i, bedKp);
-  EEPROM_WRITE_VAR(i, bedKi);
-  EEPROM_WRITE_VAR(i, bedKd);
-
   #if DISABLED(HAS_LCD_CONTRAST)
     const int lcd_contrast = 32;
   #endif
@@ -450,20 +451,17 @@ void Config_RetrieveSettings() {
     #endif
     EEPROM_READ_VAR(i, lpq_len);
 
-    #if DISABLED(PIDTEMPBED)
-      float bedKp, bedKi, bedKd;
+    #if ENABLED(PIDTEMPBED)
+      EEPROM_READ_VAR(i, dummy); // bedKp
+      if (dummy != DUMMY_PID_VALUE) {
+        thermalManager.bedKp = dummy;
+        EEPROM_READ_VAR(i, thermalManager.bedKi);
+        EEPROM_READ_VAR(i, thermalManager.bedKd);
+      }
+    #else
+      for (uint8_t q=3; q--;) EEPROM_READ_VAR(i, dummy); // bedKp, bedKi, bedKd
     #endif
 
-    EEPROM_READ_VAR(i, dummy); // bedKp
-    if (dummy != DUMMY_PID_VALUE) {
-      bedKp = dummy; UNUSED(bedKp);
-      EEPROM_READ_VAR(i, bedKi);
-      EEPROM_READ_VAR(i, bedKd);
-    }
-    else {
-      for (uint8_t q=2; q--;) EEPROM_READ_VAR(i, dummy); // bedKi, bedKd
-    }
-
     #if DISABLED(HAS_LCD_CONTRAST)
       int lcd_contrast;
     #endif
@@ -502,8 +500,8 @@ void Config_RetrieveSettings() {
     }
 
     calculate_volumetric_multipliers();
-    // Call updatePID (similar to when we have processed M301)
-    updatePID();
+    // Call thermalManager.updatePID (similar to when we have processed M301)
+    thermalManager.updatePID();
 
     // Report settings retrieved and length
     SERIAL_ECHO_START;
@@ -602,14 +600,14 @@ void Config_ResetDefault() {
     #if ENABLED(PID_ADD_EXTRUSION_RATE)
       lpq_len = 20; // default last-position-queue size
     #endif
-    // call updatePID (similar to when we have processed M301)
-    updatePID();
+    // call thermalManager.updatePID (similar to when we have processed M301)
+    thermalManager.updatePID();
   #endif // PIDTEMP
 
   #if ENABLED(PIDTEMPBED)
-    bedKp = DEFAULT_bedKp;
-    bedKi = scalePID_i(DEFAULT_bedKi);
-    bedKd = scalePID_d(DEFAULT_bedKd);
+    thermalManager.bedKp = DEFAULT_bedKp;
+    thermalManager.bedKi = scalePID_i(DEFAULT_bedKi);
+    thermalManager.bedKd = scalePID_d(DEFAULT_bedKd);
   #endif
 
   #if ENABLED(FWRETRACT)
@@ -835,9 +833,9 @@ void Config_PrintSettings(bool forReplay) {
 
     #if ENABLED(PIDTEMPBED)
       CONFIG_ECHO_START;
-      SERIAL_ECHOPAIR("  M304 P", bedKp);
-      SERIAL_ECHOPAIR(" I", unscalePID_i(bedKi));
-      SERIAL_ECHOPAIR(" D", unscalePID_d(bedKd));
+      SERIAL_ECHOPAIR("  M304 P", thermalManager.bedKp);
+      SERIAL_ECHOPAIR(" I", unscalePID_i(thermalManager.bedKi));
+      SERIAL_ECHOPAIR(" D", unscalePID_d(thermalManager.bedKd));
       SERIAL_EOL;
     #endif
 
diff --git a/Marlin/dogm_lcd_implementation.h b/Marlin/dogm_lcd_implementation.h
index 3af19e15d4..89094bb00d 100644
--- a/Marlin/dogm_lcd_implementation.h
+++ b/Marlin/dogm_lcd_implementation.h
@@ -294,13 +294,13 @@ FORCE_INLINE void _draw_heater_status(int x, int heater) {
     const bool isBed = false;
   #endif
 
-  _draw_centered_temp((isBed ? degTargetBed() : degTargetHotend(heater)) + 0.5, x, 7);
+  _draw_centered_temp((isBed ? thermalManager.degTargetBed() : thermalManager.degTargetHotend(heater)) + 0.5, x, 7);
 
-  _draw_centered_temp((isBed ? degBed() : degHotend(heater)) + 0.5, x, 28);
+  _draw_centered_temp((isBed ? thermalManager.degBed() : thermalManager.degHotend(heater)) + 0.5, x, 28);
 
   int h = isBed ? 7 : 8,
       y = isBed ? 18 : 17;
-  if (isBed ? isHeatingBed() : isHeatingHotend(heater)) {
+  if (isBed ? thermalManager.isHeatingBed() : thermalManager.isHeatingHotend(heater)) {
     u8g.setColorIndex(0); // white on black
     u8g.drawBox(x + h, y, 2, 2);
     u8g.setColorIndex(1); // black on white
diff --git a/Marlin/endstops.cpp b/Marlin/endstops.cpp
index 8d0b0f979e..d08bf1a8aa 100644
--- a/Marlin/endstops.cpp
+++ b/Marlin/endstops.cpp
@@ -153,7 +153,7 @@ void Endstops::report_state() {
         card.sdprinting = false;
         card.closefile();
         stepper.quick_stop();
-        disable_all_heaters(); // switch off all heaters.
+        thermalManager.disable_all_heaters(); // switch off all heaters.
       }
     #endif
   }
diff --git a/Marlin/planner.cpp b/Marlin/planner.cpp
index 4fe8dee9fa..0beae32674 100644
--- a/Marlin/planner.cpp
+++ b/Marlin/planner.cpp
@@ -304,7 +304,7 @@ void Planner::recalculate() {
     static float oldt = 0;
 
     if (!autotemp_enabled) return;
-    if (degTargetHotend0() + 2 < autotemp_min) return; // probably temperature set to zero.
+    if (thermalManager.degTargetHotend(0) + 2 < autotemp_min) return; // probably temperature set to zero.
 
     float high = 0.0;
     for (uint8_t b = block_buffer_tail; b != block_buffer_head; b = next_block_index(b)) {
@@ -322,7 +322,7 @@ void Planner::recalculate() {
       t += (AUTOTEMP_OLDWEIGHT) * oldt;
     }
     oldt = t;
-    setTargetHotend0(t);
+    thermalManager.setTargetHotend(t, 0);
   }
 
 #endif //AUTOTEMP
@@ -489,11 +489,12 @@ void Planner::check_axes_activity() {
   // The target position of the tool in absolute steps
   // Calculate target position in absolute steps
   //this should be done after the wait, because otherwise a M92 code within the gcode disrupts this calculation somehow
-  long target[NUM_AXIS];
-  target[X_AXIS] = lround(x * axis_steps_per_unit[X_AXIS]);
-  target[Y_AXIS] = lround(y * axis_steps_per_unit[Y_AXIS]);
-  target[Z_AXIS] = lround(z * axis_steps_per_unit[Z_AXIS]);
-  target[E_AXIS] = lround(e * axis_steps_per_unit[E_AXIS]);
+  long target[NUM_AXIS] = {
+    lround(x * axis_steps_per_unit[X_AXIS]),
+    lround(y * axis_steps_per_unit[Y_AXIS]),
+    lround(z * axis_steps_per_unit[Z_AXIS]),
+    lround(e * axis_steps_per_unit[E_AXIS])
+  };
 
   long dx = target[X_AXIS] - position[X_AXIS],
        dy = target[Y_AXIS] - position[Y_AXIS],
@@ -507,7 +508,7 @@ void Planner::check_axes_activity() {
 
   #if ENABLED(PREVENT_DANGEROUS_EXTRUDE)
     if (de) {
-      if (degHotend(extruder) < extrude_min_temp) {
+      if (thermalManager.tooColdToExtrude(extruder)) {
         position[E_AXIS] = target[E_AXIS]; // Behave as if the move really took place, but ignore E part
         de = 0; // no difference
         SERIAL_ECHO_START;
@@ -788,7 +789,7 @@ void Planner::check_axes_activity() {
         // If the index has changed (must have gone forward)...
         if (filwidth_delay_index1 != filwidth_delay_index2) {
           filwidth_e_count = 0; // Reset the E movement counter
-          int8_t meas_sample = widthFil_to_size_ratio() - 100; // Subtract 100 to reduce magnitude - to store in a signed char
+          int8_t meas_sample = thermalManager.widthFil_to_size_ratio() - 100; // Subtract 100 to reduce magnitude - to store in a signed char
           do {
             filwidth_delay_index2 = (filwidth_delay_index2 + 1) % MMD_CM; // The next unused slot
             measurement_delay[filwidth_delay_index2] = meas_sample;       // Store the measurement
diff --git a/Marlin/temperature.cpp b/Marlin/temperature.cpp
index 3f5b2d1c53..1d71724521 100644
--- a/Marlin/temperature.cpp
+++ b/Marlin/temperature.cpp
@@ -21,24 +21,8 @@
  */
 
 /**
-  temperature.cpp - temperature control
-  Part of Marlin
-
- Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
+ * temperature.cpp - temperature control
+ */
 
 #include "Marlin.h"
 #include "ultralcd.h"
@@ -50,144 +34,10 @@
   #include "watchdog.h"
 #endif
 
-//===========================================================================
-//================================== macros =================================
-//===========================================================================
-
 #ifdef K1 // Defined in Configuration.h in the PID settings
   #define K2 (1.0-K1)
 #endif
 
-#if ENABLED(PIDTEMPBED) || ENABLED(PIDTEMP)
-  #define PID_dT ((OVERSAMPLENR * 12.0)/(F_CPU / 64.0 / 256.0))
-#endif
-
-//===========================================================================
-//============================= public variables ============================
-//===========================================================================
-
-int target_temperature[4] = { 0 };
-int target_temperature_bed = 0;
-int current_temperature_raw[4] = { 0 };
-float current_temperature[4] = { 0.0 };
-int current_temperature_bed_raw = 0;
-float current_temperature_bed = 0.0;
-#if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
-  int redundant_temperature_raw = 0;
-  float redundant_temperature = 0.0;
-#endif
-
-#if ENABLED(PIDTEMPBED)
-  float bedKp = DEFAULT_bedKp;
-  float bedKi = (DEFAULT_bedKi* PID_dT);
-  float bedKd = (DEFAULT_bedKd / PID_dT);
-#endif //PIDTEMPBED
-
-#if ENABLED(FAN_SOFT_PWM)
-  unsigned char fanSpeedSoftPwm[FAN_COUNT];
-#endif
-
-unsigned char soft_pwm_bed;
-
-#if ENABLED(BABYSTEPPING)
-  volatile int babystepsTodo[3] = { 0 };
-#endif
-
-#if ENABLED(FILAMENT_WIDTH_SENSOR)
-  int current_raw_filwidth = 0;  //Holds measured filament diameter - one extruder only
-#endif
-
-#if ENABLED(THERMAL_PROTECTION_HOTENDS) || ENABLED(THERMAL_PROTECTION_BED)
-  enum TRState { TRReset, TRInactive, TRFirstHeating, TRStable, TRRunaway };
-  void thermal_runaway_protection(TRState* state, millis_t* timer, float temperature, float target_temperature, int heater_id, int period_seconds, int hysteresis_degc);
-  #if ENABLED(THERMAL_PROTECTION_HOTENDS)
-    static TRState thermal_runaway_state_machine[4] = { TRReset, TRReset, TRReset, TRReset };
-    static millis_t thermal_runaway_timer[4]; // = {0,0,0,0};
-  #endif
-  #if ENABLED(THERMAL_PROTECTION_BED) && TEMP_SENSOR_BED != 0
-    static TRState thermal_runaway_bed_state_machine = TRReset;
-    static millis_t thermal_runaway_bed_timer;
-  #endif
-#endif
-
-//===========================================================================
-//============================ private variables ============================
-//===========================================================================
-
-static volatile bool temp_meas_ready = false;
-
-#if ENABLED(PIDTEMP)
-  //static cannot be external:
-  static float temp_iState[EXTRUDERS] = { 0 };
-  static float temp_dState[EXTRUDERS] = { 0 };
-  static float pTerm[EXTRUDERS];
-  static float iTerm[EXTRUDERS];
-  static float dTerm[EXTRUDERS];
-  #if ENABLED(PID_ADD_EXTRUSION_RATE)
-    static float cTerm[EXTRUDERS];
-    static long last_position[EXTRUDERS];
-    static long lpq[LPQ_MAX_LEN];
-    static int lpq_ptr = 0;
-  #endif
-  //int output;
-  static float pid_error[EXTRUDERS];
-  static float temp_iState_min[EXTRUDERS];
-  static float temp_iState_max[EXTRUDERS];
-  static bool pid_reset[EXTRUDERS];
-#endif //PIDTEMP
-#if ENABLED(PIDTEMPBED)
-  //static cannot be external:
-  static float temp_iState_bed = { 0 };
-  static float temp_dState_bed = { 0 };
-  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;
-#else //PIDTEMPBED
-  static millis_t  next_bed_check_ms;
-#endif //PIDTEMPBED
-static unsigned char soft_pwm[EXTRUDERS];
-
-#if ENABLED(FAN_SOFT_PWM)
-  static unsigned char soft_pwm_fan[FAN_COUNT];
-#endif
-#if HAS_AUTO_FAN
-  static millis_t next_auto_fan_check_ms;
-#endif
-
-#if ENABLED(PIDTEMP)
-  #if ENABLED(PID_PARAMS_PER_EXTRUDER)
-    float Kp[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(DEFAULT_Kp);
-    float Ki[EXTRUDERS] = ARRAY_BY_EXTRUDERS1((DEFAULT_Ki) * (PID_dT));
-    float Kd[EXTRUDERS] = ARRAY_BY_EXTRUDERS1((DEFAULT_Kd) / (PID_dT));
-    #if ENABLED(PID_ADD_EXTRUSION_RATE)
-      float Kc[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(DEFAULT_Kc);
-    #endif // PID_ADD_EXTRUSION_RATE
-  #else //PID_PARAMS_PER_EXTRUDER
-    float Kp = DEFAULT_Kp;
-    float Ki = (DEFAULT_Ki) * (PID_dT);
-    float Kd = (DEFAULT_Kd) / (PID_dT);
-    #if ENABLED(PID_ADD_EXTRUSION_RATE)
-      float Kc = DEFAULT_Kc;
-    #endif // PID_ADD_EXTRUSION_RATE
-  #endif // PID_PARAMS_PER_EXTRUDER
-#endif //PIDTEMP
-
-// 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, HEATER_3_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, HEATER_3_RAW_HI_TEMP);
-static int minttemp[EXTRUDERS] = { 0 };
-static int maxttemp[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(16383);
-#ifdef BED_MINTEMP
-  static int bed_minttemp_raw = HEATER_BED_RAW_LO_TEMP;
-#endif
-#ifdef BED_MAXTEMP
-  static int bed_maxttemp_raw = HEATER_BED_RAW_HI_TEMP;
-#endif
-
 #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
   static void* heater_ttbl_map[2] = {(void*)HEATER_0_TEMPTABLE, (void*)HEATER_1_TEMPTABLE };
   static uint8_t heater_ttbllen_map[2] = { HEATER_0_TEMPTABLE_LEN, HEATER_1_TEMPTABLE_LEN };
@@ -196,39 +46,11 @@ static int maxttemp[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(16383);
   static uint8_t heater_ttbllen_map[EXTRUDERS] = ARRAY_BY_EXTRUDERS(HEATER_0_TEMPTABLE_LEN, HEATER_1_TEMPTABLE_LEN, HEATER_2_TEMPTABLE_LEN, HEATER_3_TEMPTABLE_LEN);
 #endif
 
-static float analog2temp(int raw, uint8_t e);
-static float analog2tempBed(int raw);
-static void updateTemperaturesFromRawValues();
-
-#if ENABLED(THERMAL_PROTECTION_HOTENDS) && WATCH_TEMP_PERIOD > 0
-  int watch_target_temp[EXTRUDERS] = { 0 };
-  millis_t watch_heater_next_ms[EXTRUDERS] = { 0 };
-#endif
-
-#if ENABLED(THERMAL_PROTECTION_BED) && WATCH_BED_TEMP_PERIOD > 0
-  int watch_target_bed_temp = 0;
-  millis_t watch_bed_next_ms = 0;
-#endif
-
-#ifndef SOFT_PWM_SCALE
-  #define SOFT_PWM_SCALE 0
-#endif
-
-#if ENABLED(FILAMENT_WIDTH_SENSOR)
-  static int meas_shift_index;  //used to point to a delayed sample in buffer for filament width sensor
-#endif
-
-#if ENABLED(HEATER_0_USES_MAX6675)
-  static int read_max6675();
-#endif
-
-//===========================================================================
-//================================ Functions ================================
-//===========================================================================
+Temperature thermalManager;
 
 #if HAS_PID_HEATING
 
-  void PID_autotune(float temp, int extruder, int ncycles, bool set_result/*=false*/) {
+  void Temperature::PID_autotune(float temp, int extruder, int ncycles, bool set_result/*=false*/) {
     float input = 0.0;
     int cycles = 0;
     bool heating = true;
@@ -242,7 +64,7 @@ static void updateTemperaturesFromRawValues();
     float max = 0, min = 10000;
 
     #if HAS_AUTO_FAN
-      millis_t next_auto_fan_check_ms = temp_ms + 2500UL;
+      next_auto_fan_check_ms = temp_ms + 2500UL;
     #endif
 
     if (false
@@ -459,9 +281,37 @@ static void updateTemperaturesFromRawValues();
     }
   }
 
-#endif // PIDTEMP
+#endif // HAS_PID_HEATING
 
-void updatePID() {
+#if ENABLED(PIDTEMP)
+
+  #if ENABLED(PID_PARAMS_PER_EXTRUDER)
+
+    float Temperature::Kp[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(DEFAULT_Kp),
+          Temperature::Ki[EXTRUDERS] = ARRAY_BY_EXTRUDERS1((DEFAULT_Ki) * (PID_dT)),
+          Temperature::Kd[EXTRUDERS] = ARRAY_BY_EXTRUDERS1((DEFAULT_Kd) / (PID_dT));
+
+    #if ENABLED(PID_ADD_EXTRUSION_RATE)
+      float Temperature::Kc[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(DEFAULT_Kc);
+    #endif
+
+  #else
+
+    float Temperature::Kp = DEFAULT_Kp,
+          Temperature::Ki = (DEFAULT_Ki) * (PID_dT),
+          Temperature::Kd = (DEFAULT_Kd) / (PID_dT);
+
+    #if ENABLED(PID_ADD_EXTRUSION_RATE)
+      float Temperature::Kc = DEFAULT_Kc;
+    #endif
+
+  #endif
+
+#endif
+
+Temperature::Temperature() { }
+
+void Temperature::updatePID() {
   #if ENABLED(PIDTEMP)
     for (int e = 0; e < EXTRUDERS; e++) {
       temp_iState_max[e] = (PID_INTEGRAL_DRIVE_MAX) / PID_PARAM(Ki, e);
@@ -475,85 +325,41 @@ void updatePID() {
   #endif
 }
 
-int getHeaterPower(int heater) {
+int Temperature::getHeaterPower(int heater) {
   return heater < 0 ? soft_pwm_bed : soft_pwm[heater];
 }
 
 #if HAS_AUTO_FAN
 
-void setExtruderAutoFanState(int pin, bool state) {
-  unsigned char newFanSpeed = (state != 0) ? EXTRUDER_AUTO_FAN_SPEED : 0;
-  // this idiom allows both digital and PWM fan outputs (see M42 handling).
-  digitalWrite(pin, newFanSpeed);
-  analogWrite(pin, newFanSpeed);
-}
-
-void checkExtruderAutoFans() {
-  uint8_t fanState = 0;
-
-  // which fan pins need to be turned on?
-  #if HAS_AUTO_FAN_0
-    if (current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE)
-      fanState |= 1;
-  #endif
-  #if HAS_AUTO_FAN_1
-    if (current_temperature[1] > EXTRUDER_AUTO_FAN_TEMPERATURE) {
-      if (EXTRUDER_1_AUTO_FAN_PIN == EXTRUDER_0_AUTO_FAN_PIN)
-        fanState |= 1;
-      else
-        fanState |= 2;
+  void Temperature::checkExtruderAutoFans() {
+    const uint8_t fanPin[] = { EXTRUDER_0_AUTO_FAN_PIN, EXTRUDER_1_AUTO_FAN_PIN, EXTRUDER_2_AUTO_FAN_PIN, EXTRUDER_3_AUTO_FAN_PIN };
+    const int fanBit[] = { 0,
+      EXTRUDER_1_AUTO_FAN_PIN == EXTRUDER_0_AUTO_FAN_PIN ? 0 : 1,
+      EXTRUDER_2_AUTO_FAN_PIN == EXTRUDER_0_AUTO_FAN_PIN ? 0 :
+      EXTRUDER_2_AUTO_FAN_PIN == EXTRUDER_1_AUTO_FAN_PIN ? 1 : 2,
+      EXTRUDER_3_AUTO_FAN_PIN == EXTRUDER_0_AUTO_FAN_PIN ? 0 :
+      EXTRUDER_3_AUTO_FAN_PIN == EXTRUDER_1_AUTO_FAN_PIN ? 1 :
+      EXTRUDER_3_AUTO_FAN_PIN == EXTRUDER_2_AUTO_FAN_PIN ? 2 : 3
+    };
+    uint8_t fanState = 0;
+    for (int f = 0; f <= 3; f++) {
+      if (current_temperature[f] > EXTRUDER_AUTO_FAN_TEMPERATURE)
+        SBI(fanState, fanBit[f]);
     }
-  #endif
-  #if HAS_AUTO_FAN_2
-    if (current_temperature[2] > EXTRUDER_AUTO_FAN_TEMPERATURE) {
-      if (EXTRUDER_2_AUTO_FAN_PIN == EXTRUDER_0_AUTO_FAN_PIN)
-        fanState |= 1;
-      else if (EXTRUDER_2_AUTO_FAN_PIN == EXTRUDER_1_AUTO_FAN_PIN)
-        fanState |= 2;
-      else
-        fanState |= 4;
+    for (int f = 0; f <= 3; f++) {
+      unsigned char newFanSpeed = TEST(fanState, f) ? EXTRUDER_AUTO_FAN_SPEED : 0;
+      // this idiom allows both digital and PWM fan outputs (see M42 handling).
+      digitalWrite(fanPin[f], newFanSpeed);
+      analogWrite(fanPin[f], newFanSpeed);
     }
-  #endif
-  #if HAS_AUTO_FAN_3
-    if (current_temperature[3] > EXTRUDER_AUTO_FAN_TEMPERATURE) {
-      if (EXTRUDER_3_AUTO_FAN_PIN == EXTRUDER_0_AUTO_FAN_PIN)
-        fanState |= 1;
-      else if (EXTRUDER_3_AUTO_FAN_PIN == EXTRUDER_1_AUTO_FAN_PIN)
-        fanState |= 2;
-      else if (EXTRUDER_3_AUTO_FAN_PIN == EXTRUDER_2_AUTO_FAN_PIN)
-        fanState |= 4;
-      else
-        fanState |= 8;
-    }
-  #endif
-
-  // update extruder auto fan states
-  #if HAS_AUTO_FAN_0
-    setExtruderAutoFanState(EXTRUDER_0_AUTO_FAN_PIN, (fanState & 1) != 0);
-  #endif
-  #if HAS_AUTO_FAN_1
-    if (EXTRUDER_1_AUTO_FAN_PIN != EXTRUDER_0_AUTO_FAN_PIN)
-      setExtruderAutoFanState(EXTRUDER_1_AUTO_FAN_PIN, (fanState & 2) != 0);
-  #endif
-  #if HAS_AUTO_FAN_2
-    if (EXTRUDER_2_AUTO_FAN_PIN != EXTRUDER_0_AUTO_FAN_PIN
-        && EXTRUDER_2_AUTO_FAN_PIN != EXTRUDER_1_AUTO_FAN_PIN)
-      setExtruderAutoFanState(EXTRUDER_2_AUTO_FAN_PIN, (fanState & 4) != 0);
-  #endif
-  #if HAS_AUTO_FAN_3
-    if (EXTRUDER_3_AUTO_FAN_PIN != EXTRUDER_0_AUTO_FAN_PIN
-        && EXTRUDER_3_AUTO_FAN_PIN != EXTRUDER_1_AUTO_FAN_PIN
-        && EXTRUDER_3_AUTO_FAN_PIN != EXTRUDER_2_AUTO_FAN_PIN)
-      setExtruderAutoFanState(EXTRUDER_3_AUTO_FAN_PIN, (fanState & 8) != 0);
-  #endif
-}
+  }
 
 #endif // HAS_AUTO_FAN
 
 //
 // Temperature Error Handlers
 //
-inline void _temp_error(int e, const char* serial_msg, const char* lcd_msg) {
+void Temperature::_temp_error(int e, const char* serial_msg, const char* lcd_msg) {
   static bool killed = false;
   if (IsRunning()) {
     SERIAL_ERROR_START;
@@ -572,14 +378,14 @@ inline void _temp_error(int e, const char* serial_msg, const char* lcd_msg) {
   #endif
 }
 
-void max_temp_error(uint8_t e) {
+void Temperature::max_temp_error(uint8_t e) {
   _temp_error(e, PSTR(MSG_T_MAXTEMP), PSTR(MSG_ERR_MAXTEMP));
 }
-void min_temp_error(uint8_t e) {
+void Temperature::min_temp_error(uint8_t e) {
   _temp_error(e, PSTR(MSG_T_MINTEMP), PSTR(MSG_ERR_MINTEMP));
 }
 
-float get_pid_output(int e) {
+float Temperature::get_pid_output(int e) {
   float pid_output;
   #if ENABLED(PIDTEMP)
     #if DISABLED(PID_OPENLOOP)
@@ -658,7 +464,7 @@ float get_pid_output(int e) {
 }
 
 #if ENABLED(PIDTEMPBED)
-  float get_pid_output_bed() {
+  float Temperature::get_pid_output_bed() {
     float pid_output;
     #if DISABLED(PID_OPENLOOP)
       pid_error_bed = target_temperature_bed - current_temperature_bed;
@@ -710,7 +516,7 @@ float get_pid_output(int e) {
  *  - Apply filament width to the extrusion rate (may move)
  *  - Update the heated bed PID output value
  */
-void manage_heater() {
+void Temperature::manage_heater() {
 
   if (!temp_meas_ready) return;
 
@@ -811,7 +617,7 @@ void manage_heater() {
 
   #if TEMP_SENSOR_BED != 0
 
-    #if ENABLED(THERMAL_PROTECTION_BED)
+    #if HAS_THERMALLY_PROTECTED_BED
       thermal_runaway_protection(&thermal_runaway_bed_state_machine, &thermal_runaway_bed_timer, current_temperature_bed, target_temperature_bed, -1, THERMAL_PROTECTION_BED_PERIOD, THERMAL_PROTECTION_BED_HYSTERESIS);
     #endif
 
@@ -846,9 +652,10 @@ void manage_heater() {
 }
 
 #define PGM_RD_W(x)   (short)pgm_read_word(&x)
+
 // Derived from RepRap FiveD extruder::getTemperature()
 // For hot end temperature measurement.
-static float analog2temp(int raw, uint8_t e) {
+float Temperature::analog2temp(int raw, uint8_t e) {
   #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
     if (e > EXTRUDERS)
   #else
@@ -891,7 +698,7 @@ static float analog2temp(int raw, uint8_t e) {
 
 // Derived from RepRap FiveD extruder::getTemperature()
 // For bed temperature measurement.
-static float analog2tempBed(int raw) {
+float Temperature::analog2tempBed(int raw) {
   #if ENABLED(BED_USES_THERMISTOR)
     float celsius = 0;
     byte i;
@@ -923,18 +730,22 @@ static float analog2tempBed(int raw) {
   #endif
 }
 
-/* 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() {
+/**
+ * Get the raw values into the actual temperatures.
+ * The raw values are created in interrupt context,
+ * and this function is called from normal context
+ * as it would block the stepper routine.
+ */
+void Temperature::updateTemperaturesFromRawValues() {
   #if ENABLED(HEATER_0_USES_MAX6675)
     current_temperature_raw[0] = read_max6675();
   #endif
   for (uint8_t e = 0; e < EXTRUDERS; e++) {
-    current_temperature[e] = analog2temp(current_temperature_raw[e], e);
+    current_temperature[e] = Temperature::analog2temp(current_temperature_raw[e], e);
   }
-  current_temperature_bed = analog2tempBed(current_temperature_bed_raw);
+  current_temperature_bed = Temperature::analog2tempBed(current_temperature_bed_raw);
   #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
-    redundant_temperature = analog2temp(redundant_temperature_raw, 1);
+    redundant_temperature = Temperature::analog2temp(redundant_temperature_raw, 1);
   #endif
   #if ENABLED(FILAMENT_WIDTH_SENSOR)
     filament_width_meas = analog2widthFil();
@@ -954,13 +765,13 @@ static void updateTemperaturesFromRawValues() {
 #if ENABLED(FILAMENT_WIDTH_SENSOR)
 
   // Convert raw Filament Width to millimeters
-  float analog2widthFil() {
+  float Temperature::analog2widthFil() {
     return current_raw_filwidth / 16383.0 * 5.0;
     //return current_raw_filwidth;
   }
 
   // Convert raw Filament Width to a ratio
-  int widthFil_to_size_ratio() {
+  int Temperature::widthFil_to_size_ratio() {
     float temp = filament_width_meas;
     if (temp < MEASURED_LOWER_LIMIT) temp = filament_width_nominal;  //assume sensor cut out
     else NOMORE(temp, MEASURED_UPPER_LIMIT);
@@ -974,7 +785,8 @@ static void updateTemperaturesFromRawValues() {
  * Initialize the temperature manager
  * The manager is implemented by periodic calls to manage_heater()
  */
-void tp_init() {
+void Temperature::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
     MCUCR = _BV(JTD);
@@ -1189,7 +1001,7 @@ void tp_init() {
    * their target temperature by a configurable margin.
    * This is called when the temperature is set. (M104, M109)
    */
-  void start_watching_heater(int e) {
+  void Temperature::start_watching_heater(int e) {
     if (degHotend(e) < degTargetHotend(e) - (WATCH_TEMP_INCREASE + TEMP_HYSTERESIS + 1)) {
       watch_target_temp[e] = degHotend(e) + WATCH_TEMP_INCREASE;
       watch_heater_next_ms[e] = millis() + (WATCH_TEMP_PERIOD) * 1000UL;
@@ -1205,7 +1017,7 @@ void tp_init() {
    * their target temperature by a configurable margin.
    * This is called when the temperature is set. (M140, M190)
    */
-  void start_watching_bed() {
+  void Temperature::start_watching_bed() {
     if (degBed() < degTargetBed() - (WATCH_BED_TEMP_INCREASE + TEMP_BED_HYSTERESIS + 1)) {
       watch_target_bed_temp = degBed() + WATCH_BED_TEMP_INCREASE;
       watch_bed_next_ms = millis() + (WATCH_BED_TEMP_PERIOD) * 1000UL;
@@ -1215,9 +1027,9 @@ void tp_init() {
   }
 #endif
 
-#if ENABLED(THERMAL_PROTECTION_HOTENDS) || ENABLED(THERMAL_PROTECTION_BED)
+#if ENABLED(THERMAL_PROTECTION_HOTENDS) || HAS_THERMALLY_PROTECTED_BED
 
-  void thermal_runaway_protection(TRState* state, millis_t* timer, float temperature, float target_temperature, int heater_id, int period_seconds, int hysteresis_degc) {
+  void Temperature::thermal_runaway_protection(TRState* state, millis_t* timer, float temperature, float target_temperature, int heater_id, int period_seconds, int hysteresis_degc) {
 
     static float tr_target_temperature[EXTRUDERS + 1] = { 0.0 };
 
@@ -1273,7 +1085,7 @@ void tp_init() {
 
 #endif // THERMAL_PROTECTION_HOTENDS || THERMAL_PROTECTION_BED
 
-void disable_all_heaters() {
+void Temperature::disable_all_heaters() {
   for (int i = 0; i < EXTRUDERS; i++) setTargetHotend(0, i);
   setTargetBed(0);
 
@@ -1327,9 +1139,9 @@ void disable_all_heaters() {
     #define MAX6675_DISCARD_BITS 3
   #endif
 
-  static millis_t next_max6675_ms = 0;
+  int Temperature::read_max6675() {
 
-  static int read_max6675() {
+    static millis_t next_max6675_ms = 0;
 
     millis_t ms = millis();
 
@@ -1392,10 +1204,10 @@ enum TempState {
   StartupDelay // Startup, delay initial temp reading a tiny bit so the hardware can settle
 };
 
-static unsigned long raw_temp_value[4] = { 0 };
-static unsigned long raw_temp_bed_value = 0;
-
-static void set_current_temp_raw() {
+/**
+ * Get raw temperatures
+ */
+void Temperature::set_current_temp_raw() {
   #if HAS_TEMP_0 && DISABLED(HEATER_0_USES_MAX6675)
     current_temperature_raw[0] = raw_temp_value[0];
   #endif
@@ -1423,7 +1235,9 @@ static void set_current_temp_raw() {
  *  - Check new temperature values for MIN/MAX errors
  *  - Step the babysteps value for each axis towards 0
  */
-ISR(TIMER0_COMPB_vect) {
+ISR(TIMER0_COMPB_vect) { thermalManager.isr(); }
+
+void Temperature::isr() {
 
   static unsigned char temp_count = 0;
   static TempState temp_state = StartupDelay;
@@ -1845,11 +1659,3 @@ ISR(TIMER0_COMPB_vect) {
     }
   #endif //BABYSTEPPING
 }
-
-#if ENABLED(PIDTEMP)
-  // Apply the scale factors to the PID values
-  float scalePID_i(float i)   { return i * PID_dT; }
-  float unscalePID_i(float i) { return i / PID_dT; }
-  float scalePID_d(float d)   { return d / PID_dT; }
-  float unscalePID_d(float d) { return d * PID_dT; }
-#endif //PIDTEMP
diff --git a/Marlin/temperature.h b/Marlin/temperature.h
index c5754a590b..2e404ba805 100644
--- a/Marlin/temperature.h
+++ b/Marlin/temperature.h
@@ -21,182 +21,331 @@
  */
 
 /**
-  temperature.h - temperature controller
-  Part of Marlin
-
-  Copyright (c) 2011 Erik van der Zalm
-
-  Grbl is free software: you can redistribute it and/or modify
-  it under the terms of the GNU General Public License as published by
-  the Free Software Foundation, either version 3 of the License, or
-  (at your option) any later version.
-
-  Grbl is distributed in the hope that it will be useful,
-  but WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  GNU General Public License for more details.
-
-  You should have received a copy of the GNU General Public License
-  along with Grbl.  If not, see <http://www.gnu.org/licenses/>.
-*/
+ * temperature.h - temperature controller
+ */
 
 #ifndef TEMPERATURE_H
 #define TEMPERATURE_H
 
 #include "Marlin.h"
 #include "planner.h"
+
 #if ENABLED(PID_ADD_EXTRUSION_RATE)
   #include "stepper.h"
 #endif
 
-// public functions
-void tp_init();  //initialize the heating
-void manage_heater(); //it is critical that this is called periodically.
-
-#if ENABLED(FILAMENT_WIDTH_SENSOR)
-  // For converting raw Filament Width to milimeters
-  float analog2widthFil();
-
-  // For converting raw Filament Width to an extrusion ratio
-  int widthFil_to_size_ratio();
+#ifndef SOFT_PWM_SCALE
+  #define SOFT_PWM_SCALE 0
 #endif
 
-// low level conversion routines
-// do not use these routines and variables outside of temperature.cpp
-extern int target_temperature[4];
-extern float current_temperature[4];
-#if ENABLED(SHOW_TEMP_ADC_VALUES)
-  extern int current_temperature_raw[4];
-  extern int current_temperature_bed_raw;
-#endif
-extern int target_temperature_bed;
-extern float current_temperature_bed;
-#if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
-  extern float redundant_temperature;
-#endif
+class Temperature {
 
-#if HAS_CONTROLLERFAN
-  extern unsigned char soft_pwm_bed;
-#endif
+  public:
 
-#if ENABLED(FAN_SOFT_PWM)
-  extern unsigned char fanSpeedSoftPwm[FAN_COUNT];
-#endif
+    int current_temperature_raw[EXTRUDERS] = { 0 };
+    float current_temperature[EXTRUDERS] = { 0.0 };
+    int target_temperature[EXTRUDERS] = { 0 };
 
-#if ENABLED(PIDTEMP)
+    int current_temperature_bed_raw = 0;
+    float current_temperature_bed = 0.0;
+    int target_temperature_bed = 0;
 
-  #if ENABLED(PID_PARAMS_PER_EXTRUDER)
-    extern float Kp[EXTRUDERS], Ki[EXTRUDERS], Kd[EXTRUDERS];  // one param per extruder
-    #if ENABLED(PID_ADD_EXTRUSION_RATE)
-      extern float Kc[EXTRUDERS];
+    #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
+      float redundant_temperature = 0.0;
     #endif
-    #define PID_PARAM(param, e) param[e] // use macro to point to array value
-  #else
-    extern float Kp, Ki, Kd;  // one param per extruder - saves 20 or 36 bytes of ram (inc array pointer)
-    #if ENABLED(PID_ADD_EXTRUSION_RATE)
-      extern float Kc;
+
+    unsigned char soft_pwm_bed;
+
+    #if ENABLED(FAN_SOFT_PWM)
+      unsigned char fanSpeedSoftPwm[FAN_COUNT];
     #endif
-    #define PID_PARAM(param, e) param // use macro to point directly to value
-  #endif // PID_PARAMS_PER_EXTRUDER
-  float scalePID_i(float i);
-  float scalePID_d(float d);
-  float unscalePID_i(float i);
-  float unscalePID_d(float d);
 
-#endif
+    #if ENABLED(PIDTEMP) || ENABLED(PIDTEMPBED)
+      #define PID_dT ((OVERSAMPLENR * 12.0)/(F_CPU / 64.0 / 256.0))
+    #endif
 
-#if ENABLED(PIDTEMPBED)
-  extern float bedKp, bedKi, bedKd;
-#endif
+    #if ENABLED(PIDTEMP)
 
-#if ENABLED(BABYSTEPPING)
-  extern volatile int babystepsTodo[3];
-#endif
+      #if ENABLED(PID_PARAMS_PER_EXTRUDER)
 
-//high level conversion routines, for use outside of temperature.cpp
-//inline so that there is no performance decrease.
-//deg=degreeCelsius
+        static float Kp[EXTRUDERS], Ki[EXTRUDERS], Kd[EXTRUDERS];
+        #if ENABLED(PID_ADD_EXTRUSION_RATE)
+          float Kc[EXTRUDERS];
+        #endif
+        #define PID_PARAM(param, e) Temperature::param[e]
 
-FORCE_INLINE float degHotend(uint8_t extruder) { return current_temperature[extruder]; }
-FORCE_INLINE float degBed() { return current_temperature_bed; }
+      #else
 
-#if ENABLED(SHOW_TEMP_ADC_VALUES)
-FORCE_INLINE float rawHotendTemp(uint8_t extruder) { return current_temperature_raw[extruder]; }
-FORCE_INLINE float rawBedTemp() { return current_temperature_bed_raw; }
-#endif
+        static float Kp, Ki, Kd;
+        #if ENABLED(PID_ADD_EXTRUSION_RATE)
+          static float Kc;
+        #endif
+        #define PID_PARAM(param, e) Temperature::param
 
-FORCE_INLINE float degTargetHotend(uint8_t extruder) { return target_temperature[extruder]; }
-FORCE_INLINE float degTargetBed() { return target_temperature_bed; }
+      #endif // PID_PARAMS_PER_EXTRUDER
 
-#if ENABLED(THERMAL_PROTECTION_HOTENDS) && WATCH_TEMP_PERIOD > 0
-  void start_watching_heater(int e = 0);
-#endif
+      // Apply the scale factors to the PID values
+      #define scalePID_i(i)   ( (i) * PID_dT )
+      #define unscalePID_i(i) ( (i) / PID_dT )
+      #define scalePID_d(d)   ( (d) / PID_dT )
+      #define unscalePID_d(d) ( (d) * PID_dT )
 
-#if ENABLED(THERMAL_PROTECTION_BED) && WATCH_BED_TEMP_PERIOD > 0
-  void start_watching_bed();
-#endif
+    #endif
 
-FORCE_INLINE void setTargetHotend(const float& celsius, uint8_t extruder) {
-  target_temperature[extruder] = celsius;
-  #if ENABLED(THERMAL_PROTECTION_HOTENDS) && WATCH_TEMP_PERIOD > 0
-    start_watching_heater(extruder);
-  #endif
-}
-FORCE_INLINE void setTargetBed(const float& celsius) {
-  target_temperature_bed = celsius;
-  #if ENABLED(THERMAL_PROTECTION_BED) && WATCH_BED_TEMP_PERIOD > 0
-    start_watching_bed();
-  #endif
-}
+    #if ENABLED(PIDTEMPBED)
+      float bedKp = DEFAULT_bedKp,
+            bedKi = ((DEFAULT_bedKi) * PID_dT),
+            bedKd = ((DEFAULT_bedKd) / PID_dT);
+    #endif
 
-FORCE_INLINE bool isHeatingHotend(uint8_t extruder) { return target_temperature[extruder] > current_temperature[extruder]; }
-FORCE_INLINE bool isHeatingBed() { return target_temperature_bed > current_temperature_bed; }
+    #if ENABLED(BABYSTEPPING)
+      volatile int babystepsTodo[3] = { 0 };
+    #endif
 
-FORCE_INLINE bool isCoolingHotend(uint8_t extruder) { return target_temperature[extruder] < current_temperature[extruder]; }
-FORCE_INLINE bool isCoolingBed() { return target_temperature_bed < current_temperature_bed; }
+    #if ENABLED(THERMAL_PROTECTION_HOTENDS) && WATCH_TEMP_PERIOD > 0
+      int watch_target_temp[EXTRUDERS] = { 0 };
+      millis_t watch_heater_next_ms[EXTRUDERS] = { 0 };
+    #endif
 
-#define HOTEND_ROUTINES(NR) \
-  FORCE_INLINE float degHotend##NR() { return degHotend(NR); } \
-  FORCE_INLINE float degTargetHotend##NR() { return degTargetHotend(NR); } \
-  FORCE_INLINE void setTargetHotend##NR(const float c) { setTargetHotend(c, NR); } \
-  FORCE_INLINE bool isHeatingHotend##NR() { return isHeatingHotend(NR); } \
-  FORCE_INLINE bool isCoolingHotend##NR() { return isCoolingHotend(NR); }
-HOTEND_ROUTINES(0);
-#if EXTRUDERS > 1
-  HOTEND_ROUTINES(1);
-#else
-  #define setTargetHotend1(c) do{}while(0)
-#endif
-#if EXTRUDERS > 2
-  HOTEND_ROUTINES(2);
-#else
-  #define setTargetHotend2(c) do{}while(0)
-#endif
-#if EXTRUDERS > 3
-  HOTEND_ROUTINES(3);
-#else
-  #define setTargetHotend3(c) do{}while(0)
-#endif
+    #if ENABLED(THERMAL_PROTECTION_HOTENDS) && WATCH_BED_TEMP_PERIOD > 0
+      int watch_target_bed_temp = 0;
+      millis_t watch_bed_next_ms = 0;
+    #endif
 
-int getHeaterPower(int heater);
-void disable_all_heaters();
-void updatePID();
+    #if ENABLED(PREVENT_DANGEROUS_EXTRUDE)
+      float extrude_min_temp = EXTRUDE_MINTEMP;
+      FORCE_INLINE bool tooColdToExtrude(uint8_t e) { return degHotend(e) < extrude_min_temp; }
+    #else
+      FORCE_INLINE bool tooColdToExtrude(uint8_t e) { UNUSED(e); return false; }
+    #endif
 
-#if ENABLED(PIDTEMP)
-  void PID_autotune(float temp, int extruder, int ncycles, bool set_result=false);
-#endif
+  private:
 
-void setExtruderAutoFanState(int pin, bool state);
-void checkExtruderAutoFans();
+    #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
+      int redundant_temperature_raw = 0;
+      float redundant_temperature = 0.0;
+    #endif
 
-FORCE_INLINE void autotempShutdown() {
-  #if ENABLED(AUTOTEMP)
-    if (planner.autotemp_enabled) {
-      planner.autotemp_enabled = false;
-      if (degTargetHotend(active_extruder) > planner.autotemp_min)
-        setTargetHotend(0, active_extruder);
+    volatile bool temp_meas_ready = false;
+
+    #if ENABLED(PIDTEMP)
+      float temp_iState[EXTRUDERS] = { 0 };
+      float temp_dState[EXTRUDERS] = { 0 };
+      float pTerm[EXTRUDERS];
+      float iTerm[EXTRUDERS];
+      float dTerm[EXTRUDERS];
+
+      #if ENABLED(PID_ADD_EXTRUSION_RATE)
+        float cTerm[EXTRUDERS];
+        long last_position[EXTRUDERS];
+        long lpq[LPQ_MAX_LEN];
+        int lpq_ptr = 0;
+      #endif
+
+      float pid_error[EXTRUDERS];
+      float temp_iState_min[EXTRUDERS];
+      float temp_iState_max[EXTRUDERS];
+      bool pid_reset[EXTRUDERS];
+    #endif
+
+    #if ENABLED(PIDTEMPBED)
+      float temp_iState_bed = { 0 };
+      float temp_dState_bed = { 0 };
+      float pTerm_bed;
+      float iTerm_bed;
+      float dTerm_bed;
+      float pid_error_bed;
+      float temp_iState_min_bed;
+      float temp_iState_max_bed;
+    #else
+      millis_t next_bed_check_ms;
+    #endif
+
+    unsigned long raw_temp_value[4] = { 0 };
+    unsigned long raw_temp_bed_value = 0;
+
+    // Init min and max temp with extreme values to prevent false errors during startup
+    int minttemp_raw[EXTRUDERS] = ARRAY_BY_EXTRUDERS(HEATER_0_RAW_LO_TEMP , HEATER_1_RAW_LO_TEMP , HEATER_2_RAW_LO_TEMP, HEATER_3_RAW_LO_TEMP);
+    int maxttemp_raw[EXTRUDERS] = ARRAY_BY_EXTRUDERS(HEATER_0_RAW_HI_TEMP , HEATER_1_RAW_HI_TEMP , HEATER_2_RAW_HI_TEMP, HEATER_3_RAW_HI_TEMP);
+    int minttemp[EXTRUDERS] = { 0 };
+    int maxttemp[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(16383);
+
+    #ifdef BED_MINTEMP
+      int bed_minttemp_raw = HEATER_BED_RAW_LO_TEMP;
+    #endif
+
+    #ifdef BED_MAXTEMP
+      int bed_maxttemp_raw = HEATER_BED_RAW_HI_TEMP;
+    #endif
+
+    #if ENABLED(FILAMENT_WIDTH_SENSOR)
+      int meas_shift_index;  // Index of a delayed sample in buffer
+    #endif
+
+    #if HAS_AUTO_FAN
+      millis_t next_auto_fan_check_ms;
+    #endif
+
+    unsigned char soft_pwm[EXTRUDERS];
+
+    #if ENABLED(FAN_SOFT_PWM)
+      unsigned char soft_pwm_fan[FAN_COUNT];
+    #endif
+
+    #if ENABLED(FILAMENT_WIDTH_SENSOR)
+      int current_raw_filwidth = 0;  //Holds measured filament diameter - one extruder only
+    #endif
+
+  public:
+
+    /**
+     * Static (class) methods
+     */
+    static float analog2temp(int raw, uint8_t e);
+    static float analog2tempBed(int raw);
+
+    /**
+     * Instance Methods
+     */
+
+    Temperature();
+
+    void init();
+
+    /**
+     * Called from the Temperature ISR
+     */
+    void isr();
+
+    /**
+     * Call periodically to manage heaters
+     */
+    void manage_heater();
+
+    #if ENABLED(FILAMENT_WIDTH_SENSOR)
+      float analog2widthFil(); // Convert raw Filament Width to millimeters
+      int widthFil_to_size_ratio(); // Convert raw Filament Width to an extrusion ratio
+    #endif
+
+
+    //high level conversion routines, for use outside of temperature.cpp
+    //inline so that there is no performance decrease.
+    //deg=degreeCelsius
+
+    FORCE_INLINE float degHotend(uint8_t extruder) { return current_temperature[extruder]; }
+    FORCE_INLINE float degBed() { return current_temperature_bed; }
+
+    #if ENABLED(SHOW_TEMP_ADC_VALUES)
+    FORCE_INLINE float rawHotendTemp(uint8_t extruder) { return current_temperature_raw[extruder]; }
+    FORCE_INLINE float rawBedTemp() { return current_temperature_bed_raw; }
+    #endif
+
+    FORCE_INLINE float degTargetHotend(uint8_t extruder) { return target_temperature[extruder]; }
+    FORCE_INLINE float degTargetBed() { return target_temperature_bed; }
+
+    #if ENABLED(THERMAL_PROTECTION_HOTENDS) && WATCH_TEMP_PERIOD > 0
+      void start_watching_heater(int e = 0);
+    #endif
+
+    #if ENABLED(THERMAL_PROTECTION_BED) && WATCH_BED_TEMP_PERIOD > 0
+      void start_watching_bed();
+    #endif
+
+    FORCE_INLINE void setTargetHotend(const float& celsius, uint8_t extruder) {
+      target_temperature[extruder] = celsius;
+      #if ENABLED(THERMAL_PROTECTION_HOTENDS) && WATCH_TEMP_PERIOD > 0
+        start_watching_heater(extruder);
+      #endif
     }
-  #endif
-}
+
+    FORCE_INLINE void setTargetBed(const float& celsius) {
+      target_temperature_bed = celsius;
+      #if ENABLED(THERMAL_PROTECTION_BED) && WATCH_BED_TEMP_PERIOD > 0
+        start_watching_bed();
+      #endif
+    }
+
+    FORCE_INLINE bool isHeatingHotend(uint8_t extruder) { return target_temperature[extruder] > current_temperature[extruder]; }
+    FORCE_INLINE bool isHeatingBed() { return target_temperature_bed > current_temperature_bed; }
+
+    FORCE_INLINE bool isCoolingHotend(uint8_t extruder) { return target_temperature[extruder] < current_temperature[extruder]; }
+    FORCE_INLINE bool isCoolingBed() { return target_temperature_bed < current_temperature_bed; }
+
+    /**
+     * The software PWM power for a heater
+     */
+    int getHeaterPower(int heater);
+
+    /**
+     * Switch off all heaters, set all target temperatures to 0
+     */
+    void disable_all_heaters();
+
+    /**
+     * Perform auto-tuning for hotend or bed in response to M303
+     */
+    #if HAS_PID_HEATING
+      void PID_autotune(float temp, int extruder, int ncycles, bool set_result=false);
+    #endif
+
+    /**
+     * Update the temp manager when PID values change
+     */
+    void updatePID();
+
+    FORCE_INLINE void autotempShutdown() {
+      #if ENABLED(AUTOTEMP)
+        if (planner.autotemp_enabled) {
+          planner.autotemp_enabled = false;
+          if (degTargetHotend(active_extruder) > planner.autotemp_min)
+            setTargetHotend(0, active_extruder);
+        }
+      #endif
+    }
+
+  private:
+
+    void set_current_temp_raw();
+
+    void updateTemperaturesFromRawValues();
+
+    #if ENABLED(HEATER_0_USES_MAX6675)
+      int read_max6675();
+    #endif
+
+    void setExtruderAutoFanState(int pin, bool state);
+    void checkExtruderAutoFans();
+
+    float get_pid_output(int e);
+
+    #if ENABLED(PIDTEMPBED)
+      float get_pid_output_bed();
+    #endif
+
+    void _temp_error(int e, const char* serial_msg, const char* lcd_msg);
+    void min_temp_error(uint8_t e);
+    void max_temp_error(uint8_t e);
+
+    #if ENABLED(THERMAL_PROTECTION_HOTENDS) || HAS_THERMALLY_PROTECTED_BED
+
+      typedef enum TRState { TRReset, TRInactive, TRFirstHeating, TRStable, TRRunaway } TRstate;
+
+      void thermal_runaway_protection(TRState* state, millis_t* timer, float temperature, float target_temperature, int heater_id, int period_seconds, int hysteresis_degc);
+
+      #if ENABLED(THERMAL_PROTECTION_HOTENDS)
+        TRState thermal_runaway_state_machine[EXTRUDERS] = { TRReset };
+        millis_t thermal_runaway_timer[EXTRUDERS] = { 0 };
+      #endif
+
+      #if HAS_THERMALLY_PROTECTED_BED
+        TRState thermal_runaway_bed_state_machine = TRReset;
+        millis_t thermal_runaway_bed_timer;
+      #endif
+
+    #endif // THERMAL_PROTECTION
+
+};
+
+extern Temperature thermalManager;
 
 #endif // TEMPERATURE_H
diff --git a/Marlin/ultralcd.cpp b/Marlin/ultralcd.cpp
index 93991abbcc..7b247fa177 100644
--- a/Marlin/ultralcd.cpp
+++ b/Marlin/ultralcd.cpp
@@ -479,7 +479,7 @@ inline void line_to_current(AxisEnum axis) {
     stepper.quick_stop();
     card.sdprinting = false;
     card.closefile();
-    autotempShutdown();
+    thermalManager.autotempShutdown();
     cancel_heatup = true;
     lcd_setstatus(MSG_PRINT_ABORTED, true);
   }
@@ -605,16 +605,16 @@ void lcd_set_home_offsets() {
  */
 #if ENABLED(THERMAL_PROTECTION_HOTENDS) && WATCH_TEMP_PERIOD > 0
   #if TEMP_SENSOR_0 != 0
-    void watch_temp_callback_E0() { start_watching_heater(0); }
+    void watch_temp_callback_E0() { thermalManager.start_watching_heater(0); }
   #endif
   #if EXTRUDERS > 1 && TEMP_SENSOR_1 != 0
-    void watch_temp_callback_E1() { start_watching_heater(1); }
+    void watch_temp_callback_E1() { thermalManager.start_watching_heater(1); }
   #endif // EXTRUDERS > 1
   #if EXTRUDERS > 2 && TEMP_SENSOR_2 != 0
-    void watch_temp_callback_E2() { start_watching_heater(2); }
+    void watch_temp_callback_E2() { thermalManager.start_watching_heater(2); }
   #endif // EXTRUDERS > 2
   #if EXTRUDERS > 3 && TEMP_SENSOR_3 != 0
-    void watch_temp_callback_E3() { start_watching_heater(3); }
+    void watch_temp_callback_E3() { thermalManager.start_watching_heater(3); }
   #endif // EXTRUDERS > 3
 #else
   #if TEMP_SENSOR_0 != 0
@@ -633,7 +633,7 @@ void lcd_set_home_offsets() {
 
 #if ENABLED(THERMAL_PROTECTION_BED) && WATCH_BED_TEMP_PERIOD > 0
   #if TEMP_SENSOR_BED != 0
-    void watch_temp_callback_bed() { start_watching_bed(); }
+    void watch_temp_callback_bed() { thermalManager.start_watching_bed(); }
   #endif
 #else
   #if TEMP_SENSOR_BED != 0
@@ -670,22 +670,22 @@ static void lcd_tune_menu() {
   //
   #if EXTRUDERS == 1
     #if TEMP_SENSOR_0 != 0
-      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE, &target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
     #endif
   #else //EXTRUDERS > 1
     #if TEMP_SENSOR_0 != 0
-      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N1, &target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N1, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
     #endif
     #if TEMP_SENSOR_1 != 0
-      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N2, &target_temperature[1], 0, HEATER_1_MAXTEMP - 15, watch_temp_callback_E1);
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N2, &thermalManager.target_temperature[1], 0, HEATER_1_MAXTEMP - 15, watch_temp_callback_E1);
     #endif
     #if EXTRUDERS > 2
       #if TEMP_SENSOR_2 != 0
-        MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N3, &target_temperature[2], 0, HEATER_2_MAXTEMP - 15, watch_temp_callback_E2);
+        MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N3, &thermalManager.target_temperature[2], 0, HEATER_2_MAXTEMP - 15, watch_temp_callback_E2);
       #endif
       #if EXTRUDERS > 3
         #if TEMP_SENSOR_3 != 0
-          MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N4, &target_temperature[3], 0, HEATER_3_MAXTEMP - 15, watch_temp_callback_E3);
+          MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N4, &thermalManager.target_temperature[3], 0, HEATER_3_MAXTEMP - 15, watch_temp_callback_E3);
         #endif
       #endif // EXTRUDERS > 3
     #endif // EXTRUDERS > 2
@@ -695,7 +695,7 @@ static void lcd_tune_menu() {
   // Bed:
   //
   #if TEMP_SENSOR_BED != 0
-    MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_BED, &target_temperature_bed, 0, BED_MAXTEMP - 15, watch_temp_callback_bed);
+    MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_BED, &thermalManager.target_temperature_bed, 0, BED_MAXTEMP - 15, watch_temp_callback_bed);
   #endif
 
   //
@@ -768,9 +768,9 @@ static void lcd_tune_menu() {
  *
  */
 void _lcd_preheat(int endnum, const float temph, const float tempb, const int fan) {
-  if (temph > 0) setTargetHotend(temph, endnum);
+  if (temph > 0) thermalManager.setTargetHotend(temph, endnum);
   #if TEMP_SENSOR_BED != 0
-    setTargetBed(tempb);
+    thermalManager.setTargetBed(tempb);
   #else
     UNUSED(tempb);
   #endif
@@ -805,19 +805,27 @@ void _lcd_preheat(int endnum, const float temph, const float tempb, const int fa
 
   void lcd_preheat_pla0123() {
     #if EXTRUDERS > 1
-      setTargetHotend0(plaPreheatHotendTemp);
-      setTargetHotend1(plaPreheatHotendTemp);
-      setTargetHotend2(plaPreheatHotendTemp);
+      thermalManager.setTargetHotend(plaPreheatHotendTemp, 1);
+      #if EXTRUDERS > 2
+        thermalManager.setTargetHotend(plaPreheatHotendTemp, 2);
+        #if EXTRUDERS > 3
+          thermalManager.setTargetHotend(plaPreheatHotendTemp, 3);
+        #endif
+      #endif
     #endif
-    _lcd_preheat(EXTRUDERS - 1, plaPreheatHotendTemp, plaPreheatHPBTemp, plaPreheatFanSpeed);
+    lcd_preheat_pla0();
   }
   void lcd_preheat_abs0123() {
     #if EXTRUDERS > 1
-      setTargetHotend0(absPreheatHotendTemp);
-      setTargetHotend1(absPreheatHotendTemp);
-      setTargetHotend2(absPreheatHotendTemp);
+      thermalManager.setTargetHotend(absPreheatHotendTemp, 1);
+      #if EXTRUDERS > 2
+        thermalManager.setTargetHotend(absPreheatHotendTemp, 2);
+        #if EXTRUDERS > 3
+          thermalManager.setTargetHotend(absPreheatHotendTemp, 3);
+        #endif
+      #endif
     #endif
-    _lcd_preheat(EXTRUDERS - 1, absPreheatHotendTemp, absPreheatHPBTemp, absPreheatFanSpeed);
+    lcd_preheat_abs0();
   }
 
 #endif // EXTRUDERS > 1
@@ -879,7 +887,7 @@ void lcd_cooldown() {
   #if FAN_COUNT > 0
     for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0;
   #endif
-  disable_all_heaters();
+  thermalManager.disable_all_heaters();
   lcd_return_to_status();
 }
 
@@ -1414,14 +1422,14 @@ static void lcd_control_menu() {
       UNUSED(e);
     #endif
     PID_PARAM(Ki, e) = scalePID_i(raw_Ki);
-    updatePID();
+    thermalManager.updatePID();
   }
   void copy_and_scalePID_d(int e) {
     #if DISABLED(PID_PARAMS_PER_EXTRUDER)
       UNUSED(e);
     #endif
     PID_PARAM(Kd, e) = scalePID_d(raw_Kd);
-    updatePID();
+    thermalManager.updatePID();
   }
   #define _PIDTEMP_BASE_FUNCTIONS(eindex) \
     void copy_and_scalePID_i_E ## eindex() { copy_and_scalePID_i(eindex); } \
@@ -1469,22 +1477,22 @@ static void lcd_control_temperature_menu() {
   //
   #if EXTRUDERS == 1
     #if TEMP_SENSOR_0 != 0
-      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE, &target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
     #endif
   #else //EXTRUDERS > 1
     #if TEMP_SENSOR_0 != 0
-      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N1, &target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N1, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0);
     #endif
     #if TEMP_SENSOR_1 != 0
-      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N2, &target_temperature[1], 0, HEATER_1_MAXTEMP - 15, watch_temp_callback_E1);
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N2, &thermalManager.target_temperature[1], 0, HEATER_1_MAXTEMP - 15, watch_temp_callback_E1);
     #endif
     #if EXTRUDERS > 2
       #if TEMP_SENSOR_2 != 0
-        MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N3, &target_temperature[2], 0, HEATER_2_MAXTEMP - 15, watch_temp_callback_E2);
+        MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N3, &thermalManager.target_temperature[2], 0, HEATER_2_MAXTEMP - 15, watch_temp_callback_E2);
       #endif
       #if EXTRUDERS > 3
         #if TEMP_SENSOR_3 != 0
-          MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N4, &target_temperature[3], 0, HEATER_3_MAXTEMP - 15, watch_temp_callback_E3);
+          MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N4, &thermalManager.target_temperature[3], 0, HEATER_3_MAXTEMP - 15, watch_temp_callback_E3);
         #endif
       #endif // EXTRUDERS > 3
     #endif // EXTRUDERS > 2
@@ -1494,7 +1502,7 @@ static void lcd_control_temperature_menu() {
   // Bed:
   //
   #if TEMP_SENSOR_BED != 0
-    MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_BED, &target_temperature_bed, 0, BED_MAXTEMP - 15);
+    MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_BED, &thermalManager.target_temperature_bed, 0, BED_MAXTEMP - 15);
   #endif
 
   //
diff --git a/Marlin/ultralcd_implementation_hitachi_HD44780.h b/Marlin/ultralcd_implementation_hitachi_HD44780.h
index b6d60c2ffc..05afe59a3d 100644
--- a/Marlin/ultralcd_implementation_hitachi_HD44780.h
+++ b/Marlin/ultralcd_implementation_hitachi_HD44780.h
@@ -633,7 +633,7 @@ static void lcd_implementation_status_screen() {
     //
     // Hotend 0 Temperature
     //
-    LCD_TEMP_ONLY(degHotend(0), degTargetHotend(0));
+    LCD_TEMP_ONLY(thermalManager.degHotend(0), thermalManager.degTargetHotend(0));
 
     //
     // Hotend 1 or Bed Temperature
@@ -643,10 +643,10 @@ static void lcd_implementation_status_screen() {
       lcd.setCursor(8, 0);
       #if EXTRUDERS > 1
         lcd.print(LCD_STR_THERMOMETER[0]);
-        LCD_TEMP_ONLY(degHotend(1), degTargetHotend(1));
+        LCD_TEMP_ONLY(thermalManager.degHotend(1), thermalManager.degTargetHotend(1));
       #else
         lcd.print(LCD_STR_BEDTEMP[0]);
-        LCD_TEMP_ONLY(degBed(), degTargetBed());
+        LCD_TEMP_ONLY(thermalManager.degBed(), thermalManager.degTargetBed());
       #endif
 
     #endif // EXTRUDERS > 1 || TEMP_SENSOR_BED != 0
@@ -656,7 +656,7 @@ static void lcd_implementation_status_screen() {
     //
     // Hotend 0 Temperature
     //
-    LCD_TEMP(degHotend(0), degTargetHotend(0), LCD_STR_THERMOMETER[0]);
+    LCD_TEMP(thermalManager.degHotend(0), thermalManager.degTargetHotend(0), LCD_STR_THERMOMETER[0]);
 
     //
     // Hotend 1 or Bed Temperature
@@ -664,9 +664,9 @@ static void lcd_implementation_status_screen() {
     #if EXTRUDERS > 1 || TEMP_SENSOR_BED != 0
       lcd.setCursor(10, 0);
       #if EXTRUDERS > 1
-        LCD_TEMP(degHotend(1), degTargetHotend(1), LCD_STR_THERMOMETER[0]);
+        LCD_TEMP(thermalManager.degHotend(1), thermalManager.degTargetHotend(1), LCD_STR_THERMOMETER[0]);
       #else
-        LCD_TEMP(degBed(), degTargetBed(), LCD_STR_BEDTEMP[0]);
+        LCD_TEMP(thermalManager.degBed(), thermalManager.degTargetBed(), LCD_STR_BEDTEMP[0]);
       #endif
 
     #endif  // EXTRUDERS > 1 || TEMP_SENSOR_BED != 0
@@ -702,7 +702,7 @@ static void lcd_implementation_status_screen() {
         // If we both have a 2nd extruder and a heated bed,
         // show the heated bed temp on the left,
         // since the first line is filled with extruder temps
-        LCD_TEMP(degBed(), degTargetBed(), LCD_STR_BEDTEMP[0]);
+        LCD_TEMP(thermalManager.degBed(), thermalManager.degTargetBed(), LCD_STR_BEDTEMP[0]);
 
       #else
         // Before homing the axis letters are blinking 'X' <-> '?'.
@@ -925,9 +925,9 @@ void lcd_implementation_drawedit(const char* pstr, const char* value=NULL) {
     static uint8_t ledsprev = 0;
     uint8_t leds = 0;
 
-    if (target_temperature_bed > 0) leds |= LED_A;
+    if (thermalManager.degTargetBed() > 0) leds |= LED_A;
 
-    if (target_temperature[0] > 0) leds |= LED_B;
+    if (thermalManager.degTargetHotend(0) > 0) leds |= LED_B;
 
     #if FAN_COUNT > 0
       if (0
@@ -944,7 +944,7 @@ void lcd_implementation_drawedit(const char* pstr, const char* value=NULL) {
     #endif // FAN_COUNT > 0
 
     #if EXTRUDERS > 1
-      if (target_temperature[1] > 0) leds |= LED_C;
+      if (thermalManager.degTargetHotend(1) > 0) leds |= LED_C;
     #endif
 
     if (leds != ledsprev) {