M98 & M99 G-Code: CNC Subprograms

M98 & M99 G-Code: CNC Subprograms & Macro Calls

CNCCookbook’s G-Code Tutorial: Learn GCode

Subprograms and Macro Calls for Code Building Blocks

In the article on Parameterized Programming, we learned how to modify g-code so it can be used in many different situations through the use of variables. You could try copying and pasting your code everywhere to reuse it, but that’s a mess. First thing that happens is you need to change the code, perhaps to fix a bug, but you have copies of it scattered everywhere.

To make g-code convenient for re-use, we need some way of centralizing it and then accessing it from the part program. The G-Code language provides two different methods for doing this: subprogram calls and macro calls. Think of each as a way to access code sequestered in its own little mini-program. You “call” that code, it does its thing, and then it “returns” to your main program to continue executing right after the call. It’s kind of like delegating responsibility to that code snippet.

If you have a big library of powerful snippets, you’ve got the potential for some real productivity enhancement. Even if you’re not trying to reuse code, using calls can make your existing code much smaller and easier to understand. Consider the process of milling a pocket by stepping down several levels and cutting the same toolpath. Why repeat the same path for each level? GibbsCAM generates g-code that generates the path for a level once as a subprogram and then calls it multiple times after setting the z-level deeper each time. Makes for much smaller programs. Pretty cool trick!

M98 and M99 G-Codes: Subprogram Calls

Let’s start out with Subprogram Calls, because they’re a little simpler to understand than Macro Calls, albeit less powerful. A subprogram call is pretty straightforward. First, stick the g-code for the subprogram under its own subprogram number–its own “O” number. Your program might look like this:



N100 (Main program)

N330 (Call Subprogram) M98 P2000

N1000 M2 (Program Stop)

O2000 (Subprogram)

N2000 …

N2130 M99 (Return to Main program)

As you can see, the main program and the subprogram each have their own “O” number. The call to the subprogram is “M98”, which takes a parameter telling it the “O” number where it can find the subprogram. When it hits M98, execution jumps over to the subprogram. When it hits an “M99” in the subprogram, that causes it to return to the main program right where it left off and keep going.

Subprogram Call Syntax

Depending on your controller, there are a number of different syntaxes available for Subprogram calls. Here is G-Wizard CNC Simulator and Editor’s screen for setting up your controller’s syntax:

M98 M99 g-code cnc subprogram

Subprogram and Macro Call Syntax…

While there are a lot of different styles, they’re mostly just slightly different ways of saying the same things:

– Where do I find the subprogram? There is a word (most often the “P” word) that provides the address. That address is typically an “O” word, but many controllers have the option of looking for an “N” word if the O# is not found.

– What about a repeat? It is often convenient to code a repeat on the subprogram in the same line using a different word. For example “L” may tell how many times to call the subprogram before finally continuing to the next line.

– How deeply can I nest subprograms and macros? Your program can call a subprogram, which can call another subprogram, yada, yada. How many levels deep are allowed? Controllers have a limit to this.

– G65 G-Code is a Macro Call, whereas M98 G-Code is a Subprogram Call. More on macro calls in a minute.

– Some controllers allow the M99 G-Code to return to a particular line instead of just returning to where the subprogram was called. I don’t find this to be a good practice because it is confusing. If you wanted to “goto” a line there is a macro “GOTO” for that purpose, but not all controllers have it.

That’s about it. Not so bad. For now, just focus on calling subprograms and returning from them.


Haas Special Subprogram Goodies

It’s not uncommon for a controller manufacturer to have their own “Special Sauce” features that make their controller special. So it is with the Haas Local Subroutine, M97. M97 is a quick and dirty subprogram because you don’t have to bother setting up “O” numbers. You can just call any “N” number with M97 and “poof” it’s a subprogram. Convenient, although other controllers have that option to look for an “N” if the “O” is missing, which takes a little of the wind out of the quick-n-dirty M97’s sails.

Note that Haas will do M98 too, it just has M97 for the quick and dirty calls.

M97 is enabled in GWE by default. If your controller doesn’t support it, go to Setup Post G/M-Codes and disable M97.

Macro Calls

A lot of cool things can be accomplished with M98-style subprogram calls. Once you start using them, one of the first things you’ll notice is you need to pass information to the subprogram. You want to give it parameters, in other words. With what you’ve learned so far, you’re probably ready to do this by plugging values into Common Variables before calling the subprogram. But, if your controller supports Macro Calls, there is a better way.

Macro Calls are initiated with G65 G-Code instead of M98 G-Code (or M97 G-Code on a Haas). Macro Calls have what are called “arguments”. Silly word, nobody is arguing about anything, but all programming languages that have this capability refer to it as “arguments”. Arguments let you use word address format to send information to your macro. Let’s say we have a special custom deep hole drilling cycle we have created. We want to pass it the X and Y coordinates where the hole goes as well as a Z coordinate for how deep to drill. With a custom Subprogram Call, it might look like this:

N100 (Custom Deep Hole Cycle Subprogram Call)

N110 #1 = 2.5 (Load X into #1)

N120 #2 = 3.0 (Load Y into #2)

N130 #3 = 5.4 (Load Z into #3)

N140 M98 P1000 (Call the Deep Hole Cycle)

As you can see, we loaded X, Y, and Z into Local Variables. The subprogram knows which local variables contain which information and can go on about its business after retrieving those values. Now here is what it might look like for a macro call:

N100 (Custom Deep Hole Cycle Macro Call)

N120 G65 X2.5 Y3.0 Z5.4

That’s a whole lot easier on the eyes, isn’t it? Easier to remember too. How does the macro get access to X, Y, and Z?

