diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index 986e3c49abd1..925946837447 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -112,6 +112,7 @@
  * :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000]
  */
 #define BAUDRATE 250000
+
 //#define BAUD_RATE_GCODE     // Enable G-code M575 to set the baud rate
 
 /**
@@ -120,7 +121,7 @@
  * :[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7]
  */
 //#define SERIAL_PORT_2 -1
-//#define BAUDRATE_2 250000   // Enable to override BAUDRATE
+//#define BAUDRATE_2 250000   // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE
 
 /**
  * Select a third serial port on the board to use for communication with the host.
@@ -128,7 +129,7 @@
  * :[-1, 0, 1, 2, 3, 4, 5, 6, 7]
  */
 //#define SERIAL_PORT_3 1
-//#define BAUDRATE_3 250000   // Enable to override BAUDRATE
+//#define BAUDRATE_3 250000   // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE
 
 // Enable the Bluetooth serial interface on AT90USB devices
 //#define BLUETOOTH
@@ -387,7 +388,7 @@
 //#define HOTEND_OFFSET_Y { 0.0, 5.00 }  // (mm) relative Y-offset for each nozzle
 //#define HOTEND_OFFSET_Z { 0.0, 0.00 }  // (mm) relative Z-offset for each nozzle
 
-// @section machine
+// @section psu control
 
 /**
  * Power Supply Control
@@ -549,22 +550,32 @@
 #define DUMMY_THERMISTOR_999_VALUE 100
 
 // Resistor values when using MAX31865 sensors (-5) on TEMP_SENSOR_0 / 1
-//#define MAX31865_SENSOR_OHMS_0      100   // (Ω) Typically 100 or 1000 (PT100 or PT1000)
-//#define MAX31865_CALIBRATION_OHMS_0 430   // (Ω) Typically 430 for Adafruit PT100; 4300 for Adafruit PT1000
-//#define MAX31865_SENSOR_OHMS_1      100
-//#define MAX31865_CALIBRATION_OHMS_1 430
+#if TEMP_SENSOR_IS_MAX_TC(0)
+  #define MAX31865_SENSOR_OHMS_0      100   // (Ω) Typically 100 or 1000 (PT100 or PT1000)
+  #define MAX31865_CALIBRATION_OHMS_0 430   // (Ω) Typically 430 for Adafruit PT100; 4300 for Adafruit PT1000
+#endif
+#if TEMP_SENSOR_IS_MAX_TC(1)
+  #define MAX31865_SENSOR_OHMS_1      100
+  #define MAX31865_CALIBRATION_OHMS_1 430
+#endif
 
-#define TEMP_RESIDENCY_TIME         10  // (seconds) Time to wait for hotend to "settle" in M109
-#define TEMP_WINDOW                  1  // (°C) Temperature proximity for the "temperature reached" timer
-#define TEMP_HYSTERESIS              3  // (°C) Temperature proximity considered "close enough" to the target
+#if HAS_E_TEMP_SENSOR
+  #define TEMP_RESIDENCY_TIME         10  // (seconds) Time to wait for hotend to "settle" in M109
+  #define TEMP_WINDOW                  1  // (°C) Temperature proximity for the "temperature reached" timer
+  #define TEMP_HYSTERESIS              3  // (°C) Temperature proximity considered "close enough" to the target
+#endif
 
-#define TEMP_BED_RESIDENCY_TIME     10  // (seconds) Time to wait for bed to "settle" in M190
-#define TEMP_BED_WINDOW              1  // (°C) Temperature proximity for the "temperature reached" timer
-#define TEMP_BED_HYSTERESIS          3  // (°C) Temperature proximity considered "close enough" to the target
+#if TEMP_SENSOR_BED
+  #define TEMP_BED_RESIDENCY_TIME     10  // (seconds) Time to wait for bed to "settle" in M190
+  #define TEMP_BED_WINDOW              1  // (°C) Temperature proximity for the "temperature reached" timer
+  #define TEMP_BED_HYSTERESIS          3  // (°C) Temperature proximity considered "close enough" to the target
+#endif
 
-#define TEMP_CHAMBER_RESIDENCY_TIME 10  // (seconds) Time to wait for chamber to "settle" in M191
-#define TEMP_CHAMBER_WINDOW          1  // (°C) Temperature proximity for the "temperature reached" timer
-#define TEMP_CHAMBER_HYSTERESIS      3  // (°C) Temperature proximity considered "close enough" to the target
+#if TEMP_SENSOR_CHAMBER
+  #define TEMP_CHAMBER_RESIDENCY_TIME 10  // (seconds) Time to wait for chamber to "settle" in M191
+  #define TEMP_CHAMBER_WINDOW          1  // (°C) Temperature proximity for the "temperature reached" timer
+  #define TEMP_CHAMBER_HYSTERESIS      3  // (°C) Temperature proximity considered "close enough" to the target
+#endif
 
 /**
  * Redundant Temperature Sensor (TEMP_SENSOR_REDUNDANT)
@@ -623,6 +634,8 @@
 //============================= PID Settings ================================
 //===========================================================================
 
+// @section hotend temp
+
 // Enable PIDTEMP for PID control or MPCTEMP for Predictive Model.
 // temperature control. Disable both for bang-bang heating.
 #define PIDTEMP          // See the PID Tuning Guide at https://reprap.org/wiki/PID_Tuning
@@ -633,7 +646,8 @@
 #define PID_K1 0.95      // Smoothing factor within any PID loop
 
 #if ENABLED(PIDTEMP)
-  //#define PID_PARAMS_PER_HOTEND // Uses separate PID parameters for each extruder (useful for mismatched extruders)
+  //#define PID_DEBUG             // Print PID debug data to the serial port. Use 'M303 D' to toggle activation.
+  //#define PID_PARAMS_PER_HOTEND // Use separate PID parameters for each extruder (useful for mismatched extruders)
                                   // Set/get with G-code: M301 E[extruder number, 0-2]
 
   #if ENABLED(PID_PARAMS_PER_HOTEND)
@@ -655,6 +669,7 @@
  * Use a physical model of the hotend to control temperature. When configured correctly
  * this gives better responsiveness and stability than PID and it also removes the need
  * for PID_EXTRUSION_SCALING and PID_FAN_SCALING. Use M306 T to autotune the model.
+ * @section mpctemp
  */
 #if ENABLED(MPCTEMP)
   //#define MPC_EDIT_MENU                             // Add MPC editing to the "Advanced Settings" menu. (~1300 bytes of flash)
@@ -707,6 +722,7 @@
  * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 250W
  * heater. If your configuration is significantly different than this and you don't understand
  * the issues involved, don't use bed PID until someone else verifies that your hardware works.
+ * @section bed temp
  */
 //#define PIDTEMPBED
 
@@ -722,7 +738,7 @@
 
 #if ENABLED(PIDTEMPBED)
   //#define MIN_BED_POWER 0
-  //#define PID_BED_DEBUG // Sends debug data to the serial port.
+  //#define PID_BED_DEBUG // Print Bed PID debug data to the serial port.
 
   // 120V 250W silicone heater into 4mm borosilicate (MendelMax 1.5+)
   // from FOPDT model - kp=.39 Tp=405 Tdead=66, Tc set to 79.2, aggressive factor of .15 (vs .1, 1, 10)
@@ -750,6 +766,7 @@
  * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 200W
  * heater. If your configuration is significantly different than this and you don't understand
  * the issues involved, don't use chamber PID until someone else verifies that your hardware works.
+ * @section chamber temp
  */
 //#define PIDTEMPCHAMBER
 //#define CHAMBER_LIMIT_SWITCHING
@@ -764,7 +781,7 @@
 
 #if ENABLED(PIDTEMPCHAMBER)
   #define MIN_CHAMBER_POWER 0
-  //#define PID_CHAMBER_DEBUG // Sends debug data to the serial port.
+  //#define PID_CHAMBER_DEBUG // Print Chamber PID debug data to the serial port.
 
   // Lasko "MyHeat Personal Heater" (200w) modified with a Fotek SSR-10DA to control only the heating element
   // and placed inside the small Creality printer enclosure tent.
@@ -778,7 +795,6 @@
 #endif // PIDTEMPCHAMBER
 
 #if ANY(PIDTEMP, PIDTEMPBED, PIDTEMPCHAMBER)
-  //#define PID_DEBUG             // Sends debug data to the serial port. Use 'M303 D' to toggle activation.
   //#define PID_OPENLOOP          // Puts PID in open loop. M104/M140 sets the output power from 0 to PID_MAX
   //#define SLOW_PWM_HEATERS      // PWM with very low frequency (roughly 0.125Hz=8s) and minimum state time of approximately 1s useful for heaters driven by a relay
   #define PID_FUNCTIONAL_RANGE 10 // If the temperature difference between the target temperature and the actual temperature
@@ -788,7 +804,7 @@
   //#define PID_AUTOTUNE_MENU     // Add PID auto-tuning to the "Advanced Settings" menu. (~250 bytes of flash)
 #endif
 
-// @section extruder
+// @section safety
 
 /**
  * Prevent extrusion if the temperature is below EXTRUDE_MINTEMP.
@@ -856,6 +872,8 @@
   #define POLAR_SEGMENTS_PER_SECOND 5
 #endif
 
+// @section delta
+
 // Enable for DELTA kinematics and configure below
 //#define DELTA
 #if ENABLED(DELTA)
@@ -915,6 +933,8 @@
   //#define DELTA_DIAGONAL_ROD_TRIM_TOWER { 0.0, 0.0, 0.0 }
 #endif
 
+// @section scara
+
 /**
  * MORGAN_SCARA was developed by QHARLEY in South Africa in 2012-2013.
  * Implemented and slightly reworked by JCERNY in June, 2014.
@@ -958,6 +978,8 @@
 
 #endif
 
+// @section tpara
+
 // Enable for TPARA kinematics and configure below
 //#define AXEL_TPARA
 #if ENABLED(AXEL_TPARA)
@@ -984,6 +1006,8 @@
   #define PSI_HOMING_OFFSET    0
 #endif
 
+// @section machine
+
 // Articulated robot (arm). Joints are directly mapped to axes with no kinematics.
 //#define ARTICULATED_ROBOT_ARM
 
@@ -995,7 +1019,7 @@
 //============================== Endstop Settings ===========================
 //===========================================================================
 
-// @section homing
+// @section endstops
 
 // Specify here all the endstop connectors that are connected to any endstop or probe.
 // Almost all printers will be using one per axis. Probes will use one or more of the
@@ -1659,7 +1683,7 @@
 //#define V_HOME_DIR -1
 //#define W_HOME_DIR -1
 
-// @section machine
+// @section geometry
 
 // The size of the printable area
 #define X_BED_SIZE 200
@@ -2119,7 +2143,7 @@
 //============================= Additional Features ===========================
 //=============================================================================
 
-// @section extras
+// @section eeprom
 
 /**
  * EEPROM
@@ -2139,6 +2163,8 @@
   //#define EEPROM_INIT_NOW   // Init EEPROM on first boot after a new build.
 #endif
 
+// @section host
+
 //
 // Host Keepalive
 //
@@ -2149,6 +2175,8 @@
 #define DEFAULT_KEEPALIVE_INTERVAL 2  // Number of seconds between "busy" messages. Set with M113.
 #define BUSY_WHILE_HEATING            // Some hosts require "busy" messages even during heating
 
+// @section units
+
 //
 // G20/G21 Inch mode support
 //
@@ -2176,6 +2204,8 @@
 #define PREHEAT_2_TEMP_CHAMBER 35
 #define PREHEAT_2_FAN_SPEED     0 // Value from 0 to 255
 
+// @section motion
+
 /**
  * Nozzle Park
  *
@@ -2274,6 +2304,8 @@
 
 #endif
 
+// @section host
+
 /**
  * Print Job Timer
  *
@@ -2300,6 +2332,8 @@
  */
 #define PRINTJOB_TIMER_AUTOSTART
 
