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