跳到主要内容

小型视觉质检

TIA 设备名 = 配置工具站点名(例如 vis-qc-01)。

硬件配置​

  • 工控机:Windows × 1
  • 相机 × 1
  • PLC × 1
  • 光电 × 1 + 剔除气缸 × 1

性能指标​

  • 节拍:500 ms / 件(120 件 / min)
  • 检测时间:小于 150 ms

应用场景​

小型产线视觉质检工位。PLC 管来料、停稳、放行、NG 剔除;工控机只跑相机和判定,网上是一台 IO Device。图、尺寸、存档留在工控机,不进过程数据。配方号只用来在本地选检测模板。

工位时序​

  1. 光电有料 — 工件挡住光电,PLC 置 Present。无料不拍。
  2. 拍照上升沿 — 停稳后 PLC 把 Trigger 拉高。工控机只认 0→1,且当时 Present、未忙碌。
  3. 忙碌互斥 — 认沿后立刻清上次 OK/NG,Ready=0、Busy=1。忙碌期间新沿丢掉,不叠拍。
  4. 本地判图 — 按 Recipe 选本地模板,取图判定(仿真用睡眠代替相机)。
  5. OK / NG — 合格 Ok=1,不合格 Ng=1;然后 Busy=0、Ready=1。PLC 看 OK 放行,看 NG 打剔除气缸。
  6. 掉线 — 曾经连上过、后来又断开: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();
}