text: /*
    settings.c - eeprom configuration handling
    Part of Grbl
  
    Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC
    Copyright (c) 2009-2011 Simen Svale Skogsrud
  
    Grbl is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
  
    Grbl is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
  
    You should have received a copy of the GNU General Public License
    along with Grbl.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  #include "grbl.h"
  
  settings_t settings;
  
  const __flash settings_t defaults = {\
      .pulse_microseconds = DEFAULT_STEP_PULSE_MICROSECONDS,
      .stepper_idle_lock_time = DEFAULT_STEPPER_IDLE_LOCK_TIME,
      .step_invert_mask = DEFAULT_STEPPING_INVERT_MASK,
      .dir_invert_mask = DEFAULT_DIRECTION_INVERT_MASK,
      .status_report_mask = DEFAULT_STATUS_REPORT_MASK,
      .junction_deviation = DEFAULT_JUNCTION_DEVIATION,
      .arc_tolerance = DEFAULT_ARC_TOLERANCE,
      .rpm_max = DEFAULT_SPINDLE_RPM_MAX,
      .rpm_min = DEFAULT_SPINDLE_RPM_MIN,
      .homing_dir_mask = DEFAULT_HOMING_DIR_MASK,
      .homing_feed_rate = DEFAULT_HOMING_FEED_RATE,
      .homing_seek_rate = DEFAULT_HOMING_SEEK_RATE,
      .homing_debounce_delay = DEFAULT_HOMING_DEBOUNCE_DELAY,
      .homing_pulloff = DEFAULT_HOMING_PULLOFF,
      .flags = (DEFAULT_REPORT_INCHES << BIT_REPORT_INCHES) | \
               (DEFAULT_LASER_MODE << BIT_LASER_MODE) | \
               (DEFAULT_INVERT_ST_ENABLE << BIT_INVERT_ST_ENABLE) | \
               (DEFAULT_HARD_LIMIT_ENABLE << BIT_HARD_LIMIT_ENABLE) | \
               (DEFAULT_HOMING_ENABLE << BIT_HOMING_ENABLE) | \
               (DEFAULT_SOFT_LIMIT_ENABLE << BIT_SOFT_LIMIT_ENABLE) | \
               (DEFAULT_INVERT_LIMIT_PINS << BIT_INVERT_LIMIT_PINS) | \
               (DEFAULT_INVERT_PROBE_PIN << BIT_INVERT_PROBE_PIN),
      .steps_per_mm[X_AXIS] = DEFAULT_X_STEPS_PER_MM,
      .steps_per_mm[Y_AXIS] = DEFAULT_Y_STEPS_PER_MM,
      .steps_per_mm[Z_AXIS] = DEFAULT_Z_STEPS_PER_MM,
      .max_rate[X_AXIS] = DEFAULT_X_MAX_RATE,
      .max_rate[Y_AXIS] = DEFAULT_Y_MAX_RATE,
      .max_rate[Z_AXIS] = DEFAULT_Z_MAX_RATE,
      .acceleration[X_AXIS] = DEFAULT_X_ACCELERATION,
      .acceleration[Y_AXIS] = DEFAULT_Y_ACCELERATION,
      .acceleration[Z_AXIS] = DEFAULT_Z_ACCELERATION,
      .max_travel[X_AXIS] = (-DEFAULT_X_MAX_TRAVEL),
      .max_travel[Y_AXIS] = (-DEFAULT_Y_MAX_TRAVEL),
      .max_travel[Z_AXIS] = (-DEFAULT_Z_MAX_TRAVEL),
  	.backlash_per_axis[X_AXIS]=DEFAULT_X_BACKLASH_MM,
  	.backlash_per_axis[Y_AXIS]=DEFAULT_Y_BACKLASH_MM,
  	.backlash_per_axis[Z_AXIS]=DEFAULT_Z_BACKLASH_MM,};
  
  
  // Method to store startup lines into EEPROM
  void settings_store_startup_line(uint8_t n, char *line)
  {
    #ifdef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE
      protocol_buffer_synchronize(); // A startup line may contain a motion and be executing. 
    #endif
    uint32_t addr = n*(LINE_BUFFER_SIZE+1)+EEPROM_ADDR_STARTUP_BLOCK;
    memcpy_to_eeprom_with_checksum(addr,(char*)line, LINE_BUFFER_SIZE);
  }
  
  
  // Method to store build info into EEPROM
  // NOTE: This function can only be called in IDLE state.
  void settings_store_build_info(char *line)
  {
    // Build info can only be stored when state is IDLE.
    memcpy_to_eeprom_with_checksum(EEPROM_ADDR_BUILD_INFO,(char*)line, LINE_BUFFER_SIZE);
  }
  
  
  // Method to store coord data parameters into EEPROM
  void settings_write_coord_data(uint8_t coord_select, float *coord_data)
  {
    #ifdef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE
      protocol_buffer_synchronize();
    #endif
    uint32_t addr = coord_select*(sizeof(float)*N_AXIS+1) + EEPROM_ADDR_PARAMETERS;
    memcpy_to_eeprom_with_checksum(addr,(char*)coord_data, sizeof(float)*N_AXIS);
  }
  
  
  // Method to store Grbl global settings struct and version number into EEPROM
  // NOTE: This function can only be called in IDLE state.
  void write_global_settings()
  {
    eeprom_put_char(0, SETTINGS_VERSION);
    memcpy_to_eeprom_with_checksum(EEPROM_ADDR_GLOBAL, (char*)&settings, sizeof(settings_t));
  }
  
  
  // Method to restore EEPROM-saved Grbl global settings back to defaults.
  void settings_restore(uint8_t restore_flag) {
    if (restore_flag & SETTINGS_RESTORE_DEFAULTS) {
      settings = defaults;
      write_global_settings();
    }
  
    if (restore_flag & SETTINGS_RESTORE_PARAMETERS) {
      uint8_t idx;
      float coord_data[N_AXIS];
      memset(&coord_data, 0, sizeof(coord_data));
      for (idx=0; idx <= SETTING_INDEX_NCOORD; idx++) { settings_write_coord_data(idx, coord_data); }
    }
  
    if (restore_flag & SETTINGS_RESTORE_STARTUP_LINES) {
      #if N_STARTUP_LINE > 0
        eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK, 0);
        eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK+1, 0); // Checksum
      #endif
      #if N_STARTUP_LINE > 1
        eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK+(LINE_BUFFER_SIZE+1), 0);
        eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK+(LINE_BUFFER_SIZE+2), 0); // Checksum
      #endif
    }
  
    if (restore_flag & SETTINGS_RESTORE_BUILD_INFO) {
      eeprom_put_char(EEPROM_ADDR_BUILD_INFO , 0);
      eeprom_put_char(EEPROM_ADDR_BUILD_INFO+1 , 0); // Checksum
    }
  }
  
  
  // Reads startup line from EEPROM. Updated pointed line string data.
  uint8_t settings_read_startup_line(uint8_t n, char *line)
  {
    uint32_t addr = n*(LINE_BUFFER_SIZE+1)+EEPROM_ADDR_STARTUP_BLOCK;
    if (!(memcpy_from_eeprom_with_checksum((char*)line, addr, LINE_BUFFER_SIZE))) {
      // Reset line with default value
      line[0] = 0; // Empty line
      settings_store_startup_line(n, line);
      return(false);
    }
    return(true);
  }
  
  
  // Reads startup line from EEPROM. Updated pointed line string data.
  uint8_t settings_read_build_info(char *line)
  {
    if (!(memcpy_from_eeprom_with_checksum((char*)line, EEPROM_ADDR_BUILD_INFO, LINE_BUFFER_SIZE))) {
      // Reset line with default value
      line[0] = 0; // Empty line
      settings_store_build_info(line);
      return(false);
    }
    return(true);
  }
  
  
  // Read selected coordinate data from EEPROM. Updates pointed coord_data value.
  uint8_t settings_read_coord_data(uint8_t coord_select, float *coord_data)
  {
    uint32_t addr = coord_select*(sizeof(float)*N_AXIS+1) + EEPROM_ADDR_PARAMETERS;
    if (!(memcpy_from_eeprom_with_checksum((char*)coord_data, addr, sizeof(float)*N_AXIS))) {
      // Reset with default zero vector
      clear_vector_float(coord_data);
      settings_write_coord_data(coord_select,coord_data);
      return(false);
    }
    return(true);
  }
  
  
  // Reads Grbl global settings struct from EEPROM.
  uint8_t read_global_settings() {
    // Check version-byte of eeprom
    uint8_t version = eeprom_get_char(0);
    if (version == SETTINGS_VERSION) {
      // Read settings-record and check checksum
      if (!(memcpy_from_eeprom_with_checksum((char*)&settings, EEPROM_ADDR_GLOBAL, sizeof(settings_t)))) {
        return(false);
      }
    } else {
      return(false);
    }
    return(true);
  }
  
  
  // A helper method to set settings from command line
  uint8_t settings_store_global_setting(uint8_t parameter, float value) {
    if (value < 0.0) { return(STATUS_NEGATIVE_VALUE); }
    if (parameter >= AXIS_SETTINGS_START_VAL) {
      // Store axis configuration. Axis numbering sequence set by AXIS_SETTING defines.
      // NOTE: Ensure the setting index corresponds to the report.c settings printout.
      parameter -= AXIS_SETTINGS_START_VAL;
      uint8_t set_idx = 0;
      while (set_idx < AXIS_N_SETTINGS) {
        if (parameter < N_AXIS) {
          // Valid axis setting found.
          switch (set_idx) {
            case 0:
              #ifdef MAX_STEP_RATE_HZ
                if (value*settings.max_rate[parameter] > (MAX_STEP_RATE_HZ*60.0)) { return(STATUS_MAX_STEP_RATE_EXCEEDED); }
              #endif
              settings.steps_per_mm[parameter] = value;
              break;
            case 1:
              #ifdef MAX_STEP_RATE_HZ
                if (value*settings.steps_per_mm[parameter] > (MAX_STEP_RATE_HZ*60.0)) {  return(STATUS_MAX_STEP_RATE_EXCEEDED); }
              #endif
              settings.max_rate[parameter] = value;
              break;
            case 2: settings.acceleration[parameter] = value*60*60; break; // Convert to mm/min^2 for grbl internal use.
            case 3: settings.max_travel[parameter] = -value; break;  // Store as negative for grbl internal use.
  		  case 4: settings.backlash_per_axis[parameter] = value; break;  
          }
          break; // Exit while-loop after setting has been configured and proceed to the EEPROM write call.
        } else {
          set_idx++;
          // If axis index greater than N_AXIS or setting index greater than number of axis settings, error out.
          if ((parameter < AXIS_SETTINGS_INCREMENT) || (set_idx == AXIS_N_SETTINGS)) { return(STATUS_INVALID_STATEMENT); }
          parameter -= AXIS_SETTINGS_INCREMENT;
        }
      }
    } else {
      // Store non-axis Grbl settings
      uint8_t int_value = trunc(value);
      switch(parameter) {
        case 0:
          if (int_value < 3) { return(STATUS_SETTING_STEP_PULSE_MIN); }
          settings.pulse_microseconds = int_value; break;
        case 1: settings.stepper_idle_lock_time = int_value; break;
        case 2:
          settings.step_invert_mask = int_value;
          st_generate_step_dir_invert_masks(); // Regenerate step and direction port invert masks.
          break;
        case 3:
          settings.dir_invert_mask = int_value;
          st_generate_step_dir_invert_masks(); // Regenerate step and direction port invert masks.
          break;
        case 4: // Reset to ensure change. Immediate re-init may cause problems.
          if (int_value) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; }
          else { settings.flags &= ~BITFLAG_INVERT_ST_ENABLE; }
          break;
        case 5: // Reset to ensure change. Immediate re-init may cause problems.
          if (int_value) { settings.flags |= BITFLAG_INVERT_LIMIT_PINS; }
          else { settings.flags &= ~BITFLAG_INVERT_LIMIT_PINS; }
          break;
        case 6: // Reset to ensure change. Immediate re-init may cause problems.
          if (int_value) { settings.flags |= BITFLAG_INVERT_PROBE_PIN; }
          else { settings.flags &= ~BITFLAG_INVERT_PROBE_PIN; }
          probe_configure_invert_mask(false);
          break;
        case 10: settings.status_report_mask = int_value; break;
        case 11: settings.junction_deviation = value; break;
        case 12: settings.arc_tolerance = value; break;
        case 13:
          if (int_value) { settings.flags |= BITFLAG_REPORT_INCHES; }
          else { settings.flags &= ~BITFLAG_REPORT_INCHES; }
          system_flag_wco_change(); // Make sure WCO is immediately updated.
          break;
        case 20:
          if (int_value) {
            if (bit_isfalse(settings.flags, BITFLAG_HOMING_ENABLE)) { return(STATUS_SOFT_LIMIT_ERROR); }
            settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE;
          } else { settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE; }
          break;
        case 21:
          if (int_value) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; }
          else { settings.flags &= ~BITFLAG_HARD_LIMIT_ENABLE; }
          limits_init(); // Re-init to immediately change. NOTE: Nice to have but could be problematic later.
          break;
        case 22:
          if (int_value) { settings.flags |= BITFLAG_HOMING_ENABLE; }
          else {
            settings.flags &= ~BITFLAG_HOMING_ENABLE;
            settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE; // Force disable soft-limits.
          }
          break;
        case 23: settings.homing_dir_mask = int_value; break;
        case 24: settings.homing_feed_rate = value; break;
        case 25: settings.homing_seek_rate = value; break;
        case 26: settings.homing_debounce_delay = int_value; break;
        case 27: settings.homing_pulloff = value; break;
        case 30: settings.rpm_max = value; spindle_init(); break; // Re-initialize spindle rpm calibration
        case 31: settings.rpm_min = value; spindle_init(); break; // Re-initialize spindle rpm calibration
        case 32:
          if (int_value) { settings.flags |= BITFLAG_LASER_MODE; }
          else { settings.flags &= ~BITFLAG_LASER_MODE; }
          break;
        default:
          return(STATUS_INVALID_STATEMENT);
      }
    }
    write_global_settings();
    return(STATUS_OK);
  }
  
  
  // Initialize the config subsystem
  void settings_init() {
    if(!read_global_settings()) {
      report_status_message(STATUS_SETTING_READ_FAIL);
      settings_restore(SETTINGS_RESTORE_ALL); // Force restore all EEPROM data.
      report_grbl_settings();
    }
  }
  
  
  // Returns step pin mask according to Grbl internal axis indexing.
  uint8_t get_step_pin_mask(uint8_t axis_idx)
  {
    #ifdef DEFAULTS_RAMPS_BOARD
      if ( axis_idx == X_AXIS ) { return((1<<STEP_BIT(X_AXIS))); }
      if ( axis_idx == Y_AXIS ) { return((1<<STEP_BIT(Y_AXIS))); }
      return((1<<STEP_BIT(Z_AXIS)));
    #else
      if ( axis_idx == X_AXIS ) { return((1<<X_STEP_BIT)); }
      if ( axis_idx == Y_AXIS ) { return((1<<Y_STEP_BIT)); }
      return((1<<Z_STEP_BIT));
    #endif // DEFAULTS_RAMPS_BOARD
  }
  
  
  // Returns direction pin mask according to Grbl internal axis indexing.
  uint8_t get_direction_pin_mask(uint8_t axis_idx)
  {
    #ifdef DEFAULTS_RAMPS_BOARD
      if ( axis_idx == X_AXIS ) { return((1<<DIRECTION_BIT(X_AXIS))); }
      if ( axis_idx == Y_AXIS ) { return((1<<DIRECTION_BIT(Y_AXIS))); }
      return((1<<DIRECTION_BIT(Z_AXIS)));
    #else
      if ( axis_idx == X_AXIS ) { return((1<<X_DIRECTION_BIT)); }
      if ( axis_idx == Y_AXIS ) { return((1<<Y_DIRECTION_BIT)); }
      return((1<<Z_DIRECTION_BIT));
    #endif // DEFAULTS_RAMPS_BOARD
  }
  
  
  // Returns limit pin mask according to Grbl internal axis indexing.
  
  #ifdef DEFAULTS_RAMPS_BOARD
    uint8_t get_min_limit_pin_mask(uint8_t axis_idx)
    {
      if ( axis_idx == X_AXIS ) { return((1<<LIMIT_BIT(X_AXIS))); }
      if ( axis_idx == Y_AXIS ) { return((1<<LIMIT_BIT(Y_AXIS))); }
      return((1<<LIMIT_BIT(Z_AXIS)));
    }
  
     uint8_t get_max_limit_pin_mask(uint8_t axis_idx)
     {
       if ( axis_idx == X_AXIS ) { return((1<<LIMIT_BIT(X_AXIS))); }
       if ( axis_idx == Y_AXIS ) { return((1<<LIMIT_BIT(Y_AXIS))); }
       return((1<<LIMIT_BIT(Z_AXIS)));
    }
  #else
    uint8_t get_limit_pin_mask(uint8_t axis_idx)
    {
      if ( axis_idx == X_AXIS ) { return((1<<X_LIMIT_BIT)); }
      if ( axis_idx == Y_AXIS ) { return((1<<Y_LIMIT_BIT)); }
      return((1<<Z_LIMIT_BIT));
    }
  #endif //DEFAULTS_RAMPS_BOARD
  
  