简介:这份资源是面向工业自动化开发者与上位机编程学习者的西门子S7-200Smart通讯C#源码项目,围绕PLC与上位机之间的数据交互展开,适合已具备一定C#基础、希望掌握PLC通讯原理与读写实现的中级开发者参考。压缩包共74个文件,约1.43MB,以cs源码文件为主,配合sln解决方案、csproj工程文件、config配置、resx资源、dll依赖及exe可执行程序等,构成一套可直接打开运行的Visual Studio工程。源码核心演示了通过TCP/IP协议与S7-200Smart建立Socket连接、构造读写请求数据包、解析返回数据并处理通讯异常的完整流程,同时借助Windows Forms搭建图形界面,直观展示PLC状态与读写操作结果。目前已有222人学习下载,读者可从中理解S7通讯协议的打包与解析思路,掌握连接维护、地址与长度设定等关键环节,为自建上位机监控程序或二次开发提供可复用的代码骨架与排错参考。
1. 拆开 S7200SmartTest:一个能直接跑的 C# 与西门子 S7-200 Smart 通讯源码包
车间里一台 S7-200 Smart 跑着产线,上位机要读它的 V 区数据做看板,找厂家报价动辄几万,自己写又卡在协议打包上——这是我拿到S7200SmartTest这个源码包时的真实场景。它是一份 C# 写的 WinForms 工程,核心就干一件事:通过以太网跟西门子 S7-200 Smart PLC 做数据交互,读写指定地址。压缩包里是完整的.sln解决方案,MainForm.cs负责界面,Program.cs是入口,Dll目录放依赖库,App.config管配置。适合两类人:一是刚接触 C# 上位机、想找个能编译能连 PLC 的样板工程的工控新手;二是手头有 200 Smart、需要快速验证读写逻辑的现场工程师。它不依赖西门子官方收费库,走的是原生 Socket 那条路,协议细节全在源码里摊开,这对想搞懂报文结构的人来说比调 API 有价值得多。
2. 通讯原理与工程结构:为什么这份源码值得逐行读
2.1 S7-200 Smart 的以太网通讯到底走的是什么
S7-200 Smart 本体自带以太网口,上位机跟它通讯最常用的就是 TCP/IP。这里要先厘清一个容易混的点:很多人一听西门子通讯就想到 S7 协议、想到 ISO-on-TCP(端口 102),但 200 Smart 的固件对标准 S7 协议的支持是有限制的,实际工程里更常见的是基于 TCP 的自定义报文交互,或者走 S7 协议里那套精简的读写服务。这份源码没有引入Siemens.S7这类封装库,而是自己用System.Net.Sockets建连接、拼报文、解析返回,等于把黑匣子拆开了。
它的通讯模型是典型的短连接请求-响应:上位机作为客户端,主动连 PLC 的 IP 和端口,发一条读或写的请求帧,PLC 回一条响应帧,解析出数据。这种模式的好处是实现透明、可控,坏处是每次操作都要处理连接状态和超时。源码里MainForm.cs承担了连接管理、按钮事件触发、结果回显;Program.cs只做应用启动;App.config里通常放 IP、端口、超时时间这类可调参数。Dll目录是编译依赖,Properties下是程序集信息。整个工程结构不复杂,但麻雀虽小,读写链路是完整的。
2.2 工程文件逐个说清楚,别一上来就懵
拿到一个陌生.sln,先别急着 F5。把文件清单过一遍,心里有张地图,后面改代码才不迷路。
| 文件/目录 | 作用 | 你大概率要动它吗 |
|---|---|---|
S7200SmartTest.sln | 解决方案入口,VS 双击打开 | 否 |
S7200SmartTest.csproj | 工程配置,目标框架、引用 | 换 .NET 版本时动 |
Program.cs | 程序入口,Application.Run | 基本不动 |
MainForm.cs | 核心逻辑:连接、读写、事件 | 重点改这里 |
MainForm.Designer.cs | 界面控件布局代码 | 加控件时动 |
MainForm.resx | 窗体资源 | 一般不动 |
App.config | IP、端口等配置项 | 部署时改 |
Dll/ | 第三方或自封装依赖库 | 看引用关系 |
Properties/ | 程序集元信息 | 一般不动 |
S7200SmartTest.v12.suo | VS 用户选项缓存 | 可删,会自动重建 |
.suo这个文件是 Visual Studio 的用户级缓存,换机器或者版本对不上时它经常是编译玄学的源头,直接删掉让 VS 重建就行。Dll目录要特别留意:如果里面是源码作者自己封装的通讯类库,那真正的协议实现可能藏在那儿,而不是MainForm.cs里。打开工程后先在解决方案资源管理器里看引用,确认哪些是项目引用、哪些是外部 DLL,这决定了你读代码的入口。
2.3 把工程跑起来:从打开到连上 PLC 的完整步骤
先保证能编译,再谈连 PLC。下面这套流程是我在干净环境里复现过的顺序。
第一步,确认开发环境。这份工程是较早期的 C# WinForms 项目,用 Visual Studio 2015 及以上都能打开,目标框架大概率是 .NET Framework 4.x。如果打开时报“目标框架未安装”,去 VS 安装器里补上对应版本的 .NET Framework 开发工具。
第二步,还原依赖。如果Dll目录里的库是外部引用,检查.csproj里的<Reference>路径是不是相对路径。相对路径的写法在换机器后最容易翻车,路径对不上就报“找不到类型或命名空间”。
# 打开解决方案前,先确认 Dll 目录内容 ls -la S7200SmartTest/Dll/ # 如果 csproj 里引用的是绝对路径,改成相对路径 grep -n "HintPath" S7200SmartTest/S7200SmartTest.csproj这段命令是排查引用问题的起手式。ls看Dll里到底有没有东西,grep HintPath把工程文件里所有外部引用的物理路径揪出来。如果HintPath指向的是作者本机的C:\Users\xxx\...,那必须改成..\Dll\xxx.dll这种相对写法,否则你这边永远编译不过。
第三步,改配置。打开App.config,把 PLC 的 IP 和端口改成你现场设备的实际值。200 Smart 默认以太网口 IP 常见是192.168.2.1,端口按源码里写的来,别想当然填 102。
<appSettings> <!-- PLC 的 IP 地址,按现场实际改 --> <add key="PlcIp" value="192.168.2.1"/> <!-- 通讯端口,以源码实际使用的为准 --> <add key="PlcPort" value="102"/> <!-- 连接超时,单位毫秒,网络差就调大 --> <add key="Timeout" value="3000"/> </appSettings>配置项的含义很直白:PlcIp是目标设备地址,PlcPort决定连哪个服务,Timeout控制等待响应的上限。现场网络抖动大时,Timeout给 3000 毫秒往往不够,可以试 5000。改完配置记得在 VS 里确认App.config的“复制到输出目录”属性是“始终复制”,否则程序跑起来读的还是旧的。
第四步,编译运行。先不连 PLC,直接 F5,看界面能不能正常弹出来。界面出来说明工程本身没问题,剩下的就是通讯链路的事。这一步能过滤掉一大半环境问题。
2.4 读写逻辑在代码里长什么样
连上之后,核心就是读和写两个动作。源码里通常会把它们封装成方法,调用时传地址、长度、数据类型。下面这段是这类工程的典型写法,我按常见实现补全,你对照自己的MainForm.cs看结构。
// 读取 PLC 指定地址的数据 // address: 如 "V100",V 区起始地址 // length: 读取字节数 public byte[] ReadPlc(string address, int length) { // 1. 构造请求报文:包含命令码、地址、长度 byte[] request = BuildReadRequest(address, length); // 2. 通过已建立的 Socket 发送 _socket.Send(request); // 3. 接收响应,缓冲区大小按协议最大帧预留 byte[] buffer = new byte[1024]; int received = _socket.Receive(buffer); // 4. 解析响应,去掉头部,取出数据段 return ParseReadResponse(buffer, received); }逻辑分四步:拼报文、发、收、解析。BuildReadRequest是关键,它把地址字符串转成协议要求的字节序列,地址编码方式(比如 V 区偏移怎么算)直接决定 PLC 认不认这条请求。ParseReadResponse负责跳过响应头,把有效数据抠出来。参数上,length不能超过协议单帧上限,超了要分包,这是新手最容易忽略的边界。写操作同理,只是报文里带的是要写入的数据,且通常需要 PLC 返回一个确认帧才算成功。
3. 报文构造与地址映射:读写能不能成,全看这一层
3.1 地址编码:V 区、I 区、Q 区不是随便填字符串
PLC 的地址不是给人看的字符串,是要编码进报文的。S7-200 Smart 的存储区分几块:V 区(变量存储)、I 区(输入)、Q 区(输出)、M 区(位存储)。每块区在协议里有对应的区域标识码,地址偏移量要按字节算。比如V100表示 V 区第 100 个字节,编码时区域码 + 偏移量一起进报文。
// 把地址字符串转成协议需要的区域码和偏移 // 常见区域码:V=0x01, I=0x81, Q=0x82, M=0x83(以实际协议为准) private (byte area, int offset) ParseAddress(string address) { // 取首字母判断区域 char areaChar = address[0]; // 剩余部分转偏移量 int offset = int.Parse(address.Substring(1)); byte area = areaChar switch { 'V' => 0x01, 'I' => 0x81, 'Q' => 0x82, 'M' => 0x83, _ => throw new ArgumentException("不支持的地址区域") }; return (area, offset); }这段代码把V100拆成区域码和偏移。注意区域码的具体取值必须以你源码里实际用的协议为准,不同实现有差异,我这里给的是常见值,照抄前先跟源码核对。偏移量是字节偏移,不是位偏移,读位数据(比如V100.3)时还要额外处理位号,这是另一个坑。
3.2 报文结构:头部、命令、数据段怎么排
一条完整的请求帧大致分三段:头部(含长度、协议标识)、命令段(读还是写、区域、偏移、长度)、数据段(写操作才有)。响应帧结构类似,但数据段放的是 PLC 返回的值。构造时最容易错的是长度字段——它通常指后面数据段的字节数,算错了 PLC 直接丢弃不响应,你这边就是一直超时。
private byte[] BuildReadRequest(string address, int length) { var (area, offset) = ParseAddress(address); var frame = new List<byte>(); // 头部:协议标识 + 后续长度占位 frame.Add(0x03); // 协议标识,以源码为准 frame.Add(0x00); // 长度高位,稍后回填 frame.Add(0x00); // 长度低位,稍后回填 // 命令段 frame.Add(0x04); // 读命令码 frame.Add(area); // 区域码 frame.Add((byte)(offset >> 8)); // 偏移高位 frame.Add((byte)(offset & 0xFF)); // 偏移低位 frame.Add((byte)(length >> 8)); // 长度高位 frame.Add((byte)(length & 0xFF)); // 长度低位 // 回填长度字段:从命令段开始算 int payloadLen = frame.Count - 3; frame[1] = (byte)(payloadLen >> 8); frame[2] = (byte)(payloadLen & 0xFF); return frame.ToArray(); }逐段看:前三个字节是头部,第一个是协议标识,后两个是长度占位,等命令段拼完再回填。命令段里0x04是读命令,接着是区域码、两字节偏移、两字节长度。偏移和长度都是大端序(高位在前),这是网络协议的惯例,写反了 PLC 解析出来的地址就完全不对。回填长度时frame.Count - 3是因为长度字段本身不参与计数,从命令段第一个字节算起。这个细节错了,现象就是连接正常但永远读不到数据。
3.3 响应解析:怎么从返回帧里把数据抠出来
PLC 收到正确请求后会回一帧,结构是头部 + 命令回显 + 数据。解析时要先校验命令码对不对,再按长度取数据段。
private byte[] ParseReadResponse(byte[] buffer, int received) { // 最小长度校验,防止越界 if (received < 9) throw new Exception("响应帧过短,可能通讯异常"); // 命令码在固定位置,确认是读响应 byte cmd = buffer[3]; if (cmd != 0x04) throw new Exception($"响应命令码异常: {cmd}"); // 数据长度在命令段之后 int dataLen = (buffer[7] << 8) | buffer[8]; // 数据段起始位置按协议偏移取 byte[] data = new byte[dataLen]; Array.Copy(buffer, 9, data, 0, dataLen); return data; }解析的核心是偏移量对齐。buffer[3]是命令码位置,buffer[7]和buffer[8]拼出数据长度,数据从buffer[9]开始。这些下标必须跟你的请求帧结构严格对应,请求和响应的偏移约定要一致。如果读出来的数据总是错位一两个字节,八成是这里下标算错了。received参数是实际收到的字节数,用它做长度校验比用buffer.Length靠谱,因为缓冲区往往开得比实际数据大。
4. 避坑与排查:连不上、读不到、数据错位的血泪经验
4.1 现象:程序编译报“找不到类型或命名空间”
原因基本锁定在引用路径。.csproj里的HintPath指向了作者本机的绝对路径,换到你机器上自然找不到。解决方式是打开工程文件,把所有HintPath改成相对路径,或者干脆把Dll目录下的库重新添加引用。改完清理解决方案再重新生成,别只点生成,缓存会骗人。
4.2 现象:Socket 连接被拒绝或超时
先分清楚是网络不通还是端口不对。用ping确认能通到 PLC 的 IP,再用 telnet 类工具测端口。如果 ping 通但端口连不上,多半是端口号填错了,或者 PLC 那边没开对应的通讯服务。还有一种情况是上位机跟 PLC 不在同一网段,IP 和子网掩码都要核对。现场经常遇到电脑双网卡,一个连办公网一个连设备网,路由走错了也会连不上,把无关网卡禁用掉再试。
4.3 现象:连接成功但读数据一直超时
这是最典型的报文错误。连接建立只说明 TCP 握手成功,不代表你的请求帧 PLC 认。重点查三处:长度字段算对没有、偏移量是不是大端序、区域码跟地址匹配没有。我一般会在发送前把请求帧的十六进制打出来,跟协议文档逐字节对。另外Timeout设太短也会误判,网络正常但 PLC 响应慢时,3 秒可能不够,先调到 5 秒排除超时因素。
4.4 现象:读回来的数据错位或全是零
错位通常是响应解析的偏移下标不对,或者请求的长度跟实际读的区域不匹配。全是零则要怀疑地址本身——你读的 V 区地址在 PLC 里可能根本没被程序写过,读出来自然是零。还有一种隐蔽情况:PLC 的 V 区有保持性设置,断电后部分区域会清零,如果你读的是掉电不保持的区,重启后就是零。排查时先用 PLC 编程软件在线监控那个地址的实际值,跟上位机读到的对比,能快速定位是通讯问题还是数据源问题。
4.5 现象:换台电脑就编译不过或运行报错
.suo文件和bin、obj目录是重灾区。换机器时先把bin、obj、.suo全删掉,让 VS 重新生成。如果还报错,检查目标框架版本是否一致,.NET Framework 4.5和4.7之间有些 API 行为有差异。另外App.config如果没设成“始终复制”,运行目录下读不到配置,程序会用默认值连一个不存在的 IP,现象就是莫名其妙连不上。
5. 进阶:把这份源码改成能长期稳定跑的采集程序
源码能跑通读写只是起点,真放到产线上长期采集,还得补几件事。第一是连接保活。短连接模式每次操作都建连开销大,改成维持一个长连接、断线自动重连更实际。重连逻辑我一般这么写:捕获SocketException,关闭旧 socket,延时 2 秒后重建,连续失败超过阈值就报警而不是死循环重试。
private void EnsureConnected() { if (_socket != null && _socket.Connected) return; // 断线后重建,带退避 for (int i = 0; i < 3; i++) { try { _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _socket.Connect(_plcIp, _plcPort); return; } catch (SocketException) { Thread.Sleep(2000); // 退避 2 秒再试 } } throw new Exception("PLC 重连失败,请检查网络"); }第二是读写分离到后台线程。WinForms 的 UI 线程直接做 Socket 收发,网络一卡界面就假死。把采集逻辑放到独立线程或Task里,通过Invoke回主线程更新界面,这是上位机的基本功。第三是加日志。每次请求和响应的原始字节、时间戳、耗时都记下来,出问题时这份日志比任何猜测都管用。我吃过没日志的亏,现场偶发读错,没记录根本没法复现,从那以后我每次做 PLC 通讯都强制把收发报文落盘,哪怕只是临时调试。
第四是地址配置化。别把V100、V200硬编码在代码里,抽到一个配置文件或数据库表,现场改地址不用重新编译。这份源码的App.config已经开了个头,可以顺着扩成地址列表。最后提醒一句,200 Smart 的 V 区大小跟具体型号有关,读之前确认地址没越界,越界请求 PLC 可能不响应也可能返回错误码,两种情况都要处理。希望这份拆解帮到你,少走几个我当年踩过的坑。
本文还有配套的精品资源,点击获取