/* settings.c - non-volatile storage configuration handling Part of grblHAL Copyright (c) 2017-2025 Terje Io Copyright (c) 2011-2015 Sungeun K. Jeon Copyright (c) 2009-2011 Simen Svale Skogsrud grblHAL 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. grblHAL 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 grblHAL. If not, see . */ #include #include #include #include #include #include "hal.h" #include "config.h" #include "machine_limits.h" #include "nvs_buffer.h" #include "tool_change.h" #include "state_machine.h" #if ENABLE_BACKLASH_COMPENSATION #include "motion_control.h" #endif #if ENABLE_SPINDLE_LINEARIZATION #include #endif #if SPINDLE_SYNC_ENABLE extern void st_spindle_sync_cfg (settings_t *settings, settings_changed_flags_t changed); #endif settings_t settings; static const control_signals_t limits_override = { .limits_override = On }; const settings_restore_t settings_all = { .defaults = SETTINGS_RESTORE_DEFAULTS, .parameters = SETTINGS_RESTORE_PARAMETERS, .startup_lines = SETTINGS_RESTORE_STARTUP_LINES, .build_info = SETTINGS_RESTORE_BUILD_INFO, .driver_parameters = SETTINGS_RESTORE_DRIVER_PARAMETERS }; PROGMEM const settings_t defaults = { .version.id = SETTINGS_VERSION, .version.build = (GRBL_BUILD - 20000000UL), #if DEFAULT_LASER_MODE .mode = Mode_Laser, #elif DEFAULT_LATHE_MODE .mode = Mode_Lathe, #else .mode = Mode_Standard, #endif .junction_deviation = DEFAULT_JUNCTION_DEVIATION, .arc_tolerance = DEFAULT_ARC_TOLERANCE, .g73_retract = DEFAULT_G73_RETRACT, .report_interval = DEFAULT_AUTOREPORT_INTERVAL, .timezone = DEFAULT_TIMEZONE_OFFSET, .planner_buffer_blocks = DEFAULT_PLANNER_BUFFER_BLOCKS, .flags.legacy_rt_commands = DEFAULT_LEGACY_RTCOMMANDS, .flags.report_inches = DEFAULT_REPORT_INCHES, .flags.sleep_enable = DEFAULT_SLEEP_ENABLE && SLEEP_DURATION > 0.0f, .flags.compatibility_level = COMPATIBILITY_LEVEL, #if DEFAULT_DISABLE_G92_PERSISTENCE .flags.g92_is_volatile = 1, #else .flags.g92_is_volatile = 0, #endif .flags.disable_laser_during_hold = DEFAULT_DISABLE_LASER_DURING_HOLD, .flags.restore_after_feed_hold = DEFAULT_RESTORE_AFTER_FEED_HOLD, .flags.force_initialization_alarm = DEFAULT_FORCE_INITIALIZATION_ALARM, .flags.restore_overrides = DEFAULT_RESET_OVERRIDES, .flags.no_restore_position_after_M6 = DEFAULT_TOOLCHANGE_NO_RESTORE_POSITION, .flags.tool_change_at_g30 = DEFAULT_TOOLCHANGE_AT_G30, .flags.tool_change_fast_pulloff = DEFAULT_TOOLCHANGE_FAST_PROBE_PULLOFF, .flags.no_unlock_after_estop = DEFAULT_NO_UNLOCK_AFTER_ESTOP, .flags.keep_offsets_on_reset = DEFAULT_KEEP_OFFSETS_ON_RESET, .flags.keep_rapids_override_on_reset = DEFAULT_KEEP_RAPIDS_OVR_ON_RESET, .flags.keep_feed_override_on_reset = DEFAULT_KEEP_FEED_OVR_ON_RESET, .flags.tool_persistent = DEFAULT_PERSIST_TOOL, .probe.disable_probe_pullup = DEFAULT_PROBE_SIGNAL_DISABLE_PULLUP, .probe.allow_feed_override = DEFAULT_ALLOW_FEED_OVERRIDE_DURING_PROBE_CYCLES, .probe.soft_limited = DEFAULT_SOFT_LIMIT_PROBE_CYCLES, .probe.invert_probe_pin = DEFAULT_PROBE_SIGNAL_INVERT, .probe.invert_toolsetter_input = DEFAULT_TOOLSETTER_SIGNAL_INVERT, .probe.disable_toolsetter_pullup = DEFAULT_TOOLSETTER_SIGNAL_DISABLE_PULLUP, .stepper_enable_delay = DEFAULT_STEPPER_ENABLE_DELAY, .steppers.pulse_microseconds = DEFAULT_STEP_PULSE_MICROSECONDS, .steppers.pulse_delay_microseconds = DEFAULT_STEP_PULSE_DELAY, .steppers.idle_lock_time = DEFAULT_STEPPER_IDLE_LOCK_TIME, .steppers.step_invert.mask = DEFAULT_STEP_SIGNALS_INVERT_MASK, .steppers.dir_invert.mask = DEFAULT_DIR_SIGNALS_INVERT_MASK, .steppers.ganged_dir_invert.mask = DEFAULT_GANGED_DIRECTION_INVERT_MASK, #if COMPATIBILITY_LEVEL <= 2 .steppers.enable_invert.mask = DEFAULT_ENABLE_SIGNALS_INVERT_MASK, #elif DEFAULT_ENABLE_SIGNALS_INVERT_MASK .steppers.enable_invert.mask = 0, #else .steppers.enable_invert.mask = AXES_BITMASK, #endif .steppers.energize.mask = DEFAULT_STEPPER_DEENERGIZE_MASK, #if N_AXIS > 3 .steppers.is_rotary.mask = (DEFAULT_AXIS_ROTATIONAL_MASK & AXES_BITMASK) & 0b11111000, .steppers.rotary_wrap.mask = (DEFAULT_AXIS_ROTARY_WRAP_MASK & AXES_BITMASK) & 0b11111000, #endif .motor_warning_enable.mask = DEFAULT_MOTOR_WARNING_SIGNALS_ENABLE, .motor_warning_invert.mask = DEFAULT_MOTOR_WARNING_SIGNALS_INVERT, .motor_fault_enable.mask = DEFAULT_MOTOR_FAULT_SIGNALS_ENABLE, .motor_fault_invert.mask = DEFAULT_MOTOR_FAULT_SIGNALS_INVERT, #if DEFAULT_HOMING_ENABLE .homing.flags.enabled = DEFAULT_HOMING_ENABLE, .homing.flags.init_lock = DEFAULT_HOMING_INIT_LOCK, .homing.flags.single_axis_commands = DEFAULT_HOMING_SINGLE_AXIS_COMMANDS, .homing.flags.force_set_origin = DEFAULT_HOMING_FORCE_SET_ORIGIN, .homing.flags.manual = DEFAULT_HOMING_ALLOW_MANUAL, .homing.flags.override_locks = DEFAULT_HOMING_OVERRIDE_LOCKS, .homing.flags.keep_on_reset = DEFAULT_HOMING_KEEP_STATUS_ON_RESET, .homing.flags.use_limit_switches = DEFAULT_HOMING_USE_LIMIT_SWITCHES, .homing.flags.nx_scrips_on_homed_only = DEFAULT_RUN_STARTUP_SCRIPTS_ONLY_ON_HOMED, #else .homing.flags.value = 0, #endif .homing.dir_mask.value = DEFAULT_HOMING_DIR_MASK, .homing.debounce_delay = DEFAULT_HOMING_DEBOUNCE_DELAY, .homing.pulloff = DEFAULT_HOMING_PULLOFF, .homing.locate_cycles = DEFAULT_N_HOMING_LOCATE_CYCLE, .homing.cycle[0].mask = DEFAULT_HOMING_CYCLE_0, .homing.cycle[1].mask = DEFAULT_HOMING_CYCLE_1, .homing.cycle[2].mask = DEFAULT_HOMING_CYCLE_2, .homing.dual_axis.fail_length_percent = DEFAULT_DUAL_AXIS_HOMING_FAIL_AXIS_LENGTH_PERCENT, .homing.dual_axis.fail_distance_min = DEFAULT_DUAL_AXIS_HOMING_FAIL_DISTANCE_MIN, .homing.dual_axis.fail_distance_max = DEFAULT_DUAL_AXIS_HOMING_FAIL_DISTANCE_MAX, .home_invert.mask = DEFAULT_HOME_SIGNALS_INVERT_MASK, .status_report.machine_position = DEFAULT_REPORT_MACHINE_POSITION, .status_report.buffer_state = DEFAULT_REPORT_BUFFER_STATE, .status_report.line_numbers = DEFAULT_REPORT_LINE_NUMBERS, .status_report.feed_speed = DEFAULT_REPORT_CURRENT_FEED_SPEED, .status_report.pin_state = DEFAULT_REPORT_PIN_STATE, .status_report.work_coord_offset = DEFAULT_REPORT_WORK_COORD_OFFSET, .status_report.overrides = DEFAULT_REPORT_OVERRIDES, .status_report.probe_coordinates = DEFAULT_REPORT_PROBE_COORDINATES, .status_report.sync_on_wco_change = DEFAULT_REPORT_SYNC_ON_WCO_CHANGE, .status_report.parser_state = DEFAULT_REPORT_PARSER_STATE, .status_report.alarm_substate = DEFAULT_REPORT_ALARM_SUBSTATE, .status_report.run_substate = DEFAULT_REPORT_RUN_SUBSTATE, .status_report.when_homing = DEFAULT_REPORT_WHEN_HOMING, .status_report.distance_to_go = DEFAULT_REPORT_DISTANCE_TO_GO, .limits.flags.hard_enabled = DEFAULT_HARD_LIMIT_ENABLE, .limits.flags.jog_soft_limited = DEFAULT_JOG_LIMIT_ENABLE, .limits.flags.check_at_init = DEFAULT_CHECK_LIMITS_AT_INIT, .limits.flags.hard_disabled_rotary = DEFAULT_HARD_LIMITS_DISABLE_FOR_ROTARY, .limits.flags.two_switches = DEFAULT_LIMITS_TWO_SWITCHES_ON_AXES, .limits.invert.mask = DEFAULT_LIMIT_SIGNALS_INVERT_MASK, .limits.disable_pullup.mask = DEFAULT_LIMIT_SIGNALS_PULLUP_DISABLE_MASK, .limits.soft_enabled.mask = (DEFAULT_SOFT_LIMIT_ENABLE ? AXES_BITMASK : 0), .control_invert.mask = DEFAULT_CONTROL_SIGNALS_INVERT_MASK, .control_disable_pullup.mask = DEFAULT_DISABLE_CONTROL_PINS_PULL_UP_MASK, .spindle.ref_id = DEFAULT_SPINDLE, .spindle.on_delay = DEFAULT_SPINDLE_ON_DELAY, .spindle.off_delay = DEFAULT_SPINDLE_OFF_DELAY, .spindle.at_speed_tolerance = DEFAULT_SPINDLE_AT_SPEED_TOLERANCE, .spindle.encoder_spindle = DEFAULT_SPINDLE, .spindle.ppr = DEFAULT_SPINDLE_PPR, .pwm_spindle.rpm_max = DEFAULT_SPINDLE_RPM_MAX, .pwm_spindle.rpm_min = DEFAULT_SPINDLE_RPM_MIN, .pwm_spindle.flags.pwm_disable = false, .pwm_spindle.flags.enable_rpm_controlled = DEFAULT_SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED, .pwm_spindle.flags.laser_mode_disable = DEFAULT_PWM_SPINDLE_DISABLE_LASER_MODE, .pwm_spindle.invert.on = DEFAULT_INVERT_SPINDLE_ENABLE_PIN, .pwm_spindle.invert.ccw = DEFAULT_INVERT_SPINDLE_CCW_PIN, .pwm_spindle.invert.pwm = DEFAULT_INVERT_SPINDLE_PWM_PIN, .pwm_spindle.pwm_freq = DEFAULT_SPINDLE_PWM_FREQ, .pwm_spindle.pwm_off_value = DEFAULT_SPINDLE_PWM_OFF_VALUE, .pwm_spindle.pwm_min_value = DEFAULT_SPINDLE_PWM_MIN_VALUE, .pwm_spindle.pwm_max_value = DEFAULT_SPINDLE_PWM_MAX_VALUE, .pwm_spindle.at_speed_tolerance = DEFAULT_SPINDLE_AT_SPEED_TOLERANCE, #if ENABLE_SPINDLE_LINEARIZATION #if SPINDLE_NPWM_PIECES > 0 .pwm_spindle.pwm_piece[0] = { .rpm = DEFAULT_RPM_POINT01, .start = DEFAULT_RPM_LINE_A1, .end = DEFAULT_RPM_LINE_B1 }, #endif #if SPINDLE_NPWM_PIECES > 1 .pwm_spindle.pwm_piece[1] = { .rpm = DEFAULT_RPM_POINT12, .start = DEFAULT_RPM_LINE_A2, .end = DEFAULT_RPM_LINE_B2 }, #endif #if SPINDLE_NPWM_PIECES > 2 .pwm_spindle.pwm_piece[2] = { .rpm = DEFAULT_RPM_POINT23, .start = DEFAULT_RPM_LINE_A3, .end = DEFAULT_RPM_LINE_B3 }, #endif #if SPINDLE_NPWM_PIECES > 3 .pwm_spindle.pwm_piece[3] = { .rpm = DEFAULT_RPM_POINT34, .start = DEFAULT_RPM_LINE_A4, .end = DEFAULT_RPM_LINE_B4 }, #endif #else #if SPINDLE_NPWM_PIECES > 0 .pwm_spindle.pwm_piece[0] = { .rpm = NAN, .start = 0.0f, .end = 0.0f }, #endif #if SPINDLE_NPWM_PIECES > 1 .pwm_spindle.pwm_piece[1] = { .rpm = NAN, .start = 0.0f, .end = 0.0f }, #endif #if SPINDLE_NPWM_PIECES > 2 .pwm_spindle.pwm_piece[2] = { .rpm = NAN, .start = 0.0f, .end = 0.0f }, #endif #if SPINDLE_NPWM_PIECES > 3 .pwm_spindle.pwm_piece[3] = { .rpm = NAN, .start = 0.0f, .end = 0.0f }, #endif #endif .coolant.on_delay = DEFAULT_COOLANT_ON_DELAY, .coolant.invert.flood = DEFAULT_INVERT_COOLANT_FLOOD_PIN, .coolant.invert.mist = DEFAULT_INVERT_COOLANT_MIST_PIN, .axis[X_AXIS].steps_per_mm = DEFAULT_X_STEPS_PER_MM, .axis[X_AXIS].max_rate = DEFAULT_X_MAX_RATE, .axis[X_AXIS].acceleration = (DEFAULT_X_ACCELERATION * 60.0f * 60.0f), .axis[X_AXIS].jerk = (DEFAULT_X_JERK * 60.0f * 60.0f * 60.0f), .axis[X_AXIS].max_travel = (-DEFAULT_X_MAX_TRAVEL), .axis[X_AXIS].dual_axis_offset = 0.0f, .axis[X_AXIS].homing_feed_rate = DEFAULT_HOMING_FEED_RATE, .axis[X_AXIS].homing_seek_rate = DEFAULT_HOMING_SEEK_RATE, #if ENABLE_BACKLASH_COMPENSATION .axis[X_AXIS].backlash = 0.0f, #endif .axis[Y_AXIS].steps_per_mm = DEFAULT_Y_STEPS_PER_MM, .axis[Y_AXIS].max_rate = DEFAULT_Y_MAX_RATE, .axis[Y_AXIS].max_travel = (-DEFAULT_Y_MAX_TRAVEL), .axis[Y_AXIS].acceleration = (DEFAULT_Y_ACCELERATION * 60.0f * 60.0f), .axis[Y_AXIS].jerk = (DEFAULT_Y_JERK * 60.0f * 60.0f * 60.0f), .axis[Y_AXIS].dual_axis_offset = 0.0f, .axis[Y_AXIS].homing_feed_rate = DEFAULT_HOMING_FEED_RATE, .axis[Y_AXIS].homing_seek_rate = DEFAULT_HOMING_SEEK_RATE, #if ENABLE_BACKLASH_COMPENSATION .axis[Y_AXIS].backlash = 0.0f, #endif .axis[Z_AXIS].steps_per_mm = DEFAULT_Z_STEPS_PER_MM, .axis[Z_AXIS].max_rate = DEFAULT_Z_MAX_RATE, .axis[Z_AXIS].acceleration = (DEFAULT_Z_ACCELERATION * 60.0f * 60.0f), .axis[Z_AXIS].jerk = (DEFAULT_Z_JERK * 60.0f * 60.0f * 60.0f), .axis[Z_AXIS].max_travel = (-DEFAULT_Z_MAX_TRAVEL), .axis[Z_AXIS].dual_axis_offset = 0.0f, .axis[Z_AXIS].homing_feed_rate = DEFAULT_HOMING_FEED_RATE, .axis[Z_AXIS].homing_seek_rate = DEFAULT_HOMING_SEEK_RATE, #if ENABLE_BACKLASH_COMPENSATION .axis[Z_AXIS].backlash = 0.0f, #endif #ifdef A_AXIS .axis[A_AXIS].steps_per_mm = DEFAULT_A_STEPS_PER_MM, .axis[A_AXIS].max_rate = DEFAULT_A_MAX_RATE, .axis[A_AXIS].acceleration =(DEFAULT_A_ACCELERATION * 60.0f * 60.0f), .axis[A_AXIS].jerk = (DEFAULT_A_JERK * 60.0f * 60.0f * 60.0f), .axis[A_AXIS].max_travel = (-DEFAULT_A_MAX_TRAVEL), .axis[A_AXIS].dual_axis_offset = 0.0f, .axis[A_AXIS].homing_feed_rate = DEFAULT_HOMING_FEED_RATE, .axis[A_AXIS].homing_seek_rate = DEFAULT_HOMING_SEEK_RATE, #if ENABLE_BACKLASH_COMPENSATION .axis[A_AXIS].backlash = 0.0f, #endif .homing.cycle[3].mask = DEFAULT_HOMING_CYCLE_3, #endif #ifdef B_AXIS .axis[B_AXIS].steps_per_mm = DEFAULT_B_STEPS_PER_MM, .axis[B_AXIS].max_rate = DEFAULT_B_MAX_RATE, .axis[B_AXIS].acceleration = (DEFAULT_B_ACCELERATION * 60.0f * 60.0f), .axis[B_AXIS].jerk = (DEFAULT_B_JERK * 60.0f * 60.0f * 60.0f), .axis[B_AXIS].max_travel = (-DEFAULT_B_MAX_TRAVEL), .axis[B_AXIS].dual_axis_offset = 0.0f, .axis[B_AXIS].homing_feed_rate = DEFAULT_HOMING_FEED_RATE, .axis[B_AXIS].homing_seek_rate = DEFAULT_HOMING_SEEK_RATE, #if ENABLE_BACKLASH_COMPENSATION .axis[B_AXIS].backlash = 0.0f, #endif .homing.cycle[4].mask = DEFAULT_HOMING_CYCLE_4, #endif #ifdef C_AXIS .axis[C_AXIS].steps_per_mm = DEFAULT_C_STEPS_PER_MM, .axis[C_AXIS].acceleration = (DEFAULT_C_ACCELERATION * 60.0f * 60.0f), .axis[C_AXIS].jerk = (DEFAULT_C_JERK * 60.0f * 60.0f * 60.0f), .axis[C_AXIS].max_rate = DEFAULT_C_MAX_RATE, .axis[C_AXIS].max_travel = (-DEFAULT_C_MAX_TRAVEL), .axis[C_AXIS].dual_axis_offset = 0.0f, .axis[C_AXIS].homing_feed_rate = DEFAULT_HOMING_FEED_RATE, .axis[C_AXIS].homing_seek_rate = DEFAULT_HOMING_SEEK_RATE, #if ENABLE_BACKLASH_COMPENSATION .axis[C_AXIS].backlash = 0.0f, #endif .homing.cycle[5].mask = DEFAULT_HOMING_CYCLE_5, #endif #ifdef U_AXIS .axis[U_AXIS].steps_per_mm = DEFAULT_U_STEPS_PER_MM, .axis[U_AXIS].acceleration = (DEFAULT_U_ACCELERATION * 60.0f * 60.0f), .axis[U_AXIS].jerk = (DEFAULT_U_JERK * 60.0f * 60.0f * 60.0f), .axis[U_AXIS].max_rate = DEFAULT_U_MAX_RATE, .axis[U_AXIS].max_travel = (-DEFAULT_U_MAX_TRAVEL), .axis[U_AXIS].dual_axis_offset = 0.0f, .axis[U_AXIS].homing_feed_rate = DEFAULT_HOMING_FEED_RATE, .axis[U_AXIS].homing_seek_rate = DEFAULT_HOMING_SEEK_RATE, #if ENABLE_BACKLASH_COMPENSATION .axis[U_AXIS].backlash = 0.0f, #endif #endif #ifdef V_AXIS .axis[V_AXIS].steps_per_mm = DEFAULT_V_STEPS_PER_MM, .axis[V_AXIS].acceleration = (DEFAULT_V_ACCELERATION * 60.0f * 60.0f), .axis[V_AXIS].jerk = (DEFAULT_V_JERK * 60.0f * 60.0f * 60.0f), .axis[V_AXIS].max_rate = DEFAULT_V_MAX_RATE, .axis[V_AXIS].max_travel = (-DEFAULT_V_MAX_TRAVEL), .axis[V_AXIS].dual_axis_offset = 0.0f, .axis[V_AXIS].homing_feed_rate = DEFAULT_HOMING_FEED_RATE, .axis[V_AXIS].homing_seek_rate = DEFAULT_HOMING_SEEK_RATE, #if ENABLE_BACKLASH_COMPENSATION .axis[V_AXIS].backlash = 0.0f, #endif #endif .tool_change.mode = (toolchange_mode_t)DEFAULT_TOOLCHANGE_MODE, .tool_change.probing_distance = DEFAULT_TOOLCHANGE_PROBING_DISTANCE, .tool_change.feed_rate = DEFAULT_TOOLCHANGE_FEED_RATE, .tool_change.seek_rate = DEFAULT_TOOLCHANGE_SEEK_RATE, .tool_change.pulloff_rate = DEFAULT_TOOLCHANGE_PULLOFF_RATE, .parking.flags.enabled = DEFAULT_PARKING_ENABLE, .parking.flags.deactivate_upon_init = DEFAULT_DEACTIVATE_PARKING_UPON_INIT, .parking.flags.enable_override_control= DEFAULT_ENABLE_PARKING_OVERRIDE_CONTROL, .parking.axis = DEFAULT_PARKING_AXIS, .parking.target = DEFAULT_PARKING_TARGET, .parking.rate = DEFAULT_PARKING_RATE, .parking.pullout_rate = DEFAULT_PARKING_PULLOUT_RATE, .parking.pullout_increment = DEFAULT_PARKING_PULLOUT_INCREMENT, .safety_door.flags.ignore_when_idle = DEFAULT_DOOR_IGNORE_WHEN_IDLE, .safety_door.flags.keep_coolant_on = DEFAULT_DOOR_KEEP_COOLANT_ON, .safety_door.spindle_on_delay = DEFAULT_SAFETY_DOOR_SPINDLE_DELAY, .safety_door.coolant_on_delay = DEFAULT_SAFETY_DOOR_COOLANT_DELAY, .fs_options.sd_mount_on_boot = DEFAULT_FS_SD_AUTOMOUNT, .fs_options.lfs_hidden = DEFAULT_FS_LITLLEFS_HIDDEN, .fs_options.hierarchical_listing = DEFAULT_FS_HIERACHICAL_LISTING, .modbus_baud = DEFAULT_MODBUS_STREAM_BAUD, .modbus_stream_format.value = (DEFAULT_MODBUS_STREAM_PARITY << 4), .rgb_strip.length0 = DEFAULT_RGB_STRIP0_LENGTH, .rgb_strip.length1 = DEFAULT_RGB_STRIP1_LENGTH }; static bool group_is_available (const setting_group_detail_t *group) { return true; } PROGMEM static const setting_group_detail_t setting_group_detail [] = { { Group_Root, Group_Root, "Root", group_is_available }, { Group_Root, Group_General, "General", group_is_available }, { Group_Root, Group_ControlSignals, "Control signals" }, { Group_Root, Group_Limits, "Limits" }, { Group_Limits, Group_Limits_DualAxis, "Dual axis" }, { Group_Root, Group_Coolant, "Coolant" }, { Group_Root, Group_Spindle, "Spindle" }, { Group_Spindle, Group_Spindle_Sync, "Spindle sync" }, { Group_Root, Group_Toolchange, "Tool change" }, { Group_Root, Group_Homing, "Homing" }, { Group_Root, Group_Probing, "Probing" }, { Group_Root, Group_SafetyDoor, "Parking/Safety door" }, { Group_Root, Group_Jogging, "Jogging"}, { Group_Root, Group_Stepper, "Stepper" }, { Group_Root, Group_MotorDriver, "Stepper driver" }, { Group_Root, Group_Axis, "Axis", group_is_available }, { Group_Axis, Group_XAxis, "X-axis", group_is_available }, { Group_Axis, Group_YAxis, "Y-axis", group_is_available }, { Group_Axis, Group_ZAxis, "Z-axis", group_is_available }, #if !AXIS_REMAP_ABC2UVW #ifdef A_AXIS { Group_Axis, Group_AAxis, "A-axis", group_is_available }, #endif #ifdef B_AXIS { Group_Axis, Group_BAxis, "B-axis", group_is_available }, #endif #ifdef C_AXIS { Group_Axis, Group_CAxis, "C-axis", group_is_available }, #endif #ifdef U_AXIS { Group_Axis, Group_UAxis, "U-axis", group_is_available }, #endif #ifdef V_AXIS { Group_Axis, Group_VAxis, "V-axis", group_is_available } #endif #else #ifdef A_AXIS { Group_Axis, Group_AAxis, "U-axis", group_is_available }, #endif #ifdef B_AXIS { Group_Axis, Group_BAxis, "V-axis", group_is_available }, #endif #ifdef C_AXIS { Group_Axis, Group_CAxis, "W-axis", group_is_available }, #endif #endif }; static bool machine_mode_changed = false; #if COMPATIBILITY_LEVEL <= 1 static char probe_signals[] = "Probe,Toolsetter,Probe 2"; static char homing_options[] = "Enable,Enable single axis commands,Homing on startup required,Set machine origin to 0,Two switches shares one input,Allow manual,Override locks,N/A,Use limit switches,Per axis feedrates,Run startup scripts only on homing completed"; #else static char probe_signals[] = "Probe"; #endif static char probing_options[] = "Allow feed override,Apply soft limits,N/A,Auto select toolsetter,Auto select probe 2,Probe protection"; static char control_signals[] = "Reset,Feed hold,Cycle start,Safety door,Block delete,Optional stop,EStop,Probe disconnected,Motor fault,Motor warning,Limits override,Single step blocks,Toolsetter overtravel"; static char spindle_signals[] = "Spindle enable,Spindle direction,PWM"; static char coolant_signals[] = "Flood,Mist"; static char door_options[] = "Ignore when idle,Keep coolant state on door open"; static char ganged_axes[] = "X-Axis,Y-Axis,Z-Axis"; #if !AXIS_REMAP_ABC2UVW #if N_AXIS == 4 static const char rotary_axes[] = "A-Axis"; #elif N_AXIS == 5 static const char rotary_axes[] = "A-Axis,B-Axis"; #elif N_AXIS == 6 static const char rotary_axes[] = "A-Axis,B-Axis,C-Axis"; #elif N_AXIS == 7 static const char rotary_axes[] = "A-Axis,B-Axis,C-Axis,U-Axis"; #elif N_AXIS == 8 static const char rotary_axes[] = "A-Axis,B-Axis,C-Axis,U-Axis,V-Axis"; #endif #else #if N_AXIS == 4 static const char rotary_axes[] = "U-Axis"; #elif N_AXIS == 5 static const char rotary_axes[] = "U-Axis,V-Axis"; #elif N_AXIS == 6 static const char rotary_axes[] = "U-Axis,V-Axis,W-Axis"; #endif #endif static on_file_demarcate_ptr on_file_demarcate; static char step_us_min[4]; static char fs_options[] = "Auto mount SD card,Hide LittleFS,Hierarchical listing"; static char spindle_types[100] = ""; static char axis_dist[4] = "mm"; static char axis_rate[8] = "mm/min"; static char axis_accel[10] = "mm/sec^2"; #if ENABLE_JERK_ACCELERATION static char axis_jerk[10] = "mm/sec^3"; #endif #if DELTA_ROBOT static char axis_steps[9] = "step/rev"; #else static char axis_steps[9] = "step/mm"; #endif #define AXIS_OPTS { .subgroups = On, .increment = 1 } // Acceleration override static struct { bool valid; float acceleration[N_AXIS]; #if ENABLE_JERK_ACCELERATION float jerk[N_AXIS]; #endif } override_backup = { .valid = false }; static void save_override_backup (void) { uint_fast8_t idx = N_AXIS; do { idx--; override_backup.acceleration[idx] = settings.axis[idx].acceleration; #if ENABLE_JERK_ACCELERATION override_backup.jerk[idx] = settings.axis[idx].jerk; #endif } while(idx); override_backup.valid = true; } static void restore_override_backup (void) { uint_fast8_t idx = N_AXIS; if(override_backup.valid) do { idx--; settings.axis[idx].acceleration = override_backup.acceleration[idx]; #if ENABLE_JERK_ACCELERATION settings.axis[idx].jerk = override_backup.jerk[idx]; #endif } while(idx); } // Temporarily override acceleration, if 0 restore to setting value. // Note: only allowed when current state is idle. bool settings_override_acceleration (uint8_t axis, float acceleration) { sys_state_t state = state_get(); if(!(state == STATE_IDLE || (state & (STATE_HOMING|STATE_ALARM)))) return false; if(acceleration <= 0.0f) { if(override_backup.valid) settings.axis[axis].acceleration = override_backup.acceleration[axis]; } else { if(!override_backup.valid) save_override_backup(); settings.axis[axis].acceleration = acceleration * 60.0f * 60.0f; // Limit max to setting value? } return true; } #if ENABLE_JERK_ACCELERATION // Temporarily override jerk, if 0 restore to setting value. // Note: only allowed when current state is idle. bool settings_override_jerk (uint8_t axis, float jerk) { sys_state_t state = state_get(); if(!(state == STATE_IDLE || (state & (STATE_HOMING|STATE_ALARM)))) return false; if(jerk <= 0.0f) { if(override_backup.valid) settings.axis[axis].jerk = override_backup.jerk[axis]; } else { if(!override_backup.valid) save_override_backup(); settings.axis[axis].jerk = jerk * 60.0f * 60.0f * 60.0f; // Limit max to setting value? } return true; } #endif // ENABLE_JERK_ACCELERATION // --- static void homing_pulloff_init (float pulloff) { coord_data_t distance; uint_fast8_t idx = N_AXIS; do { distance.values[--idx] = pulloff; } while(idx); limits_homing_pulloff(&distance); } #if COMPATIBILITY_LEVEL > 2 static status_code_t set_enable_invert_mask (setting_id_t id, uint_fast16_t int_value) { settings.steppers.enable_invert.mask = int_value ? 0 : AXES_BITMASK; return Status_OK; } #endif #if COMPATIBILITY_LEVEL > 1 static status_code_t set_limits_invert_mask (setting_id_t id, uint_fast16_t int_value) { settings.limits.invert.mask = (int_value ? ~(DEFAULT_LIMIT_SIGNALS_INVERT_MASK) : DEFAULT_LIMIT_SIGNALS_INVERT_MASK) & AXES_BITMASK; return Status_OK; } #endif static status_code_t validate_pulse_width (float max_rate, float steps_per_mm, float pulse_width) { /* float f_step = (max_rate * steps_per_mm) / 60.0f; return !gc_state.file_run && ((hal.max_step_rate && f_step > (float)hal.max_step_rate) || (pulse_width + 2.0f) > 1000000.0 / f_step) ? Status_MaxStepRateExceeded : Status_OK; */ return Status_OK; } static status_code_t set_pulse_width (setting_id_t id, float value) { uint_fast8_t idx = N_AXIS; status_code_t status = Status_OK; do { idx--; #if N_AXIS > 3 if(bit_isfalse(settings.steppers.is_rotary.mask, bit(idx))) #endif status = validate_pulse_width(settings.axis[idx].max_rate, settings.axis[idx].steps_per_mm, value); } while(idx && status == Status_OK); if(status == Status_OK) settings.steppers.pulse_microseconds = value; return status; } static status_code_t set_probe_invert (setting_id_t id, uint_fast16_t int_value) { if(!hal.probe.configure) return Status_SettingDisabled; settings.probe.invert_probe_pin = (int_value & 0b001); if(hal.driver_cap.toolsetter) settings.probe.invert_toolsetter_input = !!(int_value & 0b010); if(hal.driver_cap.probe2) settings.probe.invert_probe2_input = !!(int_value & 0b100); ioport_setting_changed(id); hal.probe.configure(false, false); return Status_OK; } static status_code_t set_ganged_dir_invert (setting_id_t id, uint_fast16_t int_value) { if(!hal.stepper.get_ganged) return Status_SettingDisabled; settings.steppers.ganged_dir_invert.mask = int_value & hal.stepper.get_ganged(false).mask; return Status_OK; } static status_code_t set_stepper_energize_mask (setting_id_t id, uint_fast16_t int_value) { settings.steppers.energize.mask = int_value; hal.stepper.enable(settings.steppers.energize, true); return Status_OK; } static status_code_t set_report_interval (setting_id_t setting, uint_fast16_t int_value) { if((settings.report_interval = int_value) == 0) sys.flags.auto_reporting = Off; return Status_OK; } static status_code_t set_report_mask (setting_id_t id, uint_fast16_t int_value) { #if COMPATIBILITY_LEVEL <= 1 settings.status_report.mask = int_value; #else int_value &= 0b11; settings.status_report.mask = (settings.status_report.mask & ~0b11) | int_value; #endif return Status_OK; } static status_code_t set_report_inches (setting_id_t id, uint_fast16_t int_value) { settings.flags.report_inches = int_value != 0; report_init(); system_flag_wco_change(); // Make sure WCO is immediately updated. return Status_OK; } #if NGC_EXPRESSIONS_ENABLE static status_code_t set_ngc_debug_out (setting_id_t id, uint_fast16_t int_value) { settings.flags.ngc_debug_out = int_value != 0; report_init(); system_flag_wco_change(); // Make sure WCO is immediately updated. return Status_OK; } #endif static status_code_t set_control_invert (setting_id_t id, uint_fast16_t int_value) { int_value = (int_value & hal.signals_cap.mask) | limits_override.mask; bool pd_changed = hal.probe.connected_toggle && settings.control_invert.probe_disconnected != (control_signals_t){ .value = int_value }.probe_disconnected; settings.control_invert.mask = int_value; ioport_setting_changed(id); system_init_switches(); if(pd_changed) hal.probe.connected_toggle(); return Status_OK; } static status_code_t set_pwm_mode (setting_id_t id, uint_fast16_t int_value) { settings.pwm_spindle.flags.enable_rpm_controlled = int_value != 0; return Status_OK; } static status_code_t set_pwm_options (setting_id_t id, uint_fast16_t int_value) { if(int_value & 0x001) { if(int_value > 0b111) return Status_SettingValueOutOfRange; settings.pwm_spindle.flags.pwm_disable = Off; settings.pwm_spindle.flags.enable_rpm_controlled = !!(int_value & 0b010); settings.pwm_spindle.flags.laser_mode_disable = !!(int_value & 0b100); } else { settings.pwm_spindle.flags.pwm_disable = On; settings.pwm_spindle.flags.enable_rpm_controlled = settings.pwm_spindle.flags.laser_mode_disable = Off; } return Status_OK; } typedef struct { uint8_t ref_id; spindle_id_t spindle_id; } spindle_map_t; static bool get_spindle_ref_id (spindle_info_t *spindle, void *map) { bool ok; if((ok = spindle->id == ((spindle_map_t *)map)->spindle_id)) ((spindle_map_t *)map)->ref_id = spindle->ref_id; return ok; } static status_code_t set_default_spindle (setting_id_t id, uint_fast16_t int_value) { status_code_t status; spindle_map_t spindle = { .spindle_id = int_value }; if((status = spindle_enumerate_spindles(get_spindle_ref_id, &spindle) ? Status_OK : Status_SettingValueOutOfRange) == Status_OK) { settings.spindle.ref_id = spindle.ref_id; spindle_select(spindle.spindle_id); } return status; } static status_code_t set_encoder_spindle (setting_id_t id, uint_fast16_t int_value) { status_code_t status; spindle_map_t spindle = { .spindle_id = int_value }; if((status = spindle_enumerate_spindles(get_spindle_ref_id, &spindle) ? Status_OK : Status_SettingValueOutOfRange) == Status_OK) settings.spindle.encoder_spindle = spindle.ref_id; return status; } static status_code_t set_spindle_invert (setting_id_t id, uint_fast16_t int_value) { settings.pwm_spindle.invert.mask = int_value; if(settings.pwm_spindle.invert.pwm && !spindle_get_caps(false).pwm_invert) { settings.pwm_spindle.invert.pwm = Off; return Status_SettingDisabled; } return Status_OK; } static status_code_t set_control_disable_pullup (setting_id_t id, uint_fast16_t int_value) { settings.control_disable_pullup.mask = int_value & (hal.signals_cap.mask & ~limits_override.mask); ioport_setting_changed(id); return Status_OK; } static status_code_t set_probe_disable_pullup (setting_id_t id, uint_fast16_t int_value) { if(!hal.probe.configure) return Status_SettingDisabled; settings.probe.disable_probe_pullup = (int_value & 0b001); if(hal.driver_cap.toolsetter) settings.probe.disable_toolsetter_pullup = !!(int_value & 0b010); // if(hal.driver_cap.probe2) // settings.probe.disable_probe2_pullup = !!(int_value & 0b100); ioport_setting_changed(id); return Status_OK; } static void tmp_set_soft_limits (void) { sys.soft_limits.mask = 0; if(settings.limits.soft_enabled.mask) { uint_fast8_t idx = N_AXIS; do { idx--; if(bit_istrue(settings.limits.soft_enabled.mask, bit(idx)) && settings.axis[idx].max_travel < -0.0f) bit_true(sys.soft_limits.mask, bit(idx)); } while(idx); } } static status_code_t set_soft_limits_enable (setting_id_t id, uint_fast16_t int_value) { if(int_value && !settings.homing.flags.enabled) return Status_SoftLimitError; settings.limits.soft_enabled.mask = int_value ? AXES_BITMASK : 0; tmp_set_soft_limits(); return Status_OK; } static status_code_t set_estop_unlock (setting_id_t id, uint_fast16_t int_value) { if(!hal.signals_cap.e_stop) return Status_SettingDisabled; settings.flags.no_unlock_after_estop = int_value == 0; return Status_OK; } static status_code_t set_persistent_tool (setting_id_t id, uint_fast16_t int_value) { settings.flags.tool_persistent = int_value != 0; return Status_OK; } static inline void tmp_set_hard_limits (void) { sys.hard_limits.mask = settings.limits.flags.hard_enabled ? AXES_BITMASK : 0; #if N_AXIS > 3 if(settings.limits.flags.hard_disabled_rotary) sys.hard_limits.mask &= ~settings.steppers.is_rotary.mask; #endif } static status_code_t set_hard_limits_enable (setting_id_t id, uint_fast16_t int_value) { if((settings.limits.flags.hard_enabled = bit_istrue(int_value, bit(0)))) { #if COMPATIBILITY_LEVEL <= 1 settings.limits.flags.check_at_init = bit_istrue(int_value, bit(1)); #if N_AXIS > 3 settings.limits.flags.hard_disabled_rotary = bit_istrue(int_value, bit(2)); #endif #endif } else settings.limits.flags.check_at_init = settings.limits.flags.hard_disabled_rotary = Off; tmp_set_hard_limits(); hal.limits.enable(settings.limits.flags.hard_enabled, (axes_signals_t){0}); // Change immediately. NOTE: Nice to have but could be problematic later. return Status_OK; } static status_code_t set_jog_soft_limited (setting_id_t id, uint_fast16_t int_value) { if (int_value && !settings.homing.flags.enabled) return Status_SoftLimitError; settings.limits.flags.jog_soft_limited = int_value != 0; return Status_OK; } static status_code_t set_enable_legacy_rt_commands (setting_id_t id, uint_fast16_t int_value) { settings.flags.legacy_rt_commands = int_value != 0; return Status_OK; } #if !LATHE_UVW_OPTION static status_code_t set_mode (setting_id_t id, uint_fast16_t int_value) { switch((machine_mode_t)int_value) { case Mode_Standard: gc_state.modal.diameter_mode = false; break; case Mode_Laser: if(!spindle_get_caps(false).laser) return Status_SettingDisabledLaser; gc_state.modal.diameter_mode = false; break; case Mode_Lathe: break; default: // Mode_Standard return Status_InvalidStatement; } machine_mode_changed = true; settings.mode = (machine_mode_t)int_value; return Status_OK; } #endif // LATHE_UVW_OPTION #ifndef NO_SAFETY_DOOR_SUPPORT static status_code_t set_parking_enable (setting_id_t id, uint_fast16_t int_value) { settings.parking.flags.value = bit_istrue(int_value, bit(0)) ? (int_value & 0x07) : 0; if(settings.parking.flags.deactivate_upon_init) settings.parking.flags.enable_override_control = On; //setting_remove_elements(Setting_ProbePullUpDisable, mask); return Status_OK; } static status_code_t set_restore_overrides (setting_id_t id, uint_fast16_t int_value) { settings.flags.restore_overrides = int_value != 0; return Status_OK; } #endif // NO_SAFETY_DOOR_SUPPORT static status_code_t set_homing_cycle (setting_id_t id, uint_fast16_t int_value) { settings.homing.cycle[id - Setting_HomingCycle_1].mask = int_value; limits_set_homing_axes(); return Status_OK; } static status_code_t set_homing_pulloff (setting_id_t id, float value) { settings.homing.pulloff = value; homing_pulloff_init(value); return Status_OK; } static status_code_t set_homing_feedrates (setting_id_t id, float value) { uint_fast8_t idx = N_AXIS; if(!settings.homing.flags.per_axis_feedrates) switch(id) { case Setting_HomingFeedRate: do { settings.axis[--idx].homing_feed_rate = value; } while(idx); break; case Setting_HomingSeekRate: do { settings.axis[--idx].homing_seek_rate = value; } while(idx); break; default: break; } return settings.homing.flags.per_axis_feedrates ? Status_SettingDisabled : Status_OK; } static status_code_t set_homing_enable (setting_id_t id, uint_fast16_t int_value) { bool reset_feeds = !settings.homing.flags.enabled || settings.homing.flags.per_axis_feedrates; settings.homing.flags.value = int_value; if(settings.homing.flags.enabled) { #if COMPATIBILITY_LEVEL > 1 settings.homing.flags.enabled = On; settings.homing.flags.init_lock = DEFAULT_HOMING_INIT_LOCK; settings.homing.flags.single_axis_commands = DEFAULT_HOMING_SINGLE_AXIS_COMMANDS; settings.homing.flags.force_set_origin = DEFAULT_HOMING_FORCE_SET_ORIGIN; settings.homing.flags.manual = DEFAULT_HOMING_ALLOW_MANUAL; settings.homing.flags.override_locks = DEFAULT_HOMING_OVERRIDE_LOCKS; settings.homing.flags.keep_on_reset = DEFAULT_HOMING_KEEP_STATUS_ON_RESET; settings.homing.flags.use_limit_switches = DEFAULT_HOMING_USE_LIMIT_SWITCHES; settings.homing.flags.nx_scrips_on_homed_only = DEFAULT_RUN_STARTUP_SCRIPTS_ONLY_ON_HOMED; settings.limits.flags.two_switches = DEFAULT_LIMITS_TWO_SWITCHES_ON_AXES; #else settings.limits.flags.two_switches = settings.homing.flags.two_switches; settings.homing.flags.two_switches = Off; #endif } else { settings.homing.flags.enabled = On; set_soft_limits_enable(Setting_SoftLimitsEnable, 0); // Force disable soft-limits. settings.homing.flags.value = 0; settings.limits.flags.jog_soft_limited = Off; } if(settings.homing.flags.enabled && reset_feeds && !settings.homing.flags.per_axis_feedrates) { set_homing_feedrates(Setting_HomingFeedRate, settings.axis[0].homing_feed_rate); set_homing_feedrates(Setting_HomingSeekRate, settings.axis[0].homing_seek_rate); } settings.homing.flags.use_limit_switches &= hal.homing.get_state != NULL; return Status_OK; } static status_code_t set_sleep_enable (setting_id_t id, uint_fast16_t int_value) { settings.flags.sleep_enable = int_value != 0; return Status_OK; } static status_code_t set_hold_actions (setting_id_t id, uint_fast16_t int_value) { settings.flags.disable_laser_during_hold = bit_istrue(int_value, bit(0)); settings.flags.restore_after_feed_hold = bit_istrue(int_value, bit(1)); return Status_OK; } static status_code_t set_reset_actions (setting_id_t id, uint_fast16_t int_value) { settings.homing.flags.keep_on_reset = bit_isfalse(int_value, bit(0)); settings.flags.keep_offsets_on_reset = bit_isfalse(int_value, bit(1)); settings.flags.keep_rapids_override_on_reset = bit_isfalse(int_value, bit(2)); settings.flags.keep_feed_override_on_reset = bit_isfalse(int_value, bit(3)); return Status_OK; } #if COMPATIBILITY_LEVEL <= 1 static status_code_t set_g92_disable_persistence (setting_id_t id, uint_fast16_t int_value) { settings.flags.g92_is_volatile = int_value != 0; return Status_OK; } #endif static status_code_t set_force_initialization_alarm (setting_id_t id, uint_fast16_t int_value) { settings.flags.force_initialization_alarm = int_value != 0; return Status_OK; } static status_code_t set_probe_flags (setting_id_t id, uint_fast16_t int_value) { settings.probe.allow_feed_override = bit_istrue(int_value, bit(0)); settings.probe.soft_limited = bit_istrue(int_value, bit(1)); // settings.probe.? = bit_istrue(int_value, bit(2)); settings.probe.toolsetter_auto_select = bit_istrue(int_value, bit(3)) && hal.driver_cap.toolsetter && hal.probe.select; settings.probe.probe2_auto_select = bit_istrue(int_value, bit(4)) && hal.driver_cap.probe2 && hal.probe.select; settings.probe.enable_protection = bit_istrue(int_value, bit(5)); hal.probe.configure(false, false); return Status_OK; } static status_code_t set_tool_change_mode (setting_id_t id, uint_fast16_t int_value) { if(!hal.driver_cap.atc && hal.stream.suspend_read && int_value <= ToolChange_Ignore) { #if COMPATIBILITY_LEVEL > 1 if((toolchange_mode_t)int_value == ToolChange_Manual_G59_3 || (toolchange_mode_t)int_value == ToolChange_SemiAutomatic) return Status_InvalidStatement; #endif settings.tool_change.mode = (toolchange_mode_t)int_value; tc_init(); } else return Status_InvalidStatement; return Status_OK; } static status_code_t set_tool_change_probing_distance (setting_id_t id, float value) { if(hal.driver_cap.atc) return Status_InvalidStatement; settings.tool_change.probing_distance = value; return Status_OK; } static status_code_t set_toolchange_flags (setting_id_t id, uint_fast16_t int_value) { if(hal.driver_cap.atc) return Status_InvalidStatement; settings.flags.no_restore_position_after_M6 = !(int_value & 0b001); settings.flags.tool_change_at_g30 = !!(int_value & 0b010); settings.flags.tool_change_fast_pulloff = !!(int_value & 0b100); return Status_OK; } static inline void set_axis_unit (const setting_detail_t *setting, const char *unit) { // TODO: add length check if(unit) strcpy((char *)setting->unit, unit); } #if N_AXIS > 3 static status_code_t set_rotary_axes (setting_id_t id, uint_fast16_t int_value) { settings.steppers.is_rotary.mask = (int_value << 3) & AXES_BITMASK; return Status_OK; } static status_code_t set_rotary_wrap_axes (setting_id_t id, uint_fast16_t int_value) { settings.steppers.rotary_wrap.mask = (int_value << 3) & AXES_BITMASK; return Status_OK; } static inline bool axis_is_rotary (uint_fast8_t axis_idx) { return bit_istrue(settings.steppers.is_rotary.mask, bit(axis_idx)); } static const char *set_axis_setting_unit (setting_id_t setting_id, uint_fast8_t axis_idx) { char *unit = NULL; bool is_rotary = axis_is_rotary(axis_idx); switch(setting_id) { case Setting_AxisStepsPerMM: unit = is_rotary ? "step/deg" : "step/mm"; break; case Setting_AxisMaxRate: unit = is_rotary ? "deg/min" : "mm/min"; break; case Setting_AxisAcceleration: unit = is_rotary ? "deg/sec^2" : "mm/sec^2"; break; #if ENABLE_JERK_ACCELERATION case Setting_AxisJerk: unit = is_rotary ? "deg/sec^3" : "mm/sec^3"; break; #endif case Setting_AxisMaxTravel: case Setting_AxisBacklash: unit = is_rotary ? "deg" : "mm"; break; default: break; } return unit; } #endif #if ENABLE_SPINDLE_LINEARIZATION static status_code_t set_linear_piece (setting_id_t id, char *svalue) { uint32_t idx = id - Setting_LinearSpindlePiece1; float rpm, start, end; if(*svalue == '\0' || (svalue[0] == '0' && svalue[1] == '\0')) { settings.pwm_spindle.pwm_piece[idx].rpm = NAN; settings.pwm_spindle.pwm_piece[idx].start = settings.pwm_spindle.pwm_piece[idx].end = 0.0f; } else if(sscanf(svalue, "%f,%f,%f", &rpm, &start, &end) == 3) { settings.pwm_spindle.pwm_piece[idx].rpm = rpm; settings.pwm_spindle.pwm_piece[idx].start = start; settings.pwm_spindle.pwm_piece[idx].end = end; //?? if(idx == 0) // settings.spindle.rpm_min = rpm; } else return Status_SettingValueOutOfRange; return Status_OK; } static char *get_linear_piece (setting_id_t id) { static char buf[40]; uint32_t idx = id - Setting_LinearSpindlePiece1; if(isnan(settings.pwm_spindle.pwm_piece[idx].rpm)) *buf = '\0'; else snprintf(buf, sizeof(buf), "%g,%g,%g", settings.pwm_spindle.pwm_piece[idx].rpm, settings.pwm_spindle.pwm_piece[idx].start, settings.pwm_spindle.pwm_piece[idx].end); return buf; } #endif inline static setting_id_t normalize_id (setting_id_t id) { if((id > Setting_AxisSettingsBase && id <= Setting_AxisSettingsMax) || (id > Setting_AxisSettingsBase1 && id <= Setting_AxisSettingsMax1) || (id > Setting_AxisSettingsBase2 && id <= Setting_AxisSettingsMax2)) id -= id % AXIS_SETTINGS_INCREMENT; else if(id > Setting_EncoderSettingsBase && id <= Setting_EncoderSettingsMax) id = (setting_id_t)(Setting_EncoderSettingsBase + (id % ENCODER_SETTINGS_INCREMENT)); else if(id > Setting_ModbusTCPBase && id <= Setting_ModbusTCPMax) id = (setting_id_t)(Setting_ModbusTCPBase + (id % MODBUS_TCP_SETTINGS_INCREMENT)); return id; } setting_id_t settings_get_axis_base (setting_id_t id, uint_fast8_t *idx) { setting_id_t base = normalize_id(id); *idx = id - base; return *idx < N_AXIS ? base : Setting_SettingsMax; } static status_code_t set_float (setting_id_t setting, float value) { status_code_t status = Status_OK; switch(setting) { case Setting_SpindleAtSpeedTolerance: settings.spindle.at_speed_tolerance = settings.pwm_spindle.at_speed_tolerance = value; break; case Setting_SpindleOnDelay: settings.spindle.on_delay = (uint16_t)(value * 1000.0f); break; case Setting_SpindleOffDelay: settings.spindle.off_delay = (uint16_t)(value * 1000.0f); break; case Setting_CoolantOnDelay: settings.coolant.on_delay = (uint16_t)(value * 1000.0f); break; default: break; } return status; } static status_code_t set_axis_setting (setting_id_t setting, float value) { uint_fast8_t idx; status_code_t status = Status_OK; switch(settings_get_axis_base(setting, &idx)) { case Setting_AxisStepsPerMM: if((status = validate_pulse_width(settings.axis[idx].max_rate, value, settings.steppers.pulse_microseconds)) == Status_OK) { if(settings.axis[idx].steps_per_mm > 0.0f && settings.axis[idx].steps_per_mm != value) { float comp = value / settings.axis[idx].steps_per_mm; sys.position[idx] *= comp; sys.home_position[idx] *= comp; sys.probe_position[idx] *= comp; sys.tlo_reference[idx] *= comp; sync_position(); } settings.axis[idx].steps_per_mm = value; } break; case Setting_AxisMaxRate: if((status = validate_pulse_width(value, settings.axis[idx].steps_per_mm, settings.steppers.pulse_microseconds)) == Status_OK) settings.axis[idx].max_rate = value; break; case Setting_AxisAcceleration: settings.axis[idx].acceleration = override_backup.acceleration[idx] = value * 60.0f * 60.0f; // Convert to mm/min^2 for internal use. break; #if ENABLE_JERK_ACCELERATION case Setting_AxisJerk: settings.axis[idx].jerk = override_backup.jerk[idx] = value * 60.0f * 60.0f * 60.0f; // Convert to mm/min^3 for internal use. break; #endif case Setting_AxisMaxTravel: if(settings.axis[idx].max_travel != -value) { sys.flags.travel_changed = On; bit_false(sys.homed.mask, bit(idx)); system_add_rt_report(Report_Homed); } settings.axis[idx].max_travel = -value; // Store as negative for internal use. tmp_set_soft_limits(); break; case Setting_AxisBacklash: #if ENABLE_BACKLASH_COMPENSATION if(settings.axis[idx].backlash != value) { axes_signals_t axes; axes.mask = bit(idx); settings.axis[idx].backlash = value; mc_backlash_init(axes); } #else status = Status_SettingDisabled; #endif break; case Setting_AxisAutoSquareOffset: if(hal.stepper.get_ganged && bit_istrue(hal.stepper.get_ganged(true).mask, bit(idx))) settings.axis[idx].dual_axis_offset = value; else status = Status_SettingDisabled; break; case Setting_AxisHomingFeedRate: if(settings.homing.flags.per_axis_feedrates) settings.axis[idx].homing_feed_rate = value; else status = Status_SettingDisabled; break; case Setting_AxisHomingSeekRate: if(settings.homing.flags.per_axis_feedrates) settings.axis[idx].homing_seek_rate = value; else status = Status_SettingDisabled; break; default: status = Status_SettingDisabled; break; } return status; } static float get_float (setting_id_t setting) { float value = 0.0f; if((setting >= Setting_AxisSettingsBase && setting <= Setting_AxisSettingsMax) || (setting >= Setting_AxisSettingsBase1 && setting <= Setting_AxisSettingsMax1)) { uint_fast8_t idx; switch(settings_get_axis_base(setting, &idx)) { case Setting_AxisStepsPerMM: value = settings.axis[idx].steps_per_mm; break; case Setting_AxisMaxRate: value = settings.axis[idx].max_rate; break; case Setting_AxisAcceleration: value = settings.axis[idx].acceleration / (60.0f * 60.0f); // Convert from mm/min^2 to mm/sec^2. break; case Setting_AxisMaxTravel: value = -settings.axis[idx].max_travel; // Store as negative for internal use. break; #if ENABLE_BACKLASH_COMPENSATION case Setting_AxisBacklash: value = settings.axis[idx].backlash; break; #endif case Setting_AxisAutoSquareOffset: value = settings.axis[idx].dual_axis_offset; break; case Setting_AxisHomingFeedRate: value = settings.axis[idx].homing_feed_rate; break; case Setting_AxisHomingSeekRate: value = settings.axis[idx].homing_seek_rate; break; #if ENABLE_JERK_ACCELERATION case Setting_AxisJerk: value = settings.axis[idx].jerk / (60.0f * 60.0f * 60.0f); // Convert from mm/min^3 to mm/sec^3. break; #endif default: break; } } else switch(setting) { case Setting_PulseMicroseconds: value = settings.steppers.pulse_microseconds; break; case Setting_HomingFeedRate: value = settings.axis[0].homing_feed_rate; break; case Setting_HomingSeekRate: value = settings.axis[0].homing_seek_rate; break; case Setting_HomingPulloff: value = settings.homing.pulloff; break; case Setting_SpindleAtSpeedTolerance: value = settings.pwm_spindle.at_speed_tolerance; break; case Setting_ToolChangeProbingDistance: value = settings.tool_change.probing_distance; break; case Setting_SpindleOnDelay: value = (float)settings.spindle.on_delay / 1000.0f; break; case Setting_SpindleOffDelay: value = (float)settings.spindle.off_delay / 1000.0f; break; case Setting_CoolantOnDelay: value = (float)settings.coolant.on_delay / 1000.0f; break; default: break; } return value; } static uint32_t get_int (setting_id_t id) { uint32_t value = 0; switch(id) { #if COMPATIBILITY_LEVEL > 2 case Setting_InvertStepperEnable: value = settings.steppers.enable_invert.mask ? 0 : 1; break; #endif #if COMPATIBILITY_LEVEL > 1 case Setting_LimitPinsInvertMask: value = settings.limits.invert.mask == DEFAULT_LIMIT_SIGNALS_INVERT_MASK ? 0 : 1; break; #endif case Setting_SpindlePWMOptions: value = settings.pwm_spindle.flags.pwm_disable ? 0 : (0b001 | (settings.pwm_spindle.flags.enable_rpm_controlled ? 0b010 : 0) | (settings.pwm_spindle.flags.laser_mode_disable ? 0b100 : 0)); break; case Setting_Mode: value = settings.mode; break; case Setting_InvertProbePin: value = settings.probe.invert_probe_pin; if(hal.driver_cap.toolsetter && settings.probe.invert_toolsetter_input) value |= 0b010; if(hal.driver_cap.probe2 && settings.probe.invert_probe2_input) value |= 0b100; break; case Setting_GangedDirInvertMask: value = settings.steppers.ganged_dir_invert.mask; break; case Setting_StatusReportMask: #if COMPATIBILITY_LEVEL <= 1 value = settings.status_report.mask; #else value = settings.status_report.mask & 0b11; #endif break; case Setting_ReportInches: value = settings.flags.report_inches; break; case Setting_ControlInvertMask: value = settings.control_invert.mask & (hal.signals_cap.mask & ~limits_override.mask); break; case Setting_SpindleInvertMask: value = settings.pwm_spindle.invert.mask; break; case Setting_ControlPullUpDisableMask: value = settings.control_disable_pullup.mask; break; case Setting_ProbePullUpDisable: value = settings.probe.disable_probe_pullup; if(hal.driver_cap.toolsetter && settings.probe.disable_toolsetter_pullup) value |= 0b10; break; case Setting_SoftLimitsEnable: value = settings.limits.soft_enabled.mask != 0; break; case Setting_HardLimitsEnable: value = ((settings.limits.flags.hard_enabled & bit(0)) ? bit(0) | (settings.limits.flags.check_at_init ? bit(1) : 0) | (settings.limits.flags.hard_disabled_rotary ? bit(2) : 0) : 0); break; case Setting_JogSoftLimited: value = settings.limits.flags.jog_soft_limited; break; case Setting_HomingEnable:; #if COMPATIBILITY_LEVEL <= 1 homing_settings_flags_t homing = { .value = settings.homing.flags.value }; homing.two_switches = settings.limits.flags.two_switches; value = homing.value; #else value = settings.homing.flags.enabled; #endif break; case Setting_SteppersEnergize: value = settings.steppers.energize.mask; break; case Setting_EnableLegacyRTCommands: value = settings.flags.legacy_rt_commands; break; case Setting_ParkingEnable: value = settings.parking.flags.value; break; case Setting_HomingCycle_1: case Setting_HomingCycle_2: case Setting_HomingCycle_3: case Setting_HomingCycle_4: case Setting_HomingCycle_5: case Setting_HomingCycle_6: value = settings.homing.cycle[id - Setting_HomingCycle_1].mask; break; case Setting_RestoreOverrides: value = settings.flags.restore_overrides; break; case Setting_SleepEnable: value = settings.flags.sleep_enable; break; case Setting_HoldActions: value = settings.flags.disable_laser_during_hold | (settings.flags.restore_after_feed_hold << 1); break; case Setting_ForceInitAlarm: value = settings.flags.force_initialization_alarm; break; case Setting_ProbingFlags: value = settings.probe.allow_feed_override | (settings.probe.soft_limited << 1) | // (settings.probe.enable_protection << 2) | (settings.probe.toolsetter_auto_select << 3) | (settings.probe.probe2_auto_select << 4) | (settings.probe.enable_protection << 5); break; case Setting_ToolChangeMode: value = settings.tool_change.mode; break; case Setting_ToolChangeOptions: value = (settings.flags.no_restore_position_after_M6 ? 0b000 : 0b001) | (settings.flags.tool_change_at_g30 ? 0b010 : 0b000) | (settings.flags.tool_change_fast_pulloff ? 0b100 : 0b00); break; case Setting_DisableG92Persistence: value = settings.flags.g92_is_volatile; break; case Setting_SpindleType: { spindle_id_t spindle_id; spindle_get_id(settings.spindle.ref_id, &spindle_id); value = (uint32_t)spindle_id; } break; case Setting_AutoReportInterval: value = settings.report_interval; break; #if N_AXIS > 3 case Settings_RotaryAxes: value = (settings.steppers.is_rotary.mask & AXES_BITMASK) >> 3; break; case Setting_RotaryWrap: value = (settings.steppers.rotary_wrap.mask & AXES_BITMASK) >> 3; break; #endif case Setting_UnlockAfterEStop: value = settings.flags.no_unlock_after_estop ? 0 : 1; break; case Setting_EnableToolPersistence: value = settings.flags.tool_persistent; break; case Setting_EncoderSpindle: { spindle_id_t spindle_id; spindle_get_id(settings.spindle.encoder_spindle, &spindle_id); value = (uint32_t)spindle_id; } break; #if NGC_EXPRESSIONS_ENABLE case Setting_NGCDebugOut: value = settings.flags.ngc_debug_out; break; #endif case Setting_ResetActions: value = (!settings.homing.flags.keep_on_reset) | ((!settings.flags.keep_offsets_on_reset) << 1) | ((!settings.flags.keep_rapids_override_on_reset) << 2) | ((!settings.flags.keep_feed_override_on_reset) << 3); break; default: break; } return value; } inline static uint8_t get_decimal_places (const char *format) { char *dp = format == NULL ? NULL : strchr(format, '.'); return dp ? strchr(format, '\0') - dp - 1 : 1; } char *setting_get_value (const setting_detail_t *setting, uint_fast16_t offset) { char *value = NULL; if(setting == NULL) return NULL; switch(setting->type) { case Setting_NonCore: case Setting_IsExtended: case Setting_IsLegacy: case Setting_IsExpanded: switch(setting->datatype) { case Format_Decimal: value = ftoa(*((float *)(setting->value)), get_decimal_places(setting->format)); break; case Format_Int8: case Format_Bool: case Format_Bitfield: case Format_XBitfield: case Format_AxisMask: case Format_RadioButtons: value = uitoa(*((uint8_t *)(setting->value))); break; case Format_Int16: value = uitoa(*((uint16_t *)(setting->value))); break; case Format_Integer: value = uitoa(*((uint32_t *)(setting->value))); break; case Format_Password: value = hal.stream.state.webui_connected ? PASSWORD_MASK : ((char *)(setting->value)); break; case Format_String: case Format_IPv4: value = ((char *)(setting->value)); break; default: break; } break; case Setting_NonCoreFn: case Setting_IsExtendedFn: case Setting_IsLegacyFn: case Setting_IsExpandedFn:; setting_id_t id = (setting_id_t)(setting->id + offset); switch(setting->datatype) { case Format_Decimal: value = ftoa(((setting_get_float_ptr)(setting->get_value))(id), get_decimal_places(setting->format)); break; case Format_Password: value = hal.stream.state.webui_connected ? "********" : ((setting_get_string_ptr)(setting->get_value))(id); break; case Format_String: case Format_IPv4: value = ((setting_get_string_ptr)(setting->get_value))(id); break; default: value = uitoa(((setting_get_int_ptr)(setting->get_value))(id)); break; } break; } return value; } uint32_t setting_get_int_value (const setting_detail_t *setting, uint_fast16_t offset) { uint32_t value = 0; if(setting) switch(setting->type) { case Setting_NonCore: case Setting_IsExtended: case Setting_IsLegacy: case Setting_IsExpanded: switch(setting->datatype) { case Format_Int8: case Format_Bool: case Format_Bitfield: case Format_XBitfield: case Format_AxisMask: case Format_RadioButtons: value = *((uint8_t *)(setting->value)); break; case Format_Int16: value = *((uint16_t *)(setting->value)); break; case Format_Integer: value = *((uint32_t *)(setting->value)); break; default: break; } break; case Setting_NonCoreFn: case Setting_IsExtendedFn: case Setting_IsLegacyFn: case Setting_IsExpandedFn: switch(setting->datatype) { case Format_Decimal: case Format_String: case Format_Password: case Format_IPv4: break; default: value = ((setting_get_int_ptr)(setting->get_value))((setting_id_t)(setting->id + offset)); break; } break; } return value; } float setting_get_float_value (const setting_detail_t *setting, uint_fast16_t offset) { float value = NAN; if(setting && setting->datatype == Format_Decimal) switch(setting->type) { case Setting_NonCore: case Setting_IsExtended: case Setting_IsLegacy: case Setting_IsExpanded: value = *((float *)(setting->value)); break; case Setting_NonCoreFn: case Setting_IsExtendedFn: case Setting_IsLegacyFn: case Setting_IsExpandedFn: value = ((setting_get_float_ptr)(setting->get_value))((setting_id_t)(setting->id + offset)); break; default: break; } return value; } static bool is_group_available (const setting_detail_t *setting, uint_fast16_t offset) { return settings_is_group_available(setting->group); } static bool is_setting_available (const setting_detail_t *setting, uint_fast16_t offset) { bool available = false; if(setting) switch(setting->id) { case Setting_GangedDirInvertMask: available = hal.stepper.get_ganged && hal.stepper.get_ganged(false).mask != 0; break; case Setting_InvertProbePin: case Setting_ProbePullUpDisable: case Setting_ProbingFlags: available = hal.probe.get_state != NULL; break; case Setting_SpindlePWMBehaviour: available = false; break; case Setting_SpindlePWMOptions: available = hal.driver_cap.pwm_spindle && spindle_get_caps(false).laser; break; case Setting_ControlInvertMask: case Setting_ControlPullUpDisableMask: available = hal.signals_cap.bits & ~(control_signals_t){ .probe_triggered = On }.bits; break; case Setting_SpindleInvertMask: available = spindle_get_caps(false).gpio_controlled; break; case Setting_PWMFreq: case Setting_PWMOffValue: case Setting_PWMMinValue: case Setting_PWMMaxValue: available = hal.driver_cap.pwm_spindle; break; case Setting_SpindleType: available = spindle_get_count() > 1; break; case Setting_SpindlePPR: available = hal.driver_cap.spindle_encoder; break; case Setting_RpmMax: case Setting_RpmMin: available = spindle_get_caps(false).variable; break; case Setting_SleepEnable: available = SLEEP_DURATION > 0.0f; break; case Setting_ToolChangeMode: case Setting_ToolChangeProbingDistance: case Setting_ToolChangeFeedRate: case Setting_ToolChangeSeekRate: case Setting_ToolChangePulloffRate: case Setting_ToolChangeOptions: available = !hal.driver_cap.atc; break; case Setting_DualAxisLengthFailPercent: case Setting_DualAxisLengthFailMin: case Setting_DualAxisLengthFailMax: case Setting_AxisAutoSquareOffset: available = hal.stepper.get_ganged && hal.stepper.get_ganged(true).mask != 0; // available = hal.stepper.get_ganged && bit_istrue(hal.stepper.get_ganged(true).mask, setting->id - Setting_AxisAutoSquareOffset); break; case Setting_AxisHomingFeedRate: case Setting_AxisHomingSeekRate: available = settings.homing.flags.per_axis_feedrates; break; #ifndef NO_SAFETY_DOOR_SUPPORT case Setting_DoorOptions: available = hal.signals_cap.safety_door_ajar || !settings.parking.flags.enabled; break; case Setting_DoorSpindleOnDelay: available = spindle_get_count() && !spindle_get_caps(true).at_speed; break; case Setting_DoorCoolantOnDelay: available = hal.coolant_cap.mask != 0; break; #endif case Setting_SpindleAtSpeedTolerance: available = spindle_get_caps(true).at_speed || hal.driver_cap.spindle_encoder; break; case Setting_SpindleOnDelay: case Setting_SpindleOffDelay: available = spindle_get_count(); break; case Setting_AutoReportInterval: available = hal.get_elapsed_ticks != NULL; break; case Setting_TimeZoneOffset: available = hal.rtc.set_datetime != NULL; break; case Setting_UnlockAfterEStop: available = hal.signals_cap.e_stop; break; case Setting_EncoderSpindle: available = hal.driver_cap.spindle_encoder && spindle_get_count() > 1; break; case Setting_FSOptions: available = hal.driver_cap.sd_card || hal.driver_cap.littlefs; break; case Setting_HomePinsInvertMask: available = hal.homing.get_state != NULL && hal.home_cap.a.mask != 0; break; case Setting_CoolantOnDelay: available = hal.coolant_cap.mask != 0; break; case Setting_MotorWarningsEnable: case Setting_MotorWarningsInvert: available = hal.motor_warning_cap.a.mask != 0; break; case Setting_MotorFaultsEnable: case Setting_MotorFaultsInvert: available = hal.motor_fault_cap.a.mask != 0; break; default: break; } return available; } PROGMEM static const setting_detail_t setting_detail[] = { { Setting_PulseMicroseconds, Group_Stepper, "Step pulse time", "microseconds", Format_Decimal, "#0.0", step_us_min, NULL, Setting_IsLegacyFn, set_pulse_width, get_float, NULL }, { Setting_StepperIdleLockTime, Group_Stepper, "Step idle delay", "milliseconds", Format_Int16, "####0", NULL, "65535", Setting_IsLegacy, &settings.steppers.idle_lock_time, NULL, NULL }, { Setting_StepInvertMask, Group_Stepper, "Step pulse invert", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsLegacy, &settings.steppers.step_invert.mask, NULL, NULL }, { Setting_DirInvertMask, Group_Stepper, "Step direction invert", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsLegacy, &settings.steppers.dir_invert.mask, NULL, NULL }, #if COMPATIBILITY_LEVEL <= 2 { Setting_InvertStepperEnable, Group_Stepper, "Invert stepper enable output(s)", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsLegacy, &settings.steppers.enable_invert.mask, NULL, NULL }, #else { Setting_InvertStepperEnable, Group_Stepper, "Invert stepper enable output", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsLegacyFn, set_enable_invert_mask, get_int, NULL }, #endif #if COMPATIBILITY_LEVEL <= 1 { Setting_LimitPinsInvertMask, Group_Limits, "Invert limit inputs", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsLegacy, &settings.limits.invert.mask, NULL, NULL }, #else { Setting_LimitPinsInvertMask, Group_Limits, "Invert limit inputs", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsLegacyFn, set_limits_invert_mask, get_int, NULL }, #endif { Setting_InvertProbePin, Group_Probing, "Invert probe inputs", NULL, Format_Bitfield, probe_signals, NULL, NULL, Setting_IsLegacyFn, set_probe_invert, get_int, is_setting_available }, { Setting_SpindlePWMBehaviour, Group_Spindle, "Deprecated", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsLegacyFn, set_pwm_mode, get_int, is_setting_available }, { Setting_GangedDirInvertMask, Group_Stepper, "Ganged axes direction invert", NULL, Format_Bitfield, ganged_axes, NULL, NULL, Setting_IsExtendedFn, set_ganged_dir_invert, get_int, is_setting_available }, { Setting_SpindlePWMOptions, Group_Spindle, "PWM spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability", NULL, NULL, Setting_IsExtendedFn, set_pwm_options, get_int, is_setting_available }, #if COMPATIBILITY_LEVEL <= 1 { Setting_StatusReportMask, Group_General, "Status report options", NULL, Format_Bitfield, "Position in machine coordinate,Buffer state,Line numbers,Feed & speed,Pin state,Work coordinate offset,Overrides,Probe coordinates,Buffer sync on WCO change,Parser state,Alarm substatus,Run substatus,Enable when homing,Distance-to-go", NULL, NULL, Setting_IsExtendedFn, set_report_mask, get_int, NULL }, #else { Setting_StatusReportMask, Group_General, "Status report options", NULL, Format_Bitfield, "Position in machine coordinate,Buffer state", NULL, NULL, Setting_IsLegacyFn, set_report_mask, get_int, NULL }, #endif { Setting_JunctionDeviation, Group_General, "Junction deviation", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &settings.junction_deviation, NULL, NULL }, { Setting_ArcTolerance, Group_General, "Arc tolerance", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &settings.arc_tolerance, NULL, NULL }, { Setting_ReportInches, Group_General, "Report in inches", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsLegacyFn, set_report_inches, get_int, NULL }, { Setting_ControlInvertMask, Group_ControlSignals, "Invert control inputs", NULL, Format_Bitfield, control_signals, NULL, NULL, Setting_IsExpandedFn, set_control_invert, get_int, is_setting_available }, { Setting_CoolantInvertMask, Group_Coolant, "Invert coolant outputs", NULL, Format_Bitfield, coolant_signals, NULL, NULL, Setting_IsExtended, &settings.coolant.invert.mask, NULL, NULL }, { Setting_SpindleInvertMask, Group_Spindle, "Invert spindle signals", NULL, Format_Bitfield, spindle_signals, NULL, NULL, Setting_IsExtendedFn, set_spindle_invert, get_int, is_setting_available, { .reboot_required = On } }, { Setting_ControlPullUpDisableMask, Group_ControlSignals, "Pullup disable control inputs", NULL, Format_Bitfield, control_signals, NULL, NULL, Setting_IsExtendedFn, set_control_disable_pullup, get_int, is_setting_available }, { Setting_LimitPullUpDisableMask, Group_Limits, "Pullup disable limit inputs", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtended, &settings.limits.disable_pullup.mask, NULL, NULL }, { Setting_ProbePullUpDisable, Group_Probing, "Pullup disable probe inputs", NULL, Format_Bitfield, probe_signals, NULL, NULL, Setting_IsLegacyFn, set_probe_disable_pullup, get_int, is_setting_available }, { Setting_SoftLimitsEnable, Group_Limits, "Soft limits enable", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsLegacyFn, set_soft_limits_enable, get_int, NULL }, #if COMPATIBILITY_LEVEL <= 1 #if N_AXIS > 3 { Setting_HardLimitsEnable, Group_Limits, "Hard limits enable", NULL, Format_XBitfield, "Enable,Strict mode,Disable for rotary axes", NULL, NULL, Setting_IsExpandedFn, set_hard_limits_enable, get_int, NULL }, #else { Setting_HardLimitsEnable, Group_Limits, "Hard limits enable", NULL, Format_XBitfield, "Enable,Strict mode", NULL, NULL, Setting_IsExpandedFn, set_hard_limits_enable, get_int, NULL }, #endif #else { Setting_HardLimitsEnable, Group_Limits, "Hard limits enable", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsLegacyFn, set_hard_limits_enable, get_int, NULL }, #endif #if COMPATIBILITY_LEVEL <= 1 { Setting_HomingEnable, Group_Homing, "Homing cycle", NULL, Format_XBitfield, homing_options, NULL, NULL, Setting_IsExpandedFn, set_homing_enable, get_int, NULL }, #else { Setting_HomingEnable, Group_Homing, "Homing cycle enable", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsLegacyFn, set_homing_enable, get_int, NULL }, #endif { Setting_HomingDirMask, Group_Homing, "Homing direction invert", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsLegacy, &settings.homing.dir_mask.value, NULL, NULL }, { Setting_HomingFeedRate, Group_Homing, "Homing locate feed rate", "mm/min", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsLegacyFn, set_homing_feedrates, get_float, NULL }, { Setting_HomingSeekRate, Group_Homing, "Homing search seek rate", "mm/min", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsLegacyFn, set_homing_feedrates, get_float, NULL }, { Setting_HomingDebounceDelay, Group_Homing, "Homing switch debounce delay", "milliseconds", Format_Int16, "##0", NULL, NULL, Setting_IsLegacy, &settings.homing.debounce_delay, NULL, NULL }, { Setting_HomingPulloff, Group_Homing, "Homing switch pull-off distance", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacyFn, set_homing_pulloff, get_float, NULL }, { Setting_G73Retract, Group_General, "G73 Retract distance", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsExtended, &settings.g73_retract, NULL, NULL }, { Setting_PulseDelayMicroseconds, Group_Stepper, "Pulse delay", "microseconds", Format_Decimal, "#0.0", NULL, "20", Setting_IsExtended, &settings.steppers.pulse_delay_microseconds, NULL, NULL }, { Setting_RpmMax, Group_Spindle, "Maximum spindle speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &settings.pwm_spindle.rpm_max, NULL, is_setting_available }, { Setting_RpmMin, Group_Spindle, "Minimum spindle speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &settings.pwm_spindle.rpm_min, NULL, is_setting_available }, #if !LATHE_UVW_OPTION { Setting_Mode, Group_General, "Mode of operation", NULL, Format_RadioButtons, "Normal,Laser mode,Lathe mode", NULL, NULL, Setting_IsLegacyFn, set_mode, get_int, NULL }, #endif { Setting_PWMFreq, Group_Spindle, "Spindle PWM frequency", "Hz", Format_Decimal, "#####0", NULL, NULL, Setting_IsExtended, &settings.pwm_spindle.pwm_freq, NULL, is_setting_available }, { Setting_PWMOffValue, Group_Spindle, "Spindle PWM off value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &settings.pwm_spindle.pwm_off_value, NULL, is_setting_available }, { Setting_PWMMinValue, Group_Spindle, "Spindle PWM min value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &settings.pwm_spindle.pwm_min_value, NULL, is_setting_available }, { Setting_PWMMaxValue, Group_Spindle, "Spindle PWM max value", "percent", Format_Decimal, "##0.0", NULL, "100", Setting_IsExtended, &settings.pwm_spindle.pwm_max_value, NULL, is_setting_available }, { Setting_SteppersEnergize, Group_Stepper, "Steppers to keep enabled", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtendedFn, set_stepper_energize_mask, get_int, NULL }, { Setting_SpindlePPR, Group_Spindle, "Spindle pulses per revolution (PPR)", NULL, Format_Int16, "###0", NULL, NULL, Setting_IsExtended, &settings.spindle.ppr, NULL, is_setting_available, { .reboot_required = On } }, { Setting_EnableLegacyRTCommands, Group_General, "Enable legacy RT commands", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_enable_legacy_rt_commands, get_int, NULL }, { Setting_JogSoftLimited, Group_Jogging, "Limit jog commands", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_jog_soft_limited, get_int, NULL }, { Setting_ParkingEnable, Group_SafetyDoor, "Parking cycle", NULL, Format_XBitfield, "Enable,Deactivate upon init,Enable parking override control", NULL, NULL, Setting_IsExtendedFn, set_parking_enable, get_int, NULL }, { Setting_ParkingAxis, Group_SafetyDoor, "Parking axis", NULL, Format_RadioButtons, "X,Y,Z", NULL, NULL, Setting_IsExtended, &settings.parking.axis, NULL, NULL }, { Setting_HomingLocateCycles, Group_Homing, "Homing passes", NULL, Format_Int8, "##0", "1", "128", Setting_IsExtended, &settings.homing.locate_cycles, NULL, NULL }, { Setting_HomingCycle_1, Group_Homing, "Axes homing, first phase", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtendedFn, set_homing_cycle, get_int, NULL }, { Setting_HomingCycle_2, Group_Homing, "Axes homing, second phase", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtendedFn, set_homing_cycle, get_int, NULL }, { Setting_HomingCycle_3, Group_Homing, "Axes homing, third phase", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtendedFn, set_homing_cycle, get_int, NULL }, #ifdef A_AXIS { Setting_HomingCycle_4, Group_Homing, "Axes homing, fourth phase", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtendedFn, set_homing_cycle, get_int, NULL }, #endif #ifdef B_AXIS { Setting_HomingCycle_5, Group_Homing, "Axes homing, fifth phase", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtendedFn, set_homing_cycle, get_int, NULL }, #endif #ifdef C_AXIS { Setting_HomingCycle_6, Group_Homing, "Axes homing, sixth phase", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtendedFn, set_homing_cycle, get_int, NULL }, #endif { Setting_ParkingPulloutIncrement, Group_SafetyDoor, "Parking pull-out distance", "mm", Format_Decimal, "###0.0", NULL, NULL, Setting_IsExtended, &settings.parking.pullout_increment, NULL, NULL }, { Setting_ParkingPulloutRate, Group_SafetyDoor, "Parking pull-out rate", "mm/min", Format_Decimal, "###0.0", NULL, NULL, Setting_IsExtended, &settings.parking.pullout_rate, NULL, NULL }, { Setting_ParkingTarget, Group_SafetyDoor, "Parking target", "mm", Format_Decimal, "-###0.0", "-100000", NULL, Setting_IsExtended, &settings.parking.target, NULL, NULL }, { Setting_ParkingFastRate, Group_SafetyDoor, "Parking fast rate", "mm/min", Format_Decimal, "###0.0", NULL, NULL, Setting_IsExtended, &settings.parking.rate, NULL, NULL }, { Setting_RestoreOverrides, Group_General, "Restore overrides", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_restore_overrides, get_int, NULL }, #ifndef NO_SAFETY_DOOR_SUPPORT { Setting_DoorOptions, Group_SafetyDoor, "Safety door options", NULL, Format_Bitfield, door_options, NULL, NULL, Setting_IsExtended, &settings.safety_door.flags.value, NULL, is_setting_available }, #endif { Setting_SleepEnable, Group_General, "Sleep enable", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_sleep_enable, get_int, is_setting_available }, { Setting_HoldActions, Group_General, "Feed hold actions", NULL, Format_Bitfield, "Disable laser during hold,Restore spindle and coolant state on resume", NULL, NULL, Setting_IsExtendedFn, set_hold_actions, get_int, NULL }, { Setting_ForceInitAlarm, Group_General, "Force init alarm", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_force_initialization_alarm, get_int, NULL }, { Setting_ProbingFlags, Group_Probing, "Probing options", NULL, Format_Bitfield, probing_options, NULL, NULL, Setting_IsExtendedFn, set_probe_flags, get_int, is_setting_available }, #if ENABLE_SPINDLE_LINEARIZATION { Setting_LinearSpindlePiece1, Group_Spindle, "Spindle linearisation, 1st point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL }, #if SPINDLE_NPWM_PIECES > 1 { Setting_LinearSpindlePiece2, Group_Spindle, "Spindle linearisation, 2nd point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL }, #endif #if SPINDLE_NPWM_PIECES > 2 { Setting_LinearSpindlePiece3, Group_Spindle, "Spindle linearisation, 3rd point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL }, #endif #if SPINDLE_NPWM_PIECES > 3 { Setting_LinearSpindlePiece4, Group_Spindle, "Spindle linearisation, 4th point", NULL, Format_String, "x(39)", NULL, "39", Setting_IsExtendedFn, set_linear_piece, get_linear_piece, NULL }, #endif #endif { Setting_PositionPGain, Group_Spindle_Sync, "Spindle sync P-gain", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_IsExtended, &settings.position.pid.p_gain, NULL, is_group_available }, { Setting_PositionIGain, Group_Spindle_Sync, "Spindle sync I-gain", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_IsExtended, &settings.position.pid.i_gain, NULL, is_group_available }, { Setting_PositionDGain, Group_Spindle_Sync, "Spindle sync D-gain", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_IsExtended, &settings.position.pid.d_gain, NULL, is_group_available }, { Setting_PositionIMaxError, Group_Spindle_Sync, "Spindle sync PID max I error", NULL, Format_Decimal, "###0.000", NULL, NULL, Setting_IsExtended, &settings.position.pid.i_max_error, NULL, is_group_available }, { Setting_AxisStepsPerMM, Group_Axis0, "-axis travel resolution", axis_steps, Format_Decimal, "#####0.000##", NULL, NULL, Setting_IsLegacyFn, set_axis_setting, get_float, NULL, AXIS_OPTS }, { Setting_AxisMaxRate, Group_Axis0, "-axis maximum rate", axis_rate, Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacyFn, set_axis_setting, get_float, NULL, AXIS_OPTS }, { Setting_AxisAcceleration, Group_Axis0, "-axis acceleration", axis_accel, Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacyFn, set_axis_setting, get_float, NULL, AXIS_OPTS }, { Setting_AxisMaxTravel, Group_Axis0, "-axis maximum travel", axis_dist, Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacyFn, set_axis_setting, get_float, NULL, AXIS_OPTS }, #if ENABLE_BACKLASH_COMPENSATION { Setting_AxisBacklash, Group_Axis0, "-axis backlash compensation", axis_dist, Format_Decimal, "#####0.000##", NULL, NULL, Setting_IsExtendedFn, set_axis_setting, get_float, NULL, AXIS_OPTS }, #endif #if ENABLE_JERK_ACCELERATION { Setting_AxisJerk, Group_Axis0, "-axis jerk", axis_jerk, Format_Decimal, "#####0.000", NULL, NULL, Setting_IsExtendedFn, set_axis_setting, get_float, NULL, AXIS_OPTS }, #endif { Setting_AxisAutoSquareOffset, Group_Axis0, "-axis dual axis offset", "mm", Format_Decimal, "-0.000", "-10", "10", Setting_IsExtendedFn, set_axis_setting, get_float, is_setting_available, AXIS_OPTS }, { Setting_AxisHomingFeedRate, Group_Axis0, "-axis homing locate feed rate", axis_rate, Format_Decimal, "###0", NULL, NULL, Setting_NonCoreFn, set_axis_setting, get_float, is_setting_available, AXIS_OPTS }, { Setting_AxisHomingSeekRate, Group_Axis0, "-axis homing search seek rate", axis_rate, Format_Decimal, "###0", NULL, NULL, Setting_NonCoreFn, set_axis_setting, get_float, is_setting_available, AXIS_OPTS }, { Setting_SpindleAtSpeedTolerance, Group_Spindle, "Spindle at speed tolerance", "percent", Format_Decimal, "##0.0", NULL, NULL, Setting_IsExtendedFn, set_float, get_float, is_setting_available }, { Setting_ToolChangeMode, Group_Toolchange, "Tool change mode", NULL, Format_RadioButtons, "Normal,Manual touch off,Manual touch off @ G59.3,Automatic touch off @ G59.3,Ignore M6", NULL, NULL, Setting_IsExtendedFn, set_tool_change_mode, get_int, is_setting_available }, { Setting_ToolChangeProbingDistance, Group_Toolchange, "Tool change probing distance", "mm", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsExtendedFn, set_tool_change_probing_distance, get_float, is_setting_available }, { Setting_ToolChangeFeedRate, Group_Toolchange, "Tool change locate feed rate", "mm/min", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsExtended, &settings.tool_change.feed_rate, NULL, is_setting_available }, { Setting_ToolChangeSeekRate, Group_Toolchange, "Tool change search seek rate", "mm/min", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsExtended, &settings.tool_change.seek_rate, NULL, is_setting_available }, { Setting_ToolChangePulloffRate, Group_Toolchange, "Tool change probe pull-off rate", "mm/min", Format_Decimal, "#####0.0", NULL, NULL, Setting_IsExtended, &settings.tool_change.pulloff_rate, NULL, is_setting_available }, { Setting_ToolChangeOptions, Group_Toolchange, "Tool change options", NULL, Format_Bitfield, "Restore position after M6,Change tool at G30,Fast probe pull off", NULL, NULL, Setting_IsExtendedFn, set_toolchange_flags, get_int, is_setting_available }, { Setting_DualAxisLengthFailPercent, Group_Limits_DualAxis, "Dual axis length fail", "percent", Format_Decimal, "##0.0", "0", "100", Setting_IsExtended, &settings.homing.dual_axis.fail_length_percent, NULL, is_setting_available }, { Setting_DualAxisLengthFailMin, Group_Limits_DualAxis, "Dual axis length fail min", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsExtended, &settings.homing.dual_axis.fail_distance_min, NULL, is_setting_available }, { Setting_DualAxisLengthFailMax, Group_Limits_DualAxis, "Dual axis length fail max", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsExtended, &settings.homing.dual_axis.fail_distance_max, NULL, is_setting_available }, #if COMPATIBILITY_LEVEL <= 1 { Setting_DisableG92Persistence, Group_General, "Disable G92 persistence", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_g92_disable_persistence, get_int, NULL }, #endif #if N_AXIS > 3 { Settings_RotaryAxes, Group_Stepper, "Rotary axes", NULL, Format_Bitfield, rotary_axes, NULL, NULL, Setting_IsExtendedFn, set_rotary_axes, get_int, NULL }, #endif { Setting_DoorSpindleOnDelay, Group_SafetyDoor, "Spindle on delay", "s", Format_Decimal, "#0.0", "0.5", "20", Setting_IsExtended, &settings.safety_door.spindle_on_delay, NULL, NULL, { .allow_null = On } }, { Setting_DoorCoolantOnDelay, Group_SafetyDoor, "Coolant on delay", "s", Format_Decimal, "#0.0", "0.5", "20", Setting_IsExtended, &settings.safety_door.coolant_on_delay, NULL, NULL, { .allow_null = On } }, { Setting_SpindleOnDelay, Group_Spindle, "Spindle on delay", "s", Format_Decimal, "#0.0", "0.5", "20", Setting_IsExtendedFn, set_float, get_float, is_setting_available, { .allow_null = On } }, { Setting_SpindleType, Group_Spindle, "Default spindle", NULL, Format_RadioButtons, spindle_types, NULL, NULL, Setting_IsExtendedFn, set_default_spindle, get_int, is_setting_available, { .reboot_required = On } }, { Setting_PlannerBlocks, Group_General, "Planner buffer blocks", NULL, Format_Int16, "####0", "30", "1000", Setting_IsExtended, &settings.planner_buffer_blocks, NULL, NULL, { .reboot_required = On } }, { Setting_AutoReportInterval, Group_General, "Autoreport interval", "ms", Format_Int16, "###0", "100", "1000", Setting_IsExtendedFn, set_report_interval, get_int, NULL, { .reboot_required = On, .allow_null = On } }, // { Setting_TimeZoneOffset, Group_General, "Timezone offset", NULL, Format_Decimal, "-#0.00", "0", "12", Setting_IsExtended, &settings.timezone, NULL, NULL }, { Setting_UnlockAfterEStop, Group_General, "Unlock required after E-Stop", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_estop_unlock, get_int, is_setting_available }, { Setting_EnableToolPersistence, Group_Toolchange, "Keep tool number over reboot", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_persistent_tool, get_int, NULL }, #if NGC_EXPRESSIONS_ENABLE { Setting_NGCDebugOut, Group_General, "Output NGC debug messages", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_ngc_debug_out, get_int, NULL }, #endif #if COMPATIBILITY_LEVEL <= 1 { Setting_OffsetLock, Group_General, "Lock coordinate systems", NULL, Format_Bitfield, "G59.1,G59.2,G59.3", NULL, NULL, Setting_IsExtended, &settings.offset_lock.mask, NULL, NULL }, #endif { Setting_EncoderSpindle, Group_Spindle, "Encoder spindle", NULL, Format_RadioButtons, spindle_types, NULL, NULL, Setting_IsExtendedFn, set_encoder_spindle, get_int, is_setting_available }, #if N_AXIS > 3 { Setting_RotaryWrap, Group_Stepper, "Fast rotary go to G28", NULL, Format_Bitfield, rotary_axes, NULL, NULL, Setting_IsExtendedFn, set_rotary_wrap_axes, get_int, NULL }, #endif { Setting_SpindleOffDelay, Group_Spindle, "Spindle off delay", "s", Format_Decimal, "#0.0", "0.5", "20", Setting_IsExtendedFn, set_float, get_float, is_setting_available, { .allow_null = On } }, { Setting_FSOptions, Group_General, "File systems options", NULL, Format_Bitfield, fs_options, NULL, NULL, Setting_IsExtended, &settings.fs_options.mask, NULL, is_setting_available }, { Setting_HomePinsInvertMask, Group_Limits, "Invert home inputs", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtended, &settings.home_invert.mask, NULL, is_setting_available }, { Setting_CoolantOnDelay, Group_Coolant, "Coolant on delay", "s", Format_Decimal, "#0.0", "0.5", "20", Setting_IsExtendedFn, set_float, get_float, is_setting_available, { .allow_null = On } }, { Setting_MotorWarningsEnable, Group_Stepper, "Motor warning inputs enable", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtended, &settings.motor_warning_enable, NULL, is_setting_available }, { Setting_MotorWarningsInvert, Group_Stepper, "Invert motor warning inputs", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtended, &settings.motor_warning_invert, NULL, is_setting_available }, { Setting_MotorFaultsEnable, Group_Stepper, "Motor fault inputs enable", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtended, &settings.motor_fault_enable, NULL, is_setting_available }, { Setting_MotorFaultsInvert, Group_Stepper, "Invert motor fault inputs", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtended, &settings.motor_fault_invert, NULL, is_setting_available }, { Setting_ResetActions, Group_General, "Reset actions", NULL, Format_Bitfield, "Clear homed status if position was lost,Clear offsets (except G92),Clear rapids override,Clear feed override", NULL, NULL, Setting_IsExtendedFn, set_reset_actions, get_int, NULL }, { Setting_StepperEnableDelay, Group_Stepper, "Stepper enable delay", "ms", Format_Int16, "##0", NULL, "500", Setting_IsExtended, &settings.stepper_enable_delay, NULL, NULL }, }; PROGMEM static const setting_descr_t setting_descr[] = { { Setting_PulseMicroseconds, "Step pulse length in microseconds.\\n" "Minimum depends on the processor and is typically in the range of 1 - 2.5.\\n\\n" "The length has to be reduced from the default value of 5 when max. step rate exceed approximately 140 kHz." }, { Setting_StepperIdleLockTime, "Sets a short hold delay when stopping to let dynamics settle before disabling steppers. Value 255 keeps motors enabled." }, { Setting_StepInvertMask, "Inverts the step signals (active low)." }, { Setting_DirInvertMask, "Inverts the direction signals (active low)." }, #if COMPATIBILITY_LEVEL <= 2 { Setting_InvertStepperEnable, "Inverts the stepper driver enable signals. Most drivers uses active low enable requiring inversion.\\n\\n" "NOTE: If the stepper drivers shares the same enable signal only X is used." }, #else { Setting_InvertStepperEnable, "Inverts the stepper driver enable signals. Drivers using active high enable require inversion.\\n\\n" }, #endif { Setting_LimitPinsInvertMask, "Inverts the axis limit input signals." }, { Setting_InvertProbePin, "Inverts the probe input signal(s)." }, { Setting_SpindlePWMOptions, "Enable controls PWM output availability.\\n" "When `RPM controls spindle enable signal` is checked and M3 or M4 is active S0 switches it off and S > 0 switches it on." }, { Setting_GangedDirInvertMask, "Inverts the direction signals for the second motor used for ganged axes.\\n\\n" "NOTE: This inversion will be applied in addition to the inversion from setting $3." }, { Setting_StatusReportMask, "Specifies optional data included in status reports and if report is sent when homing.\\n" "If Run substatus is enabled it may be used for simple probe protection.\\n\\n" "NOTE: Parser state will be sent separately after the status report and only on changes." }, { Setting_JunctionDeviation, "Sets how fast grblHAL travels through consecutive motions. Lower value slows it down." }, { Setting_ArcTolerance, "Sets the G2 and G3 arc tracing accuracy based on radial error. Beware: A very small value may effect performance." }, { Setting_ReportInches, "Enables inch units when returning any position and rate value that is not a settings value." }, { Setting_ControlInvertMask, "Inverts the control signals (active low).\\n" "NOTE: Block delete, Optional stop, EStop and Probe connected are optional signals, availability is driver dependent." }, { Setting_CoolantInvertMask, "Inverts the coolant and mist signals (active low)." }, { Setting_SpindleInvertMask, "Inverts the spindle on, counterclockwise and PWM signals (active low)." }, { Setting_ControlPullUpDisableMask, "Disable the control signals pullup resistors. Potentially enables pulldown resistor if available.\\n" "NOTE: Block delete, Optional stop and EStop are optional signals, availability is driver dependent." }, { Setting_LimitPullUpDisableMask, "Disable the limit signals pullup resistors. Potentially enables pulldown resistors if available."}, { Setting_ProbePullUpDisable, "Disable the probe signal pullup resistor(s). Potentially enables pulldown resistor(s) if available." }, { Setting_SoftLimitsEnable, "Enables soft limits checks within machine travel and sets alarm when exceeded. Requires homing." }, { Setting_HardLimitsEnable, "When enabled immediately halts motion and throws an alarm when a limit switch is triggered. In strict mode only homing is possible when a switch is engaged." }, { Setting_HomingEnable, "Enables homing cycle. Requires limit switches on axes to be automatically homed.\\n\\n" "When `Enable single axis commands` is checked, single axis homing can be performed by $H commands.\\n\\n" "When `Allow manual` is checked, axes not homed automatically may be homed manually by $H or $H commands.\\n\\n" "`Override locks` is for allowing a soft reset to disable `Homing on startup required`." }, { Setting_HomingDirMask, "Homing searches for a switch in the positive direction. Set axis bit to search in negative direction." }, { Setting_HomingFeedRate, "Feed rate to slowly engage limit switch to determine its location accurately." }, { Setting_HomingSeekRate, "Seek rate to quickly find the limit switch before the slower locating phase." }, { Setting_HomingDebounceDelay, "Sets a short delay between phases of homing cycle to let a switch debounce." }, { Setting_HomingPulloff, "Retract distance after triggering switch to disengage it. Homing will fail if switch isn't cleared." }, { Setting_G73Retract, "G73 retract distance (for chip breaking drilling)." }, { Setting_PulseDelayMicroseconds, "Step pulse delay.\\n\\n" "When set > 0 and less than 2 the value is rounded up to 2 microseconds." }, { Setting_RpmMax, "Maximum spindle speed, can be overridden by spindle plugins." }, { Setting_RpmMin, "Minimum spindle speed, can be overridden by spindle plugins.\\n\\n" "When set > 0 $35 (PWM min value) may have to be set to get the configured RPM."}, #if !LATHE_UVW_OPTION { Setting_Mode, "Laser mode: consecutive G1/2/3 commands will not halt when spindle speed is changed.\\n" "Lathe mode: allows use of G7, G8, G96 and G97." }, #endif { Setting_PWMFreq, "Spindle PWM frequency." }, { Setting_PWMOffValue, "Spindle PWM off value in percent (duty cycle)." }, { Setting_PWMMinValue, "Spindle PWM min value in percent (duty cycle)." }, { Setting_PWMMaxValue, "Spindle PWM max value in percent (duty cycle)." }, { Setting_SteppersEnergize, "Specifies which steppers not to disable when stopped." }, { Setting_SpindlePPR, "Spindle encoder pulses per revolution." }, { Setting_EnableLegacyRTCommands, "Enables \"normal\" processing of ?, ! and ~ characters when part of $-setting or comment. If disabled then they are added to the input string instead." }, { Setting_JogSoftLimited, "Limit jog commands to machine workspace for homed axes." }, { Setting_ParkingEnable, "Enables parking cycle, requires parking axis homed." }, { Setting_ParkingAxis, "Define which axis that performs the parking motion." }, { Setting_HomingLocateCycles, "Number of homing passes. Minimum 1, maximum 128." }, { Setting_HomingCycle_1, "Axes to home in first phase." }, { Setting_HomingCycle_2, "Axes to home in second phase." }, { Setting_HomingCycle_3, "Axes to home in third phase." }, #ifdef A_AXIS { Setting_HomingCycle_4, "Axes to home in fourth phase." }, #endif #ifdef B_AXIS { Setting_HomingCycle_5, "Axes to home in fifth phase." }, #endif #ifdef C_AXIS { Setting_HomingCycle_6, "Axes to home in sixth phase." }, #endif { Setting_JogStepSpeed, "Step jogging speed in millimeters per minute." }, { Setting_JogSlowSpeed, "Slow jogging speed in millimeters per minute." }, { Setting_JogFastSpeed, "Fast jogging speed in millimeters per minute." }, { Setting_JogStepDistance, "Jog distance for single step jogging." }, { Setting_JogSlowDistance, "Jog distance before automatic stop." }, { Setting_JogFastDistance, "Jog distance before automatic stop." }, { Setting_ParkingPulloutIncrement, "Spindle pull-out and plunge distance in mm.Incremental distance." }, { Setting_ParkingPulloutRate, "Spindle pull-out/plunge slow feed rate in mm/min." }, { Setting_ParkingTarget, "Parking axis target. In mm, as machine coordinate [-max_travel, 0]." }, { Setting_ParkingFastRate, "Parking fast rate to target after pull-out in mm/min." }, { Setting_RestoreOverrides, "Restore overrides to default values at program end." }, #ifndef NO_SAFETY_DOOR_SUPPORT { Setting_DoorOptions, "Ignore when idle: disregard door signal in IDLE state to allow jogging etc. Available when controller has door input.\n" "Keep coolant state on open: do not turn off coolant if on." }, #endif { Setting_SleepEnable, "Enable sleep mode." }, { Setting_HoldActions, "Actions taken during feed hold and on resume from feed hold." }, { Setting_ForceInitAlarm, "Start in alarm mode after a cold reset." }, { Setting_ProbingFlags, "Allow feed override during probing and/or limit probing commands to machine workspace for homed axes." }, #if ENABLE_SPINDLE_LINEARIZATION { Setting_LinearSpindlePiece1, "Comma separated list of values: RPM_MIN, RPM_LINE_A1, RPM_LINE_B1, set to blank to disable." }, #if SPINDLE_NPWM_PIECES > 1 { Setting_LinearSpindlePiece2, "Comma separated list of values: RPM_POINT12, RPM_LINE_A2, RPM_LINE_B2, set to blank to disable." }, #endif #if SPINDLE_NPWM_PIECES > 2 { Setting_LinearSpindlePiece3, "Comma separated list of values: RPM_POINT23, RPM_LINE_A3, RPM_LINE_B3, set to blank to disable." }, #endif #if SPINDLE_NPWM_PIECES > 3 { Setting_LinearSpindlePiece4, "Comma separated list of values: RPM_POINT34, RPM_LINE_A4, RPM_LINE_B4, set to blank to disable." }, #endif #endif { Setting_PositionPGain, "" }, { Setting_PositionIGain, "" }, { Setting_PositionDGain, "" }, { Setting_PositionIMaxError, "Spindle sync PID max integrator error." }, { Setting_AxisStepsPerMM, "Travel resolution in steps per millimeter." }, { (setting_id_t)(Setting_AxisStepsPerMM + 1), "Travel resolution in steps per degree." }, // "Hack" to get correct description for rotary axes { Setting_AxisMaxRate, "Maximum rate. Used as G0 rapid rate." }, { Setting_AxisAcceleration, "Acceleration. Used for motion planning to not exceed motor torque and lose steps." }, #if ENABLE_JERK_ACCELERATION { Setting_AxisJerk, "Maximum rate of acceleration change - smoothes out acceleration profile up to max axis acceleration.\\n\\n" "Minimum value of x10 Acceleration setting to ensure decent acceleration times.\\n" "Maximum is calculated by current acceleration and stepper segment time.\\n" "At Maximum value motion is effectively trapezoidal instead of constant jerk.\\n\\n" "Can be increased by adjusting ACCELERATION_TICKS_PER_SECOND to a larger value before compiling."}, #endif { Setting_AxisMaxTravel, "Maximum axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances." }, #if ENABLE_BACKLASH_COMPENSATION { Setting_AxisBacklash, "Backlash distance to compensate for." }, #endif { Setting_AxisAutoSquareOffset, "Offset between sides to compensate for homing switches inaccuracies." }, { Setting_AxisHomingFeedRate, "Feed rate to slowly engage limit switch to determine its location accurately." }, { Setting_AxisHomingSeekRate, "Seek rate to quickly find the limit switch before the slower locating phase." }, { Setting_SpindleAtSpeedTolerance, "Spindle at speed tolerance as percentage deviation from programmed speed, set to 0 to disable.\\n" "If not within tolerance after timeout set by spindle on delay ($394) alarm 14 is raised.\\n" "NOTE: if the spindle on delay is set to 0 the timeout defaults to one minute." }, { Setting_ToolChangeMode, "Normal: allows jogging for manual touch off. Set new position manually.\\n\\n" "Manual touch off: rapids to tool change position, use jogging or $TPW for touch off.\\n\\n" "Manual touch off @ G59.3: rapids to tool change position, after change to G59.3 position for manual touch off. Use jogging or $TPW for touch off.\\n\\n" "Automatic touch off @ G59.3: rapids to tool change position, after change to G59.3 position for automatic touch off.\\n\\n" "Depending on settings the tool (controlled point) will be moved back to the to original position after touch off. " "\"tool change position\" is either tool axis home, G59.3 or G30 position depending on settings." }, { Setting_ToolChangeProbingDistance, "Maximum probing distance for automatic or $TPW touch off." }, { Setting_ToolChangeFeedRate, "Feed rate to slowly engage tool change sensor to determine the tool offset accurately." }, { Setting_ToolChangeSeekRate, "Seek rate to quickly find the tool change sensor before the slower locating phase." }, { Setting_ToolChangePulloffRate, "Pull-off rate for the retract move before the slower locating phase." }, { Setting_ToolChangeOptions, "Restore position after M6: when set the spindle is moved so that the controlled point (tool tip) is the same as before the M6 command," "if not the spindle is only moved to the Z home position.\\n\\n" "Change tool at G30: when set rapids to the G30 position via tool axis home. Requires axes to be homed.\\n\\n" "Fast probe pulloff: use G38.4 style probing for faster touch off." }, { Setting_DualAxisLengthFailPercent, "Dual axis length fail in percent of axis max travel." }, { Setting_DualAxisLengthFailMin, "Dual axis length fail minimum distance." }, { Setting_DualAxisLengthFailMax, "Dual axis length fail maximum distance." }, #if COMPATIBILITY_LEVEL <= 1 { Setting_DisableG92Persistence, "Disables save/restore of G92 offset to non-volatile storage (NVS)." }, #endif #if N_AXIS > 3 { Settings_RotaryAxes, "Designates axes as rotary, interpretation some other relevant axis settings is changed accordingly." }, #endif #ifndef NO_SAFETY_DOOR_SUPPORT { Setting_DoorSpindleOnDelay, "Delay to allow spindle to spin up after safety door is closed or on resume from park." }, { Setting_DoorCoolantOnDelay, "Delay to allow coolant to restart after safety door is closed or on resume from park." }, #endif { Setting_SpindleOnDelay, "Delay to allow spindle to spin up. 0 or 0.5 - 20s\\n" "If spindle supports \"at speed\" functionality it is the time to wait before alarm 14 is raised." }, { Setting_SpindleType, "Spindle selected on startup." }, { Setting_PlannerBlocks, "Number of blocks in the planner buffer." }, { Setting_AutoReportInterval, "Interval the real time report will be sent, set to 0 to disable." }, { Setting_TimeZoneOffset, "Offset in hours from UTC." }, { Setting_UnlockAfterEStop, "If set unlock (by sending $X) is required after resetting a cleared E-Stop condition." }, #if COMPATIBILITY_LEVEL <= 1 { Setting_OffsetLock, "Lock coordinate systems against accidental changes." }, #endif #if NGC_EXPRESSIONS_ENABLE { Setting_NGCDebugOut, "Example: (debug, metric mode: #<_metric>, coord system: #5220)" }, #endif { Setting_SpindleOffDelay, "Delay to allow spindle to spin down. 0 or 0.5 - 20s\\n" "If spindle supports \"at speed\" functionality it is the time to wait before alarm 14 is raised." }, { Setting_EncoderSpindle, "Specifies which spindle has the encoder attached." }, #if N_AXIS > 3 { Setting_RotaryWrap, "Perform fast move to angle stored in G28 position.\\n" "Use:\\n" " G91G280\\n" " G90\\n" }, #endif { Setting_FSOptions, "Auto mount SD card on startup." }, { Setting_HomePinsInvertMask, "Inverts the axis home input signals." }, { Setting_CoolantOnDelay, "Delay to allow coolant to start. 0 or 0.5 - 20s." }, { Setting_ResetActions, "Controls actions taken on a soft reset." }, { Setting_StepperEnableDelay, "Delay from stepper enable to first step output. The driver typically adds ~2ms to this." } /* { Setting_MotorWarningsEnable, "Motor warning enable" }, { Setting_MotorWarningsInvert, "Invert motor warning inputs" }, { Setting_MotorFaultsEnable, "Motor fault enable" }, { Setting_MotorFaultsInvert, "Invert motor fault inputs" } */ }; static setting_details_t setting_details = { .is_core = true, .groups = setting_group_detail, .n_groups = sizeof(setting_group_detail) / sizeof(setting_group_detail_t), .settings = setting_detail, .n_settings = sizeof(setting_detail) / sizeof(setting_detail_t), .descriptions = setting_descr, .n_descriptions = sizeof(setting_descr) / sizeof(setting_descr_t), .save = settings_write_global }; static setting_details_t *settingsd = &setting_details; void settings_register (setting_details_t *details) { settingsd->next = details; settingsd = details; } setting_details_t *settings_get_details (void) { return &setting_details; } /**/ // Write build info to persistent storage void settings_write_build_info (char *line) { if(hal.nvs.type != NVS_None) hal.nvs.memcpy_to_nvs(NVS_ADDR_BUILD_INFO, (uint8_t *)line, sizeof(stored_line_t), true); } // Read build info from persistent storage. bool settings_read_build_info(char *line) { if (!(hal.nvs.type != NVS_None && hal.nvs.memcpy_from_nvs((uint8_t *)line, NVS_ADDR_BUILD_INFO, sizeof(stored_line_t), true) == NVS_TransferResult_OK)) { settings_restore((settings_restore_t){ .build_info = On }); return false; } return true; } // Write startup line to persistent storage void settings_write_startup_line (uint8_t idx, char *line) { assert(idx < N_STARTUP_LINE); #ifdef FORCE_BUFFER_SYNC_DURING_NVS_WRITE protocol_buffer_synchronize(); // A startup line may contain a motion and be executing. #endif if(hal.nvs.type != NVS_None) hal.nvs.memcpy_to_nvs(NVS_ADDR_STARTUP_BLOCK + idx * (sizeof(stored_line_t) + NVS_CRC_BYTES), (uint8_t *)line, sizeof(stored_line_t), true); } // Read startup line to persistent storage. bool settings_read_startup_line (uint8_t idx, char *line) { assert(idx < N_STARTUP_LINE); if (!(hal.nvs.type != NVS_None && hal.nvs.memcpy_from_nvs((uint8_t *)line, NVS_ADDR_STARTUP_BLOCK + idx * (sizeof(stored_line_t) + NVS_CRC_BYTES), sizeof(stored_line_t), true) == NVS_TransferResult_OK)) { // Reset line with default value *line = '\0'; // Empty line settings_write_startup_line(idx, line); return false; } return true; } // Write selected coordinate data to persistent storage. void settings_write_coord_data (coord_system_id_t id, const float (*coord_data)[N_AXIS]) { assert(id <= N_CoordinateSystems); #ifdef FORCE_BUFFER_SYNC_DURING_NVS_WRITE protocol_buffer_synchronize(); #endif if(grbl.on_wco_saved) grbl.on_wco_saved(id, (coord_data_t *)coord_data); if(hal.nvs.type != NVS_None) hal.nvs.memcpy_to_nvs(NVS_ADDR_PARAMETERS + id * (sizeof(coord_data_t) + NVS_CRC_BYTES), (uint8_t *)coord_data, sizeof(coord_data_t), true); } // Read selected coordinate data from persistent storage. bool settings_read_coord_data (coord_system_id_t id, const float (*coord_data)[N_AXIS]) { assert(id <= N_CoordinateSystems); if (!(hal.nvs.type != NVS_None && hal.nvs.memcpy_from_nvs((uint8_t *)coord_data, NVS_ADDR_PARAMETERS + id * (sizeof(coord_data_t) + NVS_CRC_BYTES), sizeof(coord_data_t), true) == NVS_TransferResult_OK)) { // Reset with default zero vector memcpy((float *)coord_data, null_vector.values, sizeof(coord_data_t)); settings_write_coord_data(id, coord_data); return false; } return true; } #if N_TOOLS static tool_data_t tool_data[N_TOOLS + 1]; // Write selected tool data to persistent storage. static bool settings_set_tool_data (tool_data_t *tool) { bool ok = tool->tool_id <= N_TOOLS; if(ok && tool->tool_id && hal.nvs.type != NVS_None) hal.nvs.memcpy_to_nvs(NVS_ADDR_TOOL_TABLE + (tool->tool_id - 1) * (sizeof(tool_data_t) + NVS_CRC_BYTES), (uint8_t *)tool, sizeof(tool_data_t), true); return ok; } // Read selected tool data from persistent storage. static tool_table_entry_t *settings_get_tool_data (tool_id_t tool_id) { static tool_table_entry_t tool = {0}; tool.data = tool_id <= N_TOOLS ? &tool_data[tool_id] : NULL; if(tool_id && tool.data && !(hal.nvs.type != NVS_None && hal.nvs.memcpy_from_nvs((uint8_t *)tool.data, NVS_ADDR_TOOL_TABLE + (tool_id - 1) * (sizeof(tool_data_t) + NVS_CRC_BYTES), sizeof(tool_data_t), true) == NVS_TransferResult_OK && tool.data->tool_id == tool_id)) { memset(tool.data, 0, sizeof(tool_data_t)); tool.data->tool_id = tool_id; } tool.pocket = (pocket_id_t)(tool.data ? tool_id : -1); return &tool; } // Clear all tool data in persistent storage. static bool settings_clear_tool_data (void) { uint_fast8_t idx; tool_data_t tool_data; memset(&tool_data, 0, sizeof(tool_data_t)); for (idx = 1; idx <= N_TOOLS; idx++) { tool_data.tool_id = (tool_id_t)idx; settings_set_tool_data(&tool_data); } return true; } #else static tool_data_t tool_data = {0}; // Write selected tool data to persistent storage. static bool settings_set_tool_data (tool_data_t *tool_data) { return true; } // Read selected tool data from persistent storage. static tool_table_entry_t *settings_get_tool_data (tool_id_t tool_id) { static tool_table_entry_t tool = {0}; if(tool_id <= MAX_TOOL_NUMBER) { tool_data.tool_id = tool_id; tool.pocket = (pocket_id_t)tool_id; tool.data = &tool_data; } else { tool.data = NULL; tool.pocket = (pocket_id_t)-1; } return &tool; } // Clear all tool data in persistent storage. static bool settings_clear_tool_data (void) { memset(&tool_data, 0, sizeof(tool_data_t)); return true; } #endif // N_TOOLS // Sanity check of settings, board map could have been changed... static void sanity_check (void) { #if LATHE_UVW_OPTION settings.mode = Mode_Lathe; #else if(settings.mode == Mode_Laser && !spindle_get_caps(false).laser) settings.mode = Mode_Standard; #endif if(settings.planner_buffer_blocks < 30 || settings.planner_buffer_blocks > 1000) settings.planner_buffer_blocks = 35; if(!hal.driver_cap.spindle_encoder) settings.spindle.ppr = 0; if(settings.steppers.pulse_microseconds < hal.step_us_min) settings.steppers.pulse_microseconds = hal.step_us_min; if(hal.max_step_rate) { uint_fast8_t idx = N_AXIS; do { idx--; #if N_AXIS > 3 if(bit_isfalse(settings.steppers.is_rotary.mask, bit(idx)) && #else if( #endif (settings.axis[idx].max_rate * settings.axis[idx].steps_per_mm) / 60.0f > (float)hal.max_step_rate) settings.axis[idx].max_rate = (float)hal.max_step_rate * 60.0f / settings.axis[idx].steps_per_mm; // TODO: warn if changed? } while(idx); } if(settings.tool_change.mode > ToolChange_Ignore) { settings.tool_change.mode = ToolChange_SemiAutomatic; settings.flags.tool_change_fast_pulloff = On; } settings.probe.probe2_auto_select &= hal.driver_cap.probe2 && hal.probe.select; settings.probe.toolsetter_auto_select &= hal.driver_cap.toolsetter && hal.probe.select; if(SLEEP_DURATION <= 0.0f) settings.flags.sleep_enable = Off; #if COMPATIBILITY_LEVEL > 1 && DEFAULT_DISABLE_G92_PERSISTENCE settings.flags.g92_is_volatile = On; #endif #if COMPATIBILITY_LEVEL > 2 if(settings.steppers.enable_invert.mask) settings.steppers.enable_invert.mask = AXES_BITMASK; #endif #if N_AXIS > 3 settings.steppers.rotary_wrap.mask &= settings.steppers.is_rotary.mask; #endif settings.control_invert.mask |= limits_override.mask; settings.control_disable_pullup.mask &= ~limits_override.mask; } // Read global settings from persistent storage. // Checks version-byte of non-volatile storage and global settings copy. bool read_global_settings (void) { bool ok = hal.nvs.type != NVS_None && SETTINGS_VERSION == hal.nvs.get_byte(0) && hal.nvs.memcpy_from_nvs((uint8_t *)&settings, NVS_ADDR_GLOBAL, sizeof(settings_t), true) == NVS_TransferResult_OK; sanity_check(); return ok && settings.version.id == SETTINGS_VERSION; } // Write global settings to persistent storage void settings_write_global (void) { if(override_backup.valid) restore_override_backup(); settings.flags.compatibility_level = COMPATIBILITY_LEVEL; if(hal.nvs.type != NVS_None) hal.nvs.memcpy_to_nvs(NVS_ADDR_GLOBAL, (uint8_t *)&settings, sizeof(settings_t), true); } #if N_SPINDLE > 1 static bool get_default_spindle (spindle_info_t *spindle, void *data) { bool ok; if((ok = spindle->ref_id == (uint8_t)((uint32_t)data))) settings.spindle.ref_id = spindle->ref_id; return ok; } #endif // Restore global settings to defaults and write to persistent storage void settings_restore (settings_restore_t restore) { uint_fast8_t idx; stored_line_t empty_line; memset(empty_line, 0xFF, sizeof(stored_line_t)); *empty_line = '\0'; hal.nvs.put_byte(0, SETTINGS_VERSION); // Forces write to physical storage if(restore.defaults) { memcpy(&settings, &defaults, sizeof(settings_t)); settings.control_invert.mask = (settings.control_invert.mask & hal.signals_cap.mask) | limits_override.mask; settings.control_disable_pullup.mask &= (hal.signals_cap.mask & ~limits_override.mask); settings.pwm_spindle.invert.ccw &= spindle_get_caps(false).direction; settings.pwm_spindle.invert.pwm &= spindle_get_caps(false).pwm_invert; #if ENABLE_BACKLASH_COMPENSATION if(sys.driver_started) mc_backlash_init((axes_signals_t){AXES_BITMASK}); #endif #if N_SPINDLE > 1 spindle_enumerate_spindles(get_default_spindle, (void *)DEFAULT_SPINDLE); #endif settings_write_global(); } if(restore.parameters) { float coord_data[N_AXIS]; memset(coord_data, 0, sizeof(coord_data)); #if COMPATIBILITY_LEVEL <= 1 for(idx = 0; idx <= N_WorkCoordinateSystems; idx++) { if(idx < CoordinateSystem_G59_1 || idx > CoordinateSystem_G59_3 || bit_isfalse(settings.offset_lock.mask, bit(idx - CoordinateSystem_G59_1))) settings_write_coord_data((coord_system_id_t)idx, &coord_data); } #endif settings_write_coord_data(CoordinateSystem_G92, &coord_data); // Clear G92 offsets #if N_TOOLS settings_clear_tool_data(); #endif } if(restore.startup_lines) { for (idx = 0; idx < N_STARTUP_LINE; idx++) settings_write_startup_line(idx, empty_line); } if(restore.build_info) { settings_write_build_info(empty_line); settings_write_build_info(BUILD_INFO); } if(restore.defaults && hal.settings_changed) hal.settings_changed(&settings, (settings_changed_flags_t){-1}); setting_details_t *details = setting_details.next; if(details) do { if(details->is_core ? restore.defaults : restore.driver_parameters) { if(details->restore) details->restore(); if(details->on_changed) details->on_changed(&settings, details->is_core ? (settings_changed_flags_t){-1} : (settings_changed_flags_t){0}); } } while((details = details->next)); nvs_buffer_sync_physical(); } inline static bool is_available (const setting_detail_t *setting, uint_fast16_t offset) { return setting->is_available == NULL || setting->is_available(setting, offset); } bool settings_is_group_available (setting_group_t id) { const setting_group_detail_t *group = setting_get_group_details(id); if(!group) return false; bool available = group->is_available ? group->is_available(group) : false; if(!available) switch(group->id) { case Group_Probing: available = hal.probe.get_state != NULL; break; case Group_Spindle_Sync: available = hal.driver_cap.spindle_sync; break; case Group_Spindle_ClosedLoop: available = hal.driver_cap.spindle_pid; break; case Group_Limits_DualAxis: available = hal.stepper.get_ganged && hal.stepper.get_ganged(true).mask != 0; break; case Group_Homing: case Group_Jogging: case Group_Limits: case Group_ControlSignals: case Group_Spindle: available = true; break; default: { uint_fast16_t idx; setting_details_t *details = &setting_details; do { if(details->settings) { for(idx = 0; idx < details->n_settings; idx++) { if(details->settings[idx].group == id && (available = is_available(&details->settings[idx], 0))) break; } } } while(!available && (details = details->next)); } break; } return available; } setting_group_t settings_normalize_group (setting_group_t group) { return (group > Group_Axis0 && group < Group_Axis0 + N_AXIS) ? Group_Axis0 : group; } bool settings_iterator (const setting_detail_t *setting, setting_output_ptr callback, void *data) { bool ok = false; if(setting->group == Group_Axis0) { uint_fast8_t axis_idx = 0; for(axis_idx = 0; axis_idx < N_AXIS; axis_idx++) { if(setting->is_available == NULL || setting->is_available(setting, axis_idx)) { if(grbl.on_set_axis_setting_unit) set_axis_unit(setting, grbl.on_set_axis_setting_unit(setting->id, axis_idx)); if(!(ok = callback(setting, axis_idx, data))) break; } } } else if(setting->flags.increment) { setting_details_t *set; setting = setting_get_details(setting->id, &set); if(set->iterator) ok = set->iterator(setting, callback, data); } else ok = callback(setting, 0, data); return ok; } static inline const setting_detail_t *_setting_get_details (setting_id_t id, uint_fast16_t offset, setting_details_t **set) { uint_fast16_t idx; setting_details_t *details = settings_get_details(); id -= offset; do { for(idx = 0; idx < details->n_settings; idx++) { if(details->settings[idx].id == id && is_available(&details->settings[idx], offset)) { if(details->settings[idx].group == Group_Axis0 && grbl.on_set_axis_setting_unit) set_axis_unit(&details->settings[idx], grbl.on_set_axis_setting_unit(details->settings[idx].id, offset)); if(offset && details->iterator == NULL && offset >= (details->settings[idx].group == Group_Encoder0 ? hal.encoder.get_n_encoders() : N_AXIS)) return NULL; if(set) *set = details; return &details->settings[idx]; } } } while((details = details->next)); return NULL; } const setting_detail_t *setting_get_details (setting_id_t id, setting_details_t **set) { const setting_detail_t *detail; if((detail = _setting_get_details(id, id - normalize_id(id), set)) == NULL) { uint_fast16_t idx, offset; setting_details_t *details = settings_get_details(); do { if(details->normalize && (offset = id - details->normalize(id))) { id -= offset; for(idx = 0; idx < details->n_settings; idx++) { if(details->settings[idx].id == id && is_available(&details->settings[idx], offset)) { detail = &details->settings[idx]; if(set) *set = details; } } break; } } while((details = details->next)); } return detail; } const char *setting_get_description (setting_id_t id) { const char *description = NULL; if(grbl.on_setting_get_description == NULL || (description = grbl.on_setting_get_description(id)) == NULL) { uint_fast16_t idx; setting_details_t *settings = settings_get_details(); const setting_detail_t *setting = setting_get_details(id, NULL); if(setting) do { if(settings->descriptions) { idx = settings->n_descriptions; do { if(settings->descriptions[--idx].id == setting->id) { #if N_AXIS > 3 if(setting->id == Setting_AxisStepsPerMM && axis_is_rotary(id - setting->id)) idx++; #endif description = settings->descriptions[idx].description; } } while(idx && description == NULL); } } while(description == NULL && (settings = settings->next)); } return description; } const setting_group_detail_t *setting_get_group_details (setting_group_t id) { uint_fast16_t idx; setting_details_t *details = settings_get_details(); const setting_group_detail_t *detail = NULL; do { for(idx = 0; idx < details->n_groups; idx++) { if(details->groups[idx].id == id) detail = &details->groups[idx]; } } while(detail == NULL && (details = details->next)); return detail; } /* setting_group_t setting_get_parent_group (setting_group_t id) { const setting_group_detail_t *group = setting_get_group_details(id); return group ? group->parent : Group_Unknown; } */ static status_code_t validate_value (const setting_detail_t *setting, float value) { float val; uint_fast8_t set_idx = 0; if(setting->min_value) { if(!read_float((char *)setting->min_value, &set_idx, &val)) return Status_BadNumberFormat; if(!(value >= val || (setting->flags.allow_null && value == 0.0f))) return Status_SettingValueOutOfRange; } else if(value < 0.0f) return Status_NegativeValue; if(setting->max_value) { set_idx = 0; if(!read_float((char *)setting->max_value, &set_idx, &val)) return Status_BadNumberFormat; if(value > val) return Status_SettingValueOutOfRange; } return Status_OK; } static status_code_t validate_uint_value (const setting_detail_t *setting, uint32_t value) { uint32_t val; uint_fast8_t set_idx = 0; status_code_t status; if(setting->min_value) { if((status = read_uint((char *)setting->min_value, &set_idx, &val)) != Status_OK) return status; if(!(value >= val || (setting->flags.allow_null && value == 0))) return Status_SettingValueOutOfRange; } else if(value < 0.0f) return Status_NegativeValue; if(setting->max_value) { set_idx = 0; if((status = read_uint((char *)setting->max_value, &set_idx, &val)) != Status_OK) return Status_BadNumberFormat; if(value > val) return Status_SettingValueOutOfRange; } return Status_OK; } static uint32_t strnumentries (const char *s, const char delimiter) { if(s == NULL || *s == '\0') return 0; char *p = (char *)s; uint32_t entries = 1; while((p = strchr(p, delimiter))) { p++; entries++; } return entries; } setting_datatype_t setting_datatype_to_external (setting_datatype_t datatype) { switch(datatype) { case Format_Int8: case Format_Int16: datatype = Format_Integer; break; default: break; } return datatype; } bool setting_is_list (const setting_detail_t *setting) { return setting->datatype == Format_Bitfield || setting->datatype == Format_XBitfield || setting->datatype == Format_RadioButtons; } bool setting_is_integer (const setting_detail_t *setting) { return setting->datatype == Format_Integer || setting->datatype == Format_Int8 || setting->datatype == Format_Int16; } static char *remove_element (char *s, uint_fast8_t entry) { while(entry && *s) { if(*s == ',') entry--; s++; } if(entry == 0) { *s++ = 'N'; *s++ = '/'; *s++ = 'A'; char *s2 = s; while(*s2 && *s2 != ',') s2++; while(*s2) *s++ = *s2++; *s = '\0'; } return s; } static void setting_remove_element (setting_id_t id, uint_fast8_t pos) { const setting_detail_t *setting = setting_get_details(id, NULL); if(setting && setting_is_list(setting)) remove_element((char *)setting->format, pos); } // Flag setting elements for bitfields as N/A according to a mask // Note: setting format string has to reside in RAM. void setting_remove_elements (setting_id_t id, uint32_t mask) { const setting_detail_t *setting; if((setting = setting_get_details(id, NULL))) { char *format = (char *)setting->format, *s; uint_fast8_t idx, entries = strnumentries(format, ','); for(idx = 0; idx < entries; idx++ ) { if(!(mask & 0x1)) setting_remove_element(id, idx); mask >>= 1; } // Strip trailing N/A's while((s = strrchr(format, ','))) { if(strncmp(s, ",N/A", 4)) break; *s = '\0'; } } } inline static bool setting_is_string (setting_datatype_t datatype) { return datatype == Format_String || datatype == Format_Password || datatype == Format_IPv4; } inline static bool setting_is_core (setting_type_t type) { return !(type == Setting_NonCore || type == Setting_NonCoreFn); } static status_code_t setting_validate_me_uint (const setting_detail_t *setting, char *svalue) { uint_fast8_t idx = 0; uint32_t value; status_code_t status; if((status = read_uint(svalue, &idx, &value)) != Status_OK) return status; switch(setting->datatype) { case Format_Bool: if(!(value == 0 || value == 1)) status = Status_SettingValueOutOfRange; break; case Format_Bitfield: case Format_XBitfield:; if(value >= (1UL << strnumentries(setting->format, ','))) status = Status_SettingValueOutOfRange; break; case Format_RadioButtons: if(value >= strnumentries(setting->format, ',')) status = Status_SettingValueOutOfRange; break; case Format_AxisMask: if(value >= (1 << N_AXIS)) status = Status_SettingValueOutOfRange; break; case Format_Int8: case Format_Int16: case Format_Integer: status = validate_uint_value(setting, value); break; default: break; } return status; } status_code_t setting_validate_me (const setting_detail_t *setting, float value, char *svalue) { status_code_t status = Status_OK; switch(setting->datatype) { case Format_Bool: case Format_Bitfield: case Format_XBitfield:; case Format_RadioButtons: case Format_AxisMask: case Format_Int8: case Format_Int16: case Format_Integer: status = setting_validate_me_uint(setting, svalue); break; case Format_Decimal: status = validate_value(setting, value); break; case Format_Password: { uint_fast16_t len = strlen(svalue); if(hal.stream.state.webui_connected && len == strlen(PASSWORD_MASK) && !strcmp(PASSWORD_MASK, svalue)) status = Status_InvalidStatement; else status = validate_value(setting, (float)len); } break; case Format_String: { uint_fast16_t len = strlen(svalue); status = validate_value(setting, (float)len); } break; case Format_IPv4: // handled by driver or plugin, dependent on network library break; } return status; } status_code_t setting_validate (setting_id_t id, float value, char *svalue) { const setting_detail_t *setting = setting_get_details(id, NULL); // If no details available setting could nevertheless be a valid setting id. return setting == NULL ? Status_OK : setting_validate_me(setting, value, svalue); } static bool settings_changed_spindle (void) { static spindle_settings_t spindle_settings = {}; static spindle_pwm_settings_t spindle_pwm_settings = {}; bool base_changed, pwm_changed; if((base_changed = memcmp(&spindle_settings, &settings.spindle, sizeof(spindle_settings_t))) != 0) memcpy(&spindle_settings, &settings.spindle, sizeof(spindle_settings_t)); if((pwm_changed = memcmp(&spindle_pwm_settings, &settings.pwm_spindle, sizeof(spindle_pwm_settings_t))) != 0) memcpy(&spindle_pwm_settings, &settings.pwm_spindle, sizeof(spindle_pwm_settings_t)); return base_changed || pwm_changed; } // A helper method to set settings from command line status_code_t settings_store_setting (setting_id_t id, char *svalue) { uint_fast8_t set_idx = 0; uint32_t int_value = 0; float value = NAN; status_code_t status = Status_OK; setting_details_t *set; const setting_detail_t *setting = setting_get_details(id, &set); if(setting == NULL) { if(id == Setting_SpindlePWMBehaviour) { set = &setting_details; setting = &setting_detail[Setting_SpindlePWMBehaviour]; } else return Status_SettingDisabled; } // Trim leading spaces while(*svalue == ' ') svalue++; if(setting->datatype == Format_Decimal) { if(!read_float(svalue, &set_idx, &value) && setting_is_core(setting->type)) return Status_BadNumberFormat; } else if(!setting_is_string(setting->datatype) && read_uint(svalue, &set_idx, &int_value) != Status_OK && setting_is_core(setting->type)) return Status_BadNumberFormat; if((status = setting_validate_me(setting, value, svalue)) != Status_OK) { if(setting == Setting_PulseMicroseconds && status == Status_SettingValueOutOfRange) status = Status_SettingStepPulseMin; return status; } switch(setting->type) { case Setting_NonCore: case Setting_IsExtended: case Setting_IsLegacy: case Setting_IsExpanded: switch(setting->datatype) { case Format_Decimal: *((float *)(setting->value)) = value; break; case Format_String: case Format_Password: strcpy(((char *)(setting->value)), svalue); break; case Format_AxisMask: *((uint8_t *)(setting->value)) = (uint8_t)int_value & AXES_BITMASK; break; case Format_Bool: case Format_Bitfield: case Format_XBitfield: case Format_RadioButtons: case Format_Int8: *((uint8_t *)(setting->value)) = (uint8_t)int_value; break; case Format_Int16: *((uint16_t *)(setting->value)) = (uint16_t)int_value; break; case Format_Integer: *((uint32_t *)(setting->value)) = (uint32_t)int_value; break; default: status = Status_BadNumberFormat; break; } break; case Setting_NonCoreFn: case Setting_IsExtendedFn: case Setting_IsLegacyFn: case Setting_IsExpandedFn: switch(setting->datatype) { case Format_Decimal: status = ((setting_set_float_ptr)(setting->value))(id, value); break; case Format_String: case Format_Password: case Format_IPv4: status = ((setting_set_string_ptr)(setting->value))(id, svalue); break; default: status = ((setting_set_int_ptr)(setting->value))(id, (uint_fast16_t)int_value); break; } break; } if(status == Status_OK) { xbar_set_homing_source(); if(set->save) set->save(); if(set == &setting_details) set->on_changed = hal.settings_changed; if(set->on_changed) { settings_changed_flags_t changed = {0}; changed.spindle = settings_changed_spindle() || machine_mode_changed; machine_mode_changed = false; set->on_changed(&settings, changed); } } return status; } bool settings_add_spindle_type (const char *type) { bool ok; if((ok = strlen(spindle_types) + strlen(type) + 1 < sizeof(spindle_types))) { if(*spindle_types != '\0') strcat(spindle_types, ","); strcat(spindle_types, type); } return ok; } void onFileDemarcate (bool start) { if(!start) { sanity_check(); if(sys.flags.travel_changed && limits_homing_required()) { sys.flags.travel_changed = Off; system_raise_alarm(Alarm_HomingRequired); grbl.report.feedback_message(Message_HomingCycleRequired); } } if(on_file_demarcate) on_file_demarcate(start); } // Clear settings chain void settings_clear (void) { setting_details.next = NULL; settingsd = &setting_details; } // Initialize the config subsystem void settings_init (void) { settings_changed_flags_t changed = {0}; #if N_AXIS > 3 if(grbl.on_set_axis_setting_unit == NULL) grbl.on_set_axis_setting_unit = set_axis_setting_unit; #endif if(grbl.tool_table.n_tools == 0) { grbl.tool_table.n_tools = N_TOOLS; grbl.tool_table.get_tool = settings_get_tool_data; grbl.tool_table.get_tool_by_idx = (get_tool_by_idx_ptr)settings_get_tool_data; grbl.tool_table.set_tool = settings_set_tool_data; grbl.tool_table.clear = settings_clear_tool_data; } #if N_TOOLS memset(tool_data, 0, sizeof(tool_data_t)); // First entry is for tools not in tool table #endif strcpy(step_us_min, ftoa(hal.step_us_min, 1)); if(!read_global_settings()) { settings_restore_t settings = settings_all; settings.defaults = 1; // Ensure global settings get restored if(hal.nvs.type != NVS_None) grbl.report.status_message(Status_SettingReadFail); settings_restore(settings); // Force restore all non-volatile storage data. report_init(); #if COMPATIBILITY_LEVEL <= 1 report_grbl_settings(true, NULL); #else report_grbl_settings(false, NULL); #endif changed.spindle = settings_changed_spindle(); } else { report_init(); changed.spindle = settings_changed_spindle(); hal.settings_changed(&settings, changed); if(hal.probe.configure) // Initialize probe invert mask. hal.probe.configure(false, false); } #if SPINDLE_SYNC_ENABLE st_spindle_sync_cfg(&settings, changed); #endif xbar_set_homing_source(); tmp_set_soft_limits(); tmp_set_hard_limits(); homing_pulloff_init(settings.homing.pulloff); if(spindle_get_count() == 0) spindle_add_null(); spindle_cap_t spindle_cap = spindle_get_caps(false); if(spindle_cap.gpio_controlled) { spindle_state_t spindle_state = { .on = On }; spindle_state.ccw = spindle_cap.direction; spindle_state.pwm = spindle_cap.pwm_invert; setting_remove_elements(Setting_SpindleInvertMask, spindle_state.mask); } setting_remove_elements(Setting_ControlInvertMask, hal.signals_cap.mask & ~limits_override.mask); if(hal.stepper.get_ganged) setting_remove_elements(Setting_GangedDirInvertMask, hal.stepper.get_ganged(false).mask); setting_remove_elements(Setting_CoolantInvertMask, hal.coolant_cap.mask); #if COMPATIBILITY_LEVEL <= 1 if(hal.homing.get_state == NULL) { homing_settings_flags_t homing; homing.value = (uint16_t)-1; homing.use_limit_switches = Off; setting_remove_elements(Setting_HomingEnable, homing.value); } #endif #if ENABLE_JERK_ACCELERATION uint_fast8_t idx = N_AXIS; do { if(settings.axis[--idx].jerk == 0.0f) settings.axis[idx].jerk = settings.axis[idx].acceleration * 60.0f * 10.0f; } while(idx); #endif setting_details_t *details = setting_details.next; if(details) do { if(details->load) details->load(); if(details->on_changed) details->on_changed(&settings, changed); } while((details = details->next)); uint32_t mask = 0b001 | (hal.driver_cap.toolsetter << 1) | (hal.driver_cap.probe2 << 2); setting_remove_elements(Setting_InvertProbePin, mask); setting_remove_elements(Setting_ProbePullUpDisable, mask); #ifndef NO_SAFETY_DOOR_SUPPORT setting_remove_elements(Setting_DoorOptions, ((!settings.parking.flags.enabled || hal.signals_cap.safety_door_ajar) << 1) | hal.signals_cap.safety_door_ajar); #endif mask = 0b00011 | (hal.probe.select ? ((hal.driver_cap.toolsetter << 3) | (hal.driver_cap.probe2 << 4)) : 0); #if 0 if(hal.probe.get_caps) { if(hal.probe.get_caps(Probe_Default).watchable) mask |= (1 << 5); } #else settings.probe.enable_protection = Off; #endif setting_remove_elements(Setting_ProbingFlags, mask); if(!settings.flags.settings_downgrade && settings.version.build != (GRBL_BUILD - 20000000UL)) { if(settings.version.build <= 250102) { // settings.spindle.on_delay = settings.safety_door.spindle_on_delay * 1000.0f; settings.coolant.on_delay = settings.safety_door.coolant_on_delay * 1000.0f; if((changed.spindle = settings.spindle.at_speed_tolerance != settings.pwm_spindle.at_speed_tolerance)) { settings.spindle.at_speed_tolerance = settings.pwm_spindle.at_speed_tolerance; hal.settings_changed(&settings, changed); } } settings.version.build = (GRBL_BUILD - 20000000UL); settings_write_global(); } setting_details.on_changed = hal.settings_changed; on_file_demarcate = grbl.on_file_demarcate; grbl.on_file_demarcate = onFileDemarcate; }