小型视觉质检
TIA 设备名 = 配置工具站点名(例如 vis-qc-01)。
硬件配置
- 工控机:Windows × 1
- 相机 × 1
- PLC × 1
- 光电 × 1 + 剔除气缸 × 1
性能指标
- 节拍:500 ms / 件(120 件 / min)
- 检测时间:小于 150 ms
应用场景
小型产线视觉质检工位。PLC 管来料、停稳、放行、NG 剔除;工控机只跑相机和判定,网上是一台 IO Device。图、尺寸、存档留在工控机,不进过程数据。配方号只用来在本地选检测模板。
工位时序
- 光电有料 — 工件挡住光电,PLC 置
Present。无料不拍。 - 拍照上升沿 — 停稳后 PLC 把
Trigger拉高。工控机只认 0→1,且当时Present、未忙碌。 - 忙碌互斥 — 认沿后立刻清上次 OK/NG,
Ready=0、Busy=1。忙碌期间新沿丢掉,不叠拍。 - 本地判图 — 按
Recipe选本地模板,取图判定(仿真用睡眠代替相机)。 - OK / NG — 合格
Ok=1,不合格Ng=1;然后Busy=0、Ready=1。PLC 看 OK 放行,看 NG 打剔除气缸。 - 掉线 — 曾经连上过、后来又断开:
Fault=1,Ready拿掉,不再判图。重连后重新置就绪,当前Trigger电平不当成新沿。
过程数据
| 方向 | 字段 | 工位含义 |
|---|---|---|
| 来自 PLC(I) | Present | 光电有料 |
| 来自 PLC(I) | Trigger | 拍照触发,只认上升沿 |
| 来自 PLC(I) | Recipe | 配方号,本地选检测模板 |
| 发给 PLC(Q) | Ready | 可接下一件 |
| 发给 PLC(Q) | Busy | 正在拍/判,互斥 |
| 发给 PLC(Q) | Ok | 本件合格 → PLC 放行 |
| 发给 PLC(Q) | Ng | 本件不合格 → PLC 打剔除 |
| 发给 PLC(Q) | Fault | 掉线/故障 |
连上之后再按上面握手读写。下面这段按件数造 OK/NG(每 5 件 1 个 NG)。真机把 Thread.Sleep 换成取图和本地算法,把模板路径换成自己的。
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Darra.PnS;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct FromPlc
{
public bool Present;
public bool Trigger;
public short Recipe;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ToPlc
{
public bool Ready;
public bool Busy;
public bool Ok;
public bool Ng;
public bool Fault;
}
using (var device = new DarraPnS())
{
PnSErrorCode rc = device.Start();
if (rc != PnSErrorCode.Success)
return;
string[] templates = { "default.bin", "sku-a.bin", "sku-b.bin" };
ToPlc output = default;
bool lastTrigger = true;
bool wasConnected = false;
bool busy = false;
int piece = 0;
bool running = true;
while (running)
{
if (!device.Connected)
{
if (wasConnected)
{
output = new ToPlc { Fault = true };
device.ProcessData.Write(ref output);
lastTrigger = true;
busy = false;
}
wasConnected = false;
Thread.Sleep(20);
continue;
}
if (!wasConnected)
{
output = new ToPlc { Ready = true };
device.ProcessData.Write(ref output);
lastTrigger = true;
busy = false;
wasConnected = true;
}
FromPlc input = new FromPlc();
device.ProcessData.Read(ref input);
bool rising = input.Trigger && !lastTrigger;
lastTrigger = input.Trigger;
if (rising && input.Present && !busy)
{
busy = true;
output = new ToPlc { Busy = true };
device.ProcessData.Write(ref output);
string template = templates[0];
int recipe = input.Recipe;
if (recipe >= 0 && recipe < templates.Length)
template = templates[recipe];
Thread.Sleep(120);
piece++;
bool ok = (piece % 5) != 0;
output = new ToPlc { Ready = true, Ok = ok, Ng = !ok };
device.ProcessData.Write(ref output);
busy = false;
Console.WriteLine(
"第 " + piece + " 件 模板 " + template + " → " + (ok ? "OK 放行" : "NG 剔除"));
}
Thread.Sleep(10);
}
device.Stop();
}