装配工位
TIA 设备名 = 配置工具站点名(例如 asm-st-01)。
硬件配置
- PLC:西门子 S7-1200 × 1(夹具夹紧 / 放行 / NG 剔除)
- 工控机:Windows 10 × 1 + Intel i5 × 1,跑 Darra Profinet Slave
- 视觉:工业面阵相机 × 1 + 镜头 × 1 + 环形光源 × 1
- 夹紧工装:气缸 × 1,夹紧到位磁性开关 × 1。PLC 管夹紧、松开、放行、NG 剔除
- 网络:标准千兆网卡 × 1(与 PLC 同一 PROFINET 网)
成本分析
| 类别 | 型号/规格 | 参考单价 (¥) | 数量 | 小计 (¥) |
|---|---|---|---|---|
| PLC | 西门子 S7-1200 CPU 1214C | 1,000 | 1 | 1,000 |
| 工控机 | i5 无风扇双网口 | 1,200 | 1 | 1,200 |
| 面阵相机 | 海康 MV-CA013 130 万 USB3 | 950 | 1 | 950 |
| 镜头 | C 口工业定焦 | 200 | 1 | 200 |
| 环形光源 | 机器视觉 LED | 150 | 1 | 150 |
| 气动夹具 | 亚德客附磁气缸 + 磁性开关 | 120 | 1 | 120 |
| 整线参考总计 | ≈ ¥3,620 |
淘宝常见成交价(2026-09),不含机架、气管、线缆、治具底板。PLC 取 1214C 正品常见区间,相机取海康 130 万 USB3 店售价,不取几十元板级货。
性能指标
- 对位判定:本地完成,过程数据只回 OK / NG / 完成
- 配方:结构体字段 Recipe 选本地模板,工位可挂多套
- 节拍:视觉小于 150 ms/件(仿真用睡眠代替)
- 图、偏移明细不进过程数据
应用场景
压装 / 拧紧 / 点胶前的装配工位。比 小型视觉质检 多三路:夹紧到位、完成边沿、多配方模板。PLC 管夹紧、放行、NG 剔除;工控机按配方在本地对位。
工位时序
- 有料 — PLC 置
Present。 - 夹紧到位才触发 — PLC 夹紧,等磁性开关
Clamped。未夹紧不得拉Trigger。工控机侧同样:无Clamped的上升沿丢掉。 - 对位上升沿 —
Clamped后 PLC 拉高Trigger。工控机只认 0→1,且Present、Clamped、未忙碌。 - 忙碌互斥 — 认沿后清上次 OK/NG/
Done,Ready=0、Busy=1。忙碌期间新沿丢掉。 - 配方选本地模板 —
Recipe只当模板号,对位图和偏移留在工控机。 - Done 边沿 — 判定结束:
Ok或Ng,Busy=0、Ready=1,Done0→1。PLC 用Done上升沿步进(OK 放行 / NG 剔除 / 松开)。Done保持到下一件认沿时拉回 0,下一件结束再产生新的上升沿。 - 掉线 — 曾经连上过、后来又断开:
Fault=1,Ready拿掉。重连后重新置就绪,当前Trigger电平不当成新沿。
过程数据
过程数据只回 OK / NG / 完成和配方号。对位图、偏移明细、存档路径留在工控机。
过程数据
| 方向 | 字段 | 工位含义 |
|---|---|---|
| 来自 PLC(I) | Present | 有料 |
| 来自 PLC(I) | Clamped | 夹紧到位;未到位不认 Trigger |
| 来自 PLC(I) | Trigger | 对位触发,只认上升沿 |
| 来自 PLC(I) | Recipe | 配方号,本地选对位模板 |
| 发给 PLC(Q) | Ready | 可接下一件 |
| 发给 PLC(Q) | Busy | 正在对位,互斥 |
| 发给 PLC(Q) | Ok | 对位合格 → PLC 放行 |
| 发给 PLC(Q) | Ng | 对位不合格 → PLC 打剔除 |
| 发给 PLC(Q) | Fault | 掉线/故障 |
| 发给 PLC(Q) | Done | 本件判定结束,PLC 采上升沿 |
连上之后再按上面握手读写。仿真按件数造 OK/NG(每 7 件 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 Clamped;
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;
public bool Done;
}
using (var device = new DarraPnS())
{
PnSErrorCode rc = device.Start();
if (rc != PnSErrorCode.Success)
return;
string[] templates = { "default.bin", "housing-a.bin", "housing-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 && input.Clamped && !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 % 7) != 0;
output = new ToPlc { Ready = true, Ok = ok, Ng = !ok, Done = true };
device.ProcessData.Write(ref output);
busy = false;
Console.WriteLine(
"第 " + piece + " 件 模板 " + template + " → " + (ok ? "OK 放行" : "NG 剔除") + " Done");
}
Thread.Sleep(10);
}
device.Stop();
}