Skip to content

Adding custom Tools and Modules to µCNC

Paciente8159 edited this page May 23, 2022 · 40 revisions

Jump to section

Adding custom modules to µCNC

NOTE: Future version 1.4.6 (current master) will bring changes to module initializations. 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.

µCNC existing events/delegates

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
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 translates to a function pointer like void (*fp) (void). The exceptions are:

gcode_parse

Input args:

type Description
unsigned char This is the GCode word being parsed (usually 'G' or 'M')
uint8_t This is the GCode word argument converted to uint8_t format (example 98)
uint8_t The parser current error code (STATUS_GCODE_EXTENDED_UNSUPPORTED)
float This is the actual GCode word argument parsed as float. (example 98.1)
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. 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.

gcode_exec

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. 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.

itp_reset_rt_position

Input args:

type Description
float * The new origin that will be fixed as the machine new coordinates

Returns:

nothing

Creating a new event listener

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.

Adding custom tools to µCNC

µ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.

µCNC tool structure

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;

µCNC creating a dummy tool

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 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
    .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.