2. URScript程序结构解析

URScript 程序在控制器中并不是以“一个文件跑到底”的方式存在,而是由若干结构单元组合而成。理解这些结构单元,是后续编写可维护、可复用程序的基础。本节围绕 .script 文件的组织方式、主程序与子程序的关系、def/end 关键字的作用,以及程序从加载到结束的生命周期展开。

一、.script 文件的组织方式

UR 控制器中,一个可被加载执行的程序通常以 .script 为扩展名保存。它本质上是纯文本文件,内部由若干“定义块”组成。每个定义块以 def 开头、以 end 结尾,中间是该程序段的语句。一个 .script 文件可以只包含一个定义块,也可以包含多个定义块,其中第一个定义块通常作为入口程序,其余定义块作为可被调用的子程序。

从工程实践看,.script 文件常见的组织方式有两种:

  • 单文件单程序:整个文件只有一个 def ... end,适合简单任务,例如只做一次抓取放置。
  • 单文件多程序:文件内包含一个主程序和若干子程序,主程序负责流程调度,子程序负责具体动作,例如 pick()、place()、move_home()。

需要特别注意的是,UR 控制器在加载 .script 文件时,会解析其中的所有定义块,但只有被显式调用的程序才会真正执行。因此,文件内定义多个子程序不会自动运行,必须由主程序或其它程序通过名称调用。

二、主程序与子程序

主程序是程序的入口,通常命名为 main 或与任务同名的名称。它负责初始化、流程控制、调用子程序以及异常处理。子程序则是被主程序调用的独立定义块,用于封装重复动作或复杂逻辑。

主程序与子程序之间通过“调用”建立关系。调用时,控制器会暂停当前程序的执行,跳转到被调用程序的第一条语句,执行完毕后返回调用点继续执行。这种关系可以用下面的结构图表示:

.script 文件内部结构与调用关系 example.script 主程序 def main() 初始化:设置速度/加速度 调用 pick() 调用 place() 调用 move_home() 结束 end 子程序 def pick() 移动到抓取点 → 闭合夹爪 end 子程序 def place() 移动到放置点 → 打开夹爪 end 子程序 def move_home() 回到安全位 → 设置关节角 end 调用 调用 调用

图中可以看到,主程序按顺序调用三个子程序,每个子程序内部完成一组具体动作后返回。这种结构让主程序保持简洁,子程序各自独立,便于修改和复用。

三、def/end 关键字

def 和 end 是 URScript 中定义程序块的一对关键字。def 后面跟程序名和参数列表,end 标记该程序块的结束。语法形式如下:

def 程序名(参数1, 参数2, ...):
    语句1
    语句2
    ...
end

程序名必须符合标识符规则:以字母或下划线开头,由字母、数字、下划线组成,不能与 URScript 内置函数重名。参数列表可以为空,也可以包含多个参数,参数之间用逗号分隔。参数在程序内部作为局部变量使用,调用时传入实际值。

一个常见的误区是认为 def 只是“声明”,实际上它同时完成了定义和可调用注册。只要文件被加载,所有 def 块都会被解析并注册为可调用程序。因此,子程序可以定义在主程序之前或之后,调用顺序不受定义位置影响。

下面是一个带参数的子程序示例:

def move_to(x, y, z, speed):
    movel(p[x, y, z, 0, 0, 0], a=1.2, v=speed)
end

调用时传入具体数值,例如 move_to(0.3, 0.1, 0.2, 0.25),程序内部使用这些值完成直线运动。参数化让子程序具备通用性,避免为每个位置单独写一个程序。

四、程序生命周期

URScript 程序从加载到结束,经历若干阶段。理解这些阶段有助于判断程序在何时被解析、何时被调用、何时释放资源。

阶段 发生位置 关键行为
加载 控制器文件系统 读取 .script 文件,解析所有 def/end 块,注册程序名
启动 主程序入口 从第一个 def 块开始执行,初始化变量和运动参数
调用 主程序内部 遇到子程序名时跳转执行,执行完返回调用点
运行 控制器实时层 按语句顺序执行运动、IO、逻辑判断等指令
结束 主程序 end 释放局部变量,程序停止,等待下一次启动

需要强调的是,程序“结束”并不等于控制器断电或文件被删除。程序结束后,定义块仍然保留在控制器中,可以再次启动。只有重新加载文件或删除文件,才会改变已注册的程序集合。

实践提示:在编写多程序文件时,建议将主程序放在文件最前面,子程序按功能分组放在后面。这样在示教器或 PolyScope 中查看时,入口清晰,便于快速定位。同时,子程序命名应使用动词或动宾结构,例如 pick_part()、open_gripper(),避免使用 p1、test 这类含义模糊的名称。

五、一个完整的结构示例

下面给出一个包含主程序和两个子程序的完整 .script 文件示例,用于演示本节所述结构:

def main():
    set_tcp(p[0, 0, 0.15, 0, 0, 0])
    set_speed(0.3)
    pick()
    place()
end

def pick():
    movel(p[0.3, 0.1, 0.2, 0, 0, 0], a=1.2, v=0.25)
    set_digital_out(0, True)
    sleep(0.3)
end

def place():
    movel(p[0.1, 0.3, 0.2, 0, 0, 0], a=1.2, v=0.25)
    set_digital_out(0, False)
    sleep(0.3)
end

该文件加载后,控制器注册 main、pick、place 三个程序。启动 main 后,先设置工具中心和速度,然后依次调用 pick 和 place。每个子程序内部完成运动与 IO 操作,执行完毕后返回主程序,最终到达 main 的 end,程序停止。

从工程角度看,这种“主程序调度 + 子程序执行”的结构,是 URScript 程序组织的基本范式。它让程序逻辑分层清晰,便于调试、复用和后期维护。掌握 .script 文件组织、主程序与子程序关系、def/end 语法以及程序生命周期,是进一步学习 URScript 运动控制、IO 操作和异常处理的前提。