C# 上位机实战:PLC(S7-1200) 以太网通信 + 机器视觉系统架构(上篇)
一台设备要"自己会干活",通常是PLC 控制机械动作 + 上位机 + 机器视觉(相机)三者配合:PLC 告诉上位机"现在该拍照了",上位机拍照算偏差,再回写 PLC 做补偿。这篇用一个真实项目,先把S7.Net 连 PLC、通讯信号表、坐标/偏移读写讲透;下篇讲视觉检测、九点标定和旋转中心拟合。(下篇见《C# 上位机实战:视觉检测与九点标定、旋转中心拟合完整实现》)
一、项目是干什么的?(按功能命名)
这是一个PLC 通信 + WinForm + VisionPro 机器视觉的综合上位机:通过以太网连西门子 S7-1200 PLC,同时控制 Cognex(康耐视)相机,实现视觉引导定位、偏差计算、回写 PLC 补偿的自动测量与矫正闭环。
┌─────────────── 上位机 (C# WinForm) ───────────────┐ │ PLC通信 (S7.Net) 机器视觉 (VisionPro) │ │ PlcOperator 类 Vision 类 │ │ 读: 坐标/拍照编号 加载 VPP ToolBlock │ │ 写: 偏移/确认编号 拍照→识别→算偏差 │ └──────┬──────────────────────┬──────────────────────┘ │ 以太网 │ GigE 相机 西门子 S7-1200 Cognex 相机(识别+标定)核心链路:PLC 发"拍照编号" → 上位机拍照识别 → 计算 X/Y/R 偏移 → 回写 PLC → PLC 补偿动作。
二、通讯信号表:PLC 和上位机怎么"说话"
项目根目录有一份《与上位机通讯信号.xlsx》,定义了两块数据区的地址约定,这是整个系统的"接口协议"。
1. PLC → 上位机(DB50,只读)
| 名称 | 类型 | 地址 | 说明 |
|---|---|---|---|
| 设备故障代码 | DInt | DB50.DBD0 | 0 表示无故障 |
| 测量模式拍照编号 | DInt | DB50.DBD4 | 测量时通知上位机拍照(=1 第一次、=2 第二次) |
| 9点矫正拍照编号 | DInt | DB50.DBD8 | 九点矫正时通知拍照(=1~9 共九次) |
| 中心矫正拍照编号 | DInt | DB50.DBD12 | 中心矫正时通知拍照(=1~3) |
| 当前控制模式 | DInt | DB50.DBD16 | 0手动 / 1检测运行 / 2九点矫正 / 3暂停 / 4中心矫正 |
| X/Y/Z/R 轴坐标 | Real | DB50.DBD20/24/28/32 | 设备当前机械坐标 |
2. 上位机 → PLC(DB51,只写)
| 名称 | 类型 | 地址 | 说明 |
|---|---|---|---|
| 角度偏移 R | Real | DB51.DBD0 | 第一次拍照算出的角度偏差 |
| X 轴偏移 | Real | DB51.DBD4 | 第二次拍照算出的 X 偏差 |
| Y 轴偏移 | Real | DB51.DBD8 | 第二次拍照算出的 Y 偏差 |
| 测量拍照确认编号 | DInt | DB51.DBD12 | 拍完照写回编号,告诉 PLC"拍完了" |
| 中心矫正拍照确认编号 | DInt | DB51.DBD16 | 同上 |
| 放料 1-2 位选择 | DInt | DB51.DBD20 | 0 放料位一 / 1 放料位二 |
| 9点矫正拍照确认编号 | DInt | DB51.DBD24 | 同上 |
握手协议:PLC 写"拍照编号"通知上位机 → 上位机拍完照 → 先写"偏移值",再写"确认编号" → PLC 收到确认后执行下一步。必须先发偏移值再发确认编号,顺序不能反。
三、PlcOperator:PLC 操作单例类
用一个单例封装 PLC 读写,全程序共享同一个连接:
internalclassPlcOperator{// 单例:保证每次 new 都是同一个对象privatestaticreadonlyLazy<PlcOperator>_instance=newLazy<PlcOperator>(()=>newPlcOperator());publicstaticPlcOperatorInstance{get{return_instance.Value;}}privatePlcplc=null;privateTimertimer=null;// 三组"拍照编号变化"事件(测量/九点/中心矫正)publiceventAction<int>MeasureNoChange;publiceventAction<int>NPointCalibrationNoChange;publiceventAction<int>CenterCalibrationNoChange;// ... 地址常量 + 读写属性}1. 连接 / 断开 PLC(S7-1200,以太网)
publicasyncTaskConnect(CpuTypecpu,stringip,shortrack,shortslot){plc=newPlc(cpu,ip,rack,slot);// S71200, IP, 机架, 插槽awaitplc.OpenAsync();timer.Start();// 启动定时轮询}publicvoidDisConnect(){plc?.Close();plc=null;timer.Stop();}需要 NuGet 安装S7netplus包(0.20.0),主窗体调用:
await plcOperator.Connect(CpuType.S71200, globalConfig.PlcIp, 0, 1);
2. 定时读取 PLC 数据(240ms 轮询)
privatevoidTimer_Elapsed(objectsender,ElapsedEventArgse){// 读取 PLC 信息:坐标 + 三个拍照编号PosX=((uint)plc.Read(PosXAddr)).ConvertToFloat();PosY=((uint)plc.Read(PosYAddr)).ConvertToFloat();PosZ=((uint)plc.Read(PosZAddr)).ConvertToFloat();PosR=((uint)plc.Read(PosRAddr)).ConvertToFloat();MeasureNo=(int)(uint)plc.Read(MeasureNoAddr);// PLC 通知上位机"现在该做什么"NPointCalibrationNo=(int)(uint)plc.Read(NPointCalibrationNoAddr);CenterCalibrationNo=(int)(uint)plc.Read(CenterCalibrationNoAddr);}S7.Net 关键坑:读出的 Real 要先转
uint再ConvertToFloat()。不能直接(float)强转——(float)uintValue是数值转换,ConvertToFloat()是 S7.Net 专为 PLC 浮点字节序提供的转换,否则数值不对。
3. 拍照编号变化 → 触发事件
MeasureNo等属性设值时,若值不为 0 且与上次不同,就触发事件,让界面订阅响应:
publicintMeasureNo{get=>measureNo;privateset{if(value!=0&&value!=measureNo){measureNo=value;MeasureNoChange?.Invoke(value);// 触发拍照事件/* 相当于 if (MeasureNoChange != null) MeasureNoChange(value); */}measureNo=value;}}
事件?.Invoke(value):?.判空再调用委托,比if (事件 != null)更简洁。
4. 写回 PLC:偏移 + 确认编号
privatevoidWriteToPlc(stringaddr,objectvalue){if(plc==null||!plc.IsConnected)return;// 未连接直接返回plc.Write(addr,value);}// 测量拍照确认编号:拍完照写回,通知 PLC 执行下个动作publicintMeasureCheckNo{set{_measureCheckNo=value;WriteToPlc(MeasureCheckNoAddr,(uint)value);}}// X/Y/R 轴偏移:先发偏移值,再发确认编号publicfloatOffsetX{set{_offsetX=value;WriteToPlc(OffsetXAddr,value);}}publicfloatOffsetY{set{_offsetY=value;WriteToPlc(OffsetYAddr,value);}}publicfloatOffsetR{set{_offsetR=value;WriteToPlc(OffsetRAddr,value);}}地址常量:
DB51.DBD0(角度偏移)、DB51.DBD4(X偏移)、DB51.DBD8(Y偏移)、DB51.DBD12(测量确认编号)等,与通讯信号表一一对应。
四、配置管理:INI 文件持久化
PLC 的 IP、基准点、旋转中心等参数,存在Config.ini里,用自写的Ini类读写:
[PointConfig] BaseX=-76.5 BaseY=138.19 BaseA=-0.8 RotateCenterX=-58.0823 RotateCenterY=147.98 [PlcConfig] PlcIp=192.168.8.10GlobalConfig(单例)在构造时读 INI,析构时写回保存:
privateGlobalConfig(){ini=newIni(Path.GetFullPath(INI_FILE_PATH));BaseX=Convert.ToSingle(ini.GetValue("BaseX","PointConfig","0"));// ...PlcIp=ini.GetValue("PlcIp","PlcConfig","0.0.0.0");}~GlobalConfig()// 程序退出时自动保存{ini.WriteValue("BaseX","PointConfig",BaseX.ToString());// ...ini.Save();}双单例写法对比:
PlcOperator用Lazy<T>单例;GlobalConfig用lock双重判空单例。两种都是线程安全的经典写法。
五、上篇小结
| 知识点 | 一句话 |
|---|---|
| 系统架构 | PLC 控制动作 + 上位机调度 + 相机识别,三者联动 |
| 通讯信号表 | DB50 只读(坐标/拍照编号)、DB51 只写(偏移/确认编号) |
| 握手协议 | 先发偏移值、再发确认编号,PLC 才执行下一步 |
| S7.Net 连接 | new Plc(S71200, ip, rack, slot)+OpenAsync() |
| 定时轮询 | 240ms Timer 读坐标和拍照编号 |
| 事件触发 | 拍照编号变化 →事件?.Invoke()通知界面 |
| 浮点坑 | PLC Real 要先转 uint 再ConvertToFloat() |
| INI 配置 | 基准点/旋转中心/IP 持久化,退出时保存 |
下篇预告
PLC 通信打底之后,最核心的视觉部分来了:VPP 作业怎么加载?一次/二次拍照怎么算角度和 XY 偏移?九点标定怎么用像素坐标对机械坐标?三点怎么拟合旋转中心?下篇《C# 上位机实战:视觉检测与九点标定、旋转中心拟合完整实现》手把手拆代码,点关注别错过!
参考:结合 S7.Net 通信、VisionPro 机器视觉、WinForm 上位机教学讲义整理的学习笔记。