CNC Part5 - Macros

12 minute read

This is the 5th part of my portal milling machine sequel. It focuses on handy accessories that make your life as a CNC operator easier, and can lead to more quick and accurate milling results.


This article is about Macros for the CNC that help automate certain repeating operations. It will both cover basic and more sophisticated Macros for many purposes like automatic Z-zeroing, tool length measurement, or tool changes. It will touch some G-code commands and try to help you gain more confidence in your machine.


The following subroutines have been written for RS274 NGC interpreter. I have validated them with EdingCNC as that is the CNC software I happen to be using. I have taken some inspiration from the macro file that both Sorotec and EdingCNC provide along with their machines.

As you might have different Hardware and Software distributors for your machine, these routines might not fit 100%. My intention was more to explain what’s happening in them than to provide a 1-by-1 copy paste template.

You might want to skip reading this article if your jobs are simple enough so you never felt the need to dive into CNC subroutines or in case your setup is 100% complete and you’ll never touch it again.

So what is a Macro?

A Macro or subroutine is a collection of instructions written for the CNC interpreter to perform actions. They are often used to automate recurring operations. On my machine, such a macro starts with a SUB command and ends with an ENDSUB. They likely include branching logic and G-code commands to have the machine do things or even show user dialogs to get the operator’s input.

On my machine, all macros are residing in a single file called macro.cnc that my CNC software will read on startup so it can be executed. Some of the software’s buttons even directly call macros from this file, e.g. for homing.

macro execution in action To demonstrate that this file matters a lot, close your CNC software and open the macro.cnc document with a text editor. Search for Sub user_9 (it should be a routine that doesn’t do relevant stuff), remove its contents and add the line MSG "hello world!" to it. Save your work, start the CNC software, and in the user menu, press button 9 - Voilà!

Starters: A simple macro to detect Tool Length Sensor status

Let’s get going and connect the newly-grown knowledge about macros to a useful example.

The tool length sensor

my tool length sensor A tool length sensor is a device connected to an input of the CNC machine that triggers when touched (e.g. by a tool tip). There are many different designs available, ranging from a very simple microswitch with an enclosure and touch button to high-precision heavy steel sensors with automatic air purge operation to make sure the sensor’s surface is clean.

The tool length sensor is either mounted at the machine bed, on top of the spoilboard, or just placed at another fixed position on the machine. Mine is a very simple switch that I can put on any place of my machine. It has a claimed repeatability of 10µm.

Anyways, the actual magic is done in the CNC software.

Macro task

I want the macro to check whether the tool length sensor is operational (and not stuck). So I verify that the sensor switch’s status is “not triggered” - normally closed. If it was triggered, this could mean the sensor is stuck pushed (happened to me already) or has a broken sensor wire (didn’t happen so far), or just that the sensor is not connected to the machine at all.

Macro code

SUB is_sensor_ok
    IF [sensorStatus == triggered] ;# 5068 == 0 (normally closed)
        DLGMSG "Tool length sensor not connected or already triggered - please check"
        IF [dialogButton == 1]
            IF [sensorStatus == triggered]
                ERRMSG "Tool length sensor input still unexpected - aborting"
            ERRMSG "User abort."

The Macro’s name is is_sensor_ok. I’m checking sensor status and present a dialog message in case something is wrong here. Once the operator presses “OK”, I’m assuming that the problem has been taken care of and try again. If it is still triggered, I’ll abort the routine.

This Macro can be found in all sections below where I need the tool length sensor to measure something - It serves as a guard to not accidentally destroy my machine, the sensor, or the workpiece by interpreting wrong input.

Variable naming

