Macro Programming Foundations
Macro programming adds variables, arithmetic, and logic to G-code. Instead of hard-coding every coordinate, the machine calculates it. This page introduces the core concepts — variables, IF/GOTO, WHILE loops, and a bolt-circle example — as the entry point to advanced CNC automation.
- Trace variable values through a loop
- Count twelve and eight drilling events
- Explain the limits of input checks
Concept
Basic G-code is static: every coordinate, feed, and depth is written as a fixed number. A bolt circle of 12 holes means 12 X/Y lines. A rectangular pocket means dozens of step-over lines. Macro programming adds the tools of a simple programming language — variables, math operations, conditional logic, and loops — directly into the CNC program.
Variables are written as #1, #2, and so on. You can assign them values, do arithmetic on them, and use them in coordinates. G00 X#1 moves to whatever value #1 currently holds. With a WHILE loop, the machine can drill 12 holes around a circle by computing each position from trigonometry instead of you typing every coordinate.
This page covers the fundamentals. Full macro programming (deep parameterization, probing, custom canned cycles) is an advanced topic built on these basics.
Why It Matters
Consider drilling 12 holes on a bolt circle of radius 50 mm. Without macros, you calculate each hole position by hand:
G81 X50.0 Y0.
X43.3 Y25.0
X25.0 Y43.3
X0.0 Y50.0
... (12 lines total)
With a macro, you write a loop that computes each angle from 0 to 360 in 30° steps:
#1 = 0
WHILE [#1 LT 360] DO 1
#2 = 50 * COS[#1]
#3 = 50 * SIN[#1]
G81 X#2 Y#3 R2. Z-10. F100.
#1 = #1 + 30
END 1
Now change the bolt circle radius from 50 to 60: you edit one number (#2 = 60 * COS[#1]). Change from 12 holes to 8: edit the step from 30 to 45. No recalculating coordinates. This is the power of parameterized programming.
How It Works
Variables
Variables are storage locations named #1 through #n. You assign values, do math, and use them in G-code:
#1 = 10. (assign 10 to #1)
#2 = 20.
#3 = #1 + #2 (#3 = 30)
#4 = #1 * 2 (#4 = 20)
G00 X#1 Y#2 (rapid to X10 Y20)
G01 Z-#3 F100. (feed to Z-30)
Variables can hold coordinates, depths, feed rates, angles, or any numeric value. The # prefix tells the controller "look up the value in this variable" instead of treating it as a literal number.
Arithmetic and Functions
| Operation | Syntax | Example |
|---|---|---|
| Add | #1 + #2 | #3 = #1 + #2 |
| Subtract | #1 - #2 | #3 = #1 - #2 |
| Multiply | #1 * #2 | #3 = #1 * #2 |
| Divide | #1 / #2 | #3 = #1 / #2 |
| Sine | SIN[angle] | #3 = 50 * SIN[#1] |
| Cosine | COS[angle] | #2 = 50 * COS[#1] |
| Square root | SQRT[#1] | #4 = SQRT[#1] |
Conditional Logic: IF / GOTO
IF makes decisions. GOTO n jumps to line number Nn. This is the simplest form of branching:
#1 = 10.
IF [#1 GT 10] GOTO 100 (if #1 > 10, jump to N100)
(something when #1 <= 10)
N100 (continues here)
Comparison operators: EQ (equal), NE (not equal), GT (greater than), LT (less than), GE (greater/equal), LE (less/equal).
Loops: WHILE / DO / END
WHILE repeats a block of code while a condition is true:
#1 = 0
WHILE [#1 LT 360] DO 1
(do something)
#1 = #1 + 30
END 1
The loop starts with #1 = 0. Each pass, it runs the body, then adds 30 to #1. When #1 reaches 360, the condition #1 LT 360 is false and the loop exits. The DO 1 / END 1 pairs define which block belongs to which loop.
Example
Bolt circle macro: 6 holes on radius 50 mm, centered at G54 X0 Y0. Hole depth Z-10, R plane Z2, feed 100 mm/min. Start at angle 0°, advance 60° per hole.
O0036 (BOLT CIRCLE MACRO)
G21 G17 G90 G40 G80
G54
T01 M06 (Ø6 DRILL)
S2000 M03
G00 X0 Y0
G43 H01 Z50. M08
#1 = 0 (angle in degrees, starts at 0)
WHILE [#1 LT 360] DO 1
#2 = 50 * COS[#1] (X = radius * cos(angle))
#3 = 50 * SIN[#1] (Y = radius * sin(angle))
G99 G81 X#2 Y#3 R2. Z-10. F100.
#1 = #1 + 60 (next hole: +60 degrees)
END 1
G80
G00 Z50. M09
M30
Trace: Iteration 1: angle=0, X=50×COS(0)=50, Y=50×SIN(0)=0 → drill at (50,0). Iteration 2: angle=60, X=25, Y=43.3 → drill at (25,43.3). Continues for angles 120, 180, 240, 300. When angle reaches 360, loop exits. Six holes drilled, one loop. To change to 8 holes, change #1 = #1 + 60 to +45. To change radius, edit the 50.
When to Reach for a Macro
A macro is a parameterized program: you feed it a few numbers (radius, hole count, start angle), and it computes every coordinate. You should not reach for a macro when a subprogram or a canned cycle already solves the problem. Reach for a macro when the geometry is defined by a formula rather than a list — a bolt circle, a grid of holes with adjustable pitch, a chamfer that repeats on every corner, a probe routine that measures a bore and writes the offset automatically.
The simplest macro pattern is a WHILE loop over an angle. Start with #1 = 0, step by 30° up to 360°, compute X = R·cos(#1), Y = R·sin(#1), drill the hole, increment #1. The same loop, with #1 as depth instead of angle, becomes a peck-drill loop that steps down 3 mm at a time until the bottom. The point is not the trigonometry — it is that the machine recomputes the pattern every run, so changing R or the hole count is a one-number edit.
Macros read and write system variables: current position, current offset values, probe status, spindle load. This is how probing works: the machine moves the probe toward the wall, reads the machine X at contact, subtracts the known edge position, and writes the result into G54 X. A macro can also alarm itself: IF [#2 LT 0] THEN #3000 = 1 (BAD RADIUS). Self-diagnosing macros stop the machine before it cuts scrap, which is the production advantage over hand-coded coordinates.
Macros are controller-specific. FANUC uses #1–#33 local, #100–#199 common, and system variables like #5021 for current position; Haas uses a similar but not identical scheme; Siemens uses R parameters and Q loops. Do not assume a macro that runs on Haas will run unchanged on FANUC. The trigonometry is universal; the variable numbers and loop syntax are not.
A Working Bolt-Circle Macro
This macro drills 12 holes on a 50 mm radius starting at angle 0, stepping 30° each hole. #1 holds the current angle, #2 and #3 hold X and Y, #4 holds the radius.
O3601 (BOLT CIRCLE, R50)
G21 G17 G90 G94 G54 G80 G40
#4 = 50.0 (radius, mm)
#5 = 30.0 (angle step, deg; change to 45.0 for 8 holes)
IF [#4 LE 0] THEN #3000 = 1 (RADIUS MUST BE >0)
IF [#5 LE 0] THEN #3000 = 2 (STEP MUST BE >0, else loop never ends)
G00 G43 H01 Z50. M08
S1500 M03
G00 X#4 Y0. (rapid to first hole XY at clearance, NO cut yet)
G99 G81 X#4 Y0 R2. Z-15. F120. (drill hole #1 at 0 deg)
#1 = #5 (next angle = one step past hole #1)
WHILE [#1 LT 360] DO 1
#2 = #4 * COS[#1]
#3 = #4 * SIN[#1]
X#2 Y#3 (G81 drills at THIS XY)
#1 = #1 + #5
END 1
G80 G00 Z50. M09
M30
The loop runs while #1 is less than 360. On each iteration, it computes X and Y on the circle and the modal G81 drills at that exact XY. Note: G81 is called once after the first hole's XY is already in position — not before any XY is set. If you issue G81 with no XY on the same block, the controller drills at whatever the current XY happens to be (often the origin), which is the classic "extra first hole" bug. Change #4 to 60 for a larger circle; change 30.0 to 45.0 for 8 holes. One number edits the whole pattern.
Trigger Trace: 12 Holes vs 8 Holes
| Event | #5 = 30° (12 holes) | #5 = 45° (8 holes) |
|---|---|---|
| Pre-position G00 X#4 Y0 | Rapid only, no drill | Rapid only, no drill |
| First G81 X#4 Y0 | Drill hole #1 at 0° | Drill hole #1 at 0° |
| Loop fires at #1 | 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330 (11 drills) | 45, 90, 135, 180, 225, 270, 315 (7 drills) |
| Total G81 drills | 12 | 8 |
The input checks IF [#4 LE 0] and IF [#5 LE 0] run before any axis motion: a non-positive radius or a zero step alarms immediately instead of drilling a circle at the origin or looping forever. These checks detect the named invalid inputs before the first movement; they do not validate every program state, clearance or cutting condition.
Variables, Arithmetic, and Logic
FANUC-style macros use #1–#33 as local variables (for the current call), #100–#199 as common variables (saved across calls), and system variables like #5021 (current machine position). Arithmetic is straightforward: #2 = #1 * 2, #3 = #1 + #2. Trig functions use degrees: COS[#1], SIN[#1], TAN[#1]. Logic: IF[#1 LT 360] GOTO 10, or WHILE[#1 LT 360] DO 1…END 1. The comparison operators are EQ, NE, GT, LT, GE, LE.
Macros read and write offsets: you can measure a bore with a probe, compute the center, and write G54 X automatically. This is how automated setup works. A macro can also stop the machine with #3000 = 1 (message text) if a measured value is out of range. Self-diagnosing macros are the bridge between CNC programming and automated inspection.
Start simple: a WHILE loop over an angle. Do not start with nested IF/WHILE or subprogram calls. The bolt-circle macro above is the first real program most people write. Once it works, you can parameterize radius, hole count, start angle, depth, and feed — but each parameter is one more thing that can be wrong, so add them one at a time and test each.
When Macros Earn Their Keep
Do not write a macro for a one-off part. Macros are for patterns that repeat: bolt circles, hole grids, pockets at varying sizes, probing cycles, parameterized programs. If you will only drill this one hole pattern once, hand-coding the coordinates is faster and safer. If you will drill 20 variants of the same pattern next month, the macro pays for itself the second time.
Macros also let you build in safety. A macro can check that radius is positive, that depth is within travel, that the tool number matches. A hand-coded program has none of that. In a production cell where an operator loads parts and presses cycle start, the macro is the engineer's representative at the machine — it catches mistakes the operator cannot.
Start with the bolt-circle macro. Add one feature at a time: start angle, hole count, radius, depth. Test each on a dry run. Do not write a 50-line macro blind. The best macro is the one you can read and debug at the machine in 30 seconds.
Macro Safety and Testing
A macro that loops forever can move the machine forever. Before running a macro on a real part, test it in single-block with the tool above the part, feed override at 0, and your finger on the feed-hold button. Watch the first iteration: does it compute the right X/Y? Does the loop increment correctly? Does it exit at the right angle? If the first iteration is wrong, stop and fix the macro.
Use #3000 to alarm on bad input. If the radius variable is zero or negative, alarm immediately. If the hole count is not an integer, alarm. If the depth exceeds travel, alarm. A macro that stops with a clear message is better than a macro that drills 12 holes in the wrong place.
Document the macro. Put a comment block at the top: what it does, what variables mean, what the expected inputs are, and what the safe values are. The next person (possibly you, six months later) needs to know what #4 means without reverse-engineering the loop.
Probing and Offsets
A touch probe in the spindle measures the part automatically. The probe touches a surface, records the machine position, and the macro computes the offset. This replaces hand edge-finding. A probing macro can find the center of a bore, set G54 X/Y, and even measure tool length automatically.
The probe does not write G54 directly — it computes the offset and writes it to the work offset register. The macro must account for the probe tip radius (the probe touches a sphere, not a point). If the tip is Ø4, the center is 2 mm from the touched surface. Get the sign right: touching the left edge gives a different sign than touching the right edge.
Probing is faster and more repeatable than hand edge-finding, but it is not magic. The probe must be calibrated (tip diameter, stylus length) on a reference ring. If the calibration is off, every probe-set offset is off. Treat the probe as a measuring instrument, not a setup convenience.
Positive Inputs Are Only the First Check
The example demonstrates two intended settings: a 30-degree step for twelve holes and a 45-degree step for eight holes. A positive step alone does not prove that a different value evenly divides a revolution. For example, 100 degrees would trigger at 0, 100, 200 and 300 degrees, leaving a 60-degree closing gap. That is not an equally spaced four-hole pattern. Keep the teaching input to the two demonstrated choices, or add a separately designed validation rule before offering a general-purpose pattern generator.
Radius validity also differs from travel and clearance validity. A positive 50 mm radius produces X and Y values between −50 and +50 relative to the active work origin; the machine positions additionally depend on that origin. Compare the resulting envelope with actual travel, fixtures, drill length and holder size. The input checks catch the named numerical errors only. They do not certify a setup for unattended operation or replace the first-part verification plan.
Common Mistakes
- Learning macros before mastering basic G-code — you must understand coordinates, offsets, and canned cycles first. Macros build on top of them.
- Forgetting to increment the loop variable — if you omit
#1 = #1 + 60, the loop runs forever at the same angle. Always update the counter. - Controller-specific syntax — Haas uses
#1, FANUC uses#1, Siemens uses different system variables. Check your controller manual. - Using G81 inside a loop without G90 — the variables produce absolute coordinates, but ensure G90 is active so the positions are not incremental.
- Radians vs degrees — most controllers use degrees for SIN/COS. If your results are wildly wrong, check whether the controller expects radians.
Practice
1. What is #1 = 10. used for?
Ans
It assigns the value 10 to variable #1. Later, G00 X#1 moves to X10. The variable acts as a named storage location.
2. Why use a loop instead of writing 12 hole positions by hand?
Ans
Maintainability: change the radius or hole count in one place and the whole pattern updates. No manual trigonometry, no typos in coordinates.
3. You want 8 holes instead of 6 on the bolt circle. What do you change?
Ans
The angle increment from 60 to 45 (360/8 = 45 degrees). The loop automatically runs 8 times: 0, 45, 90, 135, 180, 225, 270, 315.
4. What happens if you forget #1 = #1 + 60?
Ans
The loop condition #1 LT 360 stays true forever (angle never changes). The machine drills the same hole repeatedly until you press feed hold.