+// @section stats
+
 /**
  * Print Counter
  *
@@ -2317,6 +2351,8 @@
   #define PRINTCOUNTER_SAVE_INTERVAL 60 // (minutes) EEPROM save interval during print
 #endif
 
+// @section security
+
 /**
  * Password
  *
@@ -2352,7 +2388,7 @@
 //============================= LCD and SD support ============================
 //=============================================================================
 
-// @section lcd
+// @section interface
 
 /**
  * LCD LANGUAGE
@@ -2508,6 +2544,7 @@
 //======================== LCD / Controller Selection =========================
 //========================   (Character-based LCDs)   =========================
 //=============================================================================
+// @section lcd
 
 //
 // RepRapDiscount Smart Controller.
@@ -3142,7 +3179,7 @@
 //=============================== Extra Features ==============================
 //=============================================================================
 
-// @section extras
+// @section fans
 
 // Set number of user-controlled fans. Disable to use all board-defined fans.
 // :[1,2,3,4,5,6,7,8]
@@ -3166,14 +3203,18 @@
 // duty cycle is attained.
 //#define SOFT_PWM_DITHER
 
+// @section extras
+
+// Support for the BariCUDA Paste Extruder
+//#define BARICUDA
+
+// @section lights
+
 // Temperature status LEDs that display the hotend and bed temperature.
 // If all hotends, bed temperature, and target temperature are under 54C
 // then the BLUE led is on. Otherwise the RED led is on. (1C hysteresis)
 //#define TEMP_STAT_LEDS
 
-// Support for the BariCUDA Paste Extruder
-//#define BARICUDA
-
 // Support for BlinkM/CyzRgb
 //#define BLINKM
 
@@ -3259,6 +3300,8 @@
   #define PRINTER_EVENT_LEDS
 #endif
 
+// @section servos
+
 /**
  * Number of servos
  *
diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 3d69cb3feb48..6a82b0c000f0 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -32,6 +32,24 @@
  */
 #define CONFIGURATION_ADV_H_VERSION 02010100
 
+// @section develop
+
+/**
+ * Configuration Dump
+ *
+ * Dump the configuration as part of the build. (See signature.py)
+ * Output files are saved with the build (e.g., .pio/build/mega2560).
+ *
+ * See `build_all_examples --ini` as an example of config.ini archiving.
+ *
+ *  1 = marlin_config.json - Dictionary containing the configuration.
+ *      This file is also generated for CONFIGURATION_EMBEDDING.
+ *  2 = config.ini - File format for PlatformIO preprocessing.
+ *  3 = schema.json - The entire configuration schema. (13 = pattern groups)
+ *  4 = schema.yml - The entire configuration schema.
+ */
+//#define CONFIG_DUMP   // :[1:'JSON', 2:'config.ini', 3:'schema.json', 4:'schema.yml']
+
 //===========================================================================
 //============================= Thermal Settings ============================
 //===========================================================================
@@ -2545,6 +2563,8 @@
   #endif
 #endif // HAS_MULTI_EXTRUDER
 
+// @section advanced pause
+
 /**
  * Advanced Pause for Filament Change
  *  - Adds the G-code M600 Filament Change to initiate a filament change.
@@ -2603,13 +2623,12 @@
   //#define FILAMENT_UNLOAD_ALL_EXTRUDERS         // Allow M702 to unload all extruders above a minimum target temp (as set by M302)
 #endif
 
-// @section tmc
-
 /**
  * TMC26X Stepper Driver options
  *
  * The TMC26XStepper library is required for this stepper driver.
  * https://github.com/trinamic/TMC26XStepper
+ * @section tmc/tmc26x
  */
 #if HAS_DRIVER(TMC26X)
 
@@ -2747,8 +2766,6 @@
 
 #endif // TMC26X
 
-// @section tmc_smart
-
 /**
  * To use TMC2130, TMC2160, TMC2660, TMC5130, TMC5160 stepper drivers in SPI mode
  * connect your SPI pins to the hardware SPI interface on your board and define
@@ -2764,6 +2781,7 @@
  *
  * TMCStepper library is required to use TMC stepper drivers.
  * https://github.com/teemuatlut/TMCStepper
+ * @section tmc/config
  */
 #if HAS_TRINAMIC_CONFIG
 
@@ -2987,6 +3005,8 @@
     //#define E7_HOLD_MULTIPLIER 0.5
   #endif
 
+  // @section tmc/spi
+
   /**
    * Override default SPI pins for TMC2130, TMC2160, TMC2660, TMC5130 and TMC5160 drivers here.
    * The default pins can be found in your board's pins file.
@@ -3024,6 +3044,8 @@
   //#define TMC_SW_MISO       -1
   //#define TMC_SW_SCK        -1
 
+  // @section tmc/serial
+
   /**
    * Four TMC2209 drivers can use the same HW/SW serial port with hardware configured addresses.
    * Set the address using jumpers on pins MS1 and MS2.
@@ -3059,6 +3081,8 @@
   //#define E6_SLAVE_ADDRESS 0
   //#define E7_SLAVE_ADDRESS 0
 
+  // @section tmc/smart
+
   /**
    * Software enable
    *
@@ -3067,6 +3091,8 @@
    */
   //#define SOFTWARE_DRIVER_ENABLE
 
+  // @section tmc/stealthchop
+
   /**
    * TMC2130, TMC2160, TMC2208, TMC2209, TMC5130 and TMC5160 only
    * Use Trinamic's ultra quiet stepping mode.
@@ -3121,6 +3147,8 @@
   //#define CHOPPER_TIMING_E6 CHOPPER_TIMING_E
   //#define CHOPPER_TIMING_E7 CHOPPER_TIMING_E
 
+  // @section tmc/status
+
   /**
    * Monitor Trinamic drivers
    * for error conditions like overtemperature and short to ground.
@@ -3140,6 +3168,8 @@
     #define STOP_ON_ERROR
   #endif
 
+  // @section tmc/hybrid
+
   /**
    * TMC2130, TMC2160, TMC2208, TMC2209, TMC5130 and TMC5160 only
    * The driver will switch to spreadCycle when stepper speed is over HYBRID_THRESHOLD.
@@ -3196,6 +3226,7 @@
    * homing and adds a guard period for endstop triggering.
    *
    * Comment *_STALL_SENSITIVITY to disable sensorless homing for that axis.
+   * @section tmc/stallguard
    */
   //#define SENSORLESS_HOMING // StallGuard capable drivers only
 
@@ -3219,6 +3250,8 @@
     //#define IMPROVE_HOMING_RELIABILITY
   #endif
 
+  // @section tmc/config
+
   /**
    * TMC Homing stepper phase.
    *
@@ -3258,7 +3291,6 @@
 
 #endif // HAS_TRINAMIC_CONFIG
 
-
 // @section i2cbus
 
 //
@@ -3300,7 +3332,7 @@
   #define I2C_SLAVE_ADDRESS  0  // Set a value from 8 to 127 to act as a slave
 #endif
 
-// @section extras
+// @section photo
 
 /**
  * Photo G-code
@@ -3343,6 +3375,8 @@
   #endif
 #endif
 
+// @section cnc
+
 /**
  * Spindle & Laser control
  *
@@ -3546,6 +3580,8 @@
   #define COOLANT_FLOOD_INVERT false  // Set "true" if the on/off function is reversed
 #endif
 
+// @section filament width
+
 /**
  * Filament Width Sensor
  *
@@ -3579,6 +3615,8 @@
   //#define FILAMENT_LCD_DISPLAY
 #endif
 
+// @section power
+
 /**
  * Power Monitor
  * Monitor voltage (V) and/or current (A), and -when possible- power (W)
@@ -3602,6 +3640,8 @@
   #define POWER_MONITOR_VOLTAGE_OFFSET  0         // Offset (in volts) applied to the calculated voltage
 #endif
 
+// @section safety
+
 /**
  * Stepper Driver Anti-SNAFU Protection
  *
@@ -3611,6 +3651,8 @@
  */
 //#define DISABLE_DRIVER_SAFE_POWER_PROTECT
 
+// @section cnc
+
 /**
  * CNC Coordinate Systems
  *
@@ -3619,6 +3661,8 @@
  */
 //#define CNC_COORDINATE_SYSTEMS
 
+// @section reporting
+
 /**
  * Auto-report fan speed with M123 S<seconds>
  * Requires fans with tachometer pins
@@ -3646,6 +3690,8 @@
   //#define M115_GEOMETRY_REPORT
 #endif
 
+// @section security
+
 /**
  * Expected Printer Check
  * Add the M16 G-code to compare a string to the MACHINE_NAME.
@@ -3653,6 +3699,8 @@
  */
 //#define EXPECTED_PRINTER_CHECK
 
+// @section volumetrics
+
 /**
  * Disable all Volumetric extrusion options
  */
@@ -3681,14 +3729,7 @@
   #endif
 #endif
 
-/**
- * Enable this option for a leaner build of Marlin that removes all
- * workspace offsets, simplifying coordinate transformations, leveling, etc.
- *
- *  - M206 and M428 are disabled.
- *  - G92 will revert to its behavior from Marlin 1.0.
- */
-//#define NO_WORKSPACE_OFFSETS
+// @section reporting
 
 // Extra options for the M114 "Current Position" report
 //#define M114_DETAIL         // Use 'M114` for details to check planner calculations
@@ -3697,6 +3738,8 @@
 
 //#define REPORT_FAN_CHANGE   // Report the new fan speed when changed by M106 (and others)
 
+// @section gcode
+
 /**
  * Spend 28 bytes of SRAM to optimize the G-code parser
  */
@@ -3714,6 +3757,15 @@
 
 //#define REPETIER_GCODE_M360     // Add commands originally from Repetier FW
 
+/**
+ * Enable this option for a leaner build of Marlin that removes all
+ * workspace offsets, simplifying coordinate transformations, leveling, etc.
+ *
+ *  - M206 and M428 are disabled.
+ *  - G92 will revert to its behavior from Marlin 1.0.
+ */
+//#define NO_WORKSPACE_OFFSETS
+
 /**
  * CNC G-code options
  * Support CNC-style G-code dialects used by laser cutters, drawing machine cams, etc.
@@ -3729,6 +3781,8 @@
   //#define VARIABLE_G0_FEEDRATE // The G0 feedrate is set by F in G0 motion mode
 #endif
 
+// @section gcode
+
 /**
  * Startup commands
  *
@@ -3753,6 +3807,8 @@
  * Up to 25 may be defined, but the actual number is LCD-dependent.
  */
 
+// @section custom main menu
+
 // Custom Menu: Main Menu
 //#define CUSTOM_MENU_MAIN
 #if ENABLED(CUSTOM_MENU_MAIN)
@@ -3783,6 +3839,8 @@
   //#define MAIN_MENU_ITEM_5_CONFIRM
 #endif
 
+// @section custom config menu
+
 // Custom Menu: Configuration Menu
 //#define CUSTOM_MENU_CONFIG
 #if ENABLED(CUSTOM_MENU_CONFIG)
@@ -3813,6 +3871,8 @@
   //#define CONFIG_MENU_ITEM_5_CONFIRM
 #endif
 
+// @section custom buttons
+
 /**
  * User-defined buttons to run custom G-code.
  * Up to 25 may be defined.
@@ -3844,6 +3904,8 @@
   #endif
 #endif
 
+// @section host
+
 /**
  * Host Action Commands
  *
@@ -3869,6 +3931,8 @@
   //#define HOST_SHUTDOWN_MENU_ITEM       // Add a menu item that tells the host to shut down
 #endif
 
+// @section extras
+
 /**
  * Cancel Objects
  *
@@ -3890,6 +3954,7 @@
  * Alternative Supplier: https://reliabuild3d.com/
  *
  * Reliabuild encoders have been modified to improve reliability.
+ * @section i2c encoders
  */
 
 //#define I2C_POSITION_ENCODERS
@@ -3961,6 +4026,7 @@
 
 /**
  * Analog Joystick(s)
+ * @section joystick
  */
 //#define JOYSTICK
 #if ENABLED(JOYSTICK)
@@ -3985,6 +4051,7 @@
  * Modern replacement for the Prusa TMC_Z_CALIBRATION.
  * Adds capability to work with any adjustable current drivers.
  * Implemented as G34 because M915 is deprecated.