The RS274/NGC language is old. I mean really old. Its first version was released in the late 1950’s. No wonder its parameters (#1 - #5399 is the allowed range) are all numeric both in naming and in the values they are able to store. No type system (like strings, int etc.) and no handy naming that makes understanding a parameter easy like sensorStatus.

It will just be #5068 and you’ll have to remember yourself that the CNC software manufacturer selected this variable to flag the tool length sensor’s status, and that it is boolean with 0 = not triggered and 1 = triggered.

To make the macro code as readable and comprehensible as possible, I refrain from using numerical parameter names in this article. A translation table can be found at the bottom for your convenience.

Using the tool length sensor to get workpiece surface (Z=0)

With this method, you don’t need to manually zero in workpiece surfaces by lowering Z-axis until the tool slightly scratches the surface and then setting workpiece coordinates.

tool length sensor sketch Once the Macro is programmed and set up, just place the tool length sensor on top of your workpiece and jog your machine so it is placed directly above the sensor (not touching yet). Then start the macro. Your machine will now automatically lower its Z-axis slowly until the sensor switches. It will then reverse very slowly until the sensor untriggers. This point is then taken to determine Z-0 which is then set automatically.


What we need before we can write the macro is the tool length sensor’s Z-position zTls at its switching point from being triggered to not triggered. I measured mine with a caliper and noted it down. We need this value so the machine can subtract it from the Z-height when touching off to yield the workpiece surface’s height.

Also, it is important to know how the switch is connected to the machine. The preferred way is “normally closed”, so the switch opens when triggered: triggered = 0. This is the more safe application because a broken wire or lost connection is detected automatically as the circuit breaks.

We’ll also have to determine touch probe forward feed and reverse feed, e.g. touch = 100mm/min, rev = 10mm/min.

The macro also features a more elaborate part: When Z-0 is about to be measured, but tool length is not known, the machine is able to store current position posX, posY in repositioning variables so that the get_tool_length macro can be called directly from here, and later repositionTo to where workpiece Z-0 is being measured.

The subroutine “Z-zero detection”

After switching the spindle off, the program will lower the machine’s Z-axis until it touches the tool length sensor (or until it has travelled down, spindle nose almost touchting the sensor where it would abort, claiming it didn’t find the sensor) via command G38.2. When triggered, it will reverse carefully until the sensor is released. This point is then saved as the new coordinate offset for the Z-axis via G92.


before we start, make sure we’re in the correct state. Tool length should already be determined so future tool changes won’t require re-touching off Z-zero. Plus, we should know that the sensor is properly connected.

SUB measure_workpiece_z0
    IF ![toolLengthStat]
        DLGMSG "WARNING - Please first measure tool length!"
        IF [dialogButton == 1]
            xPosReposition = xPos
            yPosReposition = yPos
            repositionTo = 1
            GOSUB get_tool_length

    GOSUB is_sensor_ok              ;# Check if tool length sensor status is OK

Let’s now program the actual routine by asking the operator whether Z-zeroing shall be performed now. When the CNC runs in simulator mode (without actual hardware connected), Z-zeroing won’t work so we also have to check this.

    DLGMSG "Start Z-Zeroing?"
    IF [dialogButton == 1] AND [operatingMode != simulator]
        M5                          ;#Switch spindle off
        M9                          ;#Switch coolant off
        G53 G38.2 Z[zSpindleTip + 5] F[touch] 
                                    ;# G38.2 = touch toward probe, stop on contact, flag error on fail
                                    ;# fail = 5mm before touching spindle tip
        IF [probeOk == 1]           ;# #5067 == 1 : G38.2 command success
            G38.2 G91 Z20 F[rev]    ;# now reverse slowly to find untrigger point (max. 20mm up)
            G90                     ;# back to absolute coordinates
            IF [probeOk == 1]      
                G00 Z[zTouched]     ;# #5063 Go to Z-axis's probe point	
                G92 Z[zTls]         ;# Set Z-axis's coordinate offset (0) to tool length sensor's height
                G00 Z[zTls + 5]     ;# clear sensor
                ERRMSG "Could not locate sensor untrigger point."
            DLGMSG "Could not locate sensor trigger point. Retry?" 
            IF [dialogButton == 1]  				
                GOSUB measure_workpiece_z0
                ERRMSG "User abort."

Executing the Get tool length macro on my machine

In this video, the machine is configured to return to XY zero when tool measurement has been completed. It would behave the same if I had the tool length measured after a tool change.

Tool change

When there is no macro for tool changes, the machine will pause its job and wait until you manually jogged it to where you perform the tool change, and requires that you re-zero the workiece’s Z0 position due to possibly changed tool length before continuing the job.

This task can easily be automated and the following section guides you how to write a Macro for this.


An optional flag getToolLength could be configuring the tool change macro’s behavior - whether every tool’s length should be measured after a tool change or not. If you have an automatic tool changer, you might not need this to happen.

The following additional parameters are also needed

  • X-Position xToolChg where the tool change takes place (for me it’s xToolChg = xPosTls)
  • Y-Position yToolChg where the tool change takes place (for me it’s yToolChg = yPosTls)
  • re-use zSafety from get_tool_length macro
  • newToolNumber to indicate the requested tool from G-code (or for manual input)
  • currToolNumber to indicate the “old” tool to be replaced
  • toolChangeDone helper flag to indicate whether a tool change has taken place yet

The subroutine “Change Tool”

The following steps are being performed by the macro when a tool change is indicated either by command M06 “Tool change” within the G-code file of a job or triggered manually by the user:

  1. Stop spindle, coolant etc.
  2. If requested tool is the current tool, prompt dialog asking if it should anyways change.
  3. Rapid move Z up (zSafety), then XY to the tool change position
  4. Dialog: State current tool a and request to insert requested tool b
  5. Check if tool change configuration implies tool length determination. If so, call get_tool_length macro.


SUB change_tool
    toolChangeDone = 0
    M5                                              ;# Switch spindle off
    M9                                              ;# Switch coolant off

    IF [operatingMode != simulator]
        TCAGuard off                              ;# tool change area guard: off for tool change

        ;# handle case that tool is already in place
        IF [newToolNumber == currToolNumber]
            DLGMSG "Tool already mounted. Change anyways?"
            IF [dialogButton == 1]
                toolChangeDone = 0
                toolChangeDone = 1

        ;# go to tool change position and prompt to change tool
        IF [toolChangeDone == 0]
            G53 G00 Z[zSafety]                        ;# Go to safety height (machine coordinates)
            G53 G00 X[xToolChg] Y[yToolChg]           ;# Go to tool change position
            DLGMSG "Please mount tool now. Old tool number: " [currToolNumber] 
                    " New tool number: " [newToolNumber]
            IF [dialogButton == 1]
                IF [newToolNumber > 99] OR [newToolNumber < 0]
                    TCAGuard on                       ;# tool change area guard: on for normal job
                    ERRMSG "New tool number implausible."
                toolChangeDone = 1
                ERRMSG "Tool change aborted."

        ;# prompt when complete and optionally call tool length measurement
        IF [toolChangeDone == 1]   
            MSG "Tool change from " [currToolNumber] " to "  [newToolNumber] " complete."
            M6 T[newToolNumber]				      ;# set new tool number

            IF [getToolLength == 1]               ;# config flag 0 = no, 1 = yes
                GOSUB get_tool_length             ;# Measure tool length. Careful: To be called after M6 T !
                GOSUB reposition_spindle

        TCAGuard on                               ;# tool change area guard: on for normal job

Reposition (to saved coordinates)

This is a very short macro that can be called from other subroutines to reposition the machine either to a commanded position or to XY workpiece zero. It could be further enhanced with more repositionTo flag values, e.g. 0 = no repositioning, 1 = custom position, 2 = workpiece zero, 3 = machine zero …


SUB reposition_spindle
    G53 G00 Z[zSafety]                          ;# Go to safety height (machine coordinates)
    IF [repositionTo == 1]
        G00 X[xPosReposition] Y[yPosReposition] ;# Move back to where requested before
        repositionTo = 0                        ;# reset reposition flag and values
        xPosReposition = 0
        yPosReposition = 0
        G00 X0 Y0                               ;# Move back to XY zero (workpiece coordinates)

Table of variables

In the following sections you can find all parameters I used in the macros above.

System parameters

Protected means that these parameters belong to fixed commands or states that are write-protected, and Reserved means parameters have a fix usage within my CNC software (don’t know about other CNC programs).

Variable name Parameter nr. Type Comment
xPos #5001 protected current CNC position
yPos #5002 protected current CNC position
zPos #5003 protected current CNC position
currToolNumber #5008 protected [1…99]
newToolNumber #5011 protected [1…99]
zTouched #5063 protected Z where sensor touched
probeOk #5067 protected 1 = OK
sensorStatus #5068 protected 1 = triggered
operatingMode #5397 reserved 1 = simulator
dialogButton #5398 reserved 1 = OK

Config parameters

Config parameters are the one that are set once and then kept constant as they are tied to the CNC and their geometry.

Volatility of parameters heavily depends on the CNC software solution you’re using, the numbers of your Free parameters might differ. In my software, variables in the range #4000 - #4999 are persisted while all other free and reserved parameters are volatile and/or scoped.

Variable name Parameter nr. Type Comment
triggered #4400 free, persisted 1 = normally open
currToolLength #4501 free, persisted [mm]
lastToolLength #4502 free, persisted [mm]
zSafety #4506 free, persisted Z position for move
xPosTls #4507 free, persisted tls position
xPosTls #4508 free, persisted tls position
zSpindleTip #4509 free, persisted Z zpindle on tls
zTls #4510 free, persisted TLS height [mm]
toolLengthEst #4511 free, persisted [mm]
touch #4512 free, persisted touch feed [mm/min]
rev #4513 free, persisted reverse feed [mm/min]
getToolLength #4520 free, persisted Flag, 1 = Yes
xToolChg #4521 free, persisted tool change position
yToolChg #4522 free, persisted tool change position

Flag parameters

Free parameters can be used as wished by the programmer. Care has to be taken not to accidentally re-use an existing system variable, though.

Variable name Parameter nr. Type Comment
toolLengthStat #3501 free 1 = measured
toolLengthDiff #3502 free [mm]
toolChangeDone #5015 free 1 = Yes
repositionTo #5020 free Flag, 1 = Yes
xPosReposition #5021 free reposition flag
yPosReposition #5022 free reposition flag
toolLength #5024 free [mm]