diff --git a/CMakeLists.txt b/CMakeLists.txt
index f0340c4..b4f695c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,6 +39,7 @@ target_sources(grbl INTERFACE
${CMAKE_CURRENT_LIST_DIR}/ngc_flowctrl.c
${CMAKE_CURRENT_LIST_DIR}/regex.c
${CMAKE_CURRENT_LIST_DIR}/ioports.c
+ ${CMAKE_CURRENT_LIST_DIR}/utf8.c
${CMAKE_CURRENT_LIST_DIR}/vfs.c
${CMAKE_CURRENT_LIST_DIR}/canbus.c
${CMAKE_CURRENT_LIST_DIR}/pid.c
diff --git a/README.md b/README.md
index 8fd4f34..1d25f7a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
## grblHAL ##
-Latest build date is 20251109, see the [changelog](changelog.md) for details.
+Latest build date is 20251130, 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/alarms.c b/alarms.c
index 6768db3..2202d40 100644
--- a/alarms.c
+++ b/alarms.c
@@ -7,18 +7,18 @@
Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC
Copyright (c) 2009-2011 Simen Svale Skogsrud
- 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 .
*/
#include
diff --git a/alarms.h b/alarms.h
index 9d66209..05d35f9 100644
--- a/alarms.h
+++ b/alarms.h
@@ -3,22 +3,22 @@
Part of grblHAL
- Copyright (c) 2017-2023 Terje Io
+ Copyright (c) 2017-2025 Terje Io
Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC
Copyright (c) 2009-2011 Simen Svale Skogsrud
- 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 _ALARMS_H_
@@ -67,5 +67,13 @@ alarm_details_t *alarms_get_details (void);
const char *alarms_get_description (alarm_code_t id);
void alarms_register (alarm_details_t *details);
-#endif
+static inline bool alarm_is_critical (alarm_code_t alarm)
+{
+ return alarm == Alarm_HardLimit ||
+ alarm == Alarm_SoftLimit ||
+ alarm == Alarm_EStop ||
+ alarm == Alarm_MotorFault ||
+ alarm == Alarm_ExpanderException;
+}
+#endif
diff --git a/changelog.md b/changelog.md
index d84f563..d56216c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,36 @@
## grblHAL changelog
+Build 20251130
+
+Core:
+
+* Updated to allow Bluetooth serial streams to be used for MPG/pendants.
+
+* Parking mode improvements.
+
+* Removed requirement for external encoder for spindle sync if stepper spindle is enabled.
+
+* Improved handling of `$680` stepper enable delay.
+
+Drivers:
+
+* ESP32, RP2040: Updated native Bluetooth implementations to allow Bluetooth serial streams to be used for MPG/pendants.
+
+* RP2040: updated LED strip code to use non-blocking DMA.
+Extended spindle PWM frequency range down to ~10Hz. Ref. discussion [#155](https://github.com/grblHAL/RP2040/discussions/155).
+
+* STM32F3xx: fixed compiler errors and warnings for generic board, ref. issue [#5](https://github.com/grblHAL/STM32F3xx/issues/5).
+
+* STM32F4xx: refactored LED strip drivers, now supports up to two strips via DMA based PWM.
+
+Plugins:
+
+* Bluetooth: updated to allow use for MPG/pendants.
+
+* Misc, FNC Expander: added compile time option for PWM outputs, some general improvements.
+
+---
+
Build 20251111
Core:
diff --git a/driver_opts.h b/driver_opts.h
index 260cc3f..f6803f8 100644
--- a/driver_opts.h
+++ b/driver_opts.h
@@ -198,14 +198,6 @@
#define SPINDLE_SYNC_ENABLE 0
#endif
-#ifndef SPINDLE_ENCODER_ENABLE
-#if SPINDLE_SYNC_ENABLE
-#define SPINDLE_ENCODER_ENABLE 1
-#else
-#define SPINDLE_ENCODER_ENABLE 0
-#endif
-#endif
-
#ifndef TRINAMIC_ENABLE
#define TRINAMIC_ENABLE 0
#endif
@@ -450,6 +442,14 @@
#endif
#endif
+#ifndef SPINDLE_ENCODER_ENABLE
+#if SPINDLE_SYNC_ENABLE && !(SPINDLE_ENABLE & (1 << SPINDLE_STEPPER))
+#define SPINDLE_ENCODER_ENABLE 1
+#else
+#define SPINDLE_ENCODER_ENABLE 0
+#endif
+#endif
+
#ifndef QEI_ENABLE
#define QEI_ENABLE 0
#endif
diff --git a/driver_opts2.h b/driver_opts2.h
index 620e2e7..4988c0b 100644
--- a/driver_opts2.h
+++ b/driver_opts2.h
@@ -246,6 +246,26 @@
#define TRINAMIC_MOTOR_ENABLE 0
#endif
+#if MPG_ENABLE && !defined(MPG_STREAM)
+#if USB_SERIAL_CDC
+#define MPG_STREAM 0
+#else
+#define MPG_STREAM 1
+#endif
+#if (MODBUS_ENABLE & MODBUS_RTU_ENABLED) && defined(MODBUS_RTU_STREAM) && MODBUS_RTU_STREAM == MPG_STREAM
+#undef MPG_STREAM
+#define MPG_STREAM (MODBUS_RTU_STREAM + 1)
+#endif
+#endif
+
+#if MPG_ENABLE && MPG_ENABLE != 2 && MPG_STREAM == 20 && BLUETOOTH_ENABLE != 1
+#error "MPG can only be used with native Bluetooth and character mode switching!"
+#endif
+
+#if BLUETOOTH_ENABLE == 2 && !defined(BLUETOOTH_STREAM)
+#define BLUETOOTH_STREAM 255 // Select first free UART stream
+#endif
+
#if USB_SERIAL_CDC && defined(SERIAL_PORT)
#define SP0 1
#else
@@ -276,35 +296,37 @@
#define TRINAMIC_TEST 0
#endif
-#if KEYPAD_ENABLE == 2 && MPG_ENABLE == 0
+#if MPG_ENABLE && MPG_STREAM != 20
+#define MPG_TEST 1
+#else
+#define MPG_TEST 0
+#endif
+
+#if KEYPAD_ENABLE == 2 && !MPG_TEST
#define KEYPAD_TEST 1
#else
#define KEYPAD_TEST 0
#endif
-#if (MODBUS_TEST + KEYPAD_TEST + (MPG_ENABLE ? 1 : 0) + TRINAMIC_TEST + (BLUETOOTH_ENABLE == 2 ? 1 : 0)) > (SP0 + SP1 + SP2)
-#error "Too many options that uses a serial port are enabled!"
+#if BLUETOOTH_ENABLE == 2 && (!MPG_ENABLE || MPG_STREAM != BLUETOOTH_STREAM)
+#define BT_TEST 1
+#else
+#define BT_TEST 0
+#endif
+
+#if (MODBUS_TEST + KEYPAD_TEST + MPG_TEST + TRINAMIC_TEST + BT_TEST) > (SP0 + SP1 + SP2)
+#error "Too many options that requires a serial port are enabled!"
#endif
#undef SP0
#undef SP1
#undef SP2
+#undef BT_TEST
#undef MODBUS_TEST
+#undef MPG_TEST
#undef KEYPAD_TEST
#undef TRINAMIC_TEST
-#if MPG_ENABLE && !defined(MPG_STREAM)
-#if USB_SERIAL_CDC
-#define MPG_STREAM 0
-#else
-#define MPG_STREAM 1
-#endif
-#if (MODBUS_ENABLE & MODBUS_RTU_ENABLED) && defined(MODBUS_RTU_STREAM) && MODBUS_RTU_STREAM == MPG_STREAM
-#undef MPG_STREAM
-#define MPG_STREAM (MODBUS_RTU_STREAM + 1)
-#endif
-#endif
-
#if KEYPAD_ENABLE == 2 && !defined(KEYPAD_STREAM)
#if MPG_ENABLE
#define KEYPAD_STREAM MPG_STREAM
diff --git a/grbl.h b/grbl.h
index 448beb7..23ab839 100644
--- a/grbl.h
+++ b/grbl.h
@@ -42,7 +42,7 @@
#else
#define GRBL_VERSION "1.1f"
#endif
-#define GRBL_BUILD 20251111
+#define GRBL_BUILD 20251130
#define GRBL_URL "https://github.com/grblHAL"
@@ -113,48 +113,48 @@
// at character value 128 (0x80) and up to 255 (0xFF). If the normal set of realtime commands,
// such as status reports, feed hold, reset, and cycle start, are moved to the extended set
// space, protocol.c's protocol_process_realtime() will need to be modified to accommodate the change.
-#define CMD_STATUS_REPORT 0x80 // TODO: use 0x05 ctrl-E ENQ instead?
-#define CMD_CYCLE_START 0x81 // TODO: use 0x06 ctrl-F ACK instead? or SYN/DC2/DC3?
-#define CMD_FEED_HOLD 0x82 // TODO: use 0x15 ctrl-U NAK instead?
-#define CMD_GCODE_REPORT 0x83
-#define CMD_SAFETY_DOOR 0x84
-#define CMD_JOG_CANCEL 0x85
+#define CMD_STATUS_REPORT 0x80 // (128) TODO: use 0x05 ctrl-E ENQ instead?
+#define CMD_CYCLE_START 0x81 // (129) TODO: use 0x06 ctrl-F ACK instead? or SYN/DC2/DC3?
+#define CMD_FEED_HOLD 0x82 // (130) TODO: use 0x15 ctrl-U NAK instead?
+#define CMD_GCODE_REPORT 0x83 // (131)
+#define CMD_SAFETY_DOOR 0x84 // (132)
+#define CMD_JOG_CANCEL 0x85 // (133)
//#define CMD_DEBUG_REPORT 0x86 // Only when DEBUG enabled, sends debug report in '{}' braces.
-#define CMD_STATUS_REPORT_ALL 0x87
-#define CMD_OPTIONAL_STOP_TOGGLE 0x88
-#define CMD_SINGLE_BLOCK_TOGGLE 0x89
-#define CMD_OVERRIDE_FAN0_TOGGLE 0x8A //!< Toggle Fan 0 on/off, not implemented by the core.
-#define CMD_MPG_MODE_TOGGLE 0x8B //!< Toggle MPG mode on/off, available when the MPG stream is enabled with MPG mode 2.
-#define CMD_AUTO_REPORTING_TOGGLE 0x8C //!< Toggle auto real time reporting if configured.
-#define CMD_OVERRIDE_FEED_RESET 0x90 //!< Restores feed override value to 100%.
-#define CMD_OVERRIDE_FEED_COARSE_PLUS 0x91
-#define CMD_OVERRIDE_FEED_COARSE_MINUS 0x92
-#define CMD_OVERRIDE_FEED_FINE_PLUS 0x93
-#define CMD_OVERRIDE_FEED_FINE_MINUS 0x94
-#define CMD_OVERRIDE_RAPID_RESET 0x95 //!< Restores rapid override value to 100%.
-#define CMD_OVERRIDE_RAPID_MEDIUM 0x96
-#define CMD_OVERRIDE_RAPID_LOW 0x97
+#define CMD_STATUS_REPORT_ALL 0x87 // (135)
+#define CMD_OPTIONAL_STOP_TOGGLE 0x88 // (136)
+#define CMD_SINGLE_BLOCK_TOGGLE 0x89 // (137)
+#define CMD_OVERRIDE_FAN0_TOGGLE 0x8A //!< (138) Toggle Fan 0 on/off, not implemented by the core.
+#define CMD_MPG_MODE_TOGGLE 0x8B //!< (139) Toggle MPG mode on/off, available when the MPG stream is enabled with MPG mode 2.
+#define CMD_AUTO_REPORTING_TOGGLE 0x8C //!< (140) Toggle auto real time reporting if configured.
+#define CMD_OVERRIDE_FEED_RESET 0x90 //!< (144) Restores feed override value to 100%.
+#define CMD_OVERRIDE_FEED_COARSE_PLUS 0x91 // (145)
+#define CMD_OVERRIDE_FEED_COARSE_MINUS 0x92 // (146)
+#define CMD_OVERRIDE_FEED_FINE_PLUS 0x93 // (147)
+#define CMD_OVERRIDE_FEED_FINE_MINUS 0x94 // (148)
+#define CMD_OVERRIDE_RAPID_RESET 0x95 //!< (149) Restores rapid override value to 100%.
+#define CMD_OVERRIDE_RAPID_MEDIUM 0x96 // (150)
+#define CMD_OVERRIDE_RAPID_LOW 0x97 // (151)
// #define CMD_OVERRIDE_RAPID_EXTRA_LOW 0x98 // *NOT SUPPORTED*
-#define CMD_OVERRIDE_SPINDLE_RESET 0x99 // Restores spindle override value to 100%.
-#define CMD_OVERRIDE_SPINDLE_COARSE_PLUS 0x9A
-#define CMD_OVERRIDE_SPINDLE_COARSE_MINUS 0x9B
-#define CMD_OVERRIDE_SPINDLE_FINE_PLUS 0x9C
-#define CMD_OVERRIDE_SPINDLE_FINE_MINUS 0x9D
-#define CMD_OVERRIDE_SPINDLE_STOP 0x9E
-#define CMD_OVERRIDE_COOLANT_FLOOD_TOGGLE 0xA0
-#define CMD_OVERRIDE_COOLANT_MIST_TOGGLE 0xA1
-#define CMD_PID_REPORT 0xA2
-#define CMD_TOOL_ACK 0xA3
-#define CMD_PROBE_CONNECTED_TOGGLE 0xA4
+#define CMD_OVERRIDE_SPINDLE_RESET 0x99 // (153) Restores spindle override value to 100%.
+#define CMD_OVERRIDE_SPINDLE_COARSE_PLUS 0x9A // (154)
+#define CMD_OVERRIDE_SPINDLE_COARSE_MINUS 0x9B // (155)
+#define CMD_OVERRIDE_SPINDLE_FINE_PLUS 0x9C // (156)
+#define CMD_OVERRIDE_SPINDLE_FINE_MINUS 0x9D // (157)
+#define CMD_OVERRIDE_SPINDLE_STOP 0x9E // (158)
+#define CMD_OVERRIDE_COOLANT_FLOOD_TOGGLE 0xA0 // (160)
+#define CMD_OVERRIDE_COOLANT_MIST_TOGGLE 0xA1 // (161)
+#define CMD_PID_REPORT 0xA2 // (162)
+#define CMD_TOOL_ACK 0xA3 // (163)
+#define CMD_PROBE_CONNECTED_TOGGLE 0xA4 // (164)
// The following character codes are reserved for plugin use
-#define CMD_MACRO_0 0xB0
-#define CMD_MACRO_1 0xB1
-#define CMD_MACRO_2 0xB2
-#define CMD_MACRO_3 0xB3
-#define CMD_MACRO_4 0xB4
-#define CMD_MACRO_5 0xB5
-#define CMD_MACRO_6 0xB6
-#define CMD_MACRO_7 0xB7
+#define CMD_MACRO_0 0xB0 // (176)
+#define CMD_MACRO_1 0xB1 // (177)
+#define CMD_MACRO_2 0xB2 // (178)
+#define CMD_MACRO_3 0xB3 // (179)
+#define CMD_MACRO_4 0xB4 // (180)
+#define CMD_MACRO_5 0xB5 // (181)
+#define CMD_MACRO_6 0xB6 // (182)
+#define CMD_MACRO_7 0xB7 // (183)
// System motion line numbers must be zero.
#define JOG_LINE_NUMBER 0
diff --git a/grbllib.c b/grbllib.c
index 3488cdb..0ff1009 100644
--- a/grbllib.c
+++ b/grbllib.c
@@ -95,6 +95,7 @@ static driver_startup_t driver = { .ok = 0xFF };
static core_task_t *next_task = NULL, *immediate_task = NULL, *on_booted = NULL, *systick_task = NULL, *last_freed = NULL;
static on_linestate_changed_ptr on_linestate_changed;
static settings_changed_ptr hal_settings_changed;
+static stepper_enable_ptr stepper_enable;
#ifdef KINEMATICS_API
kinematics_t kinematics;
@@ -211,6 +212,14 @@ static void onLinestateChanged (serial_linestate_t state)
on_linestate_changed(state);
}
+static void stepperEnable (axes_signals_t enable, bool hold)
+{
+ if(stepper_enable)
+ stepper_enable(enable, hold);
+
+ sys.steppers_enabled = /*!hold &&*/ enable.bits == AXES_BITMASK;
+}
+
static void print_pos_msg (void *data)
{
hal.stream.write("grblHAL: power on self-test (POS) failed!" ASCII_EOL);
@@ -354,6 +363,9 @@ int grbl_enter (void)
// check and configure driver
+ stepper_enable = hal.stepper.enable;
+ hal.stepper.enable = stepperEnable;
+
#if ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
driver.amass = hal.driver_cap.amass_level >= MAX_AMASS_LEVEL;
hal.driver_cap.amass_level = MAX_AMASS_LEVEL;
diff --git a/modbus_rtu.c b/modbus_rtu.c
index 8056e25..e943101 100644
--- a/modbus_rtu.c
+++ b/modbus_rtu.c
@@ -519,7 +519,7 @@ static void modbus_set_direction (bool tx)
ioport_digital_out(dir_port, tx);
}
-static bool claim_stream (io_stream_properties_t const *sstream)
+static bool claim_stream (io_stream_properties_t const *sstream, void *data)
{
io_stream_t const *claimed = NULL;
@@ -598,7 +598,7 @@ void modbus_rtu_init (int8_t instance, int8_t dir_aux)
stream_instance = instance;
- if((hal.driver_cap.modbus_rtu = stream_enumerate_streams(claim_stream) && (nvs_address = nvs_alloc(sizeof(rtu_settings_t))))) {
+ if((hal.driver_cap.modbus_rtu = stream_enumerate_streams(claim_stream, NULL) && (nvs_address = nvs_alloc(sizeof(rtu_settings_t))))) {
if(stream.set_direction == NULL && dir_aux != -2) {
diff --git a/protocol.c b/protocol.c
index b1c790f..e12db47 100644
--- a/protocol.c
+++ b/protocol.c
@@ -462,10 +462,7 @@ bool protocol_exec_rt_system (void)
hal.driver_reset();
// Halt everything upon a critical event flag. Currently hard and soft limits flag this.
- if((sys.blocking_event = (alarm_code_t)rt_exec == Alarm_HardLimit ||
- (alarm_code_t)rt_exec == Alarm_SoftLimit ||
- (alarm_code_t)rt_exec == Alarm_EStop ||
- (alarm_code_t)rt_exec == Alarm_MotorFault)) {
+ if((sys.blocking_event = alarm_is_critical((alarm_code_t)rt_exec))) {
static const control_signals_t blocking_signals = { .e_stop = On, .motor_fault = On };
@@ -894,9 +891,9 @@ ISR_CODE bool ISR_FUNC(protocol_enqueue_realtime_command)(uint8_t c)
break;
case CMD_SAFETY_DOOR:
- if(state_get() != STATE_SAFETY_DOOR) {
+ if((drop = state_get() != STATE_SAFETY_DOOR)) {
+ sys.flags.is_parking = settings.parking.flags.enabled;
system_set_exec_state_flag(EXEC_SAFETY_DOOR);
- drop = true;
}
break;
diff --git a/report.c b/report.c
index 256aeba..2b7e512 100644
--- a/report.c
+++ b/report.c
@@ -2366,86 +2366,121 @@ status_code_t report_pins (sys_state_t state, char *args)
return Status_OK;
}
+typedef struct {
+ uint32_t idx;
+ const io_stream_properties_t *port;
+} port_data_t;
+
+typedef struct {
+ uint8_t instance;
+ uint32_t n_pins;
+ pin_info_t data[2];
+} port_pins_t;
+
static void get_uart_pins (xbar_t *pin, void *data)
{
- if(pin->group >= PinGroup_UART && pin->group <= PinGroup_UART4)
- get_pin_info(pin, &((pin_data_t *)data)->pins[((pin_data_t *)data)->idx++]);
+ if(pin->group == PinGroup_UART + ((port_pins_t *)data)->instance)
+ get_pin_info(pin, &((port_pins_t *)data)->data[((port_pins_t *)data)->n_pins++]);
}
-static void count_uart_pins (xbar_t *pin, void *data)
+static bool report_port_info (const io_stream_properties_t *port, void *data)
{
- if(pin->group >= PinGroup_UART && pin->group <= PinGroup_UART4)
- ((pin_data_t *)data)->n_pins++;
-}
+ if(!stream_is_uart(port->type))
+ return false;
-static void report_port_info (pin_info_t *pin)
-{
+ port_pins_t pins = {0};
const io_stream_status_t *status;
- if(pin->function == Input_RX) {
- strcpy(buf, pin->port);
- strcat(buf, uitoa(pin->pin));
- strcat(buf, ",");
- strcat(buf, xbar_fn_to_pinname(Input_RX));
- strcat(buf, "|");
+ hal.stream.write("[PORT:");
+ hal.stream.write(uitoa(port->instance));
+ hal.stream.write("|");
+ if(port->type == StreamType_Bluetooth) {
+ hal.stream.write("BT||");
} else {
-
- uint8_t instance = (pin->sortkey >> 16) - PinGroup_UART;
-
- hal.stream.write("[PORT:");
- hal.stream.write(uitoa(instance));
- hal.stream.write("|");
- hal.stream.write(pin->description);
- hal.stream.write("|");
- hal.stream.write(*buf ? buf : "-|");
- if(*pin->port)
- hal.stream.write(pin->port);
- hal.stream.write(uitoa(pin->pin));
- hal.stream.write(",");
- hal.stream.write(xbar_fn_to_pinname(pin->function));
- if(hal.stream.write_char && (status = stream_get_uart_status(instance))) {
- hal.stream.write("|");
- hal.stream.write(uitoa(status->baud_rate));
- hal.stream.write(",");
- hal.stream.write_char("87"[status->format.width]);
- hal.stream.write(",");
- hal.stream.write_char("NEOMS"[status->format.parity]);
- hal.stream.write(",");
- hal.stream.write(((const char * const[]){"1", "1.5", "2", "0.5"})[status->format.stopbits]);
- if(status->flags.rts_handshake)
- hal.stream.write(",P");
- hal.stream.write("|");
- hal.stream.write_char("FC"[status->flags.claimed]);
- }
- hal.stream.write("]" ASCII_EOL);
- *buf = '\0';
+ uint32_t idx = 0;
+ pins.instance = port->instance;
+ if(hal.enumerate_pins)
+ hal.enumerate_pins(false, get_uart_pins, &pins);
+ if(pins.n_pins) {
+ hal.stream.write(pins.data[0].description);
+ for(idx = 0; idx < pins.n_pins; idx++) {
+ hal.stream.write("|");
+ if(*pins.data[idx].port)
+ hal.stream.write(pins.data[idx].port);
+ hal.stream.write(uitoa(pins.data[idx].pin));
+ hal.stream.write(",");
+ hal.stream.write(xbar_fn_to_pinname(pins.data[idx].function));
+ }
+ for(; idx < 2; idx++)
+ hal.stream.write("|");
+ } else
+ hal.stream.write("UART||");
}
+
+ if(hal.stream.write_char && (status = stream_get_uart_status(port->instance))) {
+ hal.stream.write("|");
+ hal.stream.write(uitoa(status->baud_rate));
+ hal.stream.write(",");
+ hal.stream.write_char("87"[status->format.width]);
+ hal.stream.write(",");
+ hal.stream.write_char("NEOMS"[status->format.parity]);
+ hal.stream.write(",");
+ hal.stream.write(((const char * const[]){"1", "1.5", "2", "0.5"})[status->format.stopbits]);
+ if(status->flags.rts_handshake)
+ hal.stream.write(",P");
+ hal.stream.write("|");
+ hal.stream.write_char("FC"[status->flags.claimed]);
+ }
+
+ hal.stream.write("]" ASCII_EOL);
+
+ return false;
+}
+
+bool get_ports (io_stream_properties_t const *port, void *data)
+{
+ if(stream_is_uart(port->type)) {
+ ((port_data_t *)data)[((port_data_t *)data)->idx].port = port;
+ ((port_data_t *)data)->idx++;
+ }
+
+ return false;
+}
+
+bool count_ports (io_stream_properties_t const *port, void *data)
+{
+ if(stream_is_uart(port->type))
+ (*(uint32_t *)data)++;
+
+ return false;
+}
+
+static int cmp_ports (const void *a, const void *b)
+{
+ return ((port_data_t *)a)->port->instance - ((port_data_t *)b)->port->instance;
}
status_code_t report_uart_ports (sys_state_t state, char *args)
{
- pin_data_t pin_data = {0};
+ uint32_t n_ports = 0;
+ port_data_t *port_data;
- if(hal.enumerate_pins) {
+ stream_enumerate_streams(count_ports, &n_ports);
+ if(n_ports) {
+ if((port_data = malloc(n_ports * sizeof(port_data_t)))) {
- hal.enumerate_pins(false, count_uart_pins, (void *)&pin_data);
+ port_data->idx = 0;
+ stream_enumerate_streams(get_ports, port_data);
- if((pin_data.pins = malloc(pin_data.n_pins * sizeof(pin_info_t)))) {
-
- *buf = '\0';
-
- hal.enumerate_pins(false, get_uart_pins, (void *)&pin_data);
-
- qsort(pin_data.pins, pin_data.n_pins, sizeof(pin_info_t), cmp_pins);
- for(pin_data.idx = 0; pin_data.idx < pin_data.n_pins; pin_data.idx++)
- report_port_info(&pin_data.pins[pin_data.idx]);
-
- free(pin_data.pins);
+ qsort(port_data, n_ports, sizeof(port_data_t), cmp_ports);
+ for(port_data->idx = 0; port_data->idx < n_ports; port_data->idx++)
+ report_port_info(port_data[port_data->idx].port, NULL);
+ free(port_data);
} else
- hal.enumerate_pins(false, report_pin, NULL);
- }
-
+ stream_enumerate_streams(report_port_info, NULL);
+ }
+
return Status_OK;
}
diff --git a/settings.c b/settings.c
index ccb8373..c7f0821 100644
--- a/settings.c
+++ b/settings.c
@@ -434,6 +434,7 @@ static char probing_options[] = "Allow feed override,Apply soft limits,N/A,Auto
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
@@ -950,6 +951,8 @@ static status_code_t set_parking_enable (setting_id_t id, uint_fast16_t int_valu
if(settings.parking.flags.deactivate_upon_init)
settings.parking.flags.enable_override_control = On;
+ //setting_remove_elements(Setting_ProbePullUpDisable, mask);
+
return Status_OK;
}
@@ -1951,7 +1954,7 @@ static bool is_setting_available (const setting_detail_t *setting, uint_fast16_t
#ifndef NO_SAFETY_DOOR_SUPPORT
case Setting_DoorOptions:
- available = hal.signals_cap.safety_door_ajar;
+ available = hal.signals_cap.safety_door_ajar || !settings.parking.flags.enabled;
break;
case Setting_DoorSpindleOnDelay:
@@ -2106,7 +2109,7 @@ PROGMEM static const setting_detail_t setting_detail[] = {
{ 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, "Ignore when idle,Keep coolant state on open", NULL, NULL, Setting_IsExtended, &settings.safety_door.flags.value, NULL, is_setting_available },
+ { 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 },
@@ -2287,7 +2290,8 @@ PROGMEM static const setting_descr_t setting_descr[] = {
{ 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, "Enable this if it is desirable to open the safety door when in IDLE mode (eg. for jogging)." },
+ { 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." },
@@ -3518,6 +3522,9 @@ void settings_init (void)
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
diff --git a/state_machine.c b/state_machine.c
index 4af1100..08a12bf 100644
--- a/state_machine.c
+++ b/state_machine.c
@@ -576,16 +576,17 @@ static void state_await_hold (uint_fast16_t rt_exec)
// 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
- if (!settings.safety_door.flags.keep_coolant_on || sys_state == STATE_SLEEP)
+ 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
- if (!settings.safety_door.flags.keep_coolant_on || sys_state == STATE_SLEEP)
+ 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;
}
+ sys.flags.is_parking = false;
break;
default:
diff --git a/stepper.c b/stepper.c
index 7016af5..53868e9 100644
--- a/stepper.c
+++ b/stepper.c
@@ -200,10 +200,11 @@ void st_wake_up (void)
sys.steppers_deenergize = false;
hal.stepper.go_idle(true); // Reset step & dir outputs
- hal.stepper.wake_up();
- if(settings.stepper_enable_delay) // TODO: do not add delay if deenergize is pending?
+ if(!sys.steppers_enabled && settings.stepper_enable_delay)
hal.delay_ms(settings.stepper_enable_delay, NULL);
+
+ hal.stepper.wake_up();
}
// Stepper shutdown
diff --git a/stream.c b/stream.c
index 91fb394..dab8976 100644
--- a/stream.c
+++ b/stream.c
@@ -89,7 +89,7 @@ void stream_register_streams (io_stream_details_t *details)
}
}
-bool stream_enumerate_streams (stream_enumerate_callback_ptr callback)
+bool stream_enumerate_streams (stream_enumerate_callback_ptr callback, void *data)
{
if(callback == NULL)
return false;
@@ -100,7 +100,7 @@ bool stream_enumerate_streams (stream_enumerate_callback_ptr callback)
while(details && !claimed) {
uint_fast8_t idx;
for(idx = 0; idx < details->n_streams; idx++) {
- if((claimed = callback(&details->streams[idx])))
+ if((claimed = callback(&details->streams[idx], data)))
break;
}
details = details->next;
@@ -134,7 +134,8 @@ const io_stream_status_t *stream_get_uart_status (uint8_t instance)
while(details) {
uint_fast8_t idx;
for(idx = 0; idx < details->n_streams; idx++) {
- if(details->streams[idx].type == StreamType_Serial && details->streams[idx].instance == instance) {
+ if(details->streams[idx].instance == instance &&
+ stream_is_uart(details->streams[idx].type)) {
if(details->streams[idx].get_status)
status = details->streams[idx].get_status(instance);
break;
@@ -439,33 +440,38 @@ bool stream_connect (const io_stream_t *stream)
{
bool ok;
- if((ok = stream_select(stream, true)))
+ if((ok = stream && stream_select(stream, true)))
stream_set_description(stream, "Primary UART");
return ok;
}
-static struct {
+typedef struct {
uint8_t instance;
uint32_t baud_rate;
io_stream_t const *stream;
-} connection;
+} connection_t;
-static bool _open_instance (io_stream_properties_t const *stream)
+static bool _open_instance (io_stream_properties_t const *stream, void *data)
{
- if(stream->type == StreamType_Serial && (connection.instance == 255 || stream->instance == connection.instance) && stream->flags.claimable && !stream->flags.claimed)
- connection.stream = stream->claim(connection.baud_rate);
+ connection_t *connection = (connection_t *)data;
- return connection.stream != NULL;
+ if(stream_is_uart(stream->type) &&
+ (connection->instance == 255 || stream->instance == connection->instance) &&
+ stream->flags.claimable && !stream->flags.claimed)
+ connection->stream = stream->claim(connection->baud_rate);
+
+ return connection->stream != NULL;
}
bool stream_connect_instance (uint8_t instance, uint32_t baud_rate)
{
- connection.instance = instance;
- connection.baud_rate = baud_rate;
- connection.stream = NULL;
+ connection_t connection = {
+ .instance = instance,
+ .baud_rate = baud_rate
+ };
- return stream_enumerate_streams(_open_instance) && stream_connect(connection.stream);
+ return stream_enumerate_streams(_open_instance, &connection) && stream_connect(connection.stream);
}
void stream_disconnect (const io_stream_t *stream)
@@ -476,11 +482,12 @@ void stream_disconnect (const io_stream_t *stream)
io_stream_t const *stream_open_instance (uint8_t instance, uint32_t baud_rate, stream_write_char_ptr rx_handler, const char *description)
{
- connection.instance = instance;
- connection.baud_rate = baud_rate;
- connection.stream = NULL;
+ connection_t connection = {
+ .instance = instance,
+ .baud_rate = baud_rate
+ };
- if(stream_enumerate_streams(_open_instance)) {
+ if(stream_enumerate_streams(_open_instance, &connection)) {
connection.stream->set_enqueue_rt_handler(rx_handler);
if(description)
stream_set_description(connection.stream, description);
@@ -539,7 +546,7 @@ void stream_mpg_set_mode (void *data)
stream_mpg_enable(data != NULL);
}
-ISR_CODE bool ISR_FUNC(stream_mpg_check_enable)(char c)
+ISR_CODE bool ISR_FUNC(stream_mpg_check_enable)(uint8_t c)
{
if(c == CMD_MPG_MODE_TOGGLE)
task_add_immediate(stream_mpg_set_mode, (void *)1);
@@ -554,7 +561,7 @@ ISR_CODE bool ISR_FUNC(stream_mpg_check_enable)(char c)
bool stream_mpg_register (const io_stream_t *stream, bool rx_only, stream_write_char_ptr write_char)
{
- if(stream == NULL || stream->type != StreamType_Serial || stream->disable_rx == NULL)
+ if(stream == NULL || !stream_is_uart(stream->type) || stream->disable_rx == NULL)
return false;
// base.flags.is_up = On;
@@ -630,8 +637,11 @@ bool stream_mpg_enable (bool on)
hal.stream.read = mpg.stream.read;
mpg.stream.disable_rx(false);
mpg.stream.set_enqueue_rt_handler(hal.stream.set_enqueue_rt_handler(NULL));
- if(mpg.flags.is_mpg_tx)
+ if(mpg.flags.is_mpg_tx) {
hal.stream.write = mpg.stream.write;
+ hal.stream.write_n = mpg.stream.write_n;
+ hal.stream.write_char = mpg.stream.write_char;
+ }
hal.stream.get_rx_buffer_free = mpg.stream.get_rx_buffer_free;
hal.stream.cancel_read_buffer = mpg.stream.cancel_read_buffer;
hal.stream.reset_read_buffer = mpg.stream.reset_read_buffer;
diff --git a/stream.h b/stream.h
index db6c90d..1844e60 100644
--- a/stream.h
+++ b/stream.h
@@ -131,8 +131,10 @@ typedef union {
uint8_t value;
struct {
uint8_t dtr :1,
+ dsr :1,
rts :1,
- unused :6;
+ cts :1,
+ unused :4;
};
} serial_linestate_t;
@@ -282,7 +284,9 @@ typedef union {
is_usb :1,
linestate_event :1, //!< Set when driver supports on_linestate_changed event.
passthru :1, //!< Set when stream is in passthru mode.
- unused :4;
+ utf8 :1, //!< Set when stream is in UTF8 mode.
+ eof :1, //!< Set when a file stream reaches end-of-file.
+ unused :2;
};
} io_stream_state_t;
@@ -334,7 +338,7 @@ typedef struct {
stream_get_status_ptr get_status; //!< Optional handler for getting stream status, for UART streams only
} io_stream_properties_t;
-typedef bool (*stream_enumerate_callback_ptr)(io_stream_properties_t const *properties);
+typedef bool (*stream_enumerate_callback_ptr)(io_stream_properties_t const *properties, void *data);
typedef struct io_stream_details {
uint8_t n_streams;
@@ -380,6 +384,11 @@ typedef struct {
extern "C" {
#endif
+static inline bool stream_is_uart (stream_type_t type)
+{
+ return type == StreamType_Serial || type == StreamType_Bluetooth;
+}
+
/*! \brief Dummy function for reading data from a virtual empty input buffer.
\returns always -1 as there is no data available.
*/
@@ -404,7 +413,7 @@ bool stream_mpg_enable (bool on);
void stream_mpg_set_mode (void *data);
-bool stream_mpg_check_enable (char c);
+bool stream_mpg_check_enable (uint8_t c);
bool stream_buffer_all (uint8_t c);
@@ -414,7 +423,7 @@ bool stream_enqueue_realtime_command (uint8_t c);
void stream_register_streams (io_stream_details_t *details);
-bool stream_enumerate_streams (stream_enumerate_callback_ptr callback);
+bool stream_enumerate_streams (stream_enumerate_callback_ptr callback, void *data);
bool stream_connect (const io_stream_t *stream);
diff --git a/system.c b/system.c
index 4adf0c2..e986ef7 100644
--- a/system.c
+++ b/system.c
@@ -92,6 +92,7 @@ ISR_CODE void ISR_FUNC(control_interrupt_handler)(control_signals_t signals)
} else {
#ifndef NO_SAFETY_DOOR_SUPPORT
if(signals.safety_door_ajar && hal.signals_cap.safety_door_ajar && !gc_state.tool_change) {
+ sys.flags.is_parking = false;
if(settings.safety_door.flags.ignore_when_idle) {
// Only stop the spindle (laser off) when idle or jogging,
// this to allow positioning the controlled point (spindle) when door is open.
@@ -1254,11 +1255,7 @@ void system_raise_alarm (alarm_code_t alarm)
system_set_exec_alarm(alarm);
else if(sys.alarm != alarm) {
sys.alarm = alarm;
- sys.blocking_event = sys.alarm == Alarm_HardLimit ||
- sys.alarm == Alarm_SoftLimit ||
- sys.alarm == Alarm_EStop ||
- sys.alarm == Alarm_MotorFault;
- state_set(alarm == Alarm_EStop ? STATE_ESTOP : STATE_ALARM);
+ sys.blocking_event = alarm_is_critical(sys.alarm);
if(sys.driver_started || sys.alarm == Alarm_SelftestFailed)
grbl.report.alarm_message(alarm);
}
diff --git a/system.h b/system.h
index e46083c..c373eae 100644
--- a/system.h
+++ b/system.h
@@ -274,7 +274,8 @@ typedef union {
auto_reporting :1, //!< Set to true when auto real time reporting is enabled.
travel_changed :1, //!< Set to true when maximum travel settings has changed.
is_homing :1,
- unused :4;
+ is_parking :1, //!< Set to true when CMD_SAFETY_DOOR is received.
+ unused :3;
};
} system_flags_t;
@@ -331,6 +332,7 @@ typedef struct system {
bool cold_start; //!< Set to true on boot, is false on subsequent soft resets.
bool ioinit_pending;
bool driver_started; //!< Set to true when driver initialization is completed.
+ bool steppers_enabled; //!< Set to true when all steppers are enabled.
bool mpg_mode; //!< To be moved to system_flags_t
signal_event_t last_event; //!< Last signal events (control and limits signal).
int32_t position[N_AXIS]; //!< Real-time machine (aka home) position vector in steps.
diff --git a/utf8.c b/utf8.c
new file mode 100644
index 0000000..f6d509a
--- /dev/null
+++ b/utf8.c
@@ -0,0 +1,148 @@
+/*
+ utf8.c - An embedded CNC Controller with rs274/ngc (g-code) support
+
+ Part of grblHAL
+
+ Copyright (c) 2025 Terje Io
+
+ utf32_to_utf8() is Copyright 2025 Kang-Che Sung, see license below.
+
+ 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
+
+/*
+static enqueue_realtime_command_ptr utf8_base_handler = NULL;
+
+int32_t utf8_decode (const io_stream_t *stream)
+{
+ static int32_t count = 0, utf8_c = SERIAL_NO_DATA;
+
+ if((count && (stream->get_rx_buffer_count()) < count) || (utf8_c = stream->read()) == SERIAL_NO_DATA)
+ return SERIAL_NO_DATA;
+
+ if(utf8_c & 0b11100000) {
+ count = (c & 0b11100000) == 0b11100000 ? (c & 0b00110000) >> 4 : 1;
+ if(c & (0b01000000 >> count)) {
+ count = 0;
+ utf8_c = 0xFFFD;
+ } else
+ utf8_c = c & (0b00111111 >> count);
+ }
+
+ if(count) do {
+
+ int32_t c;
+
+ if(((c = stream->read())& 0b11000000) != 0b10000000) {
+ count = 1;
+ utf8_c = 0xFFFD;
+ } else {
+ utf8_c = (utf8_c << 6) | (c & 0b00111111);
+ }
+ } while(--count);
+
+ return utf8_c;
+}
+
+ISR_CODE bool ISR_FUNC(utf8_insert)(uint8_t c)
+{
+ static int32_t count = 0, utf8_c = 0;
+
+ if((c & 0b11000000) == 0b11000000)
+ count = (utf8_c & 0b11100000) == 0b11100000 ? (utf8_c & 0b00110000) >> 4 : 1;
+ else if(count)
+ count--;
+
+ return count ? false : utf8_base_handler(c);
+}
+
+bool stream_utf8_enable (const io_stream_t *stream, bool enable)
+{
+ if(enable) {
+ if(utf8_base_handler == NULL)
+ utf8_base_handler = hal.stream.set_enqueue_rt_handler(utf8_insert);
+ } else {
+ if(utf8_base_handler) {
+ hal.stream.set_enqueue_rt_handler(utf8_base_handler);
+ utf8_base_handler = NULL;
+ }
+ }
+
+ return true;
+}
+*/
+
+/*
+
+
+Copyright 2025 Kang-Che Sung
+
+MIT License (MIT/Expat)
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/* SPDX-License-Identifier: MIT */
+
+uint16_t utf32_to_utf8 (uint8_t *buffer, uint32_t code_point)
+{
+// assert(code_point <= 0x10FFFF);
+// assert(code_point < 0xD800 || code_point > 0xDFFF);
+
+ if(code_point > 0x10FFFF)
+ return 0;
+
+ uint16_t idx, length = 1;
+ uint32_t first_byte = code_point, mask;
+
+ if(code_point <= 0x7F) {
+ mask = 0b0111111; // We assume ASCII characters appear most frequently.
+ } else {
+ // Find out how many bytes are needed.
+ mask = 0b00111111;
+ do {
+ length++;
+ first_byte >>= 6;
+ mask >>= 1;
+ } while(first_byte > mask);
+ }
+
+ if(length <= 4) {
+ buffer[0] = (uint8_t)(first_byte + (~mask << 1));
+ for(idx = length - 1; idx > 0; idx--) {
+ buffer[idx] = (code_point & 0b00111111) | 0b10000000;
+ code_point >>= 6;
+ }
+ }
+
+ return length;
+}
diff --git a/utf8.h b/utf8.h
new file mode 100644
index 0000000..4e25c4c
--- /dev/null
+++ b/utf8.h
@@ -0,0 +1,24 @@
+/*
+ utf8.c - An embedded CNC Controller with rs274/ngc (g-code) support
+
+ Part of grblHAL
+
+ Copyright (c) 2025 Terje Io
+
+ utf32_to_utf8() is Copyright 2025 Kang-Che Sung, see license in utf8.c.
+
+ 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 .
+*/
+
+uint16_t utf32_to_utf8 (uint8_t *buffer, uint32_t code_point);