Skip to content

Commit f0eac9d

Browse files
committed
Improved expression and parameter handling, simplified gcode parser related to this. Fixed typo in OR statement decode.
Added clear of return value on CALL statement and added optional return value expression support to ENDSUB. Improved handling of multiple simultaneous spindles. Still work in progress. For developers: moved user M-code entry point hooks from the HAL structure to the core handlers and changed signature of the check() function to better support valueless words (letters) when parameter support is enabled. Removed deprecated parameter from the validate() call signature.
1 parent 5825b36 commit f0eac9d

File tree

11 files changed

+184
-149
lines changed

11 files changed

+184
-149
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ It has been written to complement grblHAL and has features such as proper keyboa
1313

1414
---
1515

16-
Latest build date is 20241006, see the [changelog](changelog.md) for details.
16+
Latest build date is 20241014, see the [changelog](changelog.md) for details.
1717

1818
__NOTE:__ Build 20240222 has moved the probe input to the ioPorts pool of inputs and will be allocated from it when configured.
1919
The change is major and _potentially dangerous_, it may damage your probe, so please _verify correct operation_ after installing this, or later, builds.

changelog.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
11
## grblHAL changelog
22

3-
<a name="2024106">Build 2024106
3+
<a name="20241014">Build 20241014
4+
5+
Core:
6+
7+
* Improved expression and parameter handling, simplified gcode parser related to this. Fixed typo in `OR` statement decode.
8+
9+
* Added clear of return value on `CALL` statement and added optional return value expression support to `ENDSUB`.
10+
11+
* For developers: moved user M-code entry point hooks from the HAL structure to the core handlers and
12+
changed signature of the _check()_ function to better support valueless words \(letters\) when parameter support is enabled.
13+
Removed deprecated parameter from the _validate()_ call signature.
14+
15+
* Improved handling of multiple simultaneous spindles. Still work in progress.
16+
17+
Drivers:
18+
19+
* STM32F4xx and STM32F7xx: fixed some typos in new timer API. Ref. [issue #192](https://github.com/grblHAL/STM32F4xx/issues/192)
20+
and [issue #18](https://github.com/grblHAL/STM32F7xx/issues/18).
21+
22+
Plugins and templates:
23+
24+
* Some updated for move of core M-code entry points.
25+
26+
---
27+
28+
<a name="20241006">Build 20241006
429

530
Core:
631

core_handlers.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,69 @@ typedef struct {
140140
clear_tool_data_ptr clear;
141141
} tool_table_t;
142142

143+
/*****************
144+
* User M-code *
145+
*****************/
146+
147+
typedef enum {
148+
UserMCode_Unsupported = 0, //!< 0 - M-code is not supported.
149+
UserMCode_Normal, //!< 1 - M-code is supported.
150+
UserMCode_NoValueWords //!< 2 - M-code supports valueless parameter words.
151+
} user_mcode_type_t;
152+
153+
/*! \brief Pointer to function for checking if user defined M-code is supported.
154+
\param mcode as a #user_mcode_t enum.
155+
\returns #UserMCode_Normal or #UserMCode_NoValueWords if M-code is handled, #UserMCode_Unsupported if not.
156+
*/
157+
typedef user_mcode_type_t (*user_mcode_check_ptr)(user_mcode_t mcode);
158+
159+
/*! \brief Pointer to function for validating parameters for a user defined M-code.
160+
161+
The M-code to validate is found in \a gc_block->user_mcode, parameter values in \a gc_block->values
162+
and corresponding parameter letters in the \a gc_block->words bitfield union.
163+
164+
Parameter values claimed by the M-code must be flagged in the \a gc_block->words bitfield union by setting the
165+
respective parameter letters to \a false or the parser will raise the #Status_GcodeUnusedWords error.
166+
167+
The validation function may set \a gc_block->user_mcode_sync to \a true if it is to be executed
168+
after all buffered motions are completed, otherwise it will be executed immediately.
169+
170+
__NOTE:__ Valueless parameter letters are allowed for floats if the check function returns
171+
#UserMCode_NoValueWords for the M-code. The corresponding values are set to NAN (not a number)
172+
if no value is given.
173+
The validation function should always test all parameter values by using the isnan() function in addition
174+
to any range checks when the check function returns #UserMCode_NoValueWords for the M-code.
175+
176+
\param gc_block pointer to a parser_block_t structure.
177+
\returns #Status_OK enum value if validated ok, appropriate \ref status_code_t enum value if not or #Status_Unhandled if the M-code is not recognized.
178+
*/
179+
typedef status_code_t (*user_mcode_validate_ptr)(parser_block_t *gc_block);
180+
181+
/*! \brief Pointer to function for executing a user defined M-code.
182+
183+
The M-code to execute is found in \a gc_block->user_mcode, parameter values in \a gc_block->values
184+
and claimed/validated parameter letters in the \a gc_block->words bitfield union.
185+
186+
\param state as a #sys_state_t variable.
187+
\param gc_block pointer to a parser_block_t structure.
188+
\returns #Status_OK enum value if validated ok, appropriate \ref status_code_t enum value if not or #Status_Unhandled if M-code is not recognized.
189+
*/
190+
typedef void (*user_mcode_execute_ptr)(sys_state_t state, parser_block_t *gc_block);
191+
192+
/*! \brief Optional handlers for user defined M-codes.
193+
194+
Handlers may be chained so that several plugins can add M-codes.
195+
Chaining is achieved by saving a copy of the current user_mcode_ptrs_t struct
196+
when the plugin is initialized and calling the same handler via the copy when a
197+
M-code is not recognized.
198+
*/
199+
typedef struct {
200+
user_mcode_check_ptr check; //!< Handler for checking if a user defined M-code is supported.
201+
user_mcode_validate_ptr validate; //!< Handler for validating parameters for a user defined M-code.
202+
user_mcode_execute_ptr execute; //!< Handler for executing a user defined M-code.
203+
} user_mcode_ptrs_t;
204+
205+
143206
typedef struct {
144207
// report entry points set by core at reset.
145208
report_t report;
@@ -189,6 +252,7 @@ typedef struct {
189252
on_spindle_selected_ptr on_spindle_selected; //!< Called when spindle is selected, do not change HAL pointers here!
190253
on_reset_ptr on_reset; //!< Called from interrupt context.
191254
on_file_open_ptr on_file_open; //!< Called when a file is opened for streaming.
255+
user_mcode_ptrs_t user_mcode; //!< Optional handlers for user defined M-codes.
192256
// core entry points - set up by core before driver_init() is called.
193257
home_machine_ptr home_machine;
194258
travel_limits_ptr check_travel_limits;

gcode.c

Lines changed: 52 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ inline static bool motion_is_lasercut (motion_mode_t motion)
144144
return motion == MotionMode_Linear || motion == MotionMode_CwArc || motion == MotionMode_CcwArc || motion == MotionMode_CubicSpline || motion == MotionMode_QuadraticSpline;
145145
}
146146

147+
inline static bool no_word_value (char letter)
148+
{
149+
return letter == '\0' || (letter >= 'A' && letter <= 'Z') || letter == '$';
150+
}
151+
147152
parser_state_t *gc_get_state (void)
148153
{
149154
return &gc_state;
@@ -348,6 +353,21 @@ void gc_init (void)
348353
grbl.on_parser_init(&gc_state);
349354
}
350355

356+
#if N_SYS_SPINDLE > 1
357+
358+
inline static bool is_single_spindle_block (parser_block_t *gc_block, modal_groups_t command_words)
359+
{
360+
return gc_block->words.s ||
361+
(command_words.G1 && (gc_block->modal.motion == MotionMode_SpindleSynchronized ||
362+
gc_block->modal.motion == MotionMode_RigidTapping ||
363+
gc_block->modal.motion == MotionMode_Threading)) ||
364+
(command_words.G5 && gc_block->modal.feed_mode == FeedMode_UnitsPerRev) ||
365+
command_words.G14 ||
366+
(command_words.M9 && gc_block->override_command == Override_SpindleSpeed);
367+
}
368+
369+
#endif
370+
351371
// Set dynamic laser power mode to PPI (Pulses Per Inch)
352372
// Returns true if driver uses hardware implementation.
353373
// Driver support for pulsing the laser on signal is required for this to work.
@@ -487,54 +507,6 @@ void gc_output_message (char *message)
487507

488508
static ngc_param_t ngc_params[NGC_N_ASSIGN_PARAMETERS_PER_BLOCK];
489509

490-
static status_code_t read_parameter (char *line, uint_fast8_t *char_counter, float *value)
491-
{
492-
char c = *(line + *char_counter);
493-
status_code_t status = Status_OK;
494-
495-
if(c == '#') {
496-
497-
(*char_counter)++;
498-
499-
if(*(line + *char_counter) == '<') {
500-
501-
(*char_counter)++;
502-
char *pos = line = line + *char_counter;
503-
504-
while(*line && *line != '>') {
505-
if(*line == ' ') {
506-
char *s1 = line, *s2 = line + 1;
507-
while(*s2)
508-
*s1++ = *s2++;
509-
*(--s2) = '\0';
510-
} else
511-
line++;
512-
}
513-
514-
*char_counter += line - pos + 1;
515-
516-
if(*line == '>') {
517-
*line = '\0';
518-
if(!ngc_named_param_get(pos, value))
519-
status = Status_BadNumberFormat;
520-
*line = '>';
521-
} else
522-
status = Status_BadNumberFormat;
523-
524-
} else if(read_float(line, char_counter, value)) {
525-
if(!ngc_param_get((ngc_param_id_t)*value, value))
526-
status = Status_BadNumberFormat;
527-
} else
528-
status = Status_BadNumberFormat;
529-
530-
} else if(c == '[')
531-
status = ngc_eval_expression(line, char_counter, value);
532-
else if(!read_float(line, char_counter, value))
533-
*value = NAN;
534-
535-
return status;
536-
}
537-
538510
static int8_t get_format (char c, int8_t pos, uint8_t *decimals)
539511
{
540512
static uint8_t d;
@@ -611,7 +583,7 @@ static void substitute_parameters (char *comment, char **message)
611583
parse_format = 1;
612584
else if(c == '#') {
613585
char_counter--;
614-
if(read_parameter(comment, &char_counter, &value) == Status_OK)
586+
if(ngc_read_parameter(comment, &char_counter, &value, true) == Status_OK)
615587
len += strlen(decimals ? ftoa(value, decimals) : trim_float(ftoa(value, decimals)));
616588
else
617589
len += 3; // "N/A"
@@ -642,7 +614,7 @@ static void substitute_parameters (char *comment, char **message)
642614
fmt[0] = c;
643615
} else if(c == '#') {
644616
char_counter--;
645-
if(read_parameter(comment, &char_counter, &value) == Status_OK)
617+
if(ngc_read_parameter(comment, &char_counter, &value, true) == Status_OK)
646618
strcat(s, decimals ? ftoa(value, decimals) : trim_float(ftoa(value, decimals)));
647619
else
648620
strcat(s, "N/A");
@@ -1006,7 +978,7 @@ status_code_t gc_execute_block (char *block)
1006978
float value;
1007979
uint32_t int_value = 0;
1008980
uint_fast16_t mantissa = 0;
1009-
bool is_user_mcode = false;
981+
user_mcode_type_t user_mcode = UserMCode_Unsupported;
1010982
word_bit_t word_bit = { .parameter = {0}, .modal_group = {0} }; // Bit-value for assigning tracking variables
1011983

1012984
while ((letter = block[char_counter++]) != '\0') { // Loop until no more g-code words in block.
@@ -1034,7 +1006,7 @@ status_code_t gc_execute_block (char *block)
10341006
*s++ = '\0';
10351007
s++;
10361008
char_counter += s - name;
1037-
if((status = read_parameter(block, &char_counter, &value)) != Status_OK)
1009+
if((status = ngc_read_real_value(block, &char_counter, &value)) != Status_OK)
10381010
FAIL(status); // [Expected parameter value]
10391011
if(!ngc_named_param_set(name, value))
10401012
FAIL(Status_BadNumberFormat); // [Expected equal sign]
@@ -1043,13 +1015,16 @@ status_code_t gc_execute_block (char *block)
10431015
} else {
10441016

10451017
float param;
1046-
if (!read_float(block, &char_counter, &param))
1047-
FAIL(Status_BadNumberFormat); // [Expected parameter number]
10481018

1049-
if (block[char_counter++] != '=')
1019+
if((status = ngc_read_real_value(block, &char_counter, &param)) != Status_OK) {
1020+
FAIL(status); // [Expected parameter number]
1021+
} else if(!ngc_param_is_rw((ngc_param_id_t)param))
1022+
FAIL(Status_GcodeValueOutOfRange); // [Parameter does not exist or is read only]
1023+
1024+
if(block[char_counter++] != '=')
10501025
FAIL(Status_BadNumberFormat); // [Expected equal sign]
10511026

1052-
if((status = read_parameter(block, &char_counter, &value)) != Status_OK)
1027+
if((status = ngc_read_real_value(block, &char_counter, &value)) != Status_OK)
10531028
FAIL(status); // [Expected parameter value]
10541029

10551030
if(ngc_param_count < NGC_N_ASSIGN_PARAMETERS_PER_BLOCK && ngc_param_is_rw((ngc_param_id_t)param)) {
@@ -1070,13 +1045,15 @@ status_code_t gc_execute_block (char *block)
10701045
if((letter < 'A' && letter != '$') || letter > 'Z')
10711046
FAIL(Status_ExpectedCommandLetter); // [Expected word letter]
10721047

1073-
if((status = read_parameter(block, &char_counter, &value)) != Status_OK)
1048+
if(user_mcode == UserMCode_NoValueWords && no_word_value(block[char_counter]))
1049+
value = NAN;
1050+
else if((status = ngc_read_real_value(block, &char_counter, &value)) != Status_OK)
10741051
return status;
10751052

10761053
if(gc_state.skip_blocks && letter != 'O')
10771054
return Status_OK;
10781055

1079-
if(!is_user_mcode && isnanf(value))
1056+
if(user_mcode != UserMCode_NoValueWords && isnanf(value))
10801057
FAIL(Status_BadNumberFormat); // [Expected word value]
10811058

10821059
g65_words.value = 0;
@@ -1085,11 +1062,11 @@ status_code_t gc_execute_block (char *block)
10851062
if((letter < 'A' && letter != '$') || letter > 'Z')
10861063
FAIL(Status_ExpectedCommandLetter); // [Expected word letter]
10871064

1088-
if (!read_float(block, &char_counter, &value)) {
1089-
if(is_user_mcode) // Valueless parameters allowed for user defined M-codes.
1090-
value = NAN; // Parameter validation deferred to implementation.
1065+
if(!read_float(block, &char_counter, &value)) {
1066+
if(user_mcode == UserMCode_NoValueWords) // Valueless parameters allowed for user defined M-codes.
1067+
value = NAN; // Parameter validation deferred to implementation.
10911068
else
1092-
FAIL(Status_BadNumberFormat); // [Expected word value]
1069+
FAIL(Status_BadNumberFormat); // [Expected word value]
10931070
}
10941071

10951072
#endif
@@ -1118,7 +1095,7 @@ status_code_t gc_execute_block (char *block)
11181095

11191096
case 'G': // Determine 'G' command and its modal group
11201097

1121-
is_user_mcode = false;
1098+
user_mcode = UserMCode_Unsupported;
11221099
word_bit.modal_group.mask = 0;
11231100

11241101
switch(int_value) {
@@ -1358,7 +1335,7 @@ status_code_t gc_execute_block (char *block)
13581335
if(mantissa > 0)
13591336
FAIL(Status_GcodeCommandValueNotInteger); // [No Mxx.x commands]
13601337

1361-
is_user_mcode = false;
1338+
user_mcode = UserMCode_Unsupported;
13621339
word_bit.modal_group.mask = 0;
13631340

13641341
switch(int_value) {
@@ -1474,8 +1451,8 @@ status_code_t gc_execute_block (char *block)
14741451
break;
14751452

14761453
default:
1477-
if(hal.user_mcode.check && (gc_block.user_mcode = hal.user_mcode.check((user_mcode_t)int_value))) {
1478-
is_user_mcode = true;
1454+
if(grbl.user_mcode.check && (user_mcode = grbl.user_mcode.check((user_mcode_t)int_value))) {
1455+
gc_block.user_mcode = (user_mcode_t)int_value;
14791456
word_bit.modal_group.M10 = On;
14801457
} else
14811458
FAIL(Status_GcodeUnsupportedCommand); // [Unsupported M command]
@@ -1824,7 +1801,7 @@ status_code_t gc_execute_block (char *block)
18241801
if(command_words.M10 && gc_block.user_mcode) {
18251802

18261803
user_words.mask = gc_block.words.mask;
1827-
if((int_value = (uint_fast16_t)hal.user_mcode.validate(&gc_block, &gc_block.words)))
1804+
if((int_value = (uint_fast16_t)grbl.user_mcode.validate(&gc_block)))
18281805
FAIL((status_code_t)int_value);
18291806
user_words.mask ^= gc_block.words.mask; // Flag "taken" words for execution
18301807

@@ -1909,13 +1886,7 @@ status_code_t gc_execute_block (char *block)
19091886

19101887
// [4. Set spindle speed and address spindle ]: S or D is negative (done.)
19111888
if(gc_block.words.$) {
1912-
bool single_spindle_only = gc_block.words.s ||
1913-
(command_words.G0 && (gc_block.modal.motion == MotionMode_SpindleSynchronized ||
1914-
gc_block.modal.motion == MotionMode_RigidTapping ||
1915-
gc_block.modal.motion == MotionMode_Threading)) ||
1916-
(command_words.G5 && gc_block.modal.feed_mode == FeedMode_UnitsPerRev) ||
1917-
command_words.G14 ||
1918-
(command_words.M9 && gc_block.override_command == Override_SpindleSpeed);
1889+
bool single_spindle_only = is_single_spindle_block(&gc_block, command_words);
19191890
if(command_words.M7 || single_spindle_only) {
19201891
if(gc_block.values.$ < (single_spindle_only ? 0 : -1))
19211892
FAIL(single_spindle_only ? Status_NegativeValue : Status_GcodeValueOutOfRange);
@@ -1936,6 +1907,11 @@ status_code_t gc_execute_block (char *block)
19361907
gc_block.words.$ = Off;
19371908
}
19381909
}
1910+
#if N_SYS_SPINDLE > 1
1911+
// For now, remove when downstream code can handle multiple spindles?
1912+
else if(command_words.M7 || is_single_spindle_block(&gc_block, command_words))
1913+
sspindle = &gc_state.modal.spindle[0];
1914+
#endif
19391915

19401916
if(gc_block.modal.feed_mode == FeedMode_UnitsPerRev && (sspindle == NULL || !sspindle->hal->get_data))
19411917
FAIL(Status_GcodeUnsupportedCommand); // [G95 not supported]
@@ -3538,7 +3514,7 @@ status_code_t gc_execute_block (char *block)
35383514
gc_block.values.o = single_meaning_value.o;
35393515
gc_block.values.s = single_meaning_value.s;
35403516
gc_block.values.t = single_meaning_value.t;
3541-
hal.user_mcode.execute(state_get(), &gc_block);
3517+
grbl.user_mcode.execute(state_get(), &gc_block);
35423518
gc_block.words.mask = 0;
35433519
}
35443520

@@ -3960,7 +3936,7 @@ status_code_t gc_execute_block (char *block)
39603936
idx = N_SYS_SPINDLE;
39613937
spindle_t *spindle;
39623938
do {
3963-
if((spindle = &gc_state.modal.spindle[--idx])) {
3939+
if((spindle = &gc_state.modal.spindle[--idx])->hal) {
39643940
spindle->css = NULL;
39653941
spindle->state = (spindle_state_t){0};
39663942
spindle->rpm_mode = SpindleSpeedMode_RPM; // NOTE: not compliant with linuxcnc (?);

gcode.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,6 @@ typedef enum {
231231
__NOTE:__ Not used by the core, may be used by private user code, drivers or plugins.
232232
*/
233233
typedef enum {
234-
UserMCode_Ignore = 0, //!< 0 - Default, must be zero
235234
OpenPNP_SetPinState = 42, //!< 42 - M42
236235
UserMCode_Generic1 = 101, //!< 101 - For private use only
237236
UserMCode_Generic2 = 102, //!< 102 - For private use only
@@ -637,7 +636,7 @@ It will also be passed to mc_jog_execute() and any user M-code validation and ex
637636
typedef struct {
638637
non_modal_t non_modal_command; //!< Non modal command
639638
override_mode_t override_command; //!< Override command TODO: add to non_modal above?
640-
user_mcode_t user_mcode; //!< Set > #UserMCode_Ignore if a user M-code is found.
639+
user_mcode_t user_mcode; //!< Set > 0 if a user M-code is found.
641640
bool user_mcode_sync; //!< Set to \a true by M-code validation handler if M-code is to be executed after synchronization.
642641
gc_modal_t modal; //!< The current modal state is copied here before parsing starts.
643642
spindle_modal_t spindle_modal;

0 commit comments

Comments
 (0)