CNC Services Northwest

Centroid CNC control sales, service, training and support

Advanced CNC Macro Programming Tips and Techniques for Centroid Controls

Unless otherwise noted, the methods and examples below are for current CNC11 and CNC12 software, on Mill controls.

Moving a Touch Probe

Use the M115, M116, M125 and M126 codes to move a touch probe in custom probing cycles.

Whenever possible, specify a bounding position, even with M115 and M116 codes.

Note that the positions you specify with these codes are either absolute positions or incremental distance, depending on the current G90/G91 state.

For example:

    M105/X2.0/Y3.0 P15 F20
will move towards an absolute position of X2 Y3, at 20 in/min, until the switch on INP15 closes.

In contrast:

    M105/X2.0/Y3.0 P15 F20
will move in a direction of +2" along X, +3" along Y, at 20 in/min, until the switch on INP15 closes.

Also note that M115 and M116 are interchangeable (as are M125 and M126) when bounding positions are provided, because the direction to the bounding position determines the direction of movement.

Use M115 and M116 when you intend to locate a surface with the probe. The bounding position, if given, should be beyond the farthest likely position of the surface.

With M115 and M116, if the movement proceeds all the way to the bounding position without triggering the input (tripping the probe), then the cycle will be cancelled with an Error.

Use M125 or M126 for positioning moves during which no probe contact is expected.

Like M115 and M116, M125 and M126 will stop if the input is triggered (the probe is tripped); but M125 and M126 will then cancel the cycle with an error. This protects against probe breakage in case of an unexpected obstacle.

M115, M116, M125 and M126 are most commonly used with a touch probe, but they can be used with any switch input, or other PLC bit.

The P parameter used with these codes is either positive or negative, depending on whether you are watching for an open switch to close, or watching for a closed switch to open.

To complicate matters, the meaning of positive/negative P values was changed between CNC10 and CNC11. If you are updating CNC10 macros to run on a CNC11 or CNC12 system, you will need to account for this.

Switch Sense for M1x5 and M1x6 Codes

To further complicate matters, the M105 and M106 codes, which are similar to M115 and M116, may also interpret the sign of the P parameter differently.

The behavior for positive and negative P values with the different codes in different software versions is as follows:

M105/M106Move until
open (0 -> 1)
Not allowedMove until
open (1 -> 0)
Move until
close (0 -> 1)
Move until
open (0 -> 1)
Move until
close (1 -> 0)
Move until
close (0 -> 1)
Move until
open (1 -> 0)

Custom Homing Applications

The M105 and M106 codes are useful for axis homing, in situations where the normal M91 and M92 codes are not well suited.

For example, suppose we have a mill X axis which we want to quickly home near the center of travel, regardless of where the axis may be be sitting when the machine is powered up.

We install a dedicated home switch, which will be a normally-open proximity sensor that detects a steel rib that runs from near the mid-point, all the way to the plus limit of travel. We wire it to INP7 on the PLC board. Therefore, we expect INP7 to be open when we are in the minus half of travel, and closed when we are in the plus half.

Our homing macro can contain the following lines for the X axis:

    M105/X P7 F100  ; move X- until INP7 is open
    M106/X P-7 F100 ; move X+ until INP7 is closed
    M91/X           ; run the normal homing sequence

The first line will quickly move us to the minus side of the edge, if we were on the plus side to begin with. If we are already on the minus side (if the sensor is already open) then there will be no movement.

The second line will quickly move us (back) to the plus side of the edge, where the sensor is closed.

The third line will start the usual minus homing sequence: slow-jog speed minus until the switch opens; slow jog plus until the switch closes again; and slow movement further plus until the encoder index pulse comes around.

The complete sequence will end at the first encoder index pulse to the plus (sensor-closed) side of the edge.

This same general approach could be used with a dedicated home switch that is at one end of travel as well, as long as it trips prior to the limit switch.

This approach cannot be used with a home switch that is also a limit, because M105 and M106 will stop with an Error if a designated limit switch is tripped during the movement. Only M91 and M92 are allowed a one-time pass on tripping a limit switch (and then only for the limit switch they are looking for).

If you do home to a dedicated home switch, separate from the limit switch, it is highly desirable that the switch be set up with an extended ramp, so that the home switch remains tripped from its initial trip point, all the way out to the overtravel limit. Otherwise -- if it is physically possible to be inside the limit switches, on either side of the home switch, with the home switch not tripped -- then the homing macro cannot know whether to move plus or minus when seeking the home switch.

