-
Notifications
You must be signed in to change notification settings - Fork 65
Adding custom Tools and Modules to µCNC
Jump to section
IMPORTANT NOTE: Version 1.7 implemented breaking changes to modules declarations. Please check the modules releases to get the right modules for your version repository
NOTE: Version 1.4.6 implemented changes to module initialization. Also additional modules were moved to a new repository
µCNC has implemented a module system that allows the user to perform custom actions that get executed in an event/delegate fashion style similar to what is done with C#. Multiple callbacks functions can be attached to the same event. These modules can be quite useful and perform several things like adding custom custom gcodes to perform actions, or modifying IO states if a given condition is verified. µCNC already has a few useful modules like PID controller, Encoder module, TMC drivers support and custom G/M code support. These modules are already integrated in the Web Config Tool and can be added via this tool.
Without having to modify core code inside µCNC it is possible to listen to several already existing events. Here is a list of current events:
Event name | Enable option | Description |
---|---|---|
gcode_parse | ENABLE_PARSER_MODULES | Fires when a gcode command was not captured/understood by the core parser is trying to parse the code |
gcode_exec | ENABLE_PARSER_MODULES | Fires when a gcode command that was not captured/understood by the core parser is intercepted by gcode_parse event an recognized as an extended gcode command |
gcode_exec_modifier | ENABLE_PARSER_MODULES | Fires prior to gcode execution phase and can be used to modify the parsed command or execute custom actions or issue motions before gcode execution |
grbl_cmd | ENABLE_PARSER_MODULES | Fires when a custom/unknown '$' grbl type command is received |
parse_token | ENABLE_PARSER_MODULES | Fires na custom/unknown token/char is received for further processing |
cnc_reset | ENABLE_MAIN_LOOP_MODULES | Fires when µCNC resets |
rtc_tick | ENABLE_MAIN_LOOP_MODULES | Fires every millisecond. This code runs inside the RTC interrupt. Do not run long routines here. This is for time critical (periodic) tasks. |
cnc_dotasks | ENABLE_MAIN_LOOP_MODULES | Fires on the main loop running. Any repeating task should be hooked here |
cnc_stop | ENABLE_MAIN_LOOP_MODULES | Fires when a halt/stop condition is triggered |
cnc_exec_cmd_error | ENABLE_MAIN_LOOP_MODULES | Fires when an invalid command is received |
settings_change | ENABLE_SETTINGS_MODULES | Fires when a $ setting is changed |
settings_load | ENABLE_SETTINGS_MODULES | Fires when settings are loaded from memory |
settings_save | ENABLE_SETTINGS_MODULES | Fires when settings are saved into memory |
settings_erase | ENABLE_SETTINGS_MODULES | Fires when settings are erased/reset |
protocol_send_cnc_settings | ENABLE_SETTINGS_MODULES | Fires when printing settings values |
protocol_send_cnc_info | ENABLE_SYSTEM_INFO | Fires when printing response to $I command |
send_pins_states | ENABLE_IO_MODULES | Fires when $P command is printing the pins states |
input_change | ENABLE_IO_MODULES | Fires when a generic input pin changes state |
probe_enable | ENABLE_MOTION_MODULES | Fires when the probe is enabled |
probe_disable | ENABLE_MOTION_MODULES | Fires when the probe is disabled |
mc_line_segment | ENABLE_MOTION_CONTROL_MODULES | Fires when a line segment is about to be sent from the motion control to the planner |
Each of these events exposes a delegate and and event that have the following naming convention:
<event_name>_delegate
-> type of a function pointer that defines the function prototype to be called by the event handler
<event_name>_event
-> pointer to the event that contains all the subscribers that are going to be called by the event handler
mod_<event_name>_hook
-> the event handler that is called inside the core code and calls and executes every subscriber callback
For example cnc_dotasks
exposes cnc_dotasks_delegate
and cnc_dotasks_event
that will enable to create an attach a listener that gets called when the event executed inside the core code. This is done by the call to mod_cnc_dotasks_hook
inside cnc_dotasks
main loop function.
Most of these events have no input or output arguments. That translates to a function pointer like void (*fp) (void)
.
The exceptions are:
Input args:
gcode_parse_args_t* - Pointer to a struct of type gcode_parse_args_t. The gcode_parse_args_t struct is defined like this:
typedef struct gcode_parse_args_
{
// This is the GCode word being parsed (usually 'G' or 'M')
unsigned char word;
// This is the GCode word argument converted to uint8_t format (example 98)
uint8_t code;
// The parser current error code (STATUS_GCODE_EXTENDED_UNSUPPORTED)
uint8_t error;
// This is the actual GCode word argument parsed as float. Useful if code has mantissa or is bigger then 255 (example 98.1)
float value;
// The current parser state (struct)
parser_state_t *new_state;
// The current parser words argument values (struct)
parser_words_t *words;
// The current parser GCode command active words/groups
parser_cmd_explicit_t *cmd;
} gcode_parse_args_t;
Returns:
uint8_t - Returns the error. 3 outcomes are possible:
- The parser successfully intercepted and parsed the command (returning STATUS_OK).
- While parsing and recognizing the Gcode word found an error or invalid value trying to parse it and returns an error code.
- Ignores or does not recognize the current word an passes the current parser status error, received as a function argumento (STATUS_GCODE_EXTENDED_UNSUPPORTED) back to the event handler. The event handle recognizes this as the word not being recognized and continues to propagate the parsing by sending it to the next subscriber. In the end if no subscriber intercepts the word command the parser sends an error.
Input args:
gcode_exec_args_t* - Pointer to a struct of type gcode_exec_args_t. The gcode_exec_args_t struct is defined like this:
typedef struct gcode_exec_args_
{
// The current parser state (struct)
parser_state_t *new_state;
// The current parser words argument values (struct)
parser_words_t *words;
// The current parser GCode command active words/groups
parser_cmd_explicit_t *cmd;
} gcode_exec_args_t;
Returns:
uint8_t - Returns the error. 3 outcomes are possible:
- The parser successfully intercepted and parsed the command (returning STATUS_OK).
- While parsing and recognizing the Gcode word found an error or invalid value trying to parse it and returns an error code.
- Ignores or does not recognize the current word an passes the current parser status error, received as a function argumento (STATUS_GCODE_EXTENDED_UNSUPPORTED) back to the event handler. The event handle recognizes this as the word not being recognized and continues to propagate the parsing by sending it to the next subscriber. In the end if no subscriber intercepts the word command the parser sends an error.
Input args:
float* - The float array or pointer with the new origin that will be fixed as the machine new coordinates
Returns:
nothing
Creating a new event listener is easy. All current modules are inside the src/modules
directory but it's not mandatory. It's just a matter of having them organized.
Let's look at an example in the Arduino IDE environment by creating and attaching and event listener to cnc_dotasks.
Add a new file .c to uCNC directory (same has uCNC.ino) and paste this code
//include cnc.h relative path
#include "src/cnc.h"
//preprocessor check if module is enabled
#ifdef ENABLE_MAIN_LOOP_MODULES
//custom function declaration
void my_custom_code(void);
//create a listener of type cnc_dotasks
CREATE_LISTENER(cnc_dotasks_delegate, my_custom_code);
//custom code implementation
void my_custom_code(void)
{
// do something
}
#endif
This created the event listener that is of type cnc_dotasks. The only thing left to do is to attach it so that it gets called when cnc_dotasks is fired.
In the uCNC.ino it's just a matter of adding the listener to the event like this:
Open uCNC.ino
#include "src/cnc.h"
int main(void)
{
//initializes all systems
cnc_init();
// add the listener to cnc_dotasks
ADD_LISTENER(cnc_dotasks_delegate, my_custom_code, cnc_dotasks_event);
for (;;)
{
cnc_run();
}
}
That's it. Your custom function will run inside the main loop.
µCNC tool is a bit different from modules but it's equally straight forward. tool.h file sets a struct with a set of function pointer that can be defined to create a custom tool. In the end you just need to add the tool in cnc_hal_config.h file by attributing it to a TOOL.
The tool struct is like this:
typedef void (*tool_func)(void);
typedef int16_t (*tool_pid_err_func)(void);
typedef void (*tool_pid_upd_func)(int16_t);
typedef int16_t (*tool_range_speed_func)(int16_t ,uint8_);
typedef uint16_t (*tool_get_speed_func)(void);
typedef void (*tool_set_speed_func)(int16_t);
typedef void (*tool_coolant_func)(uint8_t);
typedef struct
{
tool_func startup_code; /*runs any custom code after the tool is loaded*/
tool_func shutdown_code; /*runs any custom code before the tool is unloaded*/
tool_func pid_update; /*runs de PID update code needed to keep the tool at the desired speed/power*/
tool_range_speed_func range_speed; /*converts core speed to tool speed*/
tool_get_speed_func get_speed; /*gets the tool speed/power (converts from tool speed to core speed)*/
tool_set_speed_func set_speed; /*sets the speed/power of the tool*/
tool_coolant_func set_coolant; /*enables/disables the coolant*/
} tool_t;
This is an example for creating a dummy tool. Again all current tools are inside the src/hal/tool/tools
directory but it's not mandatory. It's just a matter of having them organized.
Add a new file .c to uCNC directory (same has uCNC.ino) and paste this code
//include cnc.h relative path
#include "src/cnc.h"
//may be needed (contains the definition of bool)
#include <stdbool.h>
void dummy_tool_startup_code(void)
{
//run code on tool loading
}
void dummy_tool_shutdown_code(void)
{
//run code on tool unloading
}
void dummy_tool_set_speed(uint8_t value, bool invert)
{
//do whatever. value is the absolute speed of the tool from 0 to 255. invert indicates the direction
}
int16_t dummy_tool_range_speed(int16_t value, uint8_t conv)
{
// if conv is 0 convert from GCode S speed to tool IO control speed (to PWM value or other for example), if conv is not convert from IO control speed to GCode S speed
return value;
}
uint8_t dummy_tool_get_speed(void)
{
// this tool will always report speed 0 as an example
return 0;
}
//declares the tool in ROM
//all unused function pointer must be initialized to NULL to prevent unexpected behavior
const tool_t __rom__ dummy_tool = {
.startup_code = &dummy_tool_startup_code, //sets the function pointer
.shutdown_code = &dummy_tool_shutdown_code, //sets the function pointer
.set_speed = &dummy_tool_set_speed, //sets the function pointer
.range_speed = &dummy_tool_range_speed, //sets the function pointer
.set_coolant = NULL, //If you don't need to do nothing in a function you don't need to declare it. Just set the pointer to NULL.
.pid_update = NULL,
.get_speed = &dummy_tool_get_speed}; //sets the function pointer
That's it. The only thing left is to add the tool in cnc_hal_config.h Lets say this will be TOOL5
Open uCNC.ino add this to cnc_hal_config.h
#define TOOL5 dummy_tool
That's it. You can use your tool via M6 T5
command.
µCNC 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. µCNC is distributed WITHOUT ANY WARRANTY.
Also without the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
µCNC Wiki
- Home
- Basic user guide
- Porting µCNC and adding custom HAL
- Customizing the HAL file
- Adding custom Tools and Modules to µCNC
- FAQ
µCNC for ALL MCU
µCNC for AVR
µCNC for STM32F1 and STM32F4
µCNC for SAMD21
µCNC for ESP8266
µCNC for ESP32
µCNC for NXP LPC176x
µCNC for NXP RP2040