☰
C# 上位机实战:PLC(S7-1200) 以太网通信 + 机器视觉系统架构(上篇)
2026/10/14 19:34:29 网站建设 项目流程

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,只读)

名称类型地址说明
设备故障代码DIntDB50.DBD00 表示无故障
测量模式拍照编号DIntDB50.DBD4测量时通知上位机拍照(=1 第一次、=2 第二次)
9点矫正拍照编号DIntDB50.DBD8九点矫正时通知拍照(=1~9 共九次)
中心矫正拍照编号DIntDB50.DBD12中心矫正时通知拍照(=1~3)
当前控制模式DIntDB50.DBD160手动 / 1检测运行 / 2九点矫正 / 3暂停 / 4中心矫正
X/Y/Z/R 轴坐标RealDB50.DBD20/24/28/32设备当前机械坐标

2. 上位机 → PLC(DB51,只写)

名称类型地址说明
角度偏移 RRealDB51.DBD0第一次拍照算出的角度偏差
X 轴偏移RealDB51.DBD4第二次拍照算出的 X 偏差
Y 轴偏移RealDB51.DBD8第二次拍照算出的 Y 偏差
测量拍照确认编号DIntDB51.DBD12拍完照写回编号,告诉 PLC"拍完了"
中心矫正拍照确认编号DIntDB51.DBD16同上
放料 1-2 位选择DIntDB51.DBD200 放料位一 / 1 放料位二
9点矫正拍照确认编号DIntDB51.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.10

GlobalConfig(单例)在构造时读 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 上位机教学讲义整理的学习笔记。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询