简介:这份资料围绕一套LabVIEW上位机控制汇川H5U PLC与EtherCAT伺服,并结合海康威视相机完成视觉对位的项目资料展开,面向已有LabVIEW基础、希望切入非标自动化与运动控制方向的工程师及学生。内容涉及上位机通信、PLC下位机逻辑、伺服运动控制、相机取像与对位调试流程等多个环节,使用前需安装LabVIEW视觉与DSC模块,适合用来理解整机控制链路、模块分工和项目拆解思路。压缩包共5个文件,以3张jpg图片、1个txt说明和1个html页面为主,大小约633KB,可作为项目结构、界面效果和关键信息的快速索引,方便先建立整体认识再对照学习。目前已有444人学习,结合资料可梳理通信配置、运动控制与视觉对位之间的配合关系,对承接一般非标自动化项目、理解电气与软件联调具有较强参考价值。
1. 视觉对位这套组合,卡点不在算法而在三张网对不齐
把 LabVIEW 上位机、H5U 系列 PLC、EtherCAT 伺服和 GigE 工业相机拼成一套视觉对位系统,第一件要接受的事是:LabVIEW 并不直接讲 EtherCAT。上位机通过网口用 Modbus TCP 读写 PLC 的软元件,PLC 内部再把寄存器里的数值映射成轴的 CSP 目标位置;EtherCAT 主站由 PLC 本体承担,相机走另一张 GigE 网。这套分层决定了节拍上限:视觉算得再准,只要握手位、时间戳、标定残差有一处没处理干净,对位精度和节拍就一起塌。
它适合谁?单机 1 到 4 个轴、节拍在 0.3 到 1 秒之间、需要频繁换型的非标设备,比如贴装、点胶、组装定位。反过来看,如果节拍要求压到 100 毫秒以内、或者轴数上到 16 轴以上,这套“上位机算、PLC 动”的结构就会成为负担,得考虑把视觉结果直接送进运动控制器。下面按“先通路、再换算、再框架、最后压节拍”的顺序讲。
2. 三张网先跑通:Modbus TCP、EtherCAT 与 GigE 相机的地址怎么排
开工第一天不要碰算法。先把三张网各自的角色、地址段和周期定死,否则后面每次调试都要重新猜“到底是通信慢了还是轴没到位”。
2.1 拓扑与地址规划:为什么必须物理分开
上位机通常插两块网卡:一块接 PLC 的以太网口走 Modbus TCP,一块接相机走 GigE。原因是 GigE 相机的包流是突发的,一张 500 万像素黑白图在千兆网下要占几十毫秒的带宽峰值,如果和 Modbus 挤在同一张网、同一块交换机上,PLC 的读写响应会被挤成锯齿,表现出来就是“偶尔读超时,重启又好了”这类玄学问题。分开之后,两块网卡各走各的中断,互不干扰。
EtherCAT 这一段不在上位机的视野里。H5U 这类小型 PLC,本体一般带一个 EtherCAT 主站口和一个普通以太网口,前者接伺服做总线运动,后者开 Modbus TCP 从站给上位机读写。规划时按下面的方式列表格,贴在电柜门上,比记在脑子里可靠。
| 链路 | 物理口 | 协议 | 角色 | 典型周期 | 地址段示例 |
|---|---|---|---|---|---|
| 上位机到 PLC | 网卡 A | Modbus TCP | 上位机为主站,PLC 为从站 | 10 到 20 毫秒轮询 | 192.168.1.10 / 192.168.1.20 |
| PLC 到伺服 | 本体 EtherCAT 口 | EtherCAT / CiA402 | PLC 为主站,伺服为从站 | 1 到 4 毫秒 | 站号 1、2、3… |
| 上位机到相机 | 网卡 B | GigE Vision | 上位机取流、软硬触发 | 按节拍单帧 | 192.168.10.10 / 192.168.10.50 |
提示:相机网段不要配默认网关,也不要和办公网共用一个 DHCP 池,否则相机的 IP 会随机漂移,第二天开机就连不上,这类“重启解决”的故障多半来自此处。
地址规划里还要预留一段“握手区”和一段“参数区”。握手区放触发位、完成位、报警位、序列号,参数区放目标坐标、角度、拍照结果、补偿量。两段分开,改参数时不会误清握手状态。
2.2 LabVIEW 侧 Modbus TCP 主站:从寄存器映射表倒推代码
LabVIEW 做 Modbus TCP 主站有两条路:用自带的 Modbus TCP 主站函数,或者自己用 TCP Open 拼报文。前者省事、寄存器地址按协议规范填就行;后者灵活,遇到非标设备返回的时间戳、序列号可以自己解析。我一般先在 PLC 编程软件里把 Modbus 从站的软元件映射表导出,明确“哪个功能码、哪个偏移、对应哪个 D 寄存器”,再回 LabVIEW 写代码,不靠猜。
映射表通常长这样(具体地址以编程软件导出的表为准,不同固件会有偏移差异):
| 功能码 | 起始偏移 | PLC 软元件 | 方向 | 用途 |
|---|---|---|---|---|
| 03 读保持寄存器 | 0 | D100 起连续 16 字 | 上位机读 | 当前轴位置、状态字、报警码 |
| 16 写多寄存器 | 0 | D200 起连续 8 字 | 上位机写 | 目标 X、目标 Y、目标角度、序列号 |
| 01 读线圈 | 0 | M100 起连续 16 位 | 上位机读 | 到位、拍照完成、急停 |
| 05 写单线圈 | 0 | M200 起 | 上位机写 | 触发拍照、允许运动、复位 |
写代码前先用脚本把对端模拟出来,验证自己的报文拼得对不对,这一步能省掉大量“到底是哪边错”的扯皮。下面这段用常见 Modbus 客户端库模拟一个 PLC 从站,把 D 区和 M 区暴露出来(不同库版本 API 有差异,按实际文档调整)。
# 模拟 PLC 从站:供 LabVIEW 联调前自测报文 from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext from pymodbus.server import StartTcpServer # holding registers 对应 D 区,coils 对应 M 区,偏移 0 起 store = ModbusSlaveContext( hr=ModbusSequentialDataBlock(0, [0] * 64), # 保持寄存器 64 个 co=ModbusSequentialDataBlock(0, [0] * 32), # 线圈 32 个 ) context = ModbusServerContext(slaves=store, single=True) # 502 是标准端口,联调时可改成 1502 避免占用冲突 StartTcpServer(context=context, address=("0.0.0.0", 1502))逻辑说明:把 D 区做成连续 64 个寄存器,是为了让上位机一次读 16 个字时不会越界;线圈区单独开 32 位,对应握手位。参数上唯一要改的是端口,标准 502 在部分系统上需要管理员权限,联调期换成 1502 更快。跑通后再把 LabVIEW 的读取偏移和这段模拟器对齐,确认报文正常,再切到真 PLC。血泪经验是:先怀疑自己的偏移算错了一位,再怀疑 PLC,顺序不要反。
2.3 EtherCAT 伺服这一侧:PDO 映射和轴参数怎么定
上位机不碰 EtherCAT,但必须知道 PLC 里轴是怎么被驱动的,否则写目标位置时会犯“单位不对”“方向反了”的低级错误。CiA402 的 CSP 模式(周期同步位置)下,PLC 每个总线周期把目标位置写进 0x607A,伺服从 0x6064 回读实际位置。常用对象如下:
| 索引 | 名称 | 说明 | 调试关注点 |
|---|---|---|---|
| 0x6040 | 控制字 | 使能状态机 | 0x06 → 0x07 → 0x0F 顺序不能跳 |
| 0x6041 | 状态字 | 回读伺服状态 | 卡在 0x0240 说明未进入 Operation Enabled |
| 0x6060 | 运行模式 | 8 = CSP | 改模式前必须先失能 |
| 0x607A | 目标位置 | 指令位置 | 单位由电子齿轮决定 |
| 0x6064 | 实际位置 | 编码器反馈 | 与 0x607A 的差值看跟随误差 |
| 0x60C2 | 插补周期 | 与总线周期匹配 | 必须等于主站周期,否则抖动 |
轴参数里最容易翻车的是电子齿轮比和指令单位。假设编码器 23 位(8388608 脉冲每转),丝杠导程 10 毫米,希望 1 个指令单位等于 0.001 毫米,那么每转需要 10000 个指令单位,齿轮比按 8388608 : 10000 配置。这个换算必须在 PLC 侧定死,并写进上位机的常量表里,上位机只发“毫米”,不再做二次换算——两边都换算一次,就是典型的两倍误差来源。
再补一条:轴使能失败时,先读 0x6041 状态字,再对照 CiA402 状态机。控制字序列必须是 0x06(Shutdown)→ 0x07(Switch On)→ 0x0F(Enable Operation),中间不能跳步,也不能在故障未复位时直接给 0x0F。这一条几乎是每次新机器上电都要重演一遍的固定节目。
3. 像素到毫米:视觉对位的标定、换算与旋转中心
通路跑通之后,才轮到“看得准”。视觉对位本质是一次刚体变换估计:相机看到的是像素坐标,伺服要的是机械坐标,中间隔着镜头畸变、安装倾角、工件高度三个变量。
3.1 九点标定:把 px 变成 mm 的最小实验
标定的目的是求一个仿射矩阵,把像素 (u, v) 映射到机械 (X, Y):
X = a·u + b·v + c,Y = d·u + e·v + f
理论上 3 个点就能解出 6 个参数,但 3 点标定对噪声零容忍,所以我一般取 9 点做最小二乘,同时看残差。做法是让伺服把标定板(或工件上的 Mark 点)依次走到 9 个位置,每个位置记录机械坐标并提取像素坐标,然后离线求解。下面这段在 LabVIEW 公式节点或外部脚本里都能跑。
import numpy as np # 9 组对应点:pix 为像素坐标,mm 为同一时刻的机械坐标 pix = np.array([[120.5, 340.2], [520.8, 338.9], [915.3, 341.0], [118.9, 640.7], [521.4, 639.5], [918.0, 642.1], [119.7, 940.3], [523.2, 938.8], [916.5, 941.2]]) mm = np.array([[10.00, 20.00], [40.00, 20.00], [70.00, 20.00], [10.00, 40.00], [40.00, 40.00], [70.00, 40.00], [10.00, 60.00], [40.00, 60.00], [70.00, 60.00]]) # 构造齐次设计矩阵,X、Y 分别做最小二乘 A = np.hstack([pix, np.ones((pix.shape[0], 1))]) cx, *_ = np.linalg.lstsq(A, mm[:, 0], rcond=None) cy, *_ = np.linalg.lstsq(A, mm[:, 1], rcond=None) # 残差 RMS 是标定质量的唯一客观指标,单位 mm res = np.sqrt(np.mean((A @ cx - mm[:, 0]) ** 2 + (A @ cy - mm[:, 1]) ** 2)) print("残差 RMS = %.4f mm" % res) print("矩阵 =", np.round(np.vstack([cx, cy]), 6))逻辑说明:lstsq用的是最小二乘,比直接解三元一次方程组稳;残差 RMS 是判断“能不能用”的硬指标。参数上,残差超过 0.05 毫米就别急着投产,先查三件事:标定板是否与运动平面平行、9 点是否覆盖了整个视野(不要挤在中间一小块)、提取 Mark 点时是否用了亚像素。覆盖范围这一点常被忽略——用视野中心 1/3 的面积做标定,边缘误差会放大到两三倍。
3.2 角度补偿与旋转中心:对位不准的根在这里
很多工位不只是平移,还要转角度。工件被吸嘴抓起来旋转 θ 后,特征点的位置变化不是简单的“绕图像中心转”,而是绕吸嘴的机械旋转中心转。这个中心如果搞错,就算平移补偿算得分毫不差,旋转后还是会偏。
求法很直接:让工件在同一个吸嘴上转两个已知角度,记录同一特征点在旋转前后的机械坐标,得到两组对应点 P1→Q1、P2→Q2,解刚体变换 R、t,旋转中心就是不动点,满足 (R − I)·c = −t。
import numpy as np # 旋转前后的同一特征点机械坐标(至少两组,三组以上做拟合更稳) P = np.array([[10.0, 20.0], [30.0, 20.0], [20.0, 40.0]]) Q = np.array([[12.3, 21.1], [31.8, 22.5], [22.9, 41.6]]) # 求质心并去中心化 pc, qc = P.mean(axis=0), Q.mean(axis=0) P0, Q0 = P - pc, Q - qc # SVD 求最优旋转矩阵 H = P0.T @ Q0 U, S, Vt = np.linalg.svd(H) R = Vt.T @ U.T if np.linalg.det(R) < 0: # 反射修正,保证是纯旋转 Vt[-1, :] *= -1 R = Vt.T @ U.T t = qc - R @ pc # 旋转中心:解 (R - I) c = -t center = np.linalg.solve(R - np.eye(2), -t) print("旋转中心 =", np.round(center, 3), "mm")逻辑说明:SVD 求刚体变换比直接解方程稳,det(R) < 0的修正不能省,否则会解出镜像解,中心点会跑到离谱的位置。参数上要注意两点:一是采样点要离旋转中心足够远,靠近中心的点转动位移小,噪声占比大;二是每次换吸嘴或换夹具都要重测,机械装配的微小偏差会让中心漂移零点几毫米,这点偏差在 0.05 毫米精度要求下就是致命的。
3.3 一次完整对位流程:从触发到写入目标位置
把上面两段串起来,一个完整周期是这样走的:PLC 输出到位信号 → 上位机硬触发相机(软触发会引入 5 到 20 毫秒不确定延迟)→ 取图、找 Mark、算出像素偏差 → 用仿射矩阵转成机械偏差 → 叠加角度补偿 → 把目标位置写进 D200 起的参数区 → 置位 M200 触发运动 → 等 M100 到位位 → 记日志。
顺序里有三个容易写错的地方。第一,触发方式必须和 PLC 的到位信号硬联动,不能靠上位机轮询判断“应该到了”,否则运动中的工件被拍到,图像是拖影的。第二,写参数和置握手位要分两次写,先写完整块参数,再置位,中间不要插入别的读写,否则 PLC 可能读到半新半旧的数据。第三,日志里要同时记序列号、像素坐标、机械坐标、残差,出问题时这是唯一的黑匣子。
注意:找 Mark 的搜索区域要跟着上一次结果动态收缩,固定 ROI 在换型后经常框到背景上,表现出来就是“同一批产品偶发找不到 Mark”。
4. 上位机框架:队列状态机、心跳重连与整块写入
框架这一层不出彩,但决定了设备能不能连续跑三天不崩。LabVIEW 做这类上位机,我一般用生产者/消费者结构:事件结构做 UI 和按钮,队列把命令送进状态机,状态机里再分“空闲、取图、计算、下发、等待到位、报警”几个状态,通信和算法各占一个独立循环。
4.1 用队列把 UI 和通信解耦
最常见的错误是把 Modbus 读写直接写在按钮事件里。按钮按下去要等 200 毫秒通信返回,界面就卡住,操作员连点两下,队列里堆了三条命令,PLC 收到三次目标位置,轴来回抽一下——这就是“偶发飞车”的常见成因。
正确做法是事件结构只负责“产生命令”,用有界队列(长度设 8 到 16)送进状态机,状态机按顺序消费。队列满时直接丢弃并置报警位,而不是无限堆积。命令里带序列号,状态机每完成一次就把序列号回写,UI 只显示“已完成的序列号”,不直接读通信变量。这样 UI 和通信彻底解耦,通信慢只是节拍慢,不会引发重复下发。
4.2 心跳与断线重连:三次失败才判定掉线
工业现场的网络断一下是常态,交换机重启、网线被踩、电柜门夹到,都会导致短暂失联。上位机必须有明确的掉线判定,否则会出现“以为在跑,其实早就断了”的空转。
判定逻辑建议:连续 3 次读超时(每次超时 200 毫秒)判定掉线,进入重连状态,重连间隔按 1 秒、2 秒、4 秒退避,最多退 5 次后报警停机。重连成功后不要直接恢复生产,先把 PLC 侧的所有握手位清一遍,再重新同步一次轴位置,确认无报警、轴已使能,才允许继续。
import time from pymodbus.client import ModbusTcpClient def safe_read(client, addr, count, retries=3, timeout=0.2): """整块读取 + 重试,返回值直接可用于判空""" for i in range(retries): try: rr = client.read_holding_registers(address=addr, count=count, slave=1) if not rr.isError(): return rr.registers except Exception: pass time.sleep(timeout) # 单次超时,三次累计约 0.6 秒 return None # 上层据此判定掉线并进入重连 def write_block(client, addr, values): """整块写入,避免逐点写造成的中间态""" try: wr = client.write_registers(address=addr, values=values, slave=1) return not wr.isError() except Exception: return False逻辑说明:safe_read把“重试 + 超时”封在一起,调用方只看返回值是不是None,不需要在业务代码里到处 catch。参数上,count一次读 16 个寄存器比读 16 次单寄存器快一个数量级,Modbus 的报文开销主要在往返延迟,不在数据长度。slave参数按实际站号改,单从站场景填 1 即可,多从站或网关后面挂设备时要核对。注意重试次数不要设太大,3 次是经验值,超过之后恢复时间会拖到秒级,节拍就救不回来了。
4.3 参数下发的原子性:为什么整块写比逐点写安全
视觉算出来的目标位置、角度、序列号是一组相互关联的数据。如果分三次写,PLC 有可能在两次写之间扫描到一个“新 X + 旧 Y + 旧角度”的组合,短时间里轴会往错误方向走一小步。虽然下一秒就被纠正,但机械上已经产生了抖动,长期下来丝杠和联轴器都受罪。
原子性的做法有两种。一种是整块写:把 X、Y、角度、序列号放进连续的 4 个寄存器,用功能码 16 一次写完,PLC 侧用一个“数据有效位”的上升沿去锁存,锁存之后才允许使用。另一种是双缓冲:参数区开两份,写奇数区/偶数区交替,用一个标志位告诉 PLC 该读哪一份。整块写实现简单,多数场景够用;双缓冲更严谨,但 PLC 侧要写额外逻辑,属于可选优化。
5. 联调避坑:视觉对位最容易翻车的五个现场
前面四章是“应该怎么做”,这一章是“实际会怎么坏”。下面五条都是现场反复出现的,按现象、原因、解决写,遇到时可以直接对照。
第一条:图像能拍到,但坐标总是差 0.2 到 0.5 毫米,重复标定也不稳定。现象是同一批工件对位后偏移方向一致,换一批又变。原因多半是拍照时工件还在微动,或者触发用的是软触发,从“判断到位”到“真正曝光”之间隔了十几毫秒,工件恰好在这段里被皮带带着走了一点。解决是把触发改成 PLC 输出点硬触发,并且触发信号要取“到位且稳定 30 毫秒后”的那一位,而不是到位位的上升沿。改完再标定一次,残差会明显收敛。
第二条:Modbus 单次读 1 个寄存器,节拍怎么调都下不来。现象是每周期耗时稳定在 700 到 900 毫秒,查算法耗时只有几十毫秒。原因是每读一个寄存器就是一次完整报文往返,10 个参数读 10 次,光通信就吃掉大半节拍。解决是把需要的数据整理成连续区块,用一次功能码 03 读 16 到 32 个字,写侧同理用功能码 16 整块写。这一条改完,节拍通常直接砍掉一半,属于性价比最高的一刀。
第三条:伺服使能不上,状态字一直停在 0x0240。现象是 PLC 显示轴已配置,但伺服没抱死,手动推得动。原因是控制字序列跳步,或者在故障未复位、模式未切换的情况下直接给了 0x0F。解决是按 CiA402 状态机走:先写 0x06 等状态字进入 Ready to Switch On,再写 0x07 等 Switched On,最后写 0x0F 等 Operation Enabled,每一步都读回状态字确认,不要一口气连写三个值。另外改运行模式(0x6060)之前必须先失能,这条也常被跳过。
第四条:换型之后同一套程序,A 产品准、B 产品偏。现象是标定参数没动,A 工件对位误差 0.03 毫米,B 工件到了 0.3 毫米。原因是工件高度不同,镜头的景深和放大倍率随物距变化,用 A 的高度标出来的仿射矩阵套到 B 上,比例系数就不对了。解决是把工件高度做成参数,每个高度单独标一套矩阵,或者用远心镜头把物距敏感度压下去。前者成本低、后者精度高,按换型频率选。玄学问题大多有物理原因,这条就是典型。
第五条:连续跑两小时后来位不准,重启程序又恢复。现象是前两小时正常,之后对位偏差缓慢变大,重启上位机就好。原因通常是角度补偿用了累积量——每次对位都在上一次的结果上叠加微小修正,误差单向累积,跑久了就漂出去。解决是每次对位都以“标定基准 + 本次绝对偏差”重新计算,不做增量叠加;同时在日志里记录每次的绝对偏差值,画一条趋势线,漂移一眼就能看出来。这条不改,跑一整天必然翻车。
6. 节拍从 800 ms 压到 220 ms 的四个改法
最后讲压节拍。同一套硬件,把周期从 800 毫秒压到 220 毫秒,我实际做过,靠的不是换相机也不是换 PLC,而是四个改动,按收益排序。
| 改法 | 改动前 | 改动后 | 风险 |
|---|---|---|---|
| 通信整块读写 | 逐点读写约 320 ms | 一次读写约 25 ms | 寄存器地址要重排,需回归测试 |
| 硬触发替代软触发 | 触发抖动 10 到 20 ms | 抖动小于 1 ms | 需占用一个 PLC 输出点 |
| ROI 动态收缩 + 形状匹配 | 找 Mark 约 90 ms | 约 18 ms | 首次上料需全图搜索兜底 |
| 计算与下发并行 | 串行约 60 ms | 约 20 ms | 队列要有界,防堆积 |
第一刀是通信。把原来分散在十几个 D 寄存器里的参数整理成一段连续区,一次读完一次写完,节拍立刻掉下来一大块,也是最没有技术含量但最有效的一刀。
第二刀是触发。软触发的不确定延迟在节拍紧张时非常难受,改成 PLC 输出点硬触发,相机收到电平就曝光,时间戳才可复现。相机侧把曝光时间和增益固定,不要开自动曝光,自动曝光的收敛过程会让每张图的亮度都不一样,找 Mark 的阈值跟着飘。
第三刀是算法。找 Mark 不要整图跑模板匹配,先用上一次结果把 ROI 收缩到特征点附近 200 像素见方,再在这个小区域里做形状匹配或边缘拟合。首次上料或换型时用全图搜索兜底,正常生产走小 ROI。这一步在 LabVIEW 里就是把 ROI 的左上角和尺寸做成输入参数,别写死常量。
第四刀是并行。取图、算坐标、写参数这三件事没必要严格串行,取下一张图的同时把上一张的结果下发下去,用两个循环加一个有界队列衔接。队列长度设 2 就够,设大了反而会在异常时堆积出一串过期指令。
我自己的习惯是:每次改完只测一个指标——连续 200 个周期的耗时分布,看 P95 而不是平均值。平均值好看但 P95 抖到 600 毫秒的设备,产线一样会停。测完把分布图存下来,下次再改有对照。这套东西不难,难的是每次都想跳过验证直接上线,我吃过这个亏,希望帮到你。
本文还有配套的精品资源,点击获取