+ * @section calibrate
  */
 //#define MECHANICAL_GANTRY_CALIBRATION
 #if ENABLED(MECHANICAL_GANTRY_CALIBRATION)
@@ -4002,6 +4069,7 @@
 /**
  * Instant freeze / unfreeze functionality
  * Potentially useful for emergency stop that allows being resumed.
+ * @section interface
  */
 //#define FREEZE_FEATURE
 #if ENABLED(FREEZE_FEATURE)
@@ -4014,6 +4082,7 @@
  *
  * Add support for a low-cost 8x8 LED Matrix based on the Max7219 chip as a realtime status display.
  * Requires 3 signal wires. Some useful debug options are included to demonstrate its usage.
+ * @section debug matrix
  */
 //#define MAX7219_DEBUG
 #if ENABLED(MAX7219_DEBUG)
@@ -4052,6 +4121,7 @@
  * Support for Synchronized Z moves when used with NanoDLP. G0/G1 axis moves will
  * output a "Z_move_comp" string to enable synchronization with DLP projector exposure.
  * This feature allows you to use [[WaitForDoneMessage]] instead of M400 commands.
+ * @section nanodlp
  */
 //#define NANODLP_Z_SYNC
 #if ENABLED(NANODLP_Z_SYNC)
@@ -4060,6 +4130,7 @@
 
 /**
  * Ethernet. Use M552 to enable and set the IP address.
+ * @section network
  */
 #if HAS_ETHERNET
   #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xF0, 0x0D }  // A MAC address unique to your network
@@ -4087,6 +4158,8 @@
   //#include "Configuration_Secure.h" // External file with WiFi SSID / Password
 #endif
 
+// @section multi-material
+
 /**
  * Průša Multi-Material Unit (MMU)
  * Enable in Configuration.h
@@ -4192,6 +4265,7 @@
 
 /**
  * Advanced Print Counter settings
+ * @section stats
  */
 #if ENABLED(PRINTCOUNTER)
   #define SERVICE_WARNING_BUZZES  3
