diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 2724006dd19..c1bd973b4a1 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -789,7 +789,7 @@ void idle(bool no_stepper_sleep/*=false*/) {
   manage_inactivity(no_stepper_sleep);
 
   // Manage Heaters (and Watchdog)
-  thermalManager.manage_heater();
+  thermalManager.task();
 
   // Max7219 heartbeat, animation, etc
   TERN_(MAX7219_DEBUG, max7219.idle_tasks());
diff --git a/Marlin/src/core/language.h b/Marlin/src/core/language.h
index 16d7b1bf663..157bd691851 100644
--- a/Marlin/src/core/language.h
+++ b/Marlin/src/core/language.h
@@ -227,10 +227,6 @@
 #define STR_PID_DEBUG                       " PID_DEBUG "
 #define STR_PID_DEBUG_INPUT                 ": Input "
 #define STR_PID_DEBUG_OUTPUT                " Output "
-#define STR_PID_DEBUG_PTERM                 " pTerm "
-#define STR_PID_DEBUG_ITERM                 " iTerm "
-#define STR_PID_DEBUG_DTERM                 " dTerm "
-#define STR_PID_DEBUG_CTERM                 " cTerm "
 #define STR_INVALID_EXTRUDER_NUM            " - Invalid extruder number !"
 #define STR_MPC_AUTOTUNE                    "MPC Autotune"
 #define STR_MPC_AUTOTUNE_START              " start for " STR_E
diff --git a/Marlin/src/core/utility.cpp b/Marlin/src/core/utility.cpp
index 84e4c1f6969..9cdf8dec7bc 100644
--- a/Marlin/src/core/utility.cpp
+++ b/Marlin/src/core/utility.cpp
@@ -29,10 +29,10 @@ void safe_delay(millis_t ms) {
   while (ms > 50) {
     ms -= 50;
     delay(50);
-    thermalManager.manage_heater();
+    thermalManager.task();
   }
   delay(ms);
-  thermalManager.manage_heater(); // This keeps us safe if too many small safe_delay() calls are made
+  thermalManager.task(); // This keeps us safe if too many small safe_delay() calls are made
 }
 
 // A delay to provide brittle hosts time to receive bytes
diff --git a/Marlin/src/gcode/motion/G2_G3.cpp b/Marlin/src/gcode/motion/G2_G3.cpp
index d9458f61eb3..14ef9ac2a6f 100644
--- a/Marlin/src/gcode/motion/G2_G3.cpp
+++ b/Marlin/src/gcode/motion/G2_G3.cpp
@@ -290,7 +290,7 @@ void plan_arc(
 
     for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times
 
-      thermalManager.manage_heater();
+      thermalManager.task();
       const millis_t ms = millis();
       if (ELAPSED(ms, next_idle_ms)) {
         next_idle_ms = ms + 200UL;
diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp
index 454a009b859..a390a46d8e3 100644
--- a/Marlin/src/gcode/queue.cpp
+++ b/Marlin/src/gcode/queue.cpp
@@ -384,7 +384,7 @@ inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind
   buff[ind] = '\0';                   // Of course, I'm a Terminator.
   const bool is_empty = (ind == 0);   // An empty line?
   if (is_empty)
-    thermalManager.manage_heater();   // Keep sensors satisfied
+    thermalManager.task();            // Keep sensors satisfied
   else
     ind = 0;                          // Start a new line
   return is_empty;                    // Inform the caller
diff --git a/Marlin/src/lcd/e3v2/jyersui/dwin.cpp b/Marlin/src/lcd/e3v2/jyersui/dwin.cpp
index 5f1507feb33..285013d7504 100644
--- a/Marlin/src/lcd/e3v2/jyersui/dwin.cpp
+++ b/Marlin/src/lcd/e3v2/jyersui/dwin.cpp
@@ -1202,7 +1202,7 @@ void CrealityDWINClass::Menu_Item_Handler(uint8_t menu, uint8_t item, bool draw/
                 if (thermalManager.temp_hotend[0].target < thermalManager.extrude_min_temp)
                   Popup_Handler(ETemp);
                 else {
-                  if (thermalManager.temp_hotend[0].celsius < thermalManager.temp_hotend[0].target - 2) {
+                  if (thermalManager.temp_hotend[0].is_below_target(-2)) {
                     Popup_Handler(Heating);
                     thermalManager.wait_for_hotend(0);
                   }
@@ -1345,7 +1345,7 @@ void CrealityDWINClass::Menu_Item_Handler(uint8_t menu, uint8_t item, bool draw/
                 Popup_Handler(ETemp);
               }
               else {
-                if (thermalManager.temp_hotend[0].celsius < thermalManager.temp_hotend[0].target - 2) {
+                if (thermalManager.temp_hotend[0].is_below_target(-2)) {
                   Popup_Handler(Heating);
                   thermalManager.wait_for_hotend(0);
                   Redraw_Menu();
@@ -1732,7 +1732,7 @@ void CrealityDWINClass::Menu_Item_Handler(uint8_t menu, uint8_t item, bool draw/
               if (thermalManager.temp_hotend[0].target < thermalManager.extrude_min_temp)
                 Popup_Handler(ETemp);
               else {
-                if (thermalManager.temp_hotend[0].celsius < thermalManager.temp_hotend[0].target - 2) {
+                if (thermalManager.temp_hotend[0].is_below_target(-2)) {
                   Popup_Handler(Heating);
                   thermalManager.wait_for_hotend(0);
                 }
@@ -1751,7 +1751,7 @@ void CrealityDWINClass::Menu_Item_Handler(uint8_t menu, uint8_t item, bool draw/
                 Popup_Handler(ETemp);
               }
               else {
-                if (thermalManager.temp_hotend[0].celsius < thermalManager.temp_hotend[0].target - 2) {
+                if (thermalManager.temp_hotend[0].is_below_target(-2)) {
                   Popup_Handler(Heating);
                   thermalManager.wait_for_hotend(0);
                 }
@@ -1769,7 +1769,7 @@ void CrealityDWINClass::Menu_Item_Handler(uint8_t menu, uint8_t item, bool draw/
               if (thermalManager.temp_hotend[0].target < thermalManager.extrude_min_temp)
                 Popup_Handler(ETemp);
               else {
-                if (thermalManager.temp_hotend[0].celsius < thermalManager.temp_hotend[0].target - 2) {
+                if (thermalManager.temp_hotend[0].is_below_target(-2)) {
                   Popup_Handler(Heating);
                   thermalManager.wait_for_hotend(0);
                 }
@@ -4404,7 +4404,7 @@ void CrealityDWINClass::Popup_Control() {
             if (thermalManager.temp_hotend[0].target < thermalManager.extrude_min_temp)
               Popup_Handler(ETemp);
             else {
-              if (thermalManager.temp_hotend[0].celsius < thermalManager.temp_hotend[0].target - 2) {
+              if (thermalManager.temp_hotend[0].is_below_target(-2)) {
                 Popup_Handler(Heating);
                 thermalManager.wait_for_hotend(0);
               }
diff --git a/Marlin/src/lcd/extui/ui_api.cpp b/Marlin/src/lcd/extui/ui_api.cpp
index 68b0a81fe51..57822279c53 100644
--- a/Marlin/src/lcd/extui/ui_api.cpp
+++ b/Marlin/src/lcd/extui/ui_api.cpp
@@ -169,7 +169,7 @@ namespace ExtUI {
   }
 
   void yield() {
-    if (!flags.printer_killed) thermalManager.manage_heater();
+    if (!flags.printer_killed) thermalManager.task();
   }
 
   void enableHeater(const extruder_t extruder) {
diff --git a/Marlin/src/libs/buzzer.cpp b/Marlin/src/libs/buzzer.cpp
index a3c838ebc0a..1e2f23c5fde 100644
--- a/Marlin/src/libs/buzzer.cpp
+++ b/Marlin/src/libs/buzzer.cpp
@@ -48,7 +48,7 @@ void Buzzer::tone(const uint16_t duration, const uint16_t frequency/*=0*/) {
   if (!ui.sound_on) return;
   while (buffer.isFull()) {
     tick();
-    thermalManager.manage_heater();
+    thermalManager.task();
   }
   tone_t tone = { duration, frequency };
   buffer.enqueue(tone);
diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp
index 36afdb2e485..b3b607e677a 100644
--- a/Marlin/src/module/motion.cpp
+++ b/Marlin/src/module/motion.cpp
@@ -966,7 +966,7 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
     next_idle_ms = ms + 200UL;
     return idle();
   }
-  thermalManager.manage_heater();  // Returns immediately on most calls
+  thermalManager.task();  // Returns immediately on most calls
 }
 
 #if IS_KINEMATIC
diff --git a/Marlin/src/module/planner_bezier.cpp b/Marlin/src/module/planner_bezier.cpp
index fa7e16a387e..93b118f3305 100644
--- a/Marlin/src/module/planner_bezier.cpp
+++ b/Marlin/src/module/planner_bezier.cpp
@@ -123,7 +123,7 @@ void cubic_b_spline(
 
   for (float t = 0; t < 1;) {
 
-    thermalManager.manage_heater();
+    thermalManager.task();
     millis_t now = millis();
     if (ELAPSED(now, next_idle_ms)) {
       next_idle_ms = now + 200UL;
diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp
index 17ab08d4c20..0de922d1a82 100644
--- a/Marlin/src/module/temperature.cpp
+++ b/Marlin/src/module/temperature.cpp
@@ -1318,104 +1318,101 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
 }
 
 #if ANY(PID_DEBUG, PID_BED_DEBUG, PID_CHAMBER_DEBUG)
-  bool Temperature::pid_debug_flag; // = 0
+  #define HAS_PID_DEBUG 1
+  bool Temperature::pid_debug_flag; // = false
 #endif
 
+#if HAS_PID_HEATING
+
+  template<typename TT, int MIN_POW, int MAX_POW>
+  class PIDRunner {
+  public:
+    TT &tempinfo;
+    __typeof__(TT::pid) work_pid{0};
+    float temp_iState = 0, temp_dState = 0;
+    bool pid_reset = true;
+
+    PIDRunner(TT &t) : tempinfo(t) { }
+
+    float get_pid_output() {
+
+      #if ENABLED(PID_OPENLOOP)
+
+        return constrain(tempinfo.target, 0, MAX_POW);
+
+      #else // !PID_OPENLOOP
+
+        const float pid_error = tempinfo.target - tempinfo.celsius;
+        if (!tempinfo.target || pid_error < -(PID_FUNCTIONAL_RANGE)) {
+          pid_reset = true;
+          return 0;
+        }
+        else if (pid_error > PID_FUNCTIONAL_RANGE) {
+          pid_reset = true;
+          return MAX_POW;
+        }
+
+        if (pid_reset) {
+          pid_reset = false;
+          temp_iState = 0.0;
+          work_pid.Kd = 0.0;
+        }
+
+        const float max_power_over_i_gain = float(MAX_POW) / tempinfo.pid.Ki - float(MIN_POW);
+        temp_iState = constrain(temp_iState + pid_error, 0, max_power_over_i_gain);
+
+        work_pid.Kp = tempinfo.pid.Kp * pid_error;
+        work_pid.Ki = tempinfo.pid.Ki * temp_iState;
+        work_pid.Kd = work_pid.Kd + PID_K2 * (tempinfo.pid.Kd * (temp_dState - tempinfo.celsius) - work_pid.Kd);
+
+        temp_dState = tempinfo.celsius;
+
+        return constrain(work_pid.Kp + work_pid.Ki + work_pid.Kd + float(MIN_POW), 0, MAX_POW);
+
+      #endif // !PID_OPENLOOP
+    }
+
+    FORCE_INLINE void debug(const_celsius_float_t c, const_float_t pid_out, FSTR_P const name=nullptr, const int8_t index=-1) {
+      if (TERN0(HAS_PID_DEBUG, thermalManager.pid_debug_flag)) {
+        SERIAL_ECHO_START();
+        if (name) SERIAL_ECHOLNF(name);
+        if (index >= 0) SERIAL_ECHO(index);
+        SERIAL_ECHOLNPGM(
+          STR_PID_DEBUG_INPUT, c,
+          STR_PID_DEBUG_OUTPUT, pid_out
+          #if DISABLED(PID_OPENLOOP)
+            , "pTerm", work_pid.Kp, "iTerm", work_pid.Ki, "dTerm", work_pid.Kd
+          #endif
+        );
+      }
+    }
+  };
+
+#endif // HAS_PID_HEATING
+
 #if HAS_HOTEND
 
   float Temperature::get_pid_output_hotend(const uint8_t E_NAME) {
     const uint8_t ee = HOTEND_INDEX;
+
     #if ENABLED(PIDTEMP)
-      #if DISABLED(PID_OPENLOOP)
-        static hotend_pid_t work_pid[HOTENDS];
-        static float temp_iState[HOTENDS] = { 0 },
-                     temp_dState[HOTENDS] = { 0 };
-        static Flags<HOTENDS> pid_reset;
-        const float pid_error = temp_hotend[ee].target - temp_hotend[ee].celsius;
 
-        float pid_output;
+      typedef PIDRunner<hotend_info_t, 0, PID_MAX> PIDRunnerHotend;
 
-        if (temp_hotend[ee].target == 0
-          || pid_error < -(PID_FUNCTIONAL_RANGE)
-          || TERN0(HEATER_IDLE_HANDLER, heater_idle[ee].timed_out)
-        ) {
-          pid_output = 0;
-          pid_reset.set(ee);
-        }
-        else if (pid_error > PID_FUNCTIONAL_RANGE) {
-          pid_output = PID_MAX;
-          pid_reset.set(ee);
-        }
-        else {
-          if (pid_reset[ee]) {
-            temp_iState[ee] = 0.0;
-            work_pid[ee].Kd = 0.0;
-            pid_reset.clear(ee);
-          }
+      static PIDRunnerHotend hotend_pid[HOTENDS] = {
+        #define _HOTENDPID(E) temp_hotend[E],
+        REPEAT(HOTENDS, _HOTENDPID)
+      };
 
-          work_pid[ee].Kd = work_pid[ee].Kd + PID_K2 * (PID_PARAM(Kd, ee) * (temp_dState[ee] - temp_hotend[ee].celsius) - work_pid[ee].Kd);
-          const float max_power_over_i_gain = float(PID_MAX) / PID_PARAM(Ki, ee) - float(MIN_POWER);
-          temp_iState[ee] = constrain(temp_iState[ee] + pid_error, 0, max_power_over_i_gain);
-          work_pid[ee].Kp = PID_PARAM(Kp, ee) * pid_error;
-          work_pid[ee].Ki = PID_PARAM(Ki, ee) * temp_iState[ee];
-
-          pid_output = work_pid[ee].Kp + work_pid[ee].Ki + work_pid[ee].Kd + float(MIN_POWER);
-
-          #if ENABLED(PID_EXTRUSION_SCALING)
-            #if HOTENDS == 1
-              constexpr bool this_hotend = true;
-            #else
-              const bool this_hotend = (ee == active_extruder);
-            #endif
-            work_pid[ee].Kc = 0;
-            if (this_hotend) {
-              const long e_position = stepper.position(E_AXIS);
-              if (e_position > pes_e_position) {
-                lpq[lpq_ptr] = e_position - pes_e_position;
-                pes_e_position = e_position;
-              }
-              else
-                lpq[lpq_ptr] = 0;
-
-              if (++lpq_ptr >= lpq_len) lpq_ptr = 0;
-              work_pid[ee].Kc = (lpq[lpq_ptr] * planner.mm_per_step[E_AXIS]) * PID_PARAM(Kc, ee);
-              pid_output += work_pid[ee].Kc;
-            }
-          #endif // PID_EXTRUSION_SCALING
-          #if ENABLED(PID_FAN_SCALING)
-            if (fan_speed[active_extruder] > PID_FAN_SCALING_MIN_SPEED) {
-              work_pid[ee].Kf = PID_PARAM(Kf, ee) + (PID_FAN_SCALING_LIN_FACTOR) * fan_speed[active_extruder];
-              pid_output += work_pid[ee].Kf;
-            }
-            //pid_output -= work_pid[ee].Ki;
-            //pid_output += work_pid[ee].Ki * work_pid[ee].Kf
-          #endif // PID_FAN_SCALING
-          LIMIT(pid_output, 0, PID_MAX);
-        }
-        temp_dState[ee] = temp_hotend[ee].celsius;
-
-      #else // PID_OPENLOOP
-
-        const float pid_output = constrain(temp_hotend[ee].target, 0, PID_MAX);
-
-      #endif // PID_OPENLOOP
+      const float pid_output = hotend_pid[ee].get_pid_output();
 
       #if ENABLED(PID_DEBUG)
-        if (ee == active_extruder && pid_debug_flag) {
-          SERIAL_ECHO_MSG(STR_PID_DEBUG, ee, STR_PID_DEBUG_INPUT, temp_hotend[ee].celsius, STR_PID_DEBUG_OUTPUT, pid_output
-            #if DISABLED(PID_OPENLOOP)
-              , STR_PID_DEBUG_PTERM, work_pid[ee].Kp
-              , STR_PID_DEBUG_ITERM, work_pid[ee].Ki
-              , STR_PID_DEBUG_DTERM, work_pid[ee].Kd
-              #if ENABLED(PID_EXTRUSION_SCALING)
-                , STR_PID_DEBUG_CTERM, work_pid[ee].Kc
-              #endif
-            #endif
-          );
-        }
+        if (ee == active_extruder)
+          hotend_pid[ee].debug(temp_hotend[ee].celsius, pid_output, F("E"), ee);
       #endif
 
     #elif ENABLED(MPCTEMP)
+
       MPCHeaterInfo &hotend = temp_hotend[ee];
       MPC_t &constants = hotend.constants;
 
@@ -1497,7 +1494,7 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
     #else // No PID or MPC enabled
 
       const bool is_idling = TERN0(HEATER_IDLE_HANDLER, heater_idle[ee].timed_out);
-      const float pid_output = (!is_idling && temp_hotend[ee].celsius < temp_hotend[ee].target) ? BANG_MAX : 0;
+      const float pid_output = (!is_idling && temp_hotend[ee].is_below_target()) ? BANG_MAX : 0;
 
     #endif
 
@@ -1509,61 +1506,9 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
 #if ENABLED(PIDTEMPBED)
 
   float Temperature::get_pid_output_bed() {
-
-    #if DISABLED(PID_OPENLOOP)
-
-      static PID_t work_pid{0};
-      static float temp_iState = 0, temp_dState = 0;
-      static bool pid_reset = true;
-      float pid_output = 0;
-      const float max_power_over_i_gain = float(MAX_BED_POWER) / temp_bed.pid.Ki - float(MIN_BED_POWER),
-                  pid_error = temp_bed.target - temp_bed.celsius;
-
-      if (!temp_bed.target || pid_error < -(PID_FUNCTIONAL_RANGE)) {
-        pid_output = 0;
-        pid_reset = true;
-      }
-      else if (pid_error > PID_FUNCTIONAL_RANGE) {
-        pid_output = MAX_BED_POWER;
-        pid_reset = true;
-      }
-      else {
-        if (pid_reset) {
-          temp_iState = 0.0;
-          work_pid.Kd = 0.0;
-          pid_reset = false;
-        }
-
-        temp_iState = constrain(temp_iState + pid_error, 0, max_power_over_i_gain);
-
-        work_pid.Kp = temp_bed.pid.Kp * pid_error;
-        work_pid.Ki = temp_bed.pid.Ki * temp_iState;
-        work_pid.Kd = work_pid.Kd + PID_K2 * (temp_bed.pid.Kd * (temp_dState - temp_bed.celsius) - work_pid.Kd);
-
-        temp_dState = temp_bed.celsius;
-
-        pid_output = constrain(work_pid.Kp + work_pid.Ki + work_pid.Kd + float(MIN_BED_POWER), 0, MAX_BED_POWER);
-      }
-
-    #else // PID_OPENLOOP
-
-      const float pid_output = constrain(temp_bed.target, 0, MAX_BED_POWER);
-
-    #endif // PID_OPENLOOP
-
-    #if ENABLED(PID_BED_DEBUG)
-      if (pid_debug_flag) {
-        SERIAL_ECHO_MSG(
-          " PID_BED_DEBUG : Input ", temp_bed.celsius, " Output ", pid_output
-          #if DISABLED(PID_OPENLOOP)
-            , STR_PID_DEBUG_PTERM, work_pid.Kp
-            , STR_PID_DEBUG_ITERM, work_pid.Ki
-            , STR_PID_DEBUG_DTERM, work_pid.Kd
-          #endif
-        );
-      }
-    #endif
-
+    static PIDRunner<bed_info_t, MIN_BED_POWER, MAX_BED_POWER> bed_pid(temp_bed);
+    const float pid_output = bed_pid.get_pid_output();
+    TERN_(PID_BED_DEBUG, bed_pid.debug(temp_bed.celsius, pid_output, F("(Bed)")));
     return pid_output;
   }
 
@@ -1572,114 +1517,17 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
 #if ENABLED(PIDTEMPCHAMBER)
 
   float Temperature::get_pid_output_chamber() {
-
-    #if DISABLED(PID_OPENLOOP)
-
-      static PID_t work_pid{0};
-      static float temp_iState = 0, temp_dState = 0;
-      static bool pid_reset = true;
-      float pid_output = 0;
-      const float max_power_over_i_gain = float(MAX_CHAMBER_POWER) / temp_chamber.pid.Ki - float(MIN_CHAMBER_POWER),
-                  pid_error = temp_chamber.target - temp_chamber.celsius;
-
-      if (!temp_chamber.target || pid_error < -(PID_FUNCTIONAL_RANGE)) {
-        pid_output = 0;
-        pid_reset = true;
-      }
-      else if (pid_error > PID_FUNCTIONAL_RANGE) {
-        pid_output = MAX_CHAMBER_POWER;
-        pid_reset = true;
-      }
-      else {
-        if (pid_reset) {
-          temp_iState = 0.0;
-          work_pid.Kd = 0.0;
-          pid_reset = false;
-        }
-
-        temp_iState = constrain(temp_iState + pid_error, 0, max_power_over_i_gain);
-
-        work_pid.Kp = temp_chamber.pid.Kp * pid_error;
-        work_pid.Ki = temp_chamber.pid.Ki * temp_iState;
-        work_pid.Kd = work_pid.Kd + PID_K2 * (temp_chamber.pid.Kd * (temp_dState - temp_chamber.celsius) - work_pid.Kd);
-
-        temp_dState = temp_chamber.celsius;
-
-        pid_output = constrain(work_pid.Kp + work_pid.Ki + work_pid.Kd + float(MIN_CHAMBER_POWER), 0, MAX_CHAMBER_POWER);
-      }
-
-    #else // PID_OPENLOOP
-
-      const float pid_output = constrain(temp_chamber.target, 0, MAX_CHAMBER_POWER);
-
-    #endif // PID_OPENLOOP
-
-    #if ENABLED(PID_CHAMBER_DEBUG)
-    {
-      SERIAL_ECHO_MSG(
-        " PID_CHAMBER_DEBUG : Input ", temp_chamber.celsius, " Output ", pid_output
-        #if DISABLED(PID_OPENLOOP)
-          , STR_PID_DEBUG_PTERM, work_pid.Kp
-          , STR_PID_DEBUG_ITERM, work_pid.Ki
-          , STR_PID_DEBUG_DTERM, work_pid.Kd
-        #endif
-      );
-    }
-    #endif
-
+    static PIDRunner<chamber_info_t, MIN_CHAMBER_POWER, MAX_CHAMBER_POWER> chamber_pid(temp_chamber);
+    const float pid_output = chamber_pid.get_pid_output();
+    TERN_(PID_CHAMBER_DEBUG, chamber_pid.debug(temp_chamber.celsius, pid_output, F("(Chamber)")));
     return pid_output;
   }
 
 #endif // PIDTEMPCHAMBER
 
-/**
- * Manage heating activities for extruder hot-ends and a heated bed
- *  - Acquire updated temperature readings
- *    - Also resets the watchdog timer
- *  - Invoke thermal runaway protection
- *  - Manage extruder auto-fan
- *  - Apply filament width to the extrusion rate (may move)
- *  - Update the heated bed PID output value
- */
-void Temperature::manage_heater() {
-  if (marlin_state == MF_INITIALIZING) return hal.watchdog_refresh(); // If Marlin isn't started, at least reset the watchdog!
-
-  static bool no_reentry = false;  // Prevent recursion
-  if (no_reentry) return;
-  REMEMBER(mh, no_reentry, true);
-
-  #if ENABLED(EMERGENCY_PARSER)
-    if (emergency_parser.killed_by_M112) kill(FPSTR(M112_KILL_STR), nullptr, true);
-
-    if (emergency_parser.quickstop_by_M410) {
-      emergency_parser.quickstop_by_M410 = false; // quickstop_stepper may call idle so clear this now!
-      quickstop_stepper();
-    }
-  #endif
-
-  if (!updateTemperaturesIfReady()) return; // Will also reset the watchdog if temperatures are ready
-
-  #if DISABLED(IGNORE_THERMOCOUPLE_ERRORS)
-    #if TEMP_SENSOR_0_IS_MAX_TC
-      if (degHotend(0) > _MIN(HEATER_0_MAXTEMP, TEMP_SENSOR_0_MAX_TC_TMAX - 1.0)) max_temp_error(H_E0);
-      if (degHotend(0) < _MAX(HEATER_0_MINTEMP, TEMP_SENSOR_0_MAX_TC_TMIN + .01)) min_temp_error(H_E0);
-    #endif
-    #if TEMP_SENSOR_1_IS_MAX_TC
-      if (degHotend(1) > _MIN(HEATER_1_MAXTEMP, TEMP_SENSOR_1_MAX_TC_TMAX - 1.0)) max_temp_error(H_E1);
-      if (degHotend(1) < _MAX(HEATER_1_MINTEMP, TEMP_SENSOR_1_MAX_TC_TMIN + .01)) min_temp_error(H_E1);
-    #endif
-    #if TEMP_SENSOR_REDUNDANT_IS_MAX_TC
-      if (degRedundant() > TEMP_SENSOR_REDUNDANT_MAX_TC_TMAX - 1.0) max_temp_error(H_REDUNDANT);
-      if (degRedundant() < TEMP_SENSOR_REDUNDANT_MAX_TC_TMIN + .01) min_temp_error(H_REDUNDANT);
-    #endif
-  #else
-    #warning "Safety Alert! Disable IGNORE_THERMOCOUPLE_ERRORS for the final build!"
-  #endif
-
-  millis_t ms = millis();
-
-  #if HAS_HOTEND
+#if HAS_HOTEND
 
+  void Temperature::manage_hotends(const millis_t &ms) {
     HOTEND_LOOP() {
       #if ENABLED(THERMAL_PROTECTION_HOTENDS)
         if (degHotend(e) > temp_range[e].maxtemp) max_temp_error((heater_id_t)e);
@@ -1707,25 +1555,13 @@ void Temperature::manage_heater() {
       #endif
 
     } // HOTEND_LOOP
+  }
 
-  #endif // HAS_HOTEND
+#endif // HAS_HOTEND
 
-  #if HAS_TEMP_REDUNDANT
-    // Make sure measured temperatures are close together
-    if (ABS(degRedundantTarget() - degRedundant()) > TEMP_SENSOR_REDUNDANT_MAX_DIFF)
-      _temp_error((heater_id_t)HEATER_ID(TEMP_SENSOR_REDUNDANT_TARGET), F(STR_REDUNDANCY), GET_TEXT_F(MSG_ERR_REDUNDANT_TEMP));
-  #endif
+#if HAS_HEATED_BED
 
-  // Manage extruder auto fans and/or read fan tachometers
-  TERN_(HAS_FAN_LOGIC, manage_extruder_fans(ms));
-
-  /**
-   * Dynamically set the volumetric multiplier based
-   * on the delayed Filament Width measurement.
-   */
-  TERN_(FILAMENT_WIDTH_SENSOR, filwidth.update_volumetric());
-
-  #if HAS_HEATED_BED
+  void Temperature::manage_heated_bed(const millis_t &ms) {
 
     #if ENABLED(THERMAL_PROTECTION_BED)
       if (degBed() > BED_MAXTEMP) max_temp_error(H_BED);
@@ -1770,9 +1606,7 @@ void Temperature::manage_heater() {
       #if HEATER_IDLE_HANDLER
         if (heater_idle[IDLE_INDEX_BED].timed_out) {
           temp_bed.soft_pwm_amount = 0;
-          #if DISABLED(PIDTEMPBED)
-            WRITE_HEATER_BED(LOW);
-          #endif
+          if (DISABLED(PIDTEMPBED)) WRITE_HEATER_BED(LOW);
         }
         else
       #endif
@@ -1785,10 +1619,10 @@ void Temperature::manage_heater() {
             #if ENABLED(BED_LIMIT_SWITCHING)
               if (temp_bed.celsius >= temp_bed.target + BED_HYSTERESIS)
                 temp_bed.soft_pwm_amount = 0;
-              else if (temp_bed.celsius <= temp_bed.target - (BED_HYSTERESIS))
+              else if (temp_bed.is_below_target(-(BED_HYSTERESIS) + 1))
                 temp_bed.soft_pwm_amount = MAX_BED_POWER >> 1;
             #else // !PIDTEMPBED && !BED_LIMIT_SWITCHING
-              temp_bed.soft_pwm_amount = temp_bed.celsius < temp_bed.target ? MAX_BED_POWER >> 1 : 0;
+              temp_bed.soft_pwm_amount = temp_bed.is_below_target() ? MAX_BED_POWER >> 1 : 0;
             #endif
           }
           else {
@@ -1799,10 +1633,13 @@ void Temperature::manage_heater() {
       }
 
     } while (false);
+  }
 
-  #endif // HAS_HEATED_BED
+#endif // HAS_HEATED_BED
 
-  #if HAS_HEATED_CHAMBER
+#if HAS_HEATED_CHAMBER
+
+  void Temperature::manage_heated_chamber(const millis_t &ms) {
 
     #ifndef CHAMBER_CHECK_INTERVAL
       #define CHAMBER_CHECK_INTERVAL 1000UL
@@ -1897,17 +1734,17 @@ void Temperature::manage_heater() {
           if (flag_chamber_excess_heat) {
             temp_chamber.soft_pwm_amount = 0;
             #if ENABLED(CHAMBER_VENT)
-              if (!flag_chamber_off) servo[CHAMBER_VENT_SERVO_NR].move(temp_chamber.celsius <= temp_chamber.target ? 0 : 90);
+              if (!flag_chamber_off) servo[CHAMBER_VENT_SERVO_NR].move(temp_chamber.is_below_target() ? 0 : 90);
             #endif
           }
           else {
             #if ENABLED(CHAMBER_LIMIT_SWITCHING)
               if (temp_chamber.celsius >= temp_chamber.target + TEMP_CHAMBER_HYSTERESIS)
                 temp_chamber.soft_pwm_amount = 0;
-              else if (temp_chamber.celsius <= temp_chamber.target - (TEMP_CHAMBER_HYSTERESIS))
+              else if (temp_chamber.is_below_target(-(TEMP_CHAMBER_HYSTERESIS) + 1))
                 temp_chamber.soft_pwm_amount = (MAX_CHAMBER_POWER) >> 1;
             #else
-              temp_chamber.soft_pwm_amount = temp_chamber.celsius < temp_chamber.target ? (MAX_CHAMBER_POWER) >> 1 : 0;
+              temp_chamber.soft_pwm_amount = temp_chamber.is_below_target() ? (MAX_CHAMBER_POWER) >> 1 : 0;
             #endif
             #if ENABLED(CHAMBER_VENT)
               if (!flag_chamber_off) servo[CHAMBER_VENT_SERVO_NR].move(0);
@@ -1923,10 +1760,13 @@ void Temperature::manage_heater() {
        tr_state_machine[RUNAWAY_IND_CHAMBER].run(temp_chamber.celsius, temp_chamber.target, H_CHAMBER, THERMAL_PROTECTION_CHAMBER_PERIOD, THERMAL_PROTECTION_CHAMBER_HYSTERESIS);
      #endif
    #endif
+  }
 
-  #endif // HAS_HEATED_CHAMBER
+#endif // HAS_HEATED_CHAMBER
 
-  #if HAS_COOLER
+#if HAS_COOLER
+
+  void Temperature::manage_cooler(const millis_t &ms) {
 
     #ifndef COOLER_CHECK_INTERVAL
       #define COOLER_CHECK_INTERVAL 2000UL
@@ -1984,8 +1824,82 @@ void Temperature::manage_heater() {
     #if ENABLED(THERMAL_PROTECTION_COOLER)
       tr_state_machine[RUNAWAY_IND_COOLER].run(temp_cooler.celsius, temp_cooler.target, H_COOLER, THERMAL_PROTECTION_COOLER_PERIOD, THERMAL_PROTECTION_COOLER_HYSTERESIS);
     #endif
+  }
 
-  #endif // HAS_COOLER
+#endif // HAS_COOLER
+
+/**
+ * Manage heating activities for extruder hot-ends and a heated bed
+ *  - Acquire updated temperature readings
+ *    - Also resets the watchdog timer
+ *  - Invoke thermal runaway protection
+ *  - Manage extruder auto-fan
+ *  - Apply filament width to the extrusion rate (may move)
+ *  - Update the heated bed PID output value
+ */
+void Temperature::task() {
+  if (marlin_state == MF_INITIALIZING) return hal.watchdog_refresh(); // If Marlin isn't started, at least reset the watchdog!
+
+  static bool no_reentry = false;  // Prevent recursion
+  if (no_reentry) return;
+  REMEMBER(mh, no_reentry, true);
+
+  #if ENABLED(EMERGENCY_PARSER)
+    if (emergency_parser.killed_by_M112) kill(FPSTR(M112_KILL_STR), nullptr, true);
+
+    if (emergency_parser.quickstop_by_M410) {
+      emergency_parser.quickstop_by_M410 = false; // quickstop_stepper may call idle so clear this now!
+      quickstop_stepper();
+    }
+  #endif
+
+  if (!updateTemperaturesIfReady()) return; // Will also reset the watchdog if temperatures are ready
+
+  #if DISABLED(IGNORE_THERMOCOUPLE_ERRORS)
+    #if TEMP_SENSOR_0_IS_MAX_TC
+      if (degHotend(0) > _MIN(HEATER_0_MAXTEMP, TEMP_SENSOR_0_MAX_TC_TMAX - 1.0)) max_temp_error(H_E0);
+      if (degHotend(0) < _MAX(HEATER_0_MINTEMP, TEMP_SENSOR_0_MAX_TC_TMIN + .01)) min_temp_error(H_E0);
+    #endif
+    #if TEMP_SENSOR_1_IS_MAX_TC
+      if (degHotend(1) > _MIN(HEATER_1_MAXTEMP, TEMP_SENSOR_1_MAX_TC_TMAX - 1.0)) max_temp_error(H_E1);
+      if (degHotend(1) < _MAX(HEATER_1_MINTEMP, TEMP_SENSOR_1_MAX_TC_TMIN + .01)) min_temp_error(H_E1);
+    #endif
+    #if TEMP_SENSOR_REDUNDANT_IS_MAX_TC
+      if (degRedundant() > TEMP_SENSOR_REDUNDANT_MAX_TC_TMAX - 1.0) max_temp_error(H_REDUNDANT);
+      if (degRedundant() < TEMP_SENSOR_REDUNDANT_MAX_TC_TMIN + .01) min_temp_error(H_REDUNDANT);
+    #endif
+  #else
+    #warning "Safety Alert! Disable IGNORE_THERMOCOUPLE_ERRORS for the final build!"
+  #endif
+
+  const millis_t ms = millis();
+
+  // Handle Hotend Temp Errors, Heating Watch, etc.
+  TERN_(HAS_HOTEND, manage_hotends(ms));
+
+  #if HAS_TEMP_REDUNDANT
+    // Make sure measured temperatures are close together
+    if (ABS(degRedundantTarget() - degRedundant()) > TEMP_SENSOR_REDUNDANT_MAX_DIFF)
+      _temp_error((heater_id_t)HEATER_ID(TEMP_SENSOR_REDUNDANT_TARGET), F(STR_REDUNDANCY), GET_TEXT_F(MSG_ERR_REDUNDANT_TEMP));
+  #endif
+
+  // Manage extruder auto fans and/or read fan tachometers
+  TERN_(HAS_FAN_LOGIC, manage_extruder_fans(ms));
+
+  /**
+   * Dynamically set the volumetric multiplier based
+   * on the delayed Filament Width measurement.
+   */
+  TERN_(FILAMENT_WIDTH_SENSOR, filwidth.update_volumetric());
+
+  // Handle Bed Temp Errors, Heating Watch, etc.
+  TERN_(HAS_HEATED_BED, manage_heated_bed(ms));
+
+  // Handle Heated Chamber Temp Errors, Heating Watch, etc.
+  TERN_(HAS_HEATED_CHAMBER, manage_heated_chamber(ms));
+
+  // Handle Cooler Temp Errors, Cooling Watch, etc.
+  TERN_(HAS_COOLER, manage_cooler(ms));
 
   #if ENABLED(LASER_COOLANT_FLOW_METER)
     cooler.flowmeter_task(ms);
@@ -2479,7 +2393,7 @@ void Temperature::updateTemperaturesFromRawValues() {
 /**
  * Initialize the temperature manager
  *
- * The manager is implemented by periodic calls to manage_heater()
+ * The manager is implemented by periodic calls to task()
  *
  *  - Init (and disable) SPI thermocouples like MAX6675 and MAX31865
  *  - Disable RUMBA JTAG to accommodate a thermocouple extension
@@ -3111,7 +3025,7 @@ void Temperature::disable_all_heaters() {
     static millis_t next_max_tc_ms[MAX_TC_COUNT] = { 0 };
 
     // Return last-read value between readings
-    millis_t ms = millis();
+    const millis_t ms = millis();
     if (PENDING(ms, next_max_tc_ms[hindex]))
       return THERMO_TEMP(hindex);
 
@@ -3419,16 +3333,18 @@ void Temperature::isr() {
         _PWM_MOD(COOLER, soft_pwm_cooler, temp_cooler);
       #endif
 
-      #if BOTH(USE_CONTROLLER_FAN, FAN_SOFT_PWM)
-        WRITE(CONTROLLER_FAN_PIN, soft_pwm_controller.add(pwm_mask, soft_pwm_controller_speed));
-      #endif
-
       #if ENABLED(FAN_SOFT_PWM)
+
+        #if ENABLED(USE_CONTROLLER_FAN)
+          WRITE(CONTROLLER_FAN_PIN, soft_pwm_controller.add(pwm_mask, soft_pwm_controller_speed));
+        #endif
+
         #define _FAN_PWM(N) do{                                     \
           uint8_t &spcf = soft_pwm_count_fan[N];                    \
           spcf = (spcf & pwm_mask) + (soft_pwm_amount_fan[N] >> 1); \
           WRITE_FAN(N, spcf > pwm_mask ? HIGH : LOW);               \
         }while(0)
+
         #if HAS_FAN0
           _FAN_PWM(0);
         #endif
diff --git a/Marlin/src/module/temperature.h b/Marlin/src/module/temperature.h
index c11c9d76def..80d15ce39e4 100644
--- a/Marlin/src/module/temperature.h
+++ b/Marlin/src/module/temperature.h
@@ -232,6 +232,7 @@ public:
 typedef struct HeaterInfo : public TempInfo {
   celsius_t target;
   uint8_t soft_pwm_amount;
+  bool is_below_target(const celsius_t offs=0) const { return (celsius < (target + offs)); }
 } heater_info_t;
 
 // A heater with PID stabilization
@@ -715,9 +716,9 @@ class Temperature {
     static void readings_ready();
 
     /**
-     * Call periodically to manage heaters
+     * Call periodically to manage heaters and keep the watchdog fed
      */
-    static void manage_heater() __O2; // __O2 added to work around a compiler error
+    static void task();
 
     /**
      * Preheating hotends
@@ -807,6 +808,8 @@ class Temperature {
         #endif
       }
 
+      static void manage_hotends(const millis_t &ms);
+
     #endif // HAS_HOTEND
 
     #if HAS_HEATED_BED
@@ -819,6 +822,9 @@ class Temperature {
       static celsius_t degTargetBed()  { return temp_bed.target; }
       static bool isHeatingBed()       { return temp_bed.target > temp_bed.celsius; }
       static bool isCoolingBed()       { return temp_bed.target < temp_bed.celsius; }
+      static bool degBedNear(const celsius_t temp) {
+        return ABS(wholeDegBed() - temp) < (TEMP_BED_HYSTERESIS);
+      }
 
       // Start watching the Bed to make sure it's really heating up
       static void start_watching_bed() { TERN_(WATCH_BED, watch_bed.restart(degBed(), degTargetBed())); }
@@ -835,9 +841,7 @@ class Temperature {
 
       static void wait_for_bed_heating();
 
-      static bool degBedNear(const celsius_t temp) {
-        return ABS(wholeDegBed() - temp) < (TEMP_BED_HYSTERESIS);
-      }
+      static void manage_heated_bed(const millis_t &ms);
 
     #endif // HAS_HEATED_BED
 
@@ -863,6 +867,7 @@ class Temperature {
         static bool isHeatingChamber()       { return temp_chamber.target > temp_chamber.celsius; }
         static bool isCoolingChamber()       { return temp_chamber.target < temp_chamber.celsius; }
         static bool wait_for_chamber(const bool no_wait_for_cooling=true);
+        static void manage_heated_chamber(const millis_t &ms);
       #endif
     #endif
 
@@ -886,6 +891,7 @@ class Temperature {
         static bool isLaserHeating()       { return temp_cooler.target > temp_cooler.celsius; }
         static bool isLaserCooling()       { return temp_cooler.target < temp_cooler.celsius; }
         static bool wait_for_cooler(const bool no_wait_for_cooling=true);
+        static void manage_cooler(const millis_t &ms);
       #endif
     #endif
 
diff --git a/Marlin/src/sd/usb_flashdrive/Sd2Card_FlashDrive.cpp b/Marlin/src/sd/usb_flashdrive/Sd2Card_FlashDrive.cpp
index a681af4efa6..7d698247e56 100644
--- a/Marlin/src/sd/usb_flashdrive/Sd2Card_FlashDrive.cpp
+++ b/Marlin/src/sd/usb_flashdrive/Sd2Card_FlashDrive.cpp
@@ -62,7 +62,7 @@
   #define USB_HOST_MANUAL_POLL // Optimization to shut off IRQ automatically
 
   // Workarounds to keep Marlin's watchdog timer from barking...
-  void marlin_yield() { thermalManager.manage_heater(); }
+  void marlin_yield() { thermalManager.task(); }
   #define SYSTEM_OR_SPECIAL_YIELD(...) marlin_yield();
   #define delay(x) safe_delay(x)