用户宏程序与子程序入门:用实例和图解学 M98、M99、G65、#变量、IF、WHILE
写一次,改个尺寸就能反复调用。先从 M98 和 G65 的区别看起吧!


形状相同只是尺寸不同的零件、分布在圆周上的螺栓孔、反复出现的槽……这类加工,你是否每次都用 CAM 或手算重新编程?NC 中有能把程序的一部分调用并重复执行的“子程序”,以及可以使用变量、计算和条件分支的“用户宏程序”(有些厂家称为用户宏、宏程序 B(Macro B)等)。本文用动作示意图和程序示例依次讲解 M98、M99、G65、#变量、IF、WHILE,最后还可以用计算器试用螺栓孔圆(分布圆孔)宏程序。
子程序和宏程序能让哪些工作变轻松
- 重复相同的动作:等间隔的槽、形状相同的型腔,只写一处,反复调用(子程序)
- 把尺寸不同的零件合为一个程序:用“自变量”传入直径、深度、孔数,一个程序就能加工不同型号(宏程序)
- 自制固定循环:把螺栓孔圆、角度排列孔、端面铣的往复走刀等做成像 G81 那样一行就能调用的形式[1][6]
- 测量与管理:用测头测得的值修正补偿,统计加工件数后停机,统计刀具使用次数[1][10]
宏程序在研究中也有应用。已有报告提出:把从 3D 模型读取的形状尺寸送入宏程序的自变量、不用 CAM 即可生成加工程序的系统[14],以及不制作专用成形车刀、用普通切槽刀片和宏程序车削非对称梯形螺纹(锯齿形螺纹)的方法[15]。两者都发挥了宏程序“改尺寸只需改自变量”的长处。
在有些 NC 上,用户宏程序是选配功能,可用的变量范围也因机型和选项而异。本文的写法以 FANUC 兼容写法(即所谓宏程序 B)为准,机型不同的地方都随处注明。由于 FANUC 的使用说明书不对外公开,本文内容是根据公开的 Haas、Siemens(ISO 方言模式)、Tornos 等资料确认的。使用前请务必查阅自己机床的说明书。
子程序:用 M98 调用,用 M99 返回
子程序的机制是:把加工内容写在另一个程序号中,用 M98 调用,最后用 M99 返回调用方的下一行[4][5]。
| 写法 | 含义 | 机型差异 |
|---|---|---|
| M98 P1100 | 调用 O1100 一次 | — |
| M98 P51100 | 调用 O1100 五次(P 的后 4 位是程序号,前面的位是次数) | FANUC 兼容写法。Siemens ISO 方言模式的说明书中也相同(例:M98 P30021 调用 21 号程序 3 次)[7] |
| M98 P1100 L5 | 调用 O1100 五次(用 L 指定次数) | Haas 采用这种写法(L 为 1~99)[4] |
| M97 P100 | 调用同一程序中从 N100 开始的部分 | Haas 的本地子程序。放在 M30 之后,以 M99 结束[3] |
| M99 | 返回调用方的下一行 | 在主程序中执行时,返回开头重复执行(直到复位)[5][12] |
| M99 P50 | 返回调用方的 N50 | 返回目标的搜索方向(向前、向后)因机型而异[7] |
示例:切 5 条等间隔的槽
如果子程序内部用增量(G91)编写,每次调用都会在逐步错开的位置上执行相同的动作。要点是最后 先切回 G90,再 用 M99 返回。
O1000 (MAIN) G90 G17 G40 G49 G80 G54 T01 M06 (φ10 立铣刀) S2000 M03 G00 X0. Y-10. G43 Z50. H01 M08 Z2. M98 P51100 (O1100 调用 5 次;Haas 用 M98 P1100 L5) G90 G00 Z50. M09 G91 G28 Z0. M30 O1100 (SLOT 1 PITCH) G91 G00 X15. (横移 15 mm) G01 Z-7. F100 (从 Z2. 下降 7 mm 到 Z-5.) Y40. F200 (切槽 40 mm) G00 Z7. (返回 Z2.) Y-40. (返回槽起点) G90 (切回绝对值后返回) M99
不要在 G91 状态下返回如果在子程序中切换到 G91 后直接用 M99 返回,主程序中返回后的下一行会按增量移动,跑到意想不到的位置。像本例这样在子程序末尾切回 G90,并在主程序返回后的下一行也写上 G90,就是双重保险。
变量:用 #编号存放数值
在宏程序中,像 #1=100. 这样用“#”加编号来表示变量,写成X#1 就会按 X100. 运行。不同的编号范围承担不同的作用。
| 编号 | 类型 | 内容、用法 | 机型差异 |
|---|---|---|---|
| #0 | 空(空变量) | 不含值的特殊变量。只能读取,不能赋值。用#1=#0 可将 #1 恢复为空 | Haas、Tornos、MachMotion 通用[1][8][9] |
| #1〜#33 | 局部变量 | 只在该程序内部使用。用 G65 调用时会新建一组,并存入自变量的值 | Siemens 的 ISO 方言模式不用 #1~#33,而是用 $C_A~$C_Z 这些另外的变量接收自变量[7] |
| #100〜#199 #500〜#999 | 公共变量 | 任何程序都能看到相同的值。用于在调用之间传递数值,或统计加工件数 | 断电后是否保留因机型而异。Haas 两者都保留[1],MachMotion 每次启动都会清除 #100 段[9]。可用范围也因选项而异 |
| #1000〜 | 系统变量 | 读写 NC 内部的数据,如刀具补偿量、工件坐标系、计时器、报警等 | 编号分配因机型差异很大。例如 Haas 的 #2001~ 为刀具长度补偿,#3001 为毫秒计时器,#3011 为年月日[1] |
使用公共变量前先确认编号“空闲”#500 段等公共变量,有时测头、托盘交换等机床厂家内置的程序也在使用。Haas 的说明书提醒:覆盖测头的校准值(#550~#599)后需要重新校准[1]。新使用之前,请在说明书和变量画面上确认空闲的编号。
“空”和“0”不同
用 G65 调用宏程序时,新的局部变量全部变为空,只有调用行中写了地址的对应变量才会存入值[1]。也就是说,漏写的自变量不是 0,而是空。空变量用作坐标时,该轴的指令可能被忽略而“不动”;用于计算时可能被当作 0(具体处理请查阅机型说明书)。因此,通常先对重要的自变量像 IF [#11 EQ #0] GOTO 900 这样检查是否为空,为空就用报警停机,这是常用做法[10][9]。
LinuxCNC(RS274NGC 系)没有“空”,未传入的自变量为 0[11]。若把 FANUC 兼容的宏程序直接搬过去,空值判断将不起作用,请注意。
计算:四则运算与函数
变量中可以存入计算结果。运算顺序用 [ ](方括号)来决定(圆括号 ( ) 是注释,不能使用)。Haas 的例子中, [3*[9+3]] 的结果为 36[6]。
| 写法 | 含义 | 备注 |
|---|---|---|
| #3=#1+#2 - * / | 四则运算 | 先乘除,后加减 |
| SIN[#1] COS[#1] TAN[#1] | 三角函数 | 角度单位是“度”(不是弧度)[8][11] |
| ATAN[#1]/[#2] | 反正切(由两个值求角度) | 写法(/ 或 ,)和结果范围因机型而异[13] |
| SQRT[#1] ABS[#1] | 平方根、绝对值 | — |
| ROUND[#1] FIX[#1] FUP[#1] | 四舍五入、舍去、进位(取整) | 负数的取整方式因系统而异(LinuxCNC 的 FIX[-2.8] 为 -3)[11] |
| EQ NE GT LT GE LE | 比较:= ≠ > < ≥ ≤ | 用于 IF、WHILE 的条件[1][8] |
| AND OR XOR | 逻辑运算 | 组合多个条件[8][13] |
不要用“相等”比较小数计算出的值可能含有 0.0000001 这样的误差,导致 EQ 不成立。Haas 的说明书建议在比较前先用 ROUND 取整[1]。用于统计循环次数的变量,用整数计数比较安全。
G65:传递自变量调用宏程序
G65 :像G65 P9100 X0. Y0. D100. H6. 这样带上字母自变量来调用宏程序。P 为程序号,L 为次数,其他地址的值存入局部变量[2][1]。哪个字母存入哪个变量是固定的(自变量指定 I),G、L、N、O、P 不能用作自变量。
字母顺序与变量编号并不一致的地方(I=#4、D=#7、H=#11 等)容易弄错,因此在宏程序开头用注释写明“哪个自变量用作什么”,以后阅读的人会轻松很多。自变量指定 I 中不会存入值的 #10、#12、#14、#15、#16、#27、#28、#29、#30、#31、#32、#33 ,可在宏程序中用来存放计算的中间结果。
自变量要带小数点没有小数点的自变量,可能按最小单位读取。Haas 的说明书中,在公制设定下写 A1 时,#1 不是 1,而是 0.001 (D、E、F、H、L、M、S、T 除外)[1]。在 Siemens 的 ISO 方言模式中,无小数点数值的读法也随机床设定而变[7]。A1. 这样,自变量务必带上小数点。
与 M98 的区别,以及 G66(模态调用)
- 自变量:G65 可以传递,M98 不能(要传值时使用公共变量)[1][7]
- 局部变量:G65 每次调用都会新建一组,返回后恢复原值。M98 直接使用与调用方相同的变量[1]
- G66:G66 P_ 自变量 … 之后,每执行完一个移动指令就调用一次宏程序。用法是只列出孔的位置,每次都运行同一个加工宏程序。用G67 取消[7][10]
IF、GOTO、WHILE:分支与循环
| 写法 | 动作 |
|---|---|
| GOTO 100 | 跳转到 N100 行(无条件) |
| IF [#1 GT 10.] GOTO 100 | 仅当 #1 大于 10 时跳转到 N100 |
| IF [#1 GT 10.] THEN #1=10. | 仅当条件成立时,执行后面的一条语句(有些老式 NC 不能用 THEN)[8][10] |
| WHILE [#1 LT 5] DO 1 … END 1 | 条件成立期间,重复执行 DO 1 到 END 1 之间的内容[6][8] |
用报警停机:#3000
发现漏写自变量或不可能的值时,像#3000=1 (H WO KAKU) 这样 给 #3000 赋一个编号使机床停止。Haas 中会变为编号加 1000 的报警(#3000=1 即 1001),括号中的注释会显示在画面上[1]。显示方式因机型而异,但作为宏程序的安全装置,在任何机型上都是常用做法[8][10]。
编号相近的 #3006(显示信息并停止)在不同机型上动作各不相同:“暂停并等待重新启动”、“只显示信息而不停止”、“需要复位的报警”[1][8][9]。使用前务必查阅说明书确认。
示例:螺栓孔圆(PCD)宏程序
这个宏程序只需传入中心、PCD、孔数、起始角度,就能钻出在圆周上等间隔分布的孔。内部用 WHILE 按孔数循环,用 COS、SIN 计算各孔的 X、Y,再用 G81 钻孔。Haas 的说明书中也有思路相同的“斜向一排钻孔宏程序”的例子[1]。
(调用侧:先完成换刀、主轴旋转和 G43) G65 P9100 X0. Y0. D100. H6. A0. Z-15. R3. F150. O9100 (BOLT HOLE CIRCLE) (X=#24 Y=#25 中心 D=#7 PCD H=#11 孔数) (A=#1 起始角度 Z=#26 孔底 R=#18 R点 F=#9 进给) IF [#7 EQ #0] GOTO 901 (PCD 为空则停止) IF [#11 EQ #0] GOTO 902 (孔数为空则停止) IF [#1 EQ #0] THEN #1=0. (省略角度则从 0 度开始) #27=0 (当前第几个孔) #28=360./#11 (孔间角度) WHILE [#27 LT #11] DO 1 #29=#1+#27*#28 (本孔角度) #30=#24+#7/2*COS[#29] (本孔 X) #31=#25+#7/2*SIN[#29] (本孔 Y) G90 G99 G81 X#30 Y#31 Z#26 R#18 F#9 #27=#27+1 END 1 G80 M99 N901 #3000=1 (PCD D MISSING) N902 #3000=2 (HOLE COUNT H MISSING)
局部变量中,自变量指定 I 不会存入值的 #27〜#31 被用作计算的存放处。角度从 X 轴正方向起按逆时针计,以度为单位计算。孔与孔之间有压板时,请把 G99 改为 G98,退回到初始点(参见固定循环一览中的 G98、G99)。
不要直接运行此程序只是写法示例。有的 NC 不能用 THEN,有的变量范围或 #3000 的写法不同。输入机床后,请先用单段、空运行(抬起 Z 的状态)逐孔确认位置,并在变量画面上确认 #29~#31 的值与计算一致,然后再加工。在像 Haas 这样通过预读提前完成运算的 NC 上,夹有输出开/关的宏程序可能无法按预期动作(Haas 用 G103 P1 限制预读)[1][6]。
计算器:确认宏程序输出的孔坐标
用与上面宏程序相同的计算,输出各孔的 X、Y 和调用行。请在运行到机床之前用于验算。如果只想由 PCD 求孔坐标,也有 PCD 计算器可用。
常见的 8 种失误
- 忘记给自变量加小数点:
Z-15在有些机型上会被读成 -0.015。自变量应写成Z-15.的形式[1] - 弄错自变量的字母:I 是 #4,D 是 #7,H 是 #11。字母顺序与编号并不一致
- 试图把 G、L、N、O、P 用作自变量:P 会被读作程序号,L 会被读作次数
- 在用 M98 调用的程序中改写局部变量:调用方的 #1~#33 也会改变。若要保护数值,请用 G65 调用
- 子程序在 G91 状态下退出:返回后会按增量移动。请在 M99 之前切回 G90
- 公共变量与其他程序冲突:覆盖了测头或机床厂家宏程序正在使用的编号[1]
- 用 EQ 比较小数:因计算误差而不成立。请先用 ROUND 取整,或用整数计数[1]
- WHILE 的结束条件始终不成立:忘记递增计数变量会造成死循环。一开始请用单段运行确认
常见问题
- 用户宏程序在所有 NC 上都能用吗?
- 很多 NC 上这是选配功能,没有安装时 G65 和 #变量会报警。可用的变量范围也因选项而异。另外,Siemens 在 ISO 方言模式(FANUC 兼容模式)下接受 G65,但接收自变量的变量不是 #1~#33[7]。请先确认自己机床的规格和说明书。
- 子程序中还能再调用子程序吗?
- 可以(多重调用、嵌套)。但能嵌套几层因机型而异,Siemens ISO 方言模式的 M98 视版本为 4 层或 16 层[7],LinuxCNC 为 10 层[12]。这与 WHILE 的嵌套(最多 3 层)是不同的上限。
- 主程序的最后写 M99 会怎样?
- 会回到开头重复执行(直到复位)。常用于反复进行暖机运转[5][12]。加工零件时请注意不要忘记停止。
- 能把宏程序作为自己的 G 代码或 M 代码来调用吗?
- 很多 NC 可以通过参数设置,用特定的 G、M 代码调用宏程序。Haas 可把 O9010~9019 分配给 G 代码,O9000~9009 分配给 M 代码,用 G 代码调用时可以传递自变量[1]。FANUC 的培训中也讲到 G102 这样的自定义 G 代码,以及用 M、T 代码调用宏程序。[10]。
参考资料
- Haas Automation「Mill Operator's Manual 13 - Macros」
- Haas Automation「G65 Macro Subprogram Call」
- Haas Automation「M97 Local Subprogram Call」
- Haas Automation「M98 Subprogram Call」
- Haas Automation「M99 Subprogram Return or Loop」
- Haas Automation“Macro Programming Information”(PDF)
- Siemens“SINUMERIK ISO Milling Programming Manual”(ISO 方言,2007)
- Tornos“Tips & Tricks: B Macro – ISO machines”(PDF)
- MachMotion「Macro B Reference Guide」
- FANUC eACADEMY“在线点播讲座 CNC 用户宏程序”(课程介绍,日文)
- LinuxCNC“G-code Overview”(参数、表达式、函数)
- LinuxCNC“O Codes”(子程序、M98/M99)
- NIST“The NIST RS274NGC Interpreter – Version 3”(NISTIR 6556)
- Nguyen, Phung, Bui“Novel Integration of CAPP in a G-Code Generation Module Using Macro Programming for CNC Application”Machines 8(4), 61(2020)
- Kasabov 等“A Parametric CNC Approach for Buttress Thread Machining”Engineering Proceedings 150, 91(2026)


