diff --git a/README.md b/README.md
index ddc0e1b..ca15116 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
## grblHAL ##
-Latest build date is 20260331, see the [changelog](changelog.md) for details.
+Latest build date is 202603416, 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.
diff --git a/changelog.md b/changelog.md
index 3429a47..b510cc0 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,27 @@
## grblHAL changelog
+Build 20260416
+
+Core:
+
+* Formalized SPI API, added init call for 74HC595 I/O expander.
+
+* Fix for G30 not cleared on $RST, added code for syncing parser state on offsets clear. Ref. issue [#940](https://github.com/grblHAL/core/issues/940).
+
+Drivers:
+
+* ESP32, RP2040, STM32F4xx and STM32F7xx: updated SPI implementation to match new formalized API.
+
+Plugins:
+
+* Misc: added support for 74HC595 shift register(s) I/O expansion via SPI, up to 4 chips can be chained.
+> [!NOTE]
+> Do not use for step/dir signals due to latency. The ESP32 is the slowest, it takes a long time to switch devices when the SPI bus is shared.
+
+* SD card: fix to allow listing files in littlefs if no SD card mounted.
+
+---
+
Build 20260331
Core:
diff --git a/crossbar.h b/crossbar.h
index 0b6d275..e46eb42 100644
--- a/crossbar.h
+++ b/crossbar.h
@@ -319,6 +319,10 @@ typedef enum {
Output_MOSI,
Output_SPICLK,
Output_SPICS,
+ Output_SPICS0 = Output_SPICS,
+ Output_SPICS1,
+ Output_SPICS2,
+ Output_SPICS3,
Output_FlashCS,
Output_SdCardCS,
Input_SdCardDetect,
@@ -613,7 +617,10 @@ PROGMEM static const pin_name_t pin_names[] = {
{ .function = Input_MISO, .name = "MISO" },
{ .function = Output_MOSI, .name = "MOSI" },
{ .function = Output_SPICLK, .name = "SPI CLK" },
- { .function = Output_SPICS, .name = "SPI CS" },
+ { .function = Output_SPICS0, .name = "SPI CS0" },
+ { .function = Output_SPICS1, .name = "SPI CS1" },
+ { .function = Output_SPICS2, .name = "SPI CS2" },
+ { .function = Output_SPICS3, .name = "SPI CS3" },
{ .function = Output_FlashCS, .name = "Flash CS" },
{ .function = Output_SdCardCS, .name = "SD card CS" },
{ .function = Input_SdCardDetect, .name = "SD card detect" },
@@ -659,6 +666,7 @@ typedef enum {
PinGroup_MotorUART,
PinGroup_I2C,
PinGroup_SPI,
+ PinGroup_SPICS,
PinGroup_UART1,
PinGroup_UART = PinGroup_UART1,
PinGroup_UART2,
diff --git a/expanders_init.h b/expanders_init.h
index 0d47d85..bfa5176 100644
--- a/expanders_init.h
+++ b/expanders_init.h
@@ -9,7 +9,7 @@
Part of grblHAL
- Copyright (c) 2025 Terje Io
+ Copyright (c) 2025-2026 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
@@ -50,19 +50,35 @@ extern void mcp4725_init (void);
#endif
#if PCA9654E_ENABLE
-extern void pca9654e_init(void);
+extern void pca9654e_init (void);
#endif
// Third party I2C expander plugins goes after this line
#if FLEXGPIO_ENABLE
-extern void flexgpio_init(void);
+extern void flexgpio_init (void);
#endif
#endif // I2C expanders
// SPI expanders
+#if HC595_ENABLE
+
+#if defined(SPI_ENABLE) && !SPI_ENABLE
+#undef SPI_ENABLE
+#endif
+
+#ifndef SPI_ENABLE
+#define SPI_ENABLE 1
+#endif
+
+#endif
+
+#if HC595_ENABLE
+extern void hc595_init (void);
+#endif
+
//
// ModBus expanders
@@ -105,6 +121,10 @@ static inline void io_expanders_init (void)
{
board_ports_init(); // can be implemented by board specific code
+#if HC595_ENABLE
+ hc595_init();
+#endif
+
#if MCP3221_ENABLE
mcp3221_init();
#endif
diff --git a/gcode.c b/gcode.c
index 08ef861..075887d 100644
--- a/gcode.c
+++ b/gcode.c
@@ -273,6 +273,29 @@ axes_signals_t gc_get_g51_state (void)
return scaled;
}
+void gc_clear_offset (coord_system_id_t id)
+{
+ bool clear;
+ coord_system_data_t coord_data = {0};
+
+ if((id == CoordinateSystem_G92)) {
+ clear = !settings.flags.g92_is_volatile;
+ gc_state.g92_offset_applied = false;
+ memcpy(&gc_state.g92_offset, &coord_data, sizeof(coord_system_data_t));
+#if COMPATIBILITY_LEVEL <= 1
+ } else if((clear = (id < CoordinateSystem_G59_1 || id > CoordinateSystem_G59_3 || bit_isfalse(settings.offset_lock.mask, bit(id - CoordinateSystem_G59_1))))) {
+#else
+ } else {
+ clear = true;
+#endif
+ if(id == gc_state.modal.g5x_offset.id)
+ memcpy(&gc_state.modal.g5x_offset.data, &coord_data, sizeof(coord_system_data_t));
+ }
+
+ if(clear)
+ settings_write_coord_data(id, &coord_data);
+}
+
float gc_get_offset (uint_fast8_t idx, bool real_time)
{
offset_id_t offset_id;
diff --git a/gcode.h b/gcode.h
index 816c4f9..824fd4c 100644
--- a/gcode.h
+++ b/gcode.h
@@ -751,6 +751,8 @@ float *gc_get_scaling (void);
// Get current axis offset.
float gc_get_offset (uint_fast8_t idx, bool real_time);
+void gc_clear_offset (coord_system_id_t id);
+
char *gc_coord_system_to_str (coord_system_id_t id);
void gc_clear_output_commands (output_command_t *cmd);
diff --git a/grbl.h b/grbl.h
index 8fd8957..1c3da6c 100644
--- a/grbl.h
+++ b/grbl.h
@@ -42,7 +42,7 @@
#else
#define GRBL_VERSION "1.1f"
#endif
-#define GRBL_BUILD 20260331
+#define GRBL_BUILD 20260416
#define GRBL_URL "https://github.com/grblHAL"
diff --git a/modbus_rtu.c b/modbus_rtu.c
index 1d67e6c..68f29cf 100644
--- a/modbus_rtu.c
+++ b/modbus_rtu.c
@@ -327,9 +327,10 @@ static bool modbus_send_rtu (modbus_message_t *msg, const modbus_callbacks_t *ca
packet = NULL;
is_blocking = false;
+ sync_msg.msg.adu[1] = 0;
state = silence_until > 0 ? ModBus_Silent : ModBus_Idle;
- } else if(packet != &sync_msg) {
+ } else if(packet == NULL || sync_msg.msg.adu[1] == 0 || packet->msg.adu[0] != sync_msg.msg.adu[0]) {
if(head->next != tail) {
add_message((queue_entry_t *)head, msg, true, callbacks);
head = head->next;
diff --git a/pin_bits_masks.h b/pin_bits_masks.h
index 9d42a20..098b6a1 100644
--- a/pin_bits_masks.h
+++ b/pin_bits_masks.h
@@ -118,7 +118,7 @@
#else
#define add_aux_input(fn, aux, irq, signal_bit) { .function = fn, .irq_mode = irq, .signal.value = signal_bit, .port = IOPORT_UNASSIGNED, .gpio.port = (void *)aux##_PORT, .gpio.pin = aux##_PIN },
#endif
-#if defined(__IMXRT1062__) || defined(ESP_PLATFORM)
+#if defined(__IMXRT1062__) // || defined(ESP_PLATFORM)
#define add_aux_output(fn, aux) { .function = fn, .port = IOPORT_UNASSIGNED, .gpio.pin = aux##_PIN },
#else
#define add_aux_output(fn, aux) { .function = fn, .port = IOPORT_UNASSIGNED, .gpio.port = (void *)aux##_PORT, .gpio.pin = aux##_PIN },
@@ -433,6 +433,53 @@ static inline control_signals_t aux_ctrl_scan_status (control_signals_t signals)
return signals;
}
+#if defined(__IMXRT1062__) || defined(ESP_PLATFORM)
+/*
+#ifndef STEPPERS_ENABLE_PORT
+#define STEPPERS_ENABLE_PORT 0
+#endif
+*/
+#if defined(SPINDLE_ENABLE_PIN) && !defined(SPINDLE_ENABLE_PORT)
+#define SPINDLE_ENABLE_PORT 0
+#endif
+#if defined(SPINDLE_DIRECTION_PIN) && !defined(SPINDLE_DIRECTION_PORT)
+#define SPINDLE_DIRECTION_PORT 0
+#endif
+#if defined(SPINDLE_PWM_PIN) && !defined(SPINDLE_PWM_PORT)
+#define SPINDLE_PWM_PORT 0
+#endif
+
+#if defined(SPINDLE1_ENABLE_PIN) && !defined(SPINDLE1_ENABLE_PORT)
+#define SPINDLE1_ENABLE_PORT 0
+#endif
+#if defined(SPINDLE1_DIRECTION_PIN) && !defined(SPINDLE1_DIRECTION_PORT)
+#define SPINDLE1_DIRECTION_PORT 0
+#endif
+#if defined(SPINDLE1_PWM_PIN) && !defined(SPINDLE1_PWM_PORT)
+#define SPINDLE1_PWM_PORT 0
+#endif
+
+#if defined(COOLANT_FLOOD_PIN) && !defined(COOLANT_FLOOD_PORT)
+#define COOLANT_FLOOD_PORT 0
+#endif
+#if defined(COOLANT_MIST_PIN) && !defined(COOLANT_MIST_PORT)
+#define COOLANT_MIST_PORT 0
+#endif
+
+#if defined(COPROC_RESET_PIN) && !defined(COPROC_RESET_PORT)
+#define COPROC_RESET_PORT 0
+#endif
+#if defined(COPROC_BOOT0_PIN) && !defined(COPROC_BOOT0_PORT)
+#define COPROC_BOOT0_PORT 0
+#endif
+
+#if defined(SPI_RST_PIN) && !defined(SPI_RST_PORT)
+#define SPI_RST_PORT 0
+#endif
+
+#endif
+
+
// The following pins are bound explicitly to aux output pins
static aux_ctrl_out_t aux_ctrl_out[] = {
#if defined(ESP_PLATFORM) || defined(RP2040) // for now
@@ -508,9 +555,6 @@ static aux_ctrl_out_t aux_ctrl_out[] = {
add_aux_output(Output_CoProc_Boot0, COPROC_BOOT0)
#endif
#if defined(SPI_RST_PIN) && defined(RP2040)
- #ifndef SPI_RST_PORT
- #define SPI_RST_PORT 0
- #endif
add_aux_output(Output_SPIRST, SPI_RST)
#endif
};
diff --git a/plugins.h b/plugins.h
index db468e0..f822f56 100644
--- a/plugins.h
+++ b/plugins.h
@@ -226,13 +226,12 @@ extern bool i2c_transfer (i2c_transfer_t *i2c, bool read);
// SPI interface:
-/*
-typedef void (*spi_cs_ptr)(const char c);
-
typedef struct {
- spi_cs_ptr cs;
- uint32_t f_clk;
-} spi_cfg_t;
+ uint8_t cs_pin;
+ void *cs_port;
+ uint32_t f_clock;
+ void *handle;
+} spi_slave_t;
typedef union {
uint8_t ok;
@@ -244,14 +243,13 @@ typedef union {
};
} spi_cap_t;
-extern spi_cap_t spi_start (spi_cfg_t *cfg);
-extern uint32_t spi_set_speed (uint32_t prescaler);
+extern spi_cap_t spi_start (spi_slave_t *slave);
+extern bool spi_select (spi_slave_t *slave);
+extern bool spi_deselect (spi_slave_t *slave);
extern uint8_t spi_get_byte (void);
extern uint8_t spi_put_byte (uint8_t byte);
-extern void spi_write (uint8_t *data, size_t size);
-extern void spi_read (uint8_t *data, size_t size);
-extern void spi_write (uint8_t *data, size_t size);
-*/
+extern bool spi_read (uint8_t *data, uint16_t size);
+extern bool spi_write (uint8_t *data, uint16_t size);
// EEPROM/FRAM:
diff --git a/settings.c b/settings.c
index d379008..a00af07 100644
--- a/settings.c
+++ b/settings.c
@@ -2337,7 +2337,7 @@ PROGMEM static const setting_descr_t setting_descr[] = {
{ 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." },
+ "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." },
@@ -2616,14 +2616,14 @@ FLASHMEM 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;
+ 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 = &tool_data;
+ } else {
tool.data = NULL;
tool.pocket = (pocket_id_t)-1;
- }
+ }
return &tool;
}
@@ -2772,15 +2772,8 @@ FLASHMEM void settings_restore (settings_restore_t restore)
}
if(restore.parameters) {
- coord_system_data_t coord_data = {0};
- for(idx = 0; idx <= N_WorkCoordinateSystems; idx++) {
-#if COMPATIBILITY_LEVEL <= 1
- if(idx < CoordinateSystem_G59_1 || idx > CoordinateSystem_G59_3 || bit_isfalse(settings.offset_lock.mask, bit(idx - CoordinateSystem_G59_1)))
-#endif
- settings_write_coord_data((coord_system_id_t)idx, &coord_data);
- }
- settings_write_coord_data(CoordinateSystem_G92, &coord_data); // Clear G92 offsets
-
+ for(idx = 0; idx < N_CoordinateSystems; idx++)
+ gc_clear_offset((coord_system_id_t)idx);
#if N_TOOLS
settings_clear_tool_data();
#endif
diff --git a/stream.c b/stream.c
index 0c00067..31c6f59 100644
--- a/stream.c
+++ b/stream.c
@@ -889,7 +889,7 @@ void debug_printf (const char *fmt, ...)
debug_writeln(debug_out);
}
-static bool debug_claim_stream (io_stream_properties_t const *stream)
+static bool debug_claim_stream (io_stream_properties_t const *stream, void *data)
{
io_stream_t const *claimed = NULL;