十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C#上位机与西门子PLC通信:S7.Net和Sharp7选型实战指南

C#上位机与西门子PLC通信:S7.Net和Sharp7选型实战指南 简介面向C#工业自动化开发人员及西门子PLC初学者这是一份可直接运行的S7.Net与Sharp7连接PLC实例源码。资源实现C#与S7-1200 PLC通信覆盖DB块数据读写并补充了bool变量、string及Wstring类型读取从基础连接到特定数据类型访问均有示例可循适合新手快速上手也能给有经验的开发者提供工程结构参考。压缩包共35个文件以C#源码、项目与解决方案文件、配置文件为主同时包含S7.Net.dll、英文说明文档及可执行程序包体仅805KB轻量便于直接导入学习。资源包内项目工程完整采用WinForm界面展示通信过程工程结构清晰方便对照学习连接建立、DB块寻址与多类型变量转换等处理逻辑附带说明文档也免去到处寻找DLL的麻烦。目前已有4066人学习下载是工控程序员学习C#与西门子PLC通信时值得参考的实例。 做上位机开发的朋友十有八九绕不开和西门子PLC打交道。我上一个项目是设备数据采集与MES对接现场清一色S7-1200上位机用C#当时在S7.Net和Sharp7之间来回折腾最后两条路都走通了。今天干脆把这两个库怎么连接、怎么读写、各自有哪些坑一次性说清楚附带可以直接抄的实例源码。如果你正在做C#上位机、准备接西门子PLC做数据采集或者设备控制这篇内容应该能帮你少走不少弯路。先聊一个核心结论S7.Net和Sharp7都是通过西门子的S7协议TCP 102端口与PLC通信不是OPC UA也不是Modbus TCP而是西门子工业环境里最高效、最直接的以太网通信方式之一。两者解决的问题相同但设计思路完全不同实际用起来差别不小。下面我会从选型思路讲起再把环境、连接、读写、工业场景落地、排障全部走一遍。1. 整体设计与技术选型思路1.1 S7.Net和Sharp7是什么通信原理简单过一遍先理解通信本质。C#上位机要和西门子PLC交换数据底层其实是一条TCP连接端口是102。PLC作为TCP服务器上位机作为客户端发起连接然后通过ISO-on-TCP协议栈发起S7通信请求格式是一串二进制数据包。这个协议如果自己从Socket层写工作量不小而且容易踩字节序、PDU长度、ACK机制这些坑。S7.Net和Sharp7就是在这一层帮我们封装好的库。它们把“连接握手、读取请求、写入请求、响应解析”这些底层细节全部处理好我们只需要给出变量地址比如DB1.DBD0或者M10.0就能直接读回一个对象。这样做的最大好处是开发效率高代码看起来非常直观适合项目周期紧、需要快速上线的场景。S7.Net的封装程度更高喜欢用字符串地址描述变量比如Read(DB1.DBD0)读回来直接是object需要自己做类型转换。Sharp7则走的是“函数调用字节缓冲区”风格比如ReadArea方法配合byte[]数组低层API暴露得更充分数据收发性能也更好但代码写起来更啰嗦。二者本质都是TCP客户端都在.NET平台上运行没有谁绝对优于谁只有适不适合当前项目。1.2 两个库怎么选接口友好vs底层高效我自己的选择标准很简单项目里如果变量数量不多、逻辑偏业务、主要做一些按钮控制和数据显示那么S7.Net是首选因为它写起来太舒服了。比如要读一个DB块的浮点数float val (float)plc.Read(DB10.DBD4);一行代码搞定新手看一遍就会。但如果要做高频采集比如每100ms轮询几十个DB块或者要和MES大量交互、传上百个字节那Sharp7更合适。它的ReadArea/WriteArea让你直接操作字节数组没有多余的装箱拆箱开销而且自带多实例连接管理对吞吐量和稳定性要求高的场景更有底。老外做过不少对比Sharp7在大量读写时的CPU占用和内存抖动确实比S7.Net更小。我还观察到一个细节S7.Net的Plc类在内部维护了请求序列虽然使用方便但多线程同时调用同一个实例会出问题必须加锁。Sharp7的S7Client每个实例相对独立做多连接、多线程轮询时心理负担小一些。一句话总结求快用S7.Net求稳求快用Sharp7两个都学会现场遇到什么问题都不慌。1.3 为什么不是OPC UA、Modbus TCP有人会问现在OPC UA不是很流行吗为什么还用S7协议这个问题的答案在现场很现实。OPC UA功能强大、跨平台、安全性好但它需要OPC Server需要配置证书和节点模型对中小型设备和数据采集项目来说明显偏重。而且西门子PLC通过S7协议开放PUT/GET通信是官方硬件支持的能力不需要额外装软件延迟低实现简单。Modbus TCP看起来也很轻但西门子S7-1200/1500的Modbus TCP通常需要在程序中调用Modbus TCP指令块并且自己规划保持寄存器区不像S7协议那样可以随意读DB、M、I、Q区。更重要的是很多既有设备程序里根本没有做Modbus映射只有S7通信可用。所以基于TCP 102端口的S7协议反而是C#上位机与西门子PLC通信的“最低门槛”方案。2. 开发环境与PLC侧准备2.1 环境搭建和NuGet包安装我用的是Visual Studio 2022项目框架是.NET 6.0因为新项目不想再用.NET Framework了。但要注意S7.Net和Sharp7都支持.NET Framework 4.6.2及以上所以老项目也能用不用担心迁移问题。安装包很简单NuGet搜索S7.Net或Sharp7直接装最新稳定版。S7.Net目前版本是S7.Net注意不要和另一个早期库S7.NetPlus混淆了。Sharp7的包名就是Sharp7安装后引用里会出现Sharp7命名空间。dotnet add package S7.Net dotnet add package Sharp7装完后先检查using是否正常S7.Net用的是using S7.Net;Sharp7用的是using Sharp7;。两个库只要不混用类型放在同一个项目里不冲突。我实际项目中就是同时装了这两个包S7.Net负责几个操作界面上的手动读写Sharp7负责后台的批量数据采集。2.2 PLC端必须打开的“远程访问开关”很多新手最容易忽略的一点PLC程序能跑、IP也能Ping通但C#连接就是超时十有八九是PLC侧的“允许远程访问”没打开。S7-1200/1500默认是不开放PUT/GET通信的必须到PLC程序里手动设置。在TIA Portal中选中CPU右键进入“属性”找到“防护与安全”-“连接机制”勾选“允许来自远程对象的PUT/GET通信访问”。然后重新编译下载硬件配置。这一步不做S7.Net和Sharp7连上就会报“连接超时”或者“无法协商S7连接”。S7-200 SMART有些差异它需要在系统块里勾选“允许远程访问”一般默认是开启的。S7-300/400老设备相对宽松但也建议确认一下CPU的防护等级不是“完全保护”。另外PLC和上位机的IP必须在同一网段比如PLC是192.168.0.1上位机就设成192.168.0.10掩码一致。把Windows防火墙临时关掉测试一下也是排查步骤之一。2.3 IP、机架号和槽号这些参数到底怎么填S7.Net和Sharp7建立连接时都需要几个参数IP、机架号Rack、槽号Slot。很多人被这两个数字折磨过其实记住经验值就够了S7-1200Rack0Slot1S7-1500Rack0Slot1S7-300Rack0Slot2常见S7-400Rack根据机架来Slot一般为3或4S7-200 SMARTRack0Slot1但部分旧版本直接用TCP/IP连接不校验Rack/Slot我实测S7-1200和S7-1500用Rack0Slot1是稳定可靠的。如果填错连接阶段通常直接报错不会等到读写才暴露。3. 核心读写实操从连接到最后一个字节3.1 建立连接S7.Net和Sharp7的两种打开方式先看S7.Net的写法。它很简单定义一个Plc对象参数是CPU类型、IP、Rack、Slot然后Openusing S7.Net; var plc new Plc(CpuType.S71200, 192.168.0.1, 0, 1); plc.Open(); if (plc.IsConnected) { Console.WriteLine(S7.Net连接成功); }这里CpuType枚举要和你实际的CPU型号匹配如果PLC是S7-1500就填CpuType.S71500。类型填错有时也能连上但读写DB时可能表现异常还是按实际填稳妥。Sharp7的连接代码风格明显更底层using Sharp7; var client new S7Client(); int result client.ConnectTo(192.168.0.1, 0, 1); if (result 0) { Console.WriteLine(Sharp7连接成功); } else { Console.WriteLine(连接失败错误码 client.ErrorText(result)); }注意Sharp7的ConnectTo返回值是0表示成功非0是错误码。ErrorText方法能直接把错误码转成字符串排障很有用。S7.Net的Open如果失败会直接抛异常没有返回码所以习惯用try-catch包住。3.2 读I/Q/M位和写输出连接建立后先看最简单的位操作。S7.Net用地址字符串直接读写// 读输入位I0.0 bool inputBit (bool)plc.Read(I0.0); Console.WriteLine(I0.0 inputBit); // 写输出位Q0.0 plc.Write(Q0.0, true); // 写M区位 plc.Write(M20.0, false);这段代码非常适合做手动控制画面里的按钮。比如界面上一个“启动”按钮点击后Write(Q0.0, true)PLC里如果写了对应输出设备就动了。但要注意写入Q区是直接输出到物理点实际操作前一定要确认设备安全最好通过PLC程序里的逻辑互锁来控制而不是上位机直接写Q点。Sharp7在点位读写上没有字符串地址需要自己拼写内存地址和位偏移。读M20.0的代码是这样的byte buffer; int area S7AreaMK; int dbNumber 0; int start 20; int size 1; int err client.ReadArea(area, dbNumber, start, size, out buffer); bool bitValue (buffer 0x01) ! 0;如果要写位需要先读回一个字节改对应位再写回去。Sharp7在这点上确实麻烦但胜在灵活适合批量操作。实际项目里我用Sharp7读M区连续字节再用位运算取出某些状态位性能很好。3.3 读写DB块的完整示例含字符串和数组DB块是PLC程序中最常用的数据存储区也是上位机读写的重点。S7.Net读写单个变量非常直观// 读DB1.DBD0这是个浮点数 float speed (float)plc.Read(DB1.DBD0); // 读DB1.DBW4这是个无符号整数 ushort value (ushort)plc.Read(DB1.DBW4); // 写DB1.DBD10浮点数 plc.Write(DB1.DBD10, 123.456f);这里要特别强调字节偏移的计算。DBD0表示从DB块起始地址算起第0字节开始占4字节的Double Word32位浮点数。DBW4表示第4字节开始的Word16位整数。在PLC侧开发的同事通常用符号名而在上位机这边我们只能通过偏移地址来访问所以拿到DB块结构表是第一要务。每改一次PLC程序这些偏移地址都可能变建议专门维护一份地址映射表。字符串读写比较特殊。PLC中的String类型不是普通C#字符串它内部结构是第1个字节是最大长度第2个字节是当前实际长度从第3个字节开始才是字符内容。所以读DB1中的字符串之前一定要知道它存放的最大长度和起始字节。S7.Net读字符串不能直接用字符串地址需要自己读字节数组再解码// 假设DB1.DBB20是字符串起始地址 int startByte 20; int maxLength 254; // S7默认最大254 byte[] buffer new byte[maxLength 2]; plc.ReadBytes(DataType.DataBlock, 1, startByte, buffer.Length, buffer); int actualLength buffer[1]; string text Encoding.ASCII.GetString(buffer, 2, actualLength); Console.WriteLine(text);Sharp7处理字符串和字节数组就更顺手了它内置了S7.GetStringAt方法byte[] buffer new byte[256]; int err client.ReadArea(S7AreaDB, 1, 20, 256, buffer); if (err 0) { string text S7.GetStringAt(buffer, 0); Console.WriteLine(text); }数组的读写思路也类似。如果PLC里定义了一个ARRAY[0..9] OF REAL那么它实际就是连续的40个字节。用ReadBytes或ReadArea一次性读回来再用BitConverter解析即可。千万不要一个一个变量去读效率低还会增加PLC通信负载。3.4 异步读写与工业场景下的并发处理工业上位机最怕界面卡死。S7.Net从旧版本开始就提供了一些异步方法比如ReadAsync、WriteAsync内部基于Task封装UI线程调用时不会阻塞// 异步读浮点数 float speed (float)await plc.ReadAsync(DB1.DBD0);注意ReadAsync返回的是object还是要拆箱。Sharp7没有内置异步方法但可以自己包一层Task.Runfloat speed await Task.Run(() { byte[] buffer new byte[4]; int err client.ReadArea(S7AreaDB, 1, 0, 4, buffer); if (err 0) return S7.GetRealAt(buffer, 0); return float.NaN; });多线程并发是另一个大坑。S7.Net的Plc对象不是线程安全的多个线程同时读同一个实例轻则数据错乱重则直接异常。我一开始没注意在后台轮询线程和界面刷新线程同时调plc.Read结果程序跑十几分钟就开始抛Socket异常。解决办法也很直接要么所有访问都加同一把锁要么每个线程单独创建自己的Plc实例。后一种方式更推荐因为通信开销不大却省去很多锁竞争。Sharp7的S7Client在并发场景下相对安全但我也建议一个连接只给一个线程用。多连接模式性能最好不过需要自己维护连接池项目复杂度允许时可以这么干。4. 工业现场的综合落地采集、扫码与UI维护4.1 扫描枪触发事件后写入PLC很多产线项目里扫码枪并不是直接连PLC的而是先到上位机再由上位机把条码内容写入PLC。这里的关键是事件触发。扫码枪通过串口或TCP发送数据C#这边用SerialPort.DataReceived或TcpClient异步接收触发事件后解析条码然后调用S7.Net或Sharp7写入PLC的DB块字符串区。我做过一个实际案例扫码枪通过串口连接到工控机扫码后触发事件里取出条码字符串写入PLC的DB10.DBB0开始的字符串变量同时把M100.0置位通知PLC“新条码已到达”。PLC处理完后再复位M100.0。这样做的好处是上位机和PLC之间有一个简单的握手协议不会丢数据。private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { string barcode serialPort.ReadLine().Trim(); // 假设使用S7.Net plc.Write(DB10.DBB2, barcode); plc.Write(M100.0, true); }注意S7.Net的Write对字符串写入有长度限制而且PLC字符串变量最大长度要足够。串口事件里不要直接做耗时操作建议把条码放进ConcurrentQueue再由后台线程负责写入PLC这样通信更稳定。4.2 循环数据采集业务数据不卡UI的正确思路“C# 循环数据采集和UI刷新卡顿”是热词也是现场开发最典型的痛点。很多新手在定时器里直接plc.Read然后又直接textBox.Text value跑起来一会儿界面就卡死。正确的做法必须遵循两个原则PLC通信远离UI线程UI刷新只在需要时进行。我通常用一个后台Task做While循环每隔100ms读取一批数据读完后把数据封装成一个快照对象放进Volatile或ConcurrentDictionary。UI侧用System.Windows.Forms.Timer或DispatcherTimer每200ms从快照里取一次值更新到控件上。private async Task PollingLoopAsync(CancellationToken token) { while (!token.IsCancellationRequested) { var snapshot await Task.Run(() ReadAllVariables()); _latest snapshot; await Task.Delay(100, token); } }这样即使PLC响应慢也只是后台任务排队UI不会卡。还要注意UI控件的跨线程访问更新时用BeginInvoke不要频繁调用批量赋值比逐个赋值快很多。实测下来100ms轮询几十个变量UI完全流畅。4.3 延伸ABB变频器、康耐视相机等设备也能这么玩很多现场设备看似和PLC通信无关实际上只要支持S7协议都能用同一套思路接入。比如ABB变频器通过Profinet接入西门子PLCPLC程序里把变频器的运行频率、电流映射到DB块那么上位机读这些DB块就等于间接读了变频器数据不再需要单独和变频器通信。康耐视InSight相机同样可以通过Profinet与西门子PLC交互相机结果写入PLC的DB区C#上位机再从PLC读回判定结果。这就是“相机PLC上位机”的常见集成模式。作为上位机开发我们不需要关心Profinet怎么配置只要能从PLC的DB块里拿到结果整个链路就是通的。这个思路可以帮你少做很多无用的驱动开发。5. 常见问题与排障实录5.1 连接不上IP、防火墙、PUT/GET和安全设置这是出现频率最高的问题。排查顺序我建议是先ping通PLC的IP再检查上位机与PLC是否同网段然后确认PLC的PUT/GET通信是否打开最后关掉Windows防火墙测试。如果PLC是S7-1500还要注意CPU的“防护等级”不能选“完全保护”否则任何S7通信都会被拒绝。有时候连接时好时坏很可能是PLC侧连接资源被占满。S7-1200/1500的可用连接资源有限多个上位机或HMI同时连接时新连接会被拒绝。这种情况可以尝试降低轮询频率或者干脆给上位机单独分一个“连接资源”在PLC硬件组态里设置。5.2 读出来的数据不对偏移和类型长度是重灾区读回来的数据完全不对比如应该是0.5读出来却是1.084e-19这通常是偏移地址错位。PLC里的DBD0是0~3字节DBD4是4~7字节如果实际变量在4字节位置你读了DBD0拿到的就是别的变量的数据。类型长度不一致也会乱掉比如把Word当DWord读、把Real当Int读数据当然不对。解决这件事最可靠的办法是让PLC工程师导出一份DB块结构表或者自己在TIA Portal里看偏移。别凭记忆写后面对接MES出了问题排查很费时间。字符串读出来乱码多半是最大长度与实际内容长度处理不对按前面讲的buffer[1]作为实际长度解析就没问题。5.3 程序运行一段时间后卡死线程安全与连接复用我在项目里遇到过连续运行两小时后上位机所有读写都超时但PLC和网络都正常。后来定位到是S7.Net的Plc对象被多个线程并发调用内部状态被搞乱了。从那以后我定下两个规矩一是每个线程单独创建连接二是所有对同一个连接实例的访问必须加锁。另外要注意长时间不通信的连接可能被防火墙或者交换机断开。S7.Net没有自动重连机制所以轮询循环里如果捕获到PlcException或Socket异常应该关闭连接并重新Open。Sharp7同理检测到返回值非0就断开重连。写一个EnsureConnected方法每轮调用时检查一下状态是稳定运行的关键。private void EnsureConnected() { if (!plc.IsConnected) { plc.Open(); } }5.4 轮询太快拖累PLC调整协议开销上位机采集太频繁PLC CPU负载会明显上升严重时甚至影响设备逻辑执行。S7协议本身很轻但每条请求都有TCP握手之外的协议开销如果每100ms读取几十个变量实际上产生了大量数据包。最好的优化是合并读取把连续的DB字节一次性读回来再在本地解析而不是一次读一个变量。比如要读DB1中20个浮点数一次ReadBytes(DataType.DataBlock, 1, 0, 80, buffer)就够了比20次单变量读取高效得多。如果必须高频轮询建议把轮询周期降到200ms以上并只在数据变化时刷新UI这样对PLC和上位机双方都友好。我在实际项目中的体会是S7.Net和Sharp7都不是万能钥匙但它们组合起来基本覆盖了90%的西门子PLC数据交互场景。最烦人的往往不是库本身而是现场网络、PLC设置和数据结构理解。开发前花半天时间把PLC侧的地址表、连接机制和网络环境摸清楚比卡在实现阶段再反复试错要省太多时间。最后再分享一个小技巧正式上设备前先在一台笔记本上模拟PLC侧通信把两套库的读写逻辑全部跑通再带到现场联调这样效率最高也最不容易在客户现场翻车。本文还有配套的精品资源点击获取
返回列表