void CardReader::chdir(const char* relpath) {
  SdFile newfile;
  SdFile* parent = &root;
  if (workDir.isOpen()) parent = &workDir;
  if (!newfile.open(*parent, relpath, O_READ)) {
    ECHO_LMV(DB, SERIAL_SD_CANT_ENTER_SUBDIR, relpath);
  } else {
    if (workDirDepth < MAX_DIR_DEPTH) {
      ++workDirDepth;
      for (int d = workDirDepth; d--;) workDirParents[d + 1] = workDirParents[d];
      workDirParents[0] = *parent;
    }
    workDir = newfile;
  }
}
/**
 * Dive into a folder and recurse depth-first to perform a pre-set operation lsAction:
 *   LS_Count       - Add +1 to nrFiles for every file within the parent
 *   LS_GetFilename - Get the filename of the file indexed by nrFiles
 *   LS_SerialPrint - Print the full path of each file to serial output
 */
void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/) {
  dir_t p;
  uint8_t cnt = 0;

  // Read the next entry from a directory
  while (parent.readDir(p, longFilename) > 0) {

    // If the entry is a directory and the action is LS_SerialPrint
    if (DIR_IS_SUBDIR(&p) && lsAction != LS_Count && lsAction != LS_GetFilename) {

      // Get the short name for the item, which we know is a folder
      char lfilename[FILENAME_LENGTH];
      createFilename(lfilename, p);

      // Allocate enough stack space for the full path to a folder, trailing slash, and nul
      boolean prepend_is_empty = (prepend[0] == '\0');
      int len = (prepend_is_empty ? 1 : strlen(prepend)) + strlen(lfilename) + 1 + 1;
      char path[len];

      // Append the FOLDERNAME12/ to the passed string.
      // It contains the full path to the "parent" argument.
      // We now have the full path to the item in this folder.
      strcpy(path, prepend_is_empty ? "/" : prepend); // root slash if prepend is empty
      strcat(path, lfilename); // FILENAME_LENGTH-1 characters maximum
      strcat(path, "/");       // 1 character

      // Serial.print(path);

      // Get a new directory object using the full path
      // and dive recursively into it.
      SdFile dir;
      if (!dir.open(parent, lfilename, O_READ)) {
        if (lsAction == LS_SerialPrint) {
          ECHO_LMV(ER, MSG_SD_CANT_OPEN_SUBDIR, lfilename);
        }
      }
      lsDive(path, dir);
      // close() is done automatically by destructor of SdFile
    }
    else {
      char pn0 = p.name[0];
      if (pn0 == DIR_NAME_FREE) break;
      if (pn0 == DIR_NAME_DELETED || pn0 == '.') continue;
      if (longFilename[0] == '.') continue;

      if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;

      filenameIsDir = DIR_IS_SUBDIR(&p);

      if (!filenameIsDir && (p.name[8] != 'G' || p.name[9] == '~')) continue;

      switch (lsAction) {
        case LS_Count:
          nrFiles++;
          break;
        case LS_SerialPrint:
          createFilename(filename, p);
          ECHO_V(prepend);
          ECHO_EV(filename);
          break;
        case LS_GetFilename:
          createFilename(filename, p);
          if (match != NULL) {
            if (strcasecmp(match, filename) == 0) return;
          }
          else if (cnt == nrFiles) return;
          cnt++;
          break;
      }
    }
  } // while readDir
}
void CardReader::openFile(char* name, bool read, bool replace_current/*=true*/, bool lcd_status/*=true*/) {
  if (!cardOK) return;
  if (file.isOpen()) { //replacing current file by new file, or subfile call
    if (!replace_current) {
      if (file_subcall_ctr > SD_PROCEDURE_DEPTH - 1) {
        ECHO_LMV(ER, MSG_SD_MAX_DEPTH, SD_PROCEDURE_DEPTH);
        kill(PSTR(MSG_KILLED));
        return;
      }

      ECHO_SMV(DB, "SUBROUTINE CALL target:\"", name);
      ECHO_M("\" parent:\"");

      //store current filename and position
      getAbsFilename(filenames[file_subcall_ctr]);

      ECHO_V(filenames[file_subcall_ctr]);
      ECHO_EMV("\" pos", sdpos);
      filespos[file_subcall_ctr] = sdpos;
      file_subcall_ctr++;
    }
    else {
     ECHO_LMV(DB, "Now doing file: ", name);
    }
    file.close();
  }
  else { // opening fresh file
    file_subcall_ctr = 0; // resetting procedure depth in case user cancels print while in procedure
    ECHO_LMV(DB, "Now fresh file: ", name);
  }
  sdprinting = false;

  SdFile myDir;
  curDir = &root;
  char *fname = name;

  char *dirname_start, *dirname_end;
  if (name[0] == '/') {
    dirname_start = &name[1];
    while (dirname_start > 0) {
      dirname_end = strchr(dirname_start, '/');
      if (dirname_end > 0 && dirname_end > dirname_start) {
        char subdirname[FILENAME_LENGTH];
        strncpy(subdirname, dirname_start, dirname_end - dirname_start);
        subdirname[dirname_end - dirname_start] = 0;
        ECHO_EV(subdirname);
        if (!myDir.open(curDir, subdirname, O_READ)) {
          ECHO_MV(MSG_SD_OPEN_FILE_FAIL, subdirname);
          ECHO_C('.');
          return;
        }
        else {
          //ECHO_EM("dive ok");
        }

        curDir = &myDir;
        dirname_start = dirname_end + 1;
      }
      else { // the remainder after all /fsa/fdsa/ is the filename
        fname = dirname_start;
        //ECHO_EM("remainder");
        //ECHO_EV(fname);
        break;
      }
    }
  }
  else { //relative path
    curDir = &workDir;
  }

  if (read) {
    if (file.open(curDir, fname, O_READ)) {
      filesize = file.fileSize();
      ECHO_MV(MSG_SD_FILE_OPENED, fname);
      ECHO_EMV(MSG_SD_SIZE, filesize);
      sdpos = 0;

      ECHO_EM(MSG_SD_FILE_SELECTED);
      getfilename(0, fname);
      if(lcd_status) lcd_setstatus(longFilename[0] ? longFilename : fname);
    }
    else {
      ECHO_MV(MSG_SD_OPEN_FILE_FAIL, fname);
      ECHO_PGM(".\n");
    }
  }
  else { //write
    if (!file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)) {
      ECHO_MV(MSG_SD_OPEN_FILE_FAIL, fname);
      ECHO_PGM(".\n");
    }
    else {
      saving = true;
      ECHO_EMV(MSG_SD_WRITE_TO_FILE, name);
      if(lcd_status) lcd_setstatus(fname);
    }
  }
}
/**
 * M503 - Print Configuration
 */
