
我第一次接触URScript是在接手一条流水线机器人调试任务的时候。当时产线上那台UR10已经装好了示教器里走的是PolyScope图形编程每个点位、每段速度都要在界面上一个个点过去改一次轨迹就是一个下午。后来有老师傅扔给我一句话你要嫌麻烦就去写URScript。那一行脚本后来帮我省掉了大量重复点击也让我真正意识到UR机器人表面上是一套图形化示教系统骨子里其实是一台随时可以输入代码的工业计算机。URScript是Universal Robots优傲机器人内置的脚本编程语言专门用来控制机器人的运动逻辑、I/O信号、位姿运算、流程控制以及交互通讯。它和示教器上的图形化编程不是替代关系而是互补关系图形界面适合做点位示教和简单流程的快速部署URScript则适合做批量点位计算、复杂逻辑判断、对外通讯、运动学计算以及需要实时响应的控制场景。如果你在自动化集成公司做过一段时间、总被工艺工程师改动作节拍烦到或者你想搞明白UR机器人到底是怎么被控制起来的这篇文章就是给你写的。我会从URScript在整体控制系统里的位置讲起再把语法、常用指令、实操调试方法和常见坑一个个拆开保证看完之后你能独立写出一段能在真机上跑的脚本而不是只在文档里认识几个函数名。1. 内容整体设计与思路拆解1.1 URScript在整个控制体系里处于什么位置UR机器人的软件体系从外到内大致可以分成四层最上层是示教器上的PolyScope图形界面也就是你日常拖拽、记录点位、配置I/O的地方第二层是URScript脚本层它负责把逻辑、运动、信号、位姿计算翻译成机器人能执行的动作序列第三层是底层运动控制引擎负责轨迹规划、伺服周期插补、动力学监控最底层是伺服驱动器和电机硬件。PolyScope里的每一个图形节点最终都会被转换成一串URScript指令发送到控制引擎里执行。反过来你在URScript编辑器里写脚本、或者用TCP/IP端口往机器人发送脚本代码效果就相当于绕过PolyScope的操作界面直接和运动引擎对话。想清楚这个层级关系你就明白两件事第一URScript并不是什么冷门边角功能它才是UR机器人控制层的通用语言第二很多时候你在示教器上配了半天图形节点做不出来的效果本质是受限于图形化组件不够灵活而不是机器人做不到。比如要根据产量动态计算避让点、要在运动过程中根据传感器读数实时调整目标位置、要按字符串协议和工控机通信这些用URScript都能实现但在PolyScope里往往很难另外搭建出合适的图形节点。1.2 为什么不直接学Python、CURScript有什么不可替代的地方URScript的语法长得有一点像Python支持缩进结构、函数定义、if/else/while/for流程控制还有变量和列表、字典但它不是完整Python。它没有Python标准库也没有操作系统级的文件、网络、多线程能力它的运行环境是针对工业机器人实时控制裁剪过的。我的理解是你不用把它当成一门通用编程语言来学你只要把它当成控制器的遥控器就行。URScript里每个内置函数名几乎都是动词化的运动指令比如movej是关节运动movel是直线运动speedl是速度控制servoj是伺服跟踪force_mode是力控模式。它们直接作用于机器人实时控制回路和普通编程语言里调用函数处理数据完全是两码事。还有一点很实际URScript脚本可以写进startup节点、可以放在主程序开头也可以写到URCap插件里甚至可以通过socket从外部实时推送一段脚本到机器人上执行。这种灵活性让UR机器人在集成项目里变得特别好伺候你可以在不改动示教器交互界面的前提下用外部电脑以软件接口的方式一键切换工艺程序。对于做集成开发和设备联调的人来说这是真正的生产力。2. 核心细节解析与实操要点2.1 脚本编辑入口和运行方式先解决一件基础但很多人卡壳的事URScript在哪里写、在哪里运行。最直观的入口是示教器PolyScope里的URScript程序节点。你新建一个程序左侧栏里找到URScript节点拖进程序树点开就能写脚本。这个节点适合做小段逻辑比如变量初始化、位姿计算和信号置位。但它有个典型限制如果你在里面写一个while True死循环界面可能会卡住机器人的实时线程也会一直被占用所以示教器上的URScript节点一般用来做短、快、离散的逻辑。第二个入口是仪表盘Python编辑器这个适合写带网络通信、文件读写、更复杂数据处理的功能脚本。不过我要说清楚仪表盘脚本运行在UR的Linux后台环境叫Dashboard Server它并不运行在机器人运动控制实时线程里很多运动指令在仪表盘脚本里不能直接用。它负责的是机器人状态查询、加载程序、解锁、弹窗提示这类上层逻辑。第三个入口才是真正干活的通过端口30001、30002、30003发送URScript代码或者通过Dashboard端口29999发送系统指令。30003端口是Primary Client Interface它既能收脚本也能持续反馈机器人的状态数据包是集成开发里最常用的方式。外部电脑用socket往30003端口发一段movej指令机器人立刻执行这是UR即发即用的命令通道。做视觉引导、上位机控制、MES对接基本都是走30003。2.2 数据类型、变量和位姿运算看懂这张地图URScript里的核心数据类型就几种数字、字符串、列表、布尔值、位姿pose、关节位置joint以及后端运行时的一些结构体。位姿是我认为最应该优先搞懂的概念。UR机器人用三维坐标加旋转向量来表示空间位姿写法是p[x, y, z, rx, ry, rz]其中rx、ry、rz是旋转向量分量不是欧拉角也不是四元数。如果你要用欧拉角来做位姿计算URScript提供了rotvec2rpy和rpy2rotvec专门在旋转向量和欧拉角之间互相转换。刚开始容易在这上面犯错比如直接拿rx、ry、rz当欧拉角去算角度偏差算出来的结果怎么看怎么不对劲。关节位置数据类型的写法是q[0.1, -0.5, 1.2, 0.3, 0.6, 0.9]分别是六个关节的弧度值。机器人在运行时当你用get_actual_joint_positions()拿到的是关节弧度不是角度这一点在输出日志、调试打印的时候非常容易看懵。我见过不止一个新手看到负的关节值就以为机器人报错了实际上那只是弧度制下的正常读数。位姿运算函数是URScript让我觉得最实用的地方。常用的包括pose_trans坐标系变换、pose_inv位姿求逆、pose_add位姿相加、pose_sub位姿相减、pose_mult位姿点乘矩阵。我用一个具体例子说明为什么这些函数值钱假设你拿相机标定出工件在相机坐标系下的位置又要转成机器人基坐标系下的目标位置这个换算在数学上就是一个坐标变换链。# 已知相机到机器人基座的位姿为cam_to_base # 已知工件在相机坐标系下的位姿为part_in_cam # 求工件在机器人基坐标系下的位姿 part_in_base pose_trans(cam_to_base, part_in_cam)一行命令解决坐标变换这就是URScript的效率。用PolyScope图形化做这种变换得额外安装专门的节点插件还不一定顺手。2.3 运动指令与实时控制指令怎么挑URScript里的指令大致可以分成三类点位运动、速度控制、伺服控制再加上力控相关指令。点位运动类是最常用的代表指令有movej关节运动六个关节从当前角度运动到目标角度适合大范围快速到位轨迹不要求直线。movel线性运动TCP末端走直线适合路径有直线要求的工艺。movec圆弧或圆周运动适合画弧、焊接等。movepPose运动一般用于姿态精度要求高、且不需要连续路径的场合。movel_pose和movej_pose还有变体指令可以在Movel运行完之后直接获得位姿数据。速度控制类指令里speedl是比较常用的一种它让机器人按照设定的直角坐标速度向量持续运动直到收到停住指令或下一个运动指令。在视觉引导追踪中经常会用到。这里有个坑speedl是持续执行指令它不是一步到位必须在循环里不断刷新速度向量而且要配合中断指令去退出否则机器人就会一直朝那个方向跑。伺服控制类指令中servoj是最常见的。它让机器人每个控制周期默认约8ms都刷新一次目标关节位置适合做视觉引导的动态跟踪和力位混合控制。不过servoj要想跑得稳通常建议配合lookahead_time和gain参数调整。lookahead_time一般推荐0.1~0.2gain大概300~800具体数值要按实际机械负载来调不能套用默认值到底。这个话题后面再展开。力控指令里force_mode是UR在标准脚本里提供的高层接口可以设置力控坐标系、目标力/力矩、以及各方向的刚度。做打磨、装配、柔性压合基本都要用到。低速力控也可以用set_force_mode的方式写在循环里但这需要你对实时控制周期有理解新手不建议一上来就搞自定义力控循环。2.4 一个能直接跑起来的基础示例我写一个最简单的取放料循环你可以先敲进URScript节点里试试确认开发环境通了再做复杂的。# 基础变量定义 home p[0.3, 0.0, 0.3, 0.0, 3.1415, 0.0] pick_pose p[0.3, -0.2, 0.1, 0.0, 3.1415, 0.0] place_pose p[0.3, 0.2, 0.1, 0.0, 3.1415, 0.0] sz 0.0 def grip_on(): set_standard_digital_out(0, True) def grip_off(): set_standard_digital_out(0, False) while (sz 3): # 移动到工件上方然后直线下降 movej(pick_pose, t0.6, r0.02) movel(pose_trans(pick_pose, p[0.0, 0.0, -0.02, 0.0, 0.0, 0.0]), t0.3, r0.005) grip_on() sleep(0.3) # 抬起 movel(pose_trans(pick_pose, p[0.0, 0.0, 0.03, 0.0, 0.0, 0.0]), t0.3, r0.005) # 放料 movej(place_pose, t0.6, r0.02) movel(pose_trans(place_pose, p[0.0, 0.0, -0.02, 0.0, 0.0, 0.0]), t0.3, r0.005) grip_off() sleep(0.3) movel(pose_trans(place_pose, p[0.0, 0.0, 0.03, 0.0, 0.0, 0.0]), t0.3, r0.005) sz sz 1 end这个脚本的逻辑很简单定义两个位姿循环三次抓了再放。但里面已经覆盖了变量、函数定义、数字输出控制、位姿变换、运动指令、循环、延时这些就是把URScript玩起来的全部基础知识。有个小细节movej、movel里的t是时间参数单位秒r是导纳半径可以理解成轨迹拐角的平滑半径。如果你的机器人负载偏大或速度要求高t可以适当调大一些r如果设成0轨迹会很刚容易出现冲击噪声。我自己习惯在大多数运动指令里给r一个很小的非零值比如0.005到0.02保护减速机也保护工艺稳定性。3. 实操过程与核心环节实现3.1 先从URSim模拟开始别一上来就怼真机我强烈建议你写完脚本先在URSim里跑。URSim是Universal Robots官方的模拟器可以装在Windows或Linux上它模拟了完整的UR控制环境包括示教器界面、脚本引擎和传感器反馈只是没有真实伺服硬件。用URSim的好处很明显不怕写出飞车轨迹不怕把工具撞到夹具上更不怕信号接错导致气爪乱动。URSim里也有和真机一样的Polyscope界面你可以加载URScript程序、手动移动模拟机器人观察轨迹调试完再传到真机。虽然模拟器的运动学参数和动力学模型通常和真机有差距特别是在加速度、负载效应、奇异点附近的表现上但至少能把语法错误和绝大多数逻辑错误挡在真机之前。做模拟器调试时还有一招打开机器人日志窗口。写脚本出问题时控制台输出的错误信息往往能直接定位到第几行有什么问题。URSim里还支持插入print()命令输出变量值这个比你在真机上拿示教器弹窗更省力。等到真机调试再把这些print注释掉或改成日志写入避免刷屏。3.2 真机调试流程和位置校准把URSim里调试过的程序搬到真机一定要做一次干运行验证。就是把程序里的实际吸附、抓取、电机动作全部屏蔽掉只让机器人按轨迹空跑一遍。这个动作看起来保守但能帮你发现一个特别隐蔽的问题程序坐标系和真机实际安装误差。比如URSim里你把home点放在p[0.3, 0.0, 0.3]真机上如果把机器人底座装在偏转10度的支架上实际坐标就会完全对不上。所以真机调试的第一步就是用示教器手动移动机器人到目标点位记录实际位姿修改脚本里的坐标参数。这个步骤有一个被我反复验证有效的做法不在脚本里硬编码所有坐标而是到真机上用示教器记录点位再通过变量或者URScript程序读取当前位姿来赋值。current_pose get_actual_tcp_pose()在真机上运行这一行把current_pose的值打印出来再把值填进脚本这样比你目测坐标去估要精确得多。很多老师傅现场标定坐标就是这么干的比拿尺量靠谱。3.3 通过socket端口远程发送URScript做集成的时候你很少会去示教器上一个节点一个节点地按启动键。常见的做法是从上位机用socket往机器人发URScript代码实现程序切换、开始暂停、信号触发。我以Python为例写一个最简单的主机端连接发送代码import socket robot_ip 192.168.1.10 port 30003 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(5) sock.connect((robot_ip, port)) ur_script movej([0.0, -1.5708, 0.0, -1.5708, 0.0, 0.0], t0.5, r0.0)\n sock.send(ur_script.encode()) sock.close()这段脚本执行后机器人会立刻以关节运动方式回到一个指定姿态。发完代码不保证执行完成如果你想等机器人到位后再发下一条指令需要用机器人状态反馈来确认或者直接在URScript里用wait指令等位姿满足条件后再继续。这里要避一个坑30003端口发URScript机器人会以实时优先级执行但在执行期间如果你再往同一端口发一条新指令只有当上一条运动指令被前卫参数例如t、r执行完或者被中断后新的指令才会接上。如果你发完指令后立即发第二条两条指令都会被接收但执行顺序是有讲究的不会像多线程一样同时执行。所以做上位机程序时要么等机器人端口返回状态要么发完一条等一段时间再发下一条。3.4 关键参数调整心得速度、加速度和区域半径很多刚接触URScript的人只盯着运动指令觉得t和r随便填就行。真机调试跑一圈就会发现运动参数直接影响到节拍和工艺质量。我常用的写法是按工艺要求先确定目标时间再用导纳半径去消除冲击而不是反过来。举个例子一个搬运节拍要求在2秒内完成取料到放料的空行程那你就把movej的t参数设为0.4~0.6movel的下压段和抬起段设为0.25左右。如果策略需要更快的节拍可以尝试把t进一步压缩到0.3但要注意加速度上限UR的控制引擎有自己的速度和加速度限制你设的t太短实际执行时会因为超限被打回甚至机器人会报timeout警告其实不是程序错了是物理上跑不到这么快。另外在连续轨迹中r导纳半径很值得花心思调。它相当于把轨迹的拐角做了圆滑让机器人不在两个运动指令之间完全停下来。如果你希望两个动作之间不减速、不顿挫就把r设大一点如果工艺上必须精确走直角r就要设成极小值或者0但要做好速度损失的心理准备。气动抓手在拐角处如果抖动得厉害往往就是r设得不够导致加减速冲击传导到末端结构上。4. 常见问题与排查技巧实录4.1 语法报错却不告诉你行号怎么办URScript在很多时候报错信息非常简略甚至只说SyntaxError或者Unknown command却不给具体行号。这种情况我见得太多总结了几个经验性的检查顺序。第一检查缩进。URScript有严格的缩进要求虽然它不像Python那样全面强制一致性但你在if、while、def里写的下级代码缩进不统一引擎会解析失败。我把这段代码在URSim里跑一遍再用一个简单策略把所有缩进统一成两个空格不要混用Tab和空格。第二检查函数名和参数类型。URScript内置函数名是区分大小写的movej和Movej就是两个概念前者存在后者直接报错。另外函数参数的顺序、数量也和常规编程语言很不一样比如movel(pos, t, r)如果你把t和r的位置写反运动会变得完全不可预期而且不容易报错。第三检查字符串引号。URScript支持双引号字符串如果你在字符串里漏了结尾引号它会一直吞到下一个引号经常把后面的代码全部圈进字符串里报错定位就完全错乱了。遇到完全定位不到错误的时候我的做法永远是二分法隔离把大段脚本注释掉一半跑看是否报错再注释一半直到定位到出问题的代码块。这个方法土但在URScript的调试环境里确实最可靠。4.2 为什么写入循环后机器人一直执行不完URScript的实时线程是被运动指令和sleep占用的。如果你在主程序里写了一个while循环循环体内没有运动指令或者sleep但逻辑上又一直在跑机器人会一直处于占用状态示教器上可能卡在进度条你得手动停止程序这很容易产生误操作风险。更隐蔽的一个情况是在URScript里用无限循环做信号等待while (get_standard_digital_in(0) False): sleep(0.1) end这条命令本身没问题但它占用了主线程。如果你在PolyScope图形程序里同时还有另一个节点要执行运动指令那后一个节点永远等不到机器人的空档。解决方法是把这个等待放到后台线程里用线程的方式去处理或者用wait但你需要理解执行上下文。如果需要并行逻辑可以考虑在URScript里使用thread开线程而不是让所有代码堆在主线程里。不过开线程之后要特别小心共享变量和运动指令的冲突。UR机器人同一时刻只能执行一条运动指令如果你在主线程和后台线程里同时发movej会产生竞争和不可预期行为。一般我的做法是逻辑流程放主线程I/O监控和外部通讯放后台线程运动指令永远只让主线程发。4.3 力控和伺服控制常见问题使用servoj时很多人会遇到机器人抖得像帕金森的问题。通常不是代码错而是参数没调对。servoj的lookahead_time默认值在部分版本里可能偏大在负载较轻且刚性较强的机器人上容易震荡。我建议从一个较小的lookahead_time开始比如0.05再逐渐增大同时把gain放在500左右观察运行稳定性找到合适的折中点。另外servoj模式下机器人不会自动规划轨迹它只按你给的关节目标值插值跟踪所以源码里每次循环都要更新目标关节位置否则机器人会停在最后的目标位置不动而不是保持跟踪。force_mode也是容易出坑的地方。用force_mode做恒力打磨时需要在指定坐标系中定义fx、fy、fz、rx、ry、rz的目标力和刚度。复杂的地方在于一旦进入force_mode后续运动指令的位姿基准会被重新解释如果你不理解这个基准变化会以为机器人乱跑了。我的经验是进入力控模式前先记录一个基准位姿退出力控模式后再恢复到这个位姿这样能显著降低吸收冲击后的回归误差。4.4 信号处理、通信和时延排查UR的I/O信号有一些延迟在脚本中用set_standard_digital_out后立即去读回该信号可能读到旧值。在气爪动作、真空吸附这类场景里如果不等1~2个控制周期就让下一步运动开始很容易出现程序说夹了但实际没夹上的假象。我习惯在IO动作后加一个sleep(0.1~0.3)或者用wait去确认传感器反馈而不是盲目依赖指令执行顺序。与外部上位机通信时URScript从socket读取数据的时延和实时性也需要注意。虽然urscript里有socket_open、socket_send_string、socket_read_string这类访问函数但它们是阻塞式的如果没有数据就一直等待会让主线程卡在通信上。我的做法是把socket通信放到后台线程里缓存数据主程序通过变量去读最新值这样机器人运动不会因为通信延迟而停顿。调试通信问题时可以把socket读写过程中的关键值print到示教器日志里观察值和时序。这里有个坑URSim里网络环境和真机不同模拟器里的socket行为不能百分百代表真机。有条件的话通信逻辑尽量在真机上做联调或至少用真机同一型号、同版本系统的控制器。5. 我的一些个人经验和后续扩展方向做URScript开发这几年我最大的体会是别把它当成一门编程语言学要把它当成一台机器的操作手册来学。你不需要掌握多高深的算法但你必须理解每一个指令在真实机器人身上会造成什么物理效果。写脚本之前先想清楚轨迹姿态、速度时间、I/O时序然后才动手写代码这样调试效率会高很多。另外URScript的调试体验相比其他工业机器人确实友好不少因为UR自带的功能开发平台是开放的你可以随时用蒙版、日志、URCap去扩展它的能力。如果你做集成开发做得深入后续可以尝试写URCap插件把URScript封装成更易用的图形节点或者通过Dashboard脚本和外部系统打通数据流。还有一块方向是用URScript做视觉引导抓取和力控装配这是现在自动化产线里需求最多的应用之一。最后分享一个小技巧你可以在URScript里面使用get_actual_tcp_pose()、get_target_tcp_pose()和get_actual_joint_positions()做一个现场坐标校准先让机器人手动移动到一个关键位置打印出来记录再用这些实际值去替换你脚本里的估算位姿。这套流程我几乎每个项目都会跑一遍它比任何高级功能都实用。真正灵光的工业机器人程序不是写出来的是来回调出来的。URScript只是那把帮你快速来回调的钥匙。