简介:这份资源是面向工业自动化开发者与上位机编程学习者的西门子S7-200Smart通讯C#源码项目,针对PLC与上位机数据交互这一实际需求,提供可直接参考的读写实现方案。压缩包共74个文件,约1.43MB,以18个cs源码文件为核心,配合sln与csproj工程文件、config配置、resx与resources界面资源,以及exe、dll、pdb等编译调试产物,构成一套完整的Visual Studio解决方案。源码围绕TCP/IP协议与PLC建立连接,封装了数据包构造、读写请求发送与返回数据解析等关键逻辑,并借助Windows Forms搭建可视化界面,便于观察PLC状态与执行操作。目前已有222人学习下载,适合希望理解S7-200Smart通讯协议、掌握Socket通信与上位机界面开发的中级开发者,可据此快速搭建测试环境并迁移到自己的工控项目中。
1. 从一份 C# 源码看 S7-200 SMART 上位机通讯的骨架
很多做产线集成的工程师第一次拿到"S7200SmartTest_C#源码_西门子_源码"这类工程时,第一反应是打开 Visual Studio 按 F5,结果发现连不上 PLC,或者读回来的字节全是 0。问题往往不在代码本身,而在于没搞清楚这套源码背后的通讯模型:它本质是一个 C# 上位机,通过以太网 ISO-on-TCP(也就是常说的 S7 通讯)或串口 PPI/自由口,去读写西门子 S7-200 SMART 的 V 区、M 区、I/Q 区。标题里的三个关键词——S7200SmartTest、C#、西门子——分别对应测试工程、实现语言和目标设备,缺一个都跑不起来。
这类源码适合谁?一是刚接触西门子 PLC 的 C# 上位机开发者,想找一个能跑通的最小闭环;二是做设备集成的工程师,需要把 SMART 200 的数据接到 MES 或自研监控里;三是想研究 S7 协议报文结构的人。它解决的核心问题是:不依赖西门子官方 WinCC 或组态软件,用纯 C# 代码完成对 PLC 寄存器的读写。下面按"协议原理 → 环境搭建 → 读写实现 → 排错与进阶"的顺序,把这份源码里真正值钱的部分拆开讲。
2. S7-200 SMART 的通讯协议与 C# 侧选型
2.1 ISO-on-TCP 与 PPI 两条路怎么选
S7-200 SMART 的 CPU 自带以太网口,主流做法是走 ISO-on-TCP(RFC1006),端口 102。C# 侧不需要装西门子的 DLL,直接用TcpClient连上去,按 S7 协议组包即可。另一条路是串口 PPI,通过 RS485 转 USB 连 PLC 的 PORT0/PORT1,适合老设备或没有网口的场景。选型上,如果现场有交换机、PLC 固件支持以太网,优先 ISO-on-TCP,因为速率高、布线简单;如果是单机调试或 PLC 已被占用网口,才考虑 PPI。
| 方式 | 物理层 | 端口 | C# 依赖 | 适用场景 |
|---|---|---|---|---|
| ISO-on-TCP | 以太网 | 102 | TcpClient | 产线组网、多设备 |
| PPI | RS485 | 串口 | SerialPort | 单机、老设备 |
| 自由口 | RS485 | 串口 | SerialPort | 自定义协议对接 |
2.2 S7 协议报文的关键字段
ISO-on-TCP 上跑的是 S7 Communication,一次读操作大致分四段:COTP 连接请求、S7 通讯建立、读/写请求、响应解析。C# 源码里通常封装成一个S7Client类,核心是构造 TPKT + COTP + S7 PDU。以读 V 区为例,请求报文里要指定区域号(V 区是 0x84)、起始地址、读取长度。地址换算是个坑:S7-200 SMART 的 V 区按字节编址,但报文里要写成(字节偏移 << 3),也就是按位寻址。比如读 VB100 开始的 10 个字节,偏移量是 100*8=800。
// 构造读 V 区的 S7 请求参数部分(简化示意) byte[] BuildReadRequest(int area, int dbNumber, int startByte, int length) { // area: V区=0x84, M区=0x83, I区=0x81, Q区=0x82 int bitOffset = startByte * 8; // 字节地址转位地址 var req = new byte[12]; req[0] = 0x12; // 变量规格 req[1] = 0x0A; // 后续长度 req[2] = 0x10; // 请求长度 req[3] = 0x02; // 读命令 req[4] = (byte)(length >> 8); // 读取长度高字节 req[5] = (byte)(length & 0xFF); // 读取长度低字节 req[6] = (byte)(dbNumber >> 8); // DB 号,SMART 一般填 0 req[7] = (byte)(dbNumber & 0xFF); req[8] = (byte)area; // 区域标识 req[9] = (byte)((bitOffset >> 16) & 0xFF); req[10] = (byte)((bitOffset >> 8) & 0xFF); req[11] = (byte)(bitOffset & 0xFF); return req; }这段代码里area决定读哪个区,startByte是你在 STEP7-Micro/WIN SMART 里看到的字节地址,length是一次读的字节数。注意 S7-200 SMART 单次读的 PDU 有上限,一般不超过 222 字节,超了要分包。dbNumber对 SMART 200 基本填 0,因为它没有标准 DB 块的概念,V 区就相当于 DB1。
2.3 连接建立与心跳维护
TcpClient.Connect成功后,还要发 COTP 连接请求和 S7 通讯建立报文,收到正确响应才算握手完成。源码里常见做法是握手后启动一个定时器,每隔几秒发一次读请求当心跳,防止交换机或防火墙把空闲连接断掉。心跳不要用空包,读一个固定地址(比如 VB0 一个字节)最稳,既能保活又能顺带检测链路。
注意:S7-200 SMART 的并发连接数有限,一般最多 8 个 ISO-on-TCP 连接。上位机如果开多个线程同时连,容易触发连接拒绝,建议一个 PLC 复用一个连接,用队列串行化请求。
3. 用 C# 把 S7200SmartTest 源码跑起来的最小步骤
3.1 开发环境与 PLC 侧准备
先确认 PLC 固件和 IP。用 STEP7-Micro/WIN SMART 连上 PLC,在"系统块"里看以太网端口配置,记下 IP 和子网掩码。上位机电脑的 IP 要跟 PLC 同网段,比如 PLC 是 192.168.2.1,电脑就设 192.168.2.100。然后在"系统块 → 通信"里确认没有勾选限制连接。C# 侧用 Visual Studio 2019 或 2022,目标框架 .NET Framework 4.7.2 或 .NET 6/8 都行,源码如果是老工程,多半是 WinForms + .NET Framework。
# 先用 ping 确认链路通 ping 192.168.2.1 # 再用 telnet 测 102 端口是否开放(Windows 需先启用 telnet 客户端) telnet 192.168.2.1 102ping通只说明网络层没问题,telnet能连上才说明 102 端口没被防火墙拦。如果 telnet 卡住或拒绝,先查电脑防火墙和 PLC 是否被其他上位机占满连接。
3.2 关键参数配置表
源码里通常有一个配置文件或常量区,把下面这些参数填对,基本就能通。
| 参数 | 典型值 | 说明 |
|---|---|---|
| PLC IP | 192.168.2.1 | 与电脑同网段 |
| 端口 | 102 | ISO-on-TCP 固定 |
| 本地 TSAP | 0x0100 | 常见写法,部分源码用 0x0200 |
| 远端 TSAP | 0x0300 | 对应 PLC 侧 |
| 机架/槽位 | 0/1 | SMART 200 固定 |
| 读超时 | 2000 ms | 太短易误判断线 |
| 心跳间隔 | 5000 ms | 视交换机老化时间调整 |
TSAP 是容易出错的地方。S7-200 SMART 的 TSAP 跟 S7-1200/1500 不一样,常见远端是0x0300,本地0x0100。如果握手失败,先抓包看 COTP 响应里的 TSAP 是什么,再回填到代码里。
3.3 读写 V 区数据的完整调用
下面是一个读 VB100 起 10 字节、再写回 VB200 的调用示例,基于常见的S7Client封装。
var client = new S7Client(); int result = client.ConnectTo("192.168.2.1", 0, 1); // IP, 机架, 槽位 if (result != 0) { Console.WriteLine($"连接失败,错误码:{result}"); return; } byte[] buffer = new byte[10]; result = client.ReadArea(0x84, 0, 100, 10, buffer); // V区, DB0, 起始100, 长度10 if (result == 0) { Console.WriteLine("VB100-VB109: " + BitConverter.ToString(buffer)); } byte[] writeData = { 0x01, 0x02, 0x03 }; result = client.WriteArea(0x84, 0, 200, 3, writeData); // 写入 VB200 起 3 字节 Console.WriteLine(result == 0 ? "写入成功" : $"写入失败:{result}"); client.Disconnect();ConnectTo的第二个参数是机架号,S7-200 SMART 固定 0;第三个是槽位,固定 1。ReadArea的第一个参数 0x84 代表 V 区,如果要读 M 区就换 0x83,I 区 0x81,Q 区 0x82。返回值为 0 表示成功,非 0 是错误码,常见的有 0x05(地址越界)、0x0A(对象不存在)。写入时注意长度要和writeData实际长度一致,否则会写脏相邻地址。
4. 通讯失败时的排查顺序与常见坑
4.1 从错误码反推问题
源码里如果封装了错误码,先看返回值。连接阶段失败,多半是 IP、端口、TSAP 或防火墙;握手后读写失败,看是地址越界还是 PDU 长度超限。下面这张表是实践中高频出现的错误码对照。
| 错误码 | 含义 | 优先排查 |
|---|---|---|
| 0x01 | 硬件错误 | 网线、交换机 |
| 0x03 | 访问对象不允许 | 区域号写错 |
| 0x05 | 地址越界 | 起始地址+长度超范围 |
| 0x0A | 对象不存在 | DB 号或区域号错 |
| 超时无响应 | 链路断或 PLC 忙 | 心跳、连接数 |
4.2 地址换算与字节序
S7-200 SMART 是多字节大端序,读回来的 16 位整数要(high << 8) | low还原。浮点数更麻烦,需要按 IEEE 754 从 4 字节拼。源码里如果直接BitConverter.ToInt16在小端机器上会得到反的值,必须手动翻转。
// 大端序 16 位整数还原 short ToInt16BigEndian(byte[] data, int offset) { return (short)((data[offset] << 8) | data[offset + 1]); } // 大端序 32 位浮点还原 float ToFloatBigEndian(byte[] data, int offset) { byte[] tmp = new byte[4]; Array.Copy(data, offset, tmp, 0, 4); Array.Reverse(tmp); // 翻转为小端供 BitConverter 使用 return BitConverter.ToSingle(tmp, 0); }offset是数据在缓冲区里的起始下标,Array.Reverse那一步不能省,否则浮点值会完全错乱。如果现场读到的温度、压力值明显离谱,先查字节序。
4.3 多客户端与线程安全
热词里常出现"c# tcplistener 多客户端",放到这个场景就是多个上位机同时连一个 PLC。S7-200 SMART 的连接数有限,且同一连接上的请求必须串行。源码如果开了多线程直接调ReadArea,会出现响应错位——A 线程的响应被 B 线程读走。正确做法是给S7Client加锁,或者用请求队列。
private readonly object _lock = new object(); public byte[] SafeRead(int start, int len) { lock (_lock) // 保证同一连接串行 { byte[] buf = new byte[len]; int rc = _client.ReadArea(0x84, 0, start, len, buf); if (rc != 0) throw new Exception($"读失败:{rc}"); return buf; } }lock锁的是连接对象,不是方法。如果源码里每个线程 new 一个S7Client,那连接数会迅速耗尽,PLC 直接拒绝新连接。生产环境建议全局单例 + 队列。
提示:调试阶段可以用 Wireshark 过滤
tcp.port == 102,看 COTP 和 S7 PDU 的实际字节,比对着源码猜快得多。
5. 把 S7200SmartTest 源码改造成可复用的采集模块
5.1 从测试工程到生产代码的三处改动
原始测试源码通常把 IP、地址写死在按钮事件里,直接上产线会很难维护。第一处改动是把连接参数抽到配置文件,用appsettings.json或ini管理;第二处是把读写封装成带重试的PlcService,断线自动重连;第三处是加日志,记录每次请求的地址、耗时和错误码,方便事后追溯。
public bool ReadWithRetry(int start, int len, out byte[] data, int retry = 3) { data = null; for (int i = 0; i < retry; i++) { try { data = SafeRead(start, len); return true; } catch (Exception ex) { Log.Warn($"第{i + 1}次读失败:{ex.Message}"); Thread.Sleep(200); Reconnect(); // 尝试重建连接 } } return false; }retry控制重试次数,Thread.Sleep(200)给 PLC 一点恢复时间,Reconnect里重新走握手流程。注意重连不要无限制循环,配合外层熔断,避免把 PLC 连接打满。
5.2 批量采集时的地址合并技巧
如果一次要读 VB100、VB110、VB120 三个不连续块,与其发三次请求,不如合并成一次读 VB100 起 30 字节,再在内存里切片。S7 协议单次 PDU 上限内,合并请求能显著降低往返次数。判断标准是:相邻块间隔小于 32 字节就合并,否则分开读。
| 原始地址 | 长度 | 合并后起始 | 合并后长度 | 内存切片 |
|---|---|---|---|---|
| VB100 | 10 | VB100 | 30 | [0..9] |
| VB110 | 10 | VB100 | 30 | [10..19] |
| VB120 | 10 | VB100 | 30 | [20..29] |
切片时用Array.Copy或Span<byte>,别直接引用原缓冲区,否则下次读会覆盖。合并的代价是可能多读一些无用字节,但在 100ms 级的采集周期里,这点带宽可以忽略。
5.3 验证采集稳定性的一个土办法
改完之后怎么确认稳定?我一般会写一个控制台程序,循环读 1000 次,统计成功率和平均耗时,同时用 STEP7-Micro/WIN SMART 的状态表监视同一个地址,看两边数值是否一致。如果成功率低于 99%,先查心跳间隔和交换机;如果数值偶尔跳变,查字节序和切片偏移。这个办法不优雅,但能快速定位是链路问题还是代码问题。
本文还有配套的精品资源,点击获取