diff --git a/README.md b/README.md index 9190f33..2da641e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## grblHAL ## -Latest build date is 20250419, see the [changelog](changelog.md) for details. +Latest build date is 20250424, 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. --- -20250419 +20250424 diff --git a/changelog.md b/changelog.md index f6875da..9fee029 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,26 @@ ## grblHAL changelog +20250424 + +Core: + +* Moved part of the driver based spindle sync code to the core. Spindle sync now has to be enabled in _grbl/config.h_. + +Drivers: + +* iMXRT1062, MSP432P401R, STM32F4xx, STM32F7xx: removed spindle sync code now in the core. + +* RP2040: Added tentative support for spindle sync, board maps has to be updated for spindle encoder inputs - not all can be due to pin restrictions. +Fixed regression causing the Pico CNC board to lose spindle PWM output. + +* LPC176x, ESP32, TM4C123, STM32F1xx: replaced deprecated code. + +Plugins: + +* Some: replaced deprecated code. + +--- + 20250419 Core: diff --git a/config.h b/config.h index c4833e4..98c808d 100644 --- a/config.h +++ b/config.h @@ -523,6 +523,16 @@ Number of tools in tool table, edit to enable (max. 32 allowed) #endif #endif +/*! \def SPINDLE_SYNC_ENABLE +\brief +Set to \ref On or 1 to enable experimental support for spindle synced motion, G33 and G76. + +_NOTE:_ require driver and board support for spindle encoder input. +*/ +#if !defined SPINDLE_SYNC_ENABLE || defined __DOXYGEN__ +#define SPINDLE_SYNC_ENABLE Off +#endif + /*! \def NGC_EXPRESSIONS_ENABLE \brief Set to \ref On or 1 to enable experimental support for expressions. diff --git a/grbl.h b/grbl.h index 1f58bf3..cd29444 100644 --- a/grbl.h +++ b/grbl.h @@ -42,7 +42,7 @@ #else #define GRBL_VERSION "1.1f" #endif -#define GRBL_BUILD 20250419 +#define GRBL_BUILD 20250424 #define GRBL_URL "https://github.com/grblHAL" diff --git a/modbus_rtu.h b/modbus_rtu.h deleted file mode 100644 index d955568..0000000 --- a/modbus_rtu.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - - modbus_rtu.h - a lightweight ModBus RTU implementation - - Part of grblHAL - - Copyright (c) 2020-2025 Terje Io - - 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 . - -*/ - -#ifndef _MODBUS_RTU_H_ -#define _MODBUS_RTU_H_ - -#include "grbl/modbus.h" - -typedef enum { - ModBus_Idle, - ModBus_Silent, - ModBus_TX, - ModBus_AwaitReply, - ModBus_Timeout, - ModBus_GotReply, - ModBus_Exception, - ModBus_Retry -} modbus_state_t; - -typedef void (*stream_set_direction_ptr)(bool tx); - -typedef struct { - set_baud_rate_ptr set_baud_rate; - stream_set_direction_ptr set_direction; // NULL if auto direction - get_stream_buffer_count_ptr get_tx_buffer_count; - get_stream_buffer_count_ptr get_rx_buffer_count; - stream_write_n_ptr write; - stream_read_ptr read; - flush_stream_buffer_ptr flush_tx_buffer; - flush_stream_buffer_ptr flush_rx_buffer; -} modbus_stream_t; - -void modbus_rtu_init (void); -bool modbus_rtu_send (modbus_message_t *msg, const modbus_callbacks_t *callbacks, bool block); - -#endif diff --git a/motion_control.c b/motion_control.c index 2ef64b6..0c7b6d0 100644 --- a/motion_control.c +++ b/motion_control.c @@ -42,18 +42,22 @@ #if ENABLE_BACKLASH_COMPENSATION -static float target_prev[N_AXIS] = {0}; +static coord_data_t target_prev = {}, backlash_comp = {}; static axes_signals_t dir_negative = {0}, backlash_enabled = {0}; void mc_backlash_init (axes_signals_t axes) { uint_fast8_t idx = N_AXIS; + uint_fast8_t steps; do { idx--; if(bit_istrue(axes.mask, bit(idx))) { - BIT_SET(backlash_enabled.mask, bit(idx), settings.axis[idx].backlash > 0.0001f); - BIT_SET(dir_negative.mask, bit(idx), bit_isfalse(settings.homing.dir_mask.mask, bit(idx))); + if((steps = lroundf(settings.axis[idx].backlash * settings.axis[idx].steps_per_mm))) { + backlash_comp.values[idx] = (float)steps / settings.axis[idx].steps_per_mm; + BIT_SET(backlash_enabled.mask, bit(idx), settings.axis[idx].backlash > 0.0001f); + BIT_SET(dir_negative.mask, bit(idx), bit_isfalse(settings.homing.dir_mask.mask, bit(idx))); + } } } while(idx); @@ -63,7 +67,7 @@ void mc_backlash_init (axes_signals_t axes) void mc_sync_backlash_position (void) { // Update target_prev - system_convert_array_steps_to_mpos(target_prev, sys.position); + system_convert_array_steps_to_mpos(target_prev.values, sys.position); } #endif @@ -113,28 +117,28 @@ bool mc_line (float *target, plan_line_data_t *pl_data) if(backlash_enabled.mask) { - bool backlash_comp = false; + bool add_comp = false; uint_fast8_t idx = N_AXIS, axismask = bit(N_AXIS - 1); do { idx--; if(backlash_enabled.mask & axismask) { - if(target[idx] > target_prev[idx]) { + if(target[idx] > target_prev.values[idx]) { if (dir_negative.value & axismask) { dir_negative.value &= ~axismask; - target_prev[idx] += settings.axis[idx].backlash; - backlash_comp = true; + target_prev.values[idx] += backlash_comp.values[idx]; + add_comp = true; } - } else if(target[idx] < target_prev[idx] && !(dir_negative.value & axismask)) { + } else if(target[idx] < target_prev.values[idx] && !(dir_negative.value & axismask)) { dir_negative.value |= axismask; - target_prev[idx] -= settings.axis[idx].backlash; - backlash_comp = true; + target_prev.values[idx] -= backlash_comp.values[idx]; + add_comp = true; } } axismask >>= 1; } while(idx); - if(backlash_comp) { + if(add_comp) { plan_line_data_t pl_backlash; @@ -152,10 +156,10 @@ bool mc_line (float *target, plan_line_data_t *pl_data) return false; // Bail, if system abort. } - plan_buffer_line(target_prev, &pl_backlash); + plan_buffer_line(target_prev.values, &pl_backlash); } - memcpy(target_prev, target, sizeof(float) * N_AXIS); + memcpy(target_prev.values, target, sizeof(coord_data_t)); } #endif // Backlash comp diff --git a/settings.c b/settings.c index b847fa2..ec5e41b 100644 --- a/settings.c +++ b/settings.c @@ -39,6 +39,9 @@ #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; @@ -3407,6 +3410,10 @@ void settings_init (void) hal.probe.configure(false, false); } +#if SPINDLE_SYNC_ENABLE + st_spindle_sync_cfg(&settings, changed); +#endif + xbar_set_homing_source(); tmp_set_soft_limits(); diff --git a/spindle_control.c b/spindle_control.c index f0d57a7..c3fcf68 100644 --- a/spindle_control.c +++ b/spindle_control.c @@ -744,7 +744,7 @@ bool spindle_is_on (void) */ static inline uint_fast16_t invert_pwm (spindle_pwm_t *pwm_data, uint_fast16_t pwm_value) { - return pwm_data->flags.invert_pwm ? pwm_data->period - pwm_value - 1 : pwm_value; + return pwm_data->flags.invert_pwm ? pwm_data->period - pwm_value : pwm_value; } /*! \brief Spindle RPM to PWM conversion. @@ -834,6 +834,10 @@ bool spindle_precompute_pwm_values (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_ if((spindle->cap.variable = !settings->flags.pwm_disable && spindle->rpm_max > spindle->rpm_min)) { pwm_data->f_clock = clock_hz; pwm_data->period = (uint_fast16_t)((float)clock_hz / settings->pwm_freq); + pwm_data->flags.always_on = settings->pwm_off_value != 0.0f; + 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; if(settings->pwm_off_value == 0.0f) pwm_data->off_value = pwm_data->flags.invert_pwm ? pwm_data->period : 0; else @@ -843,12 +847,6 @@ bool spindle_precompute_pwm_values (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_ pwm_data->min_value = (uint_fast16_t)((float)pwm_data->max_value * 0.004f); pwm_data->pwm_gradient = (float)(pwm_data->max_value - pwm_data->min_value) / (spindle->rpm_max - spindle->rpm_min); - - pwm_data->flags.always_on = settings->pwm_off_value != 0.0f; - 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->compute_value = spindle_compute_pwm_value; } else { pwm_data->flags.value &= ((spindle_pwm_flags_t){ .cloned = pwm_data->flags.cloned }).value; diff --git a/spindle_sync.h b/spindle_sync.h index fcb08d3..ea80e6a 100644 --- a/spindle_sync.h +++ b/spindle_sync.h @@ -3,24 +3,24 @@ Spindle sync data structures - NOTE: not referenced in the core grbl code + NOTE: not referenced in the core Part of grblHAL - Copyright (c) 2020-2021 Terje Io + Copyright (c) 2020-2025 Terje Io - Grbl is free software: you can redistribute it and/or modify + 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. - Grbl is distributed in the hope that it will be useful, + 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 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with Grbl. If not, see . + along with grblHAL. If not, see . */ #ifndef _SPINDLE_SYNC_H_ @@ -61,18 +61,4 @@ typedef struct { volatile bool spin_lock; } spindle_encoder_t; -typedef struct { - float prev_pos; // Target position of previous segment - float steps_per_mm; // Steps per mm for current block - float programmed_rate; // Programmed feed in mm/rev for current block - int32_t min_cycles_per_tick; // Minimum cycles per tick for PID loop - uint_fast8_t segment_id; // Used for detecting start of new segment - pidf_t pid; // PID data for position - stepper_pulse_start_ptr stepper_pulse_start_normal; // Driver pulse function to restore after spindle sync move is completed -#ifdef PID_LOG - int32_t log[PID_LOG]; - int32_t pos[PID_LOG]; -#endif -} spindle_sync_t; - #endif diff --git a/stepper.c b/stepper.c index ac2936c..f5021f1 100644 --- a/stepper.c +++ b/stepper.c @@ -178,6 +178,7 @@ extern void gc_output_message (char *message); // + // Callback from delay to deenergize steppers after movement, might been cancelled void st_deenergize (void *data) { @@ -231,6 +232,124 @@ bool st_is_stepping (void) return stepping && st.exec_block; } +#if SPINDLE_SYNC_ENABLE + +typedef struct { + float prev_pos; // Target position of previous segment + float steps_per_mm; // Steps per mm for current block + float programmed_rate; // Programmed feed in mm/rev for current block + int32_t min_cycles_per_tick; // Minimum cycles per tick for PID loop + uint_fast8_t segment_id; // Used for detecting start of new segment + pidf_t pid; // PID data for position + stepper_pulse_start_ptr stepper_pulse_start; // Driver pulse function to restore after spindle sync move is completed +#ifdef PID_LOG +// int32_t log[PID_LOG]; +// int32_t pos[PID_LOG]; +#endif +} spindle_sync_t; + +static spindle_sync_t spindle_tracker; +static on_settings_changed_ptr on_settings_changed = NULL; + +void st_spindle_sync_cfg (settings_t *settings, settings_changed_flags_t changed) +{ + if(!on_settings_changed) { + on_settings_changed = grbl.on_settings_changed; + grbl.on_settings_changed = st_spindle_sync_cfg; + } else + on_settings_changed(settings, changed); + + spindle_tracker.min_cycles_per_tick = hal.f_step_timer / (uint32_t)(settings->axis[Z_AXIS].max_rate * settings->axis[Z_AXIS].steps_per_mm / 60.0f); + + // hal.driver_cap.spindle_encoder ?? check? + if((hal.driver_cap.spindle_sync = hal.spindle_data.get && settings->spindle.ppr) && pidf_config_changed(&spindle_tracker.pid, &settings->position.pid)) + pidf_init(&spindle_tracker.pid, &settings->position.pid); +} + +// Spindle sync version of pulse_start: inserted in front of driver version during synced motion. +// Reverts back to driver version when spindle synchronized motion is finished. +// Adjusts segment time based on difference between the actual and calculated position. +ISR_CODE static void st_spindle_sync_out (stepper_t *stepper) +{ + static bool sync = false; + static float block_start; + + if(stepper->new_block) { + if(!stepper->exec_segment->spindle_sync) { + hal.stepper.pulse_start = spindle_tracker.stepper_pulse_start; + hal.stepper.pulse_start(stepper); + return; + } + sync = true; + spindle_tracker.programmed_rate = stepper->exec_block->programmed_rate; + spindle_tracker.steps_per_mm = stepper->exec_block->steps_per_mm; + spindle_tracker.segment_id = 0; + spindle_tracker.prev_pos = 0.0f; + block_start = stepper->exec_block->spindle->get_data(SpindleData_AngularPosition)->angular_position * spindle_tracker.programmed_rate; + pidf_reset(&spindle_tracker.pid); +#ifdef PID_LOG + sys.pid_log.idx = 0; + sys.pid_log.setpoint = 100.0f; +#endif + } + + if(stepper->step_out.bits || stepper->new_block) + spindle_tracker.stepper_pulse_start(stepper); + + if(spindle_tracker.segment_id != stepper->exec_segment->id) { + + spindle_tracker.segment_id = stepper->exec_segment->id; + + if(!stepper->new_block) { // adjust this segments total time for any positional error since last segment + + float actual_pos; + + if(stepper->exec_segment->cruising) { + + float dt = (float)hal.f_step_timer / (float)(stepper->exec_segment->cycles_per_tick * stepper->exec_segment->n_step); + actual_pos = stepper->exec_block->spindle->get_data(SpindleData_AngularPosition)->angular_position * spindle_tracker.programmed_rate; + + if(sync) { + spindle_tracker.pid.sample_rate_prev = dt; +// spindle_tracker.block_start += (actual_pos - spindle_tracker.block_start) - spindle_tracker.prev_pos; +// spindle_tracker.block_start += spindle_tracker.prev_pos; + sync = false; + } + + actual_pos -= block_start; + int32_t step_delta = (int32_t)(pidf(&spindle_tracker.pid, spindle_tracker.prev_pos, actual_pos, dt) * spindle_tracker.steps_per_mm); + int32_t ticks = (((int32_t)stepper->step_count + step_delta) * (int32_t)stepper->exec_segment->cycles_per_tick) / (int32_t)stepper->step_count; + + stepper->exec_segment->cycles_per_tick = (uint32_t)max(ticks, spindle_tracker.min_cycles_per_tick >> stepper->amass_level); + + hal.stepper.cycles_per_tick(stepper->exec_segment->cycles_per_tick); + } else + actual_pos = spindle_tracker.prev_pos; + +#ifdef PID_LOG + if(sys.pid_log.idx < PID_LOG) { + + sys.pid_log.target[sys.pid_log.idx] = spindle_tracker.prev_pos; + sys.pid_log.actual[sys.pid_log.idx] = actual_pos; // - spindle_tracker.prev_pos; + + // spindle_tracker.log[sys.pid_log.idx] = STEPPER_TIMER->BGLOAD << stepper->amass_level; + // spindle_tracker.pos[sys.pid_log.idx] = stepper->exec_segment->cycles_per_tick stepper->amass_level; + // spindle_tracker.pos[sys.pid_log.idx] = stepper->exec_segment->cycles_per_tick * stepper->step_count; + // STEPPER_TIMER->BGLOAD = STEPPER_TIMER->LOAD; + + // spindle_tracker.pos[sys.pid_log.idx] = spindle_tracker.prev_pos; + + sys.pid_log.idx++; + } +#endif + } + + spindle_tracker.prev_pos = stepper->exec_segment->target_position; + } +} + +#endif // SPINDLE_SYNC_ENABLE + /* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of grblHAL. grblHAL employs the venerable Bresenham line algorithm to manage and exactly synchronize multi-axis moves. Unlike the popular DDA algorithm, the Bresenham algorithm is not susceptible to numerical @@ -287,6 +406,12 @@ ISR_CODE void ISR_FUNC(stepper_driver_interrupt_handler)(void) // Start a step pulse when there is a block to execute. if(st.exec_block) { +#if SPINDLE_SYNC_ENABLE + if(st.new_block && st.exec_segment->spindle_sync) { + spindle_tracker.stepper_pulse_start = hal.stepper.pulse_start; + hal.stepper.pulse_start = st_spindle_sync_out; + } +#endif hal.stepper.pulse_start(&st); st.new_block = false; @@ -534,6 +659,11 @@ void st_reset (void) st_go_idle(); // Initialize stepper driver idle state. +#if SPINDLE_SYNC_ENABLE + if(hal.stepper.pulse_start == st_spindle_sync_out) + hal.stepper.pulse_start = spindle_tracker.stepper_pulse_start; +#endif + // NOTE: buffer indices starts from 1 for simpler driver coding! // Set up stepper block ringbuffer as circular linked list and add id