This happens by a special process where the local variables #1..#33 are kept in what are called “levels”. When I call G65, the current values of all those locals are copied to a level, and any words I use in calling G65 are transferred into the local variables. This table shows how the words are mapped to local variables:

Argument Word
Local Variable

Argument Words and Which Local Variable They’re Transferred to in a Macro Call…

As you can see, each possible argument word has a pre-assigned local variable. If you use X, Y, and Z as we did in our example, their values will be transferred to #24, #25, and #26. Pretty simple to use and very convenient.

Now what about those levels?

Well, as we mentioned above, subprograms and macros can be nested. One can call another. Each time a macro is called, its Local Variables are saved in a level. That way, when execution returns to that macro, the local variables have been saved in a level and they can be restored unchanged. Even though other macro calls may use the same word arguments which map to the same local variables, they have their own copy of those local variables in their own level, so the two don’t interfere. In practice, it takes longer to explain what’s happening with levels than to just use the macro arguments and not have to worry about whether a local variable is getting overwritten by another macro–it’s not.

But, and this is an important “but”, it could be overwritten by a subprogram call. The answer is simply to quit using subprogram calls when you have macros available, or at the least, use variables outside the local variable range and realize they can be overwritten.

Note: A lot of Controllers Don’t Have Macro Calls

Not every controller has the convenience of macro calls. Mach3 doesn’t, for example. But, they are just that–a convenience. You can accomplish everything you really need with ordinary subprogram calls, it’ll just take a little more work is all. Sorry!


Macro and Subprogram “O” Numbers

First thing to know is that “O” numbers don’t have to appear in any particular order in your program file–they just have to be unique. You can’t have two “O1000” entries.

The second thing to note is that on some controllers it is possible to provide protection for some ranges of O numbers. This makes it possible to put standard macros onto a machine that can’t be tampered with. For example, maybe you made a big investment in a set of probing macros and don’t want them to get changed or overwritten. You’ll need to consult your controller manual to see whether it offers protection and if so how it works. For now, just assume that protected macros have “O” numbers up in the higher ranges. For Fanuc, O0001 to O7999 are unprotected, and that’s where you should put your macros.

A Stack Trace for Fanuc Macro Subprograms when G-Code Programming

Recently, I was working through a complex g-code program to find and fix some bugs in G-Wizard Editor. Trying to understand what the heck somebody else’s program is doing and whether or not my own g-code simulator was properly simulating the program often leads to helpful ideas for tools that make G-Code programming easier for everyone. For example, it got me to add the ability to make the simulator run until it hits the next macro command that would cause a branch to some location other than the next line of g-code. This is a really handy way to buzz through programs going from one subprogram to the next, and it corresponds to similar commands in debuggers and simulators for other computer languages.

This time around, I had a program that had lots of subprograms all madly calling around to make a bunch of holes and then thread mill them with NPT threads using helical interpretation. The subprograms were quite clever in how they used the “L” word to repeat the helix 4 times for 90 degree moves to complete each turn, and then 9 more times to get 9 threads milled. But man, there sure were a lot of different little subprograms all calling each other, sometimes with “L” repeats and sometimes not. I was getting lost and found myself wishing for another tool that’s commonly available for other computer languages besides g-code. That tool is called a “stack trace”. It basically shows “who called who”. Here is the newly implemented stack trace for GWE:

G-Code Stack Trace

Stack trace is underlined in red…

It’s a simple little tool. The list of numbers are the line numbers of the different callers. So we can see the following:

– At line 35, the first subprogram was called.

– Within that subprogram, at line 176, another subprogram was called.

– Within that subprogram, at line 183, a third subprogram was called, and this one has a repeat factor of “4”.

This particular g-code part program nested down 4 levels and at one point had two different repeat counts going. I told you it was complicated. This little display made it tremendously easier to keep track of what was going on. The “L” numbers count down as each repeat cycle is executed as well.

If you like to write these sorts of macro subprograms in your g-code, you’ll want to have this tool in your arsenal to help you follow what the part program is doing when you simulate it.

Tip to Convert Regular G-Code into a Subprogram or Macro

I going to wind up this discussion with a tip to help you convert your “regular” g-code into a subprogram or macro.

Let’s say you’ve got g-code that does something useful.  Perhaps it engraves your logo on a part.  You generated it with your CAM software, and now you’d like to have a subprogram that will engrave the logo at any XY coordinate you choose.  What’s the quickest and easiest way to do that?

I’d suggest converting the logo g-code to use relative coordinates.  BTW, G-Wizard Editor has a command that makes that easy–just a few steps.  The chapter on G91 and G90 g code tells how to use GW Editor to do that and also describes how relative vs absolute coordinates work in CNC.

Once you have converted the g-code to relative, wrap it up as a subprogram.  Now, just move to the XY position you want to put the logo and call the subprogram.  Since its coordinates are relative, the engraving will be relative to the starting position.  Don’t gorget to reset the coordinates back to absolute after calling the subprogram, assuming that’s what the rest of the g-code is expecting.


You now have some powerful tools for packing your g-code up as building blocks that can be recombined. Think of it as the ability to create your own custom canned cycles to perform various tasks. With our next chapter, we will introduce Conditions and Looping, which makes it possible for the g-code to make decisions on its own about what to do, and to execute a list of instructions a variable number of times based on those decisions.

Next up we’ll put parameterized programming and subprogram/macro calls together with an example.


1. Pull out your CNC controller manual and configure G-Wizard Editor for how your controller does subprograms and macros.

2. Write a subprogram call that performs a useful task, such as a custom deep hole cycle. Use variables to pass information into the subprogram.

3. Rewrite the same subprogram from Exercise #2 to be a macro call. Use arguments to pass information into the macro call.

Next Article: Conditions and Looping


Recently updated on April 30th, 2024 at 11:13 am