diff --git a/Marlin/base.ini b/Marlin/base.ini
new file mode 100644
index 000000000000..2ffcdfb7c281
--- /dev/null
+++ b/Marlin/base.ini
@@ -0,0 +1,107 @@
+#
+# Marlin Firmware
+# base.ini - A base ini to include for testing
+#
+[config:base]
+motherboard                              = BOARD_RAMPS_14_EFB
+serial_port                              = 0
+baudrate                                 = 250000
+
+use_watchdog                             = on
+thermal_protection_hotends               = on
+thermal_protection_hysteresis            = 4
+thermal_protection_period                = 40
+
+bufsize                                  = 4
+block_buffer_size                        = 16
+max_cmd_size                             = 96
+
+extruders                                = 1
+temp_sensor_0                            = 1
+
+temp_hysteresis                          = 3
+heater_0_mintemp                         = 5
+heater_0_maxtemp                         = 275
+preheat_1_temp_hotend                    = 180
+
+bang_max                                 = 255
+pidtemp                                  = on
+pid_k1                                   = 0.95
+pid_max                                  = BANG_MAX
+pid_functional_range                     = 10
+
+default_kp                               = 22.20
+default_ki                               = 1.08
+default_kd                               = 114.00
+
+x_driver_type                            = A4988
+y_driver_type                            = A4988
+z_driver_type                            = A4988
+e0_driver_type                           = A4988
+
+x_bed_size                               = 200
+x_min_pos                                = 0
+x_max_pos                                = X_BED_SIZE
+
+y_bed_size                               = 200
+y_min_pos                                = 0
+y_max_pos                                = Y_BED_SIZE
+
+z_min_pos                                = 0
+z_max_pos                                = 200
+
+x_home_dir                               = -1
+y_home_dir                               = -1
+z_home_dir                               = -1
+
+use_xmin_plug                            = on
+use_ymin_plug                            = on
+use_zmin_plug                            = on
+
+x_min_endstop_inverting                  = false
+y_min_endstop_inverting                  = false
+z_min_endstop_inverting                  = false
+
+default_axis_steps_per_unit              = { 80, 80, 400, 500 }
+axis_relative_modes                      = { false, false, false, false }
+default_max_feedrate                     = { 300, 300, 5, 25 }
+default_max_acceleration                 = { 3000, 3000, 100, 10000 }
+
+homing_feedrate_mm_m                     = { (50*60), (50*60), (4*60) }
+homing_bump_divisor                      = { 2, 2, 4 }
+
+x_enable_on                              = 0
+y_enable_on                              = 0
+z_enable_on                              = 0
+e_enable_on                              = 0
+
+invert_x_dir                             = false
+invert_y_dir                             = true
+invert_z_dir                             = false
+invert_e0_dir                            = false
+
+invert_e_step_pin                        = false
+invert_x_step_pin                        = false
+invert_y_step_pin                        = false
+invert_z_step_pin                        = false
+
+disable_x                                = false
+disable_y                                = false
+disable_z                                = false
+disable_e                                = false
+
+proportional_font_ratio                  = 1.0
+default_nominal_filament_dia             = 1.75
+
+junction_deviation_mm                    = 0.013
+
+default_acceleration                     = 3000
+default_travel_acceleration              = 3000
+default_retract_acceleration             = 3000
+
+default_minimumfeedrate                  = 0.0
+default_mintravelfeedrate                = 0.0
+
+minimum_planner_speed                    = 0.05
+min_steps_per_segment                    = 6
+default_minsegmenttime                   = 20000
diff --git a/Marlin/config.ini b/Marlin/config.ini
new file mode 100644
index 000000000000..532c982402f0
--- /dev/null
+++ b/Marlin/config.ini
@@ -0,0 +1,203 @@
+#
+# Marlin Firmware
+# config.ini - Options to apply before the build
+#
+[config:base]
+ini_use_config                           = none
+#ini_use_config                          = base.ini, another.ini
+#ini_use_config                          = example/Creality/Ender-5 Plus
+#ini_use_config                          = https://me.myserver.com/path/to/configs
+#ini_use_config                          = base
+#config_dump                             = 2
+
+motherboard                              = BOARD_RAMPS_14_EFB
+serial_port                              = 0
+baudrate                                 = 250000
+
+use_watchdog                             = on
+thermal_protection_hotends               = on
+thermal_protection_hysteresis            = 4
+thermal_protection_period                = 40
+
+bufsize                                  = 4
+block_buffer_size                        = 16
+max_cmd_size                             = 96
+
+extruders                                = 1
+temp_sensor_0                            = 1
+
+temp_hysteresis                          = 3
+heater_0_mintemp                         = 5
+heater_0_maxtemp                         = 275
+preheat_1_temp_hotend                    = 180
+
+bang_max                                 = 255
+pidtemp                                  = on
+pid_k1                                   = 0.95
+pid_max                                  = BANG_MAX
+pid_functional_range                     = 10
+
+default_kp                               = 22.20
+default_ki                               = 1.08
+default_kd                               = 114.00
+
+x_driver_type                            = A4988
+y_driver_type                            = A4988
+z_driver_type                            = A4988
+e0_driver_type                           = A4988
+
+x_bed_size                               = 200
+x_min_pos                                = 0
+x_max_pos                                = X_BED_SIZE
+
+y_bed_size                               = 200
+y_min_pos                                = 0
+y_max_pos                                = Y_BED_SIZE
+
+z_min_pos                                = 0
+z_max_pos                                = 200
+
+x_home_dir                               = -1
+y_home_dir                               = -1
+z_home_dir                               = -1
+
+use_xmin_plug                            = on
+use_ymin_plug                            = on
+use_zmin_plug                            = on
+
+x_min_endstop_inverting                  = false
+y_min_endstop_inverting                  = false
+z_min_endstop_inverting                  = false
+
+default_axis_steps_per_unit              = { 80, 80, 400, 500 }
+axis_relative_modes                      = { false, false, false, false }
+default_max_feedrate                     = { 300, 300, 5, 25 }
+default_max_acceleration                 = { 3000, 3000, 100, 10000 }
+
+homing_feedrate_mm_m                     = { (50*60), (50*60), (4*60) }
+homing_bump_divisor                      = { 2, 2, 4 }
+
+x_enable_on                              = 0
+y_enable_on                              = 0
+z_enable_on                              = 0
+e_enable_on                              = 0
+
+invert_x_dir                             = false
+invert_y_dir                             = true
+invert_z_dir                             = false
+invert_e0_dir                            = false
+
+invert_e_step_pin                        = false
+invert_x_step_pin                        = false
+invert_y_step_pin                        = false
+invert_z_step_pin                        = false
+
+disable_x                                = false
+disable_y                                = false
+disable_z                                = false
+disable_e                                = false
+
+proportional_font_ratio                  = 1.0
+default_nominal_filament_dia             = 1.75
+
+junction_deviation_mm                    = 0.013
+
+default_acceleration                     = 3000
+default_travel_acceleration              = 3000
+default_retract_acceleration             = 3000
+
+default_minimumfeedrate                  = 0.0
+default_mintravelfeedrate                = 0.0
+
+minimum_planner_speed                    = 0.05
+min_steps_per_segment                    = 6
+default_minsegmenttime                   = 20000
+
+[config:basic]
+bed_overshoot                            = 10
+busy_while_heating                       = on
+default_ejerk                            = 5.0
+default_keepalive_interval               = 2
+default_leveling_fade_height             = 0.0
+disable_inactive_extruder                = on
+display_charset_hd44780                  = JAPANESE
+eeprom_boot_silent                       = on
+eeprom_chitchat                          = on
+endstoppullups                           = on
+extrude_maxlength                        = 200
+extrude_mintemp                          = 170
+host_keepalive_feature                   = on
+hotend_overshoot                         = 15
+jd_handle_small_segments                 = on
+lcd_info_screen_style                    = 0
+lcd_language                             = en
+max_bed_power                            = 255
+mesh_inset                               = 0
+min_software_endstops                    = on
+max_software_endstops                    = on
+min_software_endstop_x                   = on
+min_software_endstop_y                   = on
+min_software_endstop_z                   = on
+max_software_endstop_x                   = on
+max_software_endstop_y                   = on
+max_software_endstop_z                   = on
+preheat_1_fan_speed                      = 0
+preheat_1_label                          = "PLA"
+preheat_1_temp_bed                       = 70
+prevent_cold_extrusion                   = on
+prevent_lengthy_extrude                  = on
+printjob_timer_autostart                 = on
+probing_margin                           = 10
+show_bootscreen                          = on
+soft_pwm_scale                           = 0
+string_config_h_author                   = "(none, default config)"
+temp_bed_hysteresis                      = 3
+temp_bed_residency_time                  = 10
+temp_bed_window                          = 1
+temp_residency_time                      = 10
+temp_window                              = 1
+validate_homing_endstops                 = on
+xy_probe_feedrate                        = (133*60)
+z_clearance_between_probes               = 5
+z_clearance_deploy_probe                 = 10
+z_clearance_multi_probe                  = 5
+
+[config:advanced]
+arc_support                              = on
+auto_report_temperatures                 = on
+autotemp                                 = on
+autotemp_oldweight                       = 0.98
+bed_check_interval                       = 5000
+default_stepper_deactive_time            = 120
+default_volumetric_extruder_limit        = 0.00
+disable_inactive_e                       = true
+disable_inactive_x                       = true
+disable_inactive_y                       = true
+disable_inactive_z                       = true
+e0_auto_fan_pin                          = -1
+encoder_100x_steps_per_sec               = 80
+encoder_10x_steps_per_sec                = 30
+encoder_rate_multiplier                  = on
+extended_capabilities_report             = on
+extruder_auto_fan_speed                  = 255
+extruder_auto_fan_temperature            = 50
+fanmux0_pin                              = -1
+fanmux1_pin                              = -1
+fanmux2_pin                              = -1
+faster_gcode_parser                      = on
+homing_bump_mm                           = { 5, 5, 2 }
+max_arc_segment_mm                       = 1.0
+min_arc_segment_mm                       = 0.1
+min_circle_segments                      = 72
+n_arc_correction                         = 25
+serial_overrun_protection                = on
+slowdown                                 = on
+slowdown_divisor                         = 2
+temp_sensor_bed                          = 0
+thermal_protection_bed_hysteresis        = 2
+thermocouple_max_errors                  = 15
+tx_buffer_size                           = 0
+watch_bed_temp_increase                  = 2
+watch_bed_temp_period                    = 60
+watch_temp_increase                      = 2
+watch_temp_period                        = 20
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 37918f619fa3..31b6184317ac 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -1220,10 +1220,10 @@ void setup() {
   SETUP_RUN(hal.init());
 
   // Init and disable SPI thermocouples; this is still needed
-  #if TEMP_SENSOR_0_IS_MAX_TC || (TEMP_SENSOR_REDUNDANT_IS_MAX_TC && REDUNDANT_TEMP_MATCH(SOURCE, E0))
+  #if TEMP_SENSOR_IS_MAX_TC(0) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E0))
     OUT_WRITE(TEMP_0_CS_PIN, HIGH);  // Disable
   #endif
-  #if TEMP_SENSOR_1_IS_MAX_TC || (TEMP_SENSOR_REDUNDANT_IS_MAX_TC && REDUNDANT_TEMP_MATCH(SOURCE, E1))
+  #if TEMP_SENSOR_IS_MAX_TC(1) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E1))
     OUT_WRITE(TEMP_1_CS_PIN, HIGH);
   #endif
 
diff --git a/Marlin/src/core/macros.h b/Marlin/src/core/macros.h
index 09a616456841..ddcf27b2b855 100644
--- a/Marlin/src/core/macros.h
+++ b/Marlin/src/core/macros.h
@@ -730,3 +730,8 @@
 #define __MAPLIST() _MAPLIST
 
 #define MAPLIST(OP,V...) EVAL(_MAPLIST(OP,V))
+
+// Temperature Sensor Config
+#define _HAS_E_TEMP(N) || (TEMP_SENSOR_##N != 0)
+#define HAS_E_TEMP_SENSOR (0 REPEAT(EXTRUDERS, _HAS_E_TEMP))
+#define TEMP_SENSOR_IS_MAX_TC(T) (TEMP_SENSOR_##T == -5 || TEMP_SENSOR_##T == -3 || TEMP_SENSOR_##T == -2)
diff --git a/Marlin/src/gcode/temp/M303.cpp b/Marlin/src/gcode/temp/M303.cpp
index ce362984a6ca..a4d514c7334f 100644
--- a/Marlin/src/gcode/temp/M303.cpp
+++ b/Marlin/src/gcode/temp/M303.cpp
@@ -48,7 +48,7 @@
 
 void GcodeSuite::M303() {
 
-  #if ANY(PID_DEBUG, PID_BED_DEBUG, PID_CHAMBER_DEBUG)
+  #if HAS_PID_DEBUG
     if (parser.seen_test('D')) {
       thermalManager.pid_debug_flag ^= true;
       SERIAL_ECHO_START();
diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h
index 0873c1f525c1..a6be04e237d8 100644
--- a/Marlin/src/inc/Conditionals_LCD.h
+++ b/Marlin/src/inc/Conditionals_LCD.h
@@ -668,6 +668,31 @@
   #define E_MANUAL EXTRUDERS
 #endif
 
+#if E_STEPPERS <= 7
+  #undef INVERT_E7_DIR
+  #if E_STEPPERS <= 6
+    #undef INVERT_E6_DIR
+    #if E_STEPPERS <= 5
+      #undef INVERT_E5_DIR
+      #if E_STEPPERS <= 4
+        #undef INVERT_E4_DIR
+        #if E_STEPPERS <= 3
+          #undef INVERT_E3_DIR
+          #if E_STEPPERS <= 2
+            #undef INVERT_E2_DIR
+            #if E_STEPPERS <= 1
+              #undef INVERT_E1_DIR
+              #if E_STEPPERS == 0
+                #undef INVERT_E0_DIR
+              #endif
+            #endif
+          #endif
+        #endif
+      #endif
+    #endif
+  #endif
+#endif
+
 /**
  * Number of Linear Axes (e.g., XYZIJKUVW)
  * All the logical axes except for the tool (E) axis
@@ -768,6 +793,9 @@
   #undef Y_MIN_POS
   #undef Y_MAX_POS
   #undef MANUAL_Y_HOME_POS
+  #undef MIN_SOFTWARE_ENDSTOP_Y
+  #undef MAX_SOFTWARE_ENDSTOP_Y
+  #undef SAFE_BED_LEVELING_START_Y
 #endif
 
 #if !HAS_Z_AXIS
@@ -785,6 +813,9 @@
   #undef Z_MIN_POS
   #undef Z_MAX_POS
   #undef MANUAL_Z_HOME_POS
+  #undef MIN_SOFTWARE_ENDSTOP_Z
+  #undef MAX_SOFTWARE_ENDSTOP_Z
+  #undef SAFE_BED_LEVELING_START_Z
 #endif
 
 #if !HAS_I_AXIS
@@ -799,6 +830,9 @@
   #undef I_MIN_POS
   #undef I_MAX_POS
   #undef MANUAL_I_HOME_POS
+  #undef MIN_SOFTWARE_ENDSTOP_I
+  #undef MAX_SOFTWARE_ENDSTOP_I
+  #undef SAFE_BED_LEVELING_START_I
 #endif
 
 #if !HAS_J_AXIS
@@ -813,6 +847,9 @@
   #undef J_MIN_POS
   #undef J_MAX_POS
   #undef MANUAL_J_HOME_POS
+  #undef MIN_SOFTWARE_ENDSTOP_J
+  #undef MAX_SOFTWARE_ENDSTOP_J
+  #undef SAFE_BED_LEVELING_START_J
 #endif
 
 #if !HAS_K_AXIS
@@ -827,6 +864,9 @@
   #undef K_MIN_POS
   #undef K_MAX_POS
   #undef MANUAL_K_HOME_POS
+  #undef MIN_SOFTWARE_ENDSTOP_K
+  #undef MAX_SOFTWARE_ENDSTOP_K
+  #undef SAFE_BED_LEVELING_START_K
 #endif
 
 #if !HAS_U_AXIS
@@ -841,6 +881,9 @@
   #undef U_MIN_POS
   #undef U_MAX_POS
   #undef MANUAL_U_HOME_POS
+  #undef MIN_SOFTWARE_ENDSTOP_U
+  #undef MAX_SOFTWARE_ENDSTOP_U
+  #undef SAFE_BED_LEVELING_START_U
 #endif
 
 #if !HAS_V_AXIS
@@ -855,6 +898,9 @@
   #undef V_MIN_POS
   #undef V_MAX_POS
   #undef MANUAL_V_HOME_POS
+  #undef MIN_SOFTWARE_ENDSTOP_V
+  #undef MAX_SOFTWARE_ENDSTOP_V
+  #undef SAFE_BED_LEVELING_START_V
 #endif
 
 #if !HAS_W_AXIS
@@ -869,6 +915,9 @@
   #undef W_MIN_POS
   #undef W_MAX_POS
   #undef MANUAL_W_HOME_POS
+  #undef MIN_SOFTWARE_ENDSTOP_W
+  #undef MAX_SOFTWARE_ENDSTOP_W
+  #undef SAFE_BED_LEVELING_START_W
 #endif
 
 #ifdef X2_DRIVER_TYPE
@@ -1398,6 +1447,10 @@
   #define EXTRUDE_MINTEMP 170
 #endif
 
+#if ANY(PID_DEBUG, PID_BED_DEBUG, PID_CHAMBER_DEBUG)
+  #define HAS_PID_DEBUG 1
+#endif
+
 /**
  * TFT Displays
  *
diff --git a/Marlin/src/inc/Conditionals_adv.h b/Marlin/src/inc/Conditionals_adv.h
index 49a1d7ef9c09..21bc424f5940 100644
--- a/Marlin/src/inc/Conditionals_adv.h
+++ b/Marlin/src/inc/Conditionals_adv.h
@@ -116,6 +116,31 @@
   #undef STEALTHCHOP_E
 #endif
 
+#if HOTENDS <= 7
+  #undef E7_AUTO_FAN_PIN
+  #if HOTENDS <= 6
+    #undef E6_AUTO_FAN_PIN
+    #if HOTENDS <= 5
+      #undef E5_AUTO_FAN_PIN
+      #if HOTENDS <= 4
+        #undef E4_AUTO_FAN_PIN
+        #if HOTENDS <= 3
+          #undef E3_AUTO_FAN_PIN
+          #if HOTENDS <= 2
+            #undef E2_AUTO_FAN_PIN
+            #if HOTENDS <= 1
+              #undef E1_AUTO_FAN_PIN
+              #if HOTENDS == 0
+                #undef E0_AUTO_FAN_PIN
+              #endif
+            #endif
+          #endif
+        #endif
+      #endif
+    #endif
+  #endif
+#endif
+
 /**
  * Temperature Sensors; define what sensor(s) we have.
  */
@@ -154,8 +179,7 @@
   #define REDUNDANT_TEMP_MATCH(...) 0
 #endif
 
-#if TEMP_SENSOR_0 == -5 || TEMP_SENSOR_0 == -3 || TEMP_SENSOR_0 == -2
-  #define TEMP_SENSOR_0_IS_MAX_TC 1
+#if TEMP_SENSOR_IS_MAX_TC(0)
   #if TEMP_SENSOR_0 == -5
     #define TEMP_SENSOR_0_IS_MAX31865 1
     #define TEMP_SENSOR_0_MAX_TC_TMIN    0
@@ -191,8 +215,7 @@
   #undef HEATER_0_MAXTEMP
 #endif
 
-#if TEMP_SENSOR_1 == -5 || TEMP_SENSOR_1 == -3 || TEMP_SENSOR_1 == -2
-  #define TEMP_SENSOR_1_IS_MAX_TC 1
+#if TEMP_SENSOR_IS_MAX_TC(1)
   #if TEMP_SENSOR_1 == -5
     #define TEMP_SENSOR_1_IS_MAX31865 1
     #define TEMP_SENSOR_1_MAX_TC_TMIN    0
@@ -238,9 +261,7 @@
   #undef HEATER_1_MAXTEMP
 #endif
 
-#if TEMP_SENSOR_REDUNDANT == -5 || TEMP_SENSOR_REDUNDANT == -3 || TEMP_SENSOR_REDUNDANT == -2
-  #define TEMP_SENSOR_REDUNDANT_IS_MAX_TC 1
-
+#if TEMP_SENSOR_IS_MAX_TC(REDUNDANT)
   #if TEMP_SENSOR_REDUNDANT == -5
     #if !REDUNDANT_TEMP_MATCH(SOURCE, E0) && !REDUNDANT_TEMP_MATCH(SOURCE, E1)
       #error "MAX31865 Thermocouples (-5) not supported for TEMP_SENSOR_REDUNDANT_SOURCE other than TEMP_SENSOR_0/TEMP_SENSOR_1 (0/1)."
@@ -282,7 +303,7 @@
     #endif
   #endif
 
-  #if (TEMP_SENSOR_0_IS_MAX_TC && TEMP_SENSOR_REDUNDANT != TEMP_SENSOR_0) || (TEMP_SENSOR_1_IS_MAX_TC && TEMP_SENSOR_REDUNDANT != TEMP_SENSOR_1)
+  #if (TEMP_SENSOR_IS_MAX_TC(0) && TEMP_SENSOR_REDUNDANT != TEMP_SENSOR_0) || (TEMP_SENSOR_IS_MAX_TC(1) && TEMP_SENSOR_REDUNDANT != TEMP_SENSOR_1)
     #if   TEMP_SENSOR_REDUNDANT == -5
       #error "If MAX31865 Thermocouple (-5) is used for TEMP_SENSOR_0/TEMP_SENSOR_1 then TEMP_SENSOR_REDUNDANT must match."
     #elif TEMP_SENSOR_REDUNDANT == -3
@@ -304,7 +325,7 @@
   #endif
 #endif
 
-#if TEMP_SENSOR_0_IS_MAX_TC || TEMP_SENSOR_1_IS_MAX_TC || TEMP_SENSOR_REDUNDANT_IS_MAX_TC
+#if TEMP_SENSOR_IS_MAX_TC(0) || TEMP_SENSOR_IS_MAX_TC(1) || TEMP_SENSOR_IS_MAX_TC(REDUNDANT)
   #define HAS_MAX_TC 1
 #endif
 #if TEMP_SENSOR_0_IS_MAX6675 || TEMP_SENSOR_1_IS_MAX6675 || TEMP_SENSOR_REDUNDANT_IS_MAX6675
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index 1c67a43ed4f4..650c420532ed 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -681,7 +681,7 @@
 #if HAS_MAX_TC
 
   // Translate old _SS, _CS, _SCK, _DO, _DI, _MISO, and _MOSI PIN defines.
-  #if TEMP_SENSOR_0_IS_MAX_TC || (TEMP_SENSOR_REDUNDANT_IS_MAX_TC && REDUNDANT_TEMP_MATCH(SOURCE, E1))
+  #if TEMP_SENSOR_IS_MAX_TC(0) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E1))
 
     #if !PIN_EXISTS(TEMP_0_CS) // SS, CS
       #if PIN_EXISTS(MAX6675_SS)
@@ -748,9 +748,9 @@
       #endif
     #endif
 
-  #endif // TEMP_SENSOR_0_IS_MAX_TC
+  #endif // TEMP_SENSOR_IS_MAX_TC(0)
 
-  #if TEMP_SENSOR_1_IS_MAX_TC || (TEMP_SENSOR_REDUNDANT_IS_MAX_TC && REDUNDANT_TEMP_MATCH(SOURCE, E1))
+  #if TEMP_SENSOR_IS_MAX_TC(1) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E1))
 
     #if !PIN_EXISTS(TEMP_1_CS) // SS2, CS2
       #if PIN_EXISTS(MAX6675_SS2)
@@ -817,7 +817,7 @@
       #endif
     #endif
 
-  #endif // TEMP_SENSOR_1_IS_MAX_TC
+  #endif // TEMP_SENSOR_IS_MAX_TC(1)
 
   //
   // User-defined thermocouple libraries
@@ -2656,7 +2656,7 @@
 //
 // ADC Temp Sensors (Thermistor or Thermocouple with amplifier ADC interface)
 //
-#define HAS_ADC_TEST(P) (PIN_EXISTS(TEMP_##P) && TEMP_SENSOR_##P != 0 && NONE(TEMP_SENSOR_##P##_IS_MAX_TC, TEMP_SENSOR_##P##_IS_DUMMY))
+#define HAS_ADC_TEST(P) (PIN_EXISTS(TEMP_##P) && TEMP_SENSOR_##P != 0 && !TEMP_SENSOR_IS_MAX_TC(P) && !TEMP_SENSOR_##P##_IS_DUMMY)
 #if HOTENDS > 0 && HAS_ADC_TEST(0)
   #define HAS_TEMP_ADC_0 1
 #endif
@@ -2700,7 +2700,7 @@
   #define HAS_TEMP_ADC_REDUNDANT 1
 #endif
 
-#define HAS_TEMP(N) ANY(HAS_TEMP_ADC_##N, TEMP_SENSOR_##N##_IS_MAX_TC, TEMP_SENSOR_##N##_IS_DUMMY)
+#define HAS_TEMP(N) (TEMP_SENSOR_IS_MAX_TC(N) || EITHER(HAS_TEMP_ADC_##N, TEMP_SENSOR_##N##_IS_DUMMY))
 #if HAS_HOTEND && HAS_TEMP(0)
   #define HAS_TEMP_HOTEND 1
 #endif
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index ed223cb6ece2..234f850cdbbc 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -2328,9 +2328,9 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
     #error "TEMP_SENSOR_REDUNDANT_TARGET can't be COOLER without TEMP_COOLER_PIN defined."
   #endif
 
-  #if TEMP_SENSOR_REDUNDANT_IS_MAX_TC && REDUNDANT_TEMP_MATCH(SOURCE, E0) && !PIN_EXISTS(TEMP_0_CS)
+  #if TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E0) && !PIN_EXISTS(TEMP_0_CS)
     #error "TEMP_SENSOR_REDUNDANT MAX Thermocouple with TEMP_SENSOR_REDUNDANT_SOURCE E0 requires TEMP_0_CS_PIN."
-  #elif TEMP_SENSOR_REDUNDANT_IS_MAX_TC && REDUNDANT_TEMP_MATCH(SOURCE, E1) && !PIN_EXISTS(TEMP_1_CS)
+  #elif TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E1) && !PIN_EXISTS(TEMP_1_CS)
     #error "TEMP_SENSOR_REDUNDANT MAX Thermocouple with TEMP_SENSOR_REDUNDANT_SOURCE E1 requires TEMP_1_CS_PIN."
   #endif
 #endif
@@ -2343,7 +2343,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
   #error "TEMP_0_PIN or TEMP_0_CS_PIN not defined for this board."
 #elif HAS_EXTRUDERS && !HAS_HEATER_0
   #error "HEATER_0_PIN not defined for this board."
-#elif TEMP_SENSOR_0_IS_MAX_TC && !PIN_EXISTS(TEMP_0_CS)
+#elif TEMP_SENSOR_IS_MAX_TC(0) && !PIN_EXISTS(TEMP_0_CS)
   #error "TEMP_SENSOR_0 MAX thermocouple requires TEMP_0_CS_PIN."
 #elif HAS_HOTEND && !HAS_TEMP_HOTEND && !TEMP_SENSOR_0_IS_DUMMY
   #error "TEMP_0_PIN (required for TEMP_SENSOR_0) not defined for this board."
@@ -2352,7 +2352,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
 #endif
 
 #if HAS_MULTI_HOTEND
-  #if TEMP_SENSOR_1_IS_MAX_TC && !PIN_EXISTS(TEMP_1_CS)
+  #if TEMP_SENSOR_IS_MAX_TC(1) && !PIN_EXISTS(TEMP_1_CS)
     #error "TEMP_SENSOR_1 MAX thermocouple requires TEMP_1_CS_PIN."
   #elif TEMP_SENSOR_1 == 0
     #error "TEMP_SENSOR_1 is required with 2 or more HOTENDS."
diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp
index 56eceea39d2d..ecd95b5e8f3c 100644
--- a/Marlin/src/module/temperature.cpp
+++ b/Marlin/src/module/temperature.cpp
@@ -77,7 +77,7 @@
 
 // MAX TC related macros
 #define TEMP_SENSOR_IS_MAX(n, M) (ENABLED(TEMP_SENSOR_##n##_IS_MAX##M) || (ENABLED(TEMP_SENSOR_REDUNDANT_IS_MAX##M) && REDUNDANT_TEMP_MATCH(SOURCE, E##n)))
-#define TEMP_SENSOR_IS_ANY_MAX_TC(n) (ENABLED(TEMP_SENSOR_##n##_IS_MAX_TC) || (ENABLED(TEMP_SENSOR_REDUNDANT_IS_MAX_TC) && REDUNDANT_TEMP_MATCH(SOURCE, E##n)))
+#define TEMP_SENSOR_IS_ANY_MAX_TC(n) (TEMP_SENSOR_IS_MAX_TC(n) || (TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E##n)))
 
 // LIB_MAX6675 can be added to the build_flags in platformio.ini to use a user-defined library
 // If LIB_MAX6675 is not on the build_flags then raw SPI reads will be used.
@@ -1317,8 +1317,7 @@ void Temperature::min_temp_error(const heater_id_t heater_id) {
   _temp_error(heater_id, F(STR_T_MINTEMP), GET_TEXT_F(MSG_ERR_MINTEMP));
 }
 
-#if ANY(PID_DEBUG, PID_BED_DEBUG, PID_CHAMBER_DEBUG)
-  #define HAS_PID_DEBUG 1
+#if HAS_PID_DEBUG
   bool Temperature::pid_debug_flag; // = false
 #endif
 
@@ -1856,15 +1855,15 @@ void Temperature::task() {
   if (!updateTemperaturesIfReady()) return; // Will also reset the watchdog if temperatures are ready
 
   #if DISABLED(IGNORE_THERMOCOUPLE_ERRORS)
-    #if TEMP_SENSOR_0_IS_MAX_TC
+    #if TEMP_SENSOR_IS_MAX_TC(0)
       if (degHotend(0) > _MIN(HEATER_0_MAXTEMP, TEMP_SENSOR_0_MAX_TC_TMAX - 1.0)) max_temp_error(H_E0);
       if (degHotend(0) < _MAX(HEATER_0_MINTEMP, TEMP_SENSOR_0_MAX_TC_TMIN + .01)) min_temp_error(H_E0);
     #endif
-    #if TEMP_SENSOR_1_IS_MAX_TC
+    #if TEMP_SENSOR_IS_MAX_TC(1)
       if (degHotend(1) > _MIN(HEATER_1_MAXTEMP, TEMP_SENSOR_1_MAX_TC_TMAX - 1.0)) max_temp_error(H_E1);
       if (degHotend(1) < _MAX(HEATER_1_MINTEMP, TEMP_SENSOR_1_MAX_TC_TMIN + .01)) min_temp_error(H_E1);
     #endif
-    #if TEMP_SENSOR_REDUNDANT_IS_MAX_TC
+    #if TEMP_SENSOR_IS_MAX_TC(REDUNDANT)
       if (degRedundant() > TEMP_SENSOR_REDUNDANT_MAX_TC_TMAX - 1.0) max_temp_error(H_REDUNDANT);
       if (degRedundant() < TEMP_SENSOR_REDUNDANT_MAX_TC_TMIN + .01) min_temp_error(H_REDUNDANT);
     #endif
@@ -2072,7 +2071,7 @@ void Temperature::task() {
       case 0:
         #if TEMP_SENSOR_0_IS_CUSTOM
           return user_thermistor_to_deg_c(CTI_HOTEND_0, raw);
-        #elif TEMP_SENSOR_0_IS_MAX_TC
+        #elif TEMP_SENSOR_IS_MAX_TC(0)
           #if TEMP_SENSOR_0_IS_MAX31865
             return TERN(LIB_INTERNAL_MAX31865,
               max31865_0.temperature(raw),
@@ -2091,7 +2090,7 @@ void Temperature::task() {
       case 1:
         #if TEMP_SENSOR_1_IS_CUSTOM
           return user_thermistor_to_deg_c(CTI_HOTEND_1, raw);
-        #elif TEMP_SENSOR_1_IS_MAX_TC
+        #elif TEMP_SENSOR_IS_MAX_TC(1)
           #if TEMP_SENSOR_0_IS_MAX31865
             return TERN(LIB_INTERNAL_MAX31865,
               max31865_1.temperature(raw),
@@ -2275,9 +2274,9 @@ void Temperature::task() {
   celsius_float_t Temperature::analog_to_celsius_redundant(const raw_adc_t raw) {
     #if TEMP_SENSOR_REDUNDANT_IS_CUSTOM
       return user_thermistor_to_deg_c(CTI_REDUNDANT, raw);
-    #elif TEMP_SENSOR_REDUNDANT_IS_MAX_TC && REDUNDANT_TEMP_MATCH(SOURCE, E0)
+    #elif TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E0)
       return TERN(TEMP_SENSOR_REDUNDANT_IS_MAX31865, max31865_0.temperature(raw), (int16_t)raw * 0.25);
-    #elif TEMP_SENSOR_REDUNDANT_IS_MAX_TC && REDUNDANT_TEMP_MATCH(SOURCE, E1)
+    #elif TEMP_SENSOR_IS_MAX_TC(REDUNDANT) && REDUNDANT_TEMP_MATCH(SOURCE, E1)
       return TERN(TEMP_SENSOR_REDUNDANT_IS_MAX31865, max31865_1.temperature(raw), (int16_t)raw * 0.25);
     #elif TEMP_SENSOR_REDUNDANT_IS_THERMISTOR
       SCAN_THERMISTOR_TABLE(TEMPTABLE_REDUNDANT, TEMPTABLE_REDUNDANT_LEN);
@@ -2308,9 +2307,15 @@ void Temperature::updateTemperaturesFromRawValues() {
 
   hal.watchdog_refresh(); // Reset because raw_temps_ready was set by the interrupt
 
-  TERN_(TEMP_SENSOR_0_IS_MAX_TC, temp_hotend[0].setraw(READ_MAX_TC(0)));
-  TERN_(TEMP_SENSOR_1_IS_MAX_TC, temp_hotend[1].setraw(READ_MAX_TC(1)));
-  TERN_(TEMP_SENSOR_REDUNDANT_IS_MAX_TC, temp_redundant.setraw(READ_MAX_TC(HEATER_ID(TEMP_SENSOR_REDUNDANT_SOURCE))));
+  #if TEMP_SENSOR_IS_MAX_TC(0)
+    temp_hotend[0].setraw(READ_MAX_TC(0));
+  #endif
+  #if TEMP_SENSOR_IS_MAX_TC(1)
+    temp_hotend[1].setraw(READ_MAX_TC(1));
+  #endif
+  #if TEMP_SENSOR_IS_MAX_TC(REDUNDANT)
+    temp_redundant.setraw(READ_MAX_TC(HEATER_ID(TEMP_SENSOR_REDUNDANT_SOURCE)));
+  #endif
 
   #if HAS_HOTEND
     HOTEND_LOOP() temp_hotend[e].celsius = analog_to_celsius_hotend(temp_hotend[e].getraw(), e);
@@ -3139,15 +3144,15 @@ void Temperature::disable_all_heaters() {
 void Temperature::update_raw_temperatures() {
 
   // TODO: can this be collapsed into a HOTEND_LOOP()?
-  #if HAS_TEMP_ADC_0 && !TEMP_SENSOR_0_IS_MAX_TC
+  #if HAS_TEMP_ADC_0 && !TEMP_SENSOR_IS_MAX_TC(0)
     temp_hotend[0].update();
   #endif
 
-  #if HAS_TEMP_ADC_1 && !TEMP_SENSOR_1_IS_MAX_TC
+  #if HAS_TEMP_ADC_1 && !TEMP_SENSOR_IS_MAX_TC(1)
     temp_hotend[1].update();
   #endif
 
-  #if HAS_TEMP_ADC_REDUNDANT && !TEMP_SENSOR_REDUNDANT_IS_MAX_TC
+  #if HAS_TEMP_ADC_REDUNDANT && !TEMP_SENSOR_IS_MAX_TC(REDUNDANT)
     temp_redundant.update();
   #endif
 
diff --git a/Marlin/src/module/temperature.h b/Marlin/src/module/temperature.h
index feec31805051..f6cf81b8a966 100644
--- a/Marlin/src/module/temperature.h
+++ b/Marlin/src/module/temperature.h
@@ -953,7 +953,7 @@ class Temperature {
      */
     #if HAS_PID_HEATING
 
-      #if ANY(PID_DEBUG, PID_BED_DEBUG, PID_CHAMBER_DEBUG)
+      #if HAS_PID_DEBUG
         static bool pid_debug_flag;
       #endif
 
@@ -1035,7 +1035,7 @@ class Temperature {
 
     // MAX Thermocouples
     #if HAS_MAX_TC
-      #define MAX_TC_COUNT COUNT_ENABLED(TEMP_SENSOR_0_IS_MAX_TC, TEMP_SENSOR_1_IS_MAX_TC, TEMP_SENSOR_REDUNDANT_IS_MAX_TC)
+      #define MAX_TC_COUNT TEMP_SENSOR_IS_MAX_TC(0) + TEMP_SENSOR_IS_MAX_TC(1) + TEMP_SENSOR_IS_MAX_TC(REDUNDANT)
       #if MAX_TC_COUNT > 1
         #define HAS_MULTI_MAX_TC 1
         #define READ_MAX_TC(N) read_max_tc(N)
diff --git a/buildroot/share/PlatformIO/scripts/configuration.py b/buildroot/share/PlatformIO/scripts/configuration.py
new file mode 100644
index 000000000000..8e67b5a0017b
--- /dev/null
+++ b/buildroot/share/PlatformIO/scripts/configuration.py
@@ -0,0 +1,236 @@
+#
+# configuration.py
+# Apply options from config.ini to the existing Configuration headers
+#
+import re, shutil, configparser
+from pathlib import Path
+
+verbose = 0
+def blab(str,level=1):
+	if verbose >= level: print(f"[config] {str}")
+
+def config_path(cpath):
+	return Path("Marlin", cpath)
+
+# Apply a single name = on/off ; name = value ; etc.
+# TODO: Limit to the given (optional) configuration
+def apply_opt(name, val, conf=None):
+	if name == "lcd": name, val = val, "on"
+
+	# Create a regex to match the option and capture parts of the line
+	regex = re.compile(r'^(\s*)(//\s*)?(#define\s+)(' + name + r'\b)(\s*)(.*?)(\s*)(//.*)?$', re.IGNORECASE)
+
+	# Find and enable and/or update all matches
+	for file in ("Configuration.h", "Configuration_adv.h"):
+		fullpath = config_path(file)
+		lines = fullpath.read_text().split('\n')
+		found = False
+		for i in range(len(lines)):
+			line = lines[i]
+			match = regex.match(line)
+			if match and match[4].upper() == name.upper():
+				found = True
+				# For boolean options un/comment the define
+				if val in ("on", "", None):
+					newline = re.sub(r'^(\s*)//+\s*(#define)(\s{1,3})?(\s*)', r'\1\2 \4', line)
+				elif val == "off":
+					newline = re.sub(r'^(\s*)(#define)(\s{1,3})?(\s*)', r'\1//\2 \4', line)
+				else:
+					# For options with values, enable and set the value
+					newline = match[1] + match[3] + match[4] + match[5] + val
+					if match[8]:
+						sp = match[7] if match[7] else ' '
+						newline += sp + match[8]
+				lines[i] = newline
+				blab(f"Set {name} to {val}")
+
+		# If the option was found, write the modified lines
+		if found:
+			fullpath.write_text('\n'.join(lines))
+			break
+
+	# If the option didn't appear in either config file, add it
+	if not found:
+		# OFF options are added as disabled items so they appear
+		# in config dumps. Useful for custom settings.
+		prefix = ""
+		if val == "off":
+			prefix, val = "//", ""	# Item doesn't appear in config dump
+			#val = "false"			# Item appears in config dump
+
+		# Uppercase the option unless already mixed/uppercase
+		added = name.upper() if name.islower() else name
+
+		# Add the provided value after the name
+		if val != "on" and val != "" and val is not None:
+			added += " " + val
+
+		# Prepend the new option after the first set of #define lines
+		fullpath = config_path("Configuration.h")
+		with fullpath.open() as f:
+			lines = f.readlines()
+			linenum = 0
+			gotdef = False
+			for line in lines:
+				isdef = line.startswith("#define")
+				if not gotdef:
+					gotdef = isdef
+				elif not isdef:
+					break
+				linenum += 1
+			lines.insert(linenum, f"{prefix}#define {added} // Added by config.ini\n")
+			fullpath.write_text('\n'.join(lines))
+
+# Fetch configuration files from GitHub given the path.
+# Return True if any files were fetched.
+def fetch_example(path):
+	if path.endswith("/"):
+		path = path[:-1]
+
+	url = path.replace("%", "%25").replace(" ", "%20")
+	if not path.startswith('http'):
+		url = "https://raw.githubusercontent.com/MarlinFirmware/Configurations/bugfix-2.1.x/config/%s" % url
+
+	# Find a suitable fetch command
+	if shutil.which("curl") is not None:
+		fetch = "curl -L -s -S -f -o"
+	elif shutil.which("wget") is not None:
+		fetch = "wget -q -O"
+	else:
+		blab("Couldn't find curl or wget", -1)
+		return False
+
+	import os
+
+	# Reset configurations to default
+	os.system("git reset --hard HEAD")
+
+	gotfile = False
+
+	# Try to fetch the remote files
+	for fn in ("Configuration.h", "Configuration_adv.h", "_Bootscreen.h", "_Statusscreen.h"):
+		if os.system("%s wgot %s/%s >/dev/null 2>&1" % (fetch, url, fn)) == 0:
+			shutil.move('wgot', config_path(fn))
+			gotfile = True
+
+	if Path('wgot').exists():
+		shutil.rmtree('wgot')
+
+	return gotfile
+
+def section_items(cp, sectkey):
+	return cp.items(sectkey) if sectkey in cp.sections() else []
+
+# Apply all items from a config section
+def apply_ini_by_name(cp, sect):
+	iniok = True
+	if sect in ('config:base', 'config:root'):
+		iniok = False
+		items = section_items(cp, 'config:base') + section_items(cp, 'config:root')
+	else:
+		items = cp.items(sect)
+
+	for item in items:
+		if iniok or not item[0].startswith('ini_'):
+			apply_opt(item[0], item[1])
+
+# Apply all config sections from a parsed file
+def apply_all_sections(cp):
+	for sect in cp.sections():
+		if sect.startswith('config:'):
+			apply_ini_by_name(cp, sect)
+
+# Apply certain config sections from a parsed file
+def apply_sections(cp, ckey='all', addbase=False):
+	blab("[config] apply section key: %s" % ckey)
+	if ckey == 'all':
+		apply_all_sections(cp)
+	else:
+		# Apply the base/root config.ini settings after external files are done
+		if addbase or ckey in ('base', 'root'):
+			apply_ini_by_name(cp, 'config:base')
+
+		# Apply historically 'Configuration.h' settings everywhere
+		if ckey == 'basic':
+			apply_ini_by_name(cp, 'config:basic')
+
+		# Apply historically Configuration_adv.h settings everywhere
+		# (Some of which rely on defines in 'Conditionals_LCD.h')
+		elif ckey in ('adv', 'advanced'):
+			apply_ini_by_name(cp, 'config:advanced')
+
+		# Apply a specific config:<name> section directly
+		elif ckey.startswith('config:'):
+			apply_ini_by_name(cp, ckey)
+
+# Apply settings from a top level config.ini
+def apply_config_ini(cp):
+	blab("=" * 20 + " Gather 'config.ini' entries...")
+
+	# Pre-scan for ini_use_config to get config_keys
+	base_items = section_items(cp, 'config:base') + section_items(cp, 'config:root')
+	config_keys = ['base']
+	for ikey, ival in base_items:
+		if ikey == 'ini_use_config':
+			config_keys = [ x.strip() for x in ival.split(',') ]
+
+	# For each ini_use_config item perform an action
+	for ckey in config_keys:
+		addbase = False
+
+		# For a key ending in .ini load and parse another .ini file
+		if ckey.endswith('.ini'):
+			sect = 'base'
+			if '@' in ckey: sect, ckey = ckey.split('@')
+			other_ini = configparser.ConfigParser()
+			other_ini.read(config_path(ckey))
+			apply_sections(other_ini, sect)
+
+		# (Allow 'example/' as a shortcut for 'examples/')
+		elif ckey.startswith('example/'):
+			ckey = 'examples' + ckey[7:]
+
+		# For 'examples/<path>' fetch an example set from GitHub.
+		# For https?:// do a direct fetch of the URL.
+		elif ckey.startswith('examples/') or ckey.startswith('http'):
+			addbase = True
+			fetch_example(ckey)
+
+		# Apply keyed sections after external files are done
+		apply_sections(cp, 'config:' + ckey, addbase)
+
+if __name__ == "__main__":
+	#
+	# From command line use the given file name
+	#
+	import sys
+	args = sys.argv[1:]
+	if len(args) > 0:
+		if args[0].endswith('.ini'):
+			ini_file = args[0]
+		else:
+			print("Usage: %s <.ini file>" % sys.argv[0])
+	else:
+		ini_file = config_path('config.ini')
+
+	if ini_file:
+		user_ini = configparser.ConfigParser()
+		user_ini.read(ini_file)
+		apply_config_ini(user_ini)
+
+else:
+	#
+	# From within PlatformIO use the loaded INI file
+	#
+	import pioutil
+	if pioutil.is_pio_build():
+
+		Import("env")
+
+		try:
+			verbose = int(env.GetProjectOption('custom_verbose'))
+		except:
+			pass
+
+		from platformio.project.config import ProjectConfig
+		apply_config_ini(ProjectConfig())
diff --git a/buildroot/share/PlatformIO/scripts/schema.py b/buildroot/share/PlatformIO/scripts/schema.py
new file mode 100755
index 000000000000..efe56ebe3583
--- /dev/null
+++ b/buildroot/share/PlatformIO/scripts/schema.py
@@ -0,0 +1,403 @@
+#!/usr/bin/env python3
+#
+# schema.py
+#
+# Used by signature.py via common-dependencies.py to generate a schema file during the PlatformIO build.
+# This script can also be run standalone from within the Marlin repo to generate all schema files.
+#
+import re,json
+from pathlib import Path
+
+def extend_dict(d:dict, k:tuple):
+	if len(k) >= 1 and k[0] not in d:
+		d[k[0]] = {}
+	if len(k) >= 2 and k[1] not in d[k[0]]:
+		d[k[0]][k[1]] = {}
+	if len(k) >= 3 and k[2] not in d[k[0]][k[1]]:
+		d[k[0]][k[1]][k[2]] = {}
+
+grouping_patterns = [
+	re.compile(r'^([XYZIJKUVW]|[XYZ]2|Z[34]|E[0-7])$'),
+	re.compile(r'^AXIS\d$'),
+	re.compile(r'^(MIN|MAX)$'),
+	re.compile(r'^[0-8]$'),
+	re.compile(r'^HOTEND[0-7]$'),
+	re.compile(r'^(HOTENDS|BED|PROBE|COOLER)$'),
+	re.compile(r'^[XYZIJKUVW]M(IN|AX)$')
+]
+# If the indexed part of the option name matches a pattern
+# then add it to the dictionary.
+def find_grouping(gdict, filekey, sectkey, optkey, pindex):
+	optparts = optkey.split('_')
+	if 1 < len(optparts) > pindex:
+		for patt in grouping_patterns:
+			if patt.match(optparts[pindex]):
+				subkey = optparts[pindex]
+				modkey = '_'.join(optparts)
+				optparts[pindex] = '*'
+				wildkey = '_'.join(optparts)
+				kkey = f'{filekey}|{sectkey}|{wildkey}'
+				if kkey not in gdict: gdict[kkey] = []
+				gdict[kkey].append((subkey, modkey))
+
+# Build a list of potential groups. Only those with multiple items will be grouped.
+def group_options(schema):
+	for pindex in range(10, -1, -1):
+		found_groups = {}
+		for filekey, f in schema.items():
+			for sectkey, s in f.items():
+				for optkey in s:
+					find_grouping(found_groups, filekey, sectkey, optkey, pindex)
+
+		fkeys = [ k for k in found_groups.keys() ]
+		for kkey in fkeys:
+			items = found_groups[kkey]
+			if len(items) > 1:
+				f, s, w = kkey.split('|')
+				extend_dict(schema, (f, s, w))						# Add wildcard group to schema
+				for subkey, optkey in items:						# Add all items to wildcard group
+					schema[f][s][w][subkey] = schema[f][s][optkey]	# Move non-wildcard item to wildcard group
+					del schema[f][s][optkey]
+			del found_groups[kkey]
+
+# Extract all board names from boards.h
+def load_boards():
+	bpath = Path("Marlin/src/core/boards.h")
+	if bpath.is_file():
+		with bpath.open() as bfile:
+			boards = []
+			for line in bfile:
+				if line.startswith("#define BOARD_"):
+					bname = line.split()[1]
+					if bname != "BOARD_UNKNOWN": boards.append(bname)
+			return "['" + "','".join(boards) + "']"
+	return ''
+
+#
+# Extract a schema from the current configuration files
+#
+def extract():
+	# Load board names from boards.h
+	boards = load_boards()
+
+	# Parsing states
+	class Parse:
+		NORMAL			= 0 # No condition yet
+		BLOCK_COMMENT	= 1 # Looking for the end of the block comment
+		EOL_COMMENT		= 2 # EOL comment started, maybe add the next comment?
+		GET_SENSORS		= 3 # Gathering temperature sensor options
+		ERROR			= 9 # Syntax error
+
+	# List of files to process, with shorthand
+	filekey = { 'Configuration.h':'basic', 'Configuration_adv.h':'advanced' }
+	# A JSON object to store the data
+	sch_out = { 'basic':{}, 'advanced':{} }
+	# Regex for #define NAME [VALUE] [COMMENT] with sanitized line
+	defgrep = re.compile(r'^(//)?\s*(#define)\s+([A-Za-z0-9_]+)\s*(.*?)\s*(//.+)?$')
+	# Defines to ignore
+	ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_DUMP')
+	# Start with unknown state
+	state = Parse.NORMAL
+	# Serial ID
+	sid = 0
+	# Loop through files and parse them line by line
+	for fn, fk in filekey.items():
+		with Path("Marlin", fn).open() as fileobj:
+			section = 'none'		# Current Settings section
+			line_number = 0			# Counter for the line number of the file
+			conditions = []			# Create a condition stack for the current file
+			comment_buff = []		# A temporary buffer for comments
+			options_json = ''		# A buffer for the most recent options JSON found
+			eol_options = False		# The options came from end of line, so only apply once
+			join_line = False		# A flag that the line should be joined with the previous one
+			line = ''				# A line buffer to handle \ continuation
+			last_added_ref = None	# Reference to the last added item
+			# Loop through the lines in the file
+			for the_line in fileobj.readlines():
+				line_number += 1
+
+				# Clean the line for easier parsing
+				the_line = the_line.strip()
+
+				if join_line:	# A previous line is being made longer
+					line += (' ' if line else '') + the_line
+				else:			# Otherwise, start the line anew
+					line, line_start = the_line, line_number
+
+				# If the resulting line ends with a \, don't process now.
+				# Strip the end off. The next line will be joined with it.
+				join_line = line.endswith("\\")
+				if join_line:
+					line = line[:-1].strip()
+					continue
+				else:
+					line_end = line_number
+
+				defmatch = defgrep.match(line)
+
+				# Special handling for EOL comments after a #define.
+				# At this point the #define is already digested and inserted,
+				# so we have to extend it
+				if state == Parse.EOL_COMMENT:
+					# If the line is not a comment, we're done with the EOL comment
+					if not defmatch and the_line.startswith('//'):
+						comment_buff.append(the_line[2:].strip())
+					else:
+						last_added_ref['comment'] = ' '.join(comment_buff)
+						comment_buff = []
+						state = Parse.NORMAL
+
+				def use_comment(c, opt, sec, bufref):
+					if c.startswith(':'):				# If the comment starts with : then it has magic JSON
+						d = c[1:].strip()				# Strip the leading :
+						cbr = c.rindex('}') if d.startswith('{') else c.rindex(']') if d.startswith('[') else 0
+						if cbr:
+							opt, cmt = c[1:cbr+1].strip(), c[cbr+1:].strip()
+							if cmt != '': bufref.append(cmt)
+						else:
+							opt = c[1:].strip()
+					elif c.startswith('@section'):		# Start a new section
+						sec = c[8:].strip()
+					elif not c.startswith('========'):
+						bufref.append(c)
+					return opt, sec
+
+				# In a block comment, capture lines up to the end of the comment.
+				# Assume nothing follows the comment closure.
+				if state in (Parse.BLOCK_COMMENT, Parse.GET_SENSORS):
+					endpos = line.find('*/')
+					if endpos < 0:
+						cline = line
+					else:
+						cline, line = line[:endpos].strip(), line[endpos+2:].strip()
+
+						# Temperature sensors are done
+						if state == Parse.GET_SENSORS:
+							options_json = f'[ {options_json[:-2]} ]'
+
+						state = Parse.NORMAL
+
+					# Strip the leading '*' from block comments
+					if cline.startswith('*'): cline = cline[1:].strip()
+
+					# Collect temperature sensors
+					if state == Parse.GET_SENSORS:
+						sens = re.match(r'^(-?\d+)\s*:\s*(.+)$', cline)
+						if sens:
+							s2 = sens[2].replace("'","''")
+							options_json += f"{sens[1]}:'{s2}', "
+
+					elif state == Parse.BLOCK_COMMENT:
+
+						# Look for temperature sensors
+						if cline == "Temperature sensors available:":
+							state, cline = Parse.GET_SENSORS, "Temperature Sensors"
+
+						options_json, section = use_comment(cline, options_json, section, comment_buff)
+
+				# For the normal state we're looking for any non-blank line
+				elif state == Parse.NORMAL:
+					# Skip a commented define when evaluating comment opening
+					st = 2 if re.match(r'^//\s*#define', line) else 0
+					cpos1 = line.find('/*')		# Start a block comment on the line?
+					cpos2 = line.find('//', st)	# Start an end of line comment on the line?
+
+					# Only the first comment starter gets evaluated
+					cpos = -1
+					if cpos1 != -1 and (cpos1 < cpos2 or cpos2 == -1):
+						cpos = cpos1
+						comment_buff = []
+						state = Parse.BLOCK_COMMENT
+						eol_options = False
+
+					elif cpos2 != -1 and (cpos2 < cpos1 or cpos1 == -1):
+						cpos = cpos2
+
+						# Expire end-of-line options after first use
+						if cline.startswith(':'): eol_options = True
+
+						# Comment after a define may be continued on the following lines
+						if state == Parse.NORMAL and defmatch != None and cpos > 10:
+							state = Parse.EOL_COMMENT
+							comment_buff = []
+
+					# Process the start of a new comment
+					if cpos != -1:
+						cline, line = line[cpos+2:].strip(), line[:cpos].strip()
+
+						# Strip leading '*' from block comments
+						if state == Parse.BLOCK_COMMENT:
+							if cline.startswith('*'): cline = cline[1:].strip()
+
+						# Buffer a non-empty comment start
+						if cline != '':
+							options_json, section = use_comment(cline, options_json, section, comment_buff)
+
+					# If the line has nothing before the comment, go to the next line
+					if line == '':
+						options_json = ''
+						continue
+
+					# Parenthesize the given expression if needed
+					def atomize(s):
+						if s == '' \
+						or re.match(r'^[A-Za-z0-9_]*(\([^)]+\))?$', s) \
+						or re.match(r'^[A-Za-z0-9_]+ == \d+?$', s):
+							return s
+						return f'({s})'
+
+					#
+					# The conditions stack is an array containing condition-arrays.
+					# Each condition-array lists the conditions for the current block.
+					# IF/N/DEF adds a new condition-array to the stack.
+					# ELSE/ELIF/ENDIF pop the condition-array.
+					# ELSE/ELIF negate the last item in the popped condition-array.
+					# ELIF adds a new condition to the end of the array.
+					# ELSE/ELIF re-push the condition-array.
+					#
+					cparts = line.split()
+					iselif, iselse = cparts[0] == '#elif', cparts[0] == '#else'
+					if iselif or iselse or cparts[0] == '#endif':
+						if len(conditions) == 0:
+							raise Exception(f'no #if block at line {line_number}')
+
+						# Pop the last condition-array from the stack
+						prev = conditions.pop()
+
+						if iselif or iselse:
+							prev[-1] = '!' + prev[-1] # Invert the last condition
+							if iselif: prev.append(atomize(line[5:].strip()))
+							conditions.append(prev)
+
+					elif cparts[0] == '#if':
+						conditions.append([ atomize(line[3:].strip()) ])
+					elif cparts[0] == '#ifdef':
+						conditions.append([ f'defined({line[6:].strip()})' ])
+					elif cparts[0] == '#ifndef':
+						conditions.append([ f'!defined({line[7:].strip()})' ])
+
+					# Handle a complete #define line
+					elif defmatch != None:
+
+						# Get the match groups into vars
+						enabled, define_name, val = defmatch[1] == None, defmatch[3], defmatch[4]
+
+						# Increment the serial ID
+						sid += 1
+
+						# Create a new dictionary for the current #define
+						define_info = {
+							'section': section,
+							'name': define_name,
+							'enabled': enabled,
+							'line': line_start,
+							'sid': sid
+						}
+
+						if val != '': define_info['value'] = val
+
+						# Type is based on the value
+						if val == '':
+							value_type = 'switch'
+						elif re.match(r'^(true|false)$', val):
+							value_type = 'bool'
+							val = val == 'true'
+						elif re.match(r'^[-+]?\s*\d+$', val):
+							value_type = 'int'
+							val = int(val)
+						elif re.match(r'[-+]?\s*(\d+\.|\d*\.\d+)([eE][-+]?\d+)?[fF]?', val):
+							value_type = 'float'
+							val = float(val.replace('f',''))
+						else:
+							value_type = 'string'	if val[0] == '"' \
+									else 'char'		if val[0] == "'" \
+									else 'state'	if re.match(r'^(LOW|HIGH)$', val) \
+									else 'enum'		if re.match(r'^[A-Za-z0-9_]{3,}$', val) \
+									else 'int[]'	if re.match(r'^{(\s*[-+]?\s*\d+\s*(,\s*)?)+}$', val) \
+									else 'float[]'	if re.match(r'^{(\s*[-+]?\s*(\d+\.|\d*\.\d+)([eE][-+]?\d+)?[fF]?\s*(,\s*)?)+}$', val) \
+									else 'array'	if val[0] == '{' \
+									else ''
+
+						if value_type != '': define_info['type'] = value_type
+
+						# Join up accumulated conditions with &&
+						if conditions: define_info['requires'] = ' && '.join(sum(conditions, []))
+
+						# If the comment_buff is not empty, add the comment to the info
+						if comment_buff:
+							full_comment = '\n'.join(comment_buff)
+
+							# An EOL comment will be added later
+							# The handling could go here instead of above
+							if state == Parse.EOL_COMMENT:
+								define_info['comment'] = ''
+							else:
+								define_info['comment'] = full_comment
+								comment_buff = []
+
+							# If the comment specifies units, add that to the info
+							units = re.match(r'^\(([^)]+)\)', full_comment)
+							if units:
+								units = units[1]
+								if units == 's' or units == 'sec': units = 'seconds'
+								define_info['units'] = units
+
+						# Set the options for the current #define
+						if define_name == "MOTHERBOARD" and boards != '':
+							define_info['options'] = boards
+						elif options_json != '':
+							define_info['options'] = options_json
+							if eol_options: options_json = ''
+
+						# Create section dict if it doesn't exist yet
+						if section not in sch_out[fk]: sch_out[fk][section] = {}
+
+						# If define has already been seen...
+						if define_name in sch_out[fk][section]:
+							info = sch_out[fk][section][define_name]
+							if isinstance(info, dict): info = [ info ]	# Convert a single dict into a list
+							info.append(define_info)					# Add to the list
+						else:
+							# Add the define dict with name as key
+							sch_out[fk][section][define_name] = define_info
+
+						if state == Parse.EOL_COMMENT:
+							last_added_ref = define_info
+
+	return sch_out
+
+def dump_json(schema:dict, jpath:Path):
+	with jpath.open('w') as jfile:
+		json.dump(schema, jfile, ensure_ascii=False, indent=2)
+
+def dump_yaml(schema:dict, ypath:Path):
+	import yaml
+	with ypath.open('w') as yfile:
+		yaml.dump(schema, yfile, default_flow_style=False, width=120, indent=2)
+
+def main():
+	try:
+		schema = extract()
+	except Exception as exc:
+		print("Error: " + str(exc))
+		schema = None
+
+	if schema:
+		print("Generating JSON ...")
+		dump_json(schema, Path('schema.json'))
+		group_options(schema)
+		dump_json(schema, Path('schema_grouped.json'))
+
+		try:
+			import yaml
+		except ImportError:
+			print("Installing YAML module ...")
+			import subprocess
+			subprocess.run(['python3', '-m', 'pip', 'install', 'pyyaml'])
+			import yaml
+
+		print("Generating YML ...")
+		dump_yaml(schema, Path('schema.yml'))
+
+if __name__ == '__main__':
+	main()
diff --git a/buildroot/share/PlatformIO/scripts/signature.py b/buildroot/share/PlatformIO/scripts/signature.py
index f1c86bb8395f..fc7c490d3d65 100644
--- a/buildroot/share/PlatformIO/scripts/signature.py
+++ b/buildroot/share/PlatformIO/scripts/signature.py
@@ -1,7 +1,9 @@
 #
 # signature.py
 #
-import os,subprocess,re,json,hashlib
+import subprocess,re,json,hashlib
+import schema
+from pathlib import Path
 
 #
 # Return all macro names in a header as an array, so we can take
@@ -51,19 +53,19 @@ def compute_build_signature(env):
 	# Definitions from these files will be kept
 	files_to_keep = [ 'Marlin/Configuration.h', 'Marlin/Configuration_adv.h' ]
 
-	build_dir = os.path.join(env['PROJECT_BUILD_DIR'], env['PIOENV'])
+	build_path = Path(env['PROJECT_BUILD_DIR'], env['PIOENV'])
 
 	# Check if we can skip processing
 	hashes = ''
 	for header in files_to_keep:
 		hashes += get_file_sha256sum(header)[0:10]
 
-	marlin_json = os.path.join(build_dir, 'marlin_config.json')
-	marlin_zip = os.path.join(build_dir, 'mc')
+	marlin_json = build_path / 'marlin_config.json'
+	marlin_zip = build_path / 'mc'
 
 	# Read existing config file
 	try:
-		with open(marlin_json, 'r') as infile:
+		with marlin_json.open() as infile:
 			conf = json.load(infile)
 			if conf['__INITIAL_HASH'] == hashes:
 				# Same configuration, skip recomputing the building signature
@@ -109,7 +111,10 @@ def compute_build_signature(env):
 
 		defines[key] = value if len(value) else ""
 
-	if not 'CONFIGURATION_EMBEDDING' in defines:
+	#
+	# Continue to gather data for CONFIGURATION_EMBEDDING or CONFIG_DUMP
+	#
+	if not ('CONFIGURATION_EMBEDDING' in defines or 'CONFIG_DUMP' in defines):
 		return
 
 	# Second step is to filter useless macro
@@ -145,6 +150,71 @@ def compute_build_signature(env):
 			if key in conf_defines[header]:
 				data[header][key] = resolved_defines[key]
 
+	# Every python needs this toy
+	def tryint(key):
+		try:
+			return int(defines[key])
+		except:
+			return 0
+
+	config_dump = tryint('CONFIG_DUMP')
+
+	#
+	# Produce an INI file if CONFIG_DUMP == 2
+	#
+	if config_dump == 2:
+		print("Generating config.ini ...")
+		ignore = ('CONFIGURATION_H_VERSION', 'CONFIGURATION_ADV_H_VERSION', 'CONFIG_DUMP')
+		filegrp = { 'Configuration.h':'config:basic', 'Configuration_adv.h':'config:advanced' }
+		config_ini = build_path / 'config.ini'
+		with config_ini.open('w') as outfile:
+			outfile.write('#\n# Marlin Firmware\n# config.ini - Options to apply before the build\n#\n')
+			# Loop through the data array of arrays
+			for header in data:
+				if header.startswith('__'):
+					continue
+				outfile.write('\n[' + filegrp[header] + ']\n')
+				for key in sorted(data[header]):
+					if key not in ignore:
+						val = 'on' if data[header][key] == '' else data[header][key]
+						outfile.write('{0:40}{1}'.format(key.lower(), ' = ' + val) + '\n')
+
+	#
+	# Produce a schema.json file if CONFIG_DUMP == 3
+	#
+	if config_dump >= 3:
+		try:
+			conf_schema = schema.extract()
+		except Exception as exc:
+			print("Error: " + str(exc))
+			conf_schema = None
+
+		if conf_schema:
+			#
+			# Produce a schema.json file if CONFIG_DUMP == 3
+			#
+			if config_dump in (3, 13):
+				print("Generating schema.json ...")
+				schema.dump_json(conf_schema, build_path / 'schema.json')
+				if config_dump == 13:
+					schema.group_options(conf_schema)
+					schema.dump_json(conf_schema, build_path / 'schema_grouped.json')
+
+			#
+			# Produce a schema.yml file if CONFIG_DUMP == 4
+			#
+			elif config_dump == 4:
+				print("Generating schema.yml ...")
+				try:
+					import yaml
+				except ImportError:
+					env.Execute(env.VerboseAction(
+						'$PYTHONEXE -m pip install "pyyaml"',
+						"Installing YAML for schema.yml export",
+					))
+					import yaml
+				schema.dump_yaml(conf_schema, build_path / 'schema.yml')
+
 	# Append the source code version and date
 	data['VERSION'] = {}
 	data['VERSION']['DETAILED_BUILD_VERSION'] = resolved_defines['DETAILED_BUILD_VERSION']
@@ -156,10 +226,17 @@ def compute_build_signature(env):
 		pass
 
 	#
-	# Produce a JSON file for CONFIGURATION_EMBEDDING or CONFIG_DUMP > 0
+	# Produce a JSON file for CONFIGURATION_EMBEDDING or CONFIG_DUMP == 1
 	#
-	with open(marlin_json, 'w') as outfile:
-		json.dump(data, outfile, separators=(',', ':'))
+	if config_dump == 1 or 'CONFIGURATION_EMBEDDING' in defines:
+		with marlin_json.open('w') as outfile:
+			json.dump(data, outfile, separators=(',', ':'))
+
+	#
+	# The rest only applies to CONFIGURATION_EMBEDDING
+	#
+	if not 'CONFIGURATION_EMBEDDING' in defines:
+		return
 
 	# Compress the JSON file as much as we can
 	compress_file(marlin_json, marlin_zip)
@@ -173,11 +250,11 @@ def compute_build_signature(env):
 			+ b'const unsigned char mc_zip[] PROGMEM = {\n '
 		)
 		count = 0
-		for b in open(os.path.join(build_dir, 'mc.zip'), 'rb').read():
+		for b in (build_path / 'mc.zip').open('rb').read():
 			result_file.write(b' 0x%02X,' % b)
 			count += 1
-			if (count % 16 == 0):
-			 	result_file.write(b'\n ')
-		if (count % 16):
+			if count % 16 == 0:
+				result_file.write(b'\n ')
+		if count % 16:
 			result_file.write(b'\n')
 		result_file.write(b'};\n')
diff --git a/platformio.ini b/platformio.ini
index 26f58662cc06..b2f178ec0fab 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -16,6 +16,7 @@ boards_dir   = buildroot/share/PlatformIO/boards
 default_envs = mega2560
 include_dir  = Marlin
 extra_configs =
+    Marlin/config.ini
     ini/avr.ini
     ini/due.ini
     ini/esp32.ini
@@ -44,12 +45,13 @@ extra_configs =
 build_flags        = -g3 -D__MARLIN_FIRMWARE__ -DNDEBUG
                      -fmax-errors=5
 extra_scripts      =
+  pre:buildroot/share/PlatformIO/scripts/configuration.py
   pre:buildroot/share/PlatformIO/scripts/common-dependencies.py
   pre:buildroot/share/PlatformIO/scripts/common-cxxflags.py
   pre:buildroot/share/PlatformIO/scripts/preflight-checks.py
   post:buildroot/share/PlatformIO/scripts/common-dependencies-post.py
 lib_deps           =
-default_src_filter = +<src/*> -<src/config> -<src/HAL> +<src/HAL/shared>
+default_src_filter = +<src/*> -<src/config> -<src/HAL> +<src/HAL/shared> -<Marlin/Marlin.ino>
   -<src/lcd/HD44780> -<src/lcd/TFTGLCD> -<src/lcd/dogm> -<src/lcd/tft> -<src/lcd/tft_io>
   -<src/HAL/STM32/tft> -<src/HAL/STM32F1/tft>
   -<src/lcd/e3v2/common> -<src/lcd/e3v2/creality> -<src/lcd/e3v2/proui> -<src/lcd/e3v2/jyersui> -<src/lcd/e3v2/marlinui>