You cannot reliably use M115 and M116 in homing sequences, because those codes always attempt to apply software travel limits. They do not recognize that the software limits are not yet valid when machine home has not yet been set, and so will apply invalid restrictions to axis movement.

M105 and M106 do not apply software travel limits, even if the machine has been homed.

Updating Parameters and Static User Variables

In many applications, it is desirable to save control state information either in Machine Parameter values or in static user variables (those CNC variables from #150 to #159).

For example, you might set Machine Parameter 987 (part of the 900-series block set aside for PLC applications) to the desired RPM of a subspindle; or you might use variable #159 to record which tool is currently in the spindle of a router with a rack-type tool changer.

Your custom macros would store new values in the parameter or variable as needed. For example, the code which starts the subspindle might say:

    G10 P987 R3500  ; store desired RPM where the PLC can read it
    M33             ; send subspindle start request to PLC
or the router's tool-changing macro might say:
    #159 = #4120    ; record that the newly requested tool is in the spindle

There are two crucial components missing from both examples above.

First, the parameter or variable should not be updated during a graphic preview of the program, nor should the variable be updated when that line is read during a mid-program search or restart either.

Second, the control's G code parser must be prevented from processing the assignment while reading ahead, pre-processing codes and queuing them up for later execution by the control. If that is allowed, then the Machine Parameters table will get updated early, and the subspindle may start using an RPM value from much later in the program. Also, if the control reads ahead past an upcoming tool change on the router, but the program is then cancelled before that tool change actually takes place, then variable #159 will hold the wrong value: it will think that the later tool is already in the spindle, when in fact the earlier tool is still there. This generally results in a tool changer crash, as the control tries to put away the tool in the wrong (already-occupied) pocket.

The first issue is solved by skipping the assignment during graph and/or search processing. This can be done by checking system variables #4201 and #4202.

The second issue is solved by forcing a wait, so that machine execution is fully caught up to CNC processing (the queue is empty) before doing the update.

With those corrections, the code examples above would look like this:

    IF [#50001]
    IF [#4201==0] G10 P987 R3500 ; store desired RPM where the PLC can read it
    M33             ; send subspindle start request to PLC
and this:
    IF [#50001]
    IF [#4201==0 && #4202==0] #159 = #4120    ; record that the newly requested tool is in the spindle

The code "IF [#50001]" is an idiom. Literally, it means "if INP1 on the PLC is closed, then do nothing". Because it is implicit that any CNC code that is conditioned upon a PLC input point is intended to test that input at the time the code actually runs, the parser concludes that it cannot test the input -- and cannot process any farther in the job -- until machine execution has caught up to that point. Any PLC bit could be used; INP1 is just the common convention.

The CNC system variable #4201 will have a value of 0 if the control is running the job on the machine, and a value of 1 if the control is generating a graphic preview.

System variable #4202 will have a value of 0 if the control is running the codes it is processing, and a value of 1 or greater if it is processing codes leading up to a mid-program start or resume (in these latter cases the codes are not sent to the machine, but instead are used to determine the CNC state at the point where job execution is to begin).

In our examples above, it makes sense to store any new values for Parameter 987 as we come across them in a search, so that when we get to the search point, the correct (latest) value is in place in the parameter table.

It does not make sense to update the tool number in variable #159 in any condition except an actual tool change.

M98, G65 and the Local Variable Stack

M98 and G65 can both be used to call a CNC subprogram.

The most significant difference between the two is that G65 allows arguments (parameters, variables) to be passed in as part of the subprogram call.

The arguments provided with a G65 call are accessible within the subprogram as CNC variables #1 through #33. For example, any value given with the letter 'A' in a G65 call will appear in variable #1 within the subprogram.

Any CNC program or subprogram can use variables #1 through #33 for their own purposes, whether or not they were given values through a G65 call.

However, therein lies a key difference between M98 and G65: a G65 call advances the "stack", so that the #1 - #33 variables in the called subprogram are a fresh set, independent of the #1 - #33 variables in the calling program. An M98 call, in contrast, does not advance the stack. Variables #1 - #33 in the M98-called subprogram are the same ones used by the calling program.

Consider this example:

    ; define a macro subprogram, for later use
      #3 = #1 + #2
      G0 Y#3
    ; main program starts here
    #1 = 10.0
    #2 = 5.0
    #3 = 1.234
    G0 X#3               ; moves X to X1.234
    G65 P9001 A3.2 B0.6  ; macro moves Y to Y3.8
    G0 Z#3               ; moves Z to Z1.234
Because the variables #1, #2 and #3 in the macro subprogram are independent of the ones in the main program, the main program's values are unchanged after the G65 call.

Compare that with the following, identical except that G65 has been replaced with M98:

    ; define a macro subprogram, for later use
      #3 = #1 + #2
      G0 Y#3
    ; main program starts here
    #1 = 10.0
    #2 = 5.0
    #3 = 1.234
    G0 X#3               ; moves X to X1.234
    M98 P9001 A3.2 B0.6  ; A and B do nothing, macro moves Y to Y15.0
    G0 Z#3               ; moves Z to Z15.0
Using M98 instead, the A and B arguments are non-sensical and are ignored. The macro reads and writes the same local variables as the main program. It therefore calculates #3 = 10.0 + 5.0 = 15.0, and moves Y there. When we get back to the main program, local variable #3 has been changed, so the Z move is affected as well.

As a rule, if you want to use #1 - #33 as local or temporary variables, you should call your macro subprograms with G65.

If you have previously worked with older controls, you might have avoided using G65 when you did not need to pass arguments, because the number of nested G65 calls was limited. For example, on early Fanuc controls, you could nest subprogram (M98 and G65) calls up to 20 levels deep, but only four of those levels could use G65: there was only enough memory for four sets of local variables.

The Centroid control allows G65 calls up to 20 levels deep, each with its own set of local variables. Computer memory has come a long way.

Rounding Decimal Values to Whole Numbers

It is often necessary to round fractional values to whole numbers (integers). For example, if you need to bring a rotary table, which may be at any angle, to the nearest whole turn (whole multiple of 360°), then you would want to round its position (converted to turns) to the nearest integer, then convert back to degrees and send the axis to that position.

Beginning with CNC12 version 4.14, you can use the functions FIX and FUP to round down or up to the next integer value. Note that for negative values, FIX still rounds down (thus away from zero) and FUP still rounds up (towards zero).

With CNC12 v4.14 or newer, then, you can round to the nearest integer by first adding 0.5, then rounding down:

    #100 = FIX[#5044/360.0 + 0.5] * 360.0
    G90 G0 A#100

With software prior to CNC12 v4.14, one way to force rounding to an integer is by applying the bit-wise OR operator. Since it only makes sense to perform bitwise operations on integers, CNC11 and CNC12 implicitly convert floating point values to integers before applying OR.

A bitwise OR operation with a second operand of zero (no bits set) returns the first operand unchanged, except for the implicit integer conversion.

When a fractional value is converted to integer using "OR 0", it is always truncated towards zero: positive values are rounded down, while negative values are rounded up. Therefore finding the nearest integer for both positive and negative values requires separate steps:

    IF [#5044 >= 0.0] THEN #100 = [#5044/360.0 + 0.5] OR 0
    IF [#5044 < 0.0] THEN #100 = [#5044/360.0 - 0.5] OR 0
    #100 = #100 * 360.0
    G90 G0 A#100

CNC System Variables in Lathe Software

When writing CNC macros for a lathe, some care is required when accessing axis and position information in CNC system variables.

Internally to the control software, the Z axis is axis #1, and the X axis is axis #2. Externally, the X axis is usually given the first position, and the Z axis second.

In most CNC system variables, X is first.

Also, most lathes are set up to run in Diameter mode, where X axis positions and distances represent part diameter quantities. Internally, the actual X distances are radius amounts: half of the specified diameter amounts.

In most CNC system variables, X values are radius values. Local position in #5041 is an exception: it is a diameter value.
X axis machine position (radius)
Z axis machine position
X axis local position (diameter)
Z axis local position
X axis tool-measuring reference (radius)
Z axis tool-measuring reference
#10000 - #10099
#11000 - #11099
X axis tool offsets (radius)
Z axis tool offsets

Note that, while #5041 returns a diameter value which can be used directly in a G0 or G1 move, #5021 returns a radius value, but G53 moves expect a diameter value for X. Therefore any value read from #5021 must be converted to diameter before use with G53.

Note also that M115, M116, M125 and M126 -- used in probing applications -- expect a radius value when an X axis limiting position is specified. Therefore any position retrieved from #5041 must be converted to radius before it is used as a limit for a probing M code.

CNC Services Northwest Home

Copyright © 2021 Marc Leonard
Last updated 08-Apr-2021 MBL