From 2eb7179b9d1b7d96e564b8a607eac14c59302b52 Mon Sep 17 00:00:00 2001 From: Terje Io Date: Sat, 7 Feb 2026 09:46:00 +0100 Subject: [PATCH] Changed spindle at speed check to fix issue with it sometimes skipping the wait cycle. Added experimental support for ramping PWM output on RPM changes. See the changelog for details. --- README.md | 4 +- changelog.md | 17 ++++ config.h | 3 + core_handlers.h | 2 + gcode.c | 4 +- grbl.h | 2 +- grbllib.c | 2 +- protocol.c | 6 +- report.c | 25 +++--- settings.c | 26 +++--- spindle_control.c | 220 ++++++++++++++++++++++++++++++++++++++-------- spindle_control.h | 9 +- state_machine.c | 28 +++--- stream.c | 13 +++ system.c | 2 +- tool_change.c | 2 +- 16 files changed, 282 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index f34e89e..acf8648 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## grblHAL ## -Latest build date is 20260205, see the [changelog](changelog.md) for details. +Latest build date is 20260206, see the [changelog](changelog.md) for details. > [!NOTE] > A settings reset will be performed on an update of builds prior to 20241208. Backup and restore of settings is recommended. @@ -89,4 +89,4 @@ G/M-codes not supported by [legacy Grbl](https://github.com/gnea/grbl/wiki) are Some [plugins](https://github.com/grblHAL/plugins) implements additional M-codes. --- -20260205 +20260206 diff --git a/changelog.md b/changelog.md index d6b5607..f4e2958 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,22 @@ ## grblHAL changelog +Build 20260206 + +Core: + +* Changed spindle at speed check to potentially fix issue with it sometimes skipping the wait cycle. Ref. discussion [#198](https://github.com/grblHAL/core/discussions/198). + +* Added experimental support for ramping PWM output on RPM changes, enable with new `$9` option flag and spindle delay settings: +`$394` > 0, enables ramp up for spindle on and on RPM changes while spindle is enabled. +`$339` > 0, enables ramp down for spindle off. +`$392` > 0, enables ramp up on door close and/or restore from parked. + +Plugins: + +* Spindle: updated to ensure `$340` setting is applied when changed instead of requiring a reboot. + +--- + Build 20260205 Core: diff --git a/config.h b/config.h index aadfb69..d31d1a1 100644 --- a/config.h +++ b/config.h @@ -1125,6 +1125,9 @@ Useful for some pre-built electronic boards. #if !defined DEFAULT_PWM_SPINDLE_DISABLE_LASER_MODE || defined __DOXYGEN__ #define DEFAULT_PWM_SPINDLE_DISABLE_LASER_MODE Off #endif +#if !defined DEFAULT_PWM_SPINDLE_ENABLE_RAMP || defined __DOXYGEN__ +#define DEFAULT_PWM_SPINDLE_ENABLE_RAMP Off +#endif ///@} /*! @name $16 - Setting_SpindleInvertMask diff --git a/core_handlers.h b/core_handlers.h index a23c1f9..48840a6 100644 --- a/core_handlers.h +++ b/core_handlers.h @@ -91,6 +91,7 @@ typedef void (*on_override_changed_ptr)(override_changed_t override); typedef void (*on_spindle_programmed_ptr)(spindle_ptrs_t *spindle, spindle_state_t state, float rpm, spindle_rpm_mode_t mode); typedef void (*on_spindle_at_speed_ptr)(spindle_ptrs_t *spindle, spindle_state_t state); typedef void (*on_port_out_ptr)(uint8_t port, io_port_type_t type, float value); +typedef void (*on_gcode_mode_changed_ptr)(void); typedef void (*on_wco_changed_ptr)(void); typedef void (*on_wco_saved_ptr)(coord_system_id_t id, coord_system_data_t *data); typedef void (*on_program_completed_ptr)(program_flow_t program_flow, bool check_mode); @@ -228,6 +229,7 @@ typedef struct { on_spindle_programmed_ptr on_spindle_programmed; on_spindle_at_speed_ptr on_spindle_at_speed; on_port_out_ptr on_port_out; //!< Might be called from interrupt context, only for unclaimed ports. + on_gcode_mode_changed_ptr on_gcode_mode_changed; //!< Called if settings.status_report.parser_state is enabled. on_wco_changed_ptr on_wco_changed; on_wco_saved_ptr on_wco_saved; on_program_completed_ptr on_program_completed; diff --git a/gcode.c b/gcode.c index ac98391..6601d58 100644 --- a/gcode.c +++ b/gcode.c @@ -836,7 +836,7 @@ void gc_spindle_off (void) memset(&gc_state.modal.spindle[idx], 0, offsetof(spindle_t, hal)); } - spindle_all_off(); + spindle_all_off(false); report_add_realtime(Report_Spindle); } @@ -4506,7 +4506,7 @@ status_code_t gc_execute_block (char *block) system_flag_wco_change(); // Set to refresh immediately just in case something altered. - spindle_all_off(); + spindle_all_off(false); hal.coolant.set_state(gc_state.modal.coolant); report_add_realtime(Report_Spindle); // Set to report change report_add_realtime(Report_Coolant); // immediately. diff --git a/grbl.h b/grbl.h index 61745d9..6a8cdd4 100644 --- a/grbl.h +++ b/grbl.h @@ -42,7 +42,7 @@ #else #define GRBL_VERSION "1.1f" #endif -#define GRBL_BUILD 20260205 +#define GRBL_BUILD 20260206 #define GRBL_URL "https://github.com/grblHAL" diff --git a/grbllib.c b/grbllib.c index cbf994a..55cf06d 100644 --- a/grbllib.c +++ b/grbllib.c @@ -424,7 +424,7 @@ int grbl_enter (void) hal.stepper.enable(settings.steppers.energize, true); - spindle_all_off(); + spindle_all_off(true); hal.coolant.set_state((coolant_state_t){0}); if(hal.get_position) diff --git a/protocol.c b/protocol.c index 37c2831..d472d0f 100644 --- a/protocol.c +++ b/protocol.c @@ -180,7 +180,7 @@ bool protocol_main_loop (void) // Ensure spindle and coolant is switched off on a cold start if(sys.cold_start) { - spindle_all_off(); + spindle_all_off(true); hal.coolant.set_state((coolant_state_t){0}); sys.cold_start = false; system_set_exec_state_flag(EXEC_RT_COMMAND); // execute any startup up tasks @@ -449,7 +449,7 @@ bool protocol_exec_rt_system (void) if((sys.reset_pending = bit_istrue(sys.rt_exec_state, EXEC_RESET))) { // Kill spindle and coolant. killed = true; - spindle_all_off(); + spindle_all_off(true); hal.coolant.set_state((coolant_state_t){0}); } @@ -519,7 +519,7 @@ bool protocol_exec_rt_system (void) if(!killed) { // Kill spindle and coolant. - spindle_all_off(); + spindle_all_off(true); hal.coolant.set_state((coolant_state_t){0}); } diff --git a/report.c b/report.c index 08c4e47..140cdd1 100644 --- a/report.c +++ b/report.c @@ -1187,6 +1187,7 @@ void report_realtime_status (stream_write_ptr stream_write, status_report_tracki static bool probing = false; uint_fast8_t idx; + bool gcode_mode_changed = false; float print_position[N_AXIS], wco[N_AXIS], dist_remaining[N_AXIS]; report_tracking_flags_t delayed_report = {0}; probe_state_t probe_state = { @@ -1550,32 +1551,30 @@ void report_realtime_status (stream_write_ptr stream_write, status_report_tracki #endif spindle_t *spindle = gc_spindle_get(0); - bool is_changed = feed_rate != gc_state.feed_rate || - spindle_rpm != spindle->rpm || - tool_id != gc_state.tool->tool_id + + gcode_mode_changed = feed_rate != gc_state.feed_rate || + spindle_rpm != spindle->rpm || + tool_id != gc_state.tool->tool_id #if NGC_PARAMETERS_ENABLE - || g66_active != !!gc_state.g66_args; + || g66_active != !!gc_state.g66_args; #else ; #endif - if(is_changed) { + if(gcode_mode_changed) { feed_rate = gc_state.feed_rate; tool_id = gc_state.tool->tool_id; spindle_rpm = spindle->rpm; #if NGC_PARAMETERS_ENABLE g66_active = !!gc_state.g66_args; #endif - } else if((is_changed = g92_active != is_g92_active())) + } else if((gcode_mode_changed = g92_active != is_g92_active())) g92_active = !g92_active; else if(memcmp(&last_state, &gc_state.modal, sizeof(gc_modal_t))) { last_state = gc_state.modal; - is_changed = true; + gcode_mode_changed = true; } - if (is_changed) - system_set_exec_state_flag(EXEC_GCODE_REPORT); - if(report->flags.tool_offset) system_set_exec_state_flag(EXEC_TLO_REPORT); } @@ -1588,6 +1587,12 @@ void report_realtime_status (stream_write_ptr stream_write, status_report_tracki report_add_realtime(Report_WCO); // Set to report on next request } else report->flags.value = delayed_report.value; + + if(gcode_mode_changed) { + system_set_exec_state_flag(EXEC_GCODE_REPORT); + if(grbl.on_gcode_mode_changed) + grbl.on_gcode_mode_changed(); + } } static void report_bitfield (const char *format, bool bitmap) diff --git a/settings.c b/settings.c index 507b4ce..14aadce 100644 --- a/settings.c +++ b/settings.c @@ -198,6 +198,7 @@ PROGMEM const settings_t defaults = { .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.flags.pwm_ramped = DEFAULT_PWM_SPINDLE_ENABLE_RAMP, .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, @@ -736,15 +737,18 @@ static status_code_t set_pwm_mode (setting_id_t id, uint_fast16_t int_value) static status_code_t set_pwm_options (setting_id_t id, uint_fast16_t int_value) { - if(int_value & 0x001) { - if(int_value > 0b111) + if(int_value & 0b0001) { + if(int_value > 0b1111) 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); + settings.pwm_spindle.flags.enable_rpm_controlled = !!(int_value & 0b0010); + settings.pwm_spindle.flags.laser_mode_disable = !!(int_value & 0b0100); + settings.pwm_spindle.flags.pwm_ramped = !!(int_value & 0b1000); } else { settings.pwm_spindle.flags.pwm_disable = On; - settings.pwm_spindle.flags.enable_rpm_controlled = settings.pwm_spindle.flags.laser_mode_disable = Off; + settings.pwm_spindle.flags.enable_rpm_controlled = + settings.pwm_spindle.flags.laser_mode_disable = + settings.pwm_spindle.flags.pwm_ramped = Off; } return Status_OK; @@ -1515,9 +1519,10 @@ FLASHMEM static uint32_t get_int (setting_id_t id) 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)); + : (0b0001 | + (settings.pwm_spindle.flags.enable_rpm_controlled ? 0b0010 : 0) | + (settings.pwm_spindle.flags.laser_mode_disable ? 0b0100 : 0) | + (settings.pwm_spindle.flags.pwm_ramped ? 0b1000 : 0)); break; case Setting_Mode: @@ -2063,7 +2068,7 @@ PROGMEM static const setting_detail_t setting_detail[] = { { 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 }, + { Setting_SpindlePWMOptions, Group_Spindle, "PWM spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability,Enable ramping", 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 @@ -2237,7 +2242,8 @@ PROGMEM static const setting_descr_t setting_descr[] = { { 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." + "When `RPM controls spindle enable signal` is checked and M3 or M4 is active S0 switches it off and S > 0 switches it on.\\n" + "When 'Ramping enable' is checked spin up and spin down time is calculated from $394 and $539 respectively." }, { 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." diff --git a/spindle_control.c b/spindle_control.c index 6745864..5b73fec 100644 --- a/spindle_control.c +++ b/spindle_control.c @@ -117,6 +117,10 @@ static bool spindle_activate (spindle_id_t spindle_id, spindle_num_t spindle_num sys_spindle[spindle_num].param.hal = &sys_spindle[spindle_num].hal; if(sys_spindle[spindle_num].param.override_pct == 0) sys_spindle[spindle_num].param.override_pct = DEFAULT_SPINDLE_RPM_OVERRIDE; + if(spindle_hal.type == SpindleType_PWM && !spindle_hal.cap.laser && spindle_hal.context.pwm->flags.ramp_pwm ) { + sys_spindle[spindle_num].param.ramp_up = settings.spindle.on_delay > 0; + sys_spindle[spindle_num].param.ramp_down = settings.spindle.off_delay > 0; + } spindle_hal.param = &sys_spindle[spindle_num].param; memcpy(&sys_spindle[spindle_num].hal, &spindle_hal, sizeof(spindle_ptrs_t)); if(grbl.on_spindle_selected) @@ -283,10 +287,15 @@ void spindle_update_caps (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_caps) idx--; if(sys_spindle[idx].enabled && spindle->id == sys_spindle[idx].hal.id) { sys_spindle[idx].hal.type = spindle->type; - sys_spindle[idx].hal.cap.laser = spindle->cap.laser; sys_spindle[idx].hal.rpm_min = spindle->rpm_min; sys_spindle[idx].hal.rpm_max = spindle->rpm_max; sys_spindle[idx].hal.pwm_off_value = spindle->pwm_off_value; + if((sys_spindle[idx].hal.cap.laser = spindle->cap.laser) || spindle->type != SpindleType_PWM || !spindle->context.pwm->flags.ramp_pwm) + sys_spindle[idx].param.ramp_up = sys_spindle[idx].param.ramp_down = Off; + else { + sys_spindle[idx].param.ramp_up = settings.spindle.on_delay > 0; + sys_spindle[idx].param.ramp_down = settings.spindle.off_delay > 0; + } break; } } while(idx); @@ -528,6 +537,70 @@ spindle_id_t spindle_add_null (void) // End null (dummy) spindle. +typedef struct { + spindle_ptrs_t *spindle; + uint16_t delay; + uint32_t start, end; + volatile float rpm; + float rpm_delta; + float rpm_target; +} rpm_override_t; + +static void spindle_ramp_task (void *data) +{ + bool ok; + rpm_override_t *ramp = (rpm_override_t *)data; + + ramp->rpm = ramp->rpm + ramp->rpm_delta; + + if(ramp->rpm_delta > 0.0f) { + if((ok = ramp->rpm >= ramp->rpm_target)) + ramp->rpm = ramp->rpm_target; + } else if((ok = ramp->rpm <= ramp->rpm_target)) + ramp->rpm = ramp->rpm_target; + + if(!ok) + task_add_delayed(spindle_ramp_task, data, ramp->delay); + else + ramp->delay = 0; // done + + ramp->end = hal.get_elapsed_ticks(); + + ramp->spindle->update_pwm(ramp->spindle, ramp->spindle->get_pwm(ramp->spindle, ramp->rpm)); +} + +static uint16_t spindle_get_ramp (spindle_ptrs_t *spindle, float rpm, float target_rpm, uint16_t delay_ms, rpm_override_t *ramp) +{ + ramp->spindle = spindle; + ramp->rpm = rpm; + ramp->rpm_target = target_rpm; + ramp->rpm_delta = target_rpm - rpm; + ramp->delay = DWELL_TIME_STEP; + + uint16_t dly = ((uint16_t)(float)delay_ms * fabsf(ramp->rpm_delta / spindle->rpm_max)); + + ramp->rpm_delta /= (float)(dly / ramp->delay) + 1; + + if(ramp->start == 0) + ramp->start = hal.get_elapsed_ticks(); + + return dly; +} + +static bool spindle_ramp_override (spindle_ptrs_t *spindle, float rpm, float target_rpm) +{ + static rpm_override_t ramp; + + task_delete(spindle_ramp_task, &ramp); + + if(ramp.delay) + rpm = ramp.rpm; + + return spindle_get_ramp(spindle, rpm, spindle->param->rpm_overridden, settings.spindle.on_delay, &ramp) && + task_add_delayed(spindle_ramp_task, &ramp, ramp.delay); + +} + /*! \brief Set spindle speed override. \param spindle pointer to a \ref spindle_ptrs_t structure. \param speed_override override as a percentage of the programmed RPM. @@ -542,15 +615,22 @@ float spindle_set_override (spindle_ptrs_t *spindle, override_t speed_override) if((uint8_t)speed_override != spindle->param->override_pct) { + float rpm = spindle->param->rpm_overridden; + sys_state_t state = state_get(); + spindle_set_rpm(spindle, spindle->param->rpm, speed_override); - if(state_get() == STATE_IDLE) { - if(spindle->get_pwm && spindle->update_pwm) - spindle->update_pwm(spindle, spindle->get_pwm(spindle, spindle->param->rpm_overridden)); - else if(spindle->update_rpm) - spindle->update_rpm(spindle, spindle->param->rpm_overridden); - } else - sys.step_control.update_spindle_rpm = On; + if(!(spindle->param->ramp_up && + spindle->get_state(spindle).on && + spindle_ramp_override(spindle, rpm, spindle->param->rpm_overridden))) { + if(state == STATE_IDLE) { + if(spindle->get_pwm && spindle->update_pwm) + spindle->update_pwm(spindle, spindle->get_pwm(spindle, spindle->param->rpm_overridden)); + else if(spindle->update_rpm) + spindle->update_rpm(spindle, spindle->param->rpm_overridden); + } else + sys.step_control.update_spindle_rpm = On; + } report_add_realtime(Report_Overrides); // Set to report change immediately @@ -590,6 +670,45 @@ bool spindle_check_state (spindle_ptrs_t *spindle, spindle_state_t state) return (state.value & mask.value) == (spindle->get_state(spindle).value & mask.value); } +static void spindle_ramp (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, float target_rpm, uint16_t delay_ms) +{ + bool ok = true; + + if(delay_ms > DWELL_TIME_STEP) { + + static rpm_override_t ramp; + + task_delete(spindle_ramp_task, NULL); + + if(spindle_get_ramp(spindle, rpm, target_rpm, delay_ms, &ramp) > DWELL_TIME_STEP) { + + float delay = (float)ramp.delay / 1000.0f; + + if(ramp.rpm_delta > 0.0f) { + if(spindle->param->state.on != state.on || state.ccw != spindle->param->state.ccw) { + spindle->param->state.on = state.on; + spindle->set_state(spindle, state, (rpm = rpm + ramp.rpm_delta)); + } + while(ok && (rpm = rpm + ramp.rpm_delta) < target_rpm) { + spindle->update_pwm(spindle, spindle->get_pwm(spindle, rpm)); + ok = delay_sec(delay, sys.suspend ? DelayMode_SysSuspend : DelayMode_Dwell); + } + } else while(ok && (rpm = rpm + ramp.rpm_delta) > target_rpm) { + spindle->update_pwm(spindle, spindle->get_pwm(spindle, rpm)); + ok = delay_sec(delay, sys.suspend ? DelayMode_SysSuspend : DelayMode_Dwell); + } + ramp.end = hal.get_elapsed_ticks(); + } + } + + if(ok && rpm != target_rpm) { + if(state.on == spindle->param->state.on) + spindle->update_pwm(spindle, spindle->get_pwm(spindle, target_rpm)); + else + spindle->set_state(spindle, state, target_rpm); + } +} + /*! \internal \brief Immediately sets spindle running state with direction and spindle rpm, if enabled. Called by g-code parser spindle_set_state_synced(), parking retract and restore, g-code program end, sleep, and spindle stop override. @@ -598,20 +717,30 @@ sleep, and spindle stop override. \param rpm the spindle RPM to set. \returns \a true if successful, \a false if the current controller state is \ref ABORTED. */ -bool spindle_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm) +static bool _spindle_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, uint16_t on_delay_ms) { - if (!ABORTED) { // Block during abort. + if(!ABORTED) { // Block during abort. - if (!state.on) { // Halt or set spindle direction and rpm. + if(!state.on) { // Halt or set spindle direction and rpm. rpm = 0.0f; - spindle->set_state(spindle, (spindle_state_t){0}, 0.0f); + if(spindle->param->ramp_down) + spindle_ramp(spindle, spindle->param->state, spindle->param->rpm_overridden, 0.0f, settings.spindle.off_delay); + spindle->set_state(spindle, (spindle_state_t){0}, (spindle->param->rpm_overridden = 0.0f)); } else { // NOTE: Assumes all calls to this function is when grblHAL is not moving or must remain off. // TODO: alarm/interlock if going from CW to CCW directly in non-laser mode? - if (spindle->cap.laser && state.ccw) + if(spindle->cap.laser && state.ccw) rpm = 0.0f; // TODO: May need to be rpm_min*(100/MAX_SPINDLE_RPM_OVERRIDE); - spindle->set_state(spindle, state, spindle_set_rpm(spindle, rpm, spindle->param->override_pct)); + if(spindle->param->ramp_up) { + if(spindle->param->state.on && spindle->param->state.ccw != state.ccw) { + spindle_ramp(spindle, spindle->param->state, spindle->param->rpm_overridden, 0.0f, settings.spindle.off_delay); + spindle->param->rpm = spindle->param->rpm_overridden = 0.0f; + } + float arpm = spindle->param->rpm_overridden; + spindle_ramp(spindle, state, arpm, spindle_set_rpm(spindle, rpm, spindle->param->override_pct), on_delay_ms ? on_delay_ms : settings.spindle.on_delay); + } else + spindle->set_state(spindle, state, spindle_set_rpm(spindle, rpm, spindle->param->override_pct)); } spindle->param->rpm = rpm; @@ -626,6 +755,11 @@ bool spindle_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rp return !ABORTED; } +bool spindle_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm) +{ + return _spindle_set_state(spindle, state, rpm, 0); +} + /*! \brief If the spindle supports at speed functionality it will wait for it to reach the speed and raise an alarm if the speed is not reached within the timeout period. \param spindle pointer to a \ref spindle_ptrs_t structure. @@ -633,28 +767,28 @@ for it to reach the speed and raise an alarm if the speed is not reached within \param rpm the spindle RPM to set. \returns \a true if successful, \a false if the current controller state is \ref ABORTED. */ -static bool spindle_set_state_wait (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, uint16_t delay_ms, delaymode_t delay_mode) +static bool spindle_set_state_wait (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, uint16_t delay_ms) { bool ok; if(!(ok = state_get() == STATE_CHECK_MODE)) { - if((ok = spindle_set_state(spindle, state, rpm))) { + if((ok = _spindle_set_state(spindle, state, rpm, delay_ms))) { - if(sys.override.control.spindle_wait_disable) { + if(sys.override.control.spindle_wait_disable || (state.on ? spindle->param->ramp_up : spindle->param->ramp_down)) { sys.override.control.spindle_wait_disable = Off; } else { bool at_speed = !spindle->cap.at_speed || spindle->cap.torch || spindle->at_speed_tolerance <= 0.0f; if(at_speed) - ok = delay_ms == 0 || spindle->cap.torch || delay_sec((float)delay_ms / 1000.0f, delay_mode); + ok = delay_ms == 0 || spindle->cap.torch || delay_sec((float)delay_ms / 1000.0f, sys.suspend ? DelayMode_SysSuspend : DelayMode_Dwell); else { uint16_t delay = 0; if(delay_ms == 0) delay_ms = 60000; // one minute... - while(!(at_speed = spindle->get_state(spindle).at_speed)) { - if(!delay_sec(0.2f, delay_mode)) + do { + if(!delay_sec(0.2f, sys.suspend ? DelayMode_SysSuspend : DelayMode_Dwell)) break; delay += 200; if(delay > delay_ms) { @@ -662,7 +796,8 @@ static bool spindle_set_state_wait (spindle_ptrs_t *spindle, spindle_state_t sta system_raise_alarm(Alarm_Spindle); break; } - } + } while(!(at_speed = spindle->get_state(spindle).at_speed)); + ok &= at_speed; } } @@ -692,7 +827,7 @@ bool spindle_set_state_synced (spindle_ptrs_t *spindle, spindle_state_t state, f if(grbl.on_spindle_programmed) grbl.on_spindle_programmed(spindle, state, rpm, mode); - ok = spindle_set_state_wait(spindle, state, rpm, state.on ? settings.spindle.on_delay : settings.spindle.off_delay, DelayMode_Dwell); + ok = spindle_set_state_wait(spindle, state, rpm, state.on ? settings.spindle.on_delay : settings.spindle.off_delay); } return ok; @@ -709,9 +844,9 @@ bool spindle_restore (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, bool ok; if(spindle->cap.laser) // When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts. - ok = (sys.step_control.update_spindle_rpm = spindle_set_state(spindle, state, 0.0f)); + ok = (sys.step_control.update_spindle_rpm = _spindle_set_state(spindle, state, 0.0f, 0)); else if(!(ok = spindle_check_state(spindle, state) && spindle->param->rpm == rpm)) - ok = spindle_set_state_wait(spindle, state, rpm, delay_ms, DelayMode_SysSuspend); + ok = spindle_set_state_wait(spindle, state, rpm, delay_ms); return ok; } @@ -737,13 +872,17 @@ float spindle_set_rpm (spindle_ptrs_t *spindle, float rpm, override_t override_p /*! \brief Turn off all enabled spindles. */ -void spindle_all_off (void) +void spindle_all_off (bool reset) { spindle_ptrs_t *spindle; uint_fast8_t spindle_num = N_SYS_SPINDLE; do { if((spindle = spindle_get(--spindle_num))) { + + if(!reset && spindle->param->ramp_down) + spindle_set_state(spindle, (spindle_state_t){0}, 0.0f); + spindle->param->rpm = spindle->param->rpm_overridden = 0.0f; spindle->param->state.value = 0; #ifdef GRBL_ESP32 @@ -879,6 +1018,7 @@ bool spindle_precompute_pwm_values (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_ pwm_data->flags.invert_pwm = settings->invert.pwm & spindle->cap.pwm_invert; pwm_data->flags.rpm_controlled = settings->flags.enable_rpm_controlled; pwm_data->flags.laser_mode_disable = settings->flags.laser_mode_disable; + pwm_data->flags.ramp_pwm = settings->flags.pwm_ramped; if(settings->pwm_off_value == 0.0f) pwm_data->off_value = pwm_data->flags.invert_pwm ? pwm_data->period - pwm_data->offset : 0; else @@ -977,15 +1117,18 @@ static status_code_t set_spindle_invert (setting_id_t id, uint_fast16_t int_valu static status_code_t set_pwm_options (setting_id_t id, uint_fast16_t int_value) { - if(int_value & 0x001) { - if(int_value > 0b111) + if(int_value & 0b0001) { + if(int_value > 0b1111) return Status_SettingValueOutOfRange; sp1_settings.cfg.flags.pwm_disable = Off; - sp1_settings.cfg.flags.enable_rpm_controlled = !!(int_value & 0b010); - sp1_settings.cfg.flags.laser_mode_disable = !!(int_value & 0b100); + sp1_settings.cfg.flags.enable_rpm_controlled = !!(int_value & 0b0010); + sp1_settings.cfg.flags.laser_mode_disable = !!(int_value & 0b0100); + sp1_settings.cfg.flags.pwm_ramped = !!(int_value & 0b1000); } else { sp1_settings.cfg.flags.pwm_disable = On; - sp1_settings.cfg.flags.enable_rpm_controlled = sp1_settings.cfg.flags.laser_mode_disable = Off; + sp1_settings.cfg.flags.enable_rpm_controlled = + sp1_settings.cfg.flags.laser_mode_disable = + sp1_settings.cfg.flags.pwm_ramped = Off; } return Status_OK; @@ -1000,9 +1143,10 @@ static uint32_t get_int (setting_id_t id) case Setting_SpindlePWMOptions1: value = sp1_settings.cfg.flags.pwm_disable ? 0 - : (0b001 | - (sp1_settings.cfg.flags.enable_rpm_controlled ? 0b010 : 0) | - (sp1_settings.cfg.flags.laser_mode_disable ? 0b100 : 0)); + : (0b0001 | + (sp1_settings.cfg.flags.enable_rpm_controlled ? 0b0010 : 0) | + (sp1_settings.cfg.flags.laser_mode_disable ? 0b0100 : 0) | + (sp1_settings.cfg.flags.pwm_ramped ? 0b1000 : 0)); break; case Setting_SpindleInvertMask1: @@ -1088,7 +1232,7 @@ static const setting_detail_t spindle1_settings[] = { { Setting_Spindle_DirPort, Group_AuxPorts, "PWM2 spindle direction port", NULL, Format_Decimal, "-#0", "-1", max_dport, Setting_NonCoreFn, set_port, get_port, has_ports, { .reboot_required = On } }, { Setting_SpindleInvertMask1, Group_Spindle, "PWM2 spindle signals invert", NULL, Format_Bitfield, spindle_signals, NULL, NULL, Setting_IsExtendedFn, set_spindle_invert, get_int, NULL, { .reboot_required = On } }, { Setting_Spindle_PWMPort, Group_AuxPorts, "PWM2 spindle PWM port", NULL, Format_Decimal, "-#0", "0", max_aport, Setting_NonCoreFn, set_port, get_port, has_ports, { .reboot_required = On } }, - { Setting_SpindlePWMOptions1, Group_Spindle, "PWM2 spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability", NULL, NULL, Setting_IsExtendedFn, set_pwm_options, get_int, has_pwm }, + { Setting_SpindlePWMOptions1, Group_Spindle, "PWM2 spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability,Enable ramping", NULL, NULL, Setting_IsExtendedFn, set_pwm_options, get_int, has_pwm }, { Setting_RpmMax1, Group_Spindle, "PWM2 spindle max speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &sp1_settings.cfg.rpm_max, NULL, has_pwm }, { Setting_RpmMin1, Group_Spindle, "PWM2 spindle min speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &sp1_settings.cfg.rpm_min, NULL, has_pwm }, { Setting_PWMFreq1, Group_Spindle, "PWM2 spindle PWM frequency", "Hz", Format_Decimal, "#####0", NULL, NULL, Setting_IsExtended, &sp1_settings.cfg.pwm_freq, NULL, has_freq }, @@ -1115,7 +1259,8 @@ static const setting_descr_t spindle1_settings_descr[] = { { Setting_SpindleInvertMask1, "Inverts the spindle on, counterclockwise and PWM signals (active low)." }, { Setting_Spindle_PWMPort, "Spindle analog aux port. Must be PWM capable!" }, { Setting_SpindlePWMOptions1, "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." + "When `RPM controls spindle enable signal` is checked and M3 or M4 is active S0 switches it off and S > 0 switches it on.\\n" + "When 'Ramping enable' is checked spin up and spin down time is calculated from $394 and $539 respectively." }, { Setting_RpmMax1, "Maximum spindle speed." }, { Setting_RpmMin1, "Minimum spindle speed." }, @@ -1165,8 +1310,9 @@ static void spindle1_settings_restore (void) .rpm_max = DEFAULT_SPINDLE1_RPM_MAX, .rpm_min = DEFAULT_SPINDLE1_RPM_MIN, .flags.pwm_disable = false, - .flags.enable_rpm_controlled = 0, //DEFAULT_SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED, TODO: add setting? - .flags.laser_mode_disable = 0, // TODO: add setting? Not possible? + .flags.enable_rpm_controlled = 0, //DEFAULT_SPINDLE_ENABLE_OFF_WITH_ZERO_SPEED, + .flags.pwm_ramped = DEFAULT_PWM_SPINDLE_ENABLE_RAMP, + .flags.laser_mode_disable = 0, // TODO: Not possible? .invert.on = DEFAULT_INVERT_SPINDLE1_ENABLE_PIN, .invert.ccw = DEFAULT_INVERT_SPINDLE1_CCW_PIN, .invert.pwm = DEFAULT_INVERT_SPINDLE1_PWM_PIN, diff --git a/spindle_control.h b/spindle_control.h index f227cf6..74460f8 100644 --- a/spindle_control.h +++ b/spindle_control.h @@ -219,7 +219,8 @@ typedef union { laser_mode_disable :1, // PWM spindle only pwm_disable :1, // PWM spindle only g92offset :1, - unassigned :4; + pwm_ramped :1, // PWM spindle only + unassigned :3; }; } spindle_settings_flags_t; @@ -272,7 +273,7 @@ typedef union { laser_mode_disable :1, laser_off_overdrive :1, enable_out :1, - unused :1; + ramp_pwm :1; }; } spindle_pwm_flags_t; @@ -347,6 +348,8 @@ typedef struct spindle_param { spindle_state_t state; override_t override_pct; //!< Spindle RPM override value in percent spindle_css_data_t css; //!< Data used for Constant Surface Speed Mode (CSS) calculations, NULL if not in CSS mode. + bool ramp_up; + bool ramp_down; spindle_ptrs_t *hal; } spindle_param_t; @@ -389,7 +392,7 @@ float spindle_set_rpm (spindle_ptrs_t *spindle, float rpm, override_t speed_over // Restore spindle running state with direction, enable, spindle RPM and appropriate delay. bool spindle_restore (spindle_ptrs_t *spindle, spindle_state_t state, float rpm, uint16_t on_delay_ms); -void spindle_all_off (void); +void spindle_all_off (bool reset); // // The following functions are not called by the core, may be called by driver code. diff --git a/state_machine.c b/state_machine.c index ef74da3..c9739f2 100644 --- a/state_machine.c +++ b/state_machine.c @@ -111,7 +111,7 @@ static void state_restore_conditions (restore_condition_t *condition) static void enter_sleep (void) { st_go_idle(); - spindle_all_off(); + spindle_all_off(false); hal.coolant.set_state((coolant_state_t){0}); grbl.report.feedback_message(Message_SleepMode); stateHandler = state_noop; @@ -362,26 +362,30 @@ void state_set (sys_state_t new_state) // Suspend manager. Controls spindle overrides in hold states. void state_suspend_manager (void) { - if (stateHandler != state_await_resume || !gc_spindle_get(0)->state.on) + if(stateHandler != state_await_resume || !gc_spindle_get(0)->state.on) return; if(sys.override.spindle_stop.value) { + spindle_t *spindle = &restore_condition.spindle[restore_condition.spindle_num]; + // Handles beginning of spindle stop if(sys.override.spindle_stop.initiate) { sys.override.spindle_stop.value = 0; // Clear stop override state - if(grbl.on_spindle_programmed) - grbl.on_spindle_programmed(restore_condition.spindle[restore_condition.spindle_num].hal, (spindle_state_t){0}, 0.0f, 0); - spindle_set_state(restore_condition.spindle[restore_condition.spindle_num].hal, (spindle_state_t){0}, 0.0f); // De-energize - sys.override.spindle_stop.enabled = On; // Set stop override state to enabled, if de-energized. - if(grbl.on_override_changed) - grbl.on_override_changed(OverrideChanged_SpindleState); + if(spindle->hal && spindle->hal->get_state(spindle->hal).on) { + if(grbl.on_spindle_programmed) + grbl.on_spindle_programmed(spindle->hal, (spindle_state_t){0}, 0.0f, SpindleSpeedMode_RPM); + spindle_set_state(spindle->hal, (spindle_state_t){0}, 0.0f); // De-energize + sys.override.spindle_stop.enabled = On; // Set stop override state to enabled, if de-energized. + if(grbl.on_override_changed) + grbl.on_override_changed(OverrideChanged_SpindleState); + } } // Handles restoring of spindle state if(sys.override.spindle_stop.restore) { grbl.report.feedback_message(Message_SpindleRestore); - state_spindle_restore(&restore_condition.spindle[restore_condition.spindle_num], settings.spindle.on_delay); + state_spindle_restore(spindle, settings.spindle.on_delay); sys.override.spindle_stop.value = 0; // Clear stop override state if(grbl.on_override_changed) grbl.on_override_changed(OverrideChanged_SpindleState); @@ -513,7 +517,7 @@ static void state_await_hold (uint_fast16_t rt_exec) switch (sys_state) { case STATE_TOOL_CHANGE: - spindle_all_off(); // De-energize + spindle_all_off(false); // De-energize hal.coolant.set_state((coolant_state_t){0}); // De-energize break; @@ -575,13 +579,13 @@ static void state_await_hold (uint_fast16_t rt_exec) } else { // Parking motion not possible. Just disable the spindle and coolant. // NOTE: Laser mode does not start a parking motion to ensure the laser stops immediately. - spindle_all_off(); // De-energize + spindle_all_off(false); // De-energize if(sys.flags.is_parking || sys_state == STATE_SLEEP || !settings.safety_door.flags.keep_coolant_on) hal.coolant.set_state((coolant_state_t){0}); // De-energize sys.parking_state = hal.control.get_state().safety_door_ajar ? Parking_DoorAjar : Parking_DoorClosed; } } else { - spindle_all_off(); // De-energize + spindle_all_off(false); // De-energize if(sys.flags.is_parking || sys_state == STATE_SLEEP || !settings.safety_door.flags.keep_coolant_on) hal.coolant.set_state((coolant_state_t){0}); // De-energize sys.parking_state = hal.control.get_state().safety_door_ajar ? Parking_DoorAjar : Parking_DoorClosed; diff --git a/stream.c b/stream.c index b2c337e..c699149 100644 --- a/stream.c +++ b/stream.c @@ -80,6 +80,7 @@ static struct { stream_write_char_ptr write_char; stream_connection_flags_t flags; on_rt_reports_added_ptr on_rt_reports_added; + on_gcode_mode_changed_ptr on_gcode_mode_changed; } mpg; void stream_register_streams (io_stream_details_t *details) @@ -553,6 +554,15 @@ ISR_CODE static void mpg_rt_report_add (report_tracking_flags_t report) mpg.stream.report.flags.value |= report.value; } +static void mpg_gcode_mode_changed (void) +{ + if(mpg.on_gcode_mode_changed) + mpg.on_gcode_mode_changed(); + + if(hal.stream.type != StreamType_MPG) + report_gcode_modes(mpg.stream.write); +} + void stream_mpg_set_mode (void *data) { stream_mpg_enable(data != NULL); @@ -645,6 +655,9 @@ bool stream_mpg_register (const io_stream_t *stream, bool rx_only, stream_write_ mpg.on_rt_reports_added = grbl.on_rt_reports_added; grbl.on_rt_reports_added = mpg_rt_report_add; + mpg.on_gcode_mode_changed = grbl.on_gcode_mode_changed; + grbl.on_gcode_mode_changed = mpg_gcode_mode_changed; + if(grbl.on_mpg_registered) grbl.on_mpg_registered(&mpg.stream, true); } diff --git a/system.c b/system.c index d7dc9c6..f21b996 100644 --- a/system.c +++ b/system.c @@ -100,7 +100,7 @@ ISR_CODE void ISR_FUNC(control_interrupt_handler)(control_signals_t signals) if(state_get() != STATE_IDLE && state_get() != STATE_JOG) system_set_exec_state_flag(EXEC_SAFETY_DOOR); if(settings.mode == Mode_Laser) // Turn off spindle immediately (laser) when in laser mode - spindle_all_off(); + spindle_all_off(true); } else system_set_exec_state_flag(EXEC_SAFETY_DOOR); } diff --git a/tool_change.c b/tool_change.c index b5a87a6..df6ac38 100644 --- a/tool_change.c +++ b/tool_change.c @@ -485,7 +485,7 @@ static status_code_t tool_change (parser_state_t *parser_state) block_cycle_start = settings.tool_change.mode != ToolChange_SemiAutomatic; // Stop spindle and coolant. - spindle_all_off(); + spindle_all_off(false); hal.coolant.set_state((coolant_state_t){0}); execute_posted = false;