From 2ef71c6ebaa1f1b496eb369879fe22c69cf9adb4 Mon Sep 17 00:00:00 2001
From: Scott Lahteine <thinkyhead@users.noreply.github.com>
Date: Mon, 12 Jun 2023 16:38:14 -0500
Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20SERIAL=5FECHO?=
 =?UTF-8?q?=20(#25928)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Since this increases AVR code size, try to optimize further.
---
 Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp      |  12 +-
 Marlin/src/HAL/shared/Delay.cpp               |   8 +-
 Marlin/src/MarlinCore.cpp                     |   2 +-
 Marlin/src/core/debug_out.h                   |  24 ---
 Marlin/src/core/debug_section.h               |   7 +-
 Marlin/src/core/serial.cpp                    |  45 +++-
 Marlin/src/core/serial.h                      | 192 +++++-------------
 Marlin/src/core/serial_base.h                 |   5 +-
 Marlin/src/core/types.h                       |  30 +++
 Marlin/src/core/utility.cpp                   |  10 +-
 Marlin/src/core/utility.h                     |  19 ++
 Marlin/src/feature/bedlevel/bedlevel.cpp      |   4 +-
 .../bedlevel/mbl/mesh_bed_leveling.cpp        |   3 +-
 Marlin/src/feature/bedlevel/ubl/ubl.cpp       |   7 +-
 Marlin/src/feature/bedlevel/ubl/ubl.h         |   6 +-
 Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp   | 139 +++++--------
 Marlin/src/feature/encoder_i2c.cpp            |   5 +-
 Marlin/src/feature/host_actions.cpp           |   5 +-
 Marlin/src/feature/max7219.cpp                |   4 +-
 Marlin/src/feature/meatpack.cpp               |   2 +-
 Marlin/src/feature/mixing.cpp                 |  13 +-
 Marlin/src/feature/mixing.h                   |  28 ++-
 Marlin/src/feature/mmu/mmu2.cpp               |   2 +-
 Marlin/src/feature/pause.cpp                  |   2 +-
 Marlin/src/feature/powerloss.cpp              |   9 +-
 Marlin/src/feature/probe_temp_comp.cpp        |  11 +-
 Marlin/src/feature/runout.h                   |   6 +-
 Marlin/src/feature/stepper_driver_safety.cpp  |   3 +-
 Marlin/src/feature/tmc_util.cpp               |  10 +-
 Marlin/src/feature/twibus.cpp                 |   3 +-
 Marlin/src/gcode/bedlevel/G35.cpp             |  17 +-
 Marlin/src/gcode/bedlevel/M420.cpp            |   2 +-
 Marlin/src/gcode/bedlevel/abl/G29.cpp         |  12 +-
 Marlin/src/gcode/calibrate/G28.cpp            |   2 +-
 Marlin/src/gcode/calibrate/G33.cpp            |  11 +-
 Marlin/src/gcode/calibrate/G34_M422.cpp       |   5 +-
 Marlin/src/gcode/calibrate/G76_M871.cpp       |   2 +-
 Marlin/src/gcode/calibrate/M100.cpp           |   4 +-
 Marlin/src/gcode/calibrate/M48.cpp            |  19 +-
 Marlin/src/gcode/calibrate/M852.cpp           |   6 +-
 Marlin/src/gcode/config/M218.cpp              |   6 +-
 Marlin/src/gcode/config/M302.cpp              |   4 +-
 Marlin/src/gcode/config/M43.cpp               |   4 +-
 Marlin/src/gcode/config/M92.cpp               |   2 +-
 Marlin/src/gcode/control/M80_M81.cpp          |   2 +-
 Marlin/src/gcode/feature/advance/M900.cpp     |   8 +-
 Marlin/src/gcode/feature/ft_motion/M493.cpp   |  22 +-
 .../src/gcode/feature/network/M552-M554.cpp   |   3 +-
 Marlin/src/gcode/feature/pause/G61.cpp        |   3 +-
 Marlin/src/gcode/feature/powerloss/M1000.cpp  |   3 +-
 Marlin/src/gcode/feature/powerloss/M413.cpp   |   4 +-
 Marlin/src/gcode/feature/trinamic/M569.cpp    |   8 +-
 Marlin/src/gcode/gcode.cpp                    |   5 +-
 Marlin/src/gcode/geometry/G17-G19.cpp         |   2 +-
 Marlin/src/gcode/probe/G30.cpp                |   2 +-
 Marlin/src/gcode/queue.cpp                    |   2 +-
 Marlin/src/gcode/stats/M31.cpp                |   2 +-
 Marlin/src/gcode/temp/M306.cpp                |  16 +-
 Marlin/src/gcode/units/M149.cpp               |   3 +-
 Marlin/src/inc/Conditionals_post.h            |   3 +
 Marlin/src/lcd/e3v2/jyersui/dwin.cpp          |  18 +-
 Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp  |  18 +-
 .../lcd/extui/anycubic_chiron/chiron_tft.cpp  |  25 +--
 .../src/lcd/extui/anycubic_vyper/dgus_tft.cpp |   6 +-
 .../lcd/extui/dgus/DGUSScreenHandlerBase.h    |   2 +-
 Marlin/src/lcd/extui/nextion/nextion_tft.cpp  |  26 +--
 Marlin/src/lcd/menu/menu_configuration.cpp    |   9 +-
 Marlin/src/lcd/utf8.cpp                       |   9 +-
 Marlin/src/libs/vector_3.cpp                  |  11 +-
 Marlin/src/module/endstops.cpp                |   5 +-
 Marlin/src/module/probe.cpp                   |   2 +-
 Marlin/src/module/settings.cpp                |  10 +-
 Marlin/src/module/temperature.cpp             |  41 ++--
 Marlin/src/pins/pinsDebug.h                   |   8 +-
 Marlin/src/sd/cardreader.cpp                  |   7 +-
 docs/Serial.md                                |   3 -
 76 files changed, 398 insertions(+), 602 deletions(-)

diff --git a/Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp b/Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp
index 1991d79719..5ebd437c1e 100644
--- a/Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp
+++ b/Marlin/src/HAL/LPC1768/eeprom_sdcard.cpp
@@ -91,15 +91,9 @@ bool PersistentStore::access_finish() {
 static void debug_rw(const bool write, int &pos, const uint8_t *value, const size_t size, const FRESULT s, const size_t total=0) {
   #if ENABLED(DEBUG_SD_EEPROM_EMULATION)
     FSTR_P const rw_str = write ? F("write") : F("read");
-    SERIAL_CHAR(' ');
-    SERIAL_ECHOF(rw_str);
-    SERIAL_ECHOLNPGM("_data(", pos, ",", *value, ",", size, ", ...)");
-    if (total) {
-      SERIAL_ECHOPGM(" f_");
-      SERIAL_ECHOF(rw_str);
-      SERIAL_ECHOPGM("()=", s, "\n size=", size, "\n bytes_");
-      SERIAL_ECHOLNF(write ? F("written=") : F("read="), total);
-    }
+    SERIAL_ECHOLN(AS_CHAR(' '), rw_str, F("_data("), pos, AS_CHAR(','), *value, AS_CHAR(','), size, F(", ...)"));
+    if (total)
+      SERIAL_ECHOLN(F(" f_"), rw_str, F("()="), s, F("\n size="), size, F("\n bytes_"), write ? F("written=") : F("read="), total);
     else
       SERIAL_ECHOLNPGM(" f_lseek()=", s);
   #endif
diff --git a/Marlin/src/HAL/shared/Delay.cpp b/Marlin/src/HAL/shared/Delay.cpp
index c64376d25d..31c3f45765 100644
--- a/Marlin/src/HAL/shared/Delay.cpp
+++ b/Marlin/src/HAL/shared/Delay.cpp
@@ -109,13 +109,7 @@
   #if ENABLED(MARLIN_DEV_MODE)
     void dump_delay_accuracy_check() {
       auto report_call_time = [](FSTR_P const name, FSTR_P const unit, const uint32_t cycles, const uint32_t total, const bool do_flush=true) {
-        SERIAL_ECHOPGM("Calling ");
-        SERIAL_ECHOF(name);
-        SERIAL_ECHOLNPGM(" for ", cycles);
-        SERIAL_ECHOF(unit);
-        SERIAL_ECHOLNPGM(" took: ", total);
-        SERIAL_CHAR(' ');
-        SERIAL_ECHOF(unit);
+        SERIAL_ECHOLN(F("Calling "), name, F(" for "), cycles, AS_CHAR(' '), unit, F(" took: "), total, AS_CHAR(' '), unit);
         if (do_flush) SERIAL_FLUSHTX();
       };
 
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index ac5a6b7ff9..09d398eb87 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -882,7 +882,7 @@ void kill(FSTR_P const lcd_error/*=nullptr*/, FSTR_P const lcd_component/*=nullp
   TERN_(HAS_CUTTER, cutter.kill()); // Full cutter shutdown including ISR control
 
   // Echo the LCD message to serial for extra context
-  if (lcd_error) { SERIAL_ECHO_START(); SERIAL_ECHOLNF(lcd_error); }
+  if (lcd_error) { SERIAL_ECHO_START(); SERIAL_ECHOLN(lcd_error); }
 
   #if HAS_DISPLAY
     ui.kill_screen(lcd_error ?: GET_TEXT_F(MSG_KILLED), lcd_component ?: FPSTR(NUL_STR));
diff --git a/Marlin/src/core/debug_out.h b/Marlin/src/core/debug_out.h
index eb1c91e507..92ee233e03 100644
--- a/Marlin/src/core/debug_out.h
+++ b/Marlin/src/core/debug_out.h
@@ -31,19 +31,11 @@
 #undef DEBUG_ERROR_START
 #undef DEBUG_CHAR
 #undef DEBUG_ECHO
-#undef DEBUG_DECIMAL
-#undef DEBUG_ECHO_F
 #undef DEBUG_ECHOLN
 #undef DEBUG_ECHOPGM
 #undef DEBUG_ECHOLNPGM
-#undef DEBUG_ECHOF
-#undef DEBUG_ECHOLNF
 #undef DEBUG_ECHOPGM_P
 #undef DEBUG_ECHOLNPGM_P
-#undef DEBUG_ECHOPAIR_F
-#undef DEBUG_ECHOPAIR_F_P
-#undef DEBUG_ECHOLNPAIR_F
-#undef DEBUG_ECHOLNPAIR_F_P
 #undef DEBUG_ECHO_MSG
 #undef DEBUG_ERROR_MSG
 #undef DEBUG_EOL
@@ -62,21 +54,13 @@
   #define DEBUG_ERROR_START       SERIAL_ERROR_START
   #define DEBUG_CHAR              SERIAL_CHAR
   #define DEBUG_ECHO              SERIAL_ECHO
-  #define DEBUG_DECIMAL           SERIAL_DECIMAL
-  #define DEBUG_ECHO_F            SERIAL_ECHO_F
   #define DEBUG_ECHOLN            SERIAL_ECHOLN
   #define DEBUG_ECHOPGM           SERIAL_ECHOPGM
   #define DEBUG_ECHOLNPGM         SERIAL_ECHOLNPGM
-  #define DEBUG_ECHOF             SERIAL_ECHOF
-  #define DEBUG_ECHOLNF           SERIAL_ECHOLNF
   #define DEBUG_ECHOPGM           SERIAL_ECHOPGM
   #define DEBUG_ECHOPGM_P         SERIAL_ECHOPGM_P
-  #define DEBUG_ECHOPAIR_F        SERIAL_ECHOPAIR_F
-  #define DEBUG_ECHOPAIR_F_P      SERIAL_ECHOPAIR_F_P
   #define DEBUG_ECHOLNPGM         SERIAL_ECHOLNPGM
   #define DEBUG_ECHOLNPGM_P       SERIAL_ECHOLNPGM_P
-  #define DEBUG_ECHOLNPAIR_F      SERIAL_ECHOLNPAIR_F
-  #define DEBUG_ECHOLNPAIR_F_P    SERIAL_ECHOLNPAIR_F_P
   #define DEBUG_ECHO_MSG          SERIAL_ECHO_MSG
   #define DEBUG_ERROR_MSG         SERIAL_ERROR_MSG
   #define DEBUG_EOL               SERIAL_EOL
@@ -93,19 +77,11 @@
   #define DEBUG_ERROR_START()       NOOP
   #define DEBUG_CHAR(...)           NOOP
   #define DEBUG_ECHO(...)           NOOP
-  #define DEBUG_DECIMAL(...)        NOOP
-  #define DEBUG_ECHO_F(...)         NOOP
   #define DEBUG_ECHOLN(...)         NOOP
   #define DEBUG_ECHOPGM(...)        NOOP
   #define DEBUG_ECHOLNPGM(...)      NOOP
-  #define DEBUG_ECHOF(...)          NOOP
-  #define DEBUG_ECHOLNF(...)        NOOP
   #define DEBUG_ECHOPGM_P(...)      NOOP
   #define DEBUG_ECHOLNPGM_P(...)    NOOP
-  #define DEBUG_ECHOPAIR_F(...)     NOOP
-  #define DEBUG_ECHOPAIR_F_P(...)   NOOP
-  #define DEBUG_ECHOLNPAIR_F(...)   NOOP
-  #define DEBUG_ECHOLNPAIR_F_P(...) NOOP
   #define DEBUG_ECHO_MSG(...)       NOOP
   #define DEBUG_ERROR_MSG(...)      NOOP
   #define DEBUG_EOL()               NOOP
diff --git a/Marlin/src/core/debug_section.h b/Marlin/src/core/debug_section.h
index 6e23d9e4ed..dc4cc0143c 100644
--- a/Marlin/src/core/debug_section.h
+++ b/Marlin/src/core/debug_section.h
@@ -38,11 +38,8 @@ private:
   bool debug;
 
   void echo_msg(FSTR_P const fpre) {
-    SERIAL_ECHOF(fpre);
-    if (the_msg) {
-      SERIAL_CHAR(' ');
-      SERIAL_ECHOF(the_msg);
-    }
+    SERIAL_ECHO(fpre);
+    if (the_msg) SERIAL_ECHO(AS_CHAR(' '), the_msg);
     SERIAL_CHAR(' ');
     print_pos(current_position);
   }
diff --git a/Marlin/src/core/serial.cpp b/Marlin/src/core/serial.cpp
index 8f3a59c814..bda1527545 100644
--- a/Marlin/src/core/serial.cpp
+++ b/Marlin/src/core/serial.cpp
@@ -68,26 +68,51 @@ MAP(_N_LBL, LOGICAL_AXIS_NAMES); MAP(_SP_N_LBL, LOGICAL_AXIS_NAMES);
 
 #endif
 
-void serial_print_P(PGM_P str) {
-  while (const char c = pgm_read_byte(str++)) SERIAL_CHAR(c);
+// Specializations for float, p_float_t, w_float_t
+template <> void SERIAL_ECHO(const float f)      { SERIAL_IMPL.print(f); }
+template <> void SERIAL_ECHO(const p_float_t pf) { SERIAL_IMPL.print(pf.value, pf.prec); }
+template <> void SERIAL_ECHO(const w_float_t wf) { char f1[20]; SERIAL_IMPL.print(dtostrf(wf.value, wf.width, wf.prec, f1)); }
+
+// Specializations for F-string
+template <> void SERIAL_ECHO(const FSTR_P fstr)   { SERIAL_ECHO_P(FTOP(fstr)); }
+template <> void SERIAL_ECHOLN(const FSTR_P fstr) { SERIAL_ECHOLN_P(FTOP(fstr)); }
+
+void SERIAL_CHAR(char a) { SERIAL_IMPL.write(a); }
+void SERIAL_EOL() { SERIAL_CHAR('\n'); }
+
+void SERIAL_ECHO(serial_char_t x) { SERIAL_IMPL.write(x.c); }
+
+void SERIAL_FLUSH()    { SERIAL_IMPL.flush(); }
+void SERIAL_FLUSHTX()  { SERIAL_IMPL.flushTX(); }
+
+void SERIAL_ECHO_P(PGM_P pstr) {
+  while (const char c = pgm_read_byte(pstr++)) SERIAL_CHAR(c);
 }
+void SERIAL_ECHOLN_P(PGM_P pstr) { SERIAL_ECHO_P(pstr); SERIAL_EOL(); }
 
-void serial_echo_start()  { serial_print(F("echo:")); }
-void serial_error_start() { serial_print(F("Error:")); }
+void SERIAL_ECHO_START()  { SERIAL_ECHO(F("echo:")); }
+void SERIAL_ERROR_START() { SERIAL_ECHO(F("Error:")); }
 
-void serial_spaces(uint8_t count) { count *= (PROPORTIONAL_FONT_RATIO); while (count--) SERIAL_CHAR(' '); }
+void SERIAL_ECHO_SP(uint8_t count) { count *= (PROPORTIONAL_FONT_RATIO); while (count--) SERIAL_CHAR(' '); }
 
 void serial_offset(const_float_t v, const uint8_t sp/*=0*/) {
   if (v == 0 && sp == 1)
     SERIAL_CHAR(' ');
   else if (v > 0 || (v == 0 && sp == 2))
     SERIAL_CHAR('+');
-  SERIAL_DECIMAL(v);
+  SERIAL_ECHO(v);
 }
 
-void serialprint_onoff(const bool onoff) { serial_print(onoff ? F(STR_ON) : F(STR_OFF)); }
+void serial_ternary(FSTR_P const pre, const bool onoff, FSTR_P const on, FSTR_P const off, FSTR_P const post/*=nullptr*/) {
+  if (pre)           SERIAL_ECHO(pre);
+  if (onoff && on)   SERIAL_ECHO(on);
+  if (!onoff && off) SERIAL_ECHO(off);
+  if (post)          SERIAL_ECHO(post);
+}
+
+void serialprint_onoff(const bool onoff) { SERIAL_ECHO(onoff ? F(STR_ON) : F(STR_OFF)); }
 void serialprintln_onoff(const bool onoff) { serialprint_onoff(onoff); SERIAL_EOL(); }
-void serialprint_truefalse(const bool tf) { serial_print(tf ? F("true") : F("false")); }
+void serialprint_truefalse(const bool tf) { SERIAL_ECHO(tf ? F("true") : F("false")); }
 
 void print_bin(uint16_t val) {
   for (uint8_t i = 16; i--;) {
@@ -97,11 +122,11 @@ void print_bin(uint16_t val) {
 }
 
 void print_pos(NUM_AXIS_ARGS_(const_float_t) FSTR_P const prefix/*=nullptr*/, FSTR_P const suffix/*=nullptr*/) {
-  if (prefix) serial_print(prefix);
+  if (prefix) SERIAL_ECHO(prefix);
   #if NUM_AXES
     SERIAL_ECHOPGM_P(
       LIST_N(DOUBLE(NUM_AXES), SP_X_STR, x, SP_Y_STR, y, SP_Z_STR, z, SP_I_STR, i, SP_J_STR, j, SP_K_STR, k, SP_U_STR, u, SP_V_STR, v, SP_W_STR, w)
     );
   #endif
-  if (suffix) serial_print(suffix); else SERIAL_EOL();
+  if (suffix) SERIAL_ECHO(suffix); else SERIAL_EOL();
 }
diff --git a/Marlin/src/core/serial.h b/Marlin/src/core/serial.h
index 62e1294e04..09f453d3b5 100644
--- a/Marlin/src/core/serial.h
+++ b/Marlin/src/core/serial.h
@@ -125,8 +125,6 @@ extern uint8_t marlin_debug_flags;
   #define SERIAL_IMPL         SERIAL_LEAF_1
 #endif
 
-#define SERIAL_OUT(WHAT, V...)  (void)SERIAL_IMPL.WHAT(V)
-
 #define PORT_REDIRECT(p)   _PORT_REDIRECT(1,p)
 #define PORT_RESTORE()     _PORT_RESTORE(1)
 #define SERIAL_PORTMASK(P) SerialMask::from(P)
@@ -134,65 +132,69 @@ extern uint8_t marlin_debug_flags;
 //
 // SERIAL_CHAR - Print one or more individual chars
 //
-inline void SERIAL_CHAR(char a) { SERIAL_IMPL.write(a); }
+void SERIAL_CHAR(char a);
 template <typename ... Args>
 void SERIAL_CHAR(char a, Args ... args) { SERIAL_IMPL.write(a); SERIAL_CHAR(args ...); }
 
 /**
- * SERIAL_ECHO - Print a single string or value.
+ * SERIAL_ECHO / SERIAL_ECHOLN - Print a single string or value.
  *   Any numeric parameter (including char) is printed as a base-10 number.
  *   A string pointer or literal will be output as a string.
  *
  * NOTE: Use SERIAL_CHAR to print char as a single character.
  */
-template <typename T>
-void SERIAL_ECHO(T x) { SERIAL_IMPL.print(x); }
+template <typename T> void SERIAL_ECHO(T x)   { SERIAL_IMPL.print(x); }
+template <typename T> void SERIAL_ECHOLN(T x) { SERIAL_IMPL.println(x); }
 
 // Wrapper for ECHO commands to interpret a char
-typedef struct SerialChar { char c; SerialChar(char n) : c(n) { } } serial_char_t;
-inline void SERIAL_ECHO(serial_char_t x) { SERIAL_IMPL.write(x.c); }
-#define AS_CHAR(C) serial_char_t(C)
+void SERIAL_ECHO(serial_char_t x);
 #define AS_DIGIT(C) AS_CHAR('0' + (C))
 
-template <typename T>
-void SERIAL_ECHOLN(T x) { SERIAL_IMPL.println(x); }
-
-// SERIAL_PRINT works like SERIAL_ECHO but also takes the numeric base
-template <typename T, typename U>
-void SERIAL_PRINT(T x, U y) { SERIAL_IMPL.print(x, y); }
-
-template <typename T>
-void SERIAL_PRINTLN(T x, PrintBase y) { SERIAL_IMPL.println(x, y); }
+// Print an integer with a numeric base such as PrintBase::Hex
+template <typename T> void SERIAL_PRINT(T x, PrintBase y)   { SERIAL_IMPL.print(x, y); }
+template <typename T> void SERIAL_PRINTLN(T x, PrintBase y) { SERIAL_IMPL.println(x, y); }
 
 // Flush the serial port
-inline void SERIAL_FLUSH()    { SERIAL_IMPL.flush(); }
-inline void SERIAL_FLUSHTX()  { SERIAL_IMPL.flushTX(); }
+void SERIAL_FLUSH();
+void SERIAL_FLUSHTX();
 
-// Serial echo and error prefixes
-#define SERIAL_ECHO_START()           serial_echo_start()
-#define SERIAL_ERROR_START()          serial_error_start()
+// Start an echo: or error: output
+void SERIAL_ECHO_START();
+void SERIAL_ERROR_START();
 
 // Serial end-of-line
-#define SERIAL_EOL()                  SERIAL_CHAR('\n')
+void SERIAL_EOL();
 
 // Print a single PROGMEM, PGM_P, or PSTR() string.
-void serial_print_P(PGM_P str);
-inline void serial_println_P(PGM_P str) { serial_print_P(str); SERIAL_EOL(); }
+void SERIAL_ECHO_P(PGM_P pstr);
+void SERIAL_ECHOLN_P(PGM_P pstr);
 
-// Print a single FSTR_P, F(), or FPSTR() string.
-inline void serial_print(FSTR_P const fstr) { serial_print_P(FTOP(fstr)); }
-inline void serial_println(FSTR_P const fstr) { serial_println_P(FTOP(fstr)); }
+// Specializations for float, p_float_t, and w_float_t
+template<> void SERIAL_ECHO(const float f);
+template<> void SERIAL_ECHO(const p_float_t pf);
+template<> void SERIAL_ECHO(const w_float_t wf);
+
+// Specializations for F-string
+template<> void SERIAL_ECHO(const FSTR_P fstr);
+template<> void SERIAL_ECHOLN(const FSTR_P fstr);
+
+// Print any number of items with arbitrary types (except loose PROGMEM strings)
+template <typename T, typename ... Args>
+void SERIAL_ECHO(T arg1, Args ... args) { SERIAL_ECHO(arg1); SERIAL_ECHO(args ...); }
+template <typename T, typename ... Args>
+void SERIAL_ECHOLN(T arg1, Args ... args) { SERIAL_ECHO(arg1); SERIAL_ECHO(args ...); SERIAL_EOL(); }
 
 //
-// SERIAL_ECHOPGM... macros are used to output string-value pairs.
+// SERIAL_ECHOPGM... macros are used to output string-value pairs, wrapping
+//                   all the odd loose string elements as PROGMEM strings.
 //
 
 // Print up to 20 pairs of values. Odd elements must be literal strings.
 #define __SEP_N(N,V...)           _SEP_##N(V)
 #define _SEP_N(N,V...)            __SEP_N(N,V)
 #define _SEP_N_REF()              _SEP_N
-#define _SEP_1(s)                 serial_print(F(s));
-#define _SEP_2(s,v)               serial_echopair(F(s),v);
+#define _SEP_1(s)                 SERIAL_ECHO(F(s));
+#define _SEP_2(s,v)               SERIAL_ECHO(F(s),v);
 #define _SEP_3(s,v,V...)          _SEP_2(s,v); DEFER2(_SEP_N_REF)()(TWO_ARGS(V),V);
 #define SERIAL_ECHOPGM(V...)      do{ EVAL(_SEP_N(TWO_ARGS(V),V)); }while(0)
 
@@ -200,8 +202,8 @@ inline void serial_println(FSTR_P const fstr) { serial_println_P(FTOP(fstr)); }
 #define __SELP_N(N,V...)          _SELP_##N(V)
 #define _SELP_N(N,V...)           __SELP_N(N,V)
 #define _SELP_N_REF()             _SELP_N
-#define _SELP_1(s)                serial_print(F(s "\n"));
-#define _SELP_2(s,v)              serial_echolnpair(F(s),v);
+#define _SELP_1(s)                SERIAL_ECHO(F(s "\n"));
+#define _SELP_2(s,v)              SERIAL_ECHOLN(F(s),v);
 #define _SELP_3(s,v,V...)         _SEP_2(s,v); DEFER2(_SELP_N_REF)()(TWO_ARGS(V),V);
 #define SERIAL_ECHOLNPGM(V...)    do{ EVAL(_SELP_N(TWO_ARGS(V),V)); }while(0)
 
@@ -209,8 +211,8 @@ inline void serial_println(FSTR_P const fstr) { serial_println_P(FTOP(fstr)); }
 #define __SEP_N_P(N,V...)         _SEP_##N##_P(V)
 #define _SEP_N_P(N,V...)          __SEP_N_P(N,V)
 #define _SEP_N_P_REF()            _SEP_N_P
-#define _SEP_1_P(p)               serial_print_P(p);
-#define _SEP_2_P(p,v)             serial_echopair_P(p,v);
+#define _SEP_1_P(p)               SERIAL_ECHO(FPSTR(p));
+#define _SEP_2_P(p,v)             SERIAL_ECHO(FPSTR(p),v);
 #define _SEP_3_P(p,v,V...)        _SEP_2_P(p,v); DEFER2(_SEP_N_P_REF)()(TWO_ARGS(V),V);
 #define SERIAL_ECHOPGM_P(V...)    do{ EVAL(_SEP_N_P(TWO_ARGS(V),V)); }while(0)
 
@@ -218,125 +220,25 @@ inline void serial_println(FSTR_P const fstr) { serial_println_P(FTOP(fstr)); }
 #define __SELP_N_P(N,V...)        _SELP_##N##_P(V)
 #define _SELP_N_P(N,V...)         __SELP_N_P(N,V)
 #define _SELP_N_P_REF()           _SELP_N_P
-#define _SELP_1_P(p)              serial_println_P(p)
-#define _SELP_2_P(p,v)            serial_echolnpair_P(p,v)
+#define _SELP_1_P(p)              SERIAL_ECHOLN(FPSTR(p));
+#define _SELP_2_P(p,v)            SERIAL_ECHOLN(FPSTR(p),v);
 #define _SELP_3_P(p,v,V...)       { _SEP_2_P(p,v); DEFER2(_SELP_N_P_REF)()(TWO_ARGS(V),V); }
 #define SERIAL_ECHOLNPGM_P(V...)  do{ EVAL(_SELP_N_P(TWO_ARGS(V),V)); }while(0)
 
-// Print up to 20 pairs of values. Odd elements must be FSTR_P, F(), or FPSTR().
-#define __SEP_N_F(N,V...)         _SEP_##N##_F(V)
-#define _SEP_N_F(N,V...)          __SEP_N_F(N,V)
-#define _SEP_N_F_REF()            _SEP_N_F
-#define _SEP_1_F(p)               serial_print(p);
-#define _SEP_2_F(p,v)             serial_echopair(p,v);
-#define _SEP_3_F(p,v,V...)        _SEP_2_F(p,v); DEFER2(_SEP_N_F_REF)()(TWO_ARGS(V),V);
-#define SERIAL_ECHOF(V...)        do{ EVAL(_SEP_N_F(TWO_ARGS(V),V)); }while(0)
-
-// Print up to 20 pairs of values followed by newline. Odd elements must be FSTR_P, F(), or FPSTR().
-#define __SELP_N_F(N,V...)        _SELP_##N##_F(V)
-#define _SELP_N_F(N,V...)         __SELP_N_F(N,V)
-#define _SELP_N_F_REF()           _SELP_N_F
-#define _SELP_1_F(p)              serial_println(p)
-#define _SELP_2_F(p,v)            serial_echolnpair(p,v)
-#define _SELP_3_F(p,v,V...)       { _SEP_2_F(p,v); DEFER2(_SELP_N_F_REF)()(TWO_ARGS(V),V); }
-#define SERIAL_ECHOLNF(V...)      do{ EVAL(_SELP_N_F(TWO_ARGS(V),V)); }while(0)
-
-#ifdef AllowDifferentTypeInList
-
-  inline void SERIAL_ECHOLIST_IMPL() {}
-  template <typename T>
-  void SERIAL_ECHOLIST_IMPL(T && t) { SERIAL_IMPL.print(t); }
-
-  template <typename T, typename ... Args>
-  void SERIAL_ECHOLIST_IMPL(T && t, Args && ... args) {
-    SERIAL_IMPL.print(t);
-    serial_print(F(", "));
-    SERIAL_ECHOLIST_IMPL(args...);
-  }
-
-  template <typename ... Args>
-  void SERIAL_ECHOLIST(FSTR_P const str, Args && ... args) {
-    SERIAL_IMPL.print(FTOP(str));
-    SERIAL_ECHOLIST_IMPL(args...);
-  }
-
-#else // Optimization if the listed type are all the same (seems to be the case in the codebase so use that instead)
-
-  template <typename ... Args>
-  void SERIAL_ECHOLIST(FSTR_P const fstr, Args && ... args) {
-    serial_print(fstr);
-    typename Private::first_type_of<Args...>::type values[] = { args... };
-    constexpr size_t argsSize = sizeof...(args);
-    for (size_t i = 0; i < argsSize; i++) {
-      if (i) serial_print(F(", "));
-      SERIAL_IMPL.print(values[i]);
-    }
-  }
-
-#endif
-
-// SERIAL_ECHO_F prints a floating point value with optional precision
-inline void SERIAL_ECHO_F(EnsureDouble x, int digit=2) { SERIAL_IMPL.print(x, digit); }
-
-#define SERIAL_ECHOPAIR_F_P(P,V...)   do{ serial_print_P(P); SERIAL_ECHO_F(V); }while(0)
-#define SERIAL_ECHOLNPAIR_F_P(P,V...) do{ SERIAL_ECHOPAIR_F_P(P,V); SERIAL_EOL(); }while(0)
-
-#define SERIAL_ECHOPAIR_F_F(S,V...)   do{ serial_print(S); SERIAL_ECHO_F(V); }while(0)
-#define SERIAL_ECHOLNPAIR_F_F(S,V...) do{ SERIAL_ECHOPAIR_F_F(S,V); SERIAL_EOL(); }while(0)
-
-#define SERIAL_ECHOPAIR_F(S,V...)     SERIAL_ECHOPAIR_F_F(F(S),V)
-#define SERIAL_ECHOLNPAIR_F(V...)     do{ SERIAL_ECHOPAIR_F(V); SERIAL_EOL(); }while(0)
-
-#define SERIAL_ECHO_MSG(V...)         do{ SERIAL_ECHO_START();  SERIAL_ECHOLNPGM(V); }while(0)
-#define SERIAL_ERROR_MSG(V...)        do{ SERIAL_ERROR_START(); SERIAL_ECHOLNPGM(V); }while(0)
-
-#define SERIAL_ECHO_SP(C)             serial_spaces(C)
+#define SERIAL_ECHO_MSG(V...)  do{ SERIAL_ECHO_START();  SERIAL_ECHOLNPGM(V); }while(0)
+#define SERIAL_ERROR_MSG(V...) do{ SERIAL_ERROR_START(); SERIAL_ECHOLNPGM(V); }while(0)
 
+// Print a prefix, conditional string, and suffix
+void serial_ternary(FSTR_P const pre, const bool onoff, FSTR_P const on, FSTR_P const off, FSTR_P const post=nullptr);
+// Shorthand to put loose strings in PROGMEM
 #define SERIAL_ECHO_TERNARY(TF, PRE, ON, OFF, POST) serial_ternary(F(PRE), TF, F(ON), F(OFF), F(POST))
 
-#if SERIAL_FLOAT_PRECISION
-  #define SERIAL_DECIMAL(V) SERIAL_PRINT(V, SERIAL_FLOAT_PRECISION)
-#else
-  #define SERIAL_DECIMAL(V) SERIAL_ECHO(V)
-#endif
+// Print up to 255 spaces
+void SERIAL_ECHO_SP(uint8_t count);
 
-//
-// Functions for serial printing from PROGMEM. (Saves loads of SRAM.)
-//
-inline void serial_echopair_P(PGM_P const pstr, serial_char_t v) { serial_print_P(pstr); SERIAL_CHAR(v.c); }
-inline void serial_echopair_P(PGM_P const pstr, float v)         { serial_print_P(pstr); SERIAL_DECIMAL(v); }
-inline void serial_echopair_P(PGM_P const pstr, double v)        { serial_print_P(pstr); SERIAL_DECIMAL(v); }
-//inline void serial_echopair_P(PGM_P const pstr, const char *v)   { serial_print_P(pstr); SERIAL_ECHO(v); }
-inline void serial_echopair_P(PGM_P const pstr, FSTR_P v)        { serial_print_P(pstr); SERIAL_ECHOF(v); }
-
-// Default implementation for types without a specialization. Handles integers.
-template <typename T>
-inline void serial_echopair_P(PGM_P const pstr, T v) { serial_print_P(pstr); SERIAL_ECHO(v); }
-
-// Add a newline.
-template <typename T>
-inline void serial_echolnpair_P(PGM_P const pstr, T v) { serial_echopair_P(pstr, v); SERIAL_EOL(); }
-
-// Catch-all for __FlashStringHelper *
-template <typename T>
-inline void serial_echopair(FSTR_P const fstr, T v) { serial_echopair_P(FTOP(fstr), v); }
-
-// Add a newline to the serial output
-template <typename T>
-inline void serial_echolnpair(FSTR_P const fstr, T v) { serial_echolnpair_P(FTOP(fstr), v); }
-
-void serial_echo_start();
-void serial_error_start();
-inline void serial_ternary(FSTR_P const pre, const bool onoff, FSTR_P const on, FSTR_P const off, FSTR_P const post=nullptr) {
-  if (pre) serial_print(pre);
-  if (onoff && on) serial_print(on);
-  if (!onoff && off) serial_print(off);
-  if (post) serial_print(post);
-}
 void serialprint_onoff(const bool onoff);
 void serialprintln_onoff(const bool onoff);
 void serialprint_truefalse(const bool tf);
-void serial_spaces(uint8_t count);
 void serial_offset(const_float_t v, const uint8_t sp=0); // For v==0 draw space (sp==1) or plus (sp==2)
 
 void print_bin(const uint16_t val);
diff --git a/Marlin/src/core/serial_base.h b/Marlin/src/core/serial_base.h
index fa0a2298f7..a2f49417b7 100644
--- a/Marlin/src/core/serial_base.h
+++ b/Marlin/src/core/serial_base.h
@@ -79,7 +79,7 @@ struct EnsureDouble {
   operator double() { return a; }
   // If the compiler breaks on ambiguity here, it's likely because print(X, base) is called with X not a double/float, and
   // a base that's not a PrintBase value. This code is made to detect the error. You MUST set a base explicitly like this:
-  // SERIAL_PRINT(v, PrintBase::Hex)
+  //SERIAL_PRINT(v, PrintBase::Hex)
   EnsureDouble(double a) : a(a) {}
   EnsureDouble(float a) : a(a) {}
 };
@@ -169,7 +169,6 @@ struct SerialBase {
   FORCE_INLINE void print(unsigned int c, PrintBase base)       { printNumber_unsigned(c, base); }
   FORCE_INLINE void print(unsigned long c, PrintBase base)      { printNumber_unsigned(c, base); }
 
-
   void print(EnsureDouble c, int digits)           { printFloat(c, digits); }
 
   // Forward the call to the former's method
@@ -180,7 +179,7 @@ struct SerialBase {
   void print(T c)    { print(c, PrintBase::Dec); }
 
   void print(float c)    { print(c, 2); }
-  void print(double c)    { print(c, 2); }
+  void print(double c)   { print(c, 2); }
 
   void println(char *s)               { print(s); println(); }
   void println(const char *s)         { print(s); println(); }
diff --git a/Marlin/src/core/types.h b/Marlin/src/core/types.h
index 8fcaa0000c..3168322860 100644
--- a/Marlin/src/core/types.h
+++ b/Marlin/src/core/types.h
@@ -283,6 +283,36 @@ typedef IF<TERN0(ABL_USES_GRID, (GRID_MAX_POINTS > 255)), uint16_t, uint8_t>::ty
 #define MMM_TO_MMS(MM_M) feedRate_t(static_cast<float>(MM_M) / 60.0f)
 #define MMS_TO_MMM(MM_S) (static_cast<float>(MM_S) * 60.0f)
 
+// Packaged character for AS_CHAR macro and other usage
+typedef struct SerialChar { char c; SerialChar(char n) : c(n) { } } serial_char_t;
+#define AS_CHAR(C) serial_char_t(C)
+
+// Packaged types: float with precision and/or width; a repeated space/character
+typedef struct WFloat { float value; char width; char prec;
+                        WFloat(float v, char w, char p) : value(v), width(w), prec(p) {}
+                      } w_float_t;
+typedef struct PFloat { float value; char prec;
+                        PFloat(float v, char p) : value(v), prec(p) {}
+                      } p_float_t;
+typedef struct RepChr { char asc; uint8_t count;
+                        RepChr(char a, uint8_t c) : asc(a), count(c) {}
+                      } repchr_t;
+typedef struct Spaces { uint8_t count;
+                        Spaces(uint8_t c) : count(c) {}
+                      } spaces_t;
+
+#ifdef __AVR__
+  typedef w_float_t w_double_t;
+  typedef p_float_t p_double_t;
+#else
+  typedef struct WDouble { double value; char width; char prec;
+                          WDouble(double v, char w, char p) : value(v), width(w), prec(p) {}
+                        } w_double_t;
+  typedef struct PDouble { double value; char prec;
+                          PDouble(double v, char p) : value(v), prec(p) {}
+                        } p_double_t;
+#endif
+
 //
 // Coordinates structures for XY, XYZ, XYZE...
 //
diff --git a/Marlin/src/core/utility.cpp b/Marlin/src/core/utility.cpp
index 64f083e197..8476591d4b 100644
--- a/Marlin/src/core/utility.cpp
+++ b/Marlin/src/core/utility.cpp
@@ -25,6 +25,10 @@
 #include "../MarlinCore.h"
 #include "../module/temperature.h"
 
+#if ENABLED(MARLIN_DEV_MODE)
+  MarlinError marlin_error_number;    // Error Number - Marlin can beep X times periodically, display, and emit...
+#endif
+
 void safe_delay(millis_t ms) {
   while (ms > 50) {
     ms -= 50;
@@ -95,9 +99,9 @@ void safe_delay(millis_t ms) {
           SERIAL_ECHOPGM(" (Aligned With");
 
         if (probe.offset_xy.y > 0)
-          SERIAL_ECHOF(F(TERN(IS_SCARA, "-Distal", "-Back")));
+          SERIAL_ECHO(F(TERN(IS_SCARA, "-Distal", "-Back")));
         else if (probe.offset_xy.y < 0)
-          SERIAL_ECHOF(F(TERN(IS_SCARA, "-Proximal", "-Front")));
+          SERIAL_ECHO(F(TERN(IS_SCARA, "-Proximal", "-Front")));
         else if (probe.offset_xy.x != 0)
           SERIAL_ECHOPGM("-Center");
 
@@ -105,7 +109,7 @@ void safe_delay(millis_t ms) {
 
       #endif
 
-      SERIAL_ECHOF(probe.offset.z < 0 ? F("Below") : probe.offset.z > 0 ? F("Above") : F("Same Z as"));
+      SERIAL_ECHO(probe.offset.z < 0 ? F("Below") : probe.offset.z > 0 ? F("Above") : F("Same Z as"));
       SERIAL_ECHOLNPGM(" Nozzle)");
 
     #endif
diff --git a/Marlin/src/core/utility.h b/Marlin/src/core/utility.h
index c4bb3c2a8a..891a4e4383 100644
--- a/Marlin/src/core/utility.h
+++ b/Marlin/src/core/utility.h
@@ -92,3 +92,22 @@ const xyze_char_t axis_codes LOGICAL_AXIS_ARRAY('E', 'X', 'Y', 'Z', AXIS4_NAME,
   #define AXIS_CHAR(A) axis_codes[A]
   #define IAXIS_CHAR(A) iaxis_codes[A]
 #endif
+
+#if ENABLED(MARLIN_DEV_MODE)
+  enum MarlinError : uint8_t {
+    ERR_NONE,
+    ERR_STRING_RANGE, // A string buffer was too small to set the whole blob
+    ERR_ASSERTION,    // An assertion was triggered
+    ERR_MALFUNCTION,
+    ERR_MEMORY_LEAK,
+    ERR_COMMS_SERIAL,
+    ERR_COMMS_SPI,
+    ERR_PLANNER_STARVED,
+    ERR_TMC_SHUTDOWN,
+    ERR_PROCEDURE_FAILED,
+    ERR_TOO_WACK,
+    ERR_PLAID_IN_SUMMER
+  };
+  extern MarlinError marlin_error_number;   // Error Number - Marlin can beep, display, and emit...
+  inline void error(const MarlinError err) { marlin_error_number = err; }
+#endif
diff --git a/Marlin/src/feature/bedlevel/bedlevel.cpp b/Marlin/src/feature/bedlevel/bedlevel.cpp
index 17407eafb9..a76c6cdd26 100644
--- a/Marlin/src/feature/bedlevel/bedlevel.cpp
+++ b/Marlin/src/feature/bedlevel/bedlevel.cpp
@@ -138,7 +138,7 @@ void reset_bed_level() {
   void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, const float *values) {
     #ifndef SCAD_MESH_OUTPUT
       for (uint8_t x = 0; x < sx; ++x) {
-        serial_spaces(precision + (x < 10 ? 3 : 2));
+        SERIAL_ECHO_SP(precision + (x < 10 ? 3 : 2));
         SERIAL_ECHO(x);
       }
       SERIAL_EOL();
@@ -158,7 +158,7 @@ void reset_bed_level() {
         const float offset = values[x * sy + y];
         if (!isnan(offset)) {
           if (offset >= 0) SERIAL_CHAR('+');
-          SERIAL_ECHO_F(offset, int(precision));
+          SERIAL_ECHO(p_float_t(offset, precision));
         }
         else {
           #ifdef SCAD_MESH_OUTPUT
diff --git a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
index 787827bb9b..f40cbccadd 100644
--- a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
+++ b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
@@ -123,8 +123,7 @@
   #endif // IS_CARTESIAN && !SEGMENT_LEVELED_MOVES
 
   void mesh_bed_leveling::report_mesh() {
-    SERIAL_ECHOPAIR_F(STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh. Z offset: ", z_offset, 5);
-    SERIAL_ECHOLNPGM("\nMeasured points:");
+    SERIAL_ECHOLN(F(STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh. Z offset: "), p_float_t(z_offset, 5), F("\nMeasured points:"));
     print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5, z_values[0]);
   }
 
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.cpp b/Marlin/src/feature/bedlevel/ubl/ubl.cpp
index b99334795d..0228bd247e 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl.cpp
+++ b/Marlin/src/feature/bedlevel/ubl/ubl.cpp
@@ -51,8 +51,7 @@ void unified_bed_leveling::report_current_mesh() {
   GRID_LOOP(x, y)
     if (!isnan(z_values[x][y])) {
       SERIAL_ECHO_START();
-      SERIAL_ECHOPGM("  M421 I", x, " J", y);
-      SERIAL_ECHOLNPAIR_F_P(SP_Z_STR, z_values[x][y], 4);
+      SERIAL_ECHOLN(F("  M421 I"), x, F(" J"), y, FPSTR(SP_Z_STR), p_float_t(z_values[x][y], 4));
       serial_delay(75); // Prevent Printrun from exploding
     }
 }
@@ -211,10 +210,10 @@ void unified_bed_leveling::display_map(const uint8_t map_type) {
         // TODO: Display on Graphical LCD
       }
       else if (isnan(f))
-        SERIAL_ECHOF(human ? F("  .   ") : F("NAN"));
+        SERIAL_ECHO(human ? F("  .   ") : F("NAN"));
       else if (human || csv) {
         if (human && f >= 0) SERIAL_CHAR(f > 0 ? '+' : ' ');  // Display sign also for positive numbers (' ' for 0)
-        SERIAL_DECIMAL(f);                                    // Positive: 5 digits, Negative: 6 digits
+        SERIAL_ECHO(p_float_t(f, 3));                         // Positive: 5 digits, Negative: 6 digits
       }
       if (csv && i < (GRID_MAX_POINTS_X) - 1) SERIAL_CHAR('\t');
 
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.h b/Marlin/src/feature/bedlevel/ubl/ubl.h
index c9bc797429..f751ce40d0 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl.h
+++ b/Marlin/src/feature/bedlevel/ubl/ubl.h
@@ -279,10 +279,8 @@ public:
       if (DEBUGGING(MESH_ADJUST)) DEBUG_ECHOLNPGM("??? Yikes! NAN in ");
     }
 
-    if (DEBUGGING(MESH_ADJUST)) {
-      DEBUG_ECHOPGM("get_z_correction(", rx0, ", ", ry0);
-      DEBUG_ECHOLNPAIR_F(") => ", z0, 6);
-    }
+    if (DEBUGGING(MESH_ADJUST))
+      DEBUG_ECHOLN(F("get_z_correction("), rx0, F(", "), ry0, F(") => "), p_float_t(z0, 6));
 
     return z0;
   }
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
index 551277c35d..1c43af1884 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
+++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
@@ -458,12 +458,8 @@ void unified_bed_leveling::G29() {
             invalidate();
             SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh.");
           }
-          if (param.V_verbosity > 1) {
-            SERIAL_ECHOPGM("Probing around (", param.XY_pos.x);
-            SERIAL_CHAR(',');
-            SERIAL_DECIMAL(param.XY_pos.y);
-            SERIAL_ECHOLNPGM(").\n");
-          }
+          if (param.V_verbosity > 1)
+            SERIAL_ECHOLN(F("Probing around ("), param.XY_pos.x, AS_CHAR(','), param.XY_pos.y, F(").\n"));
           probe_entire_mesh(param.XY_pos, parser.seen_test('T'), parser.seen_test('E'), parser.seen_test('U'));
 
           report_current_position();
@@ -722,10 +718,10 @@ void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const_float_t o
       sum_of_diff_squared += sq(z_values[x][y] - mean);
 
   SERIAL_ECHOLNPGM("# of samples: ", n);
-  SERIAL_ECHOLNPAIR_F("Mean Mesh Height: ", mean, 6);
+  SERIAL_ECHOLNPGM("Mean Mesh Height: ", p_float_t(mean, 6));
 
   const float sigma = SQRT(sum_of_diff_squared / (n + 1));
-  SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6);
+  SERIAL_ECHOLNPGM("Standard Deviation: ", p_float_t(sigma, 6));
 
   if (cflag)
     GRID_LOOP(x, y)
@@ -924,10 +920,8 @@ void set_message_with_feedback(FSTR_P const fstr) {
 
     const float thickness = ABS(z1 - z2);
 
-    if (param.V_verbosity > 1) {
-      SERIAL_ECHOPAIR_F("Business Card is ", thickness, 4);
-      SERIAL_ECHOLNPGM("mm thick.");
-    }
+    if (param.V_verbosity > 1)
+      SERIAL_ECHOLNPGM("Business Card is ", p_float_t(thickness, 4), "mm thick.");
 
     restore_ubl_active_state_and_leave();
 
@@ -993,7 +987,7 @@ void set_message_with_feedback(FSTR_P const fstr) {
       TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y]));
 
       if (param.V_verbosity > 2)
-        SERIAL_ECHOLNPAIR_F("Mesh Point Measured at: ", z_values[lpos.x][lpos.y], 6);
+        SERIAL_ECHOLNPGM("Mesh Point Measured at: ", p_float_t(z_values[lpos.x][lpos.y], 6));
       SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
     } while (location.valid());
 
@@ -1504,7 +1498,7 @@ void unified_bed_leveling::smart_fill_mesh() {
         measured_z -= TERN(UBL_TILT_ON_MESH_POINTS_3POINT, z_values[cpos[i].pos.x][cpos[i].pos.y], get_z_correction(points[i]));
         TERN_(VALIDATE_MESH_TILT, gotz[i] = measured_z);
 
-        if (param.V_verbosity > 3) { serial_spaces(16); SERIAL_ECHOLNPGM("Corrected_Z=", measured_z); }
+        if (param.V_verbosity > 3) { SERIAL_ECHO_SP(16); SERIAL_ECHOLNPGM("Corrected_Z=", measured_z); }
 
         incremental_LSF(&lsf_results, points[i], measured_z);
       }
@@ -1562,26 +1556,28 @@ void unified_bed_leveling::smart_fill_mesh() {
 
           #if ENABLED(DEBUG_LEVELING_FEATURE)
             if (DEBUGGING(LEVELING)) {
-              const xy_pos_t lpos = rpos.asLogical();
               #if ENABLED(UBL_TILT_ON_MESH_POINTS)
                 const xy_pos_t oldLpos = oldRpos.asLogical();
-                DEBUG_ECHOPGM("Calculated point: ("); DEBUG_ECHO_F(oldRpos.x, 7); DEBUG_CHAR(','); DEBUG_ECHO_F(oldRpos.y, 7);
-                DEBUG_ECHOPAIR_F(")   logical: (", oldLpos.x, 7); DEBUG_CHAR(','); DEBUG_ECHO_F(oldLpos.y, 7);
-                DEBUG_ECHOPGM(")\nSelected mesh point: ");
+                DEBUG_ECHO(F("Calculated point: ("), p_float_t(oldRpos.x, 7), AS_CHAR(','), p_float_t(oldRpos.y, 7),
+                           F(")   logical: ("), p_float_t(oldLpos.x, 7), AS_CHAR(','), p_float_t(oldLpos.y, 7),
+                           F(")\nSelected mesh point: ")
+                );
               #endif
-              DEBUG_CHAR('('); DEBUG_ECHO_F(rpos.x, 7); DEBUG_CHAR(','); DEBUG_ECHO_F(rpos.y, 7);
-              DEBUG_ECHOPAIR_F(")  logical: (", lpos.x, 7); DEBUG_CHAR(','); DEBUG_ECHO_F(lpos.y, 7);
-              DEBUG_ECHOPAIR_F(")  measured: ", measured_z, 7);
-              DEBUG_ECHOPAIR_F("  correction: ", zcorr, 7);
+              const xy_pos_t lpos = rpos.asLogical();
+              DEBUG_ECHO(      AS_CHAR('('), p_float_t(rpos.x, 7), AS_CHAR(','), p_float_t(rpos.y, 7),
+                         F(")  logical: ("), p_float_t(lpos.x, 7), AS_CHAR(','), p_float_t(lpos.y, 7),
+                         F(")  measured: "), p_float_t(measured_z, 7),
+                         F("  correction: "), p_float_t(zcorr, 7)
+              );
             }
           #endif
 
           measured_z -= zcorr;
 
-          if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR_F("  final >>>---> ", measured_z, 7);
+          if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("  final >>>---> ", p_float_t(measured_z, 7));
 
           if (param.V_verbosity > 3) {
-            serial_spaces(16);
+            SERIAL_ECHO_SP(16);
             SERIAL_ECHOLNPGM("Corrected_Z=", measured_z);
           }
           incremental_LSF(&lsf_results, rpos, measured_z);
@@ -1597,20 +1593,14 @@ void unified_bed_leveling::smart_fill_mesh() {
     probe.move_z_after_probing();
 
     if (abort_flag || finish_incremental_LSF(&lsf_results)) {
-      SERIAL_ECHOPGM("Could not complete LSF!");
+      SERIAL_ECHOLNPGM("Could not complete LSF!");
       return;
     }
 
     vector_3 normal = vector_3(lsf_results.A, lsf_results.B, 1).get_normal();
 
-    if (param.V_verbosity > 2) {
-      SERIAL_ECHOPAIR_F("bed plane normal = [", normal.x, 7);
-      SERIAL_CHAR(',');
-      SERIAL_ECHO_F(normal.y, 7);
-      SERIAL_CHAR(',');
-      SERIAL_ECHO_F(normal.z, 7);
-      SERIAL_ECHOLNPGM("]");
-    }
+    if (param.V_verbosity > 2)
+      SERIAL_ECHOLN(F("bed plane normal = ["), p_float_t(normal.x, 7), AS_CHAR(','), p_float_t(normal.y, 7), AS_CHAR(','), p_float_t(normal.z, 7), AS_CHAR(']'));
 
     matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1));
 
@@ -1618,24 +1608,14 @@ void unified_bed_leveling::smart_fill_mesh() {
       float mx = get_mesh_x(i), my = get_mesh_y(j), mz = z_values[i][j];
 
       if (DEBUGGING(LEVELING)) {
-        DEBUG_ECHOPAIR_F("before rotation = [", mx, 7);
-        DEBUG_CHAR(',');
-        DEBUG_ECHO_F(my, 7);
-        DEBUG_CHAR(',');
-        DEBUG_ECHO_F(mz, 7);
-        DEBUG_ECHOPGM("]   ---> ");
+        DEBUG_ECHOLN(F("before rotation = ["), p_float_t(mx, 7), AS_CHAR(','), p_float_t(my, 7), AS_CHAR(','), p_float_t(mz, 7), F("]   ---> "));
         DEBUG_DELAY(20);
       }
 
       rotation.apply_rotation_xyz(mx, my, mz);
 
       if (DEBUGGING(LEVELING)) {
-        DEBUG_ECHOPAIR_F("after rotation = [", mx, 7);
-        DEBUG_CHAR(',');
-        DEBUG_ECHO_F(my, 7);
-        DEBUG_CHAR(',');
-        DEBUG_ECHO_F(mz, 7);
-        DEBUG_ECHOLNPGM("]");
+        DEBUG_ECHOLN(F("after rotation = ["), p_float_t(mx, 7), AS_CHAR(','), p_float_t(my, 7), AS_CHAR(','), p_float_t(mz, 7), F("]   ---> "));
         DEBUG_DELAY(20);
       }
 
@@ -1645,17 +1625,9 @@ void unified_bed_leveling::smart_fill_mesh() {
 
     if (DEBUGGING(LEVELING)) {
       rotation.debug(F("rotation matrix:\n"));
-      DEBUG_ECHOPAIR_F("LSF Results A=", lsf_results.A, 7);
-      DEBUG_ECHOPAIR_F("  B=", lsf_results.B, 7);
-      DEBUG_ECHOLNPAIR_F("  D=", lsf_results.D, 7);
+      DEBUG_ECHOLN(F("LSF Results A="), p_float_t(lsf_results.A, 7), F("  B="), p_float_t(lsf_results.B, 7), F("  D="), p_float_t(lsf_results.D, 7));
       DEBUG_DELAY(55);
-
-      DEBUG_ECHOPAIR_F("bed plane normal = [", normal.x, 7);
-      DEBUG_CHAR(',');
-      DEBUG_ECHO_F(normal.y, 7);
-      DEBUG_CHAR(',');
-      DEBUG_ECHO_F(normal.z, 7);
-      DEBUG_ECHOLNPGM("]");
+      DEBUG_ECHOLN(F("bed plane normal = ["), p_float_t(normal.x, 7), AS_CHAR(','), p_float_t(normal.y, 7), AS_CHAR(','), p_float_t(normal.z, 7), AS_CHAR(']'));
       DEBUG_EOL();
 
       /**
@@ -1672,21 +1644,17 @@ void unified_bed_leveling::smart_fill_mesh() {
           return normal.x * pos.x + normal.y * pos.y + zadd;
         };
         auto debug_pt = [](const int num, const xy_pos_t &pos, const_float_t zadd) {
-          d_from(); DEBUG_ECHOPGM("Point ", num, ":");
-          DEBUG_ECHO_F(normed(pos, zadd), 6);
-          DEBUG_ECHOLNPAIR_F("   Z error = ", zadd - get_z_correction(pos), 6);
+          d_from();
+          DEBUG_ECHOLN(F("Point "), num, AS_CHAR(':'), p_float_t(normed(pos, zadd), 6), F("   Z error = "), p_float_t(zadd - get_z_correction(pos), 6));
         };
         debug_pt(1, probe_pt[0], normal.z * gotz[0]);
         debug_pt(2, probe_pt[1], normal.z * gotz[1]);
         debug_pt(3, probe_pt[2], normal.z * gotz[2]);
         #if ENABLED(Z_SAFE_HOMING)
           constexpr xy_float_t safe_xy = { Z_SAFE_HOMING_X_POINT, Z_SAFE_HOMING_Y_POINT };
-          d_from(); DEBUG_ECHOPGM("safe home with Z=");
-          DEBUG_ECHOLNPAIR_F("0 : ", normed(safe_xy, 0), 6);
-          d_from(); DEBUG_ECHOPGM("safe home with Z=");
-          DEBUG_ECHOLNPAIR_F("mesh value ", normed(safe_xy, get_z_correction(safe_xy)), 6);
-          DEBUG_ECHOPGM("   Z error = (", Z_SAFE_HOMING_X_POINT, ",", Z_SAFE_HOMING_Y_POINT);
-          DEBUG_ECHOLNPAIR_F(") = ", get_z_correction(safe_xy), 6);
+          d_from(); DEBUG_ECHOLN(F("safe home with Z="), F("0 : "), p_float_t(normed(safe_xy, 0), 6));
+          d_from(); DEBUG_ECHOLN(F("safe home with Z="), F("mesh value "), p_float_t(normed(safe_xy, get_z_correction(safe_xy)), 6));
+          DEBUG_ECHO(F("   Z error = ("), Z_SAFE_HOMING_X_POINT, AS_CHAR(','), Z_SAFE_HOMING_Y_POINT, F(") = "), p_float_t(get_z_correction(safe_xy), 6));
         #endif
       #endif
     } // DEBUGGING(LEVELING)
@@ -1734,7 +1702,7 @@ void unified_bed_leveling::smart_fill_mesh() {
             }
           }
           if (finish_incremental_LSF(&lsf_results)) {
-            SERIAL_ECHOLNPGM("Insufficient data");
+            SERIAL_ECHOLNPGM(" Insufficient data");
             return;
           }
           const float ez = -lsf_results.D - lsf_results.A * ppos.x - lsf_results.B * ppos.y;
@@ -1745,7 +1713,7 @@ void unified_bed_leveling::smart_fill_mesh() {
       }
     }
 
-    SERIAL_ECHOLNPGM("done");
+    SERIAL_ECHOLNPGM(" done.");
   }
 #endif // UBL_G29_P31
 
@@ -1758,20 +1726,19 @@ void unified_bed_leveling::smart_fill_mesh() {
     report_state();
 
     if (storage_slot == -1)
-      SERIAL_ECHOPGM("No Mesh Loaded.");
+      SERIAL_ECHOLNPGM("No Mesh Loaded.");
     else
-      SERIAL_ECHOPGM("Mesh ", storage_slot, " Loaded.");
-    SERIAL_EOL();
+      SERIAL_ECHOLNPGM("Mesh ", storage_slot, " Loaded.");
     serial_delay(50);
 
     #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
-      SERIAL_ECHOLNPAIR_F("Fade Height M420 Z", planner.z_fade_height, 4);
+      SERIAL_ECHOLN(F("Fade Height M420 Z"), p_float_t(planner.z_fade_height, 4));
     #endif
 
     adjust_mesh_to_mean(param.C_seen, param.C_constant);
 
     #if HAS_BED_PROBE
-      SERIAL_ECHOLNPAIR_F("Probe Offset M851 Z", probe.offset.z, 7);
+      SERIAL_ECHOLNPGM("Probe Offset M851 Z", p_float_t(probe.offset.z, 7));
     #endif
 
     SERIAL_ECHOLNPGM("MESH_MIN_X  " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X); serial_delay(50);
@@ -1785,16 +1752,14 @@ void unified_bed_leveling::smart_fill_mesh() {
 
     SERIAL_ECHOPGM("X-Axis Mesh Points at: ");
     for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) {
-      SERIAL_ECHO_F(LOGICAL_X_POSITION(get_mesh_x(i)), 3);
-      SERIAL_ECHOPGM("  ");
+      SERIAL_ECHO(p_float_t(LOGICAL_X_POSITION(get_mesh_x(i)), 3), F("  "));
       serial_delay(25);
     }
     SERIAL_EOL();
 
     SERIAL_ECHOPGM("Y-Axis Mesh Points at: ");
     for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; ++i) {
-      SERIAL_ECHO_F(LOGICAL_Y_POSITION(get_mesh_y(i)), 3);
-      SERIAL_ECHOPGM("  ");
+      SERIAL_ECHO(p_float_t(LOGICAL_Y_POSITION(get_mesh_y(i)), 3), F("  "));
       serial_delay(25);
     }
     SERIAL_EOL();
@@ -1806,23 +1771,21 @@ void unified_bed_leveling::smart_fill_mesh() {
     SERIAL_EOL();
     serial_delay(50);
 
-    #if ENABLED(UBL_DEVEL_DEBUGGING)
-      SERIAL_ECHOLNPGM("ubl_state_at_invocation :", ubl_state_at_invocation, "\nubl_state_recursion_chk :", ubl_state_recursion_chk);
-      serial_delay(50);
+    SERIAL_ECHOLNPGM("ubl_state_at_invocation :", ubl_state_at_invocation, "\nubl_state_recursion_chk :", ubl_state_recursion_chk);
+    serial_delay(50);
 
-      SERIAL_ECHOLNPGM("Meshes go from ", hex_address((void*)settings.meshes_start_index()), " to ", hex_address((void*)settings.meshes_end_index()));
-      serial_delay(50);
+    SERIAL_ECHOLNPGM("Meshes go from ", hex_address((void*)settings.meshes_start_index()), " to ", hex_address((void*)settings.meshes_end_index()));
+    serial_delay(50);
 
-      SERIAL_ECHOLNPGM("sizeof(ubl) :  ", sizeof(ubl));         SERIAL_EOL();
-      SERIAL_ECHOLNPGM("z_value[][] size: ", sizeof(z_values)); SERIAL_EOL();
-      serial_delay(25);
+    SERIAL_ECHOLNPGM("sizeof(ubl) :  ", sizeof(ubl));         SERIAL_EOL();
+    SERIAL_ECHOLNPGM("z_value[][] size: ", sizeof(z_values)); SERIAL_EOL();
+    serial_delay(25);
 
-      SERIAL_ECHOLNPGM("EEPROM free for UBL: ", hex_address((void*)(settings.meshes_end_index() - settings.meshes_start_index())));
-      serial_delay(50);
+    SERIAL_ECHOLNPGM("EEPROM free for UBL: ", hex_address((void*)(settings.meshes_end_index() - settings.meshes_start_index())));
+    serial_delay(50);
 
-      SERIAL_ECHOLNPGM("EEPROM can hold ", settings.calc_num_meshes(), " meshes.\n");
-      serial_delay(25);
-    #endif // UBL_DEVEL_DEBUGGING
+    SERIAL_ECHOLNPGM("EEPROM can hold ", settings.calc_num_meshes(), " meshes.\n");
+    serial_delay(25);
 
     if (!sanity_check()) {
       echo_name();
diff --git a/Marlin/src/feature/encoder_i2c.cpp b/Marlin/src/feature/encoder_i2c.cpp
index 1c01e1c23b..a367cea48d 100644
--- a/Marlin/src/feature/encoder_i2c.cpp
+++ b/Marlin/src/feature/encoder_i2c.cpp
@@ -106,10 +106,7 @@ void I2CPositionEncoder::update() {
           SERIAL_ECHOLNPGM("Current position is ", pos);
           SERIAL_ECHOLNPGM("Position in encoder ticks is ", positionInTicks);
           SERIAL_ECHOLNPGM("New zero-offset of ", zeroOffset);
-          SERIAL_ECHOPGM("New position reads as ", get_position());
-          SERIAL_CHAR('(');
-          SERIAL_DECIMAL(mm_from_count(get_position()));
-          SERIAL_ECHOLNPGM(")");
+          SERIAL_ECHOLN(F("New position reads as "), get_position(), AS_CHAR('('), mm_from_count(get_position()), AS_CHAR(')'));
         #endif
       }
     #endif
diff --git a/Marlin/src/feature/host_actions.cpp b/Marlin/src/feature/host_actions.cpp
index 235253b5a3..cc5face259 100644
--- a/Marlin/src/feature/host_actions.cpp
+++ b/Marlin/src/feature/host_actions.cpp
@@ -41,8 +41,7 @@ HostUI hostui;
 
 void HostUI::action(FSTR_P const fstr, const bool eol) {
   PORT_REDIRECT(SerialMask::All);
-  SERIAL_ECHOPGM("//action:");
-  SERIAL_ECHOF(fstr);
+  SERIAL_ECHOPGM("//action:", fstr);
   if (eol) SERIAL_EOL();
 }
 
@@ -107,7 +106,7 @@ void HostUI::action(FSTR_P const fstr, const bool eol) {
   void HostUI::prompt(FSTR_P const ptype, const bool eol/*=true*/) {
     PORT_REDIRECT(SerialMask::All);
     action(F("prompt_"), false);
-    SERIAL_ECHOF(ptype);
+    SERIAL_ECHO(ptype);
     if (eol) SERIAL_EOL();
   }
 
diff --git a/Marlin/src/feature/max7219.cpp b/Marlin/src/feature/max7219.cpp
index d3328855f4..991f3e79db 100644
--- a/Marlin/src/feature/max7219.cpp
+++ b/Marlin/src/feature/max7219.cpp
@@ -136,9 +136,7 @@ uint8_t Max7219::suspended; // = 0;
 
 void Max7219::error(FSTR_P const func, const int32_t v1, const int32_t v2/*=-1*/) {
   #if ENABLED(MAX7219_ERRORS)
-    SERIAL_ECHOPGM("??? Max7219::");
-    SERIAL_ECHOF(func, AS_CHAR('('));
-    SERIAL_ECHO(v1);
+    SERIAL_ECHO(F("??? Max7219::"), func, AS_CHAR('('), v1);
     if (v2 > 0) SERIAL_ECHOPGM(", ", v2);
     SERIAL_CHAR(')');
     SERIAL_EOL();
diff --git a/Marlin/src/feature/meatpack.cpp b/Marlin/src/feature/meatpack.cpp
index 07ff41e5be..5d2d112967 100644
--- a/Marlin/src/feature/meatpack.cpp
+++ b/Marlin/src/feature/meatpack.cpp
@@ -171,7 +171,7 @@ void MeatPack::report_state() {
   // should not contain the "PV' substring, as this is used to indicate protocol version
   SERIAL_ECHOPGM("[MP] " MeatPack_ProtocolVersion " ");
   serialprint_onoff(TEST(state, MPConfig_Bit_Active));
-  SERIAL_ECHOF(TEST(state, MPConfig_Bit_NoSpaces) ? F(" NSP\n") : F(" ESP\n"));
+  SERIAL_ECHO(TEST(state, MPConfig_Bit_NoSpaces) ? F(" NSP\n") : F(" ESP\n"));
 }
 
 /**
diff --git a/Marlin/src/feature/mixing.cpp b/Marlin/src/feature/mixing.cpp
index 1ce4892248..6cf59fdb56 100644
--- a/Marlin/src/feature/mixing.cpp
+++ b/Marlin/src/feature/mixing.cpp
@@ -60,10 +60,7 @@ void Mixer::normalize(const uint8_t tool_index) {
   }
   #ifdef MIXER_NORMALIZER_DEBUG
     SERIAL_ECHOPGM("Mixer: Old relation : [ ");
-    MIXER_STEPPER_LOOP(i) {
-      SERIAL_DECIMAL(collector[i] / csum);
-      SERIAL_CHAR(' ');
-    }
+    MIXER_STEPPER_LOOP(i) SERIAL_ECHO(collector[i] / csum, AS_CHAR(' '));
     SERIAL_ECHOLNPGM("]");
   #endif
 
@@ -75,16 +72,12 @@ void Mixer::normalize(const uint8_t tool_index) {
     csum = 0;
     SERIAL_ECHOPGM("Mixer: Normalize to : [ ");
     MIXER_STEPPER_LOOP(i) {
-      SERIAL_ECHO(uint16_t(color[tool_index][i]));
-      SERIAL_CHAR(' ');
+      SERIAL_ECHO(uint16_t(color[tool_index][i]), AS_CHAR(' '));
       csum += color[tool_index][i];
     }
     SERIAL_ECHOLNPGM("]");
     SERIAL_ECHOPGM("Mixer: New relation : [ ");
-    MIXER_STEPPER_LOOP(i) {
-      SERIAL_ECHO_F(uint16_t(color[tool_index][i]) / csum, 3);
-      SERIAL_CHAR(' ');
-    }
+    MIXER_STEPPER_LOOP(i) SERIAL_ECHO(p_float_t(uint16_t(color[tool_index][i]) / csum, 3), AS_CHAR(' '));
     SERIAL_ECHOLNPGM("]");
   #endif
 
diff --git a/Marlin/src/feature/mixing.h b/Marlin/src/feature/mixing.h
index a43b059944..c5c60a5e9f 100644
--- a/Marlin/src/feature/mixing.h
+++ b/Marlin/src/feature/mixing.h
@@ -137,11 +137,11 @@ class Mixer {
       MIXER_STEPPER_LOOP(i) tcolor[i] = mix[i] * scale;
 
       #ifdef MIXER_NORMALIZER_DEBUG
-        SERIAL_ECHOPGM("Mix [ ");
-        SERIAL_ECHOLIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]);
-        SERIAL_ECHOPGM(" ] to Color [ ");
-        SERIAL_ECHOLIST_N(MIXING_STEPPERS, tcolor[0], tcolor[1], tcolor[2], tcolor[3], tcolor[4], tcolor[5]);
-        SERIAL_ECHOLNPGM(" ]");
+        SERIAL_ECHOLN(
+          F("Mix [ "), LIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]),
+          F(" ] to Color [ "), LIST_N(MIXING_STEPPERS, tcolor[0], tcolor[1], tcolor[2], tcolor[3], tcolor[4], tcolor[5]),
+          F(" ]")
+        );
       #endif
     }
 
@@ -151,11 +151,10 @@ class Mixer {
       MIXER_STEPPER_LOOP(i) mix[i] = mixer_perc_t(100.0f * color[j][i] / ctot + 0.5f);
 
       #ifdef MIXER_NORMALIZER_DEBUG
-        SERIAL_ECHOPGM("V-tool ", j, " [ ");
-        SERIAL_ECHOLIST_N(MIXING_STEPPERS, color[j][0], color[j][1], color[j][2], color[j][3], color[j][4], color[j][5]);
-        SERIAL_ECHOPGM(" ] to Mix [ ");
-        SERIAL_ECHOLIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]);
-        SERIAL_ECHOLNPGM(" ]");
+        SERIAL_ECHOLN(F("V-tool "), j,
+          F(" [ "), LIST_N(MIXING_STEPPERS, color[j][0], color[j][1], color[j][2], color[j][3], color[j][4], color[j][5]),
+          F(" ] to Mix [ "), LIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]), F(" ]")
+        );
       #endif
     }
 
@@ -196,11 +195,10 @@ class Mixer {
       MIXER_STEPPER_LOOP(i) mix[i] = (mixer_perc_t)CEIL(100.0f * gradient.color[i] / ctot);
 
       #ifdef MIXER_NORMALIZER_DEBUG
-        SERIAL_ECHOPGM("Gradient [ ");
-        SERIAL_ECHOLIST_N(MIXING_STEPPERS, gradient.color[0], gradient.color[1], gradient.color[2], gradient.color[3], gradient.color[4], gradient.color[5]);
-        SERIAL_ECHOPGM(" ] to Mix [ ");
-        SERIAL_ECHOLIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]);
-        SERIAL_ECHOLNPGM(" ]");
+        SERIAL_ECHOLN(
+          F("Gradient [ "), LIST_N(MIXING_STEPPERS, gradient.color[0], gradient.color[1], gradient.color[2], gradient.color[3], gradient.color[4], gradient.color[5]),
+          F(" ] to Mix [ "), LIST_N(MIXING_STEPPERS, mix[0], mix[1], mix[2], mix[3], mix[4], mix[5]), F(" ]")
+        );
       #endif
     }
 
diff --git a/Marlin/src/feature/mmu/mmu2.cpp b/Marlin/src/feature/mmu/mmu2.cpp
index ea1a33ddaa..19aae7b7d7 100644
--- a/Marlin/src/feature/mmu/mmu2.cpp
+++ b/Marlin/src/feature/mmu/mmu2.cpp
@@ -286,7 +286,7 @@ void MMU2::mmu_loop() {
         sscanf(rx_buffer, "%hhuok\n", &finda);
 
         // This is super annoying. Only activate if necessary
-        // if (finda_runout_valid) DEBUG_ECHOLNPAIR_F("MMU <= 'P0'\nMMU => ", finda, 6);
+        //if (finda_runout_valid) DEBUG_ECHOLNPGM("MMU <= 'P0'\nMMU => ", p_float_t(finda, 6));
 
         if (!finda && finda_runout_valid) filament_runout();
         if (cmd == MMU_CMD_NONE) ready = true;
diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp
index 66f7ad15ed..02287a51b8 100644
--- a/Marlin/src/feature/pause.cpp
+++ b/Marlin/src/feature/pause.cpp
@@ -502,7 +502,7 @@ void show_continue_prompt(const bool is_reload) {
 
   ui.pause_show_message(is_reload ? PAUSE_MESSAGE_INSERT : PAUSE_MESSAGE_WAITING);
   SERIAL_ECHO_START();
-  SERIAL_ECHOF(is_reload ? F(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : F(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n"));
+  SERIAL_ECHO(is_reload ? F(_PMSG(STR_FILAMENT_CHANGE_INSERT) "\n") : F(_PMSG(STR_FILAMENT_CHANGE_WAIT) "\n"));
 }
 
 void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep_count/*=0*/ DXC_ARGS) {
diff --git a/Marlin/src/feature/powerloss.cpp b/Marlin/src/feature/powerloss.cpp
index af85a16448..a8dfadc1f6 100644
--- a/Marlin/src/feature/powerloss.cpp
+++ b/Marlin/src/feature/powerloss.cpp
@@ -613,14 +613,13 @@ void PrintJobRecovery::resume() {
 #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
 
   void PrintJobRecovery::debug(FSTR_P const prefix) {
-    DEBUG_ECHOF(prefix);
-    DEBUG_ECHOLNPGM(" Job Recovery Info...\nvalid_head:", info.valid_head, " valid_foot:", info.valid_foot);
+    DEBUG_ECHOLN(prefix, F(" Job Recovery Info...\nvalid_head:"), info.valid_head, F(" valid_foot:"), info.valid_foot);
     if (info.valid_head) {
       if (info.valid_head == info.valid_foot) {
         DEBUG_ECHOPGM("current_position: ");
         LOOP_LOGICAL_AXES(i) {
           if (i) DEBUG_CHAR(',');
-          DEBUG_DECIMAL(info.current_position[i]);
+          DEBUG_ECHO(info.current_position[i]);
         }
         DEBUG_EOL();
 
@@ -638,7 +637,7 @@ void PrintJobRecovery::resume() {
           DEBUG_ECHOPGM("home_offset: ");
           LOOP_NUM_AXES(i) {
             if (i) DEBUG_CHAR(',');
-            DEBUG_DECIMAL(info.home_offset[i]);
+            DEBUG_ECHO(info.home_offset[i]);
           }
           DEBUG_EOL();
         #endif
@@ -647,7 +646,7 @@ void PrintJobRecovery::resume() {
           DEBUG_ECHOPGM("position_shift: ");
           LOOP_NUM_AXES(i) {
             if (i) DEBUG_CHAR(',');
-            DEBUG_DECIMAL(info.position_shift[i]);
+            DEBUG_ECHO(info.position_shift[i]);
           }
           DEBUG_EOL();
         #endif
diff --git a/Marlin/src/feature/probe_temp_comp.cpp b/Marlin/src/feature/probe_temp_comp.cpp
index 2b362a2186..f640a9fd2f 100644
--- a/Marlin/src/feature/probe_temp_comp.cpp
+++ b/Marlin/src/feature/probe_temp_comp.cpp
@@ -87,14 +87,9 @@ void ProbeTempComp::print_offsets() {
   for (uint8_t s = 0; s < TSI_COUNT; ++s) {
     celsius_t temp = cali_info[s].start_temp;
     for (int16_t i = -1; i < cali_info[s].measurements; ++i) {
-      SERIAL_ECHOF(
-        TERN_(PTC_BED, s == TSI_BED ? F("Bed") :)
-        TERN_(PTC_HOTEND, s == TSI_EXT ? F("Extruder") :)
-        F("Probe")
-      );
-      SERIAL_ECHOLNPGM(
-        " temp: ", temp,
-        "C; Offset: ", i < 0 ? 0.0f : sensor_z_offsets[s][i], " um"
+      SERIAL_ECHOLN(
+        TERN_(PTC_BED, s == TSI_BED ? F("Bed") :) TERN_(PTC_HOTEND, s == TSI_EXT ? F("Extruder") :) F("Probe"),
+        F(" temp: "), temp, F("C; Offset: "), i < 0 ? 0.0f : sensor_z_offsets[s][i], F(" um")
       );
       temp += cali_info[s].temp_resolution;
     }
diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h
index 2b3516a8b4..a001459e9d 100644
--- a/Marlin/src/feature/runout.h
+++ b/Marlin/src/feature/runout.h
@@ -303,7 +303,7 @@ class FilamentSensorBase {
             static uint8_t was_out; // = 0
             if (out != TEST(was_out, s)) {
               TBI(was_out, s);
-              SERIAL_ECHOLNF(F("Filament Sensor "), AS_DIGIT(s), out ? F(" OUT") : F(" IN"));
+              SERIAL_ECHOLN(F("Filament Sensor "), AS_DIGIT(s), out ? F(" OUT") : F(" IN"));
             }
           #endif
         }
@@ -369,10 +369,10 @@ class FilamentSensorBase {
           if (ELAPSED(ms, t)) {
             t = millis() + 1000UL;
             for (uint8_t i = 0; i < NUM_RUNOUT_SENSORS; ++i)
-              SERIAL_ECHOF(i ? F(", ") : F("Runout remaining mm: "), mm_countdown.runout[i]);
+              SERIAL_ECHO(i ? F(", ") : F("Runout remaining mm: "), mm_countdown.runout[i]);
             #if ENABLED(FILAMENT_SWITCH_AND_MOTION)
               for (uint8_t i = 0; i < NUM_MOTION_SENSORS; ++i)
-                SERIAL_ECHOF(i ? F(", ") : F("Motion remaining mm: "), mm_countdown.motion[i]);
+                SERIAL_ECHO(i ? F(", ") : F("Motion remaining mm: "), mm_countdown.motion[i]);
             #endif
             SERIAL_EOL();
           }
diff --git a/Marlin/src/feature/stepper_driver_safety.cpp b/Marlin/src/feature/stepper_driver_safety.cpp
index d3fc161486..acdd695909 100644
--- a/Marlin/src/feature/stepper_driver_safety.cpp
+++ b/Marlin/src/feature/stepper_driver_safety.cpp
@@ -30,8 +30,7 @@ static uint32_t axis_plug_backward = 0;
 
 void stepper_driver_backward_error(FSTR_P const fstr) {
   SERIAL_ERROR_START();
-  SERIAL_ECHOF(fstr);
-  SERIAL_ECHOLNPGM(" driver is backward!");
+  SERIAL_ECHOLN(fstr, F(" driver is backward!"));
   ui.status_printf(2, F(S_FMT S_FMT), FTOP(fstr), GET_TEXT(MSG_DRIVER_BACKWARD));
 }
 
diff --git a/Marlin/src/feature/tmc_util.cpp b/Marlin/src/feature/tmc_util.cpp
index 095e14fe15..556a608914 100644
--- a/Marlin/src/feature/tmc_util.cpp
+++ b/Marlin/src/feature/tmc_util.cpp
@@ -562,7 +562,7 @@
   };
 
   template<class TMC>
-  static void print_vsense(TMC &st) { SERIAL_ECHOF(st.vsense() ? F("1=.18") : F("0=.325")); }
+  static void print_vsense(TMC &st) { SERIAL_ECHO(st.vsense() ? F("1=.18") : F("0=.325")); }
 
   #if HAS_DRIVER(TMC2130) || HAS_DRIVER(TMC5130)
     static void _tmc_status(TMC2130Stepper &st, const TMC_debug_enum i) {
@@ -680,7 +680,7 @@
       case TMC_ENABLED: serialprint_truefalse(st.isEnabled()); break;
       case TMC_CURRENT: SERIAL_ECHO(st.getMilliamps()); break;
       case TMC_RMS_CURRENT: SERIAL_ECHO(st.rms_current()); break;
-      case TMC_MAX_CURRENT: SERIAL_PRINT((float)st.rms_current() * 1.41, 0); break;
+      case TMC_MAX_CURRENT: SERIAL_ECHO(p_float_t(st.rms_current() * 1.41, 0)); break;
       case TMC_IRUN:
         SERIAL_ECHO(st.irun());
         SERIAL_ECHOPGM("/31");
@@ -728,12 +728,12 @@
         case TMC_ENABLED: serialprint_truefalse(st.isEnabled()); break;
         case TMC_CURRENT: SERIAL_ECHO(st.getMilliamps()); break;
         case TMC_RMS_CURRENT: SERIAL_ECHO(st.rms_current()); break;
-        case TMC_MAX_CURRENT: SERIAL_PRINT((float)st.rms_current() * 1.41, 0); break;
+        case TMC_MAX_CURRENT: SERIAL_ECHO(p_float_t(st.rms_current() * 1.41, 0)); break;
         case TMC_IRUN:
           SERIAL_ECHO(st.cs());
           SERIAL_ECHOPGM("/31");
           break;
-        case TMC_VSENSE: SERIAL_ECHOF(st.vsense() ? F("1=.165") : F("0=.310")); break;
+        case TMC_VSENSE: SERIAL_ECHO(st.vsense() ? F("1=.165") : F("0=.310")); break;
         case TMC_MICROSTEPS: SERIAL_ECHO(st.microsteps()); break;
         //case TMC_OTPW: serialprint_truefalse(st.otpw()); break;
         //case TMC_OTPW_TRIGGERED: serialprint_truefalse(st.getOTPW()); break;
@@ -1228,7 +1228,7 @@ static bool test_connection(TMC &st) {
     case 1: stat = F("HIGH"); break;
     case 2: stat = F("LOW"); break;
   }
-  SERIAL_ECHOLNF(stat);
+  SERIAL_ECHOLN(stat);
 
   return test_result;
 }
diff --git a/Marlin/src/feature/twibus.cpp b/Marlin/src/feature/twibus.cpp
index 4aedb4b5f3..5cfe9f9421 100644
--- a/Marlin/src/feature/twibus.cpp
+++ b/Marlin/src/feature/twibus.cpp
@@ -93,8 +93,7 @@ void TWIBus::send() {
 // static
 void TWIBus::echoprefix(uint8_t bytes, FSTR_P const pref, uint8_t adr) {
   SERIAL_ECHO_START();
-  SERIAL_ECHOF(pref);
-  SERIAL_ECHOPGM(": from:", adr, " bytes:", bytes, " data:");
+  SERIAL_ECHO(pref, F(": from:"), adr, F(" bytes:"), bytes, F(" data:"));
 }
 
 // static
diff --git a/Marlin/src/gcode/bedlevel/G35.cpp b/Marlin/src/gcode/bedlevel/G35.cpp
index 9c1ee472ec..a37e5623e7 100644
--- a/Marlin/src/gcode/bedlevel/G35.cpp
+++ b/Marlin/src/gcode/bedlevel/G35.cpp
@@ -100,19 +100,20 @@ void GcodeSuite::G35() {
   for (uint8_t i = 0; i < G35_PROBE_COUNT; ++i) {
     const float z_probed_height = probe.probe_at_point(tramming_points[i], PROBE_PT_RAISE);
     if (isnan(z_probed_height)) {
-      SERIAL_ECHOPGM("G35 failed at point ", i + 1, " (");
-      SERIAL_ECHOPGM_P((char *)pgm_read_ptr(&tramming_point_name[i]));
-      SERIAL_CHAR(')');
-      SERIAL_ECHOLNPGM_P(SP_X_STR, tramming_points[i].x, SP_Y_STR, tramming_points[i].y);
+      SERIAL_ECHO(
+        F("G35 failed at point "), i + 1, F(" ("), FPSTR(pgm_read_ptr(&tramming_point_name[i])), AS_CHAR(')'),
+        FPSTR(SP_X_STR), tramming_points[i].x, FPSTR(SP_Y_STR), tramming_points[i].y
+      );
       err_break = true;
       break;
     }
 
     if (DEBUGGING(LEVELING)) {
-      DEBUG_ECHOPGM("Probing point ", i + 1, " (");
-      DEBUG_ECHOF(FPSTR(pgm_read_ptr(&tramming_point_name[i])));
-      DEBUG_CHAR(')');
-      DEBUG_ECHOLNPGM_P(SP_X_STR, tramming_points[i].x, SP_Y_STR, tramming_points[i].y, SP_Z_STR, z_probed_height);
+      DEBUG_ECHOLN(
+        F("Probing point "), i + 1, F(" ("), FPSTR(pgm_read_ptr(&tramming_point_name[i])), AS_CHAR(')'),
+        FPSTR(SP_X_STR), tramming_points[i].x, FPSTR(SP_Y_STR), tramming_points[i].y,
+        FPSTR(SP_Z_STR), z_probed_height
+      );
     }
 
     z_measured[i] = z_probed_height;
diff --git a/Marlin/src/gcode/bedlevel/M420.cpp b/Marlin/src/gcode/bedlevel/M420.cpp
index 277f95b9ff..d870a4f430 100644
--- a/Marlin/src/gcode/bedlevel/M420.cpp
+++ b/Marlin/src/gcode/bedlevel/M420.cpp
@@ -248,7 +248,7 @@ void GcodeSuite::M420_report(const bool forReplay/*=true*/) {
   report_heading_etc(forReplay, F(
     TERN(MESH_BED_LEVELING, "Mesh Bed Leveling", TERN(AUTO_BED_LEVELING_UBL, "Unified Bed Leveling", "Auto Bed Leveling"))
   ));
-  SERIAL_ECHOF(
+  SERIAL_ECHO(
     F("  M420 S"), planner.leveling_active
     #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
       , FPSTR(SP_Z_STR), LINEAR_UNIT(planner.z_fade_height)
diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp
index ca02fc9765..3b669fbba2 100644
--- a/Marlin/src/gcode/bedlevel/abl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp
@@ -817,11 +817,11 @@ G29_TYPE GcodeSuite::G29() {
       abl.mean /= abl.abl_points;
 
       if (abl.verbose_level) {
-        SERIAL_ECHOPAIR_F("Eqn coefficients: a: ", plane_equation_coefficients.a, 8);
-        SERIAL_ECHOPAIR_F(" b: ", plane_equation_coefficients.b, 8);
-        SERIAL_ECHOPAIR_F(" d: ", plane_equation_coefficients.d, 8);
+        SERIAL_ECHOPGM("Eqn coefficients: a: ", p_float_t(plane_equation_coefficients.a, 8),
+                                        " b: ", p_float_t(plane_equation_coefficients.b, 8),
+                                        " d: ", p_float_t(plane_equation_coefficients.d, 8));
         if (abl.verbose_level > 2)
-          SERIAL_ECHOPAIR_F("\nMean of sampled points: ", abl.mean, 8);
+          SERIAL_ECHOPGM("\nMean of sampled points: ", p_float_t(abl.mean, 8));
         SERIAL_EOL();
       }
 
@@ -837,7 +837,7 @@ G29_TYPE GcodeSuite::G29() {
         float min_diff = 999;
 
         auto print_topo_map = [&](FSTR_P const title, const bool get_min) {
-          SERIAL_ECHOF(title);
+          SERIAL_ECHO(title);
           for (int8_t yy = abl.grid_points.y - 1; yy >= 0; yy--) {
             for (uint8_t xx = 0; xx < abl.grid_points.x; ++xx) {
               const int ind = abl.indexIntoAB[xx][yy];
@@ -848,7 +848,7 @@ G29_TYPE GcodeSuite::G29() {
               const float subval = get_min ? abl.mean : tmp.z + min_diff,
                             diff = abl.eqnBVector[ind] - subval;
               SERIAL_CHAR(' '); if (diff >= 0.0) SERIAL_CHAR('+');   // Include + for column alignment
-              SERIAL_ECHO_F(diff, 5);
+              SERIAL_ECHO(p_float_t(diff, 5));
             } // xx
             SERIAL_EOL();
           } // yy
diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp
index fde640fe98..ab7b03961c 100644
--- a/Marlin/src/gcode/calibrate/G28.cpp
+++ b/Marlin/src/gcode/calibrate/G28.cpp
@@ -274,7 +274,7 @@ void GcodeSuite::G28() {
 
     #if HAS_HOMING_CURRENT
       auto debug_current = [](FSTR_P const s, const int16_t a, const int16_t b) {
-        DEBUG_ECHOF(s); DEBUG_ECHOLNPGM(" current: ", a, " -> ", b);
+        DEBUG_ECHOLN(s, F(" current: "), a, F(" -> "), b);
       };
       #if HAS_CURRENT_HOME(X)
         const int16_t tmc_save_current_X = stepperX.getMilliamps();
diff --git a/Marlin/src/gcode/calibrate/G33.cpp b/Marlin/src/gcode/calibrate/G33.cpp
index 226570acce..ba6cccffab 100644
--- a/Marlin/src/gcode/calibrate/G33.cpp
+++ b/Marlin/src/gcode/calibrate/G33.cpp
@@ -92,8 +92,7 @@ void ac_cleanup(TERN_(HAS_MULTI_HOTEND, const uint8_t old_tool_index)) {
 }
 
 void print_signed_float(FSTR_P const prefix, const_float_t f) {
-  SERIAL_ECHOPGM("  ");
-  SERIAL_ECHOF(prefix, AS_CHAR(':'));
+  SERIAL_ECHO(F("  "), prefix, AS_CHAR(':'));
   serial_offset(f);
 }
 
@@ -636,7 +635,7 @@ void GcodeSuite::G33() {
           else
         #endif
           {
-            SERIAL_ECHOPAIR_F("std dev:", zero_std_dev_min, 3);
+            SERIAL_ECHOPGM("std dev:", p_float_t(zero_std_dev_min, 3));
           }
         SERIAL_EOL();
         char mess[21];
@@ -657,7 +656,7 @@ void GcodeSuite::G33() {
           strcpy_P(mess, PSTR("No convergence"));
         SERIAL_ECHO(mess);
         SERIAL_ECHO_SP(32);
-        SERIAL_ECHOLNPAIR_F("std dev:", zero_std_dev, 3);
+        SERIAL_ECHOLNPGM("std dev:", p_float_t(zero_std_dev, 3));
         ui.set_status(mess);
         if (verbose_level > 1)
           print_calibration_settings(_endstop_results, _angle_results);
@@ -665,9 +664,9 @@ void GcodeSuite::G33() {
     }
     else { // dry run
       FSTR_P const enddryrun = F("End DRY-RUN");
-      SERIAL_ECHOF(enddryrun);
+      SERIAL_ECHO(enddryrun);
       SERIAL_ECHO_SP(35);
-      SERIAL_ECHOLNPAIR_F("std dev:", zero_std_dev, 3);
+      SERIAL_ECHOLNPGM("std dev:", p_float_t(zero_std_dev, 3));
 
       char mess[21];
       strcpy_P(mess, FTOP(enddryrun));
diff --git a/Marlin/src/gcode/calibrate/G34_M422.cpp b/Marlin/src/gcode/calibrate/G34_M422.cpp
index 7650443de8..39f3d2d5df 100644
--- a/Marlin/src/gcode/calibrate/G34_M422.cpp
+++ b/Marlin/src/gcode/calibrate/G34_M422.cpp
@@ -411,7 +411,7 @@ void GcodeSuite::G34() {
         SERIAL_ECHOLNPGM("G34 aborted.");
       else {
         SERIAL_ECHOLNPGM("Did ", iteration + (iteration != z_auto_align_iterations), " of ", z_auto_align_iterations);
-        SERIAL_ECHOLNPAIR_F("Accuracy: ", z_maxdiff);
+        SERIAL_ECHOLNPGM("Accuracy: ", p_float_t(z_maxdiff, 2));
       }
 
       // Stow the probe because the last call to probe.probe_at_point(...)
@@ -501,8 +501,7 @@ void GcodeSuite::M422() {
   }
 
   if (!WITHIN(position_index, 1, NUM_Z_STEPPERS)) {
-    SERIAL_ECHOF(err_string);
-    SERIAL_ECHOLNPGM(" index invalid (1.." STRINGIFY(NUM_Z_STEPPERS) ").");
+    SERIAL_ECHOLN(err_string, F(" index invalid (1.." STRINGIFY(NUM_Z_STEPPERS) ")."));
     return;
   }
 
diff --git a/Marlin/src/gcode/calibrate/G76_M871.cpp b/Marlin/src/gcode/calibrate/G76_M871.cpp
index 34b72ecdf3..6fe3dd89cf 100644
--- a/Marlin/src/gcode/calibrate/G76_M871.cpp
+++ b/Marlin/src/gcode/calibrate/G76_M871.cpp
@@ -113,7 +113,7 @@
       if (isnan(measured_z))
         SERIAL_ECHOLNPGM("!Received NAN. Aborting.");
       else {
-        SERIAL_ECHOLNPAIR_F("Measured: ", measured_z);
+        SERIAL_ECHOLNPGM("Measured: ", p_float_t(measured_z, 2));
         if (targ == ProbeTempComp::cali_info[sid].start_temp)
           ptc.prepare_new_calibration(measured_z);
         else
diff --git a/Marlin/src/gcode/calibrate/M100.cpp b/Marlin/src/gcode/calibrate/M100.cpp
index 3791c69f88..c05fe12fc3 100644
--- a/Marlin/src/gcode/calibrate/M100.cpp
+++ b/Marlin/src/gcode/calibrate/M100.cpp
@@ -183,7 +183,7 @@ inline int32_t count_test_bytes(const char * const start_free_memory) {
   }
 
   void M100_dump_routine(FSTR_P const title, const char * const start, const uintptr_t size) {
-    SERIAL_ECHOLNF(title);
+    SERIAL_ECHOLN(title);
     //
     // Round the start and end locations to produce full lines of output
     //
@@ -197,7 +197,7 @@ inline int32_t count_test_bytes(const char * const start_free_memory) {
 #endif // M100_FREE_MEMORY_DUMPER
 
 inline int check_for_free_memory_corruption(FSTR_P const title) {
-  SERIAL_ECHOF(title);
+  SERIAL_ECHO(title);
 
   char *start_free_memory = free_memory_start, *end_free_memory = free_memory_end;
   int n = end_free_memory - start_free_memory;
diff --git a/Marlin/src/gcode/calibrate/M48.cpp b/Marlin/src/gcode/calibrate/M48.cpp
index 2748d4e7ba..9f285b8b8a 100644
--- a/Marlin/src/gcode/calibrate/M48.cpp
+++ b/Marlin/src/gcode/calibrate/M48.cpp
@@ -126,15 +126,13 @@ void GcodeSuite::M48() {
 
   auto dev_report = [](const bool verbose, const_float_t mean, const_float_t sigma, const_float_t min, const_float_t max, const bool final=false) {
     if (verbose) {
-      SERIAL_ECHOPAIR_F("Mean: ", mean, 6);
-      if (!final) SERIAL_ECHOPAIR_F(" Sigma: ", sigma, 6);
-      SERIAL_ECHOPAIR_F(" Min: ", min, 3);
-      SERIAL_ECHOPAIR_F(" Max: ", max, 3);
-      SERIAL_ECHOPAIR_F(" Range: ", max-min, 3);
+      SERIAL_ECHOPGM("Mean: ", p_float_t(mean, 6));
+      if (!final) SERIAL_ECHOPGM(" Sigma: ", p_float_t(sigma, 6));
+      SERIAL_ECHOPGM(" Min: ", p_float_t(min, 3), " Max: ", p_float_t(max, 3), " Range: ", p_float_t(max-min, 3));
       if (final) SERIAL_EOL();
     }
     if (final) {
-      SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6);
+      SERIAL_ECHOLNPGM("Standard Deviation: ", p_float_t(sigma, 6));
       SERIAL_EOL();
     }
   };
@@ -207,7 +205,7 @@ void GcodeSuite::M48() {
             while (!probe.can_reach(next_pos)) {
               next_pos *= 0.8f;
               if (verbose_level > 3)
-                SERIAL_ECHOLNPGM_P(PSTR("Moving inward: X"), next_pos.x, SP_Y_STR, next_pos.y);
+                SERIAL_ECHOLN(F("Moving inward: X"), next_pos.x, FPSTR(SP_Y_STR), next_pos.y);
             }
           #elif HAS_ENDSTOPS
             // For a rectangular bed just keep the probe in bounds
@@ -216,7 +214,7 @@ void GcodeSuite::M48() {
           #endif
 
           if (verbose_level > 3)
-            SERIAL_ECHOLNPGM_P(PSTR("Going to: X"), next_pos.x, SP_Y_STR, next_pos.y);
+            SERIAL_ECHOLN(F("Going to: X"), next_pos.x, FPSTR(SP_Y_STR), next_pos.y);
 
           do_blocking_move_to_xy(next_pos);
         } // n_legs loop
@@ -247,10 +245,7 @@ void GcodeSuite::M48() {
       sigma = SQRT(dev_sum / (n + 1));
 
       if (verbose_level > 1) {
-        SERIAL_ECHO(n + 1);
-        SERIAL_ECHOPGM(" of ", n_samples);
-        SERIAL_ECHOPAIR_F(": z: ", pz, 3);
-        SERIAL_CHAR(' ');
+        SERIAL_ECHO(n + 1, F(" of "), n_samples, F(": z: "), p_float_t(pz, 3), AS_CHAR(' '));
         dev_report(verbose_level > 2, mean, sigma, min, max);
         SERIAL_EOL();
       }
diff --git a/Marlin/src/gcode/calibrate/M852.cpp b/Marlin/src/gcode/calibrate/M852.cpp
index 6c661dcd61..6d3c004548 100644
--- a/Marlin/src/gcode/calibrate/M852.cpp
+++ b/Marlin/src/gcode/calibrate/M852.cpp
@@ -93,11 +93,9 @@ void GcodeSuite::M852() {
 
 void GcodeSuite::M852_report(const bool forReplay/*=true*/) {
   report_heading_etc(forReplay, F(STR_SKEW_FACTOR));
-  SERIAL_ECHOPAIR_F("  M852 I", planner.skew_factor.xy, 6);
+  SERIAL_ECHOPGM("  M852 I", p_float_t(planner.skew_factor.xy, 6));
   #if ENABLED(SKEW_CORRECTION_FOR_Z)
-    SERIAL_ECHOPAIR_F(" J", planner.skew_factor.xz, 6);
-    SERIAL_ECHOPAIR_F(" K", planner.skew_factor.yz, 6);
-    SERIAL_ECHOLNPGM(" ; XY, XZ, YZ");
+    SERIAL_ECHOLNPGM(" J", p_float_t(planner.skew_factor.xz, 6), " K", p_float_t(planner.skew_factor.yz, 6), " ; XY, XZ, YZ");
   #else
     SERIAL_ECHOLNPGM(" ; XY");
   #endif
diff --git a/Marlin/src/gcode/config/M218.cpp b/Marlin/src/gcode/config/M218.cpp
index d645685701..ff63dcaf34 100644
--- a/Marlin/src/gcode/config/M218.cpp
+++ b/Marlin/src/gcode/config/M218.cpp
@@ -66,12 +66,12 @@ void GcodeSuite::M218_report(const bool forReplay/*=true*/) {
   report_heading_etc(forReplay, F(STR_HOTEND_OFFSETS));
   for (uint8_t e = 1; e < HOTENDS; ++e) {
     report_echo_start(forReplay);
-    SERIAL_ECHOPGM_P(
+    SERIAL_ECHOLNPGM_P(
       PSTR("  M218 T"), e,
       SP_X_STR, LINEAR_UNIT(hotend_offset[e].x),
-      SP_Y_STR, LINEAR_UNIT(hotend_offset[e].y)
+      SP_Y_STR, LINEAR_UNIT(hotend_offset[e].y),
+      SP_Z_STR, p_float_t(LINEAR_UNIT(hotend_offset[e].z), 3)
     );
-    SERIAL_ECHOLNPAIR_F_P(SP_Z_STR, LINEAR_UNIT(hotend_offset[e].z), 3);
   }
 }
 
diff --git a/Marlin/src/gcode/config/M302.cpp b/Marlin/src/gcode/config/M302.cpp
index 12408c8987..26e5a468ca 100644
--- a/Marlin/src/gcode/config/M302.cpp
+++ b/Marlin/src/gcode/config/M302.cpp
@@ -59,9 +59,7 @@ void GcodeSuite::M302() {
   else if (!seen_S) {
     // Report current state
     SERIAL_ECHO_START();
-    SERIAL_ECHOPGM("Cold extrudes are ");
-    SERIAL_ECHOF(thermalManager.allow_cold_extrude ? F("en") : F("dis"));
-    SERIAL_ECHOLNPGM("abled (min temp ", thermalManager.extrude_min_temp, "C)");
+    SERIAL_ECHOLN(F("Cold extrudes are "), thermalManager.allow_cold_extrude ? F("en") : F("dis"), F("abled (min temp "), thermalManager.extrude_min_temp, F("C)"));
   }
 }
 
diff --git a/Marlin/src/gcode/config/M43.cpp b/Marlin/src/gcode/config/M43.cpp
index 7daf8afab8..7657aadc6d 100644
--- a/Marlin/src/gcode/config/M43.cpp
+++ b/Marlin/src/gcode/config/M43.cpp
@@ -294,9 +294,7 @@ void GcodeSuite::M43() {
   // 'E' Enable or disable endstop monitoring and return
   if (parser.seen('E')) {
     endstops.monitor_flag = parser.value_bool();
-    SERIAL_ECHOPGM("endstop monitor ");
-    SERIAL_ECHOF(endstops.monitor_flag ? F("en") : F("dis"));
-    SERIAL_ECHOLNPGM("abled");
+    SERIAL_ECHOLN(F("endstop monitor "), endstops.monitor_flag ? F("en") : F("dis"), F("abled"));
     return;
   }
 
diff --git a/Marlin/src/gcode/config/M92.cpp b/Marlin/src/gcode/config/M92.cpp
index e848665e6b..67f55e2232 100644
--- a/Marlin/src/gcode/config/M92.cpp
+++ b/Marlin/src/gcode/config/M92.cpp
@@ -82,7 +82,7 @@ void GcodeSuite::M92() {
       if (wanted) {
         const float best = uint16_t(wanted / z_full_step_mm) * z_full_step_mm;
         SERIAL_ECHOPGM(", best:[", best);
-        if (best != wanted) { SERIAL_CHAR(','); SERIAL_DECIMAL(best + z_full_step_mm); }
+        if (best != wanted) { SERIAL_ECHO(AS_CHAR(','), best + z_full_step_mm); }
         SERIAL_CHAR(']');
       }
       SERIAL_ECHOLNPGM(" }");
diff --git a/Marlin/src/gcode/control/M80_M81.cpp b/Marlin/src/gcode/control/M80_M81.cpp
index 94dd5e3dd9..2d3e407446 100644
--- a/Marlin/src/gcode/control/M80_M81.cpp
+++ b/Marlin/src/gcode/control/M80_M81.cpp
@@ -48,7 +48,7 @@
 
     // S: Report the current power supply state and exit
     if (parser.seen('S')) {
-      SERIAL_ECHOF(powerManager.psu_on ? F("PS:1\n") : F("PS:0\n"));
+      SERIAL_ECHO(powerManager.psu_on ? F("PS:1\n") : F("PS:0\n"));
       return;
     }
 
diff --git a/Marlin/src/gcode/feature/advance/M900.cpp b/Marlin/src/gcode/feature/advance/M900.cpp
index 8c0da41801..28cc4f8047 100644
--- a/Marlin/src/gcode/feature/advance/M900.cpp
+++ b/Marlin/src/gcode/feature/advance/M900.cpp
@@ -120,8 +120,7 @@ void GcodeSuite::M900() {
         EXTRUDER_LOOP() {
           const bool slot = TEST(lin_adv_slot, e);
           SERIAL_ECHOLNPGM("Advance T", e, " S", slot, " K", planner.extruder_advance_K[e],
-                            "(S", !slot, " K", other_extruder_advance_K[e], ")");
-          SERIAL_EOL();
+                           "(S", !slot, " K", other_extruder_advance_K[e], ")");
         }
       #endif
 
@@ -132,10 +131,7 @@ void GcodeSuite::M900() {
         SERIAL_ECHOLNPGM("Advance K=", planner.extruder_advance_K[0]);
       #else
         SERIAL_ECHOPGM("Advance K");
-        EXTRUDER_LOOP() {
-          SERIAL_CHAR(' ', '0' + e, ':');
-          SERIAL_DECIMAL(planner.extruder_advance_K[e]);
-        }
+        EXTRUDER_LOOP() SERIAL_ECHO(AS_CHAR(' '), AS_CHAR('0' + e), AS_CHAR(':'), planner.extruder_advance_K[e]);
         SERIAL_EOL();
       #endif
 
diff --git a/Marlin/src/gcode/feature/ft_motion/M493.cpp b/Marlin/src/gcode/feature/ft_motion/M493.cpp
index 9469f1575b..f34c6c1bd7 100644
--- a/Marlin/src/gcode/feature/ft_motion/M493.cpp
+++ b/Marlin/src/gcode/feature/ft_motion/M493.cpp
@@ -74,28 +74,18 @@ void say_shaping() {
 
     #if HAS_X_AXIS
       SERIAL_ECHO_TERNARY(dynamic, "X/A ", "base dynamic", "static", " compensator frequency: ");
-      SERIAL_ECHO_F(fxdTiCtrl.cfg.baseFreq[X_AXIS], 2);
-      SERIAL_ECHOPGM("Hz");
+      SERIAL_ECHO(p_float_t(fxdTiCtrl.cfg.baseFreq[X_AXIS], 2), F("Hz"));
       #if HAS_DYNAMIC_FREQ
-        if (dynamic) {
-          SERIAL_ECHOPGM(" scaling: ");
-          SERIAL_ECHO_F(fxdTiCtrl.cfg.dynFreqK[X_AXIS], 8);
-          serial_ternary(F("Hz/"), z_based, F("mm"), F("g"));
-        }
+        if (dynamic) SERIAL_ECHO(" scaling: ", p_float_t(fxdTiCtrl.cfg.dynFreqK[X_AXIS], 8), F("Hz/"), z_based ? F("mm") : F("g"));
       #endif
       SERIAL_EOL();
     #endif
 
     #if HAS_Y_AXIS
       SERIAL_ECHO_TERNARY(dynamic, "Y/B ", "base dynamic", "static", " compensator frequency: ");
-      SERIAL_ECHO_F(fxdTiCtrl.cfg.baseFreq[Y_AXIS], 2);
-      SERIAL_ECHOLNPGM(" Hz");
+      SERIAL_ECHO(p_float_t(fxdTiCtrl.cfg.baseFreq[Y_AXIS], 2), F(" Hz"));
       #if HAS_DYNAMIC_FREQ
-        if (dynamic) {
-          SERIAL_ECHOPGM(" scaling: ");
-          SERIAL_ECHO_F(fxdTiCtrl.cfg.dynFreqK[Y_AXIS], 8);
-          serial_ternary(F("Hz/"), z_based, F("mm"), F("g"));
-        }
+        if (dynamic) SERIAL_ECHO(F(" scaling: "), p_float_t(fxdTiCtrl.cfg.dynFreqK[Y_AXIS], 8), F("Hz/"), z_based ? F("mm") : F("g"));
       #endif
       SERIAL_EOL();
     #endif
@@ -103,10 +93,8 @@ void say_shaping() {
 
   #if HAS_EXTRUDERS
     SERIAL_ECHO_TERNARY(fxdTiCtrl.cfg.linearAdvEna, "Linear Advance ", "en", "dis", "abled");
-    SERIAL_ECHOPGM(". Gain: "); SERIAL_ECHO_F(fxdTiCtrl.cfg.linearAdvK, 5);
-    SERIAL_EOL();
+    SERIAL_ECHOLN(F(". Gain: "), p_float_t(fxdTiCtrl.cfg.linearAdvK, 5));
   #endif
-
 }
 
 void GcodeSuite::M493_report(const bool forReplay/*=true*/) {
diff --git a/Marlin/src/gcode/feature/network/M552-M554.cpp b/Marlin/src/gcode/feature/network/M552-M554.cpp
index ca7ddd0d36..7633ec1fd5 100644
--- a/Marlin/src/gcode/feature/network/M552-M554.cpp
+++ b/Marlin/src/gcode/feature/network/M552-M554.cpp
@@ -63,8 +63,7 @@ void ip_report(const uint16_t cmd, FSTR_P const post, const IPAddress &ipo) {
     SERIAL_ECHO(ipo[i]);
     if (i < 3) SERIAL_CHAR('.');
   }
-  SERIAL_ECHOPGM(" ; ");
-  SERIAL_ECHOLNF(post);
+  SERIAL_ECHOLN(F(" ; "), post);
 }
 
 /**
diff --git a/Marlin/src/gcode/feature/pause/G61.cpp b/Marlin/src/gcode/feature/pause/G61.cpp
index 0efcfbf208..423632e72e 100644
--- a/Marlin/src/gcode/feature/pause/G61.cpp
+++ b/Marlin/src/gcode/feature/pause/G61.cpp
@@ -87,8 +87,7 @@ void GcodeSuite::G61() {
         destination[i] = parser.seen(AXIS_CHAR(i))
           ? stored_position[slot][i] + parser.value_axis_units((AxisEnum)i)
           : current_position[i];
-        DEBUG_CHAR(' ', AXIS_CHAR(i));
-        DEBUG_ECHO_F(destination[i]);
+        DEBUG_ECHO(AS_CHAR(' '), AS_CHAR(AXIS_CHAR(i)), p_float_t(destination[i], 2));
       }
       DEBUG_EOL();
       // Move to the saved position
diff --git a/Marlin/src/gcode/feature/powerloss/M1000.cpp b/Marlin/src/gcode/feature/powerloss/M1000.cpp
index 1629a154bc..e874c19d5e 100644
--- a/Marlin/src/gcode/feature/powerloss/M1000.cpp
+++ b/Marlin/src/gcode/feature/powerloss/M1000.cpp
@@ -47,8 +47,7 @@ void menu_job_recovery();
 inline void plr_error(FSTR_P const prefix) {
   #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
     DEBUG_ECHO_START();
-    DEBUG_ECHOF(prefix);
-    DEBUG_ECHOLNPGM(" Job Recovery Data");
+    DEBUG_ECHOLN(prefix, F(" Job Recovery Data"));
   #else
     UNUSED(prefix);
   #endif
diff --git a/Marlin/src/gcode/feature/powerloss/M413.cpp b/Marlin/src/gcode/feature/powerloss/M413.cpp
index f6d82b0ad9..5e508d4f28 100644
--- a/Marlin/src/gcode/feature/powerloss/M413.cpp
+++ b/Marlin/src/gcode/feature/powerloss/M413.cpp
@@ -50,8 +50,8 @@ void GcodeSuite::M413() {
     if (parser.seen_test('D')) recovery.debug(F("M413"));
     if (parser.seen_test('O')) recovery._outage(true);
     if (parser.seen_test('C')) (void)recovery.check();
-    if (parser.seen_test('E')) SERIAL_ECHOF(recovery.exists() ? F("PLR Exists\n") : F("No PLR\n"));
-    if (parser.seen_test('V')) SERIAL_ECHOF(recovery.valid() ? F("Valid\n") : F("Invalid\n"));
+    if (parser.seen_test('E')) SERIAL_ECHO(recovery.exists() ? F("PLR Exists\n") : F("No PLR\n"));
+    if (parser.seen_test('V')) SERIAL_ECHO(recovery.valid() ? F("Valid\n") : F("Invalid\n"));
   #endif
 }
 
diff --git a/Marlin/src/gcode/feature/trinamic/M569.cpp b/Marlin/src/gcode/feature/trinamic/M569.cpp
index 50ac5c7468..6f1af7b681 100644
--- a/Marlin/src/gcode/feature/trinamic/M569.cpp
+++ b/Marlin/src/gcode/feature/trinamic/M569.cpp
@@ -35,8 +35,7 @@
 template<typename TMC>
 void tmc_say_stealth_status(TMC &st) {
   st.printLabel();
-  SERIAL_ECHOPGM(" driver mode:\t");
-  SERIAL_ECHOLNF(st.get_stealthChop() ? F("stealthChop") : F("spreadCycle"));
+  SERIAL_ECHOLN(F(" driver mode:\t"), st.get_stealthChop() ? F("stealthChop") : F("spreadCycle"));
 }
 template<typename TMC>
 void tmc_set_stealthChop(TMC &st, const bool enable) {
@@ -161,10 +160,7 @@ void GcodeSuite::M569_report(const bool forReplay/*=true*/) {
   auto say_M569 = [](const bool forReplay, FSTR_P const etc=nullptr, const bool eol=false) {
     if (!forReplay) SERIAL_ECHO_START();
     SERIAL_ECHOPGM("  M569 S1");
-    if (etc) {
-      SERIAL_CHAR(' ');
-      SERIAL_ECHOF(etc);
-    }
+    if (etc) SERIAL_ECHO(AS_CHAR(' '), etc);
     if (eol) SERIAL_EOL();
   };
 
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index 01b48a4af9..cedcf97c51 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -105,8 +105,7 @@ void GcodeSuite::report_heading(const bool forReplay, FSTR_P const fstr, const b
   if (forReplay) return;
   if (fstr) {
     SERIAL_ECHO_START();
-    SERIAL_ECHOPGM("; ");
-    SERIAL_ECHOF(fstr);
+    SERIAL_ECHO(F("; "), fstr);
   }
   if (eol) { SERIAL_CHAR(':'); SERIAL_EOL(); }
 }
@@ -1120,7 +1119,7 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
 
   if (!no_ok) queue.ok_to_send();
 
-  SERIAL_OUT(msgDone); // Call the msgDone serial hook to signal command processing done
+  SERIAL_IMPL.msgDone(); // Call the msgDone serial hook to signal command processing done
 }
 
 #if ENABLED(M100_FREE_MEMORY_DUMPER)
diff --git a/Marlin/src/gcode/geometry/G17-G19.cpp b/Marlin/src/gcode/geometry/G17-G19.cpp
index fbac7470ca..312b89286c 100644
--- a/Marlin/src/gcode/geometry/G17-G19.cpp
+++ b/Marlin/src/gcode/geometry/G17-G19.cpp
@@ -29,7 +29,7 @@
 inline void report_workspace_plane() {
   SERIAL_ECHO_START();
   SERIAL_ECHOPGM("Workspace Plane ");
-  SERIAL_ECHOF(
+  SERIAL_ECHO(
       gcode.workspace_plane == GcodeSuite::PLANE_YZ ? F("YZ\n")
     : gcode.workspace_plane == GcodeSuite::PLANE_ZX ? F("ZX\n")
                                                     : F("XY\n")
diff --git a/Marlin/src/gcode/probe/G30.cpp b/Marlin/src/gcode/probe/G30.cpp
index 4c044af9d6..43fc27bfc1 100644
--- a/Marlin/src/gcode/probe/G30.cpp
+++ b/Marlin/src/gcode/probe/G30.cpp
@@ -100,7 +100,7 @@ void GcodeSuite::G30() {
     report_current_position();
   }
   else {
-    SERIAL_ECHOLNF(GET_EN_TEXT_F(MSG_ZPROBE_OUT));
+    SERIAL_ECHOLN(GET_EN_TEXT_F(MSG_ZPROBE_OUT));
     LCD_MESSAGE(MSG_ZPROBE_OUT);
   }
 
diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp
index 967ab76897..9ce28f781c 100644
--- a/Marlin/src/gcode/queue.cpp
+++ b/Marlin/src/gcode/queue.cpp
@@ -324,7 +324,7 @@ inline int read_serial(const serial_index_t index) { return SERIAL_IMPL.read(ind
 void GCodeQueue::gcode_line_error(FSTR_P const ferr, const serial_index_t serial_ind) {
   PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
   SERIAL_ERROR_START();
-  SERIAL_ECHOLNF(ferr, serial_state[serial_ind.index].last_N);
+  SERIAL_ECHOLN(ferr, serial_state[serial_ind.index].last_N);
   while (read_serial(serial_ind) != -1) { /* nada */ } // Clear out the RX buffer. Why don't use flush here ?
   flush_and_request_resend(serial_ind);
   serial_state[serial_ind.index].count = 0;
diff --git a/Marlin/src/gcode/stats/M31.cpp b/Marlin/src/gcode/stats/M31.cpp
index 1a1c13ba2f..a76ec7ee4d 100644
--- a/Marlin/src/gcode/stats/M31.cpp
+++ b/Marlin/src/gcode/stats/M31.cpp
@@ -33,7 +33,7 @@ void GcodeSuite::M31() {
   char buffer[22];
   duration_t(print_job_timer.duration()).toString(buffer);
 
-  ui.set_status(buffer, ENABLED(DWIN_LCD_PROUI));
+  ui.set_status(buffer, ENABLED(DWIN_LCD_PROUI)); // No expire on ProUI
 
   SERIAL_ECHO_MSG("Print time: ", buffer);
 }
diff --git a/Marlin/src/gcode/temp/M306.cpp b/Marlin/src/gcode/temp/M306.cpp
index 7d2d94952f..2830a77ab4 100644
--- a/Marlin/src/gcode/temp/M306.cpp
+++ b/Marlin/src/gcode/temp/M306.cpp
@@ -92,16 +92,16 @@ void GcodeSuite::M306_report(const bool forReplay/*=true*/) {
   HOTEND_LOOP() {
     report_echo_start(forReplay);
     MPC_t &mpc = thermalManager.temp_hotend[e].mpc;
-    SERIAL_ECHOPGM("  M306 E", e);
-    SERIAL_ECHOPAIR_F(" P", mpc.heater_power, 2);
-    SERIAL_ECHOPAIR_F(" C", mpc.block_heat_capacity, 2);
-    SERIAL_ECHOPAIR_F(" R", mpc.sensor_responsiveness, 4);
-    SERIAL_ECHOPAIR_F(" A", mpc.ambient_xfer_coeff_fan0, 4);
+    SERIAL_ECHOPGM("  M306 E", e,
+                         " P", p_float_t(mpc.heater_power, 2),
+                         " C", p_float_t(mpc.block_heat_capacity, 2),
+                         " R", p_float_t(mpc.sensor_responsiveness, 4),
+                         " A", p_float_t(mpc.ambient_xfer_coeff_fan0, 4)
+    );
     #if ENABLED(MPC_INCLUDE_FAN)
-      SERIAL_ECHOPAIR_F(" F", mpc.fanCoefficient(), 4);
+      SERIAL_ECHOPGM(" F", p_float_t(mpc.fanCoefficient(), 4));
     #endif
-    SERIAL_ECHOPAIR_F(" H", mpc.filament_heat_capacity_permm, 4);
-    SERIAL_EOL();
+    SERIAL_ECHOLNPGM(" H", p_float_t(mpc.filament_heat_capacity_permm, 4));
   }
 }
 
diff --git a/Marlin/src/gcode/units/M149.cpp b/Marlin/src/gcode/units/M149.cpp
index a04247cbcb..43091b6710 100644
--- a/Marlin/src/gcode/units/M149.cpp
+++ b/Marlin/src/gcode/units/M149.cpp
@@ -38,8 +38,7 @@ void GcodeSuite::M149() {
 
 void GcodeSuite::M149_report(const bool forReplay/*=true*/) {
   report_heading_etc(forReplay, F(STR_TEMPERATURE_UNITS));
-  SERIAL_ECHOPGM("  M149 ", AS_CHAR(parser.temp_units_code()), " ; Units in ");
-  SERIAL_ECHOLNF(parser.temp_units_name());
+  SERIAL_ECHOLN(F("  M149 "), AS_CHAR(parser.temp_units_code()), F(" ; Units in "), parser.temp_units_name());
 }
 
 #endif // TEMPERATURE_UNITS_SUPPORT
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index 6d2bbd6edf..bc33c20f41 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -1815,6 +1815,9 @@
 #if ANY_AXIS_HAS(SW_SERIAL)
   #define HAS_TMC_SW_SERIAL 1
 #endif
+#ifndef SERIAL_FLOAT_PRECISION
+  #define SERIAL_FLOAT_PRECISION 2
+#endif
 
 #if DISABLED(SENSORLESS_HOMING)
   #undef SENSORLESS_BACKOFF_MM
diff --git a/Marlin/src/lcd/e3v2/jyersui/dwin.cpp b/Marlin/src/lcd/e3v2/jyersui/dwin.cpp
index 1ef9ee8517..7b83e0171f 100644
--- a/Marlin/src/lcd/e3v2/jyersui/dwin.cpp
+++ b/Marlin/src/lcd/e3v2/jyersui/dwin.cpp
@@ -295,29 +295,17 @@ private:
 
         matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1));
         GRID_LOOP(i, j) {
-          float mx = bedlevel.get_mesh_x(i),
-                my = bedlevel.get_mesh_y(j),
-                mz = bedlevel.z_values[i][j];
+          float mx = bedlevel.get_mesh_x(i), my = bedlevel.get_mesh_y(j), mz = bedlevel.z_values[i][j];
 
           if (DEBUGGING(LEVELING)) {
-            DEBUG_ECHOPAIR_F("before rotation = [", mx, 7);
-            DEBUG_CHAR(',');
-            DEBUG_ECHO_F(my, 7);
-            DEBUG_CHAR(',');
-            DEBUG_ECHO_F(mz, 7);
-            DEBUG_ECHOPGM("]   ---> ");
+            DEBUG_ECHOLN(F("before rotation = ["), p_float_t(mx, 7), AS_CHAR(','), p_float_t(my, 7), AS_CHAR(','), p_float_t(mz, 7), F("]   ---> "));
             DEBUG_DELAY(20);
           }
 
           rotation.apply_rotation_xyz(mx, my, mz);
 
           if (DEBUGGING(LEVELING)) {
-            DEBUG_ECHOPAIR_F("after rotation = [", mx, 7);
-            DEBUG_CHAR(',');
-            DEBUG_ECHO_F(my, 7);
-            DEBUG_CHAR(',');
-            DEBUG_ECHO_F(mz, 7);
-            DEBUG_ECHOLNPGM("]");
+            DEBUG_ECHOLN(F("after rotation = ["), p_float_t(mx, 7), AS_CHAR(','), p_float_t(my, 7), AS_CHAR(','), p_float_t(mz, 7), AS_CHAR(']'));
             DEBUG_DELAY(20);
           }
 
diff --git a/Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp b/Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp
index 48e07cc207..69721e4e77 100644
--- a/Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp
+++ b/Marlin/src/lcd/e3v2/proui/bedlevel_tools.cpp
@@ -104,29 +104,17 @@ char cmd[MAX_CMD_SIZE+16], str_1[16], str_2[16], str_3[16];
 
     matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1));
     GRID_LOOP(i, j) {
-      float mx = bedlevel.get_mesh_x(i),
-            my = bedlevel.get_mesh_y(j),
-            mz = bedlevel.z_values[i][j];
+      float mx = bedlevel.get_mesh_x(i), my = bedlevel.get_mesh_y(j), mz = bedlevel.z_values[i][j];
 
       if (DEBUGGING(LEVELING)) {
-        DEBUG_ECHOPAIR_F("before rotation = [", mx, 7);
-        DEBUG_CHAR(',');
-        DEBUG_ECHO_F(my, 7);
-        DEBUG_CHAR(',');
-        DEBUG_ECHO_F(mz, 7);
-        DEBUG_ECHOPGM("]   ---> ");
+        DEBUG_ECHOLN(F("before rotation = ["), p_float_t(mx, 7), AS_CHAR(','), p_float_t(my, 7), AS_CHAR(','), p_float_t(mz, 7), F("]   ---> "));
         DEBUG_DELAY(20);
       }
 
       rotation.apply_rotation_xyz(mx, my, mz);
 
       if (DEBUGGING(LEVELING)) {
-        DEBUG_ECHOPAIR_F("after rotation = [", mx, 7);
-        DEBUG_CHAR(',');
-        DEBUG_ECHO_F(my, 7);
-        DEBUG_CHAR(',');
-        DEBUG_ECHO_F(mz, 7);
-        DEBUG_ECHOLNPGM("]");
+        DEBUG_ECHOLN(F("after rotation = ["), p_float_t(mx, 7), AS_CHAR(','), p_float_t(my, 7), AS_CHAR(','), p_float_t(mz, 7), F("]   ---> "));
         DEBUG_DELAY(20);
       }
 
diff --git a/Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp b/Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp
index bcf78746a2..7d44e63537 100644
--- a/Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp
+++ b/Marlin/src/lcd/extui/anycubic_chiron/chiron_tft.cpp
@@ -88,14 +88,9 @@ void ChironTFT::startup() {
   // So we need to know what we are working with.
   // Panel type can be defined otherwise detect it automatically
   switch (panel_type) {
-    case AC_panel_new:
-      SERIAL_ECHOLNF(AC_msg_new_panel_set);
-      break;
-    case AC_panel_standard:
-      SERIAL_ECHOLNF(AC_msg_old_panel_set);
-      break;
-    default:
-      SERIAL_ECHOLNF(AC_msg_auto_panel_detection);
+    case AC_panel_new: SERIAL_ECHOLN(AC_msg_new_panel_set); break;
+    case AC_panel_standard: SERIAL_ECHOLN(AC_msg_old_panel_set); break;
+    default: SERIAL_ECHOLN(AC_msg_auto_panel_detection);
       detectPanelType();
       break;
   }
@@ -312,7 +307,7 @@ void ChironTFT::powerLossRecovery()  {
   printer_state = AC_printer_resuming_from_power_outage; // Play tune to notify user we can recover.
   last_error = AC_error_powerloss;
   PlayTune(SOS);
-  SERIAL_ECHOLNF(AC_msg_powerloss_recovery);
+  SERIAL_ECHOLN(AC_msg_powerloss_recovery);
 }
 
 void ChironTFT::printComplete() {
@@ -323,7 +318,7 @@ void ChironTFT::printComplete() {
 
 void ChironTFT::tftSend(FSTR_P const fstr/*=nullptr*/) {  // A helper to print PROGMEM string to the panel
   #if ACDEBUG(AC_SOME)
-    DEBUG_ECHOF(fstr);
+    DEBUG_ECHO(fstr);
   #endif
   PGM_P str = FTOP(fstr);
   while (const char c = pgm_read_byte(str++)) TFTSer.write(c);
@@ -447,7 +442,7 @@ void ChironTFT::selectFile() {
     selectedfile[command_len - 5] = '\0';
   }
   #if ACDEBUG(AC_FILE)
-    DEBUG_ECHOLNPGM(" Selected File: ",selectedfile);
+    DEBUG_ECHOLNPGM(" Selected File: ", selectedfile);
   #endif
   switch (selectedfile[0]) {
     case '/':   // Valid file selected
@@ -494,7 +489,7 @@ void ChironTFT::processPanelRequest() {
         if (tpos >= 0) {
           if (panel_command[tpos + 1] == 'X' && panel_command[tpos + 2] =='Y') {
             panel_type = AC_panel_standard;
-            SERIAL_ECHOLNF(AC_msg_old_panel_detected);
+            SERIAL_ECHOLN(AC_msg_old_panel_detected);
           }
         }
         else {
@@ -504,7 +499,7 @@ void ChironTFT::processPanelRequest() {
           if (tpos >= 0) {
             if (panel_command[tpos + 1] == '0' && panel_command[tpos + 2] ==']') {
               panel_type = AC_panel_new;
-              SERIAL_ECHOLNF(AC_msg_new_panel_detected);
+              SERIAL_ECHOLN(AC_msg_new_panel_detected);
             }
           }
         }
@@ -828,7 +823,7 @@ void ChironTFT::panelProcess(uint8_t req) {
         if (!isPrinting()) {
           injectCommands(F("M501\nM420 S1"));
           selectedmeshpoint.x = selectedmeshpoint.y = 99;
-          SERIAL_ECHOLNF(AC_msg_mesh_changes_abandoned);
+          SERIAL_ECHOLN(AC_msg_mesh_changes_abandoned);
         }
       }
 
@@ -836,7 +831,7 @@ void ChironTFT::panelProcess(uint8_t req) {
         if (!isPrinting()) {
           setAxisPosition_mm(1.0,Z); // Lift nozzle before any further movements are made
           injectCommands(F("M500"));
-          SERIAL_ECHOLNF(AC_msg_mesh_changes_saved);
+          SERIAL_ECHOLN(AC_msg_mesh_changes_saved);
           selectedmeshpoint.x = selectedmeshpoint.y = 99;
         }
       }
diff --git a/Marlin/src/lcd/extui/anycubic_vyper/dgus_tft.cpp b/Marlin/src/lcd/extui/anycubic_vyper/dgus_tft.cpp
index 518bda73f3..1177d2977b 100644
--- a/Marlin/src/lcd/extui/anycubic_vyper/dgus_tft.cpp
+++ b/Marlin/src/lcd/extui/anycubic_vyper/dgus_tft.cpp
@@ -701,7 +701,7 @@ namespace Anycubic {
 
   void DgusTFT::tftSend(FSTR_P const fstr/*=nullptr*/) {  // A helper to print PROGMEM string to the panel
     #if ACDEBUG(AC_SOME)
-      DEBUG_ECHOF(fstr);
+      DEBUG_ECHO(fstr);
     #endif
     PGM_P str = FTOP(fstr);
     while (const char c = pgm_read_byte(str++)) TFTSer.write(c);
@@ -3214,7 +3214,7 @@ namespace Anycubic {
   }
 
   void DEBUG_PRINT_PAUSED_STATE(const paused_state_t state, FSTR_P const msg/*=nullptr*/) {
-    if (msg) DEBUG_ECHOF(msg);
+    if (msg) DEBUG_ECHO(msg);
     DEBUG_ECHOPGM("Paused state: ", state, "  ");
     switch (state) {
       case AC_paused_heater_timed_out: DEBUG_ECHOPGM("AC_paused_heater_timed_out"); break;
@@ -3228,7 +3228,7 @@ namespace Anycubic {
   // Human-readable debugging
 
   void DEBUG_PRINT_PRINTER_STATE(const printer_state_t state, FSTR_P const msg/*=nullptr*/) {
-    if (msg) DEBUG_ECHOF(msg);
+    if (msg) DEBUG_ECHO(msg);
     DEBUG_ECHOPGM("Printer State: ", state, "  ");
     switch (state) {
       case AC_printer_idle: DEBUG_ECHOPGM("AC_printer_idle"); break;
diff --git a/Marlin/src/lcd/extui/dgus/DGUSScreenHandlerBase.h b/Marlin/src/lcd/extui/dgus/DGUSScreenHandlerBase.h
index c6dd270edf..163a095d08 100644
--- a/Marlin/src/lcd/extui/dgus/DGUSScreenHandlerBase.h
+++ b/Marlin/src/lcd/extui/dgus/DGUSScreenHandlerBase.h
@@ -207,7 +207,7 @@ public:
   static void sendFloatAsIntValueToDisplay(DGUS_VP_Variable &var) {
     if (var.memadr) {
       float f = *(float *)var.memadr;
-      DEBUG_ECHOLNPAIR_F(" >> ", f, 6);
+      DEBUG_ECHOLNPGM(" >> ", p_float_t(f, 6));
       f *= cpow(10, decimals);
       dgus.writeVariable(var.VP, (int16_t)f);
     }
diff --git a/Marlin/src/lcd/extui/nextion/nextion_tft.cpp b/Marlin/src/lcd/extui/nextion/nextion_tft.cpp
index e5e3a74be2..0327a2f137 100644
--- a/Marlin/src/lcd/extui/nextion/nextion_tft.cpp
+++ b/Marlin/src/lcd/extui/nextion/nextion_tft.cpp
@@ -112,7 +112,7 @@ void NextionTFT::statusChange(const char * const msg) {
 
 void NextionTFT::tftSend(FSTR_P const fstr/*=nullptr*/) { // A helper to print PROGMEM string to the panel
   #if NEXDEBUG(N_SOME)
-    DEBUG_ECHOF(fstr);
+    DEBUG_ECHO(fstr);
   #endif
   PGM_P str = FTOP(fstr);
   while (const char c = pgm_read_byte(str++)) LCD_SERIAL.write(c);
@@ -161,20 +161,20 @@ void NextionTFT::selectFile() {
   strncpy(selectedfile, nextion_command + 4, command_len - 4);
   selectedfile[command_len - 5] = '\0';
   #if NEXDEBUG(N_FILE)
-    DEBUG_ECHOLNPAIR_F(" Selected File: ", selectedfile);
+    DEBUG_ECHOLNPGM(" Selected File: ", selectedfile);
   #endif
   switch (selectedfile[0]) {
-  case '/': // Valid file selected
-    //SEND_TXT("tmppage.M117", msg_sd_file_open_success);
-    break;
-  case '<': // .. (go up folder level)
-    filenavigator.upDIR();
-    sendFileList(0);
-    break;
-  default: // enter sub folder
-    filenavigator.changeDIR(selectedfile);
-    sendFileList(0);
-    break;
+    case '/': // Valid file selected
+      //SEND_TXT("tmppage.M117", msg_sd_file_open_success);
+      break;
+    case '<': // .. (go up folder level)
+      filenavigator.upDIR();
+      sendFileList(0);
+      break;
+    default: // enter sub folder
+      filenavigator.changeDIR(selectedfile);
+      sendFileList(0);
+      break;
   }
 }
 
diff --git a/Marlin/src/lcd/menu/menu_configuration.cpp b/Marlin/src/lcd/menu/menu_configuration.cpp
index 6440cae033..32b6f15995 100644
--- a/Marlin/src/lcd/menu/menu_configuration.cpp
+++ b/Marlin/src/lcd/menu/menu_configuration.cpp
@@ -307,14 +307,11 @@ void menu_advanced_settings();
 
   #if ENABLED(BLTOUCH_LCD_VOLTAGE_MENU)
     void bltouch_report() {
-      PGMSTR(mode0, "OD");
-      PGMSTR(mode1, "5V");
-      DEBUG_ECHOPGM("BLTouch Mode: ");
-      DEBUG_ECHOPGM_P(bltouch.od_5v_mode ? mode1 : mode0);
-      DEBUG_ECHOLNPGM(" (Default " TERN(BLTOUCH_SET_5V_MODE, "5V", "OD") ")");
+      FSTR_P const mode0 = F("OD"), mode1 = F("5V");
+      DEBUG_ECHOLNPGM("BLTouch Mode: ", bltouch.od_5v_mode ? mode1 : mode0, " (Default ", TERN(BLTOUCH_SET_5V_MODE, mode1, mode0), ")");
       char mess[21];
       strcpy_P(mess, PSTR("BLTouch Mode: "));
-      strcpy_P(&mess[15], bltouch.od_5v_mode ? mode1 : mode0);
+      strcpy_P(&mess[15], bltouch.od_5v_mode ? FTOP(mode1) : FTOP(mode0));
       ui.set_status(mess);
       ui.return_to_status();
     }
diff --git a/Marlin/src/lcd/utf8.cpp b/Marlin/src/lcd/utf8.cpp
index 075f8b0c7c..6957fffc64 100644
--- a/Marlin/src/lcd/utf8.cpp
+++ b/Marlin/src/lcd/utf8.cpp
@@ -177,13 +177,8 @@ static inline uint8_t utf8_strlen_cb(const char *pstart, read_byte_cb_t cb_read_
   return cnt;
 }
 
-uint8_t utf8_strlen(const char *pstart) {
-  return utf8_strlen_cb(pstart, read_byte_ram);
-}
-
-uint8_t utf8_strlen_P(PGM_P pstart) {
-  return utf8_strlen_cb(pstart, read_byte_rom);
-}
+uint8_t utf8_strlen(const char *pstart) { return utf8_strlen_cb(pstart, read_byte_ram); }
+uint8_t utf8_strlen_P(PGM_P pstart) { return utf8_strlen_cb(pstart, read_byte_rom); }
 
 static inline uint8_t utf8_byte_pos_by_char_num_cb(const char *pstart, read_byte_cb_t cb_read_byte, const uint8_t charnum) {
   uint8_t *p = (uint8_t *)pstart;
diff --git a/Marlin/src/libs/vector_3.cpp b/Marlin/src/libs/vector_3.cpp
index a222b5cc2e..5668d2f392 100644
--- a/Marlin/src/libs/vector_3.cpp
+++ b/Marlin/src/libs/vector_3.cpp
@@ -76,10 +76,11 @@ void vector_3::apply_rotation(const matrix_3x3 &matrix) {
 }
 
 void vector_3::debug(FSTR_P const title) {
-  SERIAL_ECHOF(title);
-  SERIAL_ECHOPAIR_F_P(SP_X_STR, x, 6);
-  SERIAL_ECHOPAIR_F_P(SP_Y_STR, y, 6);
-  SERIAL_ECHOLNPAIR_F_P(SP_Z_STR, z, 6);
+  SERIAL_ECHOLN(title,
+    FPSTR(SP_X_STR), p_float_t(x, 6),
+    FPSTR(SP_Y_STR), p_float_t(y, 6),
+    FPSTR(SP_Z_STR), p_float_t(z, 6)
+  );
 }
 
 /**
@@ -138,7 +139,7 @@ matrix_3x3 matrix_3x3::transpose(const matrix_3x3 &original) {
 }
 
 void matrix_3x3::debug(FSTR_P const title) {
-  if (title) SERIAL_ECHOLNF(title);
+  if (title) SERIAL_ECHOLN(title);
   for (uint8_t i = 0; i < 3; ++i) {
     for (uint8_t j = 0; j < 3; ++j) {
       serial_offset(vectors[i][j], 2);
diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp
index 3031e7d694..63689bf7ca 100644
--- a/Marlin/src/module/endstops.cpp
+++ b/Marlin/src/module/endstops.cpp
@@ -375,9 +375,8 @@ void Endstops::event_handler() {
   #endif
 
   static void print_es_state(const bool is_hit, FSTR_P const flabel=nullptr) {
-    if (flabel) SERIAL_ECHOF(flabel);
-    SERIAL_ECHOPGM(": ");
-    SERIAL_ECHOLNF(is_hit ? F(STR_ENDSTOP_HIT) : F(STR_ENDSTOP_OPEN));
+    if (flabel) SERIAL_ECHO(flabel);
+    SERIAL_ECHOLN(F(": "), is_hit ? F(STR_ENDSTOP_HIT) : F(STR_ENDSTOP_OPEN));
   }
 
   #pragma GCC diagnostic pop
diff --git a/Marlin/src/module/probe.cpp b/Marlin/src/module/probe.cpp
index 0bece02c15..204617ba83 100644
--- a/Marlin/src/module/probe.cpp
+++ b/Marlin/src/module/probe.cpp
@@ -359,7 +359,7 @@ FORCE_INLINE void probe_specific_action(const bool deploy) {
     FSTR_P const ds_str = deploy ? GET_TEXT_F(MSG_MANUAL_DEPLOY) : GET_TEXT_F(MSG_MANUAL_STOW);
     ui.return_to_status();       // To display the new status message
     ui.set_status(ds_str, 99);
-    SERIAL_ECHOLNF(deploy ? GET_EN_TEXT_F(MSG_MANUAL_DEPLOY) : GET_EN_TEXT_F(MSG_MANUAL_STOW));
+    SERIAL_ECHOLN(deploy ? GET_EN_TEXT_F(MSG_MANUAL_DEPLOY) : GET_EN_TEXT_F(MSG_MANUAL_STOW));
 
     OKAY_BUZZ();
 
diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp
index 1defac2977..321691ba87 100644
--- a/Marlin/src/module/settings.cpp
+++ b/Marlin/src/module/settings.cpp
@@ -3595,7 +3595,7 @@ void MarlinSettings::reset() {
   #if ANY(EEPROM_CHITCHAT, DEBUG_LEVELING_FEATURE)
     FSTR_P const hdsl = F("Hardcoded Default Settings Loaded");
     TERN_(HOST_EEPROM_CHITCHAT, hostui.notify(hdsl));
-    DEBUG_ECHO_START(); DEBUG_ECHOLNF(hdsl);
+    DEBUG_ECHO_START(); DEBUG_ECHOLN(hdsl);
   #endif
 
   TERN_(EXTENSIBLE_UI, ExtUI::onFactoryReset());
@@ -3691,12 +3691,11 @@ void MarlinSettings::reset() {
           for (uint8_t py = 0; py < GRID_MAX_POINTS_Y; ++py) {
             for (uint8_t px = 0; px < GRID_MAX_POINTS_X; ++px) {
               CONFIG_ECHO_START();
-              SERIAL_ECHOPGM("  G29 S3 I", px, " J", py);
-              SERIAL_ECHOLNPAIR_F_P(SP_Z_STR, LINEAR_UNIT(bedlevel.z_values[px][py]), 5);
+              SERIAL_ECHOLN(F("  G29 S3 I"), px, F(" J"), py, FPSTR(SP_Z_STR), p_float_t(LINEAR_UNIT(bedlevel.z_values[px][py]), 5));
             }
           }
           CONFIG_ECHO_START();
-          SERIAL_ECHOLNPAIR_F("  G29 S4 Z", LINEAR_UNIT(bedlevel.z_offset), 5);
+          SERIAL_ECHOLNPGM("  G29 S4 Z", p_float_t(LINEAR_UNIT(bedlevel.z_offset), 5));
         }
 
       #elif ENABLED(AUTO_BED_LEVELING_UBL)
@@ -3717,8 +3716,7 @@ void MarlinSettings::reset() {
           for (uint8_t py = 0; py < GRID_MAX_POINTS_Y; ++py) {
             for (uint8_t px = 0; px < GRID_MAX_POINTS_X; ++px) {
               CONFIG_ECHO_START();
-              SERIAL_ECHOPGM("  G29 W I", px, " J", py);
-              SERIAL_ECHOLNPAIR_F_P(SP_Z_STR, LINEAR_UNIT(bedlevel.z_values[px][py]), 5);
+              SERIAL_ECHOLN(F("  G29 W I"), px, F(" J"), py, FPSTR(SP_Z_STR), p_float_t(LINEAR_UNIT(bedlevel.z_values[px][py]), 5));
             }
           }
         }
diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp
index 4d366198c1..d671e7ce89 100644
--- a/Marlin/src/module/temperature.cpp
+++ b/Marlin/src/module/temperature.cpp
@@ -870,9 +870,9 @@ volatile bool Temperature::raw_temps_ready = false;
 
         #if ANY(PIDTEMPBED, PIDTEMPCHAMBER)
           FSTR_P const estring = GHV(F("chamber"), F("bed"), FPSTR(NUL_STR));
-          say_default_(); SERIAL_ECHOF(estring); SERIAL_ECHOLNPGM("Kp ", tune_pid.p);
-          say_default_(); SERIAL_ECHOF(estring); SERIAL_ECHOLNPGM("Ki ", tune_pid.i);
-          say_default_(); SERIAL_ECHOF(estring); SERIAL_ECHOLNPGM("Kd ", tune_pid.d);
+          say_default_(); SERIAL_ECHO(estring, F("Kp "), tune_pid.p);
+          say_default_(); SERIAL_ECHO(estring, F("Ki "), tune_pid.i);
+          say_default_(); SERIAL_ECHO(estring, F("Kd "), tune_pid.d);
         #else
           say_default_(); SERIAL_ECHOLNPGM("Kp ", tune_pid.p);
           say_default_(); SERIAL_ECHOLNPGM("Ki ", tune_pid.i);
@@ -1244,7 +1244,7 @@ volatile bool Temperature::raw_temps_ready = false;
 
     #if ENABLED(DEBUG_MPC_AUTOTUNE)
       SERIAL_ECHOLNPGM("asymp_temp ", asymp_temp);
-      SERIAL_ECHOLNPAIR_F("block_responsiveness ", block_responsiveness, 4);
+      SERIAL_ECHOLNPGM("block_responsiveness ", p_float_t(block_responsiveness, 4));
     #endif
 
     // Make initial guess at transfer coefficients
@@ -1293,9 +1293,9 @@ volatile bool Temperature::raw_temps_ready = false;
       block_responsiveness = -log((t2 - asymp_temp) / (t1 - asymp_temp)) / tuner.get_sample_interval();
 
       #if ENABLED(DEBUG_MPC_AUTOTUNE)
-        SERIAL_ECHOLN("Refining estimates for:");
+        SERIAL_ECHOLNPGM("Refining estimates for:");
         SERIAL_ECHOLNPGM("asymp_temp ", asymp_temp);
-        SERIAL_ECHOLNPAIR_F("block_responsiveness ", block_responsiveness, 4);
+        SERIAL_ECHOLNPGM("block_responsiveness ", p_float_t(block_responsiveness, 4));
       #endif
 
       // Update analytic tuning values based on the above
@@ -1308,9 +1308,9 @@ volatile bool Temperature::raw_temps_ready = false;
     TERN_(DWIN_LCD_PROUI, DWIN_MPCTuning(MPC_DONE));
 
     SERIAL_ECHOLNPGM("MPC_BLOCK_HEAT_CAPACITY ", mpc.block_heat_capacity);
-    SERIAL_ECHOLNPAIR_F("MPC_SENSOR_RESPONSIVENESS ", mpc.sensor_responsiveness, 4);
-    SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF ", mpc.ambient_xfer_coeff_fan0, 4);
-    TERN_(HAS_FAN, SERIAL_ECHOLNPAIR_F("MPC_AMBIENT_XFER_COEFF_FAN255 ", ambient_xfer_coeff_fan255, 4));
+    SERIAL_ECHOLNPGM("MPC_SENSOR_RESPONSIVENESS ", p_float_t(mpc.sensor_responsiveness, 4));
+    SERIAL_ECHOLNPGM("MPC_AMBIENT_XFER_COEFF ", p_float_t(mpc.ambient_xfer_coeff_fan0, 4));
+    TERN_(HAS_FAN, SERIAL_ECHOLNPGM("MPC_AMBIENT_XFER_COEFF_FAN255 ", p_float_t(ambient_xfer_coeff_fan255, 4)));
   }
 
 #endif // MPC_AUTOTUNE
@@ -1477,7 +1477,7 @@ void Temperature::_temp_error(const heater_id_t heater_id, FSTR_P const serial_m
 
   if (IsRunning() && TERN1(BOGUS_TEMPERATURE_GRACE_PERIOD, killed == 2)) {
     SERIAL_ERROR_START();
-    SERIAL_ECHOF(serial_msg);
+    SERIAL_ECHO(serial_msg);
     SERIAL_ECHOPGM(STR_STOPPED_HEATER);
 
     heater_id_t real_heater_id = heater_id;
@@ -1583,7 +1583,7 @@ void Temperature::mintemp_error(const heater_id_t heater_id) {
     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_ECHOF(name);
+        if (name) SERIAL_ECHO(name);
         if (index >= 0) SERIAL_ECHO(index);
         SERIAL_ECHOLNPGM(
           STR_PID_DEBUG_INPUT, c,
@@ -2225,12 +2225,12 @@ void Temperature::task() {
 
     const user_thermistor_t &t = user_thermistor[t_index];
 
-    SERIAL_ECHOPAIR_F(" R", t.series_res, 1);
-    SERIAL_ECHOPAIR_F_P(SP_T_STR, t.res_25, 1);
-    SERIAL_ECHOPAIR_F_P(SP_B_STR, t.beta, 1);
-    SERIAL_ECHOPAIR_F_P(SP_C_STR, t.sh_c_coeff, 9);
-    SERIAL_ECHOPGM(" ; ");
-    SERIAL_ECHOF(
+    SERIAL_ECHO(
+      F(" R"), p_float_t(t.series_res, 1), FPSTR(SP_T_STR), p_float_t(t.res_25, 1),
+      FPSTR(SP_B_STR), p_float_t(t.beta, 1), FPSTR(SP_C_STR), p_float_t(t.sh_c_coeff, 9),
+      F(" ; ")
+    );
+    SERIAL_ECHOLN(
       TERN_(TEMP_SENSOR_0_IS_CUSTOM, t_index == CTI_HOTEND_0 ? F("HOTEND 0") :)
       TERN_(TEMP_SENSOR_1_IS_CUSTOM, t_index == CTI_HOTEND_1 ? F("HOTEND 1") :)
       TERN_(TEMP_SENSOR_2_IS_CUSTOM, t_index == CTI_HOTEND_2 ? F("HOTEND 2") :)
@@ -2245,9 +2245,8 @@ void Temperature::task() {
       TERN_(TEMP_SENSOR_PROBE_IS_CUSTOM, t_index == CTI_PROBE ? F("PROBE") :)
       TERN_(TEMP_SENSOR_BOARD_IS_CUSTOM, t_index == CTI_BOARD ? F("BOARD") :)
       TERN_(TEMP_SENSOR_REDUNDANT_IS_CUSTOM, t_index == CTI_REDUNDANT ? F("REDUNDANT") :)
-      nullptr
+      FSTR_P(nullptr)
     );
-    SERIAL_EOL();
   }
 
   celsius_float_t Temperature::user_thermistor_to_deg_c(const uint8_t t_index, const raw_adc_t raw) {
@@ -4189,8 +4188,8 @@ void Temperature::isr() {
     #else
       #define SFP 2
     #endif
-    SERIAL_CHAR(':'); SERIAL_PRINT(c, SFP);
-    if (show_t) { SERIAL_ECHOPGM(" /"); SERIAL_PRINT(t, SFP); }
+    SERIAL_ECHO(AS_CHAR(':'), p_float_t(c, SFP));
+    if (show_t) { SERIAL_ECHOPGM(" /", p_float_t(t, SFP)); }
     #if ENABLED(SHOW_TEMP_ADC_VALUES)
       // Temperature MAX SPI boards do not have an OVERSAMPLENR defined
       SERIAL_ECHOPGM(" (", TERN(HAS_MAXTC_LIBRARIES, k == 'T', false) ? r : r * RECIPROCAL(OVERSAMPLENR));
diff --git a/Marlin/src/pins/pinsDebug.h b/Marlin/src/pins/pinsDebug.h
index 4d51de52c8..9181a5b9da 100644
--- a/Marlin/src/pins/pinsDebug.h
+++ b/Marlin/src/pins/pinsDebug.h
@@ -177,11 +177,11 @@ const PinInfo pin_array[] PROGMEM = {
 bool pin_is_protected(const pin_t pin);
 
 static void print_input_or_output(const bool isout) {
-  SERIAL_ECHOF(isout ? F("Output ") : F("Input  "));
+  SERIAL_ECHO(isout ? F("Output ") : F("Input  "));
 }
 
 static void print_pin_state(const bool state) {
-  SERIAL_ECHOF(state ? F("HIGH") : F("LOW"));
+  SERIAL_ECHO(state ? F("HIGH") : F("LOW"));
 }
 
 // pretty report with PWM info
@@ -209,7 +209,7 @@ inline void report_pin_state_extended(const pin_t pin, const bool ignore, const
   for (uint8_t x = 0; x < COUNT(pin_array); ++x)  {    // scan entire array and report all instances of this pin
     if (GET_ARRAY_PIN(x) == pin) {
       if (!found) {    // report digital and analog pin number only on the first time through
-        if (start_string) SERIAL_ECHOF(start_string);
+        if (start_string) SERIAL_ECHO(start_string);
         SERIAL_ECHOPGM("PIN: ");
         PRINT_PIN(pin);
         print_port(pin);
@@ -257,7 +257,7 @@ inline void report_pin_state_extended(const pin_t pin, const bool ignore, const
   } // end of for loop
 
   if (!found) {
-    if (start_string) SERIAL_ECHOF(start_string);
+    if (start_string) SERIAL_ECHO(start_string);
     SERIAL_ECHOPGM("PIN: ");
     PRINT_PIN(pin);
     print_port(pin);
diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp
index 40c1f09f11..4bc22e84ca 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -668,9 +668,7 @@ void announceOpen(const uint8_t doing, const char * const path) {
   if (doing) {
     PORT_REDIRECT(SerialMask::All);
     SERIAL_ECHO_START();
-    SERIAL_ECHOPGM("Now ");
-    SERIAL_ECHOF(doing == 1 ? F("doing") : F("fresh"));
-    SERIAL_ECHOLNPGM(" file: ", path);
+    SERIAL_ECHOLN(F("Now "), doing == 1 ? F("doing") : F("fresh"), F(" file: "), path);
   }
 }
 
@@ -1447,8 +1445,7 @@ void CardReader::fileHasFinished() {
       recovery.init();
       removeFile(recovery.filename);
       #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
-        SERIAL_ECHOPGM("Power-loss file delete");
-        SERIAL_ECHOF(jobRecoverFileExists() ? F(" failed.\n") : F("d.\n"));
+        SERIAL_ECHOLN(F("Power-loss file delete"), jobRecoverFileExists() ? F(" failed.") : F("d."));
       #endif
     }
   }
diff --git a/docs/Serial.md b/docs/Serial.md
index 88846e1bb4..5e0d7e63eb 100644
--- a/docs/Serial.md
+++ b/docs/Serial.md
@@ -57,18 +57,15 @@ The following macros are defined (in `serial.h`) to output data to the serial po
 |-------|------------|-------|---------|-----------------|
 | `SERIAL_ECHO` | Any basic type is supported (`char`, `uint8_t`, `int16_t`, `int32_t`, `float`, `long`, `const char*`, ...). | For a numeric type it prints the number in decimal. A string is output as a string. | `uint8_t a = 123; SERIAL_ECHO(a); SERIAL_CHAR(' '); SERIAL_ECHO(' '); ` | `123 32` |
 | `SERIAL_ECHOLN` | Same as `SERIAL_ECHO` | Do `SERIAL_ECHO`, adding a newline | `int a = 456; SERIAL_ECHOLN(a);` | `456\n` |
-| `SERIAL_ECHO_F` | `float` or `double` | Print a decimal value with a given precision (default 2) | `float a = 3.1415; SERIAL_ECHO_F(a); SERIAL_CHAR(' '); SERIAL_ECHO_F(a, 4);` | `3.14 3.1415`|
 | `SERIAL_ECHOPGM` | String / Value pairs | Print a series of string literals and values alternately | `SERIAL_ECHOPGM("Bob", 34);` | `Bob34` |
 | `SERIAL_ECHOLNPGM` | Same as `SERIAL_ECHOPGM` | Do `SERIAL_ECHOPGM`, adding a newline | `SERIAL_ECHOPGM("Alice", 56);` | `alice56` |
 | `SERIAL_ECHOPGM_P` | Like `SERIAL_ECHOPGM` but takes PGM strings | Print a series of PGM strings and values alternately | `SERIAL_ECHOPGM_P(GET_TEXT(MSG_HELLO), 123);` | `Hello123` |
 | `SERIAL_ECHOLNPGM_P` | Same as `SERIAL_ECHOPGM_P` | Do `SERIAL_ECHOPGM_P`, adding a newline | `SERIAL_ECHOLNPGM_P(PSTR("Alice"), 78);` | `alice78\n` |
-| `SERIAL_ECHOLIST` | String literal, values | Print a string literal and a list of values | `SERIAL_ECHOLIST(F("Key "), 1, 2, 3);` | `Key 1, 2, 3` |
 | `SERIAL_ECHO_START` | None | Prefix an echo line | `SERIAL_ECHO_START();` | `echo:` |
 | `SERIAL_ECHO_MSG` | Same as `SERIAL_ECHOLNPGM` | Print a full echo line | `SERIAL_ECHO_MSG("Count is ", count);` | `echo:Count is 3` |
 | `SERIAL_ERROR_START`| None | Prefix an error line | `SERIAL_ERROR_START();` | `Error:` |
 | `SERIAL_ERROR_MSG` | Same as `SERIAL_ECHOLNPGM` | Print a full error line | `SERIAL_ERROR_MSG("Not found");` | `Error:Not found` |
 | `SERIAL_ECHO_SP` | Number of spaces | Print one or more spaces | `SERIAL_ECHO_SP(3)` | `   ` |
 | `SERIAL_EOL` | None | Print an end of line | `SERIAL_EOL();` | `\n` |
-| `SERIAL_OUT` | `SERIAL_OUT(myMethod)` | Call a custom serial method | `SERIAL_OUT(msgDone);` | ... |
 
 *This document was written by [X-Ryl669](https://blog.cyril.by) and is under [CC-SA license](https://creativecommons.org/licenses/by-sa)*