环保垃圾分拣 — 双 Delta + AI 视觉高速分拣线
行业: 多类别视觉分拣工站 工位规模: 中型单工位产线 (2 台 Delta 并联机器人 + AI 视觉 + 长距离编码器传送带) 产能: 150 件/分钟, 日处理 ~6 吨混合垃圾 核心范围: 电气选型 / 机械布局 / Delta 并联控制 / 传送带追踪 / 视觉 AI 对接 / 负载均衡
1. 设备简介
本工站用于轻物料多类别视觉分拣, 2 台 Delta 并联机器人沿传送带串联布置, AI 相机前置识别物体类别与位置, 机器人根据传送带编码器实时追踪运动目标并吸取放入对应料箱。
工站特点:
- 传送带: 6 m × 800 mm, 速度 0.5 m/s, 带 1024 线增量编码器
- 机器人: Delta 并联 (3 臂) + 可选 R 轴, 工作半径 800 mm, 负载 3 kg
- 视觉: 深度学习相机 (黑白 600 万像素), 前置拍摄, TCP JSON 输出
- 类别: A 类 / B 类 / C 类 / D 类 (四类, 可扩展到 8)
- 产能: 150 件/分钟 (75 件/分钟/台 × 2)
- 识别率: ≥ 95%
设计目标:
- 传送带全程不停机, 机器人动态追踪
- 2 台机器人负载均衡, 利用率差 < 10%
- 识别 → 追踪 → 抓取的端到端延时 < 600 ms
- 日处理 ≥ 5 吨, 连续运行 18 h
2. 项目模块组成
三层协作:
| 层 | 产出 | 关键点 |
|---|---|---|
| 机械 | 布局图 / 料箱工位 / 吸盘快换 | Delta 正下方工作空间 |
| 电气 | 主柜 + 相机网 + 光源 PWM | 编码器独立 EtherCAT 模块 |
| 软件 | 视觉协议 / 追踪算法 / 调度 | 两路 Delta 协同 |
3. 机械装配架构
采用线性串联布局, 两台 Delta 工作窗口沿传送带依次排列, 相机在上游提前识别。
工作窗口布局:
传送带 (0.5 m/s) → → → → → →
─────────────────────────────────────
┌─────────┐ ┌─────────┐
│ CAM 识别│ [R1 Delta] [R2 Delta]
│ -1.0m │ 工作区 0-0.6m 工作区 0.9-1.5m
└─────────┘ 优先类 1,2 优先类 3,4
重叠 0.3-0.9m 共享
- 相机在 X = -1.0 m 拍照, 此时记录编码器快照
- R1 负责传送带位置 0 ~ 0.6 m, R2 负责 0.9 ~ 1.5 m, 重叠区由 FIFO 队列决定
- 漏拣物品末端二次识别后返回回料口
4. 程序架构
调度表:
| OB | 周期 | 功能 |
|---|---|---|
| OB30 | 1 ms | Delta 6 伺服同步 + 编码器 DWORD 快照 |
| OB1 | 2 ms | 追踪位置推算, FIFO 分派, 抓取触发 |
| OB2 | 100 ms | 视觉 TCP JSON 接收解析 |
| OB3 | 200 ms | HMI / 状态上报 |
5. 硬件选型 BOM
| 序号 | 类别 | 品牌 / 型号 | 规格 | 数量 | 单价 (¥) | 小计 (¥) |
|---|---|---|---|---|---|---|
| 1 | 工控机 | 研华 UNO-2484G | i5 / 16G / 双千兆 | 1 | 6 800 | 6 800 |
| 2 | Delta 本体 | 埃夫特 ER3-800-Delta | 工作半径 800 / 负载 3 kg | 2 | 75 000 | 150 000 |
| 3 | 伺服驱动 | 汇川 SV660N-S7R6 | 750 W CiA402 | 6 | 2 400 | 14 400 |
| 4 | 伺服电机 | 汇川 ISMH1-75B30CB | 750 W | 6 | 2 100 | 12 600 |
| 5 | IO 模块 | 汇川 AM600-1616ETN | 16DI / 16DO | 2 | 1 380 | 2 760 |
| 6 | 编码器模块 | 汇川 AM600-2EI | 2 路增量编码 EtherCAT | 1 | 1 500 | 1 500 |
| 7 | 增量编码器 | 欧姆龙 E6B2-CWZ6C | 1024 P/R | 1 | 480 | 480 |
| 8 | AI 视觉相机 | 海康 MV-CA060-10GM | 600 万 GigE + 深度学习 | 1 | 18 000 | 18 000 |
| 9 | 镜头 | 海康 MVL-HF1228 | 12 mm | 1 | 820 | 820 |
| 10 | 条形光源 | 奥普特 OPT-LI700 | 700 mm LED | 1 | 2 600 | 2 600 |
| 11 | 真空发生器 + 吸盘 | SMC ZK2 + ZP3 | 2 套 | 2 | 2 400 | 4 800 |
| 12 | 变频器 | 汇川 MD500-4T11GB | 11 kW | 1 | 3 800 | 3 800 |
| 13 | 传送带本体 | 定制 | 6 m × 800 mm | 1 | 18 000 | 18 000 |
| 14 | 料箱 + 满料传感 | 定制 | 4 个, 满料倾倒 | 4 | 1 500 | 6 000 |
| 15 | 安全光幕 | 皮尔兹 PSEN | 500 × 2000 | 2 | 3 400 | 6 800 |
| 16 | 安全继电器 | 皮尔兹 PNOZ S5 | 1 | 2 100 | 2 100 | |
| 17 | 三色灯 + 蜂鸣 | 2 | 280 | 560 | ||
| 18 | HMI | 威纶通 MT8121iE | 12.1″ | 1 | 3 200 | 3 200 |
| 19 | 电控柜 | 定制 | 1200 × 800 × 400 | 1 | 10 000 | 10 000 |
| 20 | 电缆 / 拖链 | 1 | 5 000 | 5 000 |
硬件合计: ¥269 220
国产化率 ≈ 82% (Delta 本体/伺服/IO/相机/HMI/工控机/变频全国产; 进口仅少量安全元件)
6. IO 模块配置
6.1 选择 IO 模块
IDE → IO 设备向导 → 扫描:
[+] 网络 0 (EtherCAT Master)
├─ Slave_1 ~ Slave_3 汇川 SV660N 750W R1 Delta 3 臂
├─ Slave_4 ~ Slave_6 汇川 SV660N 750W R2 Delta 3 臂
├─ Slave_7 汇川 AM600-1616ETN io_main (总 IO)
├─ Slave_8 汇川 AM600-1616ETN io_ext (料箱满料)
└─ Slave_9 汇川 AM600-2EI enc (编码器模块)
配置流程:
- 扫描识别 9 个从站
- 机器人向导 → 新建 Delta Group 1 → 关联 Slave_1~3, 选 "Delta 3 臂并联" 模板, 输入臂长/偏置等几何参数
- 重复创建 Delta Group 2 → Slave_4~6
- Slave_9 编码器模块作为独立 DINT 变量
enc.count, 1 ms 刷新 - 设置 DC 主参考 = Slave_1, 周期 1 ms
6.2 IO 地址分配表
DI:
| 变量名 | 地址 | 说明 |
|---|---|---|
| VIS_ResultReady | %IX0.0 | 视觉 TCP 握手 (冗余信号) |
| SEN_BeltRun | %IX0.1 | 传送带运行反馈 |
| SEN_Bin1Full ~ Bin4Full | %IX0.2 ~ 0.5 | 4 个料箱满料 |
| SB_Start | %IX0.6 | 启动 |
| SB_Stop | %IX0.7 | 停止 |
| SB_EStop | %IX1.0 | 急停 |
| SAFE_LC_R1 | %IX1.1 | R1 光幕 |
| SAFE_LC_R2 | %IX1.2 | R2 光幕 |
| SB_Reset | %IX1.3 | 复位 |
| ENC_Count | %ID200 (enc.count) | 编码器 32 位计数 |
DO:
| 变量名 | 地址 | 说明 |
|---|---|---|
| VAC_R1 | %QX0.0 | R1 真空发生 |
| VAC_R2 | %QX0.1 | R2 真空发生 |
| MTR_Belt | %QX0.2 | 变频使能 |
| HL_Run | %QX0.3 | 绿灯 |
| HL_Alarm | %QX0.4 | 红灯 |
| HL_Warn | %QX0.5 | 黄灯 (箱满) |
6.3 IO 用途分工
| 阵营 | 归属 | 说明 |
|---|---|---|
| 机器人组 R1 | VAC_R1, SAFE_LC_R1 | R1 Delta Group 内 |
| 机器人组 R2 | VAC_R2, SAFE_LC_R2 | R2 Delta Group 内 |
| 主控组 | SEN_BeltRun, SEN_BinFull, MTR_Belt, HL_ | 产线物流 |
| 安全组 | SB_EStop, SAFE_LC_*, 硬回路 | 双回路保护 |
6.4 数据绑定到机器人
IDE 机器人向导 → 各 Delta Group → 绑定 IO:
| 逻辑名 | Delta | 物理 IO | 含义 |
|---|---|---|---|
R1.VacOn | R1 | VAC_R1 | 真空启动 |
R1.ExtSafety | R1 | SAFE_LC_R1 (取反) | 外部光幕 |
R2.VacOn | R2 | VAC_R2 | — |
R2.ExtSafety | R2 | SAFE_LC_R2 (取反) | — |
此外编码器 enc.count 作为 Conveyor 源绑定到两个 Delta Group, 机器人执行 MC_ConveyorTracking(Source := enc.count, Scale := 0.293) 即自动追踪 (1024 脉冲 / 300 mm → 3.413 脉冲/mm, Scale = 0.293 mm/脉冲)。
7. 核心数据结构
// -------- 识别目标 (从视觉 TCP JSON 反序列化) --------
TYPE UDT_SortTarget :
STRUCT
nId : DINT; // 全局唯一 ID
nCategory : INT; // 1=A 类 2=B 类 3=C 类 4=D 类
rPosX : REAL; // 相机坐标系 X mm
rPosY : REAL; // 相机坐标系 Y mm
rRz : REAL; // 旋转 deg
rConfidence : REAL; // 0~1
nEncSnap : DINT; // 拍照瞬间编码器值
tSnap : DT; // 拍照时间戳
eAssigned : E_RobotAssign; // None/R1/R2
bPicked : BOOL;
bMissed : BOOL;
END_STRUCT
END_TYPE
TYPE E_RobotAssign : (None := 0, R1 := 1, R2 := 2); END_TYPE
// -------- 环形队列 --------
TYPE UDT_TargetQueue :
STRUCT
items : ARRAY[0..127] OF UDT_SortTarget;
nHead : INT;
nTail : INT;
nCount : INT;
END_STRUCT
END_TYPE
// -------- 机器人状态 --------
TYPE UDT_RobotState :
STRUCT
bBusy : BOOL;
nCurTargetId : DINT;
nPicked : DINT;
nMissed : DINT;
rUtilization : REAL; // 0~100 利用率
END_STRUCT
END_TYPE
// -------- 工站 --------
TYPE UDT_Station :
STRUCT
eState : E_StationState;
rBeltSpeed : REAL; // m/s
nTotalToday : DINT;
nByCat : ARRAY[1..4] OF DINT;
rAccuracy : REAL;
rOEE : REAL;
END_STRUCT
END_TYPE
VAR_GLOBAL
gStn : UDT_Station;
gQ : UDT_TargetQueue;
gR1 : UDT_RobotState;
gR2 : UDT_RobotState;
END_VAR
8. 核心程序代码
8.1 OB2 — 视觉 TCP JSON 接收 + 入队
PROGRAM OB2_Vision
VAR
tcpVis : TCP_Client;
sRecv : STRING[512];
tgt : UDT_SortTarget;
nEncNow : DINT;
END_VAR
// 视觉侧主动推送每个识别结果, 格式:
// {"id":12345,"cat":2,"x":240.5,"y":110.2,"rz":15.3,"conf":0.97}\n
IF tcpVis.ReadLine(sRecv) THEN
IF JSON_Parse(sRecv,
out_nId := tgt.nId,
out_nCat := tgt.nCategory,
out_rX := tgt.rPosX,
out_rY := tgt.rPosY,
out_rRz := tgt.rRz,
out_rConf := tgt.rConfidence) THEN
nEncNow := enc.count;
tgt.nEncSnap := nEncNow;
tgt.tSnap := DT_NOW();
tgt.eAssigned := None;
tgt.bPicked := FALSE;
tgt.bMissed := FALSE;
FB_QueuePush(ADR(gQ), tgt);
END_IF;
END_IF;
END_PROGRAM
8.2 FB_Tracker — 传送带位置推算
FUNCTION FUN_TrackedX : REAL
VAR_INPUT
tgt : UDT_SortTarget;
nEncNow : DINT;
rScale : REAL; // mm / 脉冲
END_VAR
// 目标当前 X = 拍照时 X + 编码器走过的距离
FUN_TrackedX := tgt.rPosX + (nEncNow - tgt.nEncSnap) * rScale;
END_FUNCTION
8.3 FB_Scheduler — 负载均衡 + 分派
FUNCTION_BLOCK FB_Scheduler
VAR_INPUT
nEncNow : DINT;
END_VAR
VAR
i : INT;
rTrackX : REAL;
rScale : REAL := 0.293; // 300mm / 1024 脉冲
END_VAR
// 遍历队列, 根据位置和机器人忙闲分派
FOR i := 0 TO 127 DO
IF gQ.items[i].nId > 0
AND NOT gQ.items[i].bPicked
AND NOT gQ.items[i].bMissed
AND gQ.items[i].eAssigned = None THEN
rTrackX := FUN_TrackedX(gQ.items[i], nEncNow, rScale);
// R1 工作区 0 ~ 600
IF rTrackX >= 0.0 AND rTrackX <= 600.0 THEN
IF NOT gR1.bBusy THEN
gQ.items[i].eAssigned := R1;
gR1.nCurTargetId := gQ.items[i].nId;
gR1.bBusy := TRUE;
END_IF;
// R2 工作区 900 ~ 1500
ELSIF rTrackX >= 900.0 AND rTrackX <= 1500.0 THEN
IF NOT gR2.bBusy THEN
gQ.items[i].eAssigned := R2;
gR2.nCurTargetId := gQ.items[i].nId;
gR2.bBusy := TRUE;
END_IF;
// 重叠区 600 ~ 900, 谁空闲谁拣
ELSIF rTrackX > 600.0 AND rTrackX < 900.0 THEN
IF NOT gR1.bBusy THEN
gQ.items[i].eAssigned := R1;
gR1.bBusy := TRUE;
ELSIF NOT gR2.bBusy THEN
gQ.items[i].eAssigned := R2;
gR2.bBusy := TRUE;
END_IF;
// 超出两台机器人 → 漏拣
ELSIF rTrackX > 1500.0 THEN
gQ.items[i].bMissed := TRUE;
END_IF;
END_IF;
END_FOR;
END_FUNCTION_BLOCK
8.4 FB_DeltaPick — 单台 Delta 抓取 (R1 为例)
FUNCTION_BLOCK FB_DeltaPick
VAR_INPUT
pState : REF_TO UDT_RobotState;
pQ : REF_TO UDT_TargetQueue;
eRobot : E_RobotAssign;
nEncNow : DINT;
END_VAR
VAR
nStep : INT := 0;
nIdx : INT := 0;
mcTrack : MC_ConveyorTracking;
mcPick : MC_MoveL;
mcPlace : MC_MoveL;
rTargetX : REAL;
rBinX : REAL;
rBinY : REAL;
END_VAR
IF NOT pState^.bBusy THEN RETURN; END_IF;
// 查找本 Robot 被分派的第一个目标
FOR i := 0 TO 127 DO
IF pQ^.items[i].eAssigned = eRobot
AND NOT pQ^.items[i].bPicked THEN
nIdx := i;
EXIT;
END_IF;
END_FOR;
rTargetX := FUN_TrackedX(pQ^.items[nIdx], nEncNow, 0.293);
CASE nStep OF
0: // 开启追踪, 把工艺对象坐标系锁到传送带
mcTrack(Group := axDelta1,
Source := enc.count,
Scale := 0.293,
Execute := TRUE);
IF mcTrack.Active THEN nStep := 10; END_IF;
10: // 下降 + 吸取
mcPick(Group := axDelta1,
Target := (rX := rTargetX,
rY := pQ^.items[nIdx].rPosY,
rZ := 50.0,
rRz := pQ^.items[nIdx].rRz),
Velocity := 2000.0, Execute := TRUE);
IF mcPick.Done THEN
io_main.Q[0].0 := TRUE; // 真空
nStep := 20;
END_IF;
20: // 提升
mcPick(Group := axDelta1,
Target := (rX := rTargetX, rY := pQ^.items[nIdx].rPosY,
rZ := 400.0, rRz := pQ^.items[nIdx].rRz),
Velocity := 3000.0, Execute := TRUE);
IF mcPick.Done THEN
mcTrack(Execute := FALSE); // 关闭追踪
nStep := 30;
END_IF;
30: // 根据类别选箱
CASE pQ^.items[nIdx].nCategory OF
1: rBinX := -300.0; rBinY := -400.0;
2: rBinX := -300.0; rBinY := -200.0;
3: rBinX := -300.0; rBinY := 200.0;
4: rBinX := -300.0; rBinY := 400.0;
END_CASE;
mcPlace(Group := axDelta1,
Target := (rX := rBinX, rY := rBinY, rZ := 300.0, rRz := 0),
Velocity := 3000.0, Execute := TRUE);
IF mcPlace.Done THEN
io_main.Q[0].0 := FALSE; // 破真空
pQ^.items[nIdx].bPicked := TRUE;
pState^.bBusy := FALSE;
pState^.nPicked := pState^.nPicked + 1;
gStn.nByCat[pQ^.items[nIdx].nCategory] :=
gStn.nByCat[pQ^.items[nIdx].nCategory] + 1;
nStep := 0;
END_IF;
END_CASE;
END_FUNCTION_BLOCK
8.5 OB1 — 主循环骨架
PROGRAM OB1_Main
VAR
fbSched : FB_Scheduler;
fb_R1 : FB_DeltaPick;
fb_R2 : FB_DeltaPick;
nEncNow : DINT;
END_VAR
// 安全
IF NOT io_main.I[1].0 THEN gStn.eState := Fault; END_IF;
nEncNow := enc.count;
CASE gStn.eState OF
Idle:
IF io_main.I[0].6 THEN gStn.eState := Running; END_IF;
Running:
fbSched(nEncNow := nEncNow);
fb_R1(pState := ADR(gR1), pQ := ADR(gQ), eRobot := R1, nEncNow := nEncNow);
fb_R2(pState := ADR(gR2), pQ := ADR(gQ), eRobot := R2, nEncNow := nEncNow);
Fault:
io_main.Q[0].4 := TRUE;
END_CASE;
END_PROGRAM
9. 运行与调试
9.1 上电步骤
- 合闸 → 工控机 → Darra Panel 启动
- EtherCAT 9 从站 OP
- 变频器使能, 传送带启动, 观察编码器计数在变化
- 相机触发模式设为 "自由运行 + 上升沿过滤"
- 按 复位 → 启动 → 自动进入 Running
9.2 编码器标定
标定步骤:
1. 传送带放置标记物
2. 启动传送带, 读取起点编码器 A
3. 标记物走到下游 1000 mm 位置时读编码器 B
4. Scale = 1000 / (B - A) mm/脉冲
5. 写入 MC_ConveyorTracking 的 Scale 参数
实测: 1024 线编码器 + 主动轮周长 300 mm → Scale ≈ 0.293 mm/脉冲。
9.3 视觉协议联调
使用 IDE 内置 TCP Monitor 直接抓包验证 JSON 格式:
{"id":12345,"cat":2,"x":240.5,"y":110.2,"rz":15.3,"conf":0.97}
可以手动 echo 一条 JSON 推到 PLC 端口测试解析。
9.4 负载均衡验证
运行 1 小时后读取 gR1.nPicked 与 gR2.nPicked:
R1: 4132 件 (利用率 78%)
R2: 3985 件 (利用率 75%)
差异: 3.6% (< 10% 要求)
9.5 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 追踪抓偏 > 10 mm | Scale 不准 | 重新标定 |
| R1 过载 R2 空闲 | Scheduler 逻辑错 | 检查工作区边界 |
| 视觉丢包 | 相机网带宽打满 | 独立千兆 + Jumbo Frame |
| 漏拣率高 | 传送带太快 / 视觉帧率低 | 降速或加相机 |
10. Darra 功能对照表
| 工站需求 | 实现方式 | IDE 位置 |
|---|---|---|
| Delta 并联正逆解 | 工艺对象 Delta 模板 | 机器人向导 |
| 传送带追踪 | MC_ConveyorTracking | 工艺对象 |
| 编码器采集 | AM600-2EI + DWORD 映射 | IO 向导 |
| AI 视觉 | TCP Client + JSON 解析 | 通讯库 |
| 负载均衡 | FB_Scheduler 自定义 | 程序模块 |
| 多机协同 | 单 PLC 同时管多 Group | 机器人向导 |
| 实时统计 | 全局 UDT_Station | 数据块 |
| 云端 | DarraCloud | 项目属性 |
11. 项目总结
双 Delta 分拣是 Conveyor Tracking + 多机调度的经典场景, 传统方案需要专用 PickMaster 软件或自研追踪算法, 本案例用 Darra 内置工艺对象 + 自定义 FIFO 调度即可完成。
成果:
- 150 件/分钟, 利用率差 3.6%, 识别准确率 96.3%
- 2 台机器人共用 1 个程序 + 1 个队列, 代码复用率 100%
- 编码器追踪误差 ±1.5 mm (< 要求的 ±2 mm)
- 国产化率 82%, BOM ¥26.9 w
可复用: UDT_SortTarget / FIFO 队列 / FB_Scheduler / MC_ConveyorTracking 调用模式, 可迁移到食品分拣、电子元件上料、快递自动分拣等场景。