-
Notifications
You must be signed in to change notification settings - Fork 57
Adding custom Tools and Modules to µCNC
Jump to section
µ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.
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 going to execute code |
cnc_reset | ENABLE_MAIN_LOOP_MODULES | Fires when µCNC resets |
rtc_tick | ENABLE_PARSER_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_PARSER_MODULES | Fires on the main loop running. Any repeating task should be hooked here |
cnc_stop | ENABLE_PARSER_MODULES | Fires when a halt/stop condition is triggered |
itp_reset_rt_position | ENABLE_MAIN_LOOP_MODULES | Fires when the internal systems reset the machine position |
settings_change | ENABLE_SETTINGS_MODULES | Fires when a $ setting is changed |
send_pins_states | ENABLE_PROTOCOL_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 (used by bltouch module) |
probe_disable | ENABLE_MOTION_MODULES | Fires when the probe is disabled (used by bltouch module) |
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 translate to a void (*fp) (void). The exceptions are:
Input args:
type | Description |
---|---|
unsigned char | This is the GCode word being parsed (example 'G') |
uint8_t | This is the GCode word argument converted to uint8_t format (example 98) |
uint8_t | The parser current error code |
float | This is the actual GCode word argument parsed as float. (example -1.2568) |
parser_state_t * | The current parser state (struct) |
parser_words_t * | The current parser words argument values (struct) |
parser_cmd_explicit_t * | The current parser GCode command active words/groups |
Returns:
uint8_t - Returns the error. This can inform the handler if the custom parser successfully intercepted and parsed the command, if while parsing found an error, or ignores it and passes to the next custom parser listening to the event.
Input args:
type | Description |
---|---|
parser_state_t * | The current parser state (struct) |
parser_words_t * | The current parser words argument values (struct) |
parser_cmd_explicit_t * | The current parser GCode command active words/groups |
Returns:
uint8_t - Returns the error. This can inform the handler if the custom parser successfully intercepted and parsed the command, if while parsing found an error, or ignores it and passes to the next custom parser listening to the event.
Input args:
type | Description |
---|---|
float * | 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 uint8_t (*tool_func_getspeed)(void);
typedef void (*tool_spindle_func)(uint8_t, bool);
typedef void (*tool_coolant_func)(uint8_t);
typedef int16_t (*tool_pid_err_func)(void);
typedef void (*tool_pid_upd_func)(int16_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_spindle_func set_speed; /*sets the speed/power of the tool*/
tool_coolant_func set_coolant; /*enables/disables the coolant*/
tool_func_getspeed get_speed; /*gets the tool speed/power*/
tool_pid_upd_func pid_update; /*runs de PID update code needed to keep the tool at the desired speed/power*/
tool_pid_err_func pid_error; /*runs de PID update code needed to keep the tool at the desired speed/power*/
} 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
}
uint8_t laser1_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
.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 functions are executed by the PID0 of PID module
//PID module must be enabled to run PID for a tool
#if PID_CONTROLLERS > 0
.pid_update = NULL,
.pid_error = NULL,
#endif
.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