void Config_PrintSettings(bool forReplay) {
  // Always have this function, even with EEPROM_SETTINGS disabled, the current values will be shown

  CONFIG_ECHO_START("Steps per unit:");
  ECHO_SMV(CFG, "  M92 X", planner.axis_steps_per_mm[X_AXIS]);
  ECHO_MV(" Y", planner.axis_steps_per_mm[Y_AXIS]);
  ECHO_MV(" Z", planner.axis_steps_per_mm[Z_AXIS]);
  ECHO_EMV(" E", planner.axis_steps_per_mm[E_AXIS]);
  #if EXTRUDERS > 1
    for (short i = 1; i < EXTRUDERS; i++) {
      ECHO_SMV(CFG, "  M92 T", i);
      ECHO_EMV(" E", planner.axis_steps_per_mm[E_AXIS + i]);
    }
  #endif //EXTRUDERS > 1

  #if MECH(SCARA)
    CONFIG_ECHO_START("Scaling factors:");
    ECHO_SMV(CFG, "  M365 X", axis_scaling[X_AXIS]);
    ECHO_MV(" Y", axis_scaling[Y_AXIS]);
    ECHO_EMV(" Z", axis_scaling[Z_AXIS]);
  #endif // SCARA

  CONFIG_ECHO_START("Maximum feedrates (mm/s):");
  ECHO_SMV(CFG, "  M203 X", planner.max_feedrate[X_AXIS]);
  ECHO_MV(" Y", planner.max_feedrate[Y_AXIS] );
  ECHO_MV(" Z", planner.max_feedrate[Z_AXIS] );
  ECHO_EMV(" E", planner.max_feedrate[E_AXIS]);
  #if EXTRUDERS > 1
    for (short i = 1; i < EXTRUDERS; i++) {
      ECHO_SMV(CFG, "  M203 T", i);
      ECHO_EMV(" E", planner.max_acceleration_mm_per_s2[E_AXIS + i]);
    }
  #endif //EXTRUDERS > 1

  CONFIG_ECHO_START("Maximum Acceleration (mm/s2):");
  ECHO_SMV(CFG, "  M201 X", planner.max_acceleration_mm_per_s2[X_AXIS] );
  ECHO_MV(" Y", planner.max_acceleration_mm_per_s2[Y_AXIS] );
  ECHO_MV(" Z", planner.max_acceleration_mm_per_s2[Z_AXIS] );
  ECHO_EMV(" E", planner.max_acceleration_mm_per_s2[E_AXIS]);
  #if EXTRUDERS > 1
    for (int8_t i = 1; i < EXTRUDERS; i++) {
      ECHO_SMV(CFG, "  M201 T", i);
      ECHO_EMV(" E", planner.max_acceleration_mm_per_s2[E_AXIS + i]);
    }
  #endif //EXTRUDERS > 1
  
  CONFIG_ECHO_START("Accelerations: P=printing, V=travel and T* R=retract");
  ECHO_SMV(CFG,"  M204 P", planner.acceleration);
  ECHO_EMV(" V", planner.travel_acceleration);
  #if EXTRUDERS > 0
    for (int8_t i = 0; i < EXTRUDERS; i++) {
      ECHO_SMV(CFG, "  M204 T", i);
      ECHO_EMV(" R", planner.retract_acceleration[i]);
    }
  #endif

  CONFIG_ECHO_START("Advanced variables: S=Min feedrate (mm/s), V=Min travel feedrate (mm/s), B=minimum segment time (ms), X=maximum XY jerk (mm/s),  Z=maximum Z jerk (mm/s),  E=maximum E jerk (mm/s)");
  ECHO_SMV(CFG, "  M205 S", planner.min_feedrate );
  ECHO_MV(" V", planner.min_travel_feedrate );
  ECHO_MV(" B", planner.min_segment_time );
  ECHO_MV(" X", planner.max_xy_jerk );
  ECHO_MV(" Z", planner.max_z_jerk);
  ECHO_EMV(" E", planner.max_e_jerk[0]);
  #if (EXTRUDERS > 1)
    for(int8_t i = 1; i < EXTRUDERS; i++) {
      ECHO_SMV(CFG, "  M205 T", i);
      ECHO_EMV(" E" , planner.max_e_jerk[i]);
    }
  #endif

  CONFIG_ECHO_START("Home offset (mm):");
  ECHO_SMV(CFG, "  M206 X", home_offset[X_AXIS] );
  ECHO_MV(" Y", home_offset[Y_AXIS] );
  ECHO_EMV(" Z", home_offset[Z_AXIS] );

  CONFIG_ECHO_START("Hotend offset (mm):");
  for (int8_t h = 0; h < HOTENDS; h++) {
    ECHO_SMV(CFG, "  M218 T", h);
    ECHO_MV(" X", hotend_offset[X_AXIS][h]);
    ECHO_MV(" Y", hotend_offset[Y_AXIS][h]);
    ECHO_EMV(" Z", hotend_offset[Z_AXIS][h]);
  }

  #if HAS(LCD_CONTRAST)
    CONFIG_ECHO_START("LCD Contrast:");
    ECHO_LMV(CFG, "  M250 C", lcd_contrast);
  #endif

  #if ENABLED(MESH_BED_LEVELING)
    CONFIG_ECHO_START("Mesh bed leveling:");
    ECHO_SMV(CFG, "  M420 S", mbl.has_mesh() ? 1 : 0);
    ECHO_MV(" X", MESH_NUM_X_POINTS);
    ECHO_MV(" Y", MESH_NUM_Y_POINTS);
    ECHO_E;

    for (uint8_t py = 1; py <= MESH_NUM_Y_POINTS; py++) {
      for (uint8_t px = 1; px <= MESH_NUM_X_POINTS; px++) {
        ECHO_SMV(CFG, "  G29 S3 X", px);
        ECHO_MV(" Y", py);
        ECHO_EMV(" Z", mbl.z_values[py-1][px-1], 5);
      }
    }
  #endif

  #if HEATER_USES_AD595
    CONFIG_ECHO_START("AD595 Offset and Gain:");
    for (int8_t h = 0; h < HOTENDS; h++) {
      ECHO_SMV(CFG, "  M595 T", h);
      ECHO_MV(" O", ad595_offset[h]);
      ECHO_EMV(", S", ad595_gain[h]);
    }
  #endif // HEATER_USES_AD595

  #if MECH(DELTA)
    CONFIG_ECHO_START("Delta Geometry adjustment:");
    ECHO_SMV(CFG, "  M666 A", tower_adj[0], 3);
    ECHO_MV(" B", tower_adj[1], 3);
    ECHO_MV(" C", tower_adj[2], 3);
    ECHO_MV(" I", tower_adj[3], 3);
    ECHO_MV(" J", tower_adj[4], 3);
    ECHO_MV(" K", tower_adj[5], 3);
    ECHO_MV(" U", diagrod_adj[0], 3);
    ECHO_MV(" V", diagrod_adj[1], 3);
    ECHO_MV(" W", diagrod_adj[2], 3);
    ECHO_MV(" R", delta_radius);
    ECHO_MV(" D", delta_diagonal_rod);
    ECHO_EMV(" H", sw_endstop_max[2]);

    CONFIG_ECHO_START("Endstop Offsets:");
    ECHO_SMV(CFG, "  M666 X", endstop_adj[X_AXIS]);
    ECHO_MV(" Y", endstop_adj[Y_AXIS]);
    ECHO_EMV(" Z", endstop_adj[Z_AXIS]);

  #elif ENABLED(Z_DUAL_ENDSTOPS)
    CONFIG_ECHO_START("Z2 Endstop adjustement (mm):");
    ECHO_LMV(CFG, "  M666 Z", z_endstop_adj );
  #endif // DELTA
  
  /**
   * Auto Bed Leveling
   */
  #if HAS(BED_PROBE)
    CONFIG_ECHO_START("Z Probe offset (mm):");
    ECHO_LMV(CFG, "  M666 P", zprobe_zoffset);
  #endif

  #if ENABLED(ULTIPANEL)
    CONFIG_ECHO_START("Material heatup parameters:");
    ECHO_SMV(CFG, "  M145 S0 H", plaPreheatHotendTemp);
    ECHO_MV(" B", plaPreheatHPBTemp);
    ECHO_MV(" F", plaPreheatFanSpeed);
    ECHO_EM(" (Material PLA)");
    ECHO_SMV(CFG, "  M145 S1 H", absPreheatHotendTemp);
    ECHO_MV(" B", absPreheatHPBTemp);
    ECHO_MV(" F", absPreheatFanSpeed);
    ECHO_EM(" (Material ABS)");
    ECHO_SMV(CFG, "  M145 S2 H", gumPreheatHotendTemp);
    ECHO_MV(" B", gumPreheatHPBTemp);
    ECHO_MV(" F", gumPreheatFanSpeed);
    ECHO_EM(" (Material GUM)");
  #endif // ULTIPANEL

  #if ENABLED(PIDTEMP) || ENABLED(PIDTEMPBED) || ENABLED(PIDTEMPCHAMBER) || ENABLED(PIDTEMPCOOLER)
    CONFIG_ECHO_START("PID settings:");
    #if ENABLED(PIDTEMP)
      for (int8_t h = 0; h < HOTENDS; h++) {
        ECHO_SMV(CFG, "  M301 H", h);
        ECHO_MV(" P", PID_PARAM(Kp, h));
        ECHO_MV(" I", unscalePID_i(PID_PARAM(Ki, h)));
        ECHO_MV(" D", unscalePID_d(PID_PARAM(Kd, h)));
        #if ENABLED(PID_ADD_EXTRUSION_RATE)
          ECHO_MV(" C", PID_PARAM(Kc, h));
        #endif
        ECHO_E;
      }
      #if ENABLED(PID_ADD_EXTRUSION_RATE)
        ECHO_SMV(CFG, "  M301 L", lpq_len);
      #endif
    #endif
    #if ENABLED(PIDTEMPBED)
      ECHO_SMV(CFG, "  M304 P", bedKp);
      ECHO_MV(" I", unscalePID_i(bedKi));
      ECHO_EMV(" D", unscalePID_d(bedKd));
    #endif
    #if ENABLED(PIDTEMPCHAMBER)
      ECHO_SMV(CFG, "  M305 P", chamberKp);
      ECHO_MV(" I", unscalePID_i(chamberKi));
      ECHO_EMV(" D", unscalePID_d(chamberKd));
    #endif
    #if ENABLED(PIDTEMPCOOLER)
      ECHO_SMV(CFG, "  M306 P", coolerKp);
      ECHO_MV(" I", unscalePID_i(coolerKi));
      ECHO_EMV(" D", unscalePID_d(coolerKd));
    #endif
  #endif

  #if ENABLED(FWRETRACT)
    CONFIG_ECHO_START("Retract: S=Length (mm) F:Speed (mm/m) Z: ZLift (mm)");
    ECHO_SMV(CFG, "  M207 S", retract_length);
    #if EXTRUDERS > 1
      ECHO_MV(" W", retract_length_swap);
    #endif
    ECHO_MV(" F", retract_feedrate * 60);
    ECHO_EMV(" Z", retract_zlift);

    CONFIG_ECHO_START("Recover: S=Extra length (mm) F:Speed (mm/m)");
    ECHO_SMV(CFG, "  M208 S", retract_recover_length);
    #if EXTRUDERS > 1
      ECHO_MV(" W", retract_recover_length_swap);
    #endif
    ECHO_MV(" F", retract_recover_feedrate * 60);

    CONFIG_ECHO_START("Auto-Retract: S=0 to disable, 1 to interpret extrude-only moves as retracts or recoveries");
    ECHO_LMV(CFG, "  M209 S", autoretract_enabled ? 1 : 0);
  #endif // FWRETRACT

  if (volumetric_enabled) {
    CONFIG_ECHO_START("Filament settings:");
    ECHO_LMV(CFG, "  M200 D", filament_size[0]);

    #if EXTRUDERS > 1
      ECHO_LMV(CFG, "  M200 T1 D", filament_size[1]);
      #if EXTRUDERS > 2
        ECHO_LMV(CFG, "  M200 T2 D", filament_size[2]);
        #if EXTRUDERS > 3
          ECHO_LMV(CFG, "  M200 T3 D", filament_size[3]);
        #endif
      #endif
    #endif

  }
  else
    CONFIG_ECHO_START("  M200 D0");

  #if MB(ALLIGATOR)
    CONFIG_ECHO_START("Motor current:");
    ECHO_SMV(CFG, "  M906 X", motor_current[X_AXIS]);
    ECHO_MV(" Y", motor_current[Y_AXIS]);
    ECHO_MV(" Z", motor_current[Z_AXIS]);
    ECHO_EMV(" E", motor_current[E_AXIS]);
    #if DRIVER_EXTRUDERS > 1
      for (uint8_t i = 1; i < DRIVER_EXTRUDERS; i++) {
        ECHO_SMV(CFG, "  M906 T", i);
        ECHO_EMV(" E", motor_current[E_AXIS + i]);
      }
    #endif // DRIVER_EXTRUDERS > 1
  #endif // ALLIGATOR

  ConfigSD_PrintSettings(forReplay);

}
  /**
   * Print Configuration Settings - M503
   */
  void Config_PrintSettings(bool forReplay) {
    // Always have this function, even with EEPROM_SETTINGS disabled, the current values will be shown

    if (!forReplay) {
      ECHO_LM(CFG, "Steps per unit:");
    }
    ECHO_SMV(CFG, "  M92 X", axis_steps_per_unit[X_AXIS]);
    ECHO_MV(" Y", axis_steps_per_unit[Y_AXIS]);
    ECHO_MV(" Z", axis_steps_per_unit[Z_AXIS]);
    ECHO_EMV(" E", axis_steps_per_unit[E_AXIS]);
    #if EXTRUDERS > 1
      for (short i = 1; i < EXTRUDERS; i++) {
        ECHO_SMV(CFG, "  M92 T", i);
        ECHO_EMV(" E", axis_steps_per_unit[E_AXIS + i]);
      }
    #endif //EXTRUDERS > 1

    #if MECH(SCARA)
      if (!forReplay) {
        ECHO_LM(CFG, "Scaling factors:");
      }
      ECHO_SMV(CFG, "  M365 X", axis_scaling[X_AXIS]);
      ECHO_MV(" Y", axis_scaling[Y_AXIS]);
      ECHO_EMV(" Z", axis_scaling[Z_AXIS]);
    #endif // SCARA

    if (!forReplay) {
      ECHO_LM(CFG, "Maximum feedrates (mm/s):");
    }
    ECHO_SMV(CFG, "  M203 X", max_feedrate[X_AXIS]);
    ECHO_MV(" Y", max_feedrate[Y_AXIS] ); 
    ECHO_MV(" Z", max_feedrate[Z_AXIS] ); 
    ECHO_EMV(" E", max_feedrate[E_AXIS]);
    #if EXTRUDERS > 1
      for (short i = 1; i < EXTRUDERS; i++) {
        ECHO_SMV(CFG, "  M203 T", i);
        ECHO_EMV(" E", max_feedrate[E_AXIS + i]);
      }
    #endif //EXTRUDERS > 1

    if (!forReplay) {
      ECHO_LM(CFG, "Maximum Acceleration (mm/s2):");
    }
    ECHO_SMV(CFG, "  M201 X", max_acceleration_units_per_sq_second[X_AXIS] );
    ECHO_MV(" Y", max_acceleration_units_per_sq_second[Y_AXIS] );
    ECHO_MV(" Z", max_acceleration_units_per_sq_second[Z_AXIS] );
    ECHO_EMV(" E", max_acceleration_units_per_sq_second[E_AXIS]);
    #if EXTRUDERS > 1
      for (int8_t i = 1; i < EXTRUDERS; i++) {
        ECHO_SMV(CFG, "  M201 T", i);
        ECHO_EMV(" E", max_acceleration_units_per_sq_second[E_AXIS + i]);
      }
    #endif //EXTRUDERS > 1
    ECHO_E;
    
    if (!forReplay) {
      ECHO_LM(CFG, "Accelerations: P=printing, V=travel and T* R=retract");
    }
    ECHO_SMV(CFG,"  M204 P", acceleration);
    ECHO_EMV(" V", travel_acceleration);
    #if EXTRUDERS > 0
      for (int8_t i = 0; i < EXTRUDERS; i++) {
        ECHO_SMV(CFG, "  M204 T", i);
        ECHO_EMV(" R", retract_acceleration[i]);
      }
    #endif

    if (!forReplay) {
      ECHO_LM(CFG, "Advanced variables: S=Min feedrate (mm/s), V=Min travel feedrate (mm/s), B=minimum segment time (ms), X=maximum XY jerk (mm/s),  Z=maximum Z jerk (mm/s),  E=maximum E jerk (mm/s)");
    }
    ECHO_SMV(CFG, "  M205 S", minimumfeedrate );
    ECHO_MV(" V", mintravelfeedrate );
    ECHO_MV(" B", minsegmenttime );
    ECHO_MV(" X", max_xy_jerk );
    ECHO_MV(" Z", max_z_jerk);
    ECHO_EMV(" E", max_e_jerk[0]);
    #if (EXTRUDERS > 1)
      for(int8_t i = 1; i < EXTRUDERS; i++) {
        ECHO_SMV(CFG, "  M205 T", i);
        ECHO_EMV(" E" , max_e_jerk[i]);
      }
    #endif

    if (!forReplay) {
      ECHO_LM(CFG, "Home offset (mm):");
    }
    ECHO_SMV(CFG, "  M206 X", home_offset[X_AXIS] );
    ECHO_MV(" Y", home_offset[Y_AXIS] );
    ECHO_EMV(" Z", home_offset[Z_AXIS] );

    if (!forReplay) {
      ECHO_LM(CFG, "Hotend offset (mm):");
    }
    for (int8_t h = 0; h < HOTENDS; h++) {
      ECHO_SMV(CFG, "  M218 T", h);
      ECHO_MV(" X", hotend_offset[X_AXIS][h]);
      ECHO_MV(" Y", hotend_offset[Y_AXIS][h]);
      ECHO_EMV(" Z", hotend_offset[Z_AXIS][h]);
    }

    #if HEATER_USES_AD595
      if (!forReplay) {
        ECHO_LM(CFG, "AD595 Offset and Gain:");
      }
      for (int8_t h = 0; h < HOTENDS; h++) {
        ECHO_SMV(CFG, "  M595 T", h);
        ECHO_MV(" O", ad595_offset[h]);
        ECHO_EMV(", S", ad595_gain[h]);
      }
    #endif // HEATER_USES_AD595

    #if MECH(DELTA)
      if (!forReplay) {
        ECHO_LM(CFG, "Delta Geometry adjustment:");
      }
      ECHO_SMV(CFG, "  M666 A", tower_adj[0], 3);
      ECHO_MV(" B", tower_adj[1], 3);
      ECHO_MV(" C", tower_adj[2], 3);
      ECHO_MV(" I", tower_adj[3], 3);
      ECHO_MV(" J", tower_adj[4], 3);
      ECHO_MV(" K", tower_adj[5], 3);
      ECHO_MV(" U", diagrod_adj[0], 3);
      ECHO_MV(" V", diagrod_adj[1], 3);
      ECHO_MV(" W", diagrod_adj[2], 3);
      ECHO_MV(" R", delta_radius);
      ECHO_MV(" D", delta_diagonal_rod);
      ECHO_EMV(" H", sw_endstop_max[2]);

      if (!forReplay) {
        ECHO_LM(CFG, "Endstop Offsets:");
      }
      ECHO_SMV(CFG, "  M666 X", endstop_adj[X_AXIS]);
      ECHO_MV(" Y", endstop_adj[Y_AXIS]);
      ECHO_EMV(" Z", endstop_adj[Z_AXIS]);

      if (!forReplay) {
        ECHO_LM(CFG, "Z-Probe Offset:");
      }
      ECHO_SMV(CFG, "  M666 P X", z_probe_offset[0]);
      ECHO_MV(" Y", z_probe_offset[1]);
      ECHO_EMV(" Z", z_probe_offset[2]);

    #elif ENABLED(Z_DUAL_ENDSTOPS)
      if (!forReplay) {
        ECHO_LM(CFG, "Z2 Endstop adjustement (mm):");
      }
      ECHO_LMV(CFG, "  M666 Z", z_endstop_adj );
    #elif ENABLED(AUTO_BED_LEVELING_FEATURE)
      if (!forReplay) {
        ECHO_LM(CFG, "Z Probe offset (mm)");
      }
      ECHO_LMV(CFG, "  M666 P", zprobe_zoffset);
    #endif

    #if ENABLED(ULTIPANEL)
      if (!forReplay) {
        ECHO_LM(CFG, "Material heatup parameters:");
      }
      ECHO_SMV(CFG, "  M145 S0 H", plaPreheatHotendTemp);
      ECHO_MV(" B", plaPreheatHPBTemp);
      ECHO_MV(" F", plaPreheatFanSpeed);
      ECHO_EM(" (Material PLA)");
      ECHO_SMV(CFG, "  M145 S1 H", absPreheatHotendTemp);
      ECHO_MV(" B", absPreheatHPBTemp);
      ECHO_MV(" F", absPreheatFanSpeed);
      ECHO_EM(" (Material ABS)");
      ECHO_SMV(CFG, "  M145 S2 H", gumPreheatHotendTemp);
      ECHO_MV(" B", gumPreheatHPBTemp);
      ECHO_MV(" F", gumPreheatFanSpeed);
      ECHO_EM(" (Material GUM)");
    #endif // ULTIPANEL

    #if ENABLED(PIDTEMP) || ENABLED(PIDTEMPBED)
      if (!forReplay) {
        ECHO_LM(CFG, "PID settings:");
      }
      #if ENABLED(PIDTEMP)
        for (uint8_t h = 0; h < HOTENDS; h++) {
          ECHO_SMV(CFG, "  M301 H", h);
          ECHO_MV(" P", PID_PARAM(Kp, h));
          ECHO_MV(" I", unscalePID_i(PID_PARAM(Ki, h)));
          ECHO_MV(" D", unscalePID_d(PID_PARAM(Kd, h)));
          #if ENABLED(PID_ADD_EXTRUSION_RATE)
            ECHO_MV(" C", PID_PARAM(Kc, h));
          #endif
          ECHO_E;
        }
        #if ENABLED(PID_ADD_EXTRUSION_RATE)
          ECHO_SMV(CFG, "  M301 L", lpq_len);
        #endif
      #endif
      #if ENABLED(PIDTEMPBED)
        ECHO_SMV(CFG, "  M304 P", bedKp); // for compatibility with hosts, only echos values for E0
        ECHO_MV(" I", unscalePID_i(bedKi));
        ECHO_EMV(" D", unscalePID_d(bedKd));
      #endif
    #endif

    #if ENABLED(FWRETRACT)
      if (!forReplay) {
        ECHO_LM(CFG, "Retract: S=Length (mm) F:Speed (mm/m) Z: ZLift (mm)");
      }
      ECHO_SMV(CFG, "  M207 S", retract_length);
      ECHO_MV(" F", retract_feedrate*60);
      ECHO_EMV(" Z", retract_zlift);
      
      if (!forReplay) {
        ECHO_LM(CFG, "Recover: S=Extra length (mm) F:Speed (mm/m)");
      }
      ECHO_SMV(CFG, "  M208 S", retract_recover_length);
      ECHO_MV(" F", retract_recover_feedrate*60);
      
      if (!forReplay) {
        ECHO_LM(CFG, "Auto-Retract: S=0 to disable, 1 to interpret extrude-only moves as retracts or recoveries");
      }
      ECHO_LMV(CFG, "  M209 S", autoretract_enabled);

      #if EXTRUDERS > 1
        if (!forReplay) {
          ECHO_LM(CFG, "Multi-extruder settings:");
          ECHO_LMV(CFG, "   Swap retract length (mm):    ", retract_length_swap);
          ECHO_LMV(CFG, "   Swap rec. addl. length (mm): ", retract_recover_length_swap);
        }
      #endif // EXTRUDERS > 1

    #endif // FWRETRACT

    if (volumetric_enabled) {
      if (!forReplay) {
        ECHO_LM(CFG, "Filament settings:");
      }
      ECHO_LMV(CFG, "  M200 D", filament_size[0]);

      #if EXTRUDERS > 1
        ECHO_LMV(CFG, "  M200 T1 D", filament_size[1]);
        #if EXTRUDERS > 2
          ECHO_LMV(CFG, "  M200 T2 D", filament_size[2]);
          #if EXTRUDERS > 3
            ECHO_LMV(CFG, "  M200 T3 D", filament_size[3]);
          #endif
        #endif
      #endif

    } else {
      if (!forReplay) {
        ECHO_LM(CFG, "Filament settings: Disabled");
      }
    }

    #if MB(ALLIGATOR)
      if (!forReplay) {
        ECHO_LM(CFG, "Current:");
      }
      ECHO_SMV(CFG, "  M906 X", motor_current[X_AXIS]);
      ECHO_MV(" Y", motor_current[Y_AXIS]);
      ECHO_MV(" Z", motor_current[Z_AXIS]);
      ECHO_EMV(" E", motor_current[E_AXIS]);
      #if DRIVER_EXTRUDERS > 1
        for (uint8_t i = 1; i < DRIVER_EXTRUDERS; i++) {
          ECHO_SMV(CFG, "  M906 T", i);
          ECHO_EMV(" E", motor_current[E_AXIS + i]);
        }
      #endif // DRIVER_EXTRUDERS > 1
    #endif // ALLIGATOR

    ConfigSD_PrintSettings(forReplay);

  }