3. RAPID程序结构解析
RAPID 程序不是把指令从头写到尾的线性脚本,而是一套由模块(Module)承载、由例行程序(Routine)组织、由主程序(main)驱动、由中断程序(TRAP)响应异步事件的层次化结构。理解这套结构,是写出可维护、可复用、可排障的工程化机器人程序的前提。很多现场程序“能跑但改不动”,根源往往不是算法问题,而是结构分层从一开始就没有设计好。
一、模块:RAPID 的编译与存储单元
模块是 RAPID 中最外层的程序容器,也是控制器编译、加载和存储的基本单位。一个模块内部可以包含数据声明、例行程序、中断程序、以及模块级的常量与变量。模块分为两类:程序模块(Program Module)和系统模块(System Module)。程序模块由用户创建和修改,随程序一起保存;系统模块通常由系统预置或由选项自动生成,例如 RobotWare 的 BASE 模块,用户一般不应随意改动。
模块的工程意义在于“边界”。把与同一工艺相关的数据、例行程序和中断程序放进同一个模块,就形成了一个高内聚的单元。例如焊接项目可以拆成 MainModule、WeldModule、GripModule、SafetyModule 四个模块,分别负责主流程、焊接工艺、夹具控制和安全逻辑。这样做的直接好处是:修改焊接参数时只需打开 WeldModule,不会误碰夹具逻辑;多人协作时也可以按模块分工,减少冲突。
模块之间通过全局数据和例行程序调用交互。需要注意的是,RAPID 中变量有作用域之分:在模块内声明的变量默认是模块级(局部),只有用 VAR 或 PERS 声明为全局变量,才能被其他模块访问。工程上应尽量少用全局变量,优先通过例行程序的参数传递数据,降低模块间的耦合。
二、例行程序:功能封装的基本单位
例行程序是 RAPID 中可被调用的功能单元,分为三类:函数(FUNCTION)返回一个值,过程(PROC)不返回值,中断程序(TRAP)由中断触发执行。日常编程中绝大多数是 PROC,例如 PROC PickPart()、PROC WeldSeam()。
例行程序的价值在于“一次定义、多次调用”。一个设计良好的例行程序应该满足:职责单一、参数清晰、不依赖隐藏的全局状态。例如下面这个取件过程,把目标位置和夹爪动作都参数化,而不是把坐标写死在过程内部:
PROC PickPart(robtarget pPick, num nGripDelay)
MoveJ pPick, v1000, z50, tool0;
SetDO doGrip, 1;
WaitTime nGripDelay;
SetDO doGrip, 0;
ENDPROC
调用时只需 PickPart(pPick1, 0.3); 或 PickPart(pPick2, 0.5);,同一段逻辑就能服务多个工位。如果后续夹爪延时需要调整,只改调用处的参数即可,不必进入过程内部。这就是“参数化”带来的可维护性。
例行程序还支持递归调用和前向声明。当两个过程互相调用时,需要先用 PROC 声明原型,否则编译器会报“未定义”错误。工程上应尽量避免复杂的相互调用,保持调用关系呈树状,便于阅读和排障。
三、主程序 main:程序的唯一入口
每个 RAPID 程序有且只有一个 main 例行程序,它是程序启动后的第一个执行点。控制器从 main 开始,按顺序执行其中的指令,直到遇到 Stop、Exit 或程序结束。因此 main 的职责不是“干活”,而是“调度”——它应该清晰地表达整个程序的执行顺序和分支逻辑。
一个常见的工程化 main 结构如下:先做初始化(回原点、复位信号、检查安全条件),再进入主循环,循环内依次调用各个功能过程,最后处理退出逻辑。这样写的好处是:主程序短小、逻辑一目了然,具体动作都封装在子过程里。当需要增加一个工位时,只需在循环中增加一次过程调用,而不是在主程序里插入几十行运动指令。
main 控制在 50 行以内。如果主程序超过这个规模,通常说明该拆分的功能没有拆分,或者该用状态机的地方用了顺序堆叠。四、中断程序 TRAP:异步事件的响应机制
中断程序用于处理“随时可能发生、且需要立即响应”的事件,例如安全门被打开、急停按钮被按下、传感器检测到异常、或外部设备请求机器人暂停。与普通过程不同,TRAP 不是被主动调用的,而是通过 CONNECT 语句与某个中断号绑定,当中断条件满足时由系统自动触发。
典型的中断使用流程是:先用 CONNECT 把中断号与 TRAP 绑定,再用 ISignalDI 或 ITimer 等指令定义触发条件,最后用 IDelete 或 ISleep 管理中断的启用与禁用。例如:
CONNECT iTrapDoor WITH TrapDoorOpen;
ISignalDI diDoorOpen, 1, iTrapDoor;
TRAP TrapDoorOpen()
StopMove;
SetDO doAlarm, 1;
WaitTime 0.5;
SetDO doAlarm, 0;
ENDTRAP
这里 StopMove 会立即停止当前运动,TrapDoorOpen 中的逻辑在中断上下文中执行。需要特别注意的是:TRAP 中应尽量只做“紧急、简短、确定”的动作,不要在里面写复杂的运动指令或长时间等待,否则会阻塞正常程序,甚至引发不可预期的行为。中断程序执行完毕后,程序会回到被中断的位置继续执行(除非在 TRAP 中显式改变了程序流程)。
五、工程化代码分层设计
把上述四个概念组合起来,就形成了 RAPID 程序的工程化分层。推荐的分层思路是:主程序层负责流程调度和状态判断;工艺过程层负责具体动作序列,如取件、焊接、放置;设备控制层负责与 PLC、夹具、焊机等外设的信号交互;数据层集中管理点位、速度、延时等参数;中断与安全层独立处理异常和安全事件。
下面这张图展示了这种分层结构以及各层之间的调用与数据关系。图中左侧是模块边界,右侧是各层内部的典型内容,箭头表示调用方向和数据流向。
这张图的核心信息是:调用方向自上而下,数据被各层共享,中断可以打断任意层。在实际项目中,模块的划分不必与图中的层一一对应,但“主程序只调度、过程只干活、数据集中管、中断独立写”这个原则应当保持一致。
六、一个可参考的模块划分示例
假设一个搬运码垛项目,包含取件、视觉定位、码放、安全门监控四个功能。可以按如下方式划分模块与例行程序:
| 模块 | 主要例行程序 / 中断程序 | 职责 |
|---|---|---|
| MainModule | main() | 初始化、主循环、调用取放过程、异常分支 |
| PickModule | PickPart()、OpenGrip()、CloseGrip() | 取件动作序列与夹爪控制 |
| PlaceModule | PlacePart()、CalcStackPos() | 码放位置计算与放置动作 |
| DataModule | —(仅数据声明) | robtarget、speeddata、num 等参数集中定义 |
| SafetyModule | TRAP TrapDoorOpen()、TRAP TrapEStop() | 安全门、急停等异步事件响应 |
这种划分下,main 中只需要写类似 PickPart(pPick, 0.3); PlacePart(pPlace, 2); 的调用,具体动作细节都在各自模块中。当码放层数从 3 层增加到 5 层时,只需修改 PlaceModule 中的 CalcStackPos(),主程序和取件逻辑完全不受影响。
总结来说,RAPID 程序结构解析的关键不在于记住“模块、例行程序、main、TRAP”这四个名词,而在于理解它们各自承担的角色:模块是边界,例行程序是封装,main 是调度,TRAP 是应急。把这四者按工程化分层组织起来,程序才能从“能跑”走向“好改、好查、好复用”。