简介:面向汇川机器人控制系统的API开发资料包,聚焦工业机器人二次开发场景,适合自动化工程师、上位机开发人员及智能制造相关学习者。内容围绕IMC100系列控制器,涵盖API调用、通讯协议、运动控制与安全规范等关键知识点,有助于快速构建机器人控制程序。压缩包共11个文件,约165KB,包含xlsx格式的API说明文档、dll动态链接库与lib静态库,以及C#、VB.NET、C/C++等多语言接口文件,配套更新记录与快捷方式,基本满足Windows环境下集成开发需求。已有1839人学习下载。资料提供多语言示例与底层库文件,可帮助开发者理解机器人指令下发、状态反馈及错误处理流程,缩短项目调试周期,同时为后续结合MES或ERP系统实现产线自动化打好基础。
1. 汇川机器人API编写程序:先想清楚控制器到底开放了什么
做汇川机器人API编写程序的人,十有八九不是搞机器人本体的,而是做软件集成的:视觉定位完要给机器人发坐标,MES排完产要让机器人换程序,AGV到位后要触发夹爪动作。这些需求的共同点,是把“示教器上手操的工作”转成交互协议里的几行报文。你第一反应可能是先找协议文档、抄个Demo、跑通一次PTP再说。但真正决定项目能不能落地的,不是指令格式背得多熟,而是先想清楚控制器对外到底开放了哪几层能力:哪些走Modbus TCP就够,哪些必须走Socket报文,哪些压根不给上位机碰。这个判断错了,代码写得再漂亮,现场也会被示教器上的模式开关和安全逻辑卡死。下面按通讯选型、报文设计、工程骨架、现场避坑、进阶技巧从头捋一遍。
2. 通讯选型与报文格式:TCP/IP 和 Modbus TCP 怎么选、点位数据怎么封
2.1 选型判断:开关量走 Modbus,轨迹控制走 Socket
汇川机器人控制器的对外通讯能力,我分两类看。一类是 PLC 风格控制,走 Modbus TCP 读写保持寄存器,适合做“选程序号、启动、暂停、急停复位、读报警码、读写 DI/DO”这一类开关量和整型量交互。这类场景下上位机不关心运动学,也不关心当前轴角,只需要把控制器当成一个会执行既定任务的执行器。另一类是上位机主导的轨迹控制,走 TCP/IP Socket 报文,能下发 PTP、直线、圆弧等运动指令,也能读当前笛卡尔坐标和关节坐标,适合视觉引导、轨迹在线修正、点阵搬运这类需要“每一条动作都由上位机算出来”的应用。
从一个真实项目经验看,两者的分界线很清晰:某上料工位原来只要求三套程序之间切换,Modbus TCP 写几个寄存器就够了;后来加了轮廓自适应抓取,每个工件的放置角度都不一样,程序数量从 3 套涨到 60 多套,示教器根本存不过来,只能改成上位机实时下发 PTP 目标点。此时 Modbus 那套就只能保留急停和报警读取,运动轨迹全部切到 Socket 通道。
用一张表把选型要点列出来,方便做方案阶段直接对照:
| 维度 | Modbus TCP | TCP/IP Socket |
|---|---|---|
| 典型信号 | 程序号、启停、报警复位、DI/DO | 运动指令、坐标系、实时位置读取 |
| 交互模型 | 上位机作为主站轮询/写寄存器 | 命令-响应或命令-事件上报 |
| 点位灵活性 | 只能切换示教器里存好的点位 | 每个目标点都可以由上位机临时生成 |
| 开发难度 | 低,常见库即可 | 中,需要自己处理报文封包和状态机 |
| 实时性 | 一般,轮询周期决定 | 取决于报文长度和控制器扫描周期 |
| 适合谁 | PLC 集成、简单上下料 | 视觉引导、轨迹在线调整、批量点位下发 |
一个常见误区是看到控制器支持 Modbus TCP 就觉得万事大吉,结果在“临时改一个坐标”的需求上卡住。寄存器里存的坐标是示教器工程维护的,上位机要改坐标得先算出寄存器地址和数据格式,费劲不说,还容易踩到控制器内部数据缓冲的坑。我的做法是:凡是涉及坐标、轨迹、插补类型的,一律走 Socket;只有系统级使能和状态监控走 Modbus 或保留原厂 IO。
2.2 报文设计:命令字、坐标帧、速度倍率三个核心字段
Socket 报文不管控制器用哪种协议,设计时都绕不开三个核心字段:命令字、坐标帧、速度参数。命令字决定动作类型,PTP 对应点到点,MoveL 对应直线插补,Circle 对应圆弧;坐标帧决定解释方式,关节坐标给 J1 到 J6 六个轴角,笛卡尔坐标给 X、Y、Z、Rx、Ry、Rz;速度参数决定执行快慢,这地方最坑,有的按百分比,有的按毫米每秒,混用一次就出事故。
我最早跑通汇川机器人 Socket 通信时,协议文档里给的是 ASCII 文本报文,帧头、长度、命令字、数据区之间用分隔符切开。实际开发中会比二进制报文好排查得多,先用文本模式把逻辑跑通,再切二进制模式做正式交付。文本报文的核心结构大致是这样的逻辑:一条运动指令由命令字开头,坐标帧类型紧跟其后,然后是六个坐标值,最后带速度倍率与结束符。
以关节坐标 PTP 为例子,报文的拼装思路可以写成下面这段 Python 代码。这不是某本官方手册的原样复制,而是按我拆过的一台汇川机器人控制器的交互逻辑抽象出来的结构,字段对齐方式以你拿到的机型协议为准。
import socket import time ROBOT_IP = "192.168.1.10" ROBOT_PORT = 8000 def build_ptp_joint(j1, j2, j3, j4, j5, j6, speed_percent=30.0): # 按文本模式拼 PTP 关节运动指令 # 字段含义依次为:命令字、坐标系类型、六个轴角、速度倍率 line = "PTP,JOINT,{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.3f},{:.1f}\r\n".format( j1, j2, j3, j4, j5, j6, speed_percent ) return line.encode("ascii") def send_command(sock, cmd_bytes, timeout=5.0): sock.settimeout(timeout) sock.sendall(cmd_bytes) resp = sock.recv(4096) # 返回原始响应,由上层逻辑解析 return resp sock = socket.create_connection((ROBOT_IP, ROBOT_PORT), timeout=3.0) cmd = build_ptp_joint(0.0, -45.0, 90.0, 0.0, 45.0, 0.0, speed_percent=20.0) resp = send_command(sock, cmd) print(resp) sock.close()代码里的{:.3f}保留三位小数,是因为工业机器人点位精度通常到毫米和毫度级,四舍五入多一位小数不会影响运动,少一位反而可能让关节角在重复到位时出现微小抖动。speed_percent用 20.0 而不是 20,是为了避免整数除法带来的类型问题。\r\n结束符是我在这类 ASCII 报文里常见的做法,有些控制器只认\n,拿不到响应时优先排查这里。
速度倍率字段特别提醒一点:PTP 关节运动的速度基准是控制器内部定义的百分比,100 就是该轴最大速度;MoveL 直线运动的速度则有可能是绝对速度 mm/s。同一个数字放在不同指令里,含义可能完全不一样。先发一条低速、小角度的指令确认映射关系,再上生产逻辑。
3. 工程骨架与 Python 控制:从连接、登录到状态轮询
3.1 连接与登录:不是 TCP 通了就代表能用
很多人写控制器上位机程序,第一步就是socket.connect,连上之后直接发运动指令,结果发现控制器要么不回包,要么回了但机器人纹丝不动。这不是协议文档错了,而是漏掉了“登录/使能”这一步。常见的做法是:控制器在自动模式之外还区分本地控制和远程控制,远程控制通道里又要区分哪个端口有控制权限。你在示教器上看到 TCP 连接已建立,只代表网络层通了,控制层可能还处于只读状态。
我一般会在机器人基类里单独拆一个login方法,专门处理权限建立。权限建立成功后,控制器通常会返回一个带会话标识的响应,后续所有运动指令都带着这个标识发送。如果协议文档里没有会话概念,那至少要把“远程模式是否使能”作为一个前置状态检查。
class InovanceRobot: def __init__(self, ip, port): self.ip = ip self.port = port self.sock = None self.session_id = None def connect(self): self.sock = socket.create_connection((self.ip, self.port), timeout=3.0) self.sock.settimeout(5.0) def login(self, user="remote", pwd="123456"): # 按常见的新建连接->鉴权->取会话标识顺序执行 # 具体字段以机型协议为准 login_cmd = "LOGIN,{},{}\r\n".format(user, pwd).encode("ascii") self.sock.sendall(login_cmd) resp = self.sock.recv(1024).decode("ascii", errors="ignore") if resp.startswith("OK"): self.session_id = resp.split(",")[-1].strip() return True return False def close(self): if self.sock: self.sock.close() self.sock = None这段代码里connect和login分开,是因为排查问题时一定要能区分是哪一层失败。网络层失败直接抛异常,控制层拒绝则返回False。调试期我会在login失败时打印控制器返回的原始文本,通常里面会带“remote mode disabled”或“password error”一类的关键词,能直接定位到是模式开关还是密码问题。
3.2 点位运动与状态轮询:一次可复用的 MoveCore 封装
点位运动跑通之后,紧接着要解决的不是“再发一条指令”,而是“怎么知道上一条执行完了”。控制器执行 PTP 是需要时间的,上位机如果不管执行状态,连续发两条目标点,第二条指令大概率被控制器以 busy 为由拒绝。轮询状态字是绕不开的。
我的封装习惯是把“发送指令”和“等待到位”拆成两个方法。发送只管把报文写进 socket,等待到位则去读控制器的状态字或者当前位置,直到满足判定条件。判断到位最稳的指标是当前位置与目标位置的误差小于某个阈值,次选是控制器的到位标志位。
class InovanceRobot: def move_ptp_joint(self, j1, j2, j3, j4, j5, j6, speed_percent=20.0): cmd = build_ptp_joint(j1, j2, j3, j4, j5, j6, speed_percent) self.sock.sendall(cmd) resp = self.sock.recv(4096).decode("ascii", errors="ignore") if not resp.startswith("OK"): raise RuntimeError("PTP 指令被拒绝: {}".format(resp)) def wait_until_idle(self, timeout=30.0): start = time.time() while time.time() - start < timeout: self.sock.sendall(b"ROB_STATE\r\n") state = self.sock.recv(1024).decode("ascii", errors="ignore") if "IDLE" in state or "INPOS" in state: return True time.sleep(0.2) raise TimeoutError("等待机器人回到空闲状态超时")wait_until_idle里的轮询间隔取 0.2 秒,这个值不是拍脑袋写的:间隔太短会频繁占用控制器通信带宽,间隔太长又会让上位机的节拍变慢。0.2 秒对大多数搬运和下料场景都够用,视觉引导这类高节拍场景再缩短到 0.05 秒。
这里有一个容易被忽略的细节:状态字里的IDLE和INPOS含义不同。IDLE表示控制器当前没有运动任务,INPOS表示机器人已经到位。有些机型在运动完成瞬间两个标志位都成立,有些机型IDLE先出现,位置还在做微小修正。我在要求高重复精度的场景里,判断条件是INPOS而不是IDLE。
4. 避坑:汇川机器人在线编程的五个翻车现场
4.1 连接与控制层:连上不发、发了不走的典型问题
翻车现场一:指令石沉大海。
现象:TCP 能 ping 通,示教器网络状态也显示已连接,但上位机发 PTP 指令后机器人完全不动,也不报错。
原因:控制器处于手动模式,远程控制通道被禁用,运动指令被控制器直接忽略。有些型号还需要额外的“远程使能”信号接入 IO,光靠 socket 登录还不够。
解决:先在示教器上把运行模式切到自动,并确认外部控制使能打开。再抓一次登录响应,看返回文本里是否包含远程权限相关字段。最后用 IO 表查“远程控制使能”这个输入信号有没有被外部接线置位。三步走完,绝大多数指令石沉大海的问题都能定位。
翻车现场二:机器人动了,但比预期慢一个数量级。
现象:上位机下发speed=30,结果机器人以蜗牛速度挪过去,生产节拍直接被拖垮。
原因:速度倍率字段在不同指令里的含义不同。PTP 下 30 代表最大速度的 30%,MoveL 下 30 可能代表 30 mm/s 的绝对速度。把两种语义混用,自然会出现“设定值看起来差不多,实际速度天差地别”的情况。
解决:把运动指令封装成独立方法而不是公用一个速度参数。PTP 方法内部把速度字段当百分比处理,MoveL 方法内部按绝对速度处理。同时在上位机里做一个速度映射表,调试时先发 10 的倍率观察实际速度,确认语义后再调到生产值。
翻车现场三:第二条指令发出去立刻收到 busy 报错。
现象:第一条 PTP 执行过程中,上位机把第二条目标点发过去,控制器返回 busy 或 error。
原因:控制器默认不缓存多条运动指令,或者缓存队列长度只有 1。上位机没有判断前一条指令是否执行完就发下一条,被拒是必然的。这不算是 bug,本质上是时序问题。
解决:在发送任何运动指令之前,先执行wait_until_idle或者等待到位标志位。如果现场节拍要求高,要确认控制器的指令队列是否支持 FIFO 积压,不支持就把下一条指令的发送时机卡在“到位”事件上。
4.2 坐标与运动学层:数字看起来对,动作却完全不对
翻车现场四:笛卡尔坐标发过去,机器人往反方向飞。
现象:视觉系统给出一组 XYZ 坐标,上位机按笛卡尔 PTP 下发,机器人走得方向和预期完全相反,甚至撞到安全围栏。
原因:坐标系基准不一致。控制器的笛卡尔坐标分为世界坐标系、工件坐标系、工具坐标系三种,机器人当前激活的是哪个坐标系,直接决定目标点的解释结果。还有一个隐藏因素:工具号不同,TCP 的位置补偿也不同,同一个 XYZ 在不同工具号下完全不是一个物理位置。
解决:在指令里显式带上坐标系类型和工具号字段,不要依赖控制器的默认值。调试阶段先把工具号固定为 0,工件坐标系固定为世界坐标系,用一个已知安全的小偏移量做方向验证。确认 X 正方向对着实际运动方向之后,再接入视觉系统的大坐标。
翻车现场五:控制器掉线十分钟后重连,登录总是超时。
现象:上位机程序异常退出或强制断开 socket,过一会儿重新连接,登录指令发过去读不到任何响应。
原因:控制器侧还保留着旧会话,新连接进来时旧会话没有释放,控制器会把它当成同一个客户端的重复连接。尤其当调试中反复强杀上位机进程时,控制器侧的会话资源会被占满。
解决:重连前先确保旧 socket 已经完全关闭,再调用控制器的会话清理指令。或者在控制器侧设置会话超时时间,让残留会话自动过期。调试期我习惯把控制器的连接超时设短一点,比如 30 秒,省得反复手工清会话。
5. 进阶:位置校验、IO 联动与断线自恢复的状态机
前面的内容能保证“单条指令能跑通、多条指令不打架”,但到正式生产,还得解决一个工程问题:上位机不能只发指令不管结果,要把整个动作拆成带状态流转的流程。一个典型的取放流程通常由这几个环节组成:下发运动指令、等待到位、校验实际位置、触发夹爪 IO、等待 IO 反馈、回原点。我习惯把它收敛成一个状态机,而不是一长串顺序执行的move调用。顺序执行在机器人干得好好的时候没问题,一旦某一步超时或报错,整条链路的异常处理会变得非常别扭。
位置校验是我特别看重的一环。视觉引导场景里,视觉系统算出的目标点偶尔会跳一个离谱的离群值,直接发给机器人就是一次潜在事故。所以我在每个运动指令发出后,会额外读一次当前位置,与目标点做差,误差超过阈值就立即停止并报警,而不是等机器人撞到夹具再后悔。
class PickPlaceStateMachine: def __init__(self, robot): self.robot = robot self.step = "HOME" self.error = None def run_once(self): if self.step == "HOME": self.robot.move_ptp_joint(0, -45, 90, 0, 45, 0, 20.0) self.robot.wait_until_idle() self.step = "PICK_PREPARE" elif self.step == "PICK_PREPARE": if not self.robot.check_position("JOINT", [0, -45, 90, 0, 45, 0], tol=0.5): self.error = "回零位置校验失败" return False self.robot.set_do(1, True) # 打开夹爪 self.step = "PICK_WAIT_IO" elif self.step == "PICK_WAIT_IO": if self.robot.wait_di(1, timeout=5.0): self.step = "PICK_MOVE_TO_TARGET" elif self.step == "PICK_MOVE_TO_TARGET": self.robot.move_ptp_joint(30, -30, 45, 0, 60, 0, 30.0) self.robot.wait_until_idle() self.step = "PICK_CLOSE_GRIPPER" elif self.step == "PICK_CLOSE_GRIPPER": self.robot.set_do(1, False) # 闭合夹爪 self.step = "DONE" return self.step == "DONE" def run(self): while self.step != "DONE": if not self.run_once(): break这个状态机的关键点在于,每个状态转移前都有前置条件:要么是位置校验通过,要么是 IO 反馈到位。一旦某一环节卡住,run_once返回False,外层就能拿到self.error做报警。断线自恢复逻辑也可以挂在这个框架里:检测到 socket 异常时,先尝试重连和重新登录,然后回到HOME状态重新校验一次位置,而不是直接回到失败步骤。这样即使通讯中断过,机器人也不会带着错误坐标继续跑。
从那以后,我每接手一台汇川机器人,都强制自己先花半天时间做一遍通讯体检:所有控制器的业务逻辑,全都用这种带校验的状态机实现,通讯断了我能把损失控制在一条指令内。希望帮到你。
本文还有配套的精品资